From 14fc9a85bbc109c7e7ade81a78d9f42520c8036d Mon Sep 17 00:00:00 2001 From: Jeff Shillitto Date: Fri, 1 Jun 2018 22:13:23 +1000 Subject: [PATCH 001/223] Fix install paths for headers and effects Fix additional relative paths --- include/DecklinkInput.h | 4 ++-- include/DecklinkOutput.h | 4 ++-- include/FrameMapper.h | 10 +++++----- include/PlayerBase.h | 2 +- include/Qt/AudioPlaybackThread.h | 6 +++--- include/Qt/PlayerPrivate.h | 12 ++++++------ include/Qt/VideoCacheThread.h | 6 +++--- include/Qt/VideoPlaybackThread.h | 4 ++-- include/QtPlayer.h | 6 +++--- include/RendererBase.h | 2 +- src/CMakeLists.txt | 6 +++--- 11 files changed, 31 insertions(+), 31 deletions(-) diff --git a/include/DecklinkInput.h b/include/DecklinkInput.h index 9964461d..cfd5b6b1 100644 --- a/include/DecklinkInput.h +++ b/include/DecklinkInput.h @@ -62,9 +62,9 @@ #include #include "DeckLinkAPI.h" -#include "../include/Frame.h" +#include "Frame.h" #include "CacheMemory.h" -#include "../include/OpenMPUtilities.h" +#include "OpenMPUtilities.h" /// Implementation of the Blackmagic Decklink API (used by the DecklinkReader) class DeckLinkInputDelegate : public IDeckLinkInputCallback diff --git a/include/DecklinkOutput.h b/include/DecklinkOutput.h index fb461438..ddb6e9bc 100644 --- a/include/DecklinkOutput.h +++ b/include/DecklinkOutput.h @@ -63,8 +63,8 @@ #include "DeckLinkAPI.h" #include "CacheMemory.h" -#include "../include/Frame.h" -#include "../include/OpenMPUtilities.h" +#include "Frame.h" +#include "OpenMPUtilities.h" enum OutputSignal { kOutputSignalPip = 0, diff --git a/include/FrameMapper.h b/include/FrameMapper.h index e70fdbc5..06511666 100644 --- a/include/FrameMapper.h +++ b/include/FrameMapper.h @@ -34,11 +34,11 @@ #include #include #include "CacheMemory.h" -#include "../include/ReaderBase.h" -#include "../include/Frame.h" -#include "../include/Fraction.h" -#include "../include/Exceptions.h" -#include "../include/KeyFrame.h" +#include "ReaderBase.h" +#include "Frame.h" +#include "Fraction.h" +#include "Exceptions.h" +#include "KeyFrame.h" // Include FFmpeg headers and macros diff --git a/include/PlayerBase.h b/include/PlayerBase.h index 80cdf708..ecc222a8 100644 --- a/include/PlayerBase.h +++ b/include/PlayerBase.h @@ -29,7 +29,7 @@ #define OPENSHOT_PLAYER_BASE_H #include -#include "../include/ReaderBase.h" +#include "ReaderBase.h" using namespace std; diff --git a/include/Qt/AudioPlaybackThread.h b/include/Qt/AudioPlaybackThread.h index 9f534749..68a2be3b 100644 --- a/include/Qt/AudioPlaybackThread.h +++ b/include/Qt/AudioPlaybackThread.h @@ -29,9 +29,9 @@ #ifndef OPENSHOT_AUDIO_PLAYBACK_THREAD_H #define OPENSHOT_AUDIO_PLAYBACK_THREAD_H -#include "../../include/ReaderBase.h" -#include "../../include/RendererBase.h" -#include "../../include/AudioReaderSource.h" +#include "../ReaderBase.h" +#include "../RendererBase.h" +#include "../AudioReaderSource.h" namespace openshot { diff --git a/include/Qt/PlayerPrivate.h b/include/Qt/PlayerPrivate.h index 3311dea9..f626fb99 100644 --- a/include/Qt/PlayerPrivate.h +++ b/include/Qt/PlayerPrivate.h @@ -29,12 +29,12 @@ #ifndef OPENSHOT_PLAYER_PRIVATE_H #define OPENSHOT_PLAYER_PRIVATE_H -#include "../../include/ReaderBase.h" -#include "../../include/RendererBase.h" -#include "../../include/AudioReaderSource.h" -#include "../../include/Qt/AudioPlaybackThread.h" -#include "../../include/Qt/VideoPlaybackThread.h" -#include "../../include/Qt/VideoCacheThread.h" +#include "../ReaderBase.h" +#include "../RendererBase.h" +#include "../AudioReaderSource.h" +#include "../Qt/AudioPlaybackThread.h" +#include "../Qt/VideoPlaybackThread.h" +#include "../Qt/VideoCacheThread.h" namespace openshot { diff --git a/include/Qt/VideoCacheThread.h b/include/Qt/VideoCacheThread.h index 3f781f62..4afb7ee5 100644 --- a/include/Qt/VideoCacheThread.h +++ b/include/Qt/VideoCacheThread.h @@ -28,9 +28,9 @@ #ifndef OPENSHOT_VIDEO_CACHE_THREAD_H #define OPENSHOT_VIDEO_CACHE_THREAD_H -#include "../../include/OpenMPUtilities.h" -#include "../../include/ReaderBase.h" -#include "../../include/RendererBase.h" +#include "../OpenMPUtilities.h" +#include "../ReaderBase.h" +#include "../RendererBase.h" namespace openshot { diff --git a/include/Qt/VideoPlaybackThread.h b/include/Qt/VideoPlaybackThread.h index 03ffe6d2..90dc3681 100644 --- a/include/Qt/VideoPlaybackThread.h +++ b/include/Qt/VideoPlaybackThread.h @@ -29,8 +29,8 @@ #ifndef OPENSHOT_VIDEO_PLAYBACK_THREAD_H #define OPENSHOT_VIDEO_PLAYBACK_THREAD_H -#include "../../include/ReaderBase.h" -#include "../../include/RendererBase.h" +#include "../ReaderBase.h" +#include "../RendererBase.h" namespace openshot { diff --git a/include/QtPlayer.h b/include/QtPlayer.h index 8774b886..354bbfc8 100644 --- a/include/QtPlayer.h +++ b/include/QtPlayer.h @@ -31,9 +31,9 @@ #include #include -#include "../include/PlayerBase.h" -#include "../include/Qt/PlayerPrivate.h" -#include "../include/RendererBase.h" +#include "PlayerBase.h" +#include "Qt/PlayerPrivate.h" +#include "RendererBase.h" using namespace std; diff --git a/include/RendererBase.h b/include/RendererBase.h index 3f1c0b1c..c71d664a 100644 --- a/include/RendererBase.h +++ b/include/RendererBase.h @@ -28,7 +28,7 @@ #ifndef OPENSHOT_RENDERER_BASE_H #define OPENSHOT_RENDERER_BASE_H -#include "../include/Frame.h" +#include "Frame.h" #include // for realloc #include diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0c4ff990..f4e8fba4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -334,9 +334,9 @@ INSTALL( TARGETS openshot LIBRARY DESTINATION ${LIB_INSTALL_DIR} COMPONENT library ) -INSTALL(FILES ${headers} - DESTINATION ${CMAKE_INSTALL_PREFIX}/include/libopenshot ) - +INSTALL(DIRECTORY ${CMAKE_SOURCE_DIR}/include/ + DESTINATION ${CMAKE_INSTALL_PREFIX}/include/libopenshot +FILES_MATCHING PATTERN "*.h") ############### CPACK PACKAGING ############## IF(MINGW) From c570868e0e6e3b2bd2026dc67658137c53ce6e32 Mon Sep 17 00:00:00 2001 From: "FeRD (Frank Dana)" Date: Tue, 5 Jun 2018 04:34:40 -0400 Subject: [PATCH 002/223] Update wipe-tests example to latest API The `src/effects/Openshot Wipe Tests.py` was no longer usable since the code was way out of date with respect to the current libopenshot APIs. This updates the code to execute properly. (Also: Output the frame numbers on a single line, instead of one per line, for readability.) --- src/examples/OpenShot Wipe Tests.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/examples/OpenShot Wipe Tests.py b/src/examples/OpenShot Wipe Tests.py index 4fb86c85..91b00917 100644 --- a/src/examples/OpenShot Wipe Tests.py +++ b/src/examples/OpenShot Wipe Tests.py @@ -1,16 +1,17 @@ import openshot -# Create a empty clip -t = openshot.Timeline(720, 480, openshot.Fraction(24,1), 44100, 2) +# Create an empty timeline +t = openshot.Timeline(720, 480, openshot.Fraction(24,1), 44100, 2, openshot.LAYOUT_STEREO) +t.Open() # lower layer -lower = openshot.ImageReader("/home/jonathan/apps/libopenshot/src/examples/back.png") +lower = openshot.QtImageReader("back.png") c1 = openshot.Clip(lower) c1.Layer(1) t.AddClip(c1) # higher layer -higher = openshot.ImageReader("/home/jonathan/apps/libopenshot/src/examples/front3.png") +higher = openshot.QtImageReader("front3.png") c2 = openshot.Clip(higher) c2.Layer(2) #c2.alpha = openshot.Keyframe(0.5) @@ -18,25 +19,26 @@ t.AddClip(c2) # Wipe / Transition brightness = openshot.Keyframe() -brightness.AddPoint(1, 100.0, openshot.BEZIER) -brightness.AddPoint(24, -100.0, openshot.BEZIER) +brightness.AddPoint(1, 1.0, openshot.BEZIER) +brightness.AddPoint(24, -1.0, openshot.BEZIER) contrast = openshot.Keyframe() contrast.AddPoint(1, 20.0, openshot.BEZIER) contrast.AddPoint(24, 20.0, openshot.BEZIER) -e = openshot.Wipe("/home/jonathan/apps/libopenshot/src/examples/mask.png", brightness, contrast) +reader = openshot.QtImageReader("mask.png") +e = openshot.Mask(reader, brightness, contrast) e.Layer(2) e.End(60) t.AddEffect(e) -e1 = openshot.Wipe("/home/jonathan/apps/libopenshot/src/examples/mask2.png", brightness, contrast) +reader1 = openshot.QtImageReader("mask2.png") +e1 = openshot.Mask(reader1, brightness, contrast) e1.Layer(2) e1.Order(2) e1.End(60) #t.AddEffect(e1) - for n in range(1,25): - print n + print(n, end=" ", flush=1) t.GetFrame(n).Save("%s.png" % n, 1.0) From da07ff4818b74d7292ea4801ca92d25a8c998702 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Wed, 20 Jun 2018 14:07:55 -0500 Subject: [PATCH 003/223] Fix cmake module lookup for libopenshot-audio, to always try the ENV variable first (#123) --- cmake/Modules/FindOpenShotAudio.cmake | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cmake/Modules/FindOpenShotAudio.cmake b/cmake/Modules/FindOpenShotAudio.cmake index 75d8a337..1de4529b 100644 --- a/cmake/Modules/FindOpenShotAudio.cmake +++ b/cmake/Modules/FindOpenShotAudio.cmake @@ -24,7 +24,7 @@ FOREACH(HEADER_PATH ${JUCE_HEADER_FILES}) get_filename_component(HEADER_DIRECTORY ${HEADER_PATH} PATH ) - + # Append each directory into the HEADER_DIRECTORIES list LIST(APPEND HEADER_DIRECTORIES ${HEADER_DIRECTORY}) ENDFOREACH(HEADER_PATH) @@ -32,7 +32,12 @@ ENDFOREACH(HEADER_PATH) # Remove duplicates from the header directories list LIST(REMOVE_DUPLICATES HEADER_DIRECTORIES) -# Find the libopenshot-audio.so / libopenshot-audio.dll library +# Find the libopenshot-audio.so (check env var first) +find_library(LIBOPENSHOT_AUDIO_LIBRARY + NAMES libopenshot-audio openshot-audio + PATHS $ENV{LIBOPENSHOT_AUDIO_DIR}/lib/ NO_DEFAULT_PATH) + +# Find the libopenshot-audio.so / libopenshot-audio.dll library (fallback) find_library(LIBOPENSHOT_AUDIO_LIBRARY NAMES libopenshot-audio openshot-audio HINTS $ENV{LIBOPENSHOT_AUDIO_DIR}/lib/ From 694ee7c24ec21bd71583f5e3c554463cd9233884 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Thu, 21 Jun 2018 02:44:08 -0500 Subject: [PATCH 004/223] New experimental protections for FFmpegReader::GetFrame, and better frame ordering. Thanks @PeterM! (#125) --- src/FFmpegReader.cpp | 90 ++++++++++++++++++++++---------------------- src/Timeline.cpp | 2 +- 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 68c606f7..3a19f53a 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -450,55 +450,55 @@ std::shared_ptr FFmpegReader::GetFrame(int64_t requested_frame) } else { - // Create a scoped lock, allowing only a single thread to run the following code at one time - const GenericScopedLock lock(getFrameCriticalSection); + #pragma omp critical (ReadStream) + { + // Check the cache a 2nd time (due to a potential previous lock) + if (has_missing_frames) + CheckMissingFrame(requested_frame); + frame = final_cache.GetFrame(requested_frame); + if (frame) { + // Debug output + ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetFrame", "returned cached frame on 2nd look", requested_frame, "", -1, "", -1, "", -1, "", -1, "", -1); - // Check the cache a 2nd time (due to a potential previous lock) - if (has_missing_frames) - CheckMissingFrame(requested_frame); - frame = final_cache.GetFrame(requested_frame); - if (frame) { - // Debug output - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetFrame", "returned cached frame on 2nd look", requested_frame, "", -1, "", -1, "", -1, "", -1, "", -1); - - // Return the cached frame - return frame; - } - - // Frame is not in cache - // Reset seek count - seek_count = 0; - - // Check for first frame (always need to get frame 1 before other frames, to correctly calculate offsets) - if (last_frame == 0 && requested_frame != 1) - // Get first frame - ReadStream(1); - - // Are we within X frames of the requested frame? - int64_t diff = requested_frame - last_frame; - if (diff >= 1 && diff <= 20) - { - // Continue walking the stream - return ReadStream(requested_frame); - } - else - { - // Greater than 30 frames away, or backwards, we need to seek to the nearest key frame - if (enable_seek) - // Only seek if enabled - Seek(requested_frame); - - else if (!enable_seek && diff < 0) - { - // Start over, since we can't seek, and the requested frame is smaller than our position - Close(); - Open(); + // Return the cached frame } + else { + // Frame is not in cache + // Reset seek count + seek_count = 0; - // Then continue walking the stream - return ReadStream(requested_frame); - } + // Check for first frame (always need to get frame 1 before other frames, to correctly calculate offsets) + if (last_frame == 0 && requested_frame != 1) + // Get first frame + ReadStream(1); + // Are we within X frames of the requested frame? + int64_t diff = requested_frame - last_frame; + if (diff >= 1 && diff <= 20) + { + // Continue walking the stream + frame = ReadStream(requested_frame); + } + else + { + // Greater than 30 frames away, or backwards, we need to seek to the nearest key frame + if (enable_seek) + // Only seek if enabled + Seek(requested_frame); + + else if (!enable_seek && diff < 0) + { + // Start over, since we can't seek, and the requested frame is smaller than our position + Close(); + Open(); + } + + // Then continue walking the stream + frame = ReadStream(requested_frame); + } + } + } //omp critical + return frame; } } diff --git a/src/Timeline.cpp b/src/Timeline.cpp index 80e92503..d934947e 100644 --- a/src/Timeline.cpp +++ b/src/Timeline.cpp @@ -791,7 +791,7 @@ std::shared_ptr Timeline::GetFrame(int64_t requested_frame) ZmqLogger::Instance()->AppendDebugMethod("Timeline::GetFrame (Add frame to cache)", "frame_number", frame_number, "info.width", info.width, "info.height", info.height, "", -1, "", -1, "", -1); // Set frame # on mapped frame - #pragma omp critical (T_GetFrame) + #pragma omp ordered { new_frame->SetFrameNumber(frame_number); From 9ae61e50b0a4387b1a79544e6631100467dae1cf Mon Sep 17 00:00:00 2001 From: Richard Alloway Date: Fri, 22 Jun 2018 00:41:36 -0400 Subject: [PATCH 005/223] Fix logic for 3 of the 4 blur parameters (#118) --- src/effects/Blur.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/effects/Blur.cpp b/src/effects/Blur.cpp index b54929c7..6eafc2a1 100644 --- a/src/effects/Blur.cpp +++ b/src/effects/Blur.cpp @@ -302,11 +302,11 @@ void Blur::SetJsonValue(Json::Value root) { // Set data from Json (if key is found) if (!root["horizontal_radius"].isNull()) horizontal_radius.SetJsonValue(root["horizontal_radius"]); - else if (!root["vertical_radius"].isNull()) + if (!root["vertical_radius"].isNull()) vertical_radius.SetJsonValue(root["vertical_radius"]); - else if (!root["sigma"].isNull()) + if (!root["sigma"].isNull()) sigma.SetJsonValue(root["sigma"]); - else if (!root["iterations"].isNull()) + if (!root["iterations"].isNull()) iterations.SetJsonValue(root["iterations"]); } From 2f45a4eb3800f094a5623e40b854c64b63a21404 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Wed, 27 Jun 2018 01:35:38 -0500 Subject: [PATCH 006/223] Adding new audio mixing enum, to allow for no automatic audio mixing, average mixing (where all overlapping clips average to 100% audio), or reduce mixing (where clips overlapping clips are all reduced by a constant value to reduce popping). (#131) --- include/Clip.h | 1 + include/Enums.h | 8 ++++++++ src/Clip.cpp | 10 ++++++++++ src/Timeline.cpp | 25 +++++++++++++++++++++---- 4 files changed, 40 insertions(+), 4 deletions(-) diff --git a/include/Clip.h b/include/Clip.h index ea54162f..f30844b2 100644 --- a/include/Clip.h +++ b/include/Clip.h @@ -155,6 +155,7 @@ namespace openshot { ScaleType scale; ///< The scale determines how a clip should be resized to fit it's parent AnchorType anchor; ///< The anchor determines what parent a clip should snap to FrameDisplayType display; ///< The format to display the frame number (if any) + VolumeMixType mixing; ///< What strategy should be followed when mixing audio with other clips /// Default Constructor Clip(); diff --git a/include/Enums.h b/include/Enums.h index 8e994a1b..fb91f1fa 100644 --- a/include/Enums.h +++ b/include/Enums.h @@ -69,5 +69,13 @@ namespace openshot FRAME_DISPLAY_TIMELINE, ///< Display the timeline's frame number FRAME_DISPLAY_BOTH ///< Display both the clip's and timeline's frame number }; + + /// This enumeration determines the strategy when mixing audio with other clips. + enum VolumeMixType + { + VOLUME_MIX_NONE, ///< Do not apply any volume mixing adjustments. Just add the samples together. + VOLUME_MIX_AVERAGE, ///< Evenly divide the overlapping clips volume keyframes, so that the sum does not exceed 100% + VOLUME_MIX_REDUCE ///< Reduce volume by about %25, and then mix (louder, but could cause pops if the sum exceeds 100%) + }; } #endif diff --git a/src/Clip.cpp b/src/Clip.cpp index 007b0a59..913fd71f 100644 --- a/src/Clip.cpp +++ b/src/Clip.cpp @@ -41,6 +41,7 @@ void Clip::init_settings() scale = SCALE_FIT; anchor = ANCHOR_CANVAS; display = FRAME_DISPLAY_NONE; + mixing = VOLUME_MIX_NONE; waveform = false; previous_properties = ""; @@ -694,6 +695,7 @@ string Clip::PropertiesJSON(int64_t requested_frame) { root["gravity"] = add_property_json("Gravity", gravity, "int", "", NULL, 0, 8, false, requested_frame); root["scale"] = add_property_json("Scale", scale, "int", "", NULL, 0, 3, false, requested_frame); root["display"] = add_property_json("Frame Number", display, "int", "", NULL, 0, 3, false, requested_frame); + root["mixing"] = add_property_json("Volume Mixing", mixing, "int", "", NULL, 0, 2, false, requested_frame); root["waveform"] = add_property_json("Waveform", waveform, "int", "", NULL, 0, 1, false, requested_frame); // Add gravity choices (dropdown style) @@ -719,6 +721,11 @@ string Clip::PropertiesJSON(int64_t requested_frame) { root["display"]["choices"].append(add_property_choice_json("Timeline", FRAME_DISPLAY_TIMELINE, display)); root["display"]["choices"].append(add_property_choice_json("Both", FRAME_DISPLAY_BOTH, display)); + // Add volume mixing choices (dropdown style) + root["mixing"]["choices"].append(add_property_choice_json("None", VOLUME_MIX_NONE, mixing)); + root["mixing"]["choices"].append(add_property_choice_json("Average", VOLUME_MIX_AVERAGE, mixing)); + root["mixing"]["choices"].append(add_property_choice_json("Reduce", VOLUME_MIX_REDUCE, mixing)); + // Add waveform choices (dropdown style) root["waveform"]["choices"].append(add_property_choice_json("Yes", true, waveform)); root["waveform"]["choices"].append(add_property_choice_json("No", false, waveform)); @@ -758,6 +765,7 @@ Json::Value Clip::JsonValue() { root["scale"] = scale; root["anchor"] = anchor; root["display"] = display; + root["mixing"] = mixing; root["waveform"] = waveform; root["scale_x"] = scale_x.JsonValue(); root["scale_y"] = scale_y.JsonValue(); @@ -844,6 +852,8 @@ void Clip::SetJsonValue(Json::Value root) { anchor = (AnchorType) root["anchor"].asInt(); if (!root["display"].isNull()) display = (FrameDisplayType) root["display"].asInt(); + if (!root["mixing"].isNull()) + mixing = (VolumeMixType) root["mixing"].asInt(); if (!root["waveform"].isNull()) waveform = root["waveform"].asBool(); if (!root["scale_x"].isNull()) diff --git a/src/Timeline.cpp b/src/Timeline.cpp index d934947e..d042aeeb 100644 --- a/src/Timeline.cpp +++ b/src/Timeline.cpp @@ -294,11 +294,24 @@ void Timeline::add_layer(std::shared_ptr new_frame, Clip* source_clip, in if (source_frame->GetAudioChannelsCount() == info.channels && source_clip->has_audio.GetInt(clip_frame_number) != 0) for (int channel = 0; channel < source_frame->GetAudioChannelsCount(); channel++) { - float previous_volume = source_clip->volume.GetValue(clip_frame_number - 1) / fmaxf(max_volume, 1.0); // previous frame's percentage of volume (0 to 1) - float volume = source_clip->volume.GetValue(clip_frame_number) / fmaxf(max_volume, 1.0); // percentage of volume (0 to 1) + // Get volume from previous frame and this frame + float previous_volume = source_clip->volume.GetValue(clip_frame_number - 1); + float volume = source_clip->volume.GetValue(clip_frame_number); int channel_filter = source_clip->channel_filter.GetInt(clip_frame_number); // optional channel to filter (if not -1) int channel_mapping = source_clip->channel_mapping.GetInt(clip_frame_number); // optional channel to map this channel to (if not -1) + // Apply volume mixing strategy + if (source_clip->mixing == VOLUME_MIX_AVERAGE && max_volume > 1.0) { + // Don't allow this clip to exceed 100% (divide volume equally between all overlapping clips with volume + previous_volume = previous_volume / max_volume; + volume = volume / max_volume; + } + else if (source_clip->mixing == VOLUME_MIX_REDUCE && max_volume > 1.0) { + // Reduce clip volume by a bit, hoping it will prevent exceeding 100% (but it is very possible it will) + previous_volume = previous_volume * 0.77; + volume = volume * 0.77; + } + // If channel filter enabled, check for correct channel (and skip non-matching channels) if (channel_filter != -1 && channel_filter != channel) continue; // skip to next channel @@ -760,14 +773,18 @@ std::shared_ptr Timeline::GetFrame(int64_t requested_frame) long nearby_clip_start_frame = (nearby_clip->Start() * info.fps.ToDouble()) + 1; long nearby_clip_frame_number = frame_number - nearby_clip_start_position + nearby_clip_start_frame; + // Determine if top clip if (clip->Id() != nearby_clip->Id() && clip->Layer() == nearby_clip->Layer() && nearby_clip_start_position <= frame_number && nearby_clip_end_position >= frame_number && nearby_clip_start_position > clip_start_position && is_top_clip == true) { is_top_clip = false; } - if (nearby_clip_start_position <= frame_number && nearby_clip_end_position >= frame_number) { - max_volume += nearby_clip->volume.GetValue(nearby_clip_frame_number); + // Determine max volume of overlapping clips + if (nearby_clip->Reader() && nearby_clip->Reader()->info.has_audio && + nearby_clip->has_audio.GetInt(nearby_clip_frame_number) != 0 && + nearby_clip_start_position <= frame_number && nearby_clip_end_position >= frame_number) { + max_volume += nearby_clip->volume.GetValue(nearby_clip_frame_number); } } From 1de33a2e1c5c4f899fbc55db85e0129db08a0846 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sat, 30 Jun 2018 00:19:56 -0500 Subject: [PATCH 007/223] Revert: Update Version.h --- include/Version.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/Version.h b/include/Version.h index cd72ab20..971d5cfe 100644 --- a/include/Version.h +++ b/include/Version.h @@ -35,9 +35,9 @@ #endif #define OPENSHOT_VERSION_MAJOR 0; /// Major version number is incremented when huge features are added or improved. -#define OPENSHOT_VERSION_MINOR 1; /// Minor version is incremented when smaller (but still very important) improvements are added. -#define OPENSHOT_VERSION_BUILD 9; /// Build number is incremented when minor bug fixes and less important improvements are added. -#define OPENSHOT_VERSION_SO 14; /// Shared object version number. This increments any time the API and ABI changes (so old apps will no longer link) +#define OPENSHOT_VERSION_MINOR 2; /// Minor version is incremented when smaller (but still very important) improvements are added. +#define OPENSHOT_VERSION_BUILD 0; /// Build number is incremented when minor bug fixes and less important improvements are added. +#define OPENSHOT_VERSION_SO 15; /// Shared object version number. This increments any time the API and ABI changes (so old apps will no longer link) #define OPENSHOT_VERSION_MAJOR_MINOR STRINGIZE(OPENSHOT_VERSION_MAJOR) "." STRINGIZE(OPENSHOT_VERSION_MINOR); /// A string of the "Major.Minor" version #define OPENSHOT_VERSION_ALL STRINGIZE(OPENSHOT_VERSION_MAJOR) "." STRINGIZE(OPENSHOT_VERSION_MINOR) "." STRINGIZE(OPENSHOT_VERSION_BUILD); /// A string of the entire version "Major.Minor.Build" From a92ed9fb0395c74bd1fd4ef1aab61e47a15dd5c8 Mon Sep 17 00:00:00 2001 From: Jeff Shillitto Date: Mon, 16 Jul 2018 21:18:35 +1000 Subject: [PATCH 008/223] Fix bug with FFmpeg > 3.2 flushing frames Final frames stored in buffer need to be flushed/drained so that they can be written to the video file. Credit to Peter for the update. --- src/FFmpegWriter.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index 4416040a..ede07a43 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -590,6 +590,23 @@ void FFmpegWriter::flush_encoders() // Encode video packet (latest version of FFmpeg) error_code = avcodec_send_frame(video_codec, NULL); got_packet = 0; + while (error_code >= 0) { + error_code = avcodec_receive_packet(video_codec, &pkt); + if (error_code == AVERROR(EAGAIN)|| error_code == AVERROR_EOF) { + got_packet = 0; + // Write packet + avcodec_flush_buffers(video_codec); + break; + } + if (pkt.pts != AV_NOPTS_VALUE) + pkt.pts = av_rescale_q(pkt.pts, video_codec->time_base, video_st->time_base); + if (pkt.dts != AV_NOPTS_VALUE) + pkt.dts = av_rescale_q(pkt.dts, video_codec->time_base, video_st->time_base); + if (pkt.duration > 0) + pkt.duration = av_rescale_q(pkt.duration, video_codec->time_base, video_st->time_base); + pkt.stream_index = video_st->index; + error_code = av_interleaved_write_frame(oc, &pkt); + } } #else From 93c1e2eb4910229cde0de8fb130e42e34c325bee Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Wed, 25 Jul 2018 02:24:01 -0500 Subject: [PATCH 009/223] Fixing bitrate calculation (to be in bytes instead of bits), and adding in FPS detection for files which don't have valid FPS. In those cases (streaming files for example), we iterate through all packets, and average the # of frames, duration, bit rate, etc... Not idealy, but a better fallback. --- src/FFmpegReader.cpp | 112 ++++++++++++++++--------------------------- 1 file changed, 41 insertions(+), 71 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 4d435f37..e1e50594 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -339,19 +339,21 @@ void FFmpegReader::UpdateAudioInfo() void FFmpegReader::UpdateVideoInfo() { + if (check_fps) + // Already initialized all the video metadata, no reason to do it again + return; + // Set values of FileInfo struct info.has_video = true; info.file_size = pFormatCtx->pb ? avio_size(pFormatCtx->pb) : -1; info.height = AV_GET_CODEC_ATTRIBUTES(pStream, pCodecCtx)->height; info.width = AV_GET_CODEC_ATTRIBUTES(pStream, pCodecCtx)->width; info.vcodec = pCodecCtx->codec->name; - info.video_bit_rate = pFormatCtx->bit_rate; - if (!check_fps) - { - // set frames per second (fps) - info.fps.num = pStream->avg_frame_rate.num; - info.fps.den = pStream->avg_frame_rate.den; - } + info.video_bit_rate = (pFormatCtx->bit_rate / 8); + + // set frames per second (fps) + info.fps.num = pStream->avg_frame_rate.num; + info.fps.den = pStream->avg_frame_rate.den; if (pStream->sample_aspect_ratio.num != 0) { @@ -415,16 +417,22 @@ void FFmpegReader::UpdateVideoInfo() } // Override an invalid framerate - if (info.fps.ToFloat() > 240.0f || (info.fps.num == 0 || info.fps.den == 0)) - { - // Set a few important default video settings (so audio can be divided into frames) - info.fps.num = 24; - info.fps.den = 1; - info.video_timebase.num = 1; - info.video_timebase.den = 24; + if (info.fps.ToFloat() > 240.0f || (info.fps.num == 0 || info.fps.den == 0)) { + // Calculate FPS, duration, video bit rate, and video length manually + // by scanning through all the video stream packets + CheckFPS(); - // Calculate number of frames - info.video_length = round(info.duration * info.fps.ToDouble()); + // If still an invalid FPS detected, just default to 24 FPS + // Set a few important default video settings (so audio can be divided into frames) + if (info.fps.ToFloat() > 240.0f || (info.fps.num == 0 || info.fps.den == 0)) { + info.fps.num = 24; + info.fps.den = 1; + info.video_timebase.num = 1; + info.video_timebase.den = 24; + + // Calculate number of frames + info.video_length = round(info.duration * info.fps.ToDouble()); + } } // Add video metadata (if any) @@ -1857,16 +1865,12 @@ void FFmpegReader::CheckWorkingFrames(bool end_of_stream, int64_t requested_fram void FFmpegReader::CheckFPS() { check_fps = true; - AV_ALLOCATE_IMAGE(pFrame, AV_GET_CODEC_PIXEL_FORMAT(pStream, pCodecCtx), info.width, info.height); - int first_second_counter = 0; int second_second_counter = 0; int third_second_counter = 0; int forth_second_counter = 0; int fifth_second_counter = 0; - - int iterations = 0; - int threshold = 500; + int frames_detected = 0; // Loop through the stream while (true) @@ -1908,63 +1912,29 @@ void FFmpegReader::CheckFPS() forth_second_counter++; else if (video_seconds > 4.0 && video_seconds <= 5.0) fifth_second_counter++; - else - // Too far - break; + + // Increment counters + frames_detected++; } } - - // Increment counters - iterations++; - - // Give up (if threshold exceeded) - if (iterations > threshold) - break; } // Double check that all counters have greater than zero (or give up) - if (second_second_counter == 0 || third_second_counter == 0 || forth_second_counter == 0 || fifth_second_counter == 0) - { - // Seek to frame 1 - Seek(1); + if (second_second_counter != 0 && third_second_counter != 0 && forth_second_counter != 0 && fifth_second_counter != 0) { + // Calculate average FPS + int sum_fps = second_second_counter + third_second_counter + forth_second_counter + fifth_second_counter; + int avg_fps = round(sum_fps / 4.0f); - // exit with no changes to FPS (not enough data to calculate) - return; + // Update FPS + info.fps = Fraction(avg_fps, 1); + + // Update Duration and Length + info.video_length = frames_detected; + info.duration = frames_detected / round(sum_fps / 4.0f); + + // Update video bit rate + info.video_bit_rate = info.file_size / info.duration; } - - int sum_fps = second_second_counter + third_second_counter + forth_second_counter + fifth_second_counter; - int avg_fps = round(sum_fps / 4.0f); - - // Sometimes the FPS is incorrectly detected by FFmpeg. If the 1st and 2nd seconds counters - // agree with each other, we are going to adjust the FPS of this reader instance. Otherwise, print - // a warning message. - - // Get diff from actual frame rate - double fps = info.fps.ToDouble(); - double diff = fps - double(avg_fps); - - // Is difference bigger than 1 frame? - if (diff <= -1 || diff >= 1) - { - // Compare to half the frame rate (the most common type of issue) - double half_fps = Fraction(info.fps.num / 2, info.fps.den).ToDouble(); - diff = half_fps - double(avg_fps); - - // Is difference bigger than 1 frame? - if (diff <= -1 || diff >= 1) - { - // Update FPS for this reader instance - info.fps = Fraction(avg_fps, 1); - } - else - { - // Update FPS for this reader instance (to 1/2 the original framerate) - info.fps = Fraction(info.fps.num / 2, info.fps.den); - } - } - - // Seek to frame 1 - Seek(1); } // Remove AVFrame from cache (and deallocate it's memory) From 435932f41569c70a5741ff65522b4a6876ab6599 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Thu, 2 Aug 2018 00:40:44 -0500 Subject: [PATCH 010/223] Fixing another issue where larger FPS files are incorrectly changed to a different FPS --- src/FFmpegReader.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index e1e50594..0b100050 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -421,18 +421,6 @@ void FFmpegReader::UpdateVideoInfo() // Calculate FPS, duration, video bit rate, and video length manually // by scanning through all the video stream packets CheckFPS(); - - // If still an invalid FPS detected, just default to 24 FPS - // Set a few important default video settings (so audio can be divided into frames) - if (info.fps.ToFloat() > 240.0f || (info.fps.num == 0 || info.fps.den == 0)) { - info.fps.num = 24; - info.fps.den = 1; - info.video_timebase.num = 1; - info.video_timebase.den = 24; - - // Calculate number of frames - info.video_length = round(info.duration * info.fps.ToDouble()); - } } // Add video metadata (if any) @@ -1934,6 +1922,18 @@ void FFmpegReader::CheckFPS() // Update video bit rate info.video_bit_rate = info.file_size / info.duration; + } else { + + // Too short to determine framerate, just default FPS + // Set a few important default video settings (so audio can be divided into frames) + info.fps.num = 30; + info.fps.den = 1; + info.video_timebase.num = info.fps.den; + info.video_timebase.den = info.fps.num; + + // Calculate number of frames + info.video_length = frames_detected; + info.duration = frames_detected / info.video_timebase.ToFloat(); } } From da01a2c4cb1b29b701bc014b74353081f464daa4 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Thu, 2 Aug 2018 00:42:14 -0500 Subject: [PATCH 011/223] Adding "reader" property for Mask effect, to allow the user to adjust the image or video used by the mask effect. --- include/effects/Mask.h | 1 + src/effects/Mask.cpp | 76 ++++++++++++++++++++++++------------------ 2 files changed, 45 insertions(+), 32 deletions(-) diff --git a/include/effects/Mask.h b/include/effects/Mask.h index ad1a6aab..ef707f5f 100644 --- a/include/effects/Mask.h +++ b/include/effects/Mask.h @@ -65,6 +65,7 @@ namespace openshot private: ReaderBase *reader; std::shared_ptr original_mask; + bool needs_refresh; /// Init effect settings void init_effect_details(); diff --git a/src/effects/Mask.cpp b/src/effects/Mask.cpp index 58f00d20..f8f34ac6 100644 --- a/src/effects/Mask.cpp +++ b/src/effects/Mask.cpp @@ -30,14 +30,14 @@ using namespace openshot; /// Blank constructor, useful when using Json to load the effect properties -Mask::Mask() : reader(NULL), replace_image(false) { +Mask::Mask() : reader(NULL), replace_image(false), needs_refresh(true) { // Init effect properties init_effect_details(); } // Default constructor Mask::Mask(ReaderBase *mask_reader, Keyframe mask_brightness, Keyframe mask_contrast) : - reader(mask_reader), brightness(mask_brightness), contrast(mask_contrast), replace_image(false) + reader(mask_reader), brightness(mask_brightness), contrast(mask_contrast), replace_image(false), needs_refresh(true) { // Init effect properties init_effect_details(); @@ -77,7 +77,7 @@ std::shared_ptr Mask::GetFrame(std::shared_ptr frame, int64_t fram // Get mask image (if missing or different size than frame image) #pragma omp critical (open_mask_reader) { - if (!original_mask || !reader->info.has_single_image || + if (!original_mask || !reader->info.has_single_image || needs_refresh || (original_mask && original_mask->size() != frame_image->size())) { // Only get mask if needed @@ -91,6 +91,9 @@ std::shared_ptr Mask::GetFrame(std::shared_ptr frame, int64_t fram } } + // Refresh no longer needed + needs_refresh = false; + // Get pixel arrays unsigned char *pixels = (unsigned char *) frame_image->bits(); unsigned char *mask_pixels = (unsigned char *) original_mask->bits(); @@ -206,47 +209,51 @@ void Mask::SetJsonValue(Json::Value root) { contrast.SetJsonValue(root["contrast"]); if (!root["reader"].isNull()) // does Json contain a reader? { - - if (!root["reader"]["type"].isNull()) // does the reader Json contain a 'type'? + #pragma omp critical (open_mask_reader) { - // Close previous reader (if any) - if (reader) + // This reader has changed, so refresh cached assets + needs_refresh = true; + + if (!root["reader"]["type"].isNull()) // does the reader Json contain a 'type'? { - // Close and delete existing reader (if any) - reader->Close(); - delete reader; - reader = NULL; - } + // Close previous reader (if any) + if (reader) { + // Close and delete existing reader (if any) + reader->Close(); + delete reader; + reader = NULL; + } - // Create new reader (and load properties) - string type = root["reader"]["type"].asString(); + // Create new reader (and load properties) + string type = root["reader"]["type"].asString(); - if (type == "FFmpegReader") { + if (type == "FFmpegReader") { - // Create new reader - reader = new FFmpegReader(root["reader"]["path"].asString()); - reader->SetJsonValue(root["reader"]); + // Create new reader + reader = new FFmpegReader(root["reader"]["path"].asString()); + reader->SetJsonValue(root["reader"]); -#ifdef USE_IMAGEMAGICK - } else if (type == "ImageReader") { + #ifdef USE_IMAGEMAGICK + } else if (type == "ImageReader") { - // Create new reader - reader = new ImageReader(root["reader"]["path"].asString()); - reader->SetJsonValue(root["reader"]); -#endif + // Create new reader + reader = new ImageReader(root["reader"]["path"].asString()); + reader->SetJsonValue(root["reader"]); + #endif - } else if (type == "QtImageReader") { + } else if (type == "QtImageReader") { - // Create new reader - reader = new QtImageReader(root["reader"]["path"].asString()); - reader->SetJsonValue(root["reader"]); + // Create new reader + reader = new QtImageReader(root["reader"]["path"].asString()); + reader->SetJsonValue(root["reader"]); - } else if (type == "ChunkReader") { + } else if (type == "ChunkReader") { - // Create new reader - reader = new ChunkReader(root["reader"]["path"].asString(), (ChunkVersion) root["reader"]["chunk_version"].asInt()); - reader->SetJsonValue(root["reader"]); + // Create new reader + reader = new ChunkReader(root["reader"]["path"].asString(), (ChunkVersion) root["reader"]["chunk_version"].asInt()); + reader->SetJsonValue(root["reader"]); + } } } @@ -275,6 +282,11 @@ string Mask::PropertiesJSON(int64_t requested_frame) { root["brightness"] = add_property_json("Brightness", brightness.GetValue(requested_frame), "float", "", &brightness, -1.0, 1.0, false, requested_frame); root["contrast"] = add_property_json("Contrast", contrast.GetValue(requested_frame), "float", "", &contrast, 0, 20, false, requested_frame); + if (reader) + root["reader"] = add_property_json("Source", 0.0, "reader", reader->Json(), NULL, 0, 1, false, requested_frame); + else + root["reader"] = add_property_json("Source", 0.0, "reader", "{}", NULL, 0, 1, false, requested_frame); + // Return formatted string return root.toStyledString(); } From 95abdcf66b7c471d40732d791c967f76b5f5f9c5 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sat, 11 Aug 2018 18:22:18 -0500 Subject: [PATCH 012/223] FFmpeg4 support. Compile warnings fixes. Credit goes to many people, including ferdnyc, peterM, and other awesome folks! --- cmake/Modules/FindFFmpeg.cmake | 70 ++++++++++++++++++++++++------- include/CrashHandler.h | 8 ++-- include/FFmpegUtilities.h | 76 +++++++++++++++++++++++++++++++++- include/FFmpegWriter.h | 4 +- include/Frame.h | 2 +- include/FrameMapper.h | 2 +- include/ZmqLogger.h | 12 +++++- src/Clip.cpp | 11 ++--- src/EffectInfo.cpp | 1 + src/FFmpegReader.cpp | 30 +++++++------- src/FFmpegWriter.cpp | 51 ++++++++++++++++------- src/FrameMapper.cpp | 14 +++---- src/Timeline.cpp | 22 +++++----- tests/ReaderBase_Tests.cpp | 4 +- 14 files changed, 228 insertions(+), 79 deletions(-) diff --git a/cmake/Modules/FindFFmpeg.cmake b/cmake/Modules/FindFFmpeg.cmake index 4af6cc93..45a27a9e 100644 --- a/cmake/Modules/FindFFmpeg.cmake +++ b/cmake/Modules/FindFFmpeg.cmake @@ -91,6 +91,20 @@ FIND_LIBRARY( AVRESAMPLE_LIBRARY avresample avresample-2 avresample-3 $ENV{FFMPEGDIR}/lib/ffmpeg/ $ENV{FFMPEGDIR}/bin/ ) +#FindSwresample +FIND_PATH( SWRESAMPLE_INCLUDE_DIR libswresample/swresample.h + PATHS /usr/include/ + /usr/include/ffmpeg/ + $ENV{FFMPEGDIR}/include/ + $ENV{FFMPEGDIR}/include/ffmpeg/ ) + +FIND_LIBRARY( SWRESAMPLE_LIBRARY swresample + PATHS /usr/lib/ + /usr/lib/ffmpeg/ + $ENV{FFMPEGDIR}/lib/ + $ENV{FFMPEGDIR}/lib/ffmpeg/ + $ENV{FFMPEGDIR}/bin/ ) + SET( FFMPEG_FOUND FALSE ) IF ( AVFORMAT_INCLUDE_DIR AND AVFORMAT_LIBRARY ) @@ -117,27 +131,51 @@ IF ( AVRESAMPLE_INCLUDE_DIR AND AVRESAMPLE_LIBRARY ) SET ( AVRESAMPLE_FOUND TRUE ) ENDIF ( AVRESAMPLE_INCLUDE_DIR AND AVRESAMPLE_LIBRARY ) -IF ( AVFORMAT_INCLUDE_DIR OR AVCODEC_INCLUDE_DIR OR AVUTIL_INCLUDE_DIR OR AVDEVICE_FOUND OR SWSCALE_FOUND OR AVRESAMPLE_FOUND ) +IF ( SWRESAMPLE_INCLUDE_DIR AND SWRESAMPLE_LIBRARY ) + SET ( SWRESAMPLE_FOUND TRUE ) +ENDIF ( SWRESAMPLE_INCLUDE_DIR AND SWRESAMPLE_LIBRARY ) + +IF ( AVFORMAT_INCLUDE_DIR OR AVCODEC_INCLUDE_DIR OR AVUTIL_INCLUDE_DIR OR AVDEVICE_FOUND OR SWSCALE_FOUND OR AVRESAMPLE_FOUND OR SWRESAMPLE_FOUND ) SET ( FFMPEG_FOUND TRUE ) - SET ( FFMPEG_INCLUDE_DIR - ${AVFORMAT_INCLUDE_DIR} - ${AVCODEC_INCLUDE_DIR} - ${AVUTIL_INCLUDE_DIR} - ${AVDEVICE_INCLUDE_DIR} - ${SWSCALE_INCLUDE_DIR} - ${AVRESAMPLE_INCLUDE_DIR} ) + IF ( SWRESAMPLE_FOUND ) + SET ( FFMPEG_INCLUDE_DIR + ${AVFORMAT_INCLUDE_DIR} + ${AVCODEC_INCLUDE_DIR} + ${AVUTIL_INCLUDE_DIR} + ${AVDEVICE_INCLUDE_DIR} + ${SWSCALE_INCLUDE_DIR} + ${AVRESAMPLE_INCLUDE_DIR} + ${SWRESAMPLE_INCLUDE_DIR} ) - SET ( FFMPEG_LIBRARIES - ${AVFORMAT_LIBRARY} - ${AVCODEC_LIBRARY} - ${AVUTIL_LIBRARY} - ${AVDEVICE_LIBRARY} - ${SWSCALE_LIBRARY} - ${AVRESAMPLE_LIBRARY} ) + SET ( FFMPEG_LIBRARIES + ${AVFORMAT_LIBRARY} + ${AVCODEC_LIBRARY} + ${AVUTIL_LIBRARY} + ${AVDEVICE_LIBRARY} + ${SWSCALE_LIBRARY} + ${AVRESAMPLE_LIBRARY} + ${SWRESAMPLE_LIBRARY} ) + ELSE () + SET ( FFMPEG_INCLUDE_DIR + ${AVFORMAT_INCLUDE_DIR} + ${AVCODEC_INCLUDE_DIR} + ${AVUTIL_INCLUDE_DIR} + ${AVDEVICE_INCLUDE_DIR} + ${SWSCALE_INCLUDE_DIR} + ${AVRESAMPLE_INCLUDE_DIR} ) + + SET ( FFMPEG_LIBRARIES + ${AVFORMAT_LIBRARY} + ${AVCODEC_LIBRARY} + ${AVUTIL_LIBRARY} + ${AVDEVICE_LIBRARY} + ${SWSCALE_LIBRARY} + ${AVRESAMPLE_LIBRARY} ) + ENDIF () -ENDIF ( AVFORMAT_INCLUDE_DIR OR AVCODEC_INCLUDE_DIR OR AVUTIL_INCLUDE_DIR OR AVDEVICE_FOUND OR SWSCALE_FOUND OR AVRESAMPLE_FOUND ) +ENDIF ( AVFORMAT_INCLUDE_DIR OR AVCODEC_INCLUDE_DIR OR AVUTIL_INCLUDE_DIR OR AVDEVICE_FOUND OR SWSCALE_FOUND OR AVRESAMPLE_FOUND OR SWRESAMPLE_FOUND ) MARK_AS_ADVANCED( FFMPEG_LIBRARY_DIR diff --git a/include/CrashHandler.h b/include/CrashHandler.h index e3a4bbe5..12c79a86 100644 --- a/include/CrashHandler.h +++ b/include/CrashHandler.h @@ -53,13 +53,15 @@ namespace openshot { class CrashHandler { private: /// Default constructor - CrashHandler(){}; // Don't allow user to create an instance of this singleton + CrashHandler(){return;}; // Don't allow user to create an instance of this singleton /// Default copy method - CrashHandler(CrashHandler const&){}; // Don't allow the user to copy this instance + //CrashHandler(CrashHandler const&){}; // Don't allow the user to copy this instance + CrashHandler(CrashHandler const&) = delete; // Don't allow the user to copy this instance /// Default assignment operator - CrashHandler & operator=(CrashHandler const&){}; // Don't allow the user to assign this instance + //CrashHandler & operator=(CrashHandler const&){}; // Don't allow the user to assign this instance + CrashHandler & operator=(CrashHandler const&) = delete; // Don't allow the user to assign this instance /// Private variable to keep track of singleton instance static CrashHandler *m_pInstance; diff --git a/include/FFmpegUtilities.h b/include/FFmpegUtilities.h index 578c6586..346da541 100644 --- a/include/FFmpegUtilities.h +++ b/include/FFmpegUtilities.h @@ -43,7 +43,15 @@ #include #include #include + // Change this to the first version swrescale works + #if (LIBAVFORMAT_VERSION_MAJOR >= 57) + #define USE_SW + #endif + #ifdef USE_SW + #include + #else #include + #endif #include #include #include @@ -106,7 +114,65 @@ #define PIX_FMT_YUV420P AV_PIX_FMT_YUV420P #endif - #if IS_FFMPEG_3_2 + #ifdef USE_SW + #define SWR_CONVERT(ctx, out, linesize, out_count, in, linesize2, in_count) \ + swr_convert(ctx, out, out_count, (const uint8_t **)in, in_count) + #define SWR_ALLOC() swr_alloc() + #define SWR_CLOSE(ctx) {} + #define SWR_FREE(ctx) swr_free(ctx) + #define SWR_INIT(ctx) swr_init(ctx) + #define SWRCONTEXT SwrContext + #else + #define SWR_CONVERT(ctx, out, linesize, out_count, in, linesize2, in_count) \ + avresample_convert(ctx, out, linesize, out_count, (uint8_t **)in, linesize2, in_count) + #define SWR_ALLOC() avresample_alloc_context() + #define SWR_CLOSE(ctx) avresample_close(ctx) + #define SWR_FREE(ctx) avresample_free(ctx) + #define SWR_INIT(ctx) avresample_open(ctx) + #define SWRCONTEXT AVAudioResampleContext + #endif + + + #if (LIBAVFORMAT_VERSION_MAJOR >= 58) + #define AV_REGISTER_ALL + #define AVCODEC_REGISTER_ALL + #define AV_FILENAME url + #define MY_INPUT_BUFFER_PADDING_SIZE AV_INPUT_BUFFER_PADDING_SIZE + #define AV_ALLOCATE_FRAME() av_frame_alloc() + #define AV_ALLOCATE_IMAGE(av_frame, pix_fmt, width, height) av_image_alloc(av_frame->data, av_frame->linesize, width, height, pix_fmt, 1) + #define AV_RESET_FRAME(av_frame) av_frame_unref(av_frame) + #define AV_FREE_FRAME(av_frame) av_frame_free(av_frame) + #define AV_FREE_PACKET(av_packet) av_packet_unref(av_packet) + #define AV_FREE_CONTEXT(av_context) avcodec_free_context(&av_context) + #define AV_GET_CODEC_TYPE(av_stream) av_stream->codecpar->codec_type + #define AV_FIND_DECODER_CODEC_ID(av_stream) av_stream->codecpar->codec_id + auto AV_GET_CODEC_CONTEXT = [](AVStream* av_stream, AVCodec* av_codec) { \ + AVCodecContext *context = avcodec_alloc_context3(av_codec); \ + avcodec_parameters_to_context(context, av_stream->codecpar); \ + return context; \ + }; + #define AV_GET_CODEC_PAR_CONTEXT(av_stream, av_codec) av_codec; + #define AV_GET_CODEC_FROM_STREAM(av_stream,codec_in) + #define AV_GET_CODEC_ATTRIBUTES(av_stream, av_context) av_stream->codecpar + #define AV_GET_CODEC_PIXEL_FORMAT(av_stream, av_context) (AVPixelFormat) av_stream->codecpar->format + #define AV_GET_SAMPLE_FORMAT(av_stream, av_context) av_stream->codecpar->format + #define AV_GET_IMAGE_SIZE(pix_fmt, width, height) av_image_get_buffer_size(pix_fmt, width, height, 1) + #define AV_COPY_PICTURE_DATA(av_frame, buffer, pix_fmt, width, height) av_image_fill_arrays(av_frame->data, av_frame->linesize, buffer, pix_fmt, width, height, 1) + #define AV_OUTPUT_CONTEXT(output_context, path) avformat_alloc_output_context2( output_context, NULL, NULL, path) + #define AV_OPTION_FIND(priv_data, name) av_opt_find(priv_data, name, NULL, 0, 0) + #define AV_OPTION_SET( av_stream, priv_data, name, value, avcodec) av_opt_set(priv_data, name, value, 0); avcodec_parameters_from_context(av_stream->codecpar, avcodec); + #define AV_FORMAT_NEW_STREAM(oc, st_codec, av_codec, av_st) av_st = avformat_new_stream(oc, NULL);\ + if (!av_st) \ + throw OutOfMemory("Could not allocate memory for the video stream.", path); \ + c = avcodec_alloc_context3(av_codec); \ + st_codec = c; \ + av_st->codecpar->codec_id = av_codec->id; + #define AV_COPY_PARAMS_FROM_CONTEXT(av_stream, av_codec) avcodec_parameters_from_context(av_stream->codecpar, av_codec); + #elif IS_FFMPEG_3_2 + #define AV_REGISTER_ALL av_register_all(); + #define AVCODEC_REGISTER_ALL avcodec_register_all(); + #define AV_FILENAME filename + #define MY_INPUT_BUFFER_PADDING_SIZE FF_INPUT_BUFFER_PADDING_SIZE #define AV_ALLOCATE_FRAME() av_frame_alloc() #define AV_ALLOCATE_IMAGE(av_frame, pix_fmt, width, height) av_image_alloc(av_frame->data, av_frame->linesize, width, height, pix_fmt, 1) #define AV_RESET_FRAME(av_frame) av_frame_unref(av_frame) @@ -138,6 +204,10 @@ av_st->codecpar->codec_id = av_codec->id; #define AV_COPY_PARAMS_FROM_CONTEXT(av_stream, av_codec) avcodec_parameters_from_context(av_stream->codecpar, av_codec); #elif LIBAVFORMAT_VERSION_MAJOR >= 55 + #define AV_REGISTER_ALL av_register_all(); + #define AVCODEC_REGISTER_ALL avcodec_register_all(); + #define AV_FILENAME filename + #define MY_INPUT_BUFFER_PADDING_SIZE FF_INPUT_BUFFER_PADDING_SIZE #define AV_ALLOCATE_FRAME() av_frame_alloc() #define AV_ALLOCATE_IMAGE(av_frame, pix_fmt, width, height) avpicture_alloc((AVPicture *) av_frame, pix_fmt, width, height) #define AV_RESET_FRAME(av_frame) av_frame_unref(av_frame) @@ -164,6 +234,10 @@ c = av_st->codec; #define AV_COPY_PARAMS_FROM_CONTEXT(av_stream, av_codec) #else + #define AV_REGISTER_ALL av_register_all(); + #define AVCODEC_REGISTER_ALL avcodec_register_all(); + #define AV_FILENAME filename + #define MY_INPUT_BUFFER_PADDING_SIZE FF_INPUT_BUFFER_PADDING_SIZE #define AV_ALLOCATE_FRAME() avcodec_alloc_frame() #define AV_ALLOCATE_IMAGE(av_frame, pix_fmt, width, height) avpicture_alloc((AVPicture *) av_frame, pix_fmt, width, height) #define AV_RESET_FRAME(av_frame) avcodec_get_frame_defaults(av_frame) diff --git a/include/FFmpegWriter.h b/include/FFmpegWriter.h index 8343002e..7eefacb7 100644 --- a/include/FFmpegWriter.h +++ b/include/FFmpegWriter.h @@ -174,8 +174,8 @@ namespace openshot int initial_audio_input_frame_size; int audio_input_position; int audio_encoder_buffer_size; - AVAudioResampleContext *avr; - AVAudioResampleContext *avr_planar; + SWRCONTEXT *avr; + SWRCONTEXT *avr_planar; /* Resample options */ int original_sample_rate; diff --git a/include/Frame.h b/include/Frame.h index a7ad509f..eba7f8bb 100644 --- a/include/Frame.h +++ b/include/Frame.h @@ -62,7 +62,7 @@ #include "AudioResampler.h" #include "Fraction.h" - +#pragma SWIG nowarn=362 using namespace std; namespace openshot diff --git a/include/FrameMapper.h b/include/FrameMapper.h index 06511666..216fe73f 100644 --- a/include/FrameMapper.h +++ b/include/FrameMapper.h @@ -146,7 +146,7 @@ namespace openshot ReaderBase *reader; // The source video reader CacheMemory final_cache; // Cache of actual Frame objects bool is_dirty; // When this is true, the next call to GetFrame will re-init the mapping - AVAudioResampleContext *avr; // Audio resampling context object + SWRCONTEXT *avr; // Audio resampling context object // Internal methods used by init void AddField(int64_t frame); diff --git a/include/ZmqLogger.h b/include/ZmqLogger.h index c134f2cf..e825ed0e 100644 --- a/include/ZmqLogger.h +++ b/include/ZmqLogger.h @@ -72,11 +72,19 @@ namespace openshot { /// Default constructor ZmqLogger(){}; // Don't allow user to create an instance of this singleton +#if __GNUC__ >=7 /// Default copy method - ZmqLogger(ZmqLogger const&){}; // Don't allow the user to copy this instance + ZmqLogger(ZmqLogger const&) = delete; // Don't allow the user to assign this instance /// Default assignment operator - ZmqLogger & operator=(ZmqLogger const&){}; // Don't allow the user to assign this instance + ZmqLogger & operator=(ZmqLogger const&) = delete; // Don't allow the user to assign this instance +#else + /// Default copy method + ZmqLogger(ZmqLogger const&) {}; // Don't allow the user to assign this instance + + /// Default assignment operator + ZmqLogger & operator=(ZmqLogger const&); // Don't allow the user to assign this instance +#endif /// Private variable to keep track of singleton instance static ZmqLogger * m_pInstance; diff --git a/src/Clip.cpp b/src/Clip.cpp index 913fd71f..63e77412 100644 --- a/src/Clip.cpp +++ b/src/Clip.cpp @@ -925,13 +925,14 @@ void Clip::SetJsonValue(Json::Value root) { if (!existing_effect["type"].isNull()) { // Create instance of effect - e = EffectInfo().CreateEffect(existing_effect["type"].asString()); + if (e = EffectInfo().CreateEffect(existing_effect["type"].asString())) { - // Load Json into Effect - e->SetJsonValue(existing_effect); + // Load Json into Effect + e->SetJsonValue(existing_effect); - // Add Effect to Timeline - AddEffect(e); + // Add Effect to Timeline + AddEffect(e); + } } } } diff --git a/src/EffectInfo.cpp b/src/EffectInfo.cpp index 23bc9d02..f9e4c409 100644 --- a/src/EffectInfo.cpp +++ b/src/EffectInfo.cpp @@ -82,6 +82,7 @@ EffectBase* EffectInfo::CreateEffect(string effect_type) { else if (effect_type == "Wave") return new Wave(); + return NULL; } // Generate Json::JsonValue for this object diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 0b100050..ceccbe23 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -40,8 +40,8 @@ FFmpegReader::FFmpegReader(string path) current_video_frame(0), has_missing_frames(false), num_packets_since_video_frame(0), num_checks_since_final(0), packet(NULL) { // Initialize FFMpeg, and register all formats and codecs - av_register_all(); - avcodec_register_all(); + AV_REGISTER_ALL + AVCODEC_REGISTER_ALL // Init cache working_cache.SetMaxBytesFromInfo(OPEN_MP_NUM_PROCESSORS * info.fps.ToDouble() * 2, info.width, info.height, info.sample_rate, info.channels); @@ -61,8 +61,8 @@ FFmpegReader::FFmpegReader(string path, bool inspect_reader) current_video_frame(0), has_missing_frames(false), num_packets_since_video_frame(0), num_checks_since_final(0), packet(NULL) { // Initialize FFMpeg, and register all formats and codecs - av_register_all(); - avcodec_register_all(); + AV_REGISTER_ALL + AVCODEC_REGISTER_ALL // Init cache working_cache.SetMaxBytesFromInfo(OPEN_MP_NUM_PROCESSORS * info.fps.ToDouble() * 2, info.width, info.height, info.sample_rate, info.channels); @@ -974,7 +974,7 @@ void FFmpegReader::ProcessAudioPacket(int64_t requested_frame, int64_t target_fr int data_size = 0; // re-initialize buffer size (it gets changed in the avcodec_decode_audio2 method call) - int buf_size = AVCODEC_MAX_AUDIO_FRAME_SIZE + FF_INPUT_BUFFER_PADDING_SIZE; + int buf_size = AVCODEC_MAX_AUDIO_FRAME_SIZE + MY_INPUT_BUFFER_PADDING_SIZE; #pragma omp critical (ProcessAudioPacket) { #if IS_FFMPEG_3_2 @@ -1079,7 +1079,7 @@ void FFmpegReader::ProcessAudioPacket(int64_t requested_frame, int64_t target_fr // Allocate audio buffer - int16_t *audio_buf = new int16_t[AVCODEC_MAX_AUDIO_FRAME_SIZE + FF_INPUT_BUFFER_PADDING_SIZE]; + int16_t *audio_buf = new int16_t[AVCODEC_MAX_AUDIO_FRAME_SIZE + MY_INPUT_BUFFER_PADDING_SIZE]; ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ProcessAudioPacket (ReSample)", "packet_samples", packet_samples, "info.channels", info.channels, "info.sample_rate", info.sample_rate, "aCodecCtx->sample_fmt", AV_GET_SAMPLE_FORMAT(aStream, aCodecCtx), "AV_SAMPLE_FMT_S16", AV_SAMPLE_FMT_S16, "", -1); @@ -1089,11 +1089,11 @@ void FFmpegReader::ProcessAudioPacket(int64_t requested_frame, int64_t target_fr audio_converted->nb_samples = audio_frame->nb_samples; av_samples_alloc(audio_converted->data, audio_converted->linesize, info.channels, audio_frame->nb_samples, AV_SAMPLE_FMT_S16, 0); - AVAudioResampleContext *avr = NULL; + SWRCONTEXT *avr = NULL; int nb_samples = 0; // setup resample context - avr = avresample_alloc_context(); + avr = SWR_ALLOC(); av_opt_set_int(avr, "in_channel_layout", AV_GET_CODEC_ATTRIBUTES(aStream, aCodecCtx)->channel_layout, 0); av_opt_set_int(avr, "out_channel_layout", AV_GET_CODEC_ATTRIBUTES(aStream, aCodecCtx)->channel_layout, 0); av_opt_set_int(avr, "in_sample_fmt", AV_GET_SAMPLE_FORMAT(aStream, aCodecCtx), 0); @@ -1102,10 +1102,10 @@ void FFmpegReader::ProcessAudioPacket(int64_t requested_frame, int64_t target_fr av_opt_set_int(avr, "out_sample_rate", info.sample_rate, 0); av_opt_set_int(avr, "in_channels", info.channels, 0); av_opt_set_int(avr, "out_channels", info.channels, 0); - int r = avresample_open(avr); + int r = SWR_INIT(avr); // Convert audio samples - nb_samples = avresample_convert(avr, // audio resample context + nb_samples = SWR_CONVERT(avr, // audio resample context audio_converted->data, // output data pointers audio_converted->linesize[0], // output plane size, in bytes. (0 if unknown) audio_converted->nb_samples, // maximum number of samples that the output buffer can hold @@ -1117,8 +1117,8 @@ void FFmpegReader::ProcessAudioPacket(int64_t requested_frame, int64_t target_fr memcpy(audio_buf, audio_converted->data[0], audio_converted->nb_samples * av_get_bytes_per_sample(AV_SAMPLE_FMT_S16) * info.channels); // Deallocate resample buffer - avresample_close(avr); - avresample_free(&avr); + SWR_CLOSE(avr); + SWR_FREE(&avr); avr = NULL; // Free AVFrames @@ -1344,7 +1344,7 @@ void FFmpegReader::Seek(int64_t requested_frame) { seek_target = ConvertFrameToVideoPTS(requested_frame - buffer_amount); if (av_seek_frame(pFormatCtx, info.video_stream_index, seek_target, AVSEEK_FLAG_BACKWARD) < 0) { - fprintf(stderr, "%s: error while seeking video stream\n", pFormatCtx->filename); + fprintf(stderr, "%s: error while seeking video stream\n", pFormatCtx->AV_FILENAME); } else { // VIDEO SEEK @@ -1358,7 +1358,7 @@ void FFmpegReader::Seek(int64_t requested_frame) { seek_target = ConvertFrameToAudioPTS(requested_frame - buffer_amount); if (av_seek_frame(pFormatCtx, info.audio_stream_index, seek_target, AVSEEK_FLAG_BACKWARD) < 0) { - fprintf(stderr, "%s: error while seeking audio stream\n", pFormatCtx->filename); + fprintf(stderr, "%s: error while seeking audio stream\n", pFormatCtx->AV_FILENAME); } else { // AUDIO SEEK @@ -1853,6 +1853,8 @@ void FFmpegReader::CheckWorkingFrames(bool end_of_stream, int64_t requested_fram void FFmpegReader::CheckFPS() { check_fps = true; + AV_ALLOCATE_IMAGE(pFrame, AV_GET_CODEC_PIXEL_FORMAT(pStream, pCodecCtx), info.width, info.height); + int first_second_counter = 0; int second_second_counter = 0; int third_second_counter = 0; diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index ede07a43..ed4fc3fb 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -46,7 +46,7 @@ FFmpegWriter::FFmpegWriter(string path) : info.has_video = false; // Initialize FFMpeg, and register all formats and codecs - av_register_all(); + AV_REGISTER_ALL // auto detect format auto_detect_format(); @@ -299,7 +299,7 @@ void FFmpegWriter::SetOption(StreamType stream, string name, string value) /// Determine if codec name is valid bool FFmpegWriter::IsValidCodec(string codec_name) { // Initialize FFMpeg, and register all formats and codecs - av_register_all(); + AV_REGISTER_ALL // Find the codec (if any) if (avcodec_find_encoder_by_name(codec_name.c_str()) == NULL) @@ -342,7 +342,7 @@ void FFmpegWriter::WriteHeader() } // Force the output filename (which doesn't always happen for some reason) - snprintf(oc->filename, sizeof(oc->filename), "%s", path.c_str()); + snprintf(oc->AV_FILENAME, sizeof(oc->AV_FILENAME), "%s", path.c_str()); // Write the stream header, if any // TODO: add avoptions / parameters instead of NULL @@ -559,8 +559,10 @@ void FFmpegWriter::flush_encoders() { if (info.has_audio && audio_codec && AV_GET_CODEC_TYPE(audio_st) == AVMEDIA_TYPE_AUDIO && AV_GET_CODEC_ATTRIBUTES(audio_st, audio_codec)->frame_size <= 1) return; +#if (LIBAVFORMAT_VERSION_MAJOR < 58) if (info.has_video && video_codec && AV_GET_CODEC_TYPE(video_st) == AVMEDIA_TYPE_VIDEO && (oc->oformat->flags & AVFMT_RAWPICTURE) && AV_FIND_DECODER_CODEC_ID(video_st) == AV_CODEC_ID_RAWVIDEO) return; +#endif int error_code = 0; int stop_encoding = 1; @@ -751,14 +753,14 @@ void FFmpegWriter::close_audio(AVFormatContext *oc, AVStream *st) // Deallocate resample buffer if (avr) { - avresample_close(avr); - avresample_free(&avr); + SWR_CLOSE(avr); + SWR_FREE(&avr); avr = NULL; } if (avr_planar) { - avresample_close(avr_planar); - avresample_free(&avr_planar); + SWR_CLOSE(avr_planar); + SWR_FREE(&avr_planar); avr_planar = NULL; } } @@ -898,7 +900,11 @@ AVStream* FFmpegWriter::add_audio_stream() // some formats want stream headers to be separate if (oc->oformat->flags & AVFMT_GLOBALHEADER) +#if (LIBAVCODEC_VERSION_MAJOR >= 57) + c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; +#else c->flags |= CODEC_FLAG_GLOBAL_HEADER; +#endif AV_COPY_PARAMS_FROM_CONTEXT(st, c); ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::add_audio_stream", "c->codec_id", c->codec_id, "c->bit_rate", c->bit_rate, "c->channels", c->channels, "c->sample_fmt", c->sample_fmt, "c->channel_layout", c->channel_layout, "c->sample_rate", c->sample_rate); @@ -970,7 +976,11 @@ AVStream* FFmpegWriter::add_video_stream() c->mb_decision = 2; // some formats want stream headers to be separate if (oc->oformat->flags & AVFMT_GLOBALHEADER) +#if (LIBAVCODEC_VERSION_MAJOR >= 57) + c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; +#else c->flags |= CODEC_FLAG_GLOBAL_HEADER; +#endif // Find all supported pixel formats for this codec const PixelFormat* supported_pixel_formats = codec->pix_fmts; @@ -987,10 +997,12 @@ AVStream* FFmpegWriter::add_video_stream() // Raw video should use RGB24 c->pix_fmt = PIX_FMT_RGB24; +#if (LIBAVFORMAT_VERSION_MAJOR < 58) if (strcmp(fmt->name, "gif") != 0) // If not GIF format, skip the encoding process // Set raw picture flag (so we don't encode this video) oc->oformat->flags |= AVFMT_RAWPICTURE; +#endif } else { // Set the default codec c->pix_fmt = PIX_FMT_YUV420P; @@ -998,7 +1010,11 @@ AVStream* FFmpegWriter::add_video_stream() } AV_COPY_PARAMS_FROM_CONTEXT(st, c); +#if (LIBAVFORMAT_VERSION_MAJOR < 58) ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::add_video_stream (" + (string)fmt->name + " : " + (string)av_get_pix_fmt_name(c->pix_fmt) + ")", "c->codec_id", c->codec_id, "c->bit_rate", c->bit_rate, "c->pix_fmt", c->pix_fmt, "oc->oformat->flags", oc->oformat->flags, "AVFMT_RAWPICTURE", AVFMT_RAWPICTURE, "", -1); +#else + ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::add_video_stream (" + (string)fmt->name + " : " + (string)av_get_pix_fmt_name(c->pix_fmt) + ")", "c->codec_id", c->codec_id, "c->bit_rate", c->bit_rate, "c->pix_fmt", c->pix_fmt, "oc->oformat->flags", oc->oformat->flags, "", -1, "", -1); +#endif return st; } @@ -1073,7 +1089,7 @@ void FFmpegWriter::open_audio(AVFormatContext *oc, AVStream *st) av_dict_set(&st->metadata, iter->first.c_str(), iter->second.c_str(), 0); } - ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::open_audio", "audio_codec->thread_count", audio_codec->thread_count, "audio_input_frame_size", audio_input_frame_size, "buffer_size", AVCODEC_MAX_AUDIO_FRAME_SIZE + FF_INPUT_BUFFER_PADDING_SIZE, "", -1, "", -1, "", -1); + ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::open_audio", "audio_codec->thread_count", audio_codec->thread_count, "audio_input_frame_size", audio_input_frame_size, "buffer_size", AVCODEC_MAX_AUDIO_FRAME_SIZE + MY_INPUT_BUFFER_PADDING_SIZE, "", -1, "", -1, "", -1); } @@ -1239,7 +1255,7 @@ void FFmpegWriter::write_audio_packets(bool final) // setup resample context if (!avr) { - avr = avresample_alloc_context(); + avr = SWR_ALLOC(); av_opt_set_int(avr, "in_channel_layout", channel_layout_in_frame, 0); av_opt_set_int(avr, "out_channel_layout", info.channel_layout, 0); av_opt_set_int(avr, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0); @@ -1248,12 +1264,12 @@ void FFmpegWriter::write_audio_packets(bool final) av_opt_set_int(avr, "out_sample_rate", info.sample_rate, 0); av_opt_set_int(avr, "in_channels", channels_in_frame, 0); av_opt_set_int(avr, "out_channels", info.channels, 0); - avresample_open(avr); + SWR_INIT(avr); } int nb_samples = 0; // Convert audio samples - nb_samples = avresample_convert(avr, // audio resample context + nb_samples = SWR_CONVERT(avr, // audio resample context audio_converted->data, // output data pointers audio_converted->linesize[0], // output plane size, in bytes. (0 if unknown) audio_converted->nb_samples, // maximum number of samples that the output buffer can hold @@ -1314,7 +1330,7 @@ void FFmpegWriter::write_audio_packets(bool final) // setup resample context if (!avr_planar) { - avr_planar = avresample_alloc_context(); + avr_planar = SWR_ALLOC(); av_opt_set_int(avr_planar, "in_channel_layout", info.channel_layout, 0); av_opt_set_int(avr_planar, "out_channel_layout", info.channel_layout, 0); av_opt_set_int(avr_planar, "in_sample_fmt", output_sample_fmt, 0); @@ -1323,7 +1339,7 @@ void FFmpegWriter::write_audio_packets(bool final) av_opt_set_int(avr_planar, "out_sample_rate", info.sample_rate, 0); av_opt_set_int(avr_planar, "in_channels", info.channels, 0); av_opt_set_int(avr_planar, "out_channels", info.channels, 0); - avresample_open(avr_planar); + SWR_INIT(avr_planar); } // Create input frame (and allocate arrays) @@ -1346,7 +1362,7 @@ void FFmpegWriter::write_audio_packets(bool final) av_samples_alloc(frame_final->data, frame_final->linesize, info.channels, frame_final->nb_samples, audio_codec->sample_fmt, 0); // Convert audio samples - int nb_samples = avresample_convert(avr_planar, // audio resample context + int nb_samples = SWR_CONVERT(avr_planar, // audio resample context frame_final->data, // output data pointers frame_final->linesize[0], // output plane size, in bytes. (0 if unknown) frame_final->nb_samples, // maximum number of samples that the output buffer can hold @@ -1577,6 +1593,9 @@ void FFmpegWriter::process_video_packet(std::shared_ptr frame) // write video frame bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame* frame_final) { +#if (LIBAVFORMAT_VERSION_MAJOR >= 58) + ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::write_video_packet", "frame->number", frame->number, "oc->oformat->flags", oc->oformat->flags, "", -1, "", -1, "", -1, "", -1); +#else ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::write_video_packet", "frame->number", frame->number, "oc->oformat->flags & AVFMT_RAWPICTURE", oc->oformat->flags & AVFMT_RAWPICTURE, "", -1, "", -1, "", -1, "", -1); if (oc->oformat->flags & AVFMT_RAWPICTURE) { @@ -1604,7 +1623,9 @@ bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame* fra // Deallocate packet AV_FREE_PACKET(&pkt); - } else { + } else +#endif + { AVPacket pkt; av_init_packet(&pkt); diff --git a/src/FrameMapper.cpp b/src/FrameMapper.cpp index f49cbc4d..c4c68f5a 100644 --- a/src/FrameMapper.cpp +++ b/src/FrameMapper.cpp @@ -650,8 +650,8 @@ void FrameMapper::Close() // Deallocate resample buffer if (avr) { - avresample_close(avr); - avresample_free(&avr); + SWR_CLOSE(avr); + SWR_FREE(&avr); avr = NULL; } } @@ -741,8 +741,8 @@ void FrameMapper::ChangeMapping(Fraction target_fps, PulldownType target_pulldow // Deallocate resample buffer if (avr) { - avresample_close(avr); - avresample_free(&avr); + SWR_CLOSE(avr); + SWR_FREE(&avr); avr = NULL; } @@ -817,7 +817,7 @@ void FrameMapper::ResampleMappedAudio(std::shared_ptr frame, int64_t orig // setup resample context if (!avr) { - avr = avresample_alloc_context(); + avr = SWR_ALLOC(); av_opt_set_int(avr, "in_channel_layout", channel_layout_in_frame, 0); av_opt_set_int(avr, "out_channel_layout", info.channel_layout, 0); av_opt_set_int(avr, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0); @@ -826,11 +826,11 @@ void FrameMapper::ResampleMappedAudio(std::shared_ptr frame, int64_t orig av_opt_set_int(avr, "out_sample_rate", info.sample_rate, 0); av_opt_set_int(avr, "in_channels", channels_in_frame, 0); av_opt_set_int(avr, "out_channels", info.channels, 0); - avresample_open(avr); + SWR_INIT(avr); } // Convert audio samples - nb_samples = avresample_convert(avr, // audio resample context + nb_samples = SWR_CONVERT(avr, // audio resample context audio_converted->data, // output data pointers audio_converted->linesize[0], // output plane size, in bytes. (0 if unknown) audio_converted->nb_samples, // maximum number of samples that the output buffer can hold diff --git a/src/Timeline.cpp b/src/Timeline.cpp index d042aeeb..35b91283 100644 --- a/src/Timeline.cpp +++ b/src/Timeline.cpp @@ -1000,13 +1000,14 @@ void Timeline::SetJsonValue(Json::Value root) { if (!existing_effect["type"].isNull()) { // Create instance of effect - e = EffectInfo().CreateEffect(existing_effect["type"].asString()); + if (e = EffectInfo().CreateEffect(existing_effect["type"].asString())) { - // Load Json into Effect - e->SetJsonValue(existing_effect); + // Load Json into Effect + e->SetJsonValue(existing_effect); - // Add Effect to Timeline - AddEffect(e); + // Add Effect to Timeline + AddEffect(e); + } } } } @@ -1270,13 +1271,14 @@ void Timeline::apply_json_to_effects(Json::Value change, EffectBase* existing_ef EffectBase *e = NULL; // Init the matching effect object - e = EffectInfo().CreateEffect(effect_type); + if (e = EffectInfo().CreateEffect(effect_type)) { - // Load Json into Effect - e->SetJsonValue(change["value"]); + // Load Json into Effect + e->SetJsonValue(change["value"]); - // Add Effect to Timeline - AddEffect(e); + // Add Effect to Timeline + AddEffect(e); + } } else if (change_type == "update") { diff --git a/tests/ReaderBase_Tests.cpp b/tests/ReaderBase_Tests.cpp index 9d435304..70ca90d5 100644 --- a/tests/ReaderBase_Tests.cpp +++ b/tests/ReaderBase_Tests.cpp @@ -44,9 +44,9 @@ TEST(ReaderBase_Derived_Class) std::shared_ptr GetFrame(int64_t number) { std::shared_ptr f(new Frame()); return f; } void Close() { }; void Open() { }; - string Json() { }; + string Json() { return NULL; }; void SetJson(string value) { }; - Json::Value JsonValue() { }; + Json::Value JsonValue() { return (int) NULL; }; void SetJsonValue(Json::Value root) { }; bool IsOpen() { return true; }; string Name() { return "TestReader"; }; From 8216795c33a4297cb4728a0a53c88a6fc06c3ac2 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sun, 12 Aug 2018 00:15:23 -0500 Subject: [PATCH 013/223] Adding environment checking to enable/disable omp taskwait after each video/audio frame is processed. This is experimental for some users with crashes. --- include/FFmpegReader.h | 1 + include/OpenMPUtilities.h | 22 ++++++++++++++++++++-- src/FFmpegReader.cpp | 18 ++++++++++++++---- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/include/FFmpegReader.h b/include/FFmpegReader.h index 6072756a..e2c4863a 100644 --- a/include/FFmpegReader.h +++ b/include/FFmpegReader.h @@ -105,6 +105,7 @@ namespace openshot bool check_interlace; bool check_fps; bool has_missing_frames; + bool use_omp_threads; CacheMemory working_cache; CacheMemory missing_frames; diff --git a/include/OpenMPUtilities.h b/include/OpenMPUtilities.h index 8a95a950..c0f5597b 100644 --- a/include/OpenMPUtilities.h +++ b/include/OpenMPUtilities.h @@ -29,8 +29,26 @@ #define OPENSHOT_OPENMP_UTILITIES_H #include +#include +#include - // Calculate the # of OpenMP Threads to allow - #define OPEN_MP_NUM_PROCESSORS omp_get_num_procs() +// Calculate the # of OpenMP Threads to allow +#define OPEN_MP_NUM_PROCESSORS omp_get_num_procs() + +using namespace std; + +namespace openshot { + + // Check if OS2_OMP_THREADS environment variable is present, and return + // if multiple threads should be used with OMP + static bool IsOMPEnabled() { + char* OS2_OMP_THREADS = getenv("OS2_OMP_THREADS"); + if (OS2_OMP_THREADS != NULL && strcmp(OS2_OMP_THREADS, "0") == 0) + return false; + else + return true; + } + +} #endif diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index ceccbe23..736e95ee 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -37,7 +37,8 @@ FFmpegReader::FFmpegReader(string path) audio_pts_offset(99999), video_pts_offset(99999), path(path), is_video_seek(true), check_interlace(false), check_fps(false), enable_seek(true), is_open(false), seek_audio_frame_found(0), seek_video_frame_found(0), prev_samples(0), prev_pts(0), pts_total(0), pts_counter(0), is_duration_known(false), largest_frame_processed(0), - current_video_frame(0), has_missing_frames(false), num_packets_since_video_frame(0), num_checks_since_final(0), packet(NULL) { + current_video_frame(0), has_missing_frames(false), num_packets_since_video_frame(0), num_checks_since_final(0), + packet(NULL), use_omp_threads(true) { // Initialize FFMpeg, and register all formats and codecs AV_REGISTER_ALL @@ -58,7 +59,8 @@ FFmpegReader::FFmpegReader(string path, bool inspect_reader) audio_pts_offset(99999), video_pts_offset(99999), path(path), is_video_seek(true), check_interlace(false), check_fps(false), enable_seek(true), is_open(false), seek_audio_frame_found(0), seek_video_frame_found(0), prev_samples(0), prev_pts(0), pts_total(0), pts_counter(0), is_duration_known(false), largest_frame_processed(0), - current_video_frame(0), has_missing_frames(false), num_packets_since_video_frame(0), num_checks_since_final(0), packet(NULL) { + current_video_frame(0), has_missing_frames(false), num_packets_since_video_frame(0), num_checks_since_final(0), + packet(NULL), use_omp_threads(true) { // Initialize FFMpeg, and register all formats and codecs AV_REGISTER_ALL @@ -227,6 +229,9 @@ void FFmpegReader::Open() missing_frames.SetMaxBytesFromInfo(OPEN_MP_NUM_PROCESSORS * 2, info.width, info.height, info.sample_rate, info.channels); final_cache.SetMaxBytesFromInfo(OPEN_MP_NUM_PROCESSORS * 2, info.width, info.height, info.sample_rate, info.channels); + // Initialize OMP threading support + use_omp_threads = openshot::IsOMPEnabled(); + // Mark as "open" is_open = true; } @@ -633,8 +638,13 @@ std::shared_ptr FFmpegReader::ReadStream(int64_t requested_frame) ProcessAudioPacket(requested_frame, location.frame, location.sample_start); } + if (!use_omp_threads) { + // Wait on each OMP task to complete before moving on to the next one. This slows + // down processing considerably, but might be more stable on some systems. + #pragma omp taskwait + } + // Check if working frames are 'finished' - bool is_cache_found = false; if (!is_seeking) { // Check for any missing frames CheckMissingFrame(requested_frame); @@ -644,7 +654,7 @@ std::shared_ptr FFmpegReader::ReadStream(int64_t requested_frame) } // Check if requested 'final' frame is available - is_cache_found = (final_cache.GetFrame(requested_frame) != NULL); + bool is_cache_found = (final_cache.GetFrame(requested_frame) != NULL); // Increment frames processed packets_processed++; From 6b5e2d427bc7e026b0d0037986c25efaa4de0ae8 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sun, 12 Aug 2018 00:36:03 -0500 Subject: [PATCH 014/223] Moving `omp taskwait` to after the ProcessVideoPacket() method, since that is the only place it is useful. --- src/FFmpegReader.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 736e95ee..adf957f1 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -607,6 +607,12 @@ std::shared_ptr FFmpegReader::ReadStream(int64_t requested_frame) // Process Video Packet ProcessVideoPacket(requested_frame); + + if (!use_omp_threads) { + // Wait on each OMP task to complete before moving on to the next one. This slows + // down processing considerably, but might be more stable on some systems. + #pragma omp taskwait + } } } @@ -638,12 +644,6 @@ std::shared_ptr FFmpegReader::ReadStream(int64_t requested_frame) ProcessAudioPacket(requested_frame, location.frame, location.sample_start); } - if (!use_omp_threads) { - // Wait on each OMP task to complete before moving on to the next one. This slows - // down processing considerably, but might be more stable on some systems. - #pragma omp taskwait - } - // Check if working frames are 'finished' if (!is_seeking) { // Check for any missing frames From 340803e31eb3385d32a0b8de5e24f227c2b07a1b Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Fri, 31 Aug 2018 21:36:23 -0700 Subject: [PATCH 015/223] Initial rudimentary support for hardware acceleration (encode and decode) Only Linux vaapi for now --- include/FFmpegReader.h | 3 + include/FFmpegUtilities.h | 3 + src/FFmpegReader.cpp | 132 ++++++++++++++++++++++++++++- src/FFmpegWriter.cpp | 171 +++++++++++++++++++++++++++++++++++++- 4 files changed, 301 insertions(+), 8 deletions(-) diff --git a/include/FFmpegReader.h b/include/FFmpegReader.h index e2c4863a..fcc995ae 100644 --- a/include/FFmpegReader.h +++ b/include/FFmpegReader.h @@ -97,6 +97,9 @@ namespace openshot AVFormatContext *pFormatCtx; int i, videoStream, audioStream; AVCodecContext *pCodecCtx, *aCodecCtx; + #if (LIBAVFORMAT_VERSION_MAJOR >= 57) + AVBufferRef *hw_device_ctx = NULL; //PM + #endif AVStream *pStream, *aStream; AVPacket *packet; AVFrame *pFrame; diff --git a/include/FFmpegUtilities.h b/include/FFmpegUtilities.h index 346da541..0cc08f52 100644 --- a/include/FFmpegUtilities.h +++ b/include/FFmpegUtilities.h @@ -42,6 +42,9 @@ extern "C" { #include #include + #if (LIBAVFORMAT_VERSION_MAJOR >= 57) + #include //PM + #endif #include // Change this to the first version swrescale works #if (LIBAVFORMAT_VERSION_MAJOR >= 57) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index adf957f1..86b7cb0d 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -32,6 +32,9 @@ using namespace openshot; +int hw_de_on = 1; // Is set in UI +int hw_de_supported = 0; // Is set by FFmpegReader + FFmpegReader::FFmpegReader(string path) : last_frame(0), is_seeking(0), seeking_pts(0), seeking_frame(0), seek_count(0), audio_pts_offset(99999), video_pts_offset(99999), path(path), is_video_seek(true), check_interlace(false), @@ -103,6 +106,45 @@ bool AudioLocation::is_near(AudioLocation location, int samples_per_frame, int64 return false; } +#if IS_FFMPEG_3_2 +#if defined(__linux__) +#pragma message "You are compiling with experimental hardware decode" + +static enum AVPixelFormat get_vaapi_format(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) +{ + const enum AVPixelFormat *p; + + for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { + if (*p == AV_PIX_FMT_VAAPI) + return *p; + } + ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (Unable to decode this file using VA-API.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); + hw_de_supported = 0; + return AV_PIX_FMT_NONE; +} + +int is_hardware_decode_supported(int codecid) +{ + int ret; + switch (codecid) { + case AV_CODEC_ID_H264: + case AV_CODEC_ID_MPEG2VIDEO: + case AV_CODEC_ID_VC1: + case AV_CODEC_ID_WMV1: + case AV_CODEC_ID_WMV2: + case AV_CODEC_ID_WMV3: + ret = 1; + break; + default : + ret = 0; + break; + } + return ret; +} + +#endif +#endif + void FFmpegReader::Open() { // Open reader if not already open @@ -111,6 +153,14 @@ void FFmpegReader::Open() // Initialize format context pFormatCtx = NULL; + char * val = getenv( "OS2_DECODE_HW" ); + if (val == NULL) { + hw_de_on = 0; + } + else{ + hw_de_on = (val[0] == '1')? 1 : 0; + } + // Open video file if (avformat_open_input(&pFormatCtx, path.c_str(), NULL, NULL) != 0) throw InvalidFile("File could not be opened.", path); @@ -151,7 +201,11 @@ void FFmpegReader::Open() // Get codec and codec context from stream AVCodec *pCodec = avcodec_find_decoder(codecId); pCodecCtx = AV_GET_CODEC_CONTEXT(pStream, pCodec); - + #if IS_FFMPEG_3_2 + #if defined(__linux__) + hw_de_supported = is_hardware_decode_supported(pCodecCtx->codec_id); + #endif + #endif // Set number of threads equal to number of processors (not to exceed 16) pCodecCtx->thread_count = min(OPEN_MP_NUM_PROCESSORS, 16); @@ -163,6 +217,23 @@ void FFmpegReader::Open() AVDictionary *opts = NULL; av_dict_set(&opts, "strict", "experimental", 0); + #if IS_FFMPEG_3_2 + #if defined(__linux__) + if (hw_de_on & hw_de_supported) { + // Open Hardware Acceleration + hw_device_ctx = NULL; + pCodecCtx->get_format = get_vaapi_format; + if (av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_VAAPI, NULL, NULL, 0) >= 0) { + if (!(pCodecCtx->hw_device_ctx = av_buffer_ref(hw_device_ctx))) { + throw InvalidCodec("Hardware device reference create failed.", path); + } + } + else { + throw InvalidCodec("Hardware device create failed.", path); + } + } + #endif + #endif // Open video codec if (avcodec_open2(pCodecCtx, pCodec, &opts) < 0) throw InvalidCodec("A video codec was found, but could not be opened.", path); @@ -252,6 +323,16 @@ void FFmpegReader::Close() { avcodec_flush_buffers(pCodecCtx); AV_FREE_CONTEXT(pCodecCtx); + #if IS_FFMPEG_3_2 + #if defined(__linux__) + if (hw_de_on) { + if (hw_device_ctx) { + av_buffer_unref(&hw_device_ctx); + hw_device_ctx = NULL; + } + } + #endif + #endif } if (info.has_audio) { @@ -703,9 +784,13 @@ std::shared_ptr FFmpegReader::ReadStream(int64_t requested_frame) int FFmpegReader::GetNextPacket() { int found_packet = 0; - AVPacket *next_packet = new AVPacket(); + AVPacket *next_packet; + #pragma omp critical(getnextpacket) + { + next_packet = new AVPacket(); found_packet = av_read_frame(pFormatCtx, next_packet); + if (packet) { // Remove previous packet before getting next one RemoveAVPacket(packet); @@ -717,7 +802,7 @@ int FFmpegReader::GetNextPacket() // Update current packet pointer packet = next_packet; } - +} // Return if packet was found (or error number) return found_packet; } @@ -734,17 +819,51 @@ bool FFmpegReader::GetAVFrame() { #if IS_FFMPEG_3_2 frameFinished = 0; + ret = avcodec_send_packet(pCodecCtx, packet); + if (ret < 0 || ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (Packet not sent)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); } else { + AVFrame *next_frame2; + #if defined(__linux__) + if (hw_de_on && hw_de_supported) { + next_frame2 = AV_ALLOCATE_FRAME(); + } + else + #endif + { + next_frame2 = next_frame; + } pFrame = new AVFrame(); while (ret >= 0) { - ret = avcodec_receive_frame(pCodecCtx, next_frame); + ret = avcodec_receive_frame(pCodecCtx, next_frame2); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { break; } + if (ret != 0) { + ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (invalid return frame received)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); + } + #if defined(__linux__) + if (hw_de_on && hw_de_supported) { + int err; + if (next_frame2->format == AV_PIX_FMT_VAAPI) { + next_frame->format = AV_PIX_FMT_YUV420P; + if ((err = av_hwframe_transfer_data(next_frame,next_frame2,0)) < 0) { + ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (Failed to transfer data to output frame)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); + } + if ((err = av_frame_copy_props(next_frame,next_frame2)) < 0) { + ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (Failed to copy props to output frame)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); + } + } + } + else + #endif + { // No hardware acceleration used -> no copy from GPU memory needed + next_frame = next_frame2; + } + //} // TODO also handle possible further frames // Use only the first frame like avcodec_decode_video2 if (frameFinished == 0 ) { @@ -759,6 +878,11 @@ bool FFmpegReader::GetAVFrame() } } } + #if defined(__linux__) + if (hw_de_on && hw_de_supported) { + AV_FREE_FRAME(&next_frame2); + } + #endif } #else avcodec_decode_video2(pCodecCtx, next_frame, &frameFinished, packet); diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index ed4fc3fb..a62e8287 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -32,6 +32,49 @@ using namespace openshot; +#if IS_FFMPEG_3_2 +int hw_en_on = 1; // Is set in UI +int hw_en_supported = 0; // Is set by FFmpegWriter +static AVBufferRef *hw_device_ctx = NULL; +AVFrame *hw_frame = NULL; + +static int set_hwframe_ctx(AVCodecContext *ctx, AVBufferRef *hw_device_ctx, int64_t width, int64_t height) +{ + AVBufferRef *hw_frames_ref; + AVHWFramesContext *frames_ctx = NULL; + int err = 0; + + if (!(hw_frames_ref = av_hwframe_ctx_alloc(hw_device_ctx))) { + fprintf(stderr, "Failed to create VAAPI frame context.\n"); + return -1; + } + frames_ctx = (AVHWFramesContext *)(hw_frames_ref->data); + frames_ctx->format = AV_PIX_FMT_VAAPI; + frames_ctx->sw_format = AV_PIX_FMT_NV12; + frames_ctx->width = width; + frames_ctx->height = height; + frames_ctx->initial_pool_size = 20; + if ((err = av_hwframe_ctx_init(hw_frames_ref)) < 0) { + fprintf(stderr, "Failed to initialize VAAPI frame context." + "Error code: %s\n",av_err2str(err)); + av_buffer_unref(&hw_frames_ref); + return err; + } + ctx->hw_frames_ctx = av_buffer_ref(hw_frames_ref); + if (!ctx->hw_frames_ctx) + err = AVERROR(ENOMEM); + + av_buffer_unref(&hw_frames_ref); + return err; +} +#endif + +#if IS_FFMPEG_3_2 +#if defined(__linux__) +#pragma message "You are compiling with experimental hardware encode" +#endif +#endif + FFmpegWriter::FFmpegWriter(string path) : path(path), fmt(NULL), oc(NULL), audio_st(NULL), video_st(NULL), audio_pts(0), video_pts(0), samples(NULL), audio_outbuf(NULL), audio_outbuf_size(0), audio_input_frame_size(0), audio_input_position(0), @@ -116,7 +159,26 @@ void FFmpegWriter::SetVideoOptions(bool has_video, string codec, Fraction fps, i // Set the video options if (codec.length() > 0) { - AVCodec *new_codec = avcodec_find_encoder_by_name(codec.c_str()); + AVCodec *new_codec; + // Check if the codec selected is a hardware accelerated codec + #if IS_FFMPEG_3_2 + #if defined(__linux__) + if ( (strcmp(codec.c_str(),"h264_vaapi") == 0)) { + new_codec = avcodec_find_encoder_by_name(codec.c_str()); + hw_en_on = 1; + hw_en_supported = 1; + } + else { + new_codec = avcodec_find_encoder_by_name(codec.c_str()); + hw_en_on = 0; + hw_en_supported = 0; + } + #else // is FFmpeg 3 but not linux + new_codec = avcodec_find_encoder_by_name(codec.c_str()); + #endif //__linux__ + #else // not ffmpeg 3 + new_codec = avcodec_find_encoder_by_name(codec.c_str()); + #endif //IS_FFMPEG_3_2 if (new_codec == NULL) throw InvalidCodec("A valid video codec could not be found for this file.", path); else { @@ -506,6 +568,7 @@ void FFmpegWriter::write_queued_frames() is_writing = false; } // end omp single + } // end omp parallel // Raise exception from main thread @@ -735,6 +798,16 @@ void FFmpegWriter::close_video(AVFormatContext *oc, AVStream *st) { AV_FREE_CONTEXT(video_codec); video_codec = NULL; + #if IS_FFMPEG_3_2 + #if defined(__linux__) + if (hw_en_on && hw_en_supported) { + if (hw_device_ctx) { + av_buffer_unref(&hw_device_ctx); + hw_device_ctx = NULL; + } + } + #endif + #endif } // Close the audio codec @@ -1102,6 +1175,23 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) // Set number of threads equal to number of processors (not to exceed 16) video_codec->thread_count = min(OPEN_MP_NUM_PROCESSORS, 16); + #if IS_FFMPEG_3_2 + #if defined(__linux__) + if (hw_en_on && hw_en_supported) { + // Use the hw device given in the environment variable HW_DEVICE_SET or the default if not set + char *dev_hw = getenv( "HW_DEVICE_SET" ); + // Check if it is there and writable + if( dev_hw != NULL && access( dev_hw, W_OK ) == -1 ) { + dev_hw = NULL; // use default + } + if (av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_VAAPI, + dev_hw, NULL, 0) < 0) { + cerr << "FFmpegWriter::open_video : Codec name: " << info.vcodec.c_str() << " ERROR creating\n"; + throw InvalidCodec("Could not create hwdevice", path); + } + } + #endif + #endif /* find the video encoder */ codec = avcodec_find_encoder_by_name(info.vcodec.c_str()); if (!codec) @@ -1117,6 +1207,24 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) AVDictionary *opts = NULL; av_dict_set(&opts, "strict", "experimental", 0); + #if IS_FFMPEG_3_2 + #if defined(__linux__) + if (hw_en_on && hw_en_supported) { + video_codec->max_b_frames = 0; // At least this GPU doesn't support b-frames + video_codec->pix_fmt = AV_PIX_FMT_VAAPI; + video_codec->profile = FF_PROFILE_H264_BASELINE | FF_PROFILE_H264_CONSTRAINED; + av_opt_set(video_codec->priv_data,"preset","slow",0); + av_opt_set(video_codec->priv_data,"tune","zerolatency",0); + av_opt_set(video_codec->priv_data, "vprofile", "baseline", AV_OPT_SEARCH_CHILDREN); + // set hw_frames_ctx for encoder's AVCodecContext + int err; + if ((err = set_hwframe_ctx(video_codec, hw_device_ctx, info.width, info.height)) < 0) { + fprintf(stderr, "Failed to set hwframe context.\n"); + } + } + #endif + #endif + /* open the codec */ if (avcodec_open2(video_codec, codec, &opts) < 0) throw InvalidCodec("Could not open codec", path); @@ -1566,7 +1674,15 @@ void FFmpegWriter::process_video_packet(std::shared_ptr frame) // Init AVFrame for source image & final (converted image) frame_source = allocate_avframe(PIX_FMT_RGBA, source_image_width, source_image_height, &bytes_source, (uint8_t*) pixels); #if IS_FFMPEG_3_2 - AVFrame *frame_final = allocate_avframe((AVPixelFormat)(video_st->codecpar->format), info.width, info.height, &bytes_final, NULL); + AVFrame *frame_final; + #if defined(__linux__) + if (hw_en_on && hw_en_supported) { + frame_final = allocate_avframe(AV_PIX_FMT_NV12, info.width, info.height, &bytes_final, NULL); + } else + #endif + { + frame_final = allocate_avframe((AVPixelFormat)(video_st->codecpar->format), info.width, info.height, &bytes_final, NULL); + } #else AVFrame *frame_final = allocate_avframe(video_codec->pix_fmt, info.width, info.height, &bytes_final, NULL); #endif @@ -1641,14 +1757,41 @@ bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame* fra // Assign the initial AVFrame PTS from the frame counter frame_final->pts = write_video_count; - + #if IS_FFMPEG_3_2 + #if defined(__linux__) + if (hw_en_on && hw_en_supported) { + if (!(hw_frame = av_frame_alloc())) { + fprintf(stderr, "Error code: av_hwframe_alloc\n"); + } + if (av_hwframe_get_buffer(video_codec->hw_frames_ctx, hw_frame, 0) < 0) { + fprintf(stderr, "Error code: av_hwframe_get_buffer\n"); + } + if (!hw_frame->hw_frames_ctx) { + fprintf(stderr, "Error hw_frames_ctx.\n"); + } + hw_frame->format = AV_PIX_FMT_NV12; + if ( av_hwframe_transfer_data(hw_frame, frame_final, 0) < 0) { + fprintf(stderr, "Error while transferring frame data to surface.\n"); + } + av_frame_copy_props(hw_frame, frame_final); + } + #endif + #endif /* encode the image */ int got_packet_ptr = 0; int error_code = 0; #if IS_FFMPEG_3_2 // Write video packet (latest version of FFmpeg) int frameFinished = 0; - int ret = avcodec_send_frame(video_codec, frame_final); + int ret; + #if defined(__linux__) + #if IS_FFMPEG_3_2 + if (hw_en_on && hw_en_supported) { + ret = avcodec_send_frame(video_codec, hw_frame); //hw_frame!!! + } else + #endif + #endif + ret = avcodec_send_frame(video_codec, frame_final); error_code = ret; if (ret < 0 ) { ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::write_video_packet (Frame not sent)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); @@ -1709,6 +1852,7 @@ bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame* fra //pkt.pts = pkt.dts = write_video_count; // set the timestamp +// av_packet_rescale_ts(&pkt, video_st->time_base,video_codec->time_base); if (pkt.pts != AV_NOPTS_VALUE) pkt.pts = av_rescale_q(pkt.pts, video_codec->time_base, video_st->time_base); if (pkt.dts != AV_NOPTS_VALUE) @@ -1732,6 +1876,16 @@ bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame* fra // Deallocate packet AV_FREE_PACKET(&pkt); + #if IS_FFMPEG_3_2 + #if defined(__linux__) + if (hw_en_on && hw_en_supported) { + if (hw_frame) { + av_frame_free(&hw_frame); + hw_frame = NULL; + } + } + #endif + #endif } // Success @@ -1752,7 +1906,16 @@ void FFmpegWriter::InitScalers(int source_width, int source_height) for (int x = 0; x < num_of_rescalers; x++) { // Init the software scaler from FFMpeg + #if IS_FFMPEG_3_2 + #if defined(__linux__) + if (hw_en_on && hw_en_supported) { + img_convert_ctx = sws_getContext(source_width, source_height, PIX_FMT_RGBA, info.width, info.height, AV_PIX_FMT_NV12, SWS_BILINEAR, NULL, NULL, NULL); + } else + #endif + #endif + { img_convert_ctx = sws_getContext(source_width, source_height, PIX_FMT_RGBA, info.width, info.height, AV_GET_CODEC_PIXEL_FORMAT(video_st, video_st->codec), SWS_BILINEAR, NULL, NULL, NULL); + } // Add rescaler to vector image_rescalers.push_back(img_convert_ctx); From 384b6e0bc3e08435c641eab38a237f0c0b35039b Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Fri, 31 Aug 2018 21:50:23 -0700 Subject: [PATCH 016/223] Set limit of threads for OpenMP and ffmpeg by setting the environment variables LIMIT_OMP_THREADS and LIMIT_FF_THREADS If they are not set the normal values are used --- include/OpenMPUtilities.h | 3 ++- src/FFmpegReader.cpp | 4 ++-- src/FFmpegWriter.cpp | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/include/OpenMPUtilities.h b/include/OpenMPUtilities.h index c0f5597b..6ebde218 100644 --- a/include/OpenMPUtilities.h +++ b/include/OpenMPUtilities.h @@ -33,7 +33,8 @@ #include // Calculate the # of OpenMP Threads to allow -#define OPEN_MP_NUM_PROCESSORS omp_get_num_procs() +#define OPEN_MP_NUM_PROCESSORS ((getenv( "LIMIT_OMP_THREADS" )==NULL) ? omp_get_num_procs() : (min(omp_get_num_procs(), max(2, atoi(getenv( "LIMIT_OMP_THREADS" ))) ))) +#define FF_NUM_PROCESSORS ((getenv( "LIMIT_FF_THREADS" )==NULL) ? omp_get_num_procs() : (min(omp_get_num_procs(), max(2, atoi(getenv( "LIMIT_FF_THREADS" ))) ))) using namespace std; diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 86b7cb0d..3386d8ef 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -207,7 +207,7 @@ void FFmpegReader::Open() #endif #endif // Set number of threads equal to number of processors (not to exceed 16) - pCodecCtx->thread_count = min(OPEN_MP_NUM_PROCESSORS, 16); + pCodecCtx->thread_count = min(FF_NUM_PROCESSORS, 16); if (pCodec == NULL) { throw InvalidCodec("A valid video codec could not be found for this file.", path); @@ -262,7 +262,7 @@ void FFmpegReader::Open() aCodecCtx = AV_GET_CODEC_CONTEXT(aStream, aCodec); // Set number of threads equal to number of processors (not to exceed 16) - aCodecCtx->thread_count = min(OPEN_MP_NUM_PROCESSORS, 16); + aCodecCtx->thread_count = min(FF_NUM_PROCESSORS, 16); if (aCodec == NULL) { throw InvalidCodec("A valid audio codec could not be found for this file.", path); diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index a62e8287..5c902021 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -1099,7 +1099,7 @@ void FFmpegWriter::open_audio(AVFormatContext *oc, AVStream *st) AV_GET_CODEC_FROM_STREAM(st, audio_codec) // Set number of threads equal to number of processors (not to exceed 16) - audio_codec->thread_count = min(OPEN_MP_NUM_PROCESSORS, 16); + audio_codec->thread_count = min(FF_NUM_PROCESSORS, 16); // Find the audio encoder codec = avcodec_find_encoder_by_name(info.acodec.c_str()); @@ -1173,7 +1173,7 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) AV_GET_CODEC_FROM_STREAM(st, video_codec) // Set number of threads equal to number of processors (not to exceed 16) - video_codec->thread_count = min(OPEN_MP_NUM_PROCESSORS, 16); + video_codec->thread_count = min(FF_NUM_PROCESSORS, 16); #if IS_FFMPEG_3_2 #if defined(__linux__) From 314177bceba3280d3d2c465dc9e34f533252ef88 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Sun, 2 Sep 2018 18:46:04 -0700 Subject: [PATCH 017/223] Let the user choose which installed graphics card to use for decoding HW_DE_DEVICE_SET and/or encoding HW_EN_DEVICE_SET Possible options are /dev/dri/renderD128 for the first, /dev/dri/renderD129 for the second, and so on. --- src/FFmpegReader.cpp | 8 +++++++- src/FFmpegWriter.cpp | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 3386d8ef..7910693c 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -221,9 +221,15 @@ void FFmpegReader::Open() #if defined(__linux__) if (hw_de_on & hw_de_supported) { // Open Hardware Acceleration + // Use the hw device given in the environment variable HW_DE_DEVICE_SET or the default if not set + char *dev_hw = getenv( "HW_DE_DEVICE_SET" ); + // Check if it is there and writable + if( dev_hw != NULL && access( dev_hw, W_OK ) == -1 ) { + dev_hw = NULL; // use default + } hw_device_ctx = NULL; pCodecCtx->get_format = get_vaapi_format; - if (av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_VAAPI, NULL, NULL, 0) >= 0) { + if (av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_VAAPI, dev_hw, NULL, 0) >= 0) { if (!(pCodecCtx->hw_device_ctx = av_buffer_ref(hw_device_ctx))) { throw InvalidCodec("Hardware device reference create failed.", path); } diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index 5c902021..24ccec0a 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -1178,8 +1178,8 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) #if IS_FFMPEG_3_2 #if defined(__linux__) if (hw_en_on && hw_en_supported) { - // Use the hw device given in the environment variable HW_DEVICE_SET or the default if not set - char *dev_hw = getenv( "HW_DEVICE_SET" ); + // Use the hw device given in the environment variable HW_EN_DEVICE_SET or the default if not set + char *dev_hw = getenv( "HW_EN_DEVICE_SET" ); // Check if it is there and writable if( dev_hw != NULL && access( dev_hw, W_OK ) == -1 ) { dev_hw = NULL; // use default From 063faefa694ac72b242c039666c7946cb58212a5 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Tue, 4 Sep 2018 10:08:01 -0700 Subject: [PATCH 018/223] Hardware acceleration for Windows and Mac, still disabled but code is there. This should show where modifications are to be made to support Linux, Mac, and Windows Only decoding, encoding will follow soon --- src/FFmpegReader.cpp | 155 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 140 insertions(+), 15 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 7910693c..5ee6a7b2 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -107,8 +107,8 @@ bool AudioLocation::is_near(AudioLocation location, int samples_per_frame, int64 } #if IS_FFMPEG_3_2 -#if defined(__linux__) #pragma message "You are compiling with experimental hardware decode" +#if defined(__linux__) static enum AVPixelFormat get_vaapi_format(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) { @@ -141,8 +141,109 @@ int is_hardware_decode_supported(int codecid) } return ret; } - #endif + +#if defined(_WIN32) +// Works for Windows 64 and Windows 32 +// FIXME Here goes the detection for Windows +// AV_HWDEVICE_TYPE_DXVA2 AV_PIX_FMT_DXVA2_VLD AV_HWDEVICE_TYPE_D3D11VA AV_PIX_FMT_D3D11 + +static enum AVPixelFormat get_dxva2_format(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) +{ + const enum AVPixelFormat *p; + + for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { + if (*p == AV_PIX_FMT_DXVA2_VLD) + return *p; + } + ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (Unable to decode this file using DXVA2.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); + hw_de_supported = 0; + return AV_PIX_FMT_NONE; +} + +int is_hardware_decode_supported(int codecid) +{ + /* int ret; + switch (codecid) { + case AV_CODEC_ID_H264: + case AV_CODEC_ID_MPEG2VIDEO: + case AV_CODEC_ID_VC1: + case AV_CODEC_ID_WMV1: + case AV_CODEC_ID_WMV2: + case AV_CODEC_ID_WMV3: + ret = 1; + break; + default : + ret = 0; + break; + } + return ret;*/ + return 0; +} +#endif + +#if defined(__APPLE__) +// FIXME Here goes the detection for Mac +// Constants for MAC: AV_HWDEVICE_TYPE_QSV AV_PIX_FMT_QSV +int is_hardware_decode_supported(int codecid) +{ +/* int ret; + switch (codecid) { + case AV_CODEC_ID_H264: + case AV_CODEC_ID_MPEG2VIDEO: + case AV_CODEC_ID_VC1: + case AV_CODEC_ID_WMV1: + case AV_CODEC_ID_WMV2: + case AV_CODEC_ID_WMV3: + ret = 1; + break; + default : + ret = 0; + break; + } + return ret;*/ + return 0; +} +static int get_qsv_format(AVCodecContext *avctx, const enum AVPixelFormat *pix_fmts) +{ + while (*pix_fmts != AV_PIX_FMT_NONE) { + if (*pix_fmts == AV_PIX_FMT_QSV) { + DecodeContext *decode = avctx->opaque; + AVHWFramesContext *frames_ctx; + AVQSVFramesContext *frames_hwctx; + int ret; + + /* create a pool of surfaces to be used by the decoder */ + avctx->hw_frames_ctx = av_hwframe_ctx_alloc(decode->hw_device_ref); + if (!avctx->hw_frames_ctx) + return AV_PIX_FMT_NONE; + frames_ctx = (AVHWFramesContext*)avctx->hw_frames_ctx->data; + frames_hwctx = frames_ctx->hwctx; + + frames_ctx->format = AV_PIX_FMT_QSV; + frames_ctx->sw_format = avctx->sw_pix_fmt; + frames_ctx->width = FFALIGN(avctx->coded_width, 32); + frames_ctx->height = FFALIGN(avctx->coded_height, 32); + frames_ctx->initial_pool_size = 32; + + frames_hwctx->frame_type = MFX_MEMTYPE_VIDEO_MEMORY_DECODER_TARGET; + + ret = av_hwframe_ctx_init(avctx->hw_frames_ctx); + if (ret < 0) + return AV_PIX_FMT_NONE; + + return AV_PIX_FMT_QSV; + } + + pix_fmts++; + } + + fprintf(stderr, "The QSV pixel format not offered in get_format()\n"); + + return AV_PIX_FMT_NONE; +} +#endif + #endif void FFmpegReader::Open() @@ -202,9 +303,9 @@ void FFmpegReader::Open() AVCodec *pCodec = avcodec_find_decoder(codecId); pCodecCtx = AV_GET_CODEC_CONTEXT(pStream, pCodec); #if IS_FFMPEG_3_2 - #if defined(__linux__) +// #if defined(__linux__) hw_de_supported = is_hardware_decode_supported(pCodecCtx->codec_id); - #endif +// #endif #endif // Set number of threads equal to number of processors (not to exceed 16) pCodecCtx->thread_count = min(FF_NUM_PROCESSORS, 16); @@ -218,8 +319,8 @@ void FFmpegReader::Open() av_dict_set(&opts, "strict", "experimental", 0); #if IS_FFMPEG_3_2 - #if defined(__linux__) - if (hw_de_on & hw_de_supported) { +// #if defined(__linux__) + if (hw_de_on && hw_de_supported) { // Open Hardware Acceleration // Use the hw device given in the environment variable HW_DE_DEVICE_SET or the default if not set char *dev_hw = getenv( "HW_DE_DEVICE_SET" ); @@ -228,8 +329,23 @@ void FFmpegReader::Open() dev_hw = NULL; // use default } hw_device_ctx = NULL; +// FIXME get_XXX_format +// FIXME AV_HWDEVICE_TYPE_.... +// IMPORTANT: The get_format has different names because even for one plattform +// like Linux there are different modes of access like vaapi and vdpau and these +// should be chosen by the user in the future + #if defined(__linux__) pCodecCtx->get_format = get_vaapi_format; if (av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_VAAPI, dev_hw, NULL, 0) >= 0) { + #endif + #if defined(_WIN32) + pCodecCtx->get_format = get_dxva2_format; + if (av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_DXVA2, dev_hw, NULL, 0) >= 0) { + #endif + #if defined(__APPLE__) + pCodecCtx->get_format = get_qsv_format; + if (av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_QSV, dev_hw, NULL, 0) >= 0) { + #endif if (!(pCodecCtx->hw_device_ctx = av_buffer_ref(hw_device_ctx))) { throw InvalidCodec("Hardware device reference create failed.", path); } @@ -238,7 +354,7 @@ void FFmpegReader::Open() throw InvalidCodec("Hardware device create failed.", path); } } - #endif +// #endif #endif // Open video codec if (avcodec_open2(pCodecCtx, pCodec, &opts) < 0) @@ -330,14 +446,14 @@ void FFmpegReader::Close() avcodec_flush_buffers(pCodecCtx); AV_FREE_CONTEXT(pCodecCtx); #if IS_FFMPEG_3_2 - #if defined(__linux__) +// #if defined(__linux__) if (hw_de_on) { if (hw_device_ctx) { av_buffer_unref(&hw_device_ctx); hw_device_ctx = NULL; } } - #endif +// #endif #endif } if (info.has_audio) @@ -833,12 +949,12 @@ bool FFmpegReader::GetAVFrame() } else { AVFrame *next_frame2; - #if defined(__linux__) +// #if defined(__linux__) if (hw_de_on && hw_de_supported) { next_frame2 = AV_ALLOCATE_FRAME(); } else - #endif +// #endif { next_frame2 = next_frame; } @@ -851,10 +967,19 @@ bool FFmpegReader::GetAVFrame() if (ret != 0) { ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (invalid return frame received)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); } - #if defined(__linux__) +// #if defined(__linux__) if (hw_de_on && hw_de_supported) { int err; +// FIXME AV_PIX_FMT_VAAPI + #if defined(__linux__) if (next_frame2->format == AV_PIX_FMT_VAAPI) { + #endif + #if defined(__WIN32__) + if (next_frame2->format == AV_PIX_FMT_DXVA2_VLD) { + #endif + #if defined(__APPLE__) + if (next_frame2->format == AV_PIX_FMT_QSV) { + #endif next_frame->format = AV_PIX_FMT_YUV420P; if ((err = av_hwframe_transfer_data(next_frame,next_frame2,0)) < 0) { ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (Failed to transfer data to output frame)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); @@ -865,7 +990,7 @@ bool FFmpegReader::GetAVFrame() } } else - #endif +// #endif { // No hardware acceleration used -> no copy from GPU memory needed next_frame = next_frame2; } @@ -884,11 +1009,11 @@ bool FFmpegReader::GetAVFrame() } } } - #if defined(__linux__) +// #if defined(__linux__) if (hw_de_on && hw_de_supported) { AV_FREE_FRAME(&next_frame2); } - #endif +// #endif } #else avcodec_decode_video2(pCodecCtx, next_frame, &frameFinished, packet); From be979cd78c7562d3a3ec2208b7d6f7bf50dd3f80 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Thu, 6 Sep 2018 08:28:50 -0700 Subject: [PATCH 019/223] Accelerated encode now supported by Windows and Mac. Only tested on Linux though due to absense of hardware/software. Tested to compile on Ubuntu 14.04, 16.04, 18.04, and 18.10 Acceleration only available on systems with ffmpeg 3.2 and up Very early code, work in progress. Issues to be fixed soon: if hardware cannot decode because the size is too big it keeps trying. more interfaces supported like vdpau in Linux error handling user interface Many commented lines of code are still in the source to help people start who may want to help. --- src/FFmpegWriter.cpp | 80 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 62 insertions(+), 18 deletions(-) diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index 24ccec0a..d0a542ae 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -49,7 +49,13 @@ static int set_hwframe_ctx(AVCodecContext *ctx, AVBufferRef *hw_device_ctx, int6 return -1; } frames_ctx = (AVHWFramesContext *)(hw_frames_ref->data); + #if defined(__linux__) frames_ctx->format = AV_PIX_FMT_VAAPI; + #elif defined(_WIN32) + frames_ctx->format = AV_PIX_FMT_DXVA2_VLD; + #elif defined(__APPLE__) + frames_ctx->format = AV_PIX_FMT_QSV; + #endif frames_ctx->sw_format = AV_PIX_FMT_NV12; frames_ctx->width = width; frames_ctx->height = height; @@ -70,9 +76,9 @@ static int set_hwframe_ctx(AVCodecContext *ctx, AVBufferRef *hw_device_ctx, int6 #endif #if IS_FFMPEG_3_2 -#if defined(__linux__) +//#if defined(__linux__) #pragma message "You are compiling with experimental hardware encode" -#endif +//#endif #endif FFmpegWriter::FFmpegWriter(string path) : @@ -171,6 +177,28 @@ void FFmpegWriter::SetVideoOptions(bool has_video, string codec, Fraction fps, i else { new_codec = avcodec_find_encoder_by_name(codec.c_str()); hw_en_on = 0; + hw_en_supported = 0; + } + #elif defined(_WIN32) + if ( (strcmp(codec.c_str(),"h264_dxva2") == 0)) { + new_codec = avcodec_find_encoder_by_name(codec.c_str()); + hw_en_on = 1; + hw_en_supported = 1; + } + else { + new_codec = avcodec_find_encoder_by_name(codec.c_str()); + hw_en_on = 0; + hw_en_supported = 0; + } + #elif defined(__APPLE__) + if ( (strcmp(codec.c_str(),"h264_qsv") == 0)) { + new_codec = avcodec_find_encoder_by_name(codec.c_str()); + hw_en_on = 1; + hw_en_supported = 1; + } + else { + new_codec = avcodec_find_encoder_by_name(codec.c_str()); + hw_en_on = 0; hw_en_supported = 0; } #else // is FFmpeg 3 but not linux @@ -799,14 +827,14 @@ void FFmpegWriter::close_video(AVFormatContext *oc, AVStream *st) AV_FREE_CONTEXT(video_codec); video_codec = NULL; #if IS_FFMPEG_3_2 - #if defined(__linux__) +// #if defined(__linux__) if (hw_en_on && hw_en_supported) { if (hw_device_ctx) { av_buffer_unref(&hw_device_ctx); hw_device_ctx = NULL; } } - #endif +// #endif #endif } @@ -1176,8 +1204,8 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) video_codec->thread_count = min(FF_NUM_PROCESSORS, 16); #if IS_FFMPEG_3_2 - #if defined(__linux__) if (hw_en_on && hw_en_supported) { + #if defined(__linux__) // Use the hw device given in the environment variable HW_EN_DEVICE_SET or the default if not set char *dev_hw = getenv( "HW_EN_DEVICE_SET" ); // Check if it is there and writable @@ -1189,8 +1217,20 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) cerr << "FFmpegWriter::open_video : Codec name: " << info.vcodec.c_str() << " ERROR creating\n"; throw InvalidCodec("Could not create hwdevice", path); } + #elif defined(_WIN32) + if (av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_DXVA2, + NULL, NULL, 0) < 0) { + cerr << "FFmpegWriter::open_video : Codec name: " << info.vcodec.c_str() << " ERROR creating\n"; + throw InvalidCodec("Could not create hwdevice", path); + } + #elif defined(__APPLE__) + if (av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_QSV, + NULL, NULL, 0) < 0) { + cerr << "FFmpegWriter::open_video : Codec name: " << info.vcodec.c_str() << " ERROR creating\n"; + throw InvalidCodec("Could not create hwdevice", path); + } + #endif } - #endif #endif /* find the video encoder */ codec = avcodec_find_encoder_by_name(info.vcodec.c_str()); @@ -1208,10 +1248,15 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) av_dict_set(&opts, "strict", "experimental", 0); #if IS_FFMPEG_3_2 - #if defined(__linux__) if (hw_en_on && hw_en_supported) { video_codec->max_b_frames = 0; // At least this GPU doesn't support b-frames + #if defined(__linux__) video_codec->pix_fmt = AV_PIX_FMT_VAAPI; + #elif defined(_WIN32) + video_codec->pix_fmt = AV_PIX_FMT_DXVA2_VLD + #elif defined(__APPLE__) + video_codec->pix_fmt = AV_PIX_FMT_QSV + #endif video_codec->profile = FF_PROFILE_H264_BASELINE | FF_PROFILE_H264_CONSTRAINED; av_opt_set(video_codec->priv_data,"preset","slow",0); av_opt_set(video_codec->priv_data,"tune","zerolatency",0); @@ -1222,7 +1267,6 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) fprintf(stderr, "Failed to set hwframe context.\n"); } } - #endif #endif /* open the codec */ @@ -1675,11 +1719,11 @@ void FFmpegWriter::process_video_packet(std::shared_ptr frame) frame_source = allocate_avframe(PIX_FMT_RGBA, source_image_width, source_image_height, &bytes_source, (uint8_t*) pixels); #if IS_FFMPEG_3_2 AVFrame *frame_final; - #if defined(__linux__) +// #if defined(__linux__) if (hw_en_on && hw_en_supported) { frame_final = allocate_avframe(AV_PIX_FMT_NV12, info.width, info.height, &bytes_final, NULL); } else - #endif +// #endif { frame_final = allocate_avframe((AVPixelFormat)(video_st->codecpar->format), info.width, info.height, &bytes_final, NULL); } @@ -1758,7 +1802,7 @@ bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame* fra // Assign the initial AVFrame PTS from the frame counter frame_final->pts = write_video_count; #if IS_FFMPEG_3_2 - #if defined(__linux__) +// #if defined(__linux__) if (hw_en_on && hw_en_supported) { if (!(hw_frame = av_frame_alloc())) { fprintf(stderr, "Error code: av_hwframe_alloc\n"); @@ -1775,7 +1819,7 @@ bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame* fra } av_frame_copy_props(hw_frame, frame_final); } - #endif +// #endif #endif /* encode the image */ int got_packet_ptr = 0; @@ -1784,13 +1828,13 @@ bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame* fra // Write video packet (latest version of FFmpeg) int frameFinished = 0; int ret; - #if defined(__linux__) +// #if defined(__linux__) #if IS_FFMPEG_3_2 if (hw_en_on && hw_en_supported) { ret = avcodec_send_frame(video_codec, hw_frame); //hw_frame!!! } else #endif - #endif +// #endif ret = avcodec_send_frame(video_codec, frame_final); error_code = ret; if (ret < 0 ) { @@ -1877,14 +1921,14 @@ bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame* fra // Deallocate packet AV_FREE_PACKET(&pkt); #if IS_FFMPEG_3_2 - #if defined(__linux__) +// #if defined(__linux__) if (hw_en_on && hw_en_supported) { if (hw_frame) { av_frame_free(&hw_frame); hw_frame = NULL; } } - #endif +// #endif #endif } @@ -1907,11 +1951,11 @@ void FFmpegWriter::InitScalers(int source_width, int source_height) { // Init the software scaler from FFMpeg #if IS_FFMPEG_3_2 - #if defined(__linux__) +// #if defined(__linux__) if (hw_en_on && hw_en_supported) { img_convert_ctx = sws_getContext(source_width, source_height, PIX_FMT_RGBA, info.width, info.height, AV_PIX_FMT_NV12, SWS_BILINEAR, NULL, NULL, NULL); } else - #endif +// #endif #endif { img_convert_ctx = sws_getContext(source_width, source_height, PIX_FMT_RGBA, info.width, info.height, AV_GET_CODEC_PIXEL_FORMAT(video_st, video_st->codec), SWS_BILINEAR, NULL, NULL, NULL); From 6925f6f7c2fdbf179d549bee5ee4c73b6a221aec Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Fri, 7 Sep 2018 10:44:18 -0700 Subject: [PATCH 020/223] Use the static scheduler in ordered clause. Otherwise OpenMP uses a scheduler it thinks is best which can be dynamic or guided. Both sometimes let other threads continue before the block is finished. That will crash the program with high thread counts and a cache that is not large enough to hold old enough frames, which leads to a crash when in some cases like transitions two different frames are used although one is no longer in the cache. The static scheduler always waits until the block is finished before enabling other threads. --- src/Timeline.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Timeline.cpp b/src/Timeline.cpp index 35b91283..34dab1d8 100644 --- a/src/Timeline.cpp +++ b/src/Timeline.cpp @@ -717,7 +717,8 @@ std::shared_ptr Timeline::GetFrame(int64_t requested_frame) #pragma omp parallel { // Loop through all requested frames - #pragma omp for ordered firstprivate(nearby_clips, requested_frame, minimum_frames) + // The scheduler has to be static! + #pragma omp for ordered schedule(static,1) firstprivate(nearby_clips, requested_frame, minimum_frames) for (int64_t frame_number = requested_frame; frame_number < requested_frame + minimum_frames; frame_number++) { // Debug output From f7dd2b18c38612d41f6c05a30e8e0c88c49d010c Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Sat, 8 Sep 2018 16:31:03 -0700 Subject: [PATCH 021/223] First adjustment to later include NVENC (nvidia encoder) --- src/FFmpegWriter.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index d0a542ae..e01e2f83 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -35,6 +35,8 @@ using namespace openshot; #if IS_FFMPEG_3_2 int hw_en_on = 1; // Is set in UI int hw_en_supported = 0; // Is set by FFmpegWriter +AVPixelFormat hw_en_av_pix_fmt = AV_PIX_FMT_NONE; +AVHWDeviceType hw_en_av_device_type = AV_HWDEVICE_TYPE_VAAPI; static AVBufferRef *hw_device_ctx = NULL; AVFrame *hw_frame = NULL; @@ -50,7 +52,7 @@ static int set_hwframe_ctx(AVCodecContext *ctx, AVBufferRef *hw_device_ctx, int6 } frames_ctx = (AVHWFramesContext *)(hw_frames_ref->data); #if defined(__linux__) - frames_ctx->format = AV_PIX_FMT_VAAPI; + frames_ctx->format = hw_en_av_pix_fmt; #elif defined(_WIN32) frames_ctx->format = AV_PIX_FMT_DXVA2_VLD; #elif defined(__APPLE__) @@ -173,6 +175,8 @@ void FFmpegWriter::SetVideoOptions(bool has_video, string codec, Fraction fps, i new_codec = avcodec_find_encoder_by_name(codec.c_str()); hw_en_on = 1; hw_en_supported = 1; + hw_en_av_pix_fmt = AV_PIX_FMT_VAAPI; + hw_en_av_device_type = AV_HWDEVICE_TYPE_VAAPI; } else { new_codec = avcodec_find_encoder_by_name(codec.c_str()); @@ -1212,7 +1216,7 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) if( dev_hw != NULL && access( dev_hw, W_OK ) == -1 ) { dev_hw = NULL; // use default } - if (av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_VAAPI, + if (av_hwdevice_ctx_create(&hw_device_ctx, hw_en_av_device_type, dev_hw, NULL, 0) < 0) { cerr << "FFmpegWriter::open_video : Codec name: " << info.vcodec.c_str() << " ERROR creating\n"; throw InvalidCodec("Could not create hwdevice", path); @@ -1251,7 +1255,7 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) if (hw_en_on && hw_en_supported) { video_codec->max_b_frames = 0; // At least this GPU doesn't support b-frames #if defined(__linux__) - video_codec->pix_fmt = AV_PIX_FMT_VAAPI; + video_codec->pix_fmt = hw_en_av_pix_fmt; #elif defined(_WIN32) video_codec->pix_fmt = AV_PIX_FMT_DXVA2_VLD #elif defined(__APPLE__) From 16c8302f485d530f8c2513c4f96d6fbb43df310e Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Sat, 8 Sep 2018 16:53:53 -0700 Subject: [PATCH 022/223] Basic support for nvidia encode (decode later) --- src/FFmpegWriter.cpp | 55 ++++++++++++++++++-------------------------- 1 file changed, 23 insertions(+), 32 deletions(-) diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index e01e2f83..924f3490 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -47,23 +47,17 @@ static int set_hwframe_ctx(AVCodecContext *ctx, AVBufferRef *hw_device_ctx, int6 int err = 0; if (!(hw_frames_ref = av_hwframe_ctx_alloc(hw_device_ctx))) { - fprintf(stderr, "Failed to create VAAPI frame context.\n"); + fprintf(stderr, "Failed to create HW frame context.\n"); return -1; } frames_ctx = (AVHWFramesContext *)(hw_frames_ref->data); - #if defined(__linux__) frames_ctx->format = hw_en_av_pix_fmt; - #elif defined(_WIN32) - frames_ctx->format = AV_PIX_FMT_DXVA2_VLD; - #elif defined(__APPLE__) - frames_ctx->format = AV_PIX_FMT_QSV; - #endif frames_ctx->sw_format = AV_PIX_FMT_NV12; frames_ctx->width = width; frames_ctx->height = height; frames_ctx->initial_pool_size = 20; if ((err = av_hwframe_ctx_init(hw_frames_ref)) < 0) { - fprintf(stderr, "Failed to initialize VAAPI frame context." + fprintf(stderr, "Failed to initialize HW frame context." "Error code: %s\n",av_err2str(err)); av_buffer_unref(&hw_frames_ref); return err; @@ -178,16 +172,27 @@ void FFmpegWriter::SetVideoOptions(bool has_video, string codec, Fraction fps, i hw_en_av_pix_fmt = AV_PIX_FMT_VAAPI; hw_en_av_device_type = AV_HWDEVICE_TYPE_VAAPI; } - else { - new_codec = avcodec_find_encoder_by_name(codec.c_str()); - hw_en_on = 0; - hw_en_supported = 0; - } + else { + if ( (strcmp(codec.c_str(),"h264_nvenc") == 0)) { + new_codec = avcodec_find_encoder_by_name(codec.c_str()); + hw_en_on = 1; + hw_en_supported = 1; + hw_en_av_pix_fmt = AV_PIX_FMT_CUDA; + hw_en_av_device_type = AV_HWDEVICE_TYPE_CUDA; + } + else { + new_codec = avcodec_find_encoder_by_name(codec.c_str()); + hw_en_on = 0; + hw_en_supported = 0; + } + } #elif defined(_WIN32) if ( (strcmp(codec.c_str(),"h264_dxva2") == 0)) { new_codec = avcodec_find_encoder_by_name(codec.c_str()); hw_en_on = 1; hw_en_supported = 1; + hw_en_av_pix_fmt = AV_PIX_FMT_DXVA2_VLD; + hw_en_av_device_type = AV_HWDEVICE_TYPE_DXVA2; } else { new_codec = avcodec_find_encoder_by_name(codec.c_str()); @@ -199,6 +204,8 @@ void FFmpegWriter::SetVideoOptions(bool has_video, string codec, Fraction fps, i new_codec = avcodec_find_encoder_by_name(codec.c_str()); hw_en_on = 1; hw_en_supported = 1; + hw_en_av_pix_fmt = AV_PIX_FMT_QSV; + hw_en_av_device_type = AV_HWDEVICE_TYPE_QSV; } else { new_codec = avcodec_find_encoder_by_name(codec.c_str()); @@ -1216,24 +1223,14 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) if( dev_hw != NULL && access( dev_hw, W_OK ) == -1 ) { dev_hw = NULL; // use default } + #else + dev_hw = NULL; // use default + #endif if (av_hwdevice_ctx_create(&hw_device_ctx, hw_en_av_device_type, dev_hw, NULL, 0) < 0) { cerr << "FFmpegWriter::open_video : Codec name: " << info.vcodec.c_str() << " ERROR creating\n"; throw InvalidCodec("Could not create hwdevice", path); } - #elif defined(_WIN32) - if (av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_DXVA2, - NULL, NULL, 0) < 0) { - cerr << "FFmpegWriter::open_video : Codec name: " << info.vcodec.c_str() << " ERROR creating\n"; - throw InvalidCodec("Could not create hwdevice", path); - } - #elif defined(__APPLE__) - if (av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_QSV, - NULL, NULL, 0) < 0) { - cerr << "FFmpegWriter::open_video : Codec name: " << info.vcodec.c_str() << " ERROR creating\n"; - throw InvalidCodec("Could not create hwdevice", path); - } - #endif } #endif /* find the video encoder */ @@ -1254,13 +1251,7 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) #if IS_FFMPEG_3_2 if (hw_en_on && hw_en_supported) { video_codec->max_b_frames = 0; // At least this GPU doesn't support b-frames - #if defined(__linux__) video_codec->pix_fmt = hw_en_av_pix_fmt; - #elif defined(_WIN32) - video_codec->pix_fmt = AV_PIX_FMT_DXVA2_VLD - #elif defined(__APPLE__) - video_codec->pix_fmt = AV_PIX_FMT_QSV - #endif video_codec->profile = FF_PROFILE_H264_BASELINE | FF_PROFILE_H264_CONSTRAINED; av_opt_set(video_codec->priv_data,"preset","slow",0); av_opt_set(video_codec->priv_data,"tune","zerolatency",0); From e7c1ced0da1590e79a992d4ac8634edd46bc66a2 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Sat, 8 Sep 2018 21:17:24 -0700 Subject: [PATCH 023/223] Cleanup import video hardware accelerated and first attempt with nvidia cards. Still no error handling when the dimensions of the video are too large --- src/FFmpegReader.cpp | 242 ++++++++++++++++++------------------------- 1 file changed, 98 insertions(+), 144 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 5ee6a7b2..7d9c27ca 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -34,6 +34,8 @@ using namespace openshot; int hw_de_on = 1; // Is set in UI int hw_de_supported = 0; // Is set by FFmpegReader +AVPixelFormat hw_de_av_pix_fmt = AV_PIX_FMT_NONE; +AVHWDeviceType hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; FFmpegReader::FFmpegReader(string path) : last_frame(0), is_seeking(0), seeking_pts(0), seeking_frame(0), seek_count(0), @@ -108,112 +110,60 @@ bool AudioLocation::is_near(AudioLocation location, int samples_per_frame, int64 #if IS_FFMPEG_3_2 #pragma message "You are compiling with experimental hardware decode" -#if defined(__linux__) static enum AVPixelFormat get_vaapi_format(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) { const enum AVPixelFormat *p; for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { - if (*p == AV_PIX_FMT_VAAPI) - return *p; + if (*p == AV_PIX_FMT_VAAPI) { + hw_de_av_pix_fmt = AV_PIX_FMT_VAAPI; + hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; + return *p; + } + if (*p == AV_PIX_FMT_CUDA) { + hw_de_av_pix_fmt = AV_PIX_FMT_CUDA; + hw_de_av_device_type = AV_HWDEVICE_TYPE_CUDA; + return *p; + } } ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (Unable to decode this file using VA-API.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); hw_de_supported = 0; return AV_PIX_FMT_NONE; } -int is_hardware_decode_supported(int codecid) -{ - int ret; - switch (codecid) { - case AV_CODEC_ID_H264: - case AV_CODEC_ID_MPEG2VIDEO: - case AV_CODEC_ID_VC1: - case AV_CODEC_ID_WMV1: - case AV_CODEC_ID_WMV2: - case AV_CODEC_ID_WMV3: - ret = 1; - break; - default : - ret = 0; - break; - } - return ret; -} -#endif - -#if defined(_WIN32) -// Works for Windows 64 and Windows 32 -// FIXME Here goes the detection for Windows -// AV_HWDEVICE_TYPE_DXVA2 AV_PIX_FMT_DXVA2_VLD AV_HWDEVICE_TYPE_D3D11VA AV_PIX_FMT_D3D11 - -static enum AVPixelFormat get_dxva2_format(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) +static enum AVPixelFormat get_dx_format(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) { const enum AVPixelFormat *p; for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { - if (*p == AV_PIX_FMT_DXVA2_VLD) - return *p; + if (*p == AV_PIX_FMT_DXVA2_VLD) { + hw_de_av_pix_fmt = AV_PIX_FMT_DXVA2_VLD; + hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2; + return *p; + } + if (*p == AV_PIX_FMT_D3D11) { + hw_de_av_pix_fmt = AV_PIX_FMT_D3D11; + hw_de_av_device_type = AV_HWDEVICE_TYPE_D3D11VA; + return *p; + } } ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (Unable to decode this file using DXVA2.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); hw_de_supported = 0; + hw_de_av_pix_fmt = AV_PIX_FMT_NONE; return AV_PIX_FMT_NONE; } -int is_hardware_decode_supported(int codecid) -{ - /* int ret; - switch (codecid) { - case AV_CODEC_ID_H264: - case AV_CODEC_ID_MPEG2VIDEO: - case AV_CODEC_ID_VC1: - case AV_CODEC_ID_WMV1: - case AV_CODEC_ID_WMV2: - case AV_CODEC_ID_WMV3: - ret = 1; - break; - default : - ret = 0; - break; - } - return ret;*/ - return 0; -} -#endif - -#if defined(__APPLE__) -// FIXME Here goes the detection for Mac -// Constants for MAC: AV_HWDEVICE_TYPE_QSV AV_PIX_FMT_QSV -int is_hardware_decode_supported(int codecid) -{ -/* int ret; - switch (codecid) { - case AV_CODEC_ID_H264: - case AV_CODEC_ID_MPEG2VIDEO: - case AV_CODEC_ID_VC1: - case AV_CODEC_ID_WMV1: - case AV_CODEC_ID_WMV2: - case AV_CODEC_ID_WMV3: - ret = 1; - break; - default : - ret = 0; - break; - } - return ret;*/ - return 0; -} static int get_qsv_format(AVCodecContext *avctx, const enum AVPixelFormat *pix_fmts) { - while (*pix_fmts != AV_PIX_FMT_NONE) { + /* while (*pix_fmts != AV_PIX_FMT_NONE) { if (*pix_fmts == AV_PIX_FMT_QSV) { DecodeContext *decode = avctx->opaque; AVHWFramesContext *frames_ctx; AVQSVFramesContext *frames_hwctx; int ret; - /* create a pool of surfaces to be used by the decoder */ + // create a pool of surfaces to be used by the decoder avctx->hw_frames_ctx = av_hwframe_ctx_alloc(decode->hw_device_ref); if (!avctx->hw_frames_ctx) return AV_PIX_FMT_NONE; @@ -239,10 +189,40 @@ static int get_qsv_format(AVCodecContext *avctx, const enum AVPixelFormat *pix_f } fprintf(stderr, "The QSV pixel format not offered in get_format()\n"); + */ + const enum AVPixelFormat *p; + for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { + if (*p == AV_PIX_FMT_QSV) { + hw_de_av_pix_fmt = AV_PIX_FMT_QSV; + hw_de_av_device_type = AV_HWDEVICE_TYPE_QSV; + return *p; + } + } + ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (Unable to decode this file using QSV.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); + hw_de_supported = 0; + hw_de_av_pix_fmt = AV_PIX_FMT_NONE; return AV_PIX_FMT_NONE; } -#endif + +int is_hardware_decode_supported(int codecid) +{ + int ret; + switch (codecid) { + case AV_CODEC_ID_H264: + case AV_CODEC_ID_MPEG2VIDEO: + case AV_CODEC_ID_VC1: + case AV_CODEC_ID_WMV1: + case AV_CODEC_ID_WMV2: + case AV_CODEC_ID_WMV3: + ret = 1; + break; + default : + ret = 0; + break; + } + return ret; +} #endif @@ -303,9 +283,7 @@ void FFmpegReader::Open() AVCodec *pCodec = avcodec_find_decoder(codecId); pCodecCtx = AV_GET_CODEC_CONTEXT(pStream, pCodec); #if IS_FFMPEG_3_2 -// #if defined(__linux__) - hw_de_supported = is_hardware_decode_supported(pCodecCtx->codec_id); -// #endif + hw_de_supported = is_hardware_decode_supported(pCodecCtx->codec_id); #endif // Set number of threads equal to number of processors (not to exceed 16) pCodecCtx->thread_count = min(FF_NUM_PROCESSORS, 16); @@ -319,42 +297,36 @@ void FFmpegReader::Open() av_dict_set(&opts, "strict", "experimental", 0); #if IS_FFMPEG_3_2 -// #if defined(__linux__) - if (hw_de_on && hw_de_supported) { - // Open Hardware Acceleration - // Use the hw device given in the environment variable HW_DE_DEVICE_SET or the default if not set - char *dev_hw = getenv( "HW_DE_DEVICE_SET" ); - // Check if it is there and writable - if( dev_hw != NULL && access( dev_hw, W_OK ) == -1 ) { - dev_hw = NULL; // use default - } - hw_device_ctx = NULL; -// FIXME get_XXX_format -// FIXME AV_HWDEVICE_TYPE_.... -// IMPORTANT: The get_format has different names because even for one plattform -// like Linux there are different modes of access like vaapi and vdpau and these -// should be chosen by the user in the future - #if defined(__linux__) - pCodecCtx->get_format = get_vaapi_format; - if (av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_VAAPI, dev_hw, NULL, 0) >= 0) { - #endif - #if defined(_WIN32) - pCodecCtx->get_format = get_dxva2_format; - if (av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_DXVA2, dev_hw, NULL, 0) >= 0) { - #endif - #if defined(__APPLE__) - pCodecCtx->get_format = get_qsv_format; - if (av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_QSV, dev_hw, NULL, 0) >= 0) { - #endif - if (!(pCodecCtx->hw_device_ctx = av_buffer_ref(hw_device_ctx))) { - throw InvalidCodec("Hardware device reference create failed.", path); - } - } - else { - throw InvalidCodec("Hardware device create failed.", path); + if (hw_de_on && hw_de_supported) { + // Open Hardware Acceleration + // Use the hw device given in the environment variable HW_DE_DEVICE_SET or the default if not set + char *dev_hw = getenv( "HW_DE_DEVICE_SET" ); + // Check if it is there and writable + if( dev_hw != NULL && access( dev_hw, W_OK ) == -1 ) { + dev_hw = NULL; // use default + } + hw_device_ctx = NULL; + #if defined(__linux__) + pCodecCtx->get_format = get_vaapi_format; + //if (av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_VAAPI, dev_hw, NULL, 0) >= 0) { + #endif + #if defined(_WIN32) + pCodecCtx->get_format = get_dx_format; + //if (av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_DXVA2, dev_hw, NULL, 0) >= 0) { + #endif + #if defined(__APPLE__) + pCodecCtx->get_format = get_qsv_format; + //if (av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_QSV, dev_hw, NULL, 0) >= 0) { + #endif + if (av_hwdevice_ctx_create(&hw_device_ctx, hw_de_av_device_type, dev_hw, NULL, 0) >= 0) { + if (!(pCodecCtx->hw_device_ctx = av_buffer_ref(hw_device_ctx))) { + throw InvalidCodec("Hardware device reference create failed.", path); } } -// #endif + else { + throw InvalidCodec("Hardware device create failed.", path); + } + } #endif // Open video codec if (avcodec_open2(pCodecCtx, pCodec, &opts) < 0) @@ -446,14 +418,12 @@ void FFmpegReader::Close() avcodec_flush_buffers(pCodecCtx); AV_FREE_CONTEXT(pCodecCtx); #if IS_FFMPEG_3_2 -// #if defined(__linux__) - if (hw_de_on) { - if (hw_device_ctx) { - av_buffer_unref(&hw_device_ctx); - hw_device_ctx = NULL; - } + if (hw_de_on) { + if (hw_device_ctx) { + av_buffer_unref(&hw_device_ctx); + hw_device_ctx = NULL; } -// #endif + } #endif } if (info.has_audio) @@ -949,37 +919,25 @@ bool FFmpegReader::GetAVFrame() } else { AVFrame *next_frame2; -// #if defined(__linux__) if (hw_de_on && hw_de_supported) { next_frame2 = AV_ALLOCATE_FRAME(); } else -// #endif { next_frame2 = next_frame; } pFrame = new AVFrame(); while (ret >= 0) { ret = avcodec_receive_frame(pCodecCtx, next_frame2); - if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { - break; - } + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + break; + } if (ret != 0) { ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (invalid return frame received)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); } -// #if defined(__linux__) if (hw_de_on && hw_de_supported) { int err; -// FIXME AV_PIX_FMT_VAAPI - #if defined(__linux__) - if (next_frame2->format == AV_PIX_FMT_VAAPI) { - #endif - #if defined(__WIN32__) - if (next_frame2->format == AV_PIX_FMT_DXVA2_VLD) { - #endif - #if defined(__APPLE__) - if (next_frame2->format == AV_PIX_FMT_QSV) { - #endif + if (next_frame2->format == hw_de_av_pix_fmt) { next_frame->format = AV_PIX_FMT_YUV420P; if ((err = av_hwframe_transfer_data(next_frame,next_frame2,0)) < 0) { ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (Failed to transfer data to output frame)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); @@ -990,11 +948,9 @@ bool FFmpegReader::GetAVFrame() } } else -// #endif { // No hardware acceleration used -> no copy from GPU memory needed next_frame = next_frame2; } - //} // TODO also handle possible further frames // Use only the first frame like avcodec_decode_video2 if (frameFinished == 0 ) { @@ -1009,11 +965,9 @@ bool FFmpegReader::GetAVFrame() } } } -// #if defined(__linux__) - if (hw_de_on && hw_de_supported) { - AV_FREE_FRAME(&next_frame2); - } -// #endif + if (hw_de_on && hw_de_supported) { + AV_FREE_FRAME(&next_frame2); + } } #else avcodec_decode_video2(pCodecCtx, next_frame, &frameFinished, packet); From 0191ff5dbb6a4934e5a28a65d0488f626def93ae Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Sat, 8 Sep 2018 21:32:04 -0700 Subject: [PATCH 024/223] Further cleanup --- src/FFmpegReader.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 7d9c27ca..5602e40c 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -300,23 +300,23 @@ void FFmpegReader::Open() if (hw_de_on && hw_de_supported) { // Open Hardware Acceleration // Use the hw device given in the environment variable HW_DE_DEVICE_SET or the default if not set - char *dev_hw = getenv( "HW_DE_DEVICE_SET" ); + char *dev_hw = NULL; + #if defined(__linux__) + dev_hw = getenv( "HW_DE_DEVICE_SET" ); // Check if it is there and writable if( dev_hw != NULL && access( dev_hw, W_OK ) == -1 ) { dev_hw = NULL; // use default } + #endif hw_device_ctx = NULL; #if defined(__linux__) pCodecCtx->get_format = get_vaapi_format; - //if (av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_VAAPI, dev_hw, NULL, 0) >= 0) { #endif #if defined(_WIN32) pCodecCtx->get_format = get_dx_format; - //if (av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_DXVA2, dev_hw, NULL, 0) >= 0) { #endif #if defined(__APPLE__) pCodecCtx->get_format = get_qsv_format; - //if (av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_QSV, dev_hw, NULL, 0) >= 0) { #endif if (av_hwdevice_ctx_create(&hw_device_ctx, hw_de_av_device_type, dev_hw, NULL, 0) >= 0) { if (!(pCodecCtx->hw_device_ctx = av_buffer_ref(hw_device_ctx))) { From 36cbba2a3d8f5df2aebc0c867ce9a52ded8790f2 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Sat, 8 Sep 2018 21:55:23 -0700 Subject: [PATCH 025/223] More cleanup --- src/FFmpegReader.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 5602e40c..70b235df 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -111,6 +111,7 @@ bool AudioLocation::is_near(AudioLocation location, int samples_per_frame, int64 #if IS_FFMPEG_3_2 #pragma message "You are compiling with experimental hardware decode" +#if defined(__linux__) static enum AVPixelFormat get_vaapi_format(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) { const enum AVPixelFormat *p; @@ -131,7 +132,9 @@ static enum AVPixelFormat get_vaapi_format(AVCodecContext *ctx, const enum AVPix hw_de_supported = 0; return AV_PIX_FMT_NONE; } +#endif +#if defined(_WIN32) static enum AVPixelFormat get_dx_format(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) { const enum AVPixelFormat *p; @@ -153,7 +156,9 @@ static enum AVPixelFormat get_dx_format(AVCodecContext *ctx, const enum AVPixelF hw_de_av_pix_fmt = AV_PIX_FMT_NONE; return AV_PIX_FMT_NONE; } +#endif +#if defined(__APPLE__) static int get_qsv_format(AVCodecContext *avctx, const enum AVPixelFormat *pix_fmts) { /* while (*pix_fmts != AV_PIX_FMT_NONE) { @@ -204,6 +209,7 @@ static int get_qsv_format(AVCodecContext *avctx, const enum AVPixelFormat *pix_f hw_de_av_pix_fmt = AV_PIX_FMT_NONE; return AV_PIX_FMT_NONE; } +#endif int is_hardware_decode_supported(int codecid) { From e7c94e700add4af1ba2235efc6b886083e070ab8 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Sat, 8 Sep 2018 22:19:41 -0700 Subject: [PATCH 026/223] hide dx11 --- src/FFmpegReader.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 70b235df..9f4bb514 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -145,11 +145,11 @@ static enum AVPixelFormat get_dx_format(AVCodecContext *ctx, const enum AVPixelF hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2; return *p; } - if (*p == AV_PIX_FMT_D3D11) { +/* if (*p == AV_PIX_FMT_D3D11) { hw_de_av_pix_fmt = AV_PIX_FMT_D3D11; hw_de_av_device_type = AV_HWDEVICE_TYPE_D3D11VA; return *p; - } + }*/ } ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (Unable to decode this file using DXVA2.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); hw_de_supported = 0; From d6f52ead3bfd5ae3646e6226c4371fa7d52c644e Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Sat, 8 Sep 2018 22:30:16 -0700 Subject: [PATCH 027/223] Only use the hw accel variables when ffmpeg >= 3.2 --- src/FFmpegReader.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 9f4bb514..c067f3fe 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -32,10 +32,12 @@ using namespace openshot; +#if IS_FFMPEG_3_2 int hw_de_on = 1; // Is set in UI int hw_de_supported = 0; // Is set by FFmpegReader AVPixelFormat hw_de_av_pix_fmt = AV_PIX_FMT_NONE; AVHWDeviceType hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; +#endif FFmpegReader::FFmpegReader(string path) : last_frame(0), is_seeking(0), seeking_pts(0), seeking_frame(0), seek_count(0), From 2a80ccacaac4e028664de5968ede58bb4f997e6a Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Sat, 8 Sep 2018 22:57:46 -0700 Subject: [PATCH 028/223] Let hw_de_on be visible to all versions of ffmpeg --- src/FFmpegReader.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index c067f3fe..4ce92889 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -32,9 +32,9 @@ using namespace openshot; -#if IS_FFMPEG_3_2 int hw_de_on = 1; // Is set in UI int hw_de_supported = 0; // Is set by FFmpegReader +#if IS_FFMPEG_3_2 AVPixelFormat hw_de_av_pix_fmt = AV_PIX_FMT_NONE; AVHWDeviceType hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; #endif @@ -147,11 +147,11 @@ static enum AVPixelFormat get_dx_format(AVCodecContext *ctx, const enum AVPixelF hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2; return *p; } -/* if (*p == AV_PIX_FMT_D3D11) { + if (*p == AV_PIX_FMT_D3D11) { hw_de_av_pix_fmt = AV_PIX_FMT_D3D11; hw_de_av_device_type = AV_HWDEVICE_TYPE_D3D11VA; return *p; - }*/ + } } ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (Unable to decode this file using DXVA2.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); hw_de_supported = 0; @@ -291,7 +291,9 @@ void FFmpegReader::Open() AVCodec *pCodec = avcodec_find_decoder(codecId); pCodecCtx = AV_GET_CODEC_CONTEXT(pStream, pCodec); #if IS_FFMPEG_3_2 - hw_de_supported = is_hardware_decode_supported(pCodecCtx->codec_id); + if (hw_de_on) { + hw_de_supported = is_hardware_decode_supported(pCodecCtx->codec_id); + } #endif // Set number of threads equal to number of processors (not to exceed 16) pCodecCtx->thread_count = min(FF_NUM_PROCESSORS, 16); @@ -309,13 +311,11 @@ void FFmpegReader::Open() // Open Hardware Acceleration // Use the hw device given in the environment variable HW_DE_DEVICE_SET or the default if not set char *dev_hw = NULL; - #if defined(__linux__) dev_hw = getenv( "HW_DE_DEVICE_SET" ); // Check if it is there and writable if( dev_hw != NULL && access( dev_hw, W_OK ) == -1 ) { dev_hw = NULL; // use default } - #endif hw_device_ctx = NULL; #if defined(__linux__) pCodecCtx->get_format = get_vaapi_format; From c29bf21c75ee2342709021ded86e262f37f0f176 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Sun, 9 Sep 2018 09:05:16 -0700 Subject: [PATCH 029/223] Simplifications of FFmpegReader and start of setting parameters per input file --- include/FFmpegReader.h | 5 +++ src/FFmpegReader.cpp | 83 ++++-------------------------------------- 2 files changed, 12 insertions(+), 76 deletions(-) diff --git a/include/FFmpegReader.h b/include/FFmpegReader.h index fcc995ae..caf68e5e 100644 --- a/include/FFmpegReader.h +++ b/include/FFmpegReader.h @@ -146,6 +146,11 @@ namespace openshot int64_t largest_frame_processed; int64_t current_video_frame; // can't reliably use PTS of video to determine this + //int hw_de_supported = 0; // Is set by FFmpegReader + //AVPixelFormat hw_de_av_pix_fmt = AV_PIX_FMT_NONE; + + int is_hardware_decode_supported(int codecid); + /// Check for the correct frames per second value by scanning the 1st few seconds of video packets. void CheckFPS(); diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 4ce92889..083de926 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -113,12 +113,12 @@ bool AudioLocation::is_near(AudioLocation location, int samples_per_frame, int64 #if IS_FFMPEG_3_2 #pragma message "You are compiling with experimental hardware decode" -#if defined(__linux__) -static enum AVPixelFormat get_vaapi_format(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) +static enum AVPixelFormat get_hw_dec_format(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) { const enum AVPixelFormat *p; for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { + //Linux formats if (*p == AV_PIX_FMT_VAAPI) { hw_de_av_pix_fmt = AV_PIX_FMT_VAAPI; hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; @@ -129,19 +129,7 @@ static enum AVPixelFormat get_vaapi_format(AVCodecContext *ctx, const enum AVPix hw_de_av_device_type = AV_HWDEVICE_TYPE_CUDA; return *p; } - } - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (Unable to decode this file using VA-API.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); - hw_de_supported = 0; - return AV_PIX_FMT_NONE; -} -#endif - -#if defined(_WIN32) -static enum AVPixelFormat get_dx_format(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) -{ - const enum AVPixelFormat *p; - - for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { + // Windows formats if (*p == AV_PIX_FMT_DXVA2_VLD) { hw_de_av_pix_fmt = AV_PIX_FMT_DXVA2_VLD; hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2; @@ -152,68 +140,19 @@ static enum AVPixelFormat get_dx_format(AVCodecContext *ctx, const enum AVPixelF hw_de_av_device_type = AV_HWDEVICE_TYPE_D3D11VA; return *p; } - } - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (Unable to decode this file using DXVA2.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); - hw_de_supported = 0; - hw_de_av_pix_fmt = AV_PIX_FMT_NONE; - return AV_PIX_FMT_NONE; -} -#endif - -#if defined(__APPLE__) -static int get_qsv_format(AVCodecContext *avctx, const enum AVPixelFormat *pix_fmts) -{ - /* while (*pix_fmts != AV_PIX_FMT_NONE) { - if (*pix_fmts == AV_PIX_FMT_QSV) { - DecodeContext *decode = avctx->opaque; - AVHWFramesContext *frames_ctx; - AVQSVFramesContext *frames_hwctx; - int ret; - - // create a pool of surfaces to be used by the decoder - avctx->hw_frames_ctx = av_hwframe_ctx_alloc(decode->hw_device_ref); - if (!avctx->hw_frames_ctx) - return AV_PIX_FMT_NONE; - frames_ctx = (AVHWFramesContext*)avctx->hw_frames_ctx->data; - frames_hwctx = frames_ctx->hwctx; - - frames_ctx->format = AV_PIX_FMT_QSV; - frames_ctx->sw_format = avctx->sw_pix_fmt; - frames_ctx->width = FFALIGN(avctx->coded_width, 32); - frames_ctx->height = FFALIGN(avctx->coded_height, 32); - frames_ctx->initial_pool_size = 32; - - frames_hwctx->frame_type = MFX_MEMTYPE_VIDEO_MEMORY_DECODER_TARGET; - - ret = av_hwframe_ctx_init(avctx->hw_frames_ctx); - if (ret < 0) - return AV_PIX_FMT_NONE; - - return AV_PIX_FMT_QSV; - } - - pix_fmts++; - } - - fprintf(stderr, "The QSV pixel format not offered in get_format()\n"); - */ - const enum AVPixelFormat *p; - - for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { + //Mac format if (*p == AV_PIX_FMT_QSV) { hw_de_av_pix_fmt = AV_PIX_FMT_QSV; hw_de_av_device_type = AV_HWDEVICE_TYPE_QSV; return *p; } } - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (Unable to decode this file using QSV.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); + ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); hw_de_supported = 0; - hw_de_av_pix_fmt = AV_PIX_FMT_NONE; return AV_PIX_FMT_NONE; } -#endif -int is_hardware_decode_supported(int codecid) +int FFmpegReader::is_hardware_decode_supported(int codecid) { int ret; switch (codecid) { @@ -317,15 +256,7 @@ void FFmpegReader::Open() dev_hw = NULL; // use default } hw_device_ctx = NULL; - #if defined(__linux__) - pCodecCtx->get_format = get_vaapi_format; - #endif - #if defined(_WIN32) - pCodecCtx->get_format = get_dx_format; - #endif - #if defined(__APPLE__) - pCodecCtx->get_format = get_qsv_format; - #endif + pCodecCtx->get_format = get_hw_dec_format; if (av_hwdevice_ctx_create(&hw_device_ctx, hw_de_av_device_type, dev_hw, NULL, 0) >= 0) { if (!(pCodecCtx->hw_device_ctx = av_buffer_ref(hw_device_ctx))) { throw InvalidCodec("Hardware device reference create failed.", path); From aff1be93b8056fdd15bd062aba71d407e6dab8af Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Sun, 9 Sep 2018 10:54:31 -0700 Subject: [PATCH 030/223] Support for multiple input files --- include/FFmpegReader.h | 7 +++++-- src/FFmpegReader.cpp | 32 ++++++++++++++++++-------------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/include/FFmpegReader.h b/include/FFmpegReader.h index caf68e5e..f571af73 100644 --- a/include/FFmpegReader.h +++ b/include/FFmpegReader.h @@ -146,8 +146,11 @@ namespace openshot int64_t largest_frame_processed; int64_t current_video_frame; // can't reliably use PTS of video to determine this - //int hw_de_supported = 0; // Is set by FFmpegReader - //AVPixelFormat hw_de_av_pix_fmt = AV_PIX_FMT_NONE; + int hw_de_supported = 0; // Is set by FFmpegReader + #if IS_FFMPEG_3_2 + AVPixelFormat hw_de_av_pix_fmt = AV_PIX_FMT_NONE; + AVHWDeviceType hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; + #endif int is_hardware_decode_supported(int codecid); diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 083de926..4ec46191 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -33,10 +33,10 @@ using namespace openshot; int hw_de_on = 1; // Is set in UI -int hw_de_supported = 0; // Is set by FFmpegReader +//int hw_de_supported = 0; // Is set by FFmpegReader #if IS_FFMPEG_3_2 -AVPixelFormat hw_de_av_pix_fmt = AV_PIX_FMT_NONE; -AVHWDeviceType hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; +AVPixelFormat hw_de_av_pix_fmt_global = AV_PIX_FMT_NONE; +AVHWDeviceType hw_de_av_device_type_global = AV_HWDEVICE_TYPE_VAAPI; #endif FFmpegReader::FFmpegReader(string path) @@ -120,35 +120,35 @@ static enum AVPixelFormat get_hw_dec_format(AVCodecContext *ctx, const enum AVPi for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { //Linux formats if (*p == AV_PIX_FMT_VAAPI) { - hw_de_av_pix_fmt = AV_PIX_FMT_VAAPI; - hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; + hw_de_av_pix_fmt_global = AV_PIX_FMT_VAAPI; + hw_de_av_device_type_global = AV_HWDEVICE_TYPE_VAAPI; return *p; } if (*p == AV_PIX_FMT_CUDA) { - hw_de_av_pix_fmt = AV_PIX_FMT_CUDA; - hw_de_av_device_type = AV_HWDEVICE_TYPE_CUDA; + hw_de_av_pix_fmt_global = AV_PIX_FMT_CUDA; + hw_de_av_device_type_global = AV_HWDEVICE_TYPE_CUDA; return *p; } // Windows formats if (*p == AV_PIX_FMT_DXVA2_VLD) { - hw_de_av_pix_fmt = AV_PIX_FMT_DXVA2_VLD; - hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2; + hw_de_av_pix_fmt_global = AV_PIX_FMT_DXVA2_VLD; + hw_de_av_device_type_global = AV_HWDEVICE_TYPE_DXVA2; return *p; } if (*p == AV_PIX_FMT_D3D11) { - hw_de_av_pix_fmt = AV_PIX_FMT_D3D11; - hw_de_av_device_type = AV_HWDEVICE_TYPE_D3D11VA; + hw_de_av_pix_fmt_global = AV_PIX_FMT_D3D11; + hw_de_av_device_type_global = AV_HWDEVICE_TYPE_D3D11VA; return *p; } //Mac format if (*p == AV_PIX_FMT_QSV) { - hw_de_av_pix_fmt = AV_PIX_FMT_QSV; - hw_de_av_device_type = AV_HWDEVICE_TYPE_QSV; + hw_de_av_pix_fmt_global = AV_PIX_FMT_QSV; + hw_de_av_device_type_global = AV_HWDEVICE_TYPE_QSV; return *p; } } ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); - hw_de_supported = 0; + //hw_de_supported = 0; return AV_PIX_FMT_NONE; } @@ -853,6 +853,10 @@ bool FFmpegReader::GetAVFrame() ret = avcodec_send_packet(pCodecCtx, packet); + // Get the format from the variables set in get_hw_dec_format + hw_de_av_pix_fmt = hw_de_av_pix_fmt_global; + hw_de_av_device_type = hw_de_av_device_type_global; + if (ret < 0 || ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (Packet not sent)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); } From f8fed171cef786c2dc4e572207ed4c361162d19f Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Sun, 9 Sep 2018 12:57:04 -0700 Subject: [PATCH 031/223] More code cleanup (easier to read) Comment included with start of error handling --- src/FFmpegReader.cpp | 60 +++++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 4ec46191..4dabce7e 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -118,37 +118,35 @@ static enum AVPixelFormat get_hw_dec_format(AVCodecContext *ctx, const enum AVPi const enum AVPixelFormat *p; for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { - //Linux formats - if (*p == AV_PIX_FMT_VAAPI) { - hw_de_av_pix_fmt_global = AV_PIX_FMT_VAAPI; - hw_de_av_device_type_global = AV_HWDEVICE_TYPE_VAAPI; - return *p; - } - if (*p == AV_PIX_FMT_CUDA) { - hw_de_av_pix_fmt_global = AV_PIX_FMT_CUDA; - hw_de_av_device_type_global = AV_HWDEVICE_TYPE_CUDA; - return *p; - } - // Windows formats - if (*p == AV_PIX_FMT_DXVA2_VLD) { - hw_de_av_pix_fmt_global = AV_PIX_FMT_DXVA2_VLD; - hw_de_av_device_type_global = AV_HWDEVICE_TYPE_DXVA2; - return *p; - } - if (*p == AV_PIX_FMT_D3D11) { - hw_de_av_pix_fmt_global = AV_PIX_FMT_D3D11; - hw_de_av_device_type_global = AV_HWDEVICE_TYPE_D3D11VA; - return *p; - } - //Mac format - if (*p == AV_PIX_FMT_QSV) { - hw_de_av_pix_fmt_global = AV_PIX_FMT_QSV; - hw_de_av_device_type_global = AV_HWDEVICE_TYPE_QSV; - return *p; + switch (*p) { + case AV_PIX_FMT_VAAPI: + hw_de_av_pix_fmt_global = AV_PIX_FMT_VAAPI; + hw_de_av_device_type_global = AV_HWDEVICE_TYPE_VAAPI; + return *p; + break; + case AV_PIX_FMT_CUDA: + hw_de_av_pix_fmt_global = AV_PIX_FMT_CUDA; + hw_de_av_device_type_global = AV_HWDEVICE_TYPE_CUDA; + return *p; + break; + case AV_PIX_FMT_DXVA2_VLD: + hw_de_av_pix_fmt_global = AV_PIX_FMT_DXVA2_VLD; + hw_de_av_device_type_global = AV_HWDEVICE_TYPE_DXVA2; + return *p; + break; + case AV_PIX_FMT_D3D11: + hw_de_av_pix_fmt_global = AV_PIX_FMT_D3D11; + hw_de_av_device_type_global = AV_HWDEVICE_TYPE_D3D11VA; + return *p; + break; + case AV_PIX_FMT_QSV: + hw_de_av_pix_fmt_global = AV_PIX_FMT_QSV; + hw_de_av_device_type_global = AV_HWDEVICE_TYPE_QSV; + return *p; + break; } } ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); - //hw_de_supported = 0; return AV_PIX_FMT_NONE; } @@ -266,6 +264,12 @@ void FFmpegReader::Open() throw InvalidCodec("Hardware device create failed.", path); } } +/* // Check to see if the hardware supports that file (size!) + AVHWFramesConstraints *constraints = NULL; + constraints = av_hwdevice_get_hwframe_constraints(hw_device_ctx->device_ref,hwconfig); + if (constraints) + av_hwframe_constraints_free(&constraints); +*/ #endif // Open video codec if (avcodec_open2(pCodecCtx, pCodec, &opts) < 0) From e879188a7d58f09877c4531f2ef0eb0e2d2800cf Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Tue, 11 Sep 2018 00:40:31 -0500 Subject: [PATCH 032/223] FFmpeg 3 & 4 support, Travis CI support, OpenMP schedule change (#160) * FFmpeg4 support. Compile warnings fixes. Credit goes to many people, including ferdnyc, peterM, and other awesome folks! * Adding environment checking to enable/disable omp taskwait after each video/audio frame is processed. This is experimental for some users with crashes. * Moving `omp taskwait` to after the ProcessVideoPacket() method, since that is the only place it is useful. * Fixing crashes on missing Clip source file, and changing FFmpeg scaling algorthm from SWS_BILINEAR to SWS_LANCZOS (for higher quality scaling) * Update FindFFmpeg.cmake module, and updating build script. Also enabling debug builds. * Updating experimental travis build script * Fixed unit test for newer version of FFmpeg (audio resampling) * Experimental travis multiple jobs * Adding OMP schedule hint (thanks PeterM), which prevents crashes in some circumstances. --- .travis.yml | 49 ++++++ cmake/Modules/FindFFmpeg.cmake | 306 +++++++++++++++++---------------- include/CrashHandler.h | 8 +- include/FFmpegReader.h | 1 + include/FFmpegUtilities.h | 76 +++++++- include/FFmpegWriter.h | 4 +- include/Frame.h | 2 +- include/FrameMapper.h | 2 +- include/OpenMPUtilities.h | 22 ++- include/ZmqLogger.h | 12 +- src/CMakeLists.txt | 213 ++++++++++++++--------- src/Clip.cpp | 12 +- src/EffectInfo.cpp | 1 + src/FFmpegReader.cpp | 51 ++++-- src/FFmpegWriter.cpp | 53 ++++-- src/FrameMapper.cpp | 15 +- src/Timeline.cpp | 33 ++-- tests/FrameMapper_Tests.cpp | 6 +- tests/ReaderBase_Tests.cpp | 4 +- 19 files changed, 567 insertions(+), 303 deletions(-) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..fa191b2b --- /dev/null +++ b/.travis.yml @@ -0,0 +1,49 @@ +dist: trusty + +matrix: + include: + - language: cpp + name: "FFmpeg 2" + before_script: + - sudo add-apt-repository ppa:openshot.developers/libopenshot-daily -y + - sudo add-apt-repository ppa:beineri/opt-qt58-trusty -y + - sudo apt-get update -qq + - sudo apt-get install gcc-4.8 cmake libavcodec-dev libavformat-dev libswscale-dev libavresample-dev libavutil-dev libopenshot-audio-dev libopenshot-dev libfdk-aac-dev libfdk-aac-dev libjsoncpp-dev libmagick++-dev libopenshot-audio-dev libunittest++-dev libzmq3-dev pkg-config python3-dev qtbase5-dev qtmultimedia5-dev swig -y + - sudo apt autoremove -y + script: + - mkdir -p build; cd build; + - cmake -D"CMAKE_BUILD_TYPE:STRING=Debug" ../ + - make VERBOSE=1 + - make test + + - language: cpp + name: "FFmpeg 3" + before_script: + - sudo add-apt-repository ppa:openshot.developers/libopenshot-daily -y + - sudo add-apt-repository ppa:beineri/opt-qt58-trusty -y + - sudo add-apt-repository ppa:jonathonf/ffmpeg-3 -y + - sudo apt-get update -qq + - sudo apt-get install gcc-4.8 cmake libavcodec-dev libavformat-dev libswscale-dev libavresample-dev libavutil-dev libopenshot-audio-dev libopenshot-dev libfdk-aac-dev libfdk-aac-dev libjsoncpp-dev libmagick++-dev libopenshot-audio-dev libunittest++-dev libzmq3-dev pkg-config python3-dev qtbase5-dev qtmultimedia5-dev swig -y + - sudo apt autoremove -y + script: + - mkdir -p build; cd build; + - cmake -D"CMAKE_BUILD_TYPE:STRING=Debug" ../ + - make VERBOSE=1 + - make test + + - language: cpp + name: "FFmpeg 4" + before_script: + - sudo add-apt-repository ppa:openshot.developers/libopenshot-daily -y + - sudo add-apt-repository ppa:beineri/opt-qt58-trusty -y + - sudo add-apt-repository ppa:jonathonf/ffmpeg -y + - sudo add-apt-repository ppa:jonathonf/ffmpeg-4 -y + - sudo add-apt-repository ppa:jonathonf/backports -y + - sudo apt-get update -qq + - sudo apt-get install gcc-4.8 cmake libavcodec58 libavformat58 libavcodec-dev libavformat-dev libswscale-dev libavresample-dev libavutil-dev libopenshot-audio-dev libopenshot-dev libfdk-aac-dev libfdk-aac-dev libjsoncpp-dev libmagick++-dev libopenshot-audio-dev libunittest++-dev libzmq3-dev pkg-config python3-dev qtbase5-dev qtmultimedia5-dev swig -y + - sudo apt autoremove -y + script: + - mkdir -p build; cd build; + - cmake -D"CMAKE_BUILD_TYPE:STRING=Debug" ../ + - make VERBOSE=1 + - make test diff --git a/cmake/Modules/FindFFmpeg.cmake b/cmake/Modules/FindFFmpeg.cmake index 4af6cc93..34f0a7bd 100644 --- a/cmake/Modules/FindFFmpeg.cmake +++ b/cmake/Modules/FindFFmpeg.cmake @@ -1,151 +1,161 @@ -# - Try to find FFMPEG +# vim: ts=2 sw=2 +# - Try to find the required ffmpeg components(default: AVFORMAT, AVUTIL, AVCODEC) +# # Once done this will define -# -# FFMPEG_FOUND - system has FFMPEG -# FFMPEG_INCLUDE_DIR - the include directory -# FFMPEG_LIBRARY_DIR - the directory containing the libraries -# FFMPEG_LIBRARIES - Link these to use FFMPEG -# - -# FindAvformat -FIND_PATH( AVFORMAT_INCLUDE_DIR libavformat/avformat.h - PATHS /usr/include/ - /usr/include/ffmpeg/ - $ENV{FFMPEGDIR}/include/ - $ENV{FFMPEGDIR}/include/ffmpeg/ ) - -FIND_LIBRARY( AVFORMAT_LIBRARY avformat avformat-55 avformat-57 - PATHS /usr/lib/ - /usr/lib/ffmpeg/ - $ENV{FFMPEGDIR}/lib/ - $ENV{FFMPEGDIR}/lib/ffmpeg/ - $ENV{FFMPEGDIR}/bin/ ) - -#FindAvcodec -FIND_PATH( AVCODEC_INCLUDE_DIR libavcodec/avcodec.h - PATHS /usr/include/ - /usr/include/ffmpeg/ - $ENV{FFMPEGDIR}/include/ - $ENV{FFMPEGDIR}/include/ffmpeg/ ) - -FIND_LIBRARY( AVCODEC_LIBRARY avcodec avcodec-55 avcodec-57 - PATHS /usr/lib/ - /usr/lib/ffmpeg/ - $ENV{FFMPEGDIR}/lib/ - $ENV{FFMPEGDIR}/lib/ffmpeg/ - $ENV{FFMPEGDIR}/bin/ ) - -#FindAvutil -FIND_PATH( AVUTIL_INCLUDE_DIR libavutil/avutil.h - PATHS /usr/include/ - /usr/include/ffmpeg/ - $ENV{FFMPEGDIR}/include/ - $ENV{FFMPEGDIR}/include/ffmpeg/ ) - -FIND_LIBRARY( AVUTIL_LIBRARY avutil avutil-52 avutil-55 - PATHS /usr/lib/ - /usr/lib/ffmpeg/ - $ENV{FFMPEGDIR}/lib/ - $ENV{FFMPEGDIR}/lib/ffmpeg/ - $ENV{FFMPEGDIR}/bin/ ) - -#FindAvdevice -FIND_PATH( AVDEVICE_INCLUDE_DIR libavdevice/avdevice.h - PATHS /usr/include/ - /usr/include/ffmpeg/ - $ENV{FFMPEGDIR}/include/ - $ENV{FFMPEGDIR}/include/ffmpeg/ ) - -FIND_LIBRARY( AVDEVICE_LIBRARY avdevice avdevice-55 avdevice-56 - PATHS /usr/lib/ - /usr/lib/ffmpeg/ - $ENV{FFMPEGDIR}/lib/ - $ENV{FFMPEGDIR}/lib/ffmpeg/ - $ENV{FFMPEGDIR}/bin/ ) - -#FindSwscale -FIND_PATH( SWSCALE_INCLUDE_DIR libswscale/swscale.h - PATHS /usr/include/ - /usr/include/ffmpeg/ - $ENV{FFMPEGDIR}/include/ - $ENV{FFMPEGDIR}/include/ffmpeg/ ) - -FIND_LIBRARY( SWSCALE_LIBRARY swscale swscale-2 swscale-4 - PATHS /usr/lib/ - /usr/lib/ffmpeg/ - $ENV{FFMPEGDIR}/lib/ - $ENV{FFMPEGDIR}/lib/ffmpeg/ - $ENV{FFMPEGDIR}/bin/ ) - -#FindAvresample -FIND_PATH( AVRESAMPLE_INCLUDE_DIR libavresample/avresample.h - PATHS /usr/include/ - /usr/include/ffmpeg/ - $ENV{FFMPEGDIR}/include/ - $ENV{FFMPEGDIR}/include/ffmpeg/ ) - -FIND_LIBRARY( AVRESAMPLE_LIBRARY avresample avresample-2 avresample-3 - PATHS /usr/lib/ - /usr/lib/ffmpeg/ - $ENV{FFMPEGDIR}/lib/ - $ENV{FFMPEGDIR}/lib/ffmpeg/ - $ENV{FFMPEGDIR}/bin/ ) - -SET( FFMPEG_FOUND FALSE ) - -IF ( AVFORMAT_INCLUDE_DIR AND AVFORMAT_LIBRARY ) - SET ( AVFORMAT_FOUND TRUE ) -ENDIF ( AVFORMAT_INCLUDE_DIR AND AVFORMAT_LIBRARY ) - -IF ( AVCODEC_INCLUDE_DIR AND AVCODEC_LIBRARY ) - SET ( AVCODEC_FOUND TRUE) -ENDIF ( AVCODEC_INCLUDE_DIR AND AVCODEC_LIBRARY ) - -IF ( AVUTIL_INCLUDE_DIR AND AVUTIL_LIBRARY ) - SET ( AVUTIL_FOUND TRUE ) -ENDIF ( AVUTIL_INCLUDE_DIR AND AVUTIL_LIBRARY ) - -IF ( AVDEVICE_INCLUDE_DIR AND AVDEVICE_LIBRARY ) - SET ( AVDEVICE_FOUND TRUE ) -ENDIF ( AVDEVICE_INCLUDE_DIR AND AVDEVICE_LIBRARY ) - -IF ( SWSCALE_INCLUDE_DIR AND SWSCALE_LIBRARY ) - SET ( SWSCALE_FOUND TRUE ) -ENDIF ( SWSCALE_INCLUDE_DIR AND SWSCALE_LIBRARY ) - -IF ( AVRESAMPLE_INCLUDE_DIR AND AVRESAMPLE_LIBRARY ) - SET ( AVRESAMPLE_FOUND TRUE ) -ENDIF ( AVRESAMPLE_INCLUDE_DIR AND AVRESAMPLE_LIBRARY ) - -IF ( AVFORMAT_INCLUDE_DIR OR AVCODEC_INCLUDE_DIR OR AVUTIL_INCLUDE_DIR OR AVDEVICE_FOUND OR SWSCALE_FOUND OR AVRESAMPLE_FOUND ) - - SET ( FFMPEG_FOUND TRUE ) - - SET ( FFMPEG_INCLUDE_DIR - ${AVFORMAT_INCLUDE_DIR} - ${AVCODEC_INCLUDE_DIR} - ${AVUTIL_INCLUDE_DIR} - ${AVDEVICE_INCLUDE_DIR} - ${SWSCALE_INCLUDE_DIR} - ${AVRESAMPLE_INCLUDE_DIR} ) - - SET ( FFMPEG_LIBRARIES - ${AVFORMAT_LIBRARY} - ${AVCODEC_LIBRARY} - ${AVUTIL_LIBRARY} - ${AVDEVICE_LIBRARY} - ${SWSCALE_LIBRARY} - ${AVRESAMPLE_LIBRARY} ) - -ENDIF ( AVFORMAT_INCLUDE_DIR OR AVCODEC_INCLUDE_DIR OR AVUTIL_INCLUDE_DIR OR AVDEVICE_FOUND OR SWSCALE_FOUND OR AVRESAMPLE_FOUND ) - -MARK_AS_ADVANCED( - FFMPEG_LIBRARY_DIR - FFMPEG_INCLUDE_DIR -) +# FFMPEG_FOUND - System has the all required components. +# FFMPEG_INCLUDE_DIRS - Include directory necessary for using the required components headers. +# FFMPEG_LIBRARIES - Link these to use the required ffmpeg components. +# FFMPEG_DEFINITIONS - Compiler switches required for using the required ffmpeg components. +# +# For each of the components it will additionally set. +# - AVCODEC +# - AVDEVICE +# - AVFORMAT +# - AVFILTER +# - AVUTIL +# - POSTPROC +# - SWSCALE +# - SWRESAMPLE +# - AVRESAMPLE +# the following variables will be defined +# _FOUND - System has +# _INCLUDE_DIRS - Include directory necessary for using the headers +# _LIBRARIES - Link these to use +# _DEFINITIONS - Compiler switches required for using +# _VERSION - The components version +# +# Copyright (c) 2006, Matthias Kretz, +# Copyright (c) 2008, Alexander Neundorf, +# Copyright (c) 2011, Michael Jansen, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. include(FindPackageHandleStandardArgs) -# handle the QUIETLY and REQUIRED arguments and set FFMPEG_FOUND to TRUE -# if all listed variables are TRUE -find_package_handle_standard_args(FFMPEG DEFAULT_MSG - FFMPEG_LIBRARIES FFMPEG_INCLUDE_DIR) + +# The default components were taken from a survey over other FindFFMPEG.cmake files +if (NOT FFmpeg_FIND_COMPONENTS) + set(FFmpeg_FIND_COMPONENTS AVCODEC AVFORMAT AVUTIL) +endif () + +# +### Macro: set_component_found +# +# Marks the given component as found if both *_LIBRARIES AND *_INCLUDE_DIRS is present. +# +macro(set_component_found _component ) + if (${_component}_LIBRARIES AND ${_component}_INCLUDE_DIRS) + # message(STATUS " - ${_component} found.") + set(${_component}_FOUND TRUE) + else () + # message(STATUS " - ${_component} not found.") + endif () +endmacro() + +# +### Macro: find_component +# +# Checks for the given component by invoking pkgconfig and then looking up the libraries and +# include directories. +# +macro(find_component _component _pkgconfig _library _header) + + if (NOT WIN32) + # use pkg-config to get the directories and then use these values + # in the FIND_PATH() and FIND_LIBRARY() calls + find_package(PkgConfig) + if (PKG_CONFIG_FOUND) + pkg_check_modules(PC_${_component} ${_pkgconfig}) + endif () + endif (NOT WIN32) + + find_path(${_component}_INCLUDE_DIRS ${_header} + HINTS + /opt/ + /opt/include/ + ${PC_LIB${_component}_INCLUDEDIR} + ${PC_LIB${_component}_INCLUDE_DIRS} + $ENV{FFMPEGDIR}/include/ + $ENV{FFMPEGDIR}/include/ffmpeg/ + PATH_SUFFIXES + ffmpeg + ) + + find_library(${_component}_LIBRARIES NAMES ${_library} + HINTS + ${PC_LIB${_component}_LIBDIR} + ${PC_LIB${_component}_LIBRARY_DIRS} + $ENV{FFMPEGDIR}/lib/ + $ENV{FFMPEGDIR}/lib/ffmpeg/ + $ENV{FFMPEGDIR}/bin/ + ) + + set(${_component}_DEFINITIONS ${PC_${_component}_CFLAGS_OTHER} CACHE STRING "The ${_component} CFLAGS.") + set(${_component}_VERSION ${PC_${_component}_VERSION} CACHE STRING "The ${_component} version number.") + + set_component_found(${_component}) + + mark_as_advanced( + ${_component}_INCLUDE_DIRS + ${_component}_LIBRARIES + ${_component}_DEFINITIONS + ${_component}_VERSION) + +endmacro() + + +# Check for cached results. If there are skip the costly part. +if (NOT FFMPEG_LIBRARIES) + + # Check for all possible component. + find_component(AVCODEC libavcodec avcodec libavcodec/avcodec.h) + find_component(AVFORMAT libavformat avformat libavformat/avformat.h) + find_component(AVDEVICE libavdevice avdevice libavdevice/avdevice.h) + find_component(AVUTIL libavutil avutil libavutil/avutil.h) + find_component(AVFILTER libavfilter avfilter libavfilter/avfilter.h) + find_component(SWSCALE libswscale swscale libswscale/swscale.h) + find_component(POSTPROC libpostproc postproc libpostproc/postprocess.h) + find_component(SWRESAMPLE libswresample swresample libswresample/swresample.h) + find_component(AVRESAMPLE libavresample avresample libavresample/avresample.h) + + # Check if the required components were found and add their stuff to the FFMPEG_* vars. + foreach (_component ${FFmpeg_FIND_COMPONENTS}) + if (${_component}_FOUND) + # message(STATUS "Required component ${_component} present.") + set(FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${${_component}_LIBRARIES}) + set(FFMPEG_DEFINITIONS ${FFMPEG_DEFINITIONS} ${${_component}_DEFINITIONS}) + list(APPEND FFMPEG_INCLUDE_DIRS ${${_component}_INCLUDE_DIRS}) + else () + # message(STATUS "Required component ${_component} missing.") + endif () + endforeach () + + # Build the include path with duplicates removed. + if (FFMPEG_INCLUDE_DIRS) + list(REMOVE_DUPLICATES FFMPEG_INCLUDE_DIRS) + endif () + + # cache the vars. + set(FFMPEG_INCLUDE_DIRS ${FFMPEG_INCLUDE_DIRS} CACHE STRING "The FFmpeg include directories." FORCE) + set(FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} CACHE STRING "The FFmpeg libraries." FORCE) + set(FFMPEG_DEFINITIONS ${FFMPEG_DEFINITIONS} CACHE STRING "The FFmpeg cflags." FORCE) + + mark_as_advanced(FFMPEG_INCLUDE_DIRS + FFMPEG_LIBRARIES + FFMPEG_DEFINITIONS) + +endif () + +# Now set the noncached _FOUND vars for the components. +foreach (_component AVCODEC AVDEVICE AVFORMAT AVUTIL POSTPROCESS SWSCALE SWRESAMPLE AVRESAMPLE) + set_component_found(${_component}) +endforeach () + +# Compile the list of required vars +set(_FFmpeg_REQUIRED_VARS FFMPEG_LIBRARIES FFMPEG_INCLUDE_DIRS) +foreach (_component ${FFmpeg_FIND_COMPONENTS}) + list(APPEND _FFmpeg_REQUIRED_VARS ${_component}_LIBRARIES ${_component}_INCLUDE_DIRS) +endforeach () + +# Give a nice error message if some of the required vars are missing. +find_package_handle_standard_args(FFmpeg DEFAULT_MSG ${_FFmpeg_REQUIRED_VARS}) \ No newline at end of file diff --git a/include/CrashHandler.h b/include/CrashHandler.h index e3a4bbe5..12c79a86 100644 --- a/include/CrashHandler.h +++ b/include/CrashHandler.h @@ -53,13 +53,15 @@ namespace openshot { class CrashHandler { private: /// Default constructor - CrashHandler(){}; // Don't allow user to create an instance of this singleton + CrashHandler(){return;}; // Don't allow user to create an instance of this singleton /// Default copy method - CrashHandler(CrashHandler const&){}; // Don't allow the user to copy this instance + //CrashHandler(CrashHandler const&){}; // Don't allow the user to copy this instance + CrashHandler(CrashHandler const&) = delete; // Don't allow the user to copy this instance /// Default assignment operator - CrashHandler & operator=(CrashHandler const&){}; // Don't allow the user to assign this instance + //CrashHandler & operator=(CrashHandler const&){}; // Don't allow the user to assign this instance + CrashHandler & operator=(CrashHandler const&) = delete; // Don't allow the user to assign this instance /// Private variable to keep track of singleton instance static CrashHandler *m_pInstance; diff --git a/include/FFmpegReader.h b/include/FFmpegReader.h index 6072756a..e2c4863a 100644 --- a/include/FFmpegReader.h +++ b/include/FFmpegReader.h @@ -105,6 +105,7 @@ namespace openshot bool check_interlace; bool check_fps; bool has_missing_frames; + bool use_omp_threads; CacheMemory working_cache; CacheMemory missing_frames; diff --git a/include/FFmpegUtilities.h b/include/FFmpegUtilities.h index 578c6586..346da541 100644 --- a/include/FFmpegUtilities.h +++ b/include/FFmpegUtilities.h @@ -43,7 +43,15 @@ #include #include #include + // Change this to the first version swrescale works + #if (LIBAVFORMAT_VERSION_MAJOR >= 57) + #define USE_SW + #endif + #ifdef USE_SW + #include + #else #include + #endif #include #include #include @@ -106,7 +114,65 @@ #define PIX_FMT_YUV420P AV_PIX_FMT_YUV420P #endif - #if IS_FFMPEG_3_2 + #ifdef USE_SW + #define SWR_CONVERT(ctx, out, linesize, out_count, in, linesize2, in_count) \ + swr_convert(ctx, out, out_count, (const uint8_t **)in, in_count) + #define SWR_ALLOC() swr_alloc() + #define SWR_CLOSE(ctx) {} + #define SWR_FREE(ctx) swr_free(ctx) + #define SWR_INIT(ctx) swr_init(ctx) + #define SWRCONTEXT SwrContext + #else + #define SWR_CONVERT(ctx, out, linesize, out_count, in, linesize2, in_count) \ + avresample_convert(ctx, out, linesize, out_count, (uint8_t **)in, linesize2, in_count) + #define SWR_ALLOC() avresample_alloc_context() + #define SWR_CLOSE(ctx) avresample_close(ctx) + #define SWR_FREE(ctx) avresample_free(ctx) + #define SWR_INIT(ctx) avresample_open(ctx) + #define SWRCONTEXT AVAudioResampleContext + #endif + + + #if (LIBAVFORMAT_VERSION_MAJOR >= 58) + #define AV_REGISTER_ALL + #define AVCODEC_REGISTER_ALL + #define AV_FILENAME url + #define MY_INPUT_BUFFER_PADDING_SIZE AV_INPUT_BUFFER_PADDING_SIZE + #define AV_ALLOCATE_FRAME() av_frame_alloc() + #define AV_ALLOCATE_IMAGE(av_frame, pix_fmt, width, height) av_image_alloc(av_frame->data, av_frame->linesize, width, height, pix_fmt, 1) + #define AV_RESET_FRAME(av_frame) av_frame_unref(av_frame) + #define AV_FREE_FRAME(av_frame) av_frame_free(av_frame) + #define AV_FREE_PACKET(av_packet) av_packet_unref(av_packet) + #define AV_FREE_CONTEXT(av_context) avcodec_free_context(&av_context) + #define AV_GET_CODEC_TYPE(av_stream) av_stream->codecpar->codec_type + #define AV_FIND_DECODER_CODEC_ID(av_stream) av_stream->codecpar->codec_id + auto AV_GET_CODEC_CONTEXT = [](AVStream* av_stream, AVCodec* av_codec) { \ + AVCodecContext *context = avcodec_alloc_context3(av_codec); \ + avcodec_parameters_to_context(context, av_stream->codecpar); \ + return context; \ + }; + #define AV_GET_CODEC_PAR_CONTEXT(av_stream, av_codec) av_codec; + #define AV_GET_CODEC_FROM_STREAM(av_stream,codec_in) + #define AV_GET_CODEC_ATTRIBUTES(av_stream, av_context) av_stream->codecpar + #define AV_GET_CODEC_PIXEL_FORMAT(av_stream, av_context) (AVPixelFormat) av_stream->codecpar->format + #define AV_GET_SAMPLE_FORMAT(av_stream, av_context) av_stream->codecpar->format + #define AV_GET_IMAGE_SIZE(pix_fmt, width, height) av_image_get_buffer_size(pix_fmt, width, height, 1) + #define AV_COPY_PICTURE_DATA(av_frame, buffer, pix_fmt, width, height) av_image_fill_arrays(av_frame->data, av_frame->linesize, buffer, pix_fmt, width, height, 1) + #define AV_OUTPUT_CONTEXT(output_context, path) avformat_alloc_output_context2( output_context, NULL, NULL, path) + #define AV_OPTION_FIND(priv_data, name) av_opt_find(priv_data, name, NULL, 0, 0) + #define AV_OPTION_SET( av_stream, priv_data, name, value, avcodec) av_opt_set(priv_data, name, value, 0); avcodec_parameters_from_context(av_stream->codecpar, avcodec); + #define AV_FORMAT_NEW_STREAM(oc, st_codec, av_codec, av_st) av_st = avformat_new_stream(oc, NULL);\ + if (!av_st) \ + throw OutOfMemory("Could not allocate memory for the video stream.", path); \ + c = avcodec_alloc_context3(av_codec); \ + st_codec = c; \ + av_st->codecpar->codec_id = av_codec->id; + #define AV_COPY_PARAMS_FROM_CONTEXT(av_stream, av_codec) avcodec_parameters_from_context(av_stream->codecpar, av_codec); + #elif IS_FFMPEG_3_2 + #define AV_REGISTER_ALL av_register_all(); + #define AVCODEC_REGISTER_ALL avcodec_register_all(); + #define AV_FILENAME filename + #define MY_INPUT_BUFFER_PADDING_SIZE FF_INPUT_BUFFER_PADDING_SIZE #define AV_ALLOCATE_FRAME() av_frame_alloc() #define AV_ALLOCATE_IMAGE(av_frame, pix_fmt, width, height) av_image_alloc(av_frame->data, av_frame->linesize, width, height, pix_fmt, 1) #define AV_RESET_FRAME(av_frame) av_frame_unref(av_frame) @@ -138,6 +204,10 @@ av_st->codecpar->codec_id = av_codec->id; #define AV_COPY_PARAMS_FROM_CONTEXT(av_stream, av_codec) avcodec_parameters_from_context(av_stream->codecpar, av_codec); #elif LIBAVFORMAT_VERSION_MAJOR >= 55 + #define AV_REGISTER_ALL av_register_all(); + #define AVCODEC_REGISTER_ALL avcodec_register_all(); + #define AV_FILENAME filename + #define MY_INPUT_BUFFER_PADDING_SIZE FF_INPUT_BUFFER_PADDING_SIZE #define AV_ALLOCATE_FRAME() av_frame_alloc() #define AV_ALLOCATE_IMAGE(av_frame, pix_fmt, width, height) avpicture_alloc((AVPicture *) av_frame, pix_fmt, width, height) #define AV_RESET_FRAME(av_frame) av_frame_unref(av_frame) @@ -164,6 +234,10 @@ c = av_st->codec; #define AV_COPY_PARAMS_FROM_CONTEXT(av_stream, av_codec) #else + #define AV_REGISTER_ALL av_register_all(); + #define AVCODEC_REGISTER_ALL avcodec_register_all(); + #define AV_FILENAME filename + #define MY_INPUT_BUFFER_PADDING_SIZE FF_INPUT_BUFFER_PADDING_SIZE #define AV_ALLOCATE_FRAME() avcodec_alloc_frame() #define AV_ALLOCATE_IMAGE(av_frame, pix_fmt, width, height) avpicture_alloc((AVPicture *) av_frame, pix_fmt, width, height) #define AV_RESET_FRAME(av_frame) avcodec_get_frame_defaults(av_frame) diff --git a/include/FFmpegWriter.h b/include/FFmpegWriter.h index 8343002e..7eefacb7 100644 --- a/include/FFmpegWriter.h +++ b/include/FFmpegWriter.h @@ -174,8 +174,8 @@ namespace openshot int initial_audio_input_frame_size; int audio_input_position; int audio_encoder_buffer_size; - AVAudioResampleContext *avr; - AVAudioResampleContext *avr_planar; + SWRCONTEXT *avr; + SWRCONTEXT *avr_planar; /* Resample options */ int original_sample_rate; diff --git a/include/Frame.h b/include/Frame.h index a7ad509f..eba7f8bb 100644 --- a/include/Frame.h +++ b/include/Frame.h @@ -62,7 +62,7 @@ #include "AudioResampler.h" #include "Fraction.h" - +#pragma SWIG nowarn=362 using namespace std; namespace openshot diff --git a/include/FrameMapper.h b/include/FrameMapper.h index 06511666..216fe73f 100644 --- a/include/FrameMapper.h +++ b/include/FrameMapper.h @@ -146,7 +146,7 @@ namespace openshot ReaderBase *reader; // The source video reader CacheMemory final_cache; // Cache of actual Frame objects bool is_dirty; // When this is true, the next call to GetFrame will re-init the mapping - AVAudioResampleContext *avr; // Audio resampling context object + SWRCONTEXT *avr; // Audio resampling context object // Internal methods used by init void AddField(int64_t frame); diff --git a/include/OpenMPUtilities.h b/include/OpenMPUtilities.h index 8a95a950..c0f5597b 100644 --- a/include/OpenMPUtilities.h +++ b/include/OpenMPUtilities.h @@ -29,8 +29,26 @@ #define OPENSHOT_OPENMP_UTILITIES_H #include +#include +#include - // Calculate the # of OpenMP Threads to allow - #define OPEN_MP_NUM_PROCESSORS omp_get_num_procs() +// Calculate the # of OpenMP Threads to allow +#define OPEN_MP_NUM_PROCESSORS omp_get_num_procs() + +using namespace std; + +namespace openshot { + + // Check if OS2_OMP_THREADS environment variable is present, and return + // if multiple threads should be used with OMP + static bool IsOMPEnabled() { + char* OS2_OMP_THREADS = getenv("OS2_OMP_THREADS"); + if (OS2_OMP_THREADS != NULL && strcmp(OS2_OMP_THREADS, "0") == 0) + return false; + else + return true; + } + +} #endif diff --git a/include/ZmqLogger.h b/include/ZmqLogger.h index c134f2cf..e825ed0e 100644 --- a/include/ZmqLogger.h +++ b/include/ZmqLogger.h @@ -72,11 +72,19 @@ namespace openshot { /// Default constructor ZmqLogger(){}; // Don't allow user to create an instance of this singleton +#if __GNUC__ >=7 /// Default copy method - ZmqLogger(ZmqLogger const&){}; // Don't allow the user to copy this instance + ZmqLogger(ZmqLogger const&) = delete; // Don't allow the user to assign this instance /// Default assignment operator - ZmqLogger & operator=(ZmqLogger const&){}; // Don't allow the user to assign this instance + ZmqLogger & operator=(ZmqLogger const&) = delete; // Don't allow the user to assign this instance +#else + /// Default copy method + ZmqLogger(ZmqLogger const&) {}; // Don't allow the user to assign this instance + + /// Default assignment operator + ZmqLogger & operator=(ZmqLogger const&); // Don't allow the user to assign this instance +#endif /// Private variable to keep track of singleton instance static ZmqLogger * m_pInstance; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f4e8fba4..d71067a3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -37,12 +37,12 @@ IF (WIN32) ENDIF(WIN32) IF (APPLE) # If you still get errors compiling with GCC 4.8, mac headers need to be patched: http://hamelot.co.uk/programming/osx-gcc-dispatch_block_t-has-not-been-declared-invalid-typedef/ - SET_PROPERTY(GLOBAL PROPERTY JUCE_MAC "JUCE_MAC") - ADD_DEFINITIONS(-DNDEBUG) - SET(EXTENSION "mm") - - SET(JUCE_PLATFORM_SPECIFIC_DIR build/macosx/platform_specific_code) - SET(JUCE_PLATFORM_SPECIFIC_LIBRARIES "-framework Carbon -framework Cocoa -framework CoreFoundation -framework CoreAudio -framework CoreMidi -framework IOKit -framework AGL -framework AudioToolbox -framework QuartzCore -lobjc -framework Accelerate") + SET_PROPERTY(GLOBAL PROPERTY JUCE_MAC "JUCE_MAC") + ADD_DEFINITIONS(-DNDEBUG) + SET(EXTENSION "mm") + + SET(JUCE_PLATFORM_SPECIFIC_DIR build/macosx/platform_specific_code) + SET(JUCE_PLATFORM_SPECIFIC_LIBRARIES "-framework Carbon -framework Cocoa -framework CoreFoundation -framework CoreAudio -framework CoreMidi -framework IOKit -framework AGL -framework AudioToolbox -framework QuartzCore -lobjc -framework Accelerate") ENDIF(APPLE) ################ IMAGE MAGICK ################## @@ -74,13 +74,43 @@ IF (ImageMagick_FOUND) SET(CMAKE_SWIG_FLAGS "-DUSE_IMAGEMAGICK=1") ENDIF (ImageMagick_FOUND) - + ################### FFMPEG ##################### # Find FFmpeg libraries (used for video encoding / decoding) FIND_PACKAGE(FFmpeg REQUIRED) # Include FFmpeg headers (needed for compile) -include_directories(${FFMPEG_INCLUDE_DIR}) +message('AVCODEC_FOUND: ${AVCODEC_FOUND}') +message('AVCODEC_INCLUDE_DIRS: ${AVCODEC_INCLUDE_DIRS}') +message('AVCODEC_LIBRARIES: ${AVCODEC_LIBRARIES}') + +IF (AVCODEC_FOUND) + include_directories(${AVCODEC_INCLUDE_DIRS}) +ENDIF (AVCODEC_FOUND) +IF (AVDEVICE_FOUND) + include_directories(${AVDEVICE_INCLUDE_DIRS}) +ENDIF (AVDEVICE_FOUND) +IF (AVFORMAT_FOUND) + include_directories(${AVFORMAT_INCLUDE_DIRS}) +ENDIF (AVFORMAT_FOUND) +IF (AVFILTER_FOUND) + include_directories(${AVFILTER_INCLUDE_DIRS}) +ENDIF (AVFILTER_FOUND) +IF (AVUTIL_FOUND) + include_directories(${AVUTIL_INCLUDE_DIRS}) +ENDIF (AVUTIL_FOUND) +IF (POSTPROC_FOUND) + include_directories(${POSTPROC_INCLUDE_DIRS}) +ENDIF (POSTPROC_FOUND) +IF (SWSCALE_FOUND) + include_directories(${SWSCALE_INCLUDE_DIRS}) +ENDIF (SWSCALE_FOUND) +IF (SWRESAMPLE_FOUND) + include_directories(${SWRESAMPLE_INCLUDE_DIRS}) +ENDIF (SWRESAMPLE_FOUND) +IF (AVRESAMPLE_FOUND) + include_directories(${AVRESAMPLE_INCLUDE_DIRS}) +ENDIF (AVRESAMPLE_FOUND) ################# LIBOPENSHOT-AUDIO ################### # Find JUCE-based openshot Audio libraries @@ -112,11 +142,11 @@ add_definitions(${Qt5Gui_DEFINITIONS}) add_definitions(${Qt5Multimedia_DEFINITIONS}) add_definitions(${Qt5MultimediaWidgets_DEFINITIONS}) -SET(QT_LIBRARIES ${Qt5Widgets_LIBRARIES} - ${Qt5Core_LIBRARIES} - ${Qt5Gui_LIBRARIES} - ${Qt5Multimedia_LIBRARIES} - ${Qt5MultimediaWidgets_LIBRARIES}) +SET(QT_LIBRARIES ${Qt5Widgets_LIBRARIES} + ${Qt5Core_LIBRARIES} + ${Qt5Gui_LIBRARIES} + ${Qt5Multimedia_LIBRARIES} + ${Qt5MultimediaWidgets_LIBRARIES}) # Set compiler flags for Qt set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS} ") @@ -133,15 +163,15 @@ qt5_wrap_cpp(MOC_FILES ${QT_HEADER_FILES}) # Find BlackMagic DeckLinkAPI libraries IF (ENABLE_BLACKMAGIC) FIND_PACKAGE(BlackMagic) - + IF (BLACKMAGIC_FOUND) # Include headers (needed for compile) include_directories(${BLACKMAGIC_INCLUDE_DIR}) - + # define a global var (used in the C++) add_definitions( -DUSE_BLACKMAGIC=1 ) SET(CMAKE_SWIG_FLAGS "-DUSE_BLACKMAGIC=1") - + ENDIF (BLACKMAGIC_FOUND) ENDIF (ENABLE_BLACKMAGIC) @@ -150,14 +180,14 @@ ENDIF (ENABLE_BLACKMAGIC) FIND_PACKAGE(OpenMP) if (OPENMP_FOUND) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS} ") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS} ") endif(OPENMP_FOUND) ################### ZEROMQ ##################### # Find ZeroMQ library (used for socket communication & logging) FIND_PACKAGE(ZMQ REQUIRED) -# Include FFmpeg headers (needed for compile) +# Include ZeroMQ headers (needed for compile) include_directories(${ZMQ_INCLUDE_DIRS}) ################### JSONCPP ##################### @@ -182,8 +212,8 @@ FILE(GLOB QT_PLAYER_FILES "${CMAKE_CURRENT_SOURCE_DIR}/Qt/*.cpp") ############### SET LIBRARY SOURCE FILES ################# SET ( OPENSHOT_SOURCE_FILES - AudioBufferSource.cpp - AudioReaderSource.cpp + AudioBufferSource.cpp + AudioReaderSource.cpp AudioResampler.cpp CacheBase.cpp CacheDisk.cpp @@ -215,37 +245,37 @@ SET ( OPENSHOT_SOURCE_FILES QtImageReader.cpp QtPlayer.cpp Timeline.cpp - + # Qt Video Player ${QT_PLAYER_FILES} ${MOC_FILES}) - IF (NOT USE_SYSTEM_JSONCPP) - # Third Party JSON Parser - SET ( OPENSHOT_SOURCE_FILES ${OPENSHOT_SOURCE_FILES} - ../thirdparty/jsoncpp/src/lib_json/json_reader.cpp - ../thirdparty/jsoncpp/src/lib_json/json_value.cpp - ../thirdparty/jsoncpp/src/lib_json/json_writer.cpp) - ENDIF (NOT USE_SYSTEM_JSONCPP) +IF (NOT USE_SYSTEM_JSONCPP) + # Third Party JSON Parser + SET ( OPENSHOT_SOURCE_FILES ${OPENSHOT_SOURCE_FILES} + ../thirdparty/jsoncpp/src/lib_json/json_reader.cpp + ../thirdparty/jsoncpp/src/lib_json/json_value.cpp + ../thirdparty/jsoncpp/src/lib_json/json_writer.cpp) +ENDIF (NOT USE_SYSTEM_JSONCPP) + +# ImageMagic related files +IF (ImageMagick_FOUND) + SET ( OPENSHOT_SOURCE_FILES ${OPENSHOT_SOURCE_FILES} + ImageReader.cpp + ImageWriter.cpp + TextReader.cpp) +ENDIF (ImageMagick_FOUND) + +# BlackMagic related files +IF (BLACKMAGIC_FOUND) + SET ( OPENSHOT_SOURCE_FILES ${OPENSHOT_SOURCE_FILES} + DecklinkInput.cpp + DecklinkReader.cpp + DecklinkOutput.cpp + DecklinkWriter.cpp) +ENDIF (BLACKMAGIC_FOUND) - # ImageMagic related files - IF (ImageMagick_FOUND) - SET ( OPENSHOT_SOURCE_FILES ${OPENSHOT_SOURCE_FILES} - ImageReader.cpp - ImageWriter.cpp - TextReader.cpp) - ENDIF (ImageMagick_FOUND) - # BlackMagic related files - IF (BLACKMAGIC_FOUND) - SET ( OPENSHOT_SOURCE_FILES ${OPENSHOT_SOURCE_FILES} - DecklinkInput.cpp - DecklinkReader.cpp - DecklinkOutput.cpp - DecklinkWriter.cpp) - ENDIF (BLACKMAGIC_FOUND) - - # Get list of headers file(GLOB_RECURSE headers ${CMAKE_SOURCE_DIR}/include/*.h) @@ -254,44 +284,71 @@ SET(CMAKE_MACOSX_RPATH 0) ############### CREATE LIBRARY ################# # Create shared openshot library -add_library(openshot SHARED - ${OPENSHOT_SOURCE_FILES} - ${headers} ) +add_library(openshot SHARED + ${OPENSHOT_SOURCE_FILES} + ${headers} ) # Set SONAME and other library properties set_target_properties(openshot - PROPERTIES - VERSION ${PROJECT_VERSION} - SOVERSION ${SO_VERSION} - INSTALL_NAME_DIR "${CMAKE_INSTALL_PREFIX}/lib" - ) + PROPERTIES + VERSION ${PROJECT_VERSION} + SOVERSION ${SO_VERSION} + INSTALL_NAME_DIR "${CMAKE_INSTALL_PREFIX}/lib" + ) ############### LINK LIBRARY ################# SET ( REQUIRED_LIBRARIES - ${FFMPEG_LIBRARIES} ${LIBOPENSHOT_AUDIO_LIBRARIES} ${QT_LIBRARIES} ${PROFILER} ${JSONCPP_LIBRARY} ${ZMQ_LIBRARIES} ) - - IF (OPENMP_FOUND) - SET ( REQUIRED_LIBRARIES ${REQUIRED_LIBRARIES} ${OpenMP_CXX_FLAGS} ) - ENDIF (OPENMP_FOUND) - - IF (ImageMagick_FOUND) - SET ( REQUIRED_LIBRARIES ${REQUIRED_LIBRARIES} ${ImageMagick_LIBRARIES} ) - ENDIF (ImageMagick_FOUND) - IF (BLACKMAGIC_FOUND) - SET ( REQUIRED_LIBRARIES ${REQUIRED_LIBRARIES} ${BLACKMAGIC_LIBRARY_DIR} ) - ENDIF (BLACKMAGIC_FOUND) +IF (AVCODEC_FOUND) + SET ( REQUIRED_LIBRARIES ${REQUIRED_LIBRARIES} ${AVCODEC_LIBRARIES} ) +ENDIF (AVCODEC_FOUND) +IF (AVDEVICE_FOUND) + SET ( REQUIRED_LIBRARIES ${REQUIRED_LIBRARIES} ${AVDEVICE_LIBRARIES} ) +ENDIF (AVDEVICE_FOUND) +IF (AVFORMAT_FOUND) + SET ( REQUIRED_LIBRARIES ${REQUIRED_LIBRARIES} ${AVFORMAT_LIBRARIES} ) +ENDIF (AVFORMAT_FOUND) +IF (AVFILTER_FOUND) + SET ( REQUIRED_LIBRARIES ${REQUIRED_LIBRARIES} ${AVFILTER_LIBRARIES} ) +ENDIF (AVFILTER_FOUND) +IF (AVUTIL_FOUND) + SET ( REQUIRED_LIBRARIES ${REQUIRED_LIBRARIES} ${AVUTIL_LIBRARIES} ) +ENDIF (AVUTIL_FOUND) +IF (POSTPROC_FOUND) + SET ( REQUIRED_LIBRARIES ${REQUIRED_LIBRARIES} ${POSTPROC_LIBRARIES} ) +ENDIF (POSTPROC_FOUND) +IF (SWSCALE_FOUND) + SET ( REQUIRED_LIBRARIES ${REQUIRED_LIBRARIES} ${SWSCALE_LIBRARIES} ) +ENDIF (SWSCALE_FOUND) +IF (SWRESAMPLE_FOUND) + SET ( REQUIRED_LIBRARIES ${REQUIRED_LIBRARIES} ${SWRESAMPLE_LIBRARIES} ) +ENDIF (SWRESAMPLE_FOUND) +IF (AVRESAMPLE_FOUND) + SET ( REQUIRED_LIBRARIES ${REQUIRED_LIBRARIES} ${AVRESAMPLE_LIBRARIES} ) +ENDIF (AVRESAMPLE_FOUND) - IF (WIN32) - # Required for exception handling on Windows - SET ( REQUIRED_LIBRARIES ${REQUIRED_LIBRARIES} "imagehlp" "dbghelp" ) - ENDIF(WIN32) +IF (OPENMP_FOUND) + SET ( REQUIRED_LIBRARIES ${REQUIRED_LIBRARIES} ${OpenMP_CXX_FLAGS} ) +ENDIF (OPENMP_FOUND) + +IF (ImageMagick_FOUND) + SET ( REQUIRED_LIBRARIES ${REQUIRED_LIBRARIES} ${ImageMagick_LIBRARIES} ) +ENDIF (ImageMagick_FOUND) + +IF (BLACKMAGIC_FOUND) + SET ( REQUIRED_LIBRARIES ${REQUIRED_LIBRARIES} ${BLACKMAGIC_LIBRARY_DIR} ) +ENDIF (BLACKMAGIC_FOUND) + +IF (WIN32) + # Required for exception handling on Windows + SET ( REQUIRED_LIBRARIES ${REQUIRED_LIBRARIES} "imagehlp" "dbghelp" ) +ENDIF(WIN32) # Link all referenced libraries target_link_libraries(openshot ${REQUIRED_LIBRARIES}) @@ -314,9 +371,9 @@ target_link_libraries(openshot-player openshot) ############### TEST BLACKMAGIC CAPTURE APP ################ IF (BLACKMAGIC_FOUND) # Create test executable - add_executable(openshot-blackmagic - examples/ExampleBlackmagic.cpp) - + add_executable(openshot-blackmagic + examples/ExampleBlackmagic.cpp) + # Link test executable to the new library target_link_libraries(openshot-blackmagic openshot) ENDIF (BLACKMAGIC_FOUND) @@ -330,13 +387,13 @@ set(LIB_INSTALL_DIR lib${LIB_SUFFIX}) # determine correct lib folder # Install primary library INSTALL( TARGETS openshot - ARCHIVE DESTINATION ${LIB_INSTALL_DIR} - LIBRARY DESTINATION ${LIB_INSTALL_DIR} - COMPONENT library ) - + ARCHIVE DESTINATION ${LIB_INSTALL_DIR} + LIBRARY DESTINATION ${LIB_INSTALL_DIR} + COMPONENT library ) + INSTALL(DIRECTORY ${CMAKE_SOURCE_DIR}/include/ - DESTINATION ${CMAKE_INSTALL_PREFIX}/include/libopenshot -FILES_MATCHING PATTERN "*.h") + DESTINATION ${CMAKE_INSTALL_PREFIX}/include/libopenshot + FILES_MATCHING PATTERN "*.h") ############### CPACK PACKAGING ############## IF(MINGW) diff --git a/src/Clip.cpp b/src/Clip.cpp index 913fd71f..8e33f84c 100644 --- a/src/Clip.cpp +++ b/src/Clip.cpp @@ -671,6 +671,7 @@ std::shared_ptr Clip::GetOrCreateFrame(int64_t number) new_frame = std::make_shared(number, reader->info.width, reader->info.height, "#000000", samples_in_frame, reader->info.channels); new_frame->SampleRate(reader->info.sample_rate); new_frame->ChannelsLayout(reader->info.channel_layout); + new_frame->AddAudioSilence(samples_in_frame); return new_frame; } @@ -925,13 +926,14 @@ void Clip::SetJsonValue(Json::Value root) { if (!existing_effect["type"].isNull()) { // Create instance of effect - e = EffectInfo().CreateEffect(existing_effect["type"].asString()); + if (e = EffectInfo().CreateEffect(existing_effect["type"].asString())) { - // Load Json into Effect - e->SetJsonValue(existing_effect); + // Load Json into Effect + e->SetJsonValue(existing_effect); - // Add Effect to Timeline - AddEffect(e); + // Add Effect to Timeline + AddEffect(e); + } } } } diff --git a/src/EffectInfo.cpp b/src/EffectInfo.cpp index 23bc9d02..f9e4c409 100644 --- a/src/EffectInfo.cpp +++ b/src/EffectInfo.cpp @@ -82,6 +82,7 @@ EffectBase* EffectInfo::CreateEffect(string effect_type) { else if (effect_type == "Wave") return new Wave(); + return NULL; } // Generate Json::JsonValue for this object diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 0b100050..17a612c1 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -37,11 +37,12 @@ FFmpegReader::FFmpegReader(string path) audio_pts_offset(99999), video_pts_offset(99999), path(path), is_video_seek(true), check_interlace(false), check_fps(false), enable_seek(true), is_open(false), seek_audio_frame_found(0), seek_video_frame_found(0), prev_samples(0), prev_pts(0), pts_total(0), pts_counter(0), is_duration_known(false), largest_frame_processed(0), - current_video_frame(0), has_missing_frames(false), num_packets_since_video_frame(0), num_checks_since_final(0), packet(NULL) { + current_video_frame(0), has_missing_frames(false), num_packets_since_video_frame(0), num_checks_since_final(0), + packet(NULL), use_omp_threads(true) { // Initialize FFMpeg, and register all formats and codecs - av_register_all(); - avcodec_register_all(); + AV_REGISTER_ALL + AVCODEC_REGISTER_ALL // Init cache working_cache.SetMaxBytesFromInfo(OPEN_MP_NUM_PROCESSORS * info.fps.ToDouble() * 2, info.width, info.height, info.sample_rate, info.channels); @@ -58,11 +59,12 @@ FFmpegReader::FFmpegReader(string path, bool inspect_reader) audio_pts_offset(99999), video_pts_offset(99999), path(path), is_video_seek(true), check_interlace(false), check_fps(false), enable_seek(true), is_open(false), seek_audio_frame_found(0), seek_video_frame_found(0), prev_samples(0), prev_pts(0), pts_total(0), pts_counter(0), is_duration_known(false), largest_frame_processed(0), - current_video_frame(0), has_missing_frames(false), num_packets_since_video_frame(0), num_checks_since_final(0), packet(NULL) { + current_video_frame(0), has_missing_frames(false), num_packets_since_video_frame(0), num_checks_since_final(0), + packet(NULL), use_omp_threads(true) { // Initialize FFMpeg, and register all formats and codecs - av_register_all(); - avcodec_register_all(); + AV_REGISTER_ALL + AVCODEC_REGISTER_ALL // Init cache working_cache.SetMaxBytesFromInfo(OPEN_MP_NUM_PROCESSORS * info.fps.ToDouble() * 2, info.width, info.height, info.sample_rate, info.channels); @@ -227,6 +229,9 @@ void FFmpegReader::Open() missing_frames.SetMaxBytesFromInfo(OPEN_MP_NUM_PROCESSORS * 2, info.width, info.height, info.sample_rate, info.channels); final_cache.SetMaxBytesFromInfo(OPEN_MP_NUM_PROCESSORS * 2, info.width, info.height, info.sample_rate, info.channels); + // Initialize OMP threading support + use_omp_threads = openshot::IsOMPEnabled(); + // Mark as "open" is_open = true; } @@ -602,6 +607,12 @@ std::shared_ptr FFmpegReader::ReadStream(int64_t requested_frame) // Process Video Packet ProcessVideoPacket(requested_frame); + + if (!use_omp_threads) { + // Wait on each OMP task to complete before moving on to the next one. This slows + // down processing considerably, but might be more stable on some systems. + #pragma omp taskwait + } } } @@ -634,7 +645,6 @@ std::shared_ptr FFmpegReader::ReadStream(int64_t requested_frame) } // Check if working frames are 'finished' - bool is_cache_found = false; if (!is_seeking) { // Check for any missing frames CheckMissingFrame(requested_frame); @@ -644,7 +654,7 @@ std::shared_ptr FFmpegReader::ReadStream(int64_t requested_frame) } // Check if requested 'final' frame is available - is_cache_found = (final_cache.GetFrame(requested_frame) != NULL); + bool is_cache_found = (final_cache.GetFrame(requested_frame) != NULL); // Increment frames processed packets_processed++; @@ -656,6 +666,7 @@ std::shared_ptr FFmpegReader::ReadStream(int64_t requested_frame) } // end while } // end omp single + } // end omp parallel // Debug output @@ -904,7 +915,7 @@ void FFmpegReader::ProcessVideoPacket(int64_t requested_frame) AV_COPY_PICTURE_DATA(pFrameRGB, buffer, PIX_FMT_RGBA, width, height); SwsContext *img_convert_ctx = sws_getContext(info.width, info.height, AV_GET_CODEC_PIXEL_FORMAT(pStream, pCodecCtx), width, - height, PIX_FMT_RGBA, SWS_BILINEAR, NULL, NULL, NULL); + height, PIX_FMT_RGBA, SWS_LANCZOS, NULL, NULL, NULL); // Resize / Convert to RGB sws_scale(img_convert_ctx, my_frame->data, my_frame->linesize, 0, @@ -974,7 +985,7 @@ void FFmpegReader::ProcessAudioPacket(int64_t requested_frame, int64_t target_fr int data_size = 0; // re-initialize buffer size (it gets changed in the avcodec_decode_audio2 method call) - int buf_size = AVCODEC_MAX_AUDIO_FRAME_SIZE + FF_INPUT_BUFFER_PADDING_SIZE; + int buf_size = AVCODEC_MAX_AUDIO_FRAME_SIZE + MY_INPUT_BUFFER_PADDING_SIZE; #pragma omp critical (ProcessAudioPacket) { #if IS_FFMPEG_3_2 @@ -1079,7 +1090,7 @@ void FFmpegReader::ProcessAudioPacket(int64_t requested_frame, int64_t target_fr // Allocate audio buffer - int16_t *audio_buf = new int16_t[AVCODEC_MAX_AUDIO_FRAME_SIZE + FF_INPUT_BUFFER_PADDING_SIZE]; + int16_t *audio_buf = new int16_t[AVCODEC_MAX_AUDIO_FRAME_SIZE + MY_INPUT_BUFFER_PADDING_SIZE]; ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ProcessAudioPacket (ReSample)", "packet_samples", packet_samples, "info.channels", info.channels, "info.sample_rate", info.sample_rate, "aCodecCtx->sample_fmt", AV_GET_SAMPLE_FORMAT(aStream, aCodecCtx), "AV_SAMPLE_FMT_S16", AV_SAMPLE_FMT_S16, "", -1); @@ -1089,11 +1100,11 @@ void FFmpegReader::ProcessAudioPacket(int64_t requested_frame, int64_t target_fr audio_converted->nb_samples = audio_frame->nb_samples; av_samples_alloc(audio_converted->data, audio_converted->linesize, info.channels, audio_frame->nb_samples, AV_SAMPLE_FMT_S16, 0); - AVAudioResampleContext *avr = NULL; + SWRCONTEXT *avr = NULL; int nb_samples = 0; // setup resample context - avr = avresample_alloc_context(); + avr = SWR_ALLOC(); av_opt_set_int(avr, "in_channel_layout", AV_GET_CODEC_ATTRIBUTES(aStream, aCodecCtx)->channel_layout, 0); av_opt_set_int(avr, "out_channel_layout", AV_GET_CODEC_ATTRIBUTES(aStream, aCodecCtx)->channel_layout, 0); av_opt_set_int(avr, "in_sample_fmt", AV_GET_SAMPLE_FORMAT(aStream, aCodecCtx), 0); @@ -1102,10 +1113,10 @@ void FFmpegReader::ProcessAudioPacket(int64_t requested_frame, int64_t target_fr av_opt_set_int(avr, "out_sample_rate", info.sample_rate, 0); av_opt_set_int(avr, "in_channels", info.channels, 0); av_opt_set_int(avr, "out_channels", info.channels, 0); - int r = avresample_open(avr); + int r = SWR_INIT(avr); // Convert audio samples - nb_samples = avresample_convert(avr, // audio resample context + nb_samples = SWR_CONVERT(avr, // audio resample context audio_converted->data, // output data pointers audio_converted->linesize[0], // output plane size, in bytes. (0 if unknown) audio_converted->nb_samples, // maximum number of samples that the output buffer can hold @@ -1117,8 +1128,8 @@ void FFmpegReader::ProcessAudioPacket(int64_t requested_frame, int64_t target_fr memcpy(audio_buf, audio_converted->data[0], audio_converted->nb_samples * av_get_bytes_per_sample(AV_SAMPLE_FMT_S16) * info.channels); // Deallocate resample buffer - avresample_close(avr); - avresample_free(&avr); + SWR_CLOSE(avr); + SWR_FREE(&avr); avr = NULL; // Free AVFrames @@ -1344,7 +1355,7 @@ void FFmpegReader::Seek(int64_t requested_frame) { seek_target = ConvertFrameToVideoPTS(requested_frame - buffer_amount); if (av_seek_frame(pFormatCtx, info.video_stream_index, seek_target, AVSEEK_FLAG_BACKWARD) < 0) { - fprintf(stderr, "%s: error while seeking video stream\n", pFormatCtx->filename); + fprintf(stderr, "%s: error while seeking video stream\n", pFormatCtx->AV_FILENAME); } else { // VIDEO SEEK @@ -1358,7 +1369,7 @@ void FFmpegReader::Seek(int64_t requested_frame) { seek_target = ConvertFrameToAudioPTS(requested_frame - buffer_amount); if (av_seek_frame(pFormatCtx, info.audio_stream_index, seek_target, AVSEEK_FLAG_BACKWARD) < 0) { - fprintf(stderr, "%s: error while seeking audio stream\n", pFormatCtx->filename); + fprintf(stderr, "%s: error while seeking audio stream\n", pFormatCtx->AV_FILENAME); } else { // AUDIO SEEK @@ -1853,6 +1864,8 @@ void FFmpegReader::CheckWorkingFrames(bool end_of_stream, int64_t requested_fram void FFmpegReader::CheckFPS() { check_fps = true; + AV_ALLOCATE_IMAGE(pFrame, AV_GET_CODEC_PIXEL_FORMAT(pStream, pCodecCtx), info.width, info.height); + int first_second_counter = 0; int second_second_counter = 0; int third_second_counter = 0; diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index ede07a43..11017219 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -46,7 +46,7 @@ FFmpegWriter::FFmpegWriter(string path) : info.has_video = false; // Initialize FFMpeg, and register all formats and codecs - av_register_all(); + AV_REGISTER_ALL // auto detect format auto_detect_format(); @@ -299,7 +299,7 @@ void FFmpegWriter::SetOption(StreamType stream, string name, string value) /// Determine if codec name is valid bool FFmpegWriter::IsValidCodec(string codec_name) { // Initialize FFMpeg, and register all formats and codecs - av_register_all(); + AV_REGISTER_ALL // Find the codec (if any) if (avcodec_find_encoder_by_name(codec_name.c_str()) == NULL) @@ -342,7 +342,7 @@ void FFmpegWriter::WriteHeader() } // Force the output filename (which doesn't always happen for some reason) - snprintf(oc->filename, sizeof(oc->filename), "%s", path.c_str()); + snprintf(oc->AV_FILENAME, sizeof(oc->AV_FILENAME), "%s", path.c_str()); // Write the stream header, if any // TODO: add avoptions / parameters instead of NULL @@ -559,8 +559,10 @@ void FFmpegWriter::flush_encoders() { if (info.has_audio && audio_codec && AV_GET_CODEC_TYPE(audio_st) == AVMEDIA_TYPE_AUDIO && AV_GET_CODEC_ATTRIBUTES(audio_st, audio_codec)->frame_size <= 1) return; +#if (LIBAVFORMAT_VERSION_MAJOR < 58) if (info.has_video && video_codec && AV_GET_CODEC_TYPE(video_st) == AVMEDIA_TYPE_VIDEO && (oc->oformat->flags & AVFMT_RAWPICTURE) && AV_FIND_DECODER_CODEC_ID(video_st) == AV_CODEC_ID_RAWVIDEO) return; +#endif int error_code = 0; int stop_encoding = 1; @@ -751,14 +753,14 @@ void FFmpegWriter::close_audio(AVFormatContext *oc, AVStream *st) // Deallocate resample buffer if (avr) { - avresample_close(avr); - avresample_free(&avr); + SWR_CLOSE(avr); + SWR_FREE(&avr); avr = NULL; } if (avr_planar) { - avresample_close(avr_planar); - avresample_free(&avr_planar); + SWR_CLOSE(avr_planar); + SWR_FREE(&avr_planar); avr_planar = NULL; } } @@ -898,7 +900,11 @@ AVStream* FFmpegWriter::add_audio_stream() // some formats want stream headers to be separate if (oc->oformat->flags & AVFMT_GLOBALHEADER) +#if (LIBAVCODEC_VERSION_MAJOR >= 57) + c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; +#else c->flags |= CODEC_FLAG_GLOBAL_HEADER; +#endif AV_COPY_PARAMS_FROM_CONTEXT(st, c); ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::add_audio_stream", "c->codec_id", c->codec_id, "c->bit_rate", c->bit_rate, "c->channels", c->channels, "c->sample_fmt", c->sample_fmt, "c->channel_layout", c->channel_layout, "c->sample_rate", c->sample_rate); @@ -970,7 +976,11 @@ AVStream* FFmpegWriter::add_video_stream() c->mb_decision = 2; // some formats want stream headers to be separate if (oc->oformat->flags & AVFMT_GLOBALHEADER) +#if (LIBAVCODEC_VERSION_MAJOR >= 57) + c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; +#else c->flags |= CODEC_FLAG_GLOBAL_HEADER; +#endif // Find all supported pixel formats for this codec const PixelFormat* supported_pixel_formats = codec->pix_fmts; @@ -987,10 +997,12 @@ AVStream* FFmpegWriter::add_video_stream() // Raw video should use RGB24 c->pix_fmt = PIX_FMT_RGB24; +#if (LIBAVFORMAT_VERSION_MAJOR < 58) if (strcmp(fmt->name, "gif") != 0) // If not GIF format, skip the encoding process // Set raw picture flag (so we don't encode this video) oc->oformat->flags |= AVFMT_RAWPICTURE; +#endif } else { // Set the default codec c->pix_fmt = PIX_FMT_YUV420P; @@ -998,7 +1010,11 @@ AVStream* FFmpegWriter::add_video_stream() } AV_COPY_PARAMS_FROM_CONTEXT(st, c); +#if (LIBAVFORMAT_VERSION_MAJOR < 58) ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::add_video_stream (" + (string)fmt->name + " : " + (string)av_get_pix_fmt_name(c->pix_fmt) + ")", "c->codec_id", c->codec_id, "c->bit_rate", c->bit_rate, "c->pix_fmt", c->pix_fmt, "oc->oformat->flags", oc->oformat->flags, "AVFMT_RAWPICTURE", AVFMT_RAWPICTURE, "", -1); +#else + ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::add_video_stream (" + (string)fmt->name + " : " + (string)av_get_pix_fmt_name(c->pix_fmt) + ")", "c->codec_id", c->codec_id, "c->bit_rate", c->bit_rate, "c->pix_fmt", c->pix_fmt, "oc->oformat->flags", oc->oformat->flags, "", -1, "", -1); +#endif return st; } @@ -1073,7 +1089,7 @@ void FFmpegWriter::open_audio(AVFormatContext *oc, AVStream *st) av_dict_set(&st->metadata, iter->first.c_str(), iter->second.c_str(), 0); } - ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::open_audio", "audio_codec->thread_count", audio_codec->thread_count, "audio_input_frame_size", audio_input_frame_size, "buffer_size", AVCODEC_MAX_AUDIO_FRAME_SIZE + FF_INPUT_BUFFER_PADDING_SIZE, "", -1, "", -1, "", -1); + ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::open_audio", "audio_codec->thread_count", audio_codec->thread_count, "audio_input_frame_size", audio_input_frame_size, "buffer_size", AVCODEC_MAX_AUDIO_FRAME_SIZE + MY_INPUT_BUFFER_PADDING_SIZE, "", -1, "", -1, "", -1); } @@ -1239,7 +1255,7 @@ void FFmpegWriter::write_audio_packets(bool final) // setup resample context if (!avr) { - avr = avresample_alloc_context(); + avr = SWR_ALLOC(); av_opt_set_int(avr, "in_channel_layout", channel_layout_in_frame, 0); av_opt_set_int(avr, "out_channel_layout", info.channel_layout, 0); av_opt_set_int(avr, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0); @@ -1248,12 +1264,12 @@ void FFmpegWriter::write_audio_packets(bool final) av_opt_set_int(avr, "out_sample_rate", info.sample_rate, 0); av_opt_set_int(avr, "in_channels", channels_in_frame, 0); av_opt_set_int(avr, "out_channels", info.channels, 0); - avresample_open(avr); + SWR_INIT(avr); } int nb_samples = 0; // Convert audio samples - nb_samples = avresample_convert(avr, // audio resample context + nb_samples = SWR_CONVERT(avr, // audio resample context audio_converted->data, // output data pointers audio_converted->linesize[0], // output plane size, in bytes. (0 if unknown) audio_converted->nb_samples, // maximum number of samples that the output buffer can hold @@ -1314,7 +1330,7 @@ void FFmpegWriter::write_audio_packets(bool final) // setup resample context if (!avr_planar) { - avr_planar = avresample_alloc_context(); + avr_planar = SWR_ALLOC(); av_opt_set_int(avr_planar, "in_channel_layout", info.channel_layout, 0); av_opt_set_int(avr_planar, "out_channel_layout", info.channel_layout, 0); av_opt_set_int(avr_planar, "in_sample_fmt", output_sample_fmt, 0); @@ -1323,7 +1339,7 @@ void FFmpegWriter::write_audio_packets(bool final) av_opt_set_int(avr_planar, "out_sample_rate", info.sample_rate, 0); av_opt_set_int(avr_planar, "in_channels", info.channels, 0); av_opt_set_int(avr_planar, "out_channels", info.channels, 0); - avresample_open(avr_planar); + SWR_INIT(avr_planar); } // Create input frame (and allocate arrays) @@ -1346,7 +1362,7 @@ void FFmpegWriter::write_audio_packets(bool final) av_samples_alloc(frame_final->data, frame_final->linesize, info.channels, frame_final->nb_samples, audio_codec->sample_fmt, 0); // Convert audio samples - int nb_samples = avresample_convert(avr_planar, // audio resample context + int nb_samples = SWR_CONVERT(avr_planar, // audio resample context frame_final->data, // output data pointers frame_final->linesize[0], // output plane size, in bytes. (0 if unknown) frame_final->nb_samples, // maximum number of samples that the output buffer can hold @@ -1577,6 +1593,9 @@ void FFmpegWriter::process_video_packet(std::shared_ptr frame) // write video frame bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame* frame_final) { +#if (LIBAVFORMAT_VERSION_MAJOR >= 58) + ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::write_video_packet", "frame->number", frame->number, "oc->oformat->flags", oc->oformat->flags, "", -1, "", -1, "", -1, "", -1); +#else ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::write_video_packet", "frame->number", frame->number, "oc->oformat->flags & AVFMT_RAWPICTURE", oc->oformat->flags & AVFMT_RAWPICTURE, "", -1, "", -1, "", -1, "", -1); if (oc->oformat->flags & AVFMT_RAWPICTURE) { @@ -1604,7 +1623,9 @@ bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame* fra // Deallocate packet AV_FREE_PACKET(&pkt); - } else { + } else +#endif + { AVPacket pkt; av_init_packet(&pkt); @@ -1731,7 +1752,7 @@ void FFmpegWriter::InitScalers(int source_width, int source_height) for (int x = 0; x < num_of_rescalers; x++) { // Init the software scaler from FFMpeg - img_convert_ctx = sws_getContext(source_width, source_height, PIX_FMT_RGBA, info.width, info.height, AV_GET_CODEC_PIXEL_FORMAT(video_st, video_st->codec), SWS_BILINEAR, NULL, NULL, NULL); + img_convert_ctx = sws_getContext(source_width, source_height, PIX_FMT_RGBA, info.width, info.height, AV_GET_CODEC_PIXEL_FORMAT(video_st, video_st->codec), SWS_LANCZOS, NULL, NULL, NULL); // Add rescaler to vector image_rescalers.push_back(img_convert_ctx); diff --git a/src/FrameMapper.cpp b/src/FrameMapper.cpp index f49cbc4d..1817c049 100644 --- a/src/FrameMapper.cpp +++ b/src/FrameMapper.cpp @@ -376,6 +376,7 @@ std::shared_ptr FrameMapper::GetOrCreateFrame(int64_t number) new_frame = std::make_shared(number, info.width, info.height, "#000000", samples_in_frame, reader->info.channels); new_frame->SampleRate(reader->info.sample_rate); new_frame->ChannelsLayout(info.channel_layout); + new_frame->AddAudioSilence(samples_in_frame); return new_frame; } @@ -650,8 +651,8 @@ void FrameMapper::Close() // Deallocate resample buffer if (avr) { - avresample_close(avr); - avresample_free(&avr); + SWR_CLOSE(avr); + SWR_FREE(&avr); avr = NULL; } } @@ -741,8 +742,8 @@ void FrameMapper::ChangeMapping(Fraction target_fps, PulldownType target_pulldow // Deallocate resample buffer if (avr) { - avresample_close(avr); - avresample_free(&avr); + SWR_CLOSE(avr); + SWR_FREE(&avr); avr = NULL; } @@ -817,7 +818,7 @@ void FrameMapper::ResampleMappedAudio(std::shared_ptr frame, int64_t orig // setup resample context if (!avr) { - avr = avresample_alloc_context(); + avr = SWR_ALLOC(); av_opt_set_int(avr, "in_channel_layout", channel_layout_in_frame, 0); av_opt_set_int(avr, "out_channel_layout", info.channel_layout, 0); av_opt_set_int(avr, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0); @@ -826,11 +827,11 @@ void FrameMapper::ResampleMappedAudio(std::shared_ptr frame, int64_t orig av_opt_set_int(avr, "out_sample_rate", info.sample_rate, 0); av_opt_set_int(avr, "in_channels", channels_in_frame, 0); av_opt_set_int(avr, "out_channels", info.channels, 0); - avresample_open(avr); + SWR_INIT(avr); } // Convert audio samples - nb_samples = avresample_convert(avr, // audio resample context + nb_samples = SWR_CONVERT(avr, // audio resample context audio_converted->data, // output data pointers audio_converted->linesize[0], // output plane size, in bytes. (0 if unknown) audio_converted->nb_samples, // maximum number of samples that the output buffer can hold diff --git a/src/Timeline.cpp b/src/Timeline.cpp index d042aeeb..db406efc 100644 --- a/src/Timeline.cpp +++ b/src/Timeline.cpp @@ -576,8 +576,13 @@ void Timeline::update_open_clips(Clip *clip, bool does_clip_intersect) // Add clip to 'opened' list, because it's missing open_clips[clip] = clip; - // Open the clip - clip->Open(); + try { + // Open the clip + clip->Open(); + + } catch (const InvalidFile & e) { + // ... + } } // Debug output @@ -717,7 +722,7 @@ std::shared_ptr Timeline::GetFrame(int64_t requested_frame) #pragma omp parallel { // Loop through all requested frames - #pragma omp for ordered firstprivate(nearby_clips, requested_frame, minimum_frames) + #pragma omp for ordered firstprivate(nearby_clips, requested_frame, minimum_frames) schedule(static,1) for (int64_t frame_number = requested_frame; frame_number < requested_frame + minimum_frames; frame_number++) { // Debug output @@ -1000,13 +1005,14 @@ void Timeline::SetJsonValue(Json::Value root) { if (!existing_effect["type"].isNull()) { // Create instance of effect - e = EffectInfo().CreateEffect(existing_effect["type"].asString()); + if (e = EffectInfo().CreateEffect(existing_effect["type"].asString())) { - // Load Json into Effect - e->SetJsonValue(existing_effect); + // Load Json into Effect + e->SetJsonValue(existing_effect); - // Add Effect to Timeline - AddEffect(e); + // Add Effect to Timeline + AddEffect(e); + } } } } @@ -1270,13 +1276,14 @@ void Timeline::apply_json_to_effects(Json::Value change, EffectBase* existing_ef EffectBase *e = NULL; // Init the matching effect object - e = EffectInfo().CreateEffect(effect_type); + if (e = EffectInfo().CreateEffect(effect_type)) { - // Load Json into Effect - e->SetJsonValue(change["value"]); + // Load Json into Effect + e->SetJsonValue(change["value"]); - // Add Effect to Timeline - AddEffect(e); + // Add Effect to Timeline + AddEffect(e); + } } else if (change_type == "update") { diff --git a/tests/FrameMapper_Tests.cpp b/tests/FrameMapper_Tests.cpp index 2f61179d..053df31f 100644 --- a/tests/FrameMapper_Tests.cpp +++ b/tests/FrameMapper_Tests.cpp @@ -199,9 +199,9 @@ TEST(FrameMapper_resample_audio_48000_to_41000) // Check details CHECK_EQUAL(1, map.GetFrame(1)->GetAudioChannelsCount()); - CHECK_EQUAL(882, map.GetFrame(1)->GetAudioSamplesCount()); - CHECK_EQUAL(882, map.GetFrame(2)->GetAudioSamplesCount()); - CHECK_EQUAL(882, map.GetFrame(50)->GetAudioSamplesCount()); + 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(); diff --git a/tests/ReaderBase_Tests.cpp b/tests/ReaderBase_Tests.cpp index 9d435304..70ca90d5 100644 --- a/tests/ReaderBase_Tests.cpp +++ b/tests/ReaderBase_Tests.cpp @@ -44,9 +44,9 @@ TEST(ReaderBase_Derived_Class) std::shared_ptr GetFrame(int64_t number) { std::shared_ptr f(new Frame()); return f; } void Close() { }; void Open() { }; - string Json() { }; + string Json() { return NULL; }; void SetJson(string value) { }; - Json::Value JsonValue() { }; + Json::Value JsonValue() { return (int) NULL; }; void SetJsonValue(Json::Value root) { }; bool IsOpen() { return true; }; string Name() { return "TestReader"; }; From 6b37ad7e1d72c1719ccdd8e14c1fd39ff86f50d1 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Tue, 11 Sep 2018 10:48:30 -0500 Subject: [PATCH 033/223] Limiting threads for both FFmpeg and OpenMP (attempting to find a good balance of parallel performance, while not spawning too many threads). Sometimes more is not always better. --- include/OpenMPUtilities.h | 6 ++++-- src/FFmpegReader.cpp | 4 ++-- src/FFmpegWriter.cpp | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/include/OpenMPUtilities.h b/include/OpenMPUtilities.h index c0f5597b..1525730d 100644 --- a/include/OpenMPUtilities.h +++ b/include/OpenMPUtilities.h @@ -32,8 +32,10 @@ #include #include -// Calculate the # of OpenMP Threads to allow -#define OPEN_MP_NUM_PROCESSORS omp_get_num_procs() +// Calculate the # of OpenMP and FFmpeg Threads to allow. We are limiting both +// of these based on our own performance tests (more is not always better). +#define OPEN_MP_NUM_PROCESSORS (min(omp_get_num_procs(), 6)) +#define FF_NUM_PROCESSORS (min(omp_get_num_procs(), 12)) using namespace std; diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 17a612c1..9711b2e5 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -153,7 +153,7 @@ void FFmpegReader::Open() pCodecCtx = AV_GET_CODEC_CONTEXT(pStream, pCodec); // Set number of threads equal to number of processors (not to exceed 16) - pCodecCtx->thread_count = min(OPEN_MP_NUM_PROCESSORS, 16); + pCodecCtx->thread_count = min(FF_NUM_PROCESSORS, 16); if (pCodec == NULL) { throw InvalidCodec("A valid video codec could not be found for this file.", path); @@ -191,7 +191,7 @@ void FFmpegReader::Open() aCodecCtx = AV_GET_CODEC_CONTEXT(aStream, aCodec); // Set number of threads equal to number of processors (not to exceed 16) - aCodecCtx->thread_count = min(OPEN_MP_NUM_PROCESSORS, 16); + aCodecCtx->thread_count = min(FF_NUM_PROCESSORS, 16); if (aCodec == NULL) { throw InvalidCodec("A valid audio codec could not be found for this file.", path); diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index 11017219..bd5486b9 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -1026,7 +1026,7 @@ void FFmpegWriter::open_audio(AVFormatContext *oc, AVStream *st) AV_GET_CODEC_FROM_STREAM(st, audio_codec) // Set number of threads equal to number of processors (not to exceed 16) - audio_codec->thread_count = min(OPEN_MP_NUM_PROCESSORS, 16); + audio_codec->thread_count = min(FF_NUM_PROCESSORS, 16); // Find the audio encoder codec = avcodec_find_encoder_by_name(info.acodec.c_str()); @@ -1100,7 +1100,7 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) AV_GET_CODEC_FROM_STREAM(st, video_codec) // Set number of threads equal to number of processors (not to exceed 16) - video_codec->thread_count = min(OPEN_MP_NUM_PROCESSORS, 16); + video_codec->thread_count = min(FF_NUM_PROCESSORS, 16); /* find the video encoder */ codec = avcodec_find_encoder_by_name(info.vcodec.c_str()); From 4db2217f0d94dfce07359d6e526efe34389c87af Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Tue, 11 Sep 2018 20:18:11 -0700 Subject: [PATCH 034/223] Fallback for hardware accelerated decode to software decode in case the GPU can noy handle the dimensions of the frame. Not yet working, va_config not yet set. --- src/FFmpegReader.cpp | 131 ++++++++++++++++++++++++++++--------------- src/FFmpegWriter.cpp | 6 -- 2 files changed, 86 insertions(+), 51 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 4dabce7e..caa33de1 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -29,6 +29,7 @@ */ #include "../include/FFmpegReader.h" +#include "libavutil/hwcontext_vaapi.h" using namespace openshot; @@ -111,7 +112,6 @@ bool AudioLocation::is_near(AudioLocation location, int samples_per_frame, int64 } #if IS_FFMPEG_3_2 -#pragma message "You are compiling with experimental hardware decode" static enum AVPixelFormat get_hw_dec_format(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) { @@ -226,55 +226,96 @@ void FFmpegReader::Open() // Get codec and codec context from stream AVCodec *pCodec = avcodec_find_decoder(codecId); - pCodecCtx = AV_GET_CODEC_CONTEXT(pStream, pCodec); - #if IS_FFMPEG_3_2 - if (hw_de_on) { - hw_de_supported = is_hardware_decode_supported(pCodecCtx->codec_id); - } - #endif - // Set number of threads equal to number of processors (not to exceed 16) - pCodecCtx->thread_count = min(FF_NUM_PROCESSORS, 16); - - if (pCodec == NULL) { - throw InvalidCodec("A valid video codec could not be found for this file.", path); - } - - // Init options AVDictionary *opts = NULL; - av_dict_set(&opts, "strict", "experimental", 0); + int retry_decode_open = 2; + // If hw accel is selected but hardware connot handle repeat with software decoding + do { + pCodecCtx = AV_GET_CODEC_CONTEXT(pStream, pCodec); + #if IS_FFMPEG_3_2 + if (hw_de_on && (retry_decode_open==2)) { + // Up to here no decision is made if hardware or software decode + hw_de_supported = is_hardware_decode_supported(pCodecCtx->codec_id); + } + #endif + retry_decode_open = 0; + // Set number of threads equal to number of processors (not to exceed 16) + pCodecCtx->thread_count = min(FF_NUM_PROCESSORS, 16); - #if IS_FFMPEG_3_2 - if (hw_de_on && hw_de_supported) { - // Open Hardware Acceleration - // Use the hw device given in the environment variable HW_DE_DEVICE_SET or the default if not set - char *dev_hw = NULL; - dev_hw = getenv( "HW_DE_DEVICE_SET" ); - // Check if it is there and writable - if( dev_hw != NULL && access( dev_hw, W_OK ) == -1 ) { - dev_hw = NULL; // use default - } - hw_device_ctx = NULL; - pCodecCtx->get_format = get_hw_dec_format; - if (av_hwdevice_ctx_create(&hw_device_ctx, hw_de_av_device_type, dev_hw, NULL, 0) >= 0) { - if (!(pCodecCtx->hw_device_ctx = av_buffer_ref(hw_device_ctx))) { - throw InvalidCodec("Hardware device reference create failed.", path); + if (pCodec == NULL) { + throw InvalidCodec("A valid video codec could not be found for this file.", path); + } + + // Init options + av_dict_set(&opts, "strict", "experimental", 0); + #if IS_FFMPEG_3_2 + if (hw_de_on && hw_de_supported) { + // Open Hardware Acceleration + // Use the hw device given in the environment variable HW_DE_DEVICE_SET or the default if not set + char *dev_hw = NULL; + dev_hw = getenv( "HW_DE_DEVICE_SET" ); + // Check if it is there and writable + if( dev_hw != NULL && access( dev_hw, W_OK ) == -1 ) { + dev_hw = NULL; // use default + } + hw_device_ctx = NULL; + // Here the first hardware initialisations are made + pCodecCtx->get_format = get_hw_dec_format; + if (av_hwdevice_ctx_create(&hw_device_ctx, hw_de_av_device_type, dev_hw, NULL, 0) >= 0) { + if (!(pCodecCtx->hw_device_ctx = av_buffer_ref(hw_device_ctx))) { + throw InvalidCodec("Hardware device reference create failed.", path); + } + } + else { + throw InvalidCodec("Hardware device create failed.", path); } } - else { - throw InvalidCodec("Hardware device create failed.", path); - } - } -/* // Check to see if the hardware supports that file (size!) - AVHWFramesConstraints *constraints = NULL; - constraints = av_hwdevice_get_hwframe_constraints(hw_device_ctx->device_ref,hwconfig); - if (constraints) - av_hwframe_constraints_free(&constraints); -*/ - #endif - // Open video codec - if (avcodec_open2(pCodecCtx, pCodec, &opts) < 0) - throw InvalidCodec("A video codec was found, but could not be opened.", path); + #endif + // Open video codec + if (avcodec_open2(pCodecCtx, pCodec, &opts) < 0) + throw InvalidCodec("A video codec was found, but could not be opened.", path); + #if IS_FFMPEG_3_2 + if (hw_de_on && hw_de_supported) { + AVHWFramesConstraints *constraints = NULL; + // NOT WORKING needs hwconfig config_id !!!!!!!!!!!!!!!!!!!!!!!!!!! + //AVVAAPIHWConfig *hwconfig = NULL; + // void *hwconfig = NULL; + // hwconfig = av_hwdevice_hwconfig_alloc(hw_device_ctx); + //hwconfig->config_id = ((VAAPIDecodeContext *)pCodecCtx->priv_data)->va_config; + // constraints = av_hwdevice_get_hwframe_constraints(hw_device_ctx,(void*)hwconfig); + constraints = av_hwdevice_get_hwframe_constraints(hw_device_ctx,NULL); // No usable information! + if (constraints) { +// constraints->max_height = 1100; // Just for testing +// constraints->max_width = 1950; // Just for testing + if (pCodecCtx->coded_width < constraints->min_width || + pCodecCtx->coded_height < constraints->min_height || + pCodecCtx->coded_width > constraints->max_width || + pCodecCtx->coded_height > constraints->max_height) { + cerr << "DIMENSIONS ARE TOO LARGE for hardware acceleration\n"; + hw_de_supported = 0; + retry_decode_open = 1; + AV_FREE_CONTEXT(pCodecCtx); + if (hw_device_ctx) { + av_buffer_unref(&hw_device_ctx); + hw_device_ctx = NULL; + } + } + else { + // All is just peachy + cerr << "MinWidth : " << constraints->min_width << "MinHeight : " << constraints->min_height << "MaxWidth : " << constraints->max_width << "MaxHeight : " << constraints->max_height << "\n"; + cerr << "Frame width :" << pCodecCtx->coded_width << "Frame height :" << pCodecCtx->coded_height << "\n"; + retry_decode_open = 0; + } + av_hwframe_constraints_free(&constraints); + } + else { + cerr << "Constraints could not be found\n"; + } + } // if hw_de_on && hw_de_supported + #else + retry_decode_open = 0; + #endif + } while (retry_decode_open); // retry_decode_open // Free options av_dict_free(&opts); diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index 924f3490..c16ce7a8 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -71,12 +71,6 @@ static int set_hwframe_ctx(AVCodecContext *ctx, AVBufferRef *hw_device_ctx, int6 } #endif -#if IS_FFMPEG_3_2 -//#if defined(__linux__) -#pragma message "You are compiling with experimental hardware encode" -//#endif -#endif - FFmpegWriter::FFmpegWriter(string path) : path(path), fmt(NULL), oc(NULL), audio_st(NULL), video_st(NULL), audio_pts(0), video_pts(0), samples(NULL), audio_outbuf(NULL), audio_outbuf_size(0), audio_input_frame_size(0), audio_input_position(0), From a1ffa6b13202c8ab2caf85880bc36f924104a134 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Tue, 11 Sep 2018 20:30:56 -0700 Subject: [PATCH 035/223] Removed one include --- src/FFmpegReader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index caa33de1..e2d5d7bc 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -29,7 +29,7 @@ */ #include "../include/FFmpegReader.h" -#include "libavutil/hwcontext_vaapi.h" +//#include "libavutil/hwcontext_vaapi.h" using namespace openshot; From cfcddd13e5333f1ef0040b26659baba4ecb8ff20 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Thu, 13 Sep 2018 12:37:32 -0700 Subject: [PATCH 036/223] Still not able to retreive the maximum dimensions supported by the hardware (line 312 FFmegReader.cpp) Now using defaults of 1950 * 1100 defined in lines 35,36 --- src/FFmpegReader.cpp | 71 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 58 insertions(+), 13 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index e2d5d7bc..c89f8cd0 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -29,7 +29,36 @@ */ #include "../include/FFmpegReader.h" -//#include "libavutil/hwcontext_vaapi.h" +#include "libavutil/hwcontext_vaapi.h" + + +#define MAX_SUPPORTED_WIDTH 1950 +#define MAX_SUPPORTED_HEIGHT 1100 + +typedef struct VAAPIDecodeContext { + VAProfile va_profile; + VAEntrypoint va_entrypoint; + VAConfigID va_config; + VAContextID va_context; + + #if FF_API_STRUCT_VAAPI_CONTEXT +// FF_DISABLE_DEPRECATION_WARNINGS + int have_old_context; + struct vaapi_context *old_context; + AVBufferRef *device_ref; +// FF_ENABLE_DEPRECATION_WARNINGS + #endif + + AVHWDeviceContext *device; + AVVAAPIDeviceContext *hwctx; + + AVHWFramesContext *frames; + AVVAAPIFramesContext *hwfc; + + enum AVPixelFormat surface_format; + int surface_count; + } VAAPIDecodeContext; + using namespace openshot; @@ -277,16 +306,12 @@ void FFmpegReader::Open() #if IS_FFMPEG_3_2 if (hw_de_on && hw_de_supported) { AVHWFramesConstraints *constraints = NULL; - // NOT WORKING needs hwconfig config_id !!!!!!!!!!!!!!!!!!!!!!!!!!! - //AVVAAPIHWConfig *hwconfig = NULL; - // void *hwconfig = NULL; - // hwconfig = av_hwdevice_hwconfig_alloc(hw_device_ctx); - //hwconfig->config_id = ((VAAPIDecodeContext *)pCodecCtx->priv_data)->va_config; - // constraints = av_hwdevice_get_hwframe_constraints(hw_device_ctx,(void*)hwconfig); - constraints = av_hwdevice_get_hwframe_constraints(hw_device_ctx,NULL); // No usable information! + void *hwconfig = NULL; + hwconfig = av_hwdevice_hwconfig_alloc(hw_device_ctx); + // NOT WORKING needs va_config ! + ((AVVAAPIHWConfig *)hwconfig)->config_id = ((VAAPIDecodeContext *)(pCodecCtx->priv_data))->va_config; + constraints = av_hwdevice_get_hwframe_constraints(hw_device_ctx,hwconfig); if (constraints) { -// constraints->max_height = 1100; // Just for testing -// constraints->max_width = 1950; // Just for testing if (pCodecCtx->coded_width < constraints->min_width || pCodecCtx->coded_height < constraints->min_height || pCodecCtx->coded_width > constraints->max_width || @@ -302,14 +327,34 @@ void FFmpegReader::Open() } else { // All is just peachy - cerr << "MinWidth : " << constraints->min_width << "MinHeight : " << constraints->min_height << "MaxWidth : " << constraints->max_width << "MaxHeight : " << constraints->max_height << "\n"; - cerr << "Frame width :" << pCodecCtx->coded_width << "Frame height :" << pCodecCtx->coded_height << "\n"; + cerr << "Min width : " << constraints->min_width << " MinHeight : " << constraints->min_height << "MaxWidth : " << constraints->max_width << "MaxHeight : " << constraints->max_height << "\n"; + cerr << "Frame width : " << pCodecCtx->coded_width << " Frame height : " << pCodecCtx->coded_height << "\n"; retry_decode_open = 0; } av_hwframe_constraints_free(&constraints); + if (hwconfig) { + av_freep(&hwconfig); + } } else { - cerr << "Constraints could not be found\n"; + cerr << "Constraints could not be found using default 1k limit\n"; + if (pCodecCtx->coded_width < 0 || + pCodecCtx->coded_height < 0 || + pCodecCtx->coded_width > MAX_SUPPORTED_WIDTH || + pCodecCtx->coded_height > MAX_SUPPORTED_HEIGHT) { + cerr << "DIMENSIONS ARE TOO LARGE for hardware acceleration\n"; + hw_de_supported = 0; + retry_decode_open = 1; + AV_FREE_CONTEXT(pCodecCtx); + if (hw_device_ctx) { + av_buffer_unref(&hw_device_ctx); + hw_device_ctx = NULL; + } + } + else { + cerr << "Frame width : " << pCodecCtx->coded_width << " Frame height : " << pCodecCtx->coded_height << "\n"; + retry_decode_open = 0; + } } } // if hw_de_on && hw_de_supported #else From 10c8d695957ab184182029d558ffe11ccc0ec9b1 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Thu, 13 Sep 2018 14:45:09 -0700 Subject: [PATCH 037/223] Maximum width and height for hardware decode can now be set in preferences --- src/FFmpegReader.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index c89f8cd0..725ba524 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -337,11 +337,15 @@ void FFmpegReader::Open() } } else { - cerr << "Constraints could not be found using default 1k limit\n"; + int max_h, max_w; + max_h = ((getenv( "LIMIT_HEIGHT_MAX" )==NULL) ? MAX_SUPPORTED_HEIGHT : atoi(getenv( "LIMIT_HEIGHT_MAX" ))); + max_w = ((getenv( "LIMIT_WIDTH_MAX" )==NULL) ? MAX_SUPPORTED_WIDTH : atoi(getenv( "LIMIT_WIDTH_MAX" ))); + cerr << "Constraints could not be found using default limit\n"; + cerr << " Max Width : " << max_w << " Height : " << max_h << "\n"; if (pCodecCtx->coded_width < 0 || pCodecCtx->coded_height < 0 || - pCodecCtx->coded_width > MAX_SUPPORTED_WIDTH || - pCodecCtx->coded_height > MAX_SUPPORTED_HEIGHT) { + pCodecCtx->coded_width > max_w || + pCodecCtx->coded_height > max_h ) { cerr << "DIMENSIONS ARE TOO LARGE for hardware acceleration\n"; hw_de_supported = 0; retry_decode_open = 1; From 3a2d46826c9fd6249861940181b6b5978e347312 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Thu, 13 Sep 2018 18:04:16 -0700 Subject: [PATCH 038/223] Included an if for included files not present in ffmpeg 2 --- src/FFmpegReader.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 725ba524..aa3ef4ac 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -29,6 +29,8 @@ */ #include "../include/FFmpegReader.h" + +#if IS_FFMPEG_3_2 #include "libavutil/hwcontext_vaapi.h" @@ -58,6 +60,7 @@ typedef struct VAAPIDecodeContext { enum AVPixelFormat surface_format; int surface_count; } VAAPIDecodeContext; +#endif using namespace openshot; From d97a1bc85057212e83c6e575772c2f91232082f6 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Fri, 14 Sep 2018 14:03:03 -0700 Subject: [PATCH 039/223] Commented code that isn't working yet but complicates compilation by needing extra packages. --- src/FFmpegReader.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index aa3ef4ac..f8c5f4af 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -31,12 +31,12 @@ #include "../include/FFmpegReader.h" #if IS_FFMPEG_3_2 -#include "libavutil/hwcontext_vaapi.h" +//#include "libavutil/hwcontext_vaapi.h" #define MAX_SUPPORTED_WIDTH 1950 #define MAX_SUPPORTED_HEIGHT 1100 - +/* typedef struct VAAPIDecodeContext { VAProfile va_profile; VAEntrypoint va_entrypoint; @@ -60,6 +60,7 @@ typedef struct VAAPIDecodeContext { enum AVPixelFormat surface_format; int surface_count; } VAAPIDecodeContext; + */ #endif @@ -312,7 +313,7 @@ void FFmpegReader::Open() void *hwconfig = NULL; hwconfig = av_hwdevice_hwconfig_alloc(hw_device_ctx); // NOT WORKING needs va_config ! - ((AVVAAPIHWConfig *)hwconfig)->config_id = ((VAAPIDecodeContext *)(pCodecCtx->priv_data))->va_config; + // ((AVVAAPIHWConfig *)hwconfig)->config_id = ((VAAPIDecodeContext *)(pCodecCtx->priv_data))->va_config; constraints = av_hwdevice_get_hwframe_constraints(hw_device_ctx,hwconfig); if (constraints) { if (pCodecCtx->coded_width < constraints->min_width || From 08c7f88376fc2e79c265a654fa36ba90a5d19f38 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Fri, 14 Sep 2018 14:40:29 -0700 Subject: [PATCH 040/223] The part of the code that should get the config that is used to get the constraints of the GPU is now inside a #if . One can enable it by setting the constant in line 33 of FFmpegReader.cpp to 1. Do not enable that part unless you want to fid a way that works as it also needs the package libva-dev (Ubuntu) to be installed. --- src/FFmpegReader.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index f8c5f4af..3b508f23 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -30,13 +30,15 @@ #include "../include/FFmpegReader.h" +#define PRAYFORAWONDER 0 + #if IS_FFMPEG_3_2 -//#include "libavutil/hwcontext_vaapi.h" - - #define MAX_SUPPORTED_WIDTH 1950 #define MAX_SUPPORTED_HEIGHT 1100 -/* + +#if PRAYFORAWONDER +#include "libavutil/hwcontext_vaapi.h" + typedef struct VAAPIDecodeContext { VAProfile va_profile; VAEntrypoint va_entrypoint; @@ -60,7 +62,8 @@ typedef struct VAAPIDecodeContext { enum AVPixelFormat surface_format; int surface_count; } VAAPIDecodeContext; - */ + + #endif #endif @@ -313,7 +316,9 @@ void FFmpegReader::Open() void *hwconfig = NULL; hwconfig = av_hwdevice_hwconfig_alloc(hw_device_ctx); // NOT WORKING needs va_config ! - // ((AVVAAPIHWConfig *)hwconfig)->config_id = ((VAAPIDecodeContext *)(pCodecCtx->priv_data))->va_config; + #if PRAYFORAWONDER + ((AVVAAPIHWConfig *)hwconfig)->config_id = ((VAAPIDecodeContext *)(pCodecCtx->priv_data))->va_config; + #endif constraints = av_hwdevice_get_hwframe_constraints(hw_device_ctx,hwconfig); if (constraints) { if (pCodecCtx->coded_width < constraints->min_width || From c0929d22d41fe29912748acf383c149c7674d745 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sat, 15 Sep 2018 18:37:29 -0500 Subject: [PATCH 041/223] Bumping version to 0.2.1 (SO: 16) --- include/Version.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/Version.h b/include/Version.h index 971d5cfe..a22afb05 100644 --- a/include/Version.h +++ b/include/Version.h @@ -36,8 +36,8 @@ #define OPENSHOT_VERSION_MAJOR 0; /// Major version number is incremented when huge features are added or improved. #define OPENSHOT_VERSION_MINOR 2; /// Minor version is incremented when smaller (but still very important) improvements are added. -#define OPENSHOT_VERSION_BUILD 0; /// Build number is incremented when minor bug fixes and less important improvements are added. -#define OPENSHOT_VERSION_SO 15; /// Shared object version number. This increments any time the API and ABI changes (so old apps will no longer link) +#define OPENSHOT_VERSION_BUILD 1; /// Build number is incremented when minor bug fixes and less important improvements are added. +#define OPENSHOT_VERSION_SO 16; /// Shared object version number. This increments any time the API and ABI changes (so old apps will no longer link) #define OPENSHOT_VERSION_MAJOR_MINOR STRINGIZE(OPENSHOT_VERSION_MAJOR) "." STRINGIZE(OPENSHOT_VERSION_MINOR); /// A string of the "Major.Minor" version #define OPENSHOT_VERSION_ALL STRINGIZE(OPENSHOT_VERSION_MAJOR) "." STRINGIZE(OPENSHOT_VERSION_MINOR) "." STRINGIZE(OPENSHOT_VERSION_BUILD); /// A string of the entire version "Major.Minor.Build" From df9d1a57170e387613d30fcd4a2283d73459a450 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Sun, 16 Sep 2018 18:14:31 -0700 Subject: [PATCH 042/223] Implement the use of CRF instead od kB/s or MB/s for some formats: VP8, VP9, h264, h265 0 crf with VP9 is lossless 0 crf with VP8, h264, h265 should be lossless --- src/FFmpegWriter.cpp | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index c16ce7a8..b1d98a80 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -241,7 +241,9 @@ void FFmpegWriter::SetVideoOptions(bool has_video, string codec, Fraction fps, i info.pixel_ratio.num = pixel_ratio.num; info.pixel_ratio.den = pixel_ratio.den; } - if (bit_rate >= 1000) + if (bit_rate >= 1000) // bit_rate is the bitrate in b/s + info.video_bit_rate = bit_rate; + if ((bit_rate > 0) && (bit_rate <=63)) // bit_rate is the crf value info.video_bit_rate = bit_rate; info.interlaced_frame = interlaced; @@ -1040,7 +1042,30 @@ AVStream* FFmpegWriter::add_video_stream() #endif /* Init video encoder options */ - c->bit_rate = info.video_bit_rate; + if (info.video_bit_rate > 1000) { + c->bit_rate = info.video_bit_rate; + } +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 39, 101) + else { + switch (c->codec_id) { + case AV_CODEC_ID_VP8 : + av_opt_set_int(c->priv_data, "crf", min(info.video_bit_rate,63), 0); + break; + case AV_CODEC_ID_VP9 : + av_opt_set_int(c->priv_data, "crf", min(info.video_bit_rate,63), 0); + if (info.video_bit_rate == 0) { + av_opt_set_int(c->priv_data, "lossless", 1, 0); + } + break; + case AV_CODEC_ID_H264 : + av_opt_set_int(c->priv_data, "crf", min(info.video_bit_rate,51), 0); + break; + case AV_CODEC_ID_H265 : + av_opt_set_int(c->priv_data, "crf", min(info.video_bit_rate,51), 0); + break; + } + } +#endif //TODO: Implement variable bitrate feature (which actually works). This implementation throws //invalid bitrate errors and rc buffer underflow errors, etc... From b63a63ceb2a43ef20e7ba79f173276d465575d14 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Mon, 17 Sep 2018 00:27:30 -0500 Subject: [PATCH 043/223] Fix many bugs around FPS and video length calculation (especially for MP3 and streaming WEBM formats). Also protecting samples_per_frame calculation to keep from crashing on undetected FPS. --- src/FFmpegReader.cpp | 33 ++++++++++++++++++++++++--------- src/Frame.cpp | 2 ++ 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 9711b2e5..243d7385 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -333,6 +333,11 @@ void FFmpegReader::UpdateAudioInfo() info.height = 480; } + // Fix invalid video lengths for certain types of files (MP3 for example) + if (info.has_video && ((info.duration * info.fps.ToDouble()) - info.video_length > 60)) { + info.video_length = info.duration * info.fps.ToDouble(); + } + // Add audio metadata (if any found) AVDictionaryEntry *tag = NULL; while ((tag = av_dict_get(aStream->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) { @@ -422,7 +427,7 @@ void FFmpegReader::UpdateVideoInfo() } // Override an invalid framerate - if (info.fps.ToFloat() > 240.0f || (info.fps.num == 0 || info.fps.den == 0)) { + if (info.fps.ToFloat() > 240.0f || (info.fps.num <= 0 || info.fps.den <= 0) || info.video_length <= 0) { // Calculate FPS, duration, video bit rate, and video length manually // by scanning through all the video stream packets CheckFPS(); @@ -1864,7 +1869,6 @@ void FFmpegReader::CheckWorkingFrames(bool end_of_stream, int64_t requested_fram void FFmpegReader::CheckFPS() { check_fps = true; - AV_ALLOCATE_IMAGE(pFrame, AV_GET_CODEC_PIXEL_FORMAT(pStream, pCodecCtx), info.width, info.height); int first_second_counter = 0; int second_second_counter = 0; @@ -1872,6 +1876,7 @@ void FFmpegReader::CheckFPS() int forth_second_counter = 0; int fifth_second_counter = 0; int frames_detected = 0; + int64_t pts = 0; // Loop through the stream while (true) @@ -1891,7 +1896,7 @@ void FFmpegReader::CheckFPS() UpdatePTSOffset(true); // Get PTS of this packet - int64_t pts = GetVideoPTS(); + pts = GetVideoPTS(); // Remove pFrame RemoveAVFrame(pFrame); @@ -1922,7 +1927,7 @@ void FFmpegReader::CheckFPS() // Double check that all counters have greater than zero (or give up) if (second_second_counter != 0 && third_second_counter != 0 && forth_second_counter != 0 && fifth_second_counter != 0) { - // Calculate average FPS + // Calculate average FPS (average of first few seconds) int sum_fps = second_second_counter + third_second_counter + forth_second_counter + fifth_second_counter; int avg_fps = round(sum_fps / 4.0f); @@ -1931,22 +1936,32 @@ void FFmpegReader::CheckFPS() // Update Duration and Length info.video_length = frames_detected; - info.duration = frames_detected / round(sum_fps / 4.0f); + info.duration = frames_detected / (sum_fps / 4.0f); + + // Update video bit rate + info.video_bit_rate = info.file_size / info.duration; + } else if (second_second_counter != 0 && third_second_counter != 0) { + // Calculate average FPS (only on second 2) + int sum_fps = second_second_counter; + + // Update FPS + info.fps = Fraction(sum_fps, 1); + + // Update Duration and Length + info.video_length = frames_detected; + info.duration = frames_detected / float(sum_fps); // Update video bit rate info.video_bit_rate = info.file_size / info.duration; } else { - // Too short to determine framerate, just default FPS // Set a few important default video settings (so audio can be divided into frames) info.fps.num = 30; info.fps.den = 1; - info.video_timebase.num = info.fps.den; - info.video_timebase.den = info.fps.num; // Calculate number of frames info.video_length = frames_detected; - info.duration = frames_detected / info.video_timebase.ToFloat(); + info.duration = frames_detected / info.fps.ToFloat(); } } diff --git a/src/Frame.cpp b/src/Frame.cpp index 16a0d267..a00fc232 100644 --- a/src/Frame.cpp +++ b/src/Frame.cpp @@ -512,6 +512,8 @@ int Frame::GetSamplesPerFrame(int64_t number, Fraction fps, int sample_rate, int // 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. int samples_per_frame = round(total_samples - previous_samples); + if (samples_per_frame < 0) + samples_per_frame = 0; return samples_per_frame; } From 38f4bc6a216a33344c3b0857dd302d1e2bbd96f2 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Tue, 18 Sep 2018 11:10:16 -0700 Subject: [PATCH 044/223] Adding aoutput if decode device is not found --- src/FFmpegReader.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 3b508f23..b9be0b54 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -292,6 +292,7 @@ void FFmpegReader::Open() // Check if it is there and writable if( dev_hw != NULL && access( dev_hw, W_OK ) == -1 ) { dev_hw = NULL; // use default + cerr << "\n\n\nDecode Device not present using default\n\n\n"; } hw_device_ctx = NULL; // Here the first hardware initialisations are made From 800dc874592e2451adde09eb39bd09416fac8236 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Tue, 18 Sep 2018 11:35:19 -0700 Subject: [PATCH 045/223] Information is printed to the console where openshot was started that shows if hardware decode or siftware decode is being used --- src/FFmpegReader.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index b9be0b54..7dbb21d9 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -292,7 +292,7 @@ void FFmpegReader::Open() // Check if it is there and writable if( dev_hw != NULL && access( dev_hw, W_OK ) == -1 ) { dev_hw = NULL; // use default - cerr << "\n\n\nDecode Device not present using default\n\n\n"; + cerr << "\n\n\nDecode Device not present using default\n\n\n"; } hw_device_ctx = NULL; // Here the first hardware initialisations are made @@ -337,6 +337,7 @@ void FFmpegReader::Open() } else { // All is just peachy + cerr << "\nDecode hardware acceleration is used\n"; cerr << "Min width : " << constraints->min_width << " MinHeight : " << constraints->min_height << "MaxWidth : " << constraints->max_width << "MaxHeight : " << constraints->max_height << "\n"; cerr << "Frame width : " << pCodecCtx->coded_width << " Frame height : " << pCodecCtx->coded_height << "\n"; retry_decode_open = 0; @@ -351,12 +352,13 @@ void FFmpegReader::Open() max_h = ((getenv( "LIMIT_HEIGHT_MAX" )==NULL) ? MAX_SUPPORTED_HEIGHT : atoi(getenv( "LIMIT_HEIGHT_MAX" ))); max_w = ((getenv( "LIMIT_WIDTH_MAX" )==NULL) ? MAX_SUPPORTED_WIDTH : atoi(getenv( "LIMIT_WIDTH_MAX" ))); cerr << "Constraints could not be found using default limit\n"; - cerr << " Max Width : " << max_w << " Height : " << max_h << "\n"; if (pCodecCtx->coded_width < 0 || pCodecCtx->coded_height < 0 || pCodecCtx->coded_width > max_w || pCodecCtx->coded_height > max_h ) { cerr << "DIMENSIONS ARE TOO LARGE for hardware acceleration\n"; + cerr << " Max Width : " << max_w << " Height : " << max_h << "\n"; + cerr << "Frame width : " << pCodecCtx->coded_width << " Frame height : " << pCodecCtx->coded_height << "\n"; hw_de_supported = 0; retry_decode_open = 1; AV_FREE_CONTEXT(pCodecCtx); @@ -366,11 +368,16 @@ void FFmpegReader::Open() } } else { + cerr << "\nDecode hardware acceleration is used\n"; + cerr << " Max Width : " << max_w << " Height : " << max_h << "\n"; cerr << "Frame width : " << pCodecCtx->coded_width << " Frame height : " << pCodecCtx->coded_height << "\n"; retry_decode_open = 0; } } } // if hw_de_on && hw_de_supported + else { + cerr << "\nDecode in software is used\n"; + } #else retry_decode_open = 0; #endif From 161acb3d7d5d95284f10f05287465c268bc692f3 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Tue, 18 Sep 2018 12:38:53 -0700 Subject: [PATCH 046/223] Include messages in the compile display to make sure the right ffmpeg version is used (>= 3.2) to get hardware acceleration --- src/FFmpegReader.cpp | 6 ++++++ src/FFmpegWriter.cpp | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 7dbb21d9..76c0ddd0 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -32,6 +32,12 @@ #define PRAYFORAWONDER 0 +#if IS_FFMPEG_3_2 +#pragma message "You are compiling with experimental hardware decode" +#else +#pragma message "You are compiling only with software decode" +#endif + #if IS_FFMPEG_3_2 #define MAX_SUPPORTED_WIDTH 1950 #define MAX_SUPPORTED_HEIGHT 1100 diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index b1d98a80..0b978d8a 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -32,6 +32,12 @@ using namespace openshot; +#if IS_FFMPEG_3_2 +#pragma message "You are compiling with experimental hardware encode" +#else +#pragma message "You are compiling only with software encode" +#endif + #if IS_FFMPEG_3_2 int hw_en_on = 1; // Is set in UI int hw_en_supported = 0; // Is set by FFmpegWriter From 1f36d122984390b8d0da2d0e2acc0d0adcca2a15 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Tue, 18 Sep 2018 14:45:56 -0500 Subject: [PATCH 047/223] Moving delcaration outside of conditional compile logic (so Windows and Mac builds work) --- src/FFmpegWriter.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index b1d98a80..295c7c64 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -1235,13 +1235,14 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) #if IS_FFMPEG_3_2 if (hw_en_on && hw_en_supported) { + char *dev_hw = NULL; #if defined(__linux__) - // Use the hw device given in the environment variable HW_EN_DEVICE_SET or the default if not set - char *dev_hw = getenv( "HW_EN_DEVICE_SET" ); - // Check if it is there and writable - if( dev_hw != NULL && access( dev_hw, W_OK ) == -1 ) { - dev_hw = NULL; // use default - } + // Use the hw device given in the environment variable HW_EN_DEVICE_SET or the default if not set + dev_hw = getenv( "HW_EN_DEVICE_SET" ); + // Check if it is there and writable + if( dev_hw != NULL && access( dev_hw, W_OK ) == -1 ) { + dev_hw = NULL; // use default + } #else dev_hw = NULL; // use default #endif From 555eb1f3e2237caa51a7e573cea0cab18177f2f7 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Tue, 18 Sep 2018 13:08:42 -0700 Subject: [PATCH 048/223] Use logger for messages about acceleration --- src/FFmpegReader.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 76c0ddd0..aab11460 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -35,7 +35,7 @@ #if IS_FFMPEG_3_2 #pragma message "You are compiling with experimental hardware decode" #else -#pragma message "You are compiling only with software decode" +#pragma message "You are compiling only with software decode" #endif #if IS_FFMPEG_3_2 @@ -298,7 +298,8 @@ void FFmpegReader::Open() // Check if it is there and writable if( dev_hw != NULL && access( dev_hw, W_OK ) == -1 ) { dev_hw = NULL; // use default - cerr << "\n\n\nDecode Device not present using default\n\n\n"; + //cerr << "\n\n\nDecode Device not present using default\n\n\n"; + ZmqLogger::Instance()->AppendDebugMethod("\n\n\nDecode Device not present using default\n\n\n", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); } hw_device_ctx = NULL; // Here the first hardware initialisations are made @@ -332,6 +333,7 @@ void FFmpegReader::Open() pCodecCtx->coded_height < constraints->min_height || pCodecCtx->coded_width > constraints->max_width || pCodecCtx->coded_height > constraints->max_height) { + ZmqLogger::Instance()->AppendDebugMethod("DIMENSIONS ARE TOO LARGE for hardware acceleration\n", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); cerr << "DIMENSIONS ARE TOO LARGE for hardware acceleration\n"; hw_de_supported = 0; retry_decode_open = 1; @@ -343,6 +345,7 @@ void FFmpegReader::Open() } else { // All is just peachy + ZmqLogger::Instance()->AppendDebugMethod("\nDecode hardware acceleration is used\n", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); cerr << "\nDecode hardware acceleration is used\n"; cerr << "Min width : " << constraints->min_width << " MinHeight : " << constraints->min_height << "MaxWidth : " << constraints->max_width << "MaxHeight : " << constraints->max_height << "\n"; cerr << "Frame width : " << pCodecCtx->coded_width << " Frame height : " << pCodecCtx->coded_height << "\n"; @@ -362,6 +365,7 @@ void FFmpegReader::Open() pCodecCtx->coded_height < 0 || pCodecCtx->coded_width > max_w || pCodecCtx->coded_height > max_h ) { + ZmqLogger::Instance()->AppendDebugMethod("DIMENSIONS ARE TOO LARGE for hardware acceleration\n", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); cerr << "DIMENSIONS ARE TOO LARGE for hardware acceleration\n"; cerr << " Max Width : " << max_w << " Height : " << max_h << "\n"; cerr << "Frame width : " << pCodecCtx->coded_width << " Frame height : " << pCodecCtx->coded_height << "\n"; @@ -374,6 +378,7 @@ void FFmpegReader::Open() } } else { + ZmqLogger::Instance()->AppendDebugMethod("\nDecode hardware acceleration is used\n", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); cerr << "\nDecode hardware acceleration is used\n"; cerr << " Max Width : " << max_w << " Height : " << max_h << "\n"; cerr << "Frame width : " << pCodecCtx->coded_width << " Frame height : " << pCodecCtx->coded_height << "\n"; @@ -382,6 +387,7 @@ void FFmpegReader::Open() } } // if hw_de_on && hw_de_supported else { + ZmqLogger::Instance()->AppendDebugMethod("\nDecode in software is used\n", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); cerr << "\nDecode in software is used\n"; } #else From 0b260a90879d50a26b22bd8f6640de85bb8023db Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Tue, 18 Sep 2018 15:31:34 -0700 Subject: [PATCH 049/223] Code cleanup and move messages regarding hardware acceleration to Debug Logger --- src/FFmpegReader.cpp | 31 ++++++++++++++++--------------- src/FFmpegWriter.cpp | 15 ++++++++++----- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index aab11460..2225e72c 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -334,7 +334,7 @@ void FFmpegReader::Open() pCodecCtx->coded_width > constraints->max_width || pCodecCtx->coded_height > constraints->max_height) { ZmqLogger::Instance()->AppendDebugMethod("DIMENSIONS ARE TOO LARGE for hardware acceleration\n", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); - cerr << "DIMENSIONS ARE TOO LARGE for hardware acceleration\n"; + //cerr << "DIMENSIONS ARE TOO LARGE for hardware acceleration\n"; hw_de_supported = 0; retry_decode_open = 1; AV_FREE_CONTEXT(pCodecCtx); @@ -345,10 +345,10 @@ void FFmpegReader::Open() } else { // All is just peachy - ZmqLogger::Instance()->AppendDebugMethod("\nDecode hardware acceleration is used\n", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); - cerr << "\nDecode hardware acceleration is used\n"; - cerr << "Min width : " << constraints->min_width << " MinHeight : " << constraints->min_height << "MaxWidth : " << constraints->max_width << "MaxHeight : " << constraints->max_height << "\n"; - cerr << "Frame width : " << pCodecCtx->coded_width << " Frame height : " << pCodecCtx->coded_height << "\n"; + ZmqLogger::Instance()->AppendDebugMethod("\nDecode hardware acceleration is used\n", "Min width :", constraints->min_width, "Min Height :", constraints->min_height, "MaxWidth :", constraints->max_width, "MaxHeight :", constraints->max_height, "Frame width :", pCodecCtx->coded_width, "Frame height :", pCodecCtx->coded_height); + //cerr << "\nDecode hardware acceleration is used\n"; + //cerr << "Min width : " << constraints->min_width << " MinHeight : " << constraints->min_height << "MaxWidth : " << constraints->max_width << "MaxHeight : " << constraints->max_height << "\n"; + //cerr << "Frame width : " << pCodecCtx->coded_width << " Frame height : " << pCodecCtx->coded_height << "\n"; retry_decode_open = 0; } av_hwframe_constraints_free(&constraints); @@ -360,15 +360,16 @@ void FFmpegReader::Open() int max_h, max_w; max_h = ((getenv( "LIMIT_HEIGHT_MAX" )==NULL) ? MAX_SUPPORTED_HEIGHT : atoi(getenv( "LIMIT_HEIGHT_MAX" ))); max_w = ((getenv( "LIMIT_WIDTH_MAX" )==NULL) ? MAX_SUPPORTED_WIDTH : atoi(getenv( "LIMIT_WIDTH_MAX" ))); - cerr << "Constraints could not be found using default limit\n"; + ZmqLogger::Instance()->AppendDebugMethod("Constraints could not be found using default limit\n", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); + //cerr << "Constraints could not be found using default limit\n"; if (pCodecCtx->coded_width < 0 || pCodecCtx->coded_height < 0 || pCodecCtx->coded_width > max_w || pCodecCtx->coded_height > max_h ) { - ZmqLogger::Instance()->AppendDebugMethod("DIMENSIONS ARE TOO LARGE for hardware acceleration\n", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); - cerr << "DIMENSIONS ARE TOO LARGE for hardware acceleration\n"; - cerr << " Max Width : " << max_w << " Height : " << max_h << "\n"; - cerr << "Frame width : " << pCodecCtx->coded_width << " Frame height : " << pCodecCtx->coded_height << "\n"; + ZmqLogger::Instance()->AppendDebugMethod("DIMENSIONS ARE TOO LARGE for hardware acceleration\n", "Max Width :", max_w, "Max Height :", max_h, "Frame width :", pCodecCtx->coded_width, "Frame height :", pCodecCtx->coded_height, "", -1, "", -1); + //cerr << "DIMENSIONS ARE TOO LARGE for hardware acceleration\n"; + //cerr << " Max Width : " << max_w << " Height : " << max_h << "\n"; + //cerr << "Frame width : " << pCodecCtx->coded_width << " Frame height : " << pCodecCtx->coded_height << "\n"; hw_de_supported = 0; retry_decode_open = 1; AV_FREE_CONTEXT(pCodecCtx); @@ -378,17 +379,17 @@ void FFmpegReader::Open() } } else { - ZmqLogger::Instance()->AppendDebugMethod("\nDecode hardware acceleration is used\n", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); - cerr << "\nDecode hardware acceleration is used\n"; - cerr << " Max Width : " << max_w << " Height : " << max_h << "\n"; - cerr << "Frame width : " << pCodecCtx->coded_width << " Frame height : " << pCodecCtx->coded_height << "\n"; + ZmqLogger::Instance()->AppendDebugMethod("\nDecode hardware acceleration is used\n", "Max Width :", max_w, "Max Height :", max_h, "Frame width :", pCodecCtx->coded_width, "Frame height :", pCodecCtx->coded_height, "", -1, "", -1); + //cerr << "\nDecode hardware acceleration is used\n"; + //cerr << " Max Width : " << max_w << " Height : " << max_h << "\n"; + //cerr << "Frame width : " << pCodecCtx->coded_width << " Frame height : " << pCodecCtx->coded_height << "\n"; retry_decode_open = 0; } } } // if hw_de_on && hw_de_supported else { ZmqLogger::Instance()->AppendDebugMethod("\nDecode in software is used\n", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); - cerr << "\nDecode in software is used\n"; + //cerr << "\nDecode in software is used\n"; } #else retry_decode_open = 0; diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index 840acba1..9791043c 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -1254,7 +1254,8 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) #endif if (av_hwdevice_ctx_create(&hw_device_ctx, hw_en_av_device_type, dev_hw, NULL, 0) < 0) { - cerr << "FFmpegWriter::open_video : Codec name: " << info.vcodec.c_str() << " ERROR creating\n"; + ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::open_video : Codec name: ", info.vcodec.c_str(), -1, " ERROR creating\n", -1, "", -1, "", -1, "", -1, "", -1); + //cerr << "FFmpegWriter::open_video : Codec name: " << info.vcodec.c_str() << " ERROR creating\n"; throw InvalidCodec("Could not create hwdevice", path); } } @@ -1860,10 +1861,12 @@ bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame* fra error_code = ret; if (ret < 0 ) { ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::write_video_packet (Frame not sent)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); - if (ret == AVERROR(EAGAIN) ) + if (ret == AVERROR(EAGAIN) ) { cerr << "Frame EAGAIN" << "\n"; - if (ret == AVERROR_EOF ) + } + if (ret == AVERROR_EOF ) { cerr << "Frame AVERROR_EOF" << "\n"; + } avcodec_send_frame(video_codec, NULL); } else { @@ -1884,10 +1887,12 @@ bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame* fra #if LIBAVFORMAT_VERSION_MAJOR >= 54 // Write video packet (older than FFmpeg 3.2) error_code = avcodec_encode_video2(video_codec, &pkt, frame_final, &got_packet_ptr); - if (error_code != 0 ) + if (error_code != 0 ) { cerr << "Frame AVERROR_EOF" << "\n"; - if (got_packet_ptr == 0 ) + } + if (got_packet_ptr == 0 ) { cerr << "Frame gotpacket error" << "\n"; + } #else // Write video packet (even older versions of FFmpeg) int video_outbuf_size = 200000; From f2323da447b7584f8c7b91d6aca55afe1a3d0ec9 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Wed, 19 Sep 2018 17:51:21 -0700 Subject: [PATCH 050/223] Preparation to choose the graphics card not by name but by number 1, 2, 3. First implementation just for Linux and decode --- src/FFmpegReader.cpp | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 2225e72c..547169b4 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -294,17 +294,42 @@ void FFmpegReader::Open() // Open Hardware Acceleration // Use the hw device given in the environment variable HW_DE_DEVICE_SET or the default if not set char *dev_hw = NULL; + char adapter[256]; + char *adapter_ptr = NULL; + int adapter_num; dev_hw = getenv( "HW_DE_DEVICE_SET" ); + if( dev_hw != NULL) { + adapter_num = atoi(dev_hw); + if (adapter_num < 3 && adapter_num >=0) { + #if defined(__linux__) + snprintf(adapter,sizeof(adapter),"/dev/dri/renderD%d", adapter_num+128); + adapter_ptr = adapter; + #elif defined(_WIN32) + adapter_ptr = NULL; + #elif defined(__APPLE__) + adapter_ptr = NULL; + #endif + } + else { + adapter_ptr = NULL; // Just to be sure + } + } // Check if it is there and writable - if( dev_hw != NULL && access( dev_hw, W_OK ) == -1 ) { - dev_hw = NULL; // use default + #if defined(__linux__) + if( adapter_ptr != NULL && access( adapter_ptr, W_OK ) == -1 ) { + #elif defined(_WIN32) + if( adapter_ptr != NULL ) { + #elif defined(__APPLE__) + if( adapter_ptr != NULL ) { + #endif + adapter_ptr = NULL; // use default //cerr << "\n\n\nDecode Device not present using default\n\n\n"; - ZmqLogger::Instance()->AppendDebugMethod("\n\n\nDecode Device not present using default\n\n\n", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); + ZmqLogger::Instance()->AppendDebugMethod("Decode Device not present using default", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); } hw_device_ctx = NULL; // Here the first hardware initialisations are made pCodecCtx->get_format = get_hw_dec_format; - if (av_hwdevice_ctx_create(&hw_device_ctx, hw_de_av_device_type, dev_hw, NULL, 0) >= 0) { + if (av_hwdevice_ctx_create(&hw_device_ctx, hw_de_av_device_type, adapter_ptr, NULL, 0) >= 0) { if (!(pCodecCtx->hw_device_ctx = av_buffer_ref(hw_device_ctx))) { throw InvalidCodec("Hardware device reference create failed.", path); } From 02273973390311edb06cb44a886a5b26cc698d94 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Wed, 19 Sep 2018 21:37:12 -0700 Subject: [PATCH 051/223] Set the graphics card used to decode or encode by setting the environment variable HW_EN_DEVICE_SET for enncoding and HW_DE_DEVICE_SET for decoding. The first card is 0, the second 1 and so on. For now only running on Linux. --- src/FFmpegReader.cpp | 1 + src/FFmpegWriter.cpp | 42 +++++++++++++++++++++++++++++++++--------- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 547169b4..912c1e2f 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -303,6 +303,7 @@ void FFmpegReader::Open() if (adapter_num < 3 && adapter_num >=0) { #if defined(__linux__) snprintf(adapter,sizeof(adapter),"/dev/dri/renderD%d", adapter_num+128); + // Maybe 127 is better because the first card would be 1?! adapter_ptr = adapter; #elif defined(_WIN32) adapter_ptr = NULL; diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index 9791043c..497538df 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -1242,18 +1242,42 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) #if IS_FFMPEG_3_2 if (hw_en_on && hw_en_supported) { char *dev_hw = NULL; - #if defined(__linux__) + char adapter[256]; + char *adapter_ptr = NULL; + int adapter_num; // Use the hw device given in the environment variable HW_EN_DEVICE_SET or the default if not set dev_hw = getenv( "HW_EN_DEVICE_SET" ); - // Check if it is there and writable - if( dev_hw != NULL && access( dev_hw, W_OK ) == -1 ) { - dev_hw = NULL; // use default - } - #else - dev_hw = NULL; // use default - #endif + if( dev_hw != NULL) { + adapter_num = atoi(dev_hw); + if (adapter_num < 3 && adapter_num >=0) { + #if defined(__linux__) + snprintf(adapter,sizeof(adapter),"/dev/dri/renderD%d", adapter_num+128); + // Maybe 127 is better because the first card would be 1?! + adapter_ptr = adapter; + #elif defined(_WIN32) + adapter_ptr = NULL; + #elif defined(__APPLE__) + adapter_ptr = NULL; + #endif + } + else { + adapter_ptr = NULL; // Just to be sure + } + } +// Check if it is there and writable + #if defined(__linux__) + if( adapter_ptr != NULL && access( adapter_ptr, W_OK ) == -1 ) { + #elif defined(_WIN32) + if( adapter_ptr != NULL ) { + #elif defined(__APPLE__) + if( adapter_ptr != NULL ) { + #endif + adapter_ptr = NULL; // use default + //cerr << "\n\n\nEncode Device not present using default\n\n\n"; + ZmqLogger::Instance()->AppendDebugMethod("Encode Device not present using default", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); + } if (av_hwdevice_ctx_create(&hw_device_ctx, hw_en_av_device_type, - dev_hw, NULL, 0) < 0) { + adapter_ptr, NULL, 0) < 0) { ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::open_video : Codec name: ", info.vcodec.c_str(), -1, " ERROR creating\n", -1, "", -1, "", -1, "", -1, "", -1); //cerr << "FFmpegWriter::open_video : Codec name: " << info.vcodec.c_str() << " ERROR creating\n"; throw InvalidCodec("Could not create hwdevice", path); From 031c415c5f3c237a6436f43707b2f82d2a6f8244 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Fri, 21 Sep 2018 17:11:56 -0500 Subject: [PATCH 052/223] Protect effects with critical (prevents crashing and freezing around transitions). Thanks Peter! --- src/Timeline.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Timeline.cpp b/src/Timeline.cpp index db406efc..6d1b0cde 100644 --- a/src/Timeline.cpp +++ b/src/Timeline.cpp @@ -281,6 +281,7 @@ void Timeline::add_layer(std::shared_ptr new_frame, Clip* source_clip, in /* Apply effects to the source frame (if any). If multiple clips are overlapping, only process the * effects on the top clip. */ if (is_top_clip && source_frame) + #pragma omp critical (T_addLayer) source_frame = apply_effects(source_frame, timeline_frame_number, source_clip->Layer()); // Declare an image to hold the source frame's image From 082f9aa6689d83fc380ed23c15561103236f6ec5 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sat, 22 Sep 2018 13:36:41 -0500 Subject: [PATCH 053/223] Bumping version to 0.2.2 (SO: still 16) --- include/Version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/Version.h b/include/Version.h index a22afb05..4ff2c36c 100644 --- a/include/Version.h +++ b/include/Version.h @@ -36,7 +36,7 @@ #define OPENSHOT_VERSION_MAJOR 0; /// Major version number is incremented when huge features are added or improved. #define OPENSHOT_VERSION_MINOR 2; /// Minor version is incremented when smaller (but still very important) improvements are added. -#define OPENSHOT_VERSION_BUILD 1; /// Build number is incremented when minor bug fixes and less important improvements are added. +#define OPENSHOT_VERSION_BUILD 2; /// Build number is incremented when minor bug fixes and less important improvements are added. #define OPENSHOT_VERSION_SO 16; /// Shared object version number. This increments any time the API and ABI changes (so old apps will no longer link) #define OPENSHOT_VERSION_MAJOR_MINOR STRINGIZE(OPENSHOT_VERSION_MAJOR) "." STRINGIZE(OPENSHOT_VERSION_MINOR); /// A string of the "Major.Minor" version #define OPENSHOT_VERSION_ALL STRINGIZE(OPENSHOT_VERSION_MAJOR) "." STRINGIZE(OPENSHOT_VERSION_MINOR) "." STRINGIZE(OPENSHOT_VERSION_BUILD); /// A string of the entire version "Major.Minor.Build" From de9f816f253507aaeeb0e06baffcbd2f9e3542fb Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sat, 22 Sep 2018 17:41:56 -0500 Subject: [PATCH 054/223] Exclude git tags from kicking off GitLab builds (for libopenshot) --- .gitlab-ci.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3059ee9d..66701244 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -23,6 +23,8 @@ linux-builder: - mv /usr/local/lib/python3.4/dist-packages/*openshot* install-x64/python - echo -e "CI_PROJECT_NAME:$CI_PROJECT_NAME\nCI_COMMIT_REF_NAME:$CI_COMMIT_REF_NAME\nCI_COMMIT_SHA:$CI_COMMIT_SHA\nCI_JOB_ID:$CI_JOB_ID" > "install-x64/share/$CI_PROJECT_NAME" when: always + except: + - tags tags: - linux @@ -47,6 +49,8 @@ mac-builder: - mv /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/*openshot* install-x64/python - echo -e "CI_PROJECT_NAME:$CI_PROJECT_NAME\nCI_COMMIT_REF_NAME:$CI_COMMIT_REF_NAME\nCI_COMMIT_SHA:$CI_COMMIT_SHA\nCI_JOB_ID:$CI_JOB_ID" > "install-x64/share/$CI_PROJECT_NAME" when: always + except: + - tags tags: - mac @@ -73,6 +77,8 @@ windows-builder-x86: - cp src\libopenshot.dll install-x86\lib - New-Item -path "install-x86/share/" -Name "$CI_PROJECT_NAME" -Value "CI_PROJECT_NAME:$CI_PROJECT_NAME`nCI_COMMIT_REF_NAME:$CI_COMMIT_REF_NAME`nCI_COMMIT_SHA:$CI_COMMIT_SHA`nCI_JOB_ID:$CI_JOB_ID" -ItemType file -force when: always + except: + - tags tags: - windows @@ -99,6 +105,8 @@ windows-builder-x64: - cp src\libopenshot.dll install-x64\lib - New-Item -path "install-x64/share/" -Name "$CI_PROJECT_NAME" -Value "CI_PROJECT_NAME:$CI_PROJECT_NAME`nCI_COMMIT_REF_NAME:$CI_COMMIT_REF_NAME`nCI_COMMIT_SHA:$CI_COMMIT_SHA`nCI_JOB_ID:$CI_JOB_ID" -ItemType file -force when: always + except: + - tags tags: - windows @@ -108,5 +116,7 @@ trigger-pipeline: - "curl -X POST -F token=$OPENSHOT_QT_PIPELINE_TOKEN -F ref=$CI_COMMIT_REF_NAME http://gitlab.openshot.org/api/v4/projects/3/trigger/pipeline" when: always dependencies: [] + except: + - tags tags: - gitlab From b925a9ba2582833354ff89c9b6928352229b2618 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Sun, 23 Sep 2018 09:51:56 -0700 Subject: [PATCH 055/223] protect add_effect with critical --- src/Timeline.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Timeline.cpp b/src/Timeline.cpp index 34dab1d8..fe7c1022 100644 --- a/src/Timeline.cpp +++ b/src/Timeline.cpp @@ -281,6 +281,7 @@ void Timeline::add_layer(std::shared_ptr new_frame, Clip* source_clip, in /* Apply effects to the source frame (if any). If multiple clips are overlapping, only process the * effects on the top clip. */ if (is_top_clip && source_frame) + #pragma omp critical (T_addLayer) source_frame = apply_effects(source_frame, timeline_frame_number, source_clip->Layer()); // Declare an image to hold the source frame's image From 1cd8401a58394bd49f61eb17628e67b5f05c8088 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Sun, 23 Sep 2018 10:09:20 -0700 Subject: [PATCH 056/223] Put brackets in the if statement to show that the pragma critical and the followwing command are one block. --- src/Timeline.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Timeline.cpp b/src/Timeline.cpp index fe7c1022..71805837 100644 --- a/src/Timeline.cpp +++ b/src/Timeline.cpp @@ -280,9 +280,10 @@ void Timeline::add_layer(std::shared_ptr new_frame, Clip* source_clip, in /* Apply effects to the source frame (if any). If multiple clips are overlapping, only process the * effects on the top clip. */ - if (is_top_clip && source_frame) + if (is_top_clip && source_frame) { #pragma omp critical (T_addLayer) source_frame = apply_effects(source_frame, timeline_frame_number, source_clip->Layer()); + } // Declare an image to hold the source frame's image std::shared_ptr source_image; From 53eec32d9eb061f32a26e1c41c4aee73b67ad57c Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Tue, 25 Sep 2018 08:04:48 -0700 Subject: [PATCH 057/223] In case CRF is not supported like in hardware accelerated codecs or in mpeg2 a bitrate is calculated that should be close to the one expected with the given CRF value. --- src/FFmpegWriter.cpp | 59 +++++++++++++++++++++++++++++++------------- 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index 497538df..207d545f 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -1053,23 +1053,48 @@ AVStream* FFmpegWriter::add_video_stream() } #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 39, 101) else { - switch (c->codec_id) { - case AV_CODEC_ID_VP8 : - av_opt_set_int(c->priv_data, "crf", min(info.video_bit_rate,63), 0); - break; - case AV_CODEC_ID_VP9 : - av_opt_set_int(c->priv_data, "crf", min(info.video_bit_rate,63), 0); - if (info.video_bit_rate == 0) { - av_opt_set_int(c->priv_data, "lossless", 1, 0); - } - break; - case AV_CODEC_ID_H264 : - av_opt_set_int(c->priv_data, "crf", min(info.video_bit_rate,51), 0); - break; - case AV_CODEC_ID_H265 : - av_opt_set_int(c->priv_data, "crf", min(info.video_bit_rate,51), 0); - break; - } + if (hw_en_on) { + double mbs = 15000000.0; + if (info.video_bit_rate > 0) { + if (info.video_bit_rate > 42) { + mbs = 380.0; + } + else { + mbs *= pow(0.912,info.video_bit_rate); + } + } + c->bit_rate = (int)(mbs); + } + else { + switch (c->codec_id) { + case AV_CODEC_ID_VP8 : + av_opt_set_int(c->priv_data, "crf", min(info.video_bit_rate,63), 0); + break; + case AV_CODEC_ID_VP9 : + av_opt_set_int(c->priv_data, "crf", min(info.video_bit_rate,63), 0); + if (info.video_bit_rate == 0) { + av_opt_set_int(c->priv_data, "lossless", 1, 0); + } + break; + case AV_CODEC_ID_H264 : + av_opt_set_int(c->priv_data, "crf", min(info.video_bit_rate,51), 0); + break; + case AV_CODEC_ID_H265 : + av_opt_set_int(c->priv_data, "crf", min(info.video_bit_rate,51), 0); + break; + default: + double mbs = 15000000.0; + if (info.video_bit_rate > 0) { + if (info.video_bit_rate > 42) { + mbs = 380.0; + } + else { + mbs *= pow(0.912,info.video_bit_rate); + } + } + c->bit_rate = (int)(mbs); + } + } } #endif From 325f58f7731c3c58c29f172d69f4ac6a279f97a8 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Thu, 15 Nov 2018 22:04:20 -0800 Subject: [PATCH 058/223] Changes to use AV1 if ffmpeg >= 4.0 is used with libaom support --- src/FFmpegWriter.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index 207d545f..66befe5c 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -1067,6 +1067,12 @@ AVStream* FFmpegWriter::add_video_stream() } else { switch (c->codec_id) { +#if (LIBAVCODEC_VERSION_MAJOR >= 58) + case AV_CODEC_ID_AV1 : + av_opt_set_int(c->priv_data, "crf", min(info.video_bit_rate,63), 0); + c->bit_rate = 0; + break; +#endif case AV_CODEC_ID_VP8 : av_opt_set_int(c->priv_data, "crf", min(info.video_bit_rate,63), 0); break; From 514cb1134014d4604ec4062981e75f6d37fa9660 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Sun, 25 Nov 2018 20:28:25 -0800 Subject: [PATCH 059/223] When multiple graphics cards are installed the import with hardware acceleration has to have the card number set or the opening of the device will fail. TODO check multiple formats. Right now only the first is checked which is vaapi. --- src/FFmpegReader.cpp | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index d78b7c8f..231d41c7 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -300,21 +300,24 @@ void FFmpegReader::Open() dev_hw = getenv( "HW_DE_DEVICE_SET" ); if( dev_hw != NULL) { adapter_num = atoi(dev_hw); - if (adapter_num < 3 && adapter_num >=0) { - #if defined(__linux__) - snprintf(adapter,sizeof(adapter),"/dev/dri/renderD%d", adapter_num+128); - // Maybe 127 is better because the first card would be 1?! - adapter_ptr = adapter; - #elif defined(_WIN32) - adapter_ptr = NULL; - #elif defined(__APPLE__) - adapter_ptr = NULL; - #endif - } - else { - adapter_ptr = NULL; // Just to be sure - } + } else { + adapter_num = 0; } + if (adapter_num < 3 && adapter_num >=0) { + #if defined(__linux__) + snprintf(adapter,sizeof(adapter),"/dev/dri/renderD%d", adapter_num+128); + // Maybe 127 is better because the first card would be 1?! + adapter_ptr = adapter; + #elif defined(_WIN32) + adapter_ptr = NULL; + #elif defined(__APPLE__) + adapter_ptr = NULL; + #endif + } + else { + adapter_ptr = NULL; // Just to be sure + } + //} // Check if it is there and writable #if defined(__linux__) if( adapter_ptr != NULL && access( adapter_ptr, W_OK ) == -1 ) { @@ -329,6 +332,8 @@ void FFmpegReader::Open() } hw_device_ctx = NULL; // Here the first hardware initialisations are made + // TODO: check for each format in an extra call + // Now only vaapi the first in the list is found pCodecCtx->get_format = get_hw_dec_format; if (av_hwdevice_ctx_create(&hw_device_ctx, hw_de_av_device_type, adapter_ptr, NULL, 0) >= 0) { if (!(pCodecCtx->hw_device_ctx = av_buffer_ref(hw_device_ctx))) { From e7f2494040fb0a17c5f8f0c90789a66140fe8e5d Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Mon, 26 Nov 2018 09:36:21 -0800 Subject: [PATCH 060/223] First changes to make hardware accelerated DECODE work with decoders other than vaapi. Encode is already working for nvenc; nvidia driver 396 has to be installed for nvenc to work. On nVidia card turn accelerated decode off in Preferences->Performance for now --- include/FFmpegReader.h | 2 +- src/FFmpegReader.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/include/FFmpegReader.h b/include/FFmpegReader.h index f571af73..62437d7a 100644 --- a/include/FFmpegReader.h +++ b/include/FFmpegReader.h @@ -149,7 +149,7 @@ namespace openshot int hw_de_supported = 0; // Is set by FFmpegReader #if IS_FFMPEG_3_2 AVPixelFormat hw_de_av_pix_fmt = AV_PIX_FMT_NONE; - AVHWDeviceType hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; + AVHWDeviceType hw_de_av_device_type = AV_HWDEVICE_TYPE_NONE; #endif int is_hardware_decode_supported(int codecid); diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 231d41c7..9ed85114 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -79,7 +79,7 @@ int hw_de_on = 1; // Is set in UI //int hw_de_supported = 0; // Is set by FFmpegReader #if IS_FFMPEG_3_2 AVPixelFormat hw_de_av_pix_fmt_global = AV_PIX_FMT_NONE; -AVHWDeviceType hw_de_av_device_type_global = AV_HWDEVICE_TYPE_VAAPI; +AVHWDeviceType hw_de_av_device_type_global = AV_HWDEVICE_TYPE_NONE; #endif FFmpegReader::FFmpegReader(string path) @@ -334,6 +334,7 @@ void FFmpegReader::Open() // Here the first hardware initialisations are made // TODO: check for each format in an extra call // Now only vaapi the first in the list is found + hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; pCodecCtx->get_format = get_hw_dec_format; if (av_hwdevice_ctx_create(&hw_device_ctx, hw_de_av_device_type, adapter_ptr, NULL, 0) >= 0) { if (!(pCodecCtx->hw_device_ctx = av_buffer_ref(hw_device_ctx))) { From 1713fecc9080f3b68c9b458122e4fe3610b55f59 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Mon, 26 Nov 2018 13:31:53 -0800 Subject: [PATCH 061/223] More adjustments to enable hardware decode with nvdec/cuvid --- src/FFmpegReader.cpp | 94 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 90 insertions(+), 4 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 9ed85114..941435f3 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -155,7 +155,7 @@ bool AudioLocation::is_near(AudioLocation location, int samples_per_frame, int64 #if IS_FFMPEG_3_2 -static enum AVPixelFormat get_hw_dec_format(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) +static enum AVPixelFormat get_hw_dec_format_va(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) { const enum AVPixelFormat *p; @@ -166,21 +166,69 @@ static enum AVPixelFormat get_hw_dec_format(AVCodecContext *ctx, const enum AVPi hw_de_av_device_type_global = AV_HWDEVICE_TYPE_VAAPI; return *p; break; + } + } + ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); + return AV_PIX_FMT_NONE; + } + +static enum AVPixelFormat get_hw_dec_format_cu(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) +{ + const enum AVPixelFormat *p; + + for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { + switch (*p) { case AV_PIX_FMT_CUDA: hw_de_av_pix_fmt_global = AV_PIX_FMT_CUDA; hw_de_av_device_type_global = AV_HWDEVICE_TYPE_CUDA; return *p; break; + } + } + ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); + return AV_PIX_FMT_NONE; + } + +static enum AVPixelFormat get_hw_dec_format_dx(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) +{ + const enum AVPixelFormat *p; + + for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { + switch (*p) { case AV_PIX_FMT_DXVA2_VLD: hw_de_av_pix_fmt_global = AV_PIX_FMT_DXVA2_VLD; hw_de_av_device_type_global = AV_HWDEVICE_TYPE_DXVA2; return *p; break; + } + } + ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); + return AV_PIX_FMT_NONE; + } + +static enum AVPixelFormat get_hw_dec_format_d3(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) +{ + const enum AVPixelFormat *p; + + for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { + switch (*p) { case AV_PIX_FMT_D3D11: hw_de_av_pix_fmt_global = AV_PIX_FMT_D3D11; hw_de_av_device_type_global = AV_HWDEVICE_TYPE_D3D11VA; return *p; break; + } + } + ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); + return AV_PIX_FMT_NONE; + } + +static enum AVPixelFormat get_hw_dec_format_qs(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) +{ + const enum AVPixelFormat *p; + + for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { + switch (*p) { case AV_PIX_FMT_QSV: hw_de_av_pix_fmt_global = AV_PIX_FMT_QSV; hw_de_av_device_type_global = AV_HWDEVICE_TYPE_QSV; @@ -334,16 +382,54 @@ void FFmpegReader::Open() // Here the first hardware initialisations are made // TODO: check for each format in an extra call // Now only vaapi the first in the list is found + #if defined(__linux__) hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; - pCodecCtx->get_format = get_hw_dec_format; + pCodecCtx->get_format = get_hw_dec_format_va; if (av_hwdevice_ctx_create(&hw_device_ctx, hw_de_av_device_type, adapter_ptr, NULL, 0) >= 0) { if (!(pCodecCtx->hw_device_ctx = av_buffer_ref(hw_device_ctx))) { - throw InvalidCodec("Hardware device reference create failed.", path); + throw InvalidCodec("Hardware device reference create failed vaapi.", path); } } else { - throw InvalidCodec("Hardware device create failed.", path); + hw_de_av_device_type = AV_HWDEVICE_TYPE_CUDA; + pCodecCtx->get_format = get_hw_dec_format_cu; + hw_device_ctx = NULL; + if (av_hwdevice_ctx_create(&hw_device_ctx, hw_de_av_device_type, adapter_ptr, NULL, 0) >= 0) { + if (!(pCodecCtx->hw_device_ctx = av_buffer_ref(hw_device_ctx))) { + throw InvalidCodec("Hardware device reference create failed cuda.", path); + } + } + else { + throw InvalidCodec("Hardware device create failed.", path); + + } } + #endif + #if defined(_WIN32) + hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2_VLD; + pCodecCtx->get_format = get_hw_dec_format_dx; + if (av_hwdevice_ctx_create(&hw_device_ctx, hw_de_av_device_type, adapter_ptr, NULL, 0) >= 0) { + if (!(pCodecCtx->hw_device_ctx = av_buffer_ref(hw_device_ctx))) { + throw InvalidCodec("Hardware device reference create failed vaapi.", path); + } + } + else { + throw InvalidCodec("Hardware device create failed.", path); + } + #endif + #if defined(__APPLE__) + hw_de_av_device_type = AV_HWDEVICE_TYPE_QSV; + pCodecCtx->get_format = get_hw_dec_format_qs; + if (av_hwdevice_ctx_create(&hw_device_ctx, hw_de_av_device_type, adapter_ptr, NULL, 0) >= 0) { + if (!(pCodecCtx->hw_device_ctx = av_buffer_ref(hw_device_ctx))) { + throw InvalidCodec("Hardware device reference create failed vaapi.", path); + } + } + else { + throw InvalidCodec("Hardware device create failed.", path); + } + #endif + } #endif // Open video codec From 7cadeb364b94226f770fc077b3c759537f452a61 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Mon, 26 Nov 2018 18:08:08 -0800 Subject: [PATCH 062/223] More cleanup --- src/FFmpegReader.cpp | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 941435f3..2f641ab8 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -155,6 +155,7 @@ bool AudioLocation::is_near(AudioLocation location, int samples_per_frame, int64 #if IS_FFMPEG_3_2 +#if defined(__linux__) static enum AVPixelFormat get_hw_dec_format_va(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) { const enum AVPixelFormat *p; @@ -188,7 +189,9 @@ static enum AVPixelFormat get_hw_dec_format_cu(AVCodecContext *ctx, const enum A ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); return AV_PIX_FMT_NONE; } +#endif +#if defined(_WIN32) static enum AVPixelFormat get_hw_dec_format_dx(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) { const enum AVPixelFormat *p; @@ -222,7 +225,9 @@ static enum AVPixelFormat get_hw_dec_format_d3(AVCodecContext *ctx, const enum A ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); return AV_PIX_FMT_NONE; } +#endif +#if defined(__APPLE__) static enum AVPixelFormat get_hw_dec_format_qs(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) { const enum AVPixelFormat *p; @@ -239,6 +244,7 @@ static enum AVPixelFormat get_hw_dec_format_qs(AVCodecContext *ctx, const enum A ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); return AV_PIX_FMT_NONE; } +#endif int FFmpegReader::is_hardware_decode_supported(int codecid) { @@ -345,7 +351,7 @@ void FFmpegReader::Open() char adapter[256]; char *adapter_ptr = NULL; int adapter_num; - dev_hw = getenv( "HW_DE_DEVICE_SET" ); + dev_hw = getenv( "HW_DE_DEVICE_SET" ); // The first card is 0 if( dev_hw != NULL) { adapter_num = atoi(dev_hw); } else { @@ -354,7 +360,6 @@ void FFmpegReader::Open() if (adapter_num < 3 && adapter_num >=0) { #if defined(__linux__) snprintf(adapter,sizeof(adapter),"/dev/dri/renderD%d", adapter_num+128); - // Maybe 127 is better because the first card would be 1?! adapter_ptr = adapter; #elif defined(_WIN32) adapter_ptr = NULL; @@ -380,8 +385,6 @@ void FFmpegReader::Open() } hw_device_ctx = NULL; // Here the first hardware initialisations are made - // TODO: check for each format in an extra call - // Now only vaapi the first in the list is found #if defined(__linux__) hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; pCodecCtx->get_format = get_hw_dec_format_va; @@ -401,7 +404,6 @@ void FFmpegReader::Open() } else { throw InvalidCodec("Hardware device create failed.", path); - } } #endif @@ -410,19 +412,29 @@ void FFmpegReader::Open() pCodecCtx->get_format = get_hw_dec_format_dx; if (av_hwdevice_ctx_create(&hw_device_ctx, hw_de_av_device_type, adapter_ptr, NULL, 0) >= 0) { if (!(pCodecCtx->hw_device_ctx = av_buffer_ref(hw_device_ctx))) { - throw InvalidCodec("Hardware device reference create failed vaapi.", path); + throw InvalidCodec("Hardware device reference create failed dxva2.", path); } } else { - throw InvalidCodec("Hardware device create failed.", path); - } + hw_de_av_device_type = AV_HWDEVICE_TYPE_D3D11; + pCodecCtx->get_format = get_hw_dec_format_cu; + hw_device_ctx = NULL; + if (av_hwdevice_ctx_create(&hw_device_ctx, hw_de_av_device_type, adapter_ptr, NULL, 0) >= 0) { + if (!(pCodecCtx->hw_device_ctx = av_buffer_ref(hw_device_ctx))) { + throw InvalidCodec("Hardware device reference create failed d3d11.", path); + } + } + else { + throw InvalidCodec("Hardware device create failed.", path); + } + } #endif #if defined(__APPLE__) hw_de_av_device_type = AV_HWDEVICE_TYPE_QSV; pCodecCtx->get_format = get_hw_dec_format_qs; if (av_hwdevice_ctx_create(&hw_device_ctx, hw_de_av_device_type, adapter_ptr, NULL, 0) >= 0) { if (!(pCodecCtx->hw_device_ctx = av_buffer_ref(hw_device_ctx))) { - throw InvalidCodec("Hardware device reference create failed vaapi.", path); + throw InvalidCodec("Hardware device reference create failed qsv.", path); } } else { From d07e8518232f06b4b18ebc0237437670d54b56aa Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Sat, 8 Dec 2018 10:34:24 -0800 Subject: [PATCH 063/223] Hardware decode and encode can now be configured completely in Preferences->Performance. The old enable hardware decode is disabled. Now the graphics card can be chosen (0 is the first one) that should be used for encode and/or decode. They needn't be the same! nVidia decode still not working nVidia encode is working with driver 396 Vaapi should be working. mesa-va-drivers must be installed for AMD i965-va-driver must be installed for intel GPUs. Using one card to decode and one to encode an option with laptops with an iGPU and a dedicated GPU (dGPU), as an example. --- src/FFmpegReader.cpp | 72 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 56 insertions(+), 16 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 2f641ab8..2d7aaafb 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -275,13 +275,29 @@ void FFmpegReader::Open() // Initialize format context pFormatCtx = NULL; - char * val = getenv( "OS2_DECODE_HW" ); + // Old version turn hardware decode on + /*char * val = getenv( "OS2_DECODE_HW" ); if (val == NULL) { hw_de_on = 0; } else{ hw_de_on = (val[0] == '1')? 1 : 0; - } + }*/ + + // New version turn hardware decode on + { + char *decoder_hw = NULL; + decoder_hw = getenv( "HW_DECODER" ); + if(decoder_hw != NULL) { + if( strncmp(decoder_hw,"NONE",4) == 0) { + hw_de_on = 0; + } else { + hw_de_on = 1; + } + } else { + hw_de_on = 0; + } + } // Open video file if (avformat_open_input(&pFormatCtx, path.c_str(), NULL, NULL) != 0) @@ -348,6 +364,7 @@ void FFmpegReader::Open() // Open Hardware Acceleration // Use the hw device given in the environment variable HW_DE_DEVICE_SET or the default if not set char *dev_hw = NULL; + char *decoder_hw = NULL; char adapter[256]; char *adapter_ptr = NULL; int adapter_num; @@ -361,6 +378,38 @@ void FFmpegReader::Open() #if defined(__linux__) snprintf(adapter,sizeof(adapter),"/dev/dri/renderD%d", adapter_num+128); adapter_ptr = adapter; + decoder_hw = getenv( "HW_DECODER" ); + if(decoder_hw != NULL) { + if (strncmp(decoder_hw,"NONE",4) == 0) { //Will never happen + hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; + pCodecCtx->get_format = get_hw_dec_format_va; + } + if (strncmp(decoder_hw,"HW_DE_VAAPI",11) == 0) { //Will never happen + hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; + pCodecCtx->get_format = get_hw_dec_format_va; + } + if (strncmp(decoder_hw,"HW_DE_NVDEC",11) == 0) { //Will never happen + hw_de_av_device_type = AV_HWDEVICE_TYPE_CUDA; + pCodecCtx->get_format = get_hw_dec_format_cu; + } + } else { + hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; + pCodecCtx->get_format = get_hw_dec_format_va; + } + + /* This is a hack: + my first card is AMD, my second card is nVidia + */ + switch (adapter_num) { + case 1: + hw_de_av_device_type = AV_HWDEVICE_TYPE_CUDA; + pCodecCtx->get_format = get_hw_dec_format_cu; + break; + default: + hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; + pCodecCtx->get_format = get_hw_dec_format_va; + break; + } #elif defined(_WIN32) adapter_ptr = NULL; #elif defined(__APPLE__) @@ -386,25 +435,16 @@ void FFmpegReader::Open() hw_device_ctx = NULL; // Here the first hardware initialisations are made #if defined(__linux__) - hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; - pCodecCtx->get_format = get_hw_dec_format_va; + //hw_de_av_device_type = AV_HWDEVICE_TYPE_CUDA; + //pCodecCtx->get_format = get_hw_dec_format_cu; if (av_hwdevice_ctx_create(&hw_device_ctx, hw_de_av_device_type, adapter_ptr, NULL, 0) >= 0) { + cerr << "\n\n**** HW device create OK ******** \n\n"; if (!(pCodecCtx->hw_device_ctx = av_buffer_ref(hw_device_ctx))) { - throw InvalidCodec("Hardware device reference create failed vaapi.", path); + throw InvalidCodec("Hardware device reference create failed cuda.", path); } } else { - hw_de_av_device_type = AV_HWDEVICE_TYPE_CUDA; - pCodecCtx->get_format = get_hw_dec_format_cu; - hw_device_ctx = NULL; - if (av_hwdevice_ctx_create(&hw_device_ctx, hw_de_av_device_type, adapter_ptr, NULL, 0) >= 0) { - if (!(pCodecCtx->hw_device_ctx = av_buffer_ref(hw_device_ctx))) { - throw InvalidCodec("Hardware device reference create failed cuda.", path); - } - } - else { - throw InvalidCodec("Hardware device create failed.", path); - } + throw InvalidCodec("Hardware device create failed.", path); } #endif #if defined(_WIN32) From 70954f800c0f87cf6199e768586ca99dce017dd2 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Sat, 8 Dec 2018 15:54:29 -0800 Subject: [PATCH 064/223] Typo, plus removed hack for my hardware --- src/FFmpegReader.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 2d7aaafb..a3e247f5 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -400,7 +400,7 @@ void FFmpegReader::Open() /* This is a hack: my first card is AMD, my second card is nVidia */ - switch (adapter_num) { +/* switch (adapter_num) { case 1: hw_de_av_device_type = AV_HWDEVICE_TYPE_CUDA; pCodecCtx->get_format = get_hw_dec_format_cu; @@ -409,7 +409,7 @@ void FFmpegReader::Open() hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; pCodecCtx->get_format = get_hw_dec_format_va; break; - } + }*/ #elif defined(_WIN32) adapter_ptr = NULL; #elif defined(__APPLE__) @@ -440,7 +440,7 @@ void FFmpegReader::Open() if (av_hwdevice_ctx_create(&hw_device_ctx, hw_de_av_device_type, adapter_ptr, NULL, 0) >= 0) { cerr << "\n\n**** HW device create OK ******** \n\n"; if (!(pCodecCtx->hw_device_ctx = av_buffer_ref(hw_device_ctx))) { - throw InvalidCodec("Hardware device reference create failed cuda.", path); + throw InvalidCodec("Hardware device reference create failed.", path); } } else { From 23e287110d2b6950861a1460a802f310b9fb6f5a Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Sat, 8 Dec 2018 18:11:06 -0800 Subject: [PATCH 065/223] Bring Windows and Mac up to date --- src/FFmpegReader.cpp | 84 +++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 52 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index a3e247f5..6d5b058a 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -397,23 +397,42 @@ void FFmpegReader::Open() pCodecCtx->get_format = get_hw_dec_format_va; } - /* This is a hack: - my first card is AMD, my second card is nVidia - */ -/* switch (adapter_num) { - case 1: - hw_de_av_device_type = AV_HWDEVICE_TYPE_CUDA; - pCodecCtx->get_format = get_hw_dec_format_cu; - break; - default: - hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; - pCodecCtx->get_format = get_hw_dec_format_va; - break; - }*/ #elif defined(_WIN32) adapter_ptr = NULL; + decoder_hw = getenv( "HW_DECODER" ); + if(decoder_hw != NULL) { + if (strncmp(decoder_hw,"NONE",4) == 0) { //Will never happen + hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2_VLD; + pCodecCtx->get_format = get_hw_dec_format_dx; + } + if (strncmp(decoder_hw,"HW_DE_WINDOWS_DXVA2",19) == 0) { //Will never happen + hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2_VLD; + pCodecCtx->get_format = get_hw_dec_format_dx; + } + if (strncmp(decoder_hw,"HW_DE_WINDOWS_D3D11",19) == 0) { //Will never happen + hw_de_av_device_type = AV_HWDEVICE_TYPE_D3D11; + pCodecCtx->get_format = get_hw_dec_format_d3; + } + } else { + hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2_VLD; + pCodecCtx->get_format = get_hw_dec_format_dx; + } #elif defined(__APPLE__) adapter_ptr = NULL; + decoder_hw = getenv( "HW_DECODER" ); + if(decoder_hw != NULL) { + if (strncmp(decoder_hw,"NONE",4) == 0) { //Will never happen + hw_de_av_device_type = AV_HWDEVICE_TYPE_QSV; + pCodecCtx->get_format = get_hw_dec_format_qs; + } + if (strncmp(decoder_hw,"HW_DE_MACOS",11) == 0) { //Will never happen + hw_de_av_device_type = AV_HWDEVICE_TYPE_QSV; + pCodecCtx->get_format = get_hw_dec_format_qs; + } + } else { + hw_de_av_device_type = AV_HWDEVICE_TYPE_QSV; + pCodecCtx->get_format = get_hw_dec_format_qs; + } #endif } else { @@ -429,14 +448,10 @@ void FFmpegReader::Open() if( adapter_ptr != NULL ) { #endif adapter_ptr = NULL; // use default - //cerr << "\n\n\nDecode Device not present using default\n\n\n"; ZmqLogger::Instance()->AppendDebugMethod("Decode Device not present using default", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); } hw_device_ctx = NULL; // Here the first hardware initialisations are made - #if defined(__linux__) - //hw_de_av_device_type = AV_HWDEVICE_TYPE_CUDA; - //pCodecCtx->get_format = get_hw_dec_format_cu; if (av_hwdevice_ctx_create(&hw_device_ctx, hw_de_av_device_type, adapter_ptr, NULL, 0) >= 0) { cerr << "\n\n**** HW device create OK ******** \n\n"; if (!(pCodecCtx->hw_device_ctx = av_buffer_ref(hw_device_ctx))) { @@ -446,41 +461,6 @@ void FFmpegReader::Open() else { throw InvalidCodec("Hardware device create failed.", path); } - #endif - #if defined(_WIN32) - hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2_VLD; - pCodecCtx->get_format = get_hw_dec_format_dx; - if (av_hwdevice_ctx_create(&hw_device_ctx, hw_de_av_device_type, adapter_ptr, NULL, 0) >= 0) { - if (!(pCodecCtx->hw_device_ctx = av_buffer_ref(hw_device_ctx))) { - throw InvalidCodec("Hardware device reference create failed dxva2.", path); - } - } - else { - hw_de_av_device_type = AV_HWDEVICE_TYPE_D3D11; - pCodecCtx->get_format = get_hw_dec_format_cu; - hw_device_ctx = NULL; - if (av_hwdevice_ctx_create(&hw_device_ctx, hw_de_av_device_type, adapter_ptr, NULL, 0) >= 0) { - if (!(pCodecCtx->hw_device_ctx = av_buffer_ref(hw_device_ctx))) { - throw InvalidCodec("Hardware device reference create failed d3d11.", path); - } - } - else { - throw InvalidCodec("Hardware device create failed.", path); - } - } - #endif - #if defined(__APPLE__) - hw_de_av_device_type = AV_HWDEVICE_TYPE_QSV; - pCodecCtx->get_format = get_hw_dec_format_qs; - if (av_hwdevice_ctx_create(&hw_device_ctx, hw_de_av_device_type, adapter_ptr, NULL, 0) >= 0) { - if (!(pCodecCtx->hw_device_ctx = av_buffer_ref(hw_device_ctx))) { - throw InvalidCodec("Hardware device reference create failed qsv.", path); - } - } - else { - throw InvalidCodec("Hardware device create failed.", path); - } - #endif } #endif From de1bd4f50647f2a4eb0c971419b711d3957c3654 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Sun, 9 Dec 2018 09:02:46 -0800 Subject: [PATCH 066/223] Typos in Windows part --- src/FFmpegReader.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 6d5b058a..f541c295 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -402,19 +402,19 @@ void FFmpegReader::Open() decoder_hw = getenv( "HW_DECODER" ); if(decoder_hw != NULL) { if (strncmp(decoder_hw,"NONE",4) == 0) { //Will never happen - hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2_VLD; + hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2; pCodecCtx->get_format = get_hw_dec_format_dx; } if (strncmp(decoder_hw,"HW_DE_WINDOWS_DXVA2",19) == 0) { //Will never happen - hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2_VLD; + hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2; pCodecCtx->get_format = get_hw_dec_format_dx; } if (strncmp(decoder_hw,"HW_DE_WINDOWS_D3D11",19) == 0) { //Will never happen - hw_de_av_device_type = AV_HWDEVICE_TYPE_D3D11; + hw_de_av_device_type = AV_HWDEVICE_TYPE_D3D11VA; pCodecCtx->get_format = get_hw_dec_format_d3; } } else { - hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2_VLD; + hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2; pCodecCtx->get_format = get_hw_dec_format_dx; } #elif defined(__APPLE__) @@ -457,6 +457,14 @@ void FFmpegReader::Open() if (!(pCodecCtx->hw_device_ctx = av_buffer_ref(hw_device_ctx))) { throw InvalidCodec("Hardware device reference create failed.", path); } + /* + ret = av_hwframe_ctx_init(pCodecCtx->hw_device_ctx); + ret = av_hwframe_ctx_init(ist->hw_frames_ctx); + if (ret < 0) { + av_log(avctx, AV_LOG_ERROR, "Error initializing a CUDA frame pool\n"); + return ret; + } + */ } else { throw InvalidCodec("Hardware device create failed.", path); From 4ed7847fa97eff2626105859d4e68404a4d70894 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Fri, 14 Dec 2018 14:38:33 -0600 Subject: [PATCH 067/223] Improving cache performance by preventing the cache from getting behind the currently displaying frame # --- src/Qt/PlayerPrivate.cpp | 2 +- src/Qt/VideoCacheThread.cpp | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Qt/PlayerPrivate.cpp b/src/Qt/PlayerPrivate.cpp index 63d20e99..1839f1eb 100644 --- a/src/Qt/PlayerPrivate.cpp +++ b/src/Qt/PlayerPrivate.cpp @@ -149,7 +149,7 @@ namespace openshot else { // Update cache on which frame was retrieved - videoCache->current_display_frame = video_position; + videoCache->setCurrentFramePosition(video_position); // return frame from reader return reader->GetFrame(video_position); diff --git a/src/Qt/VideoCacheThread.cpp b/src/Qt/VideoCacheThread.cpp index ed224de5..208fcaab 100644 --- a/src/Qt/VideoCacheThread.cpp +++ b/src/Qt/VideoCacheThread.cpp @@ -102,6 +102,12 @@ namespace openshot // Ignore out of bounds frame exceptions } + // Is cache position behind current display frame? + if (position < current_display_frame) { + // Jump ahead + position = current_display_frame; + } + // Increment frame number position++; } From dac2c9a58c2ebefed4212ec0e49d072b859dc502 Mon Sep 17 00:00:00 2001 From: Jeff Shillitto Date: Sat, 15 Dec 2018 21:55:00 +1100 Subject: [PATCH 068/223] Add a text background colored box option to the text reader --- include/TextReader.h | 7 ++++++- src/TextReader.cpp | 15 +++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/include/TextReader.h b/include/TextReader.h index d7d653d2..8e0bc18d 100644 --- a/include/TextReader.h +++ b/include/TextReader.h @@ -90,6 +90,7 @@ namespace openshot double size; string text_color; string background_color; + string text_background_color; std::shared_ptr image; list lines; bool is_open; @@ -110,9 +111,13 @@ namespace openshot /// @param font The font of the text /// @param size The size of the text /// @param text_color The color of the text - /// @param background_color The background color of the text (also supports Transparent) + /// @param background_color The background color of the text frame image (also supports Transparent) TextReader(int width, int height, int x_offset, int y_offset, GravityType gravity, string text, string font, double size, string text_color, string background_color); + /// Draw a box under rendered text using the specified color. + /// @param text_background_color The background color behind the text + void SetTextBackgroundColor(string color); + /// Close Reader void Close(); diff --git a/src/TextReader.cpp b/src/TextReader.cpp index 8234aa5d..245ca6a8 100644 --- a/src/TextReader.cpp +++ b/src/TextReader.cpp @@ -45,6 +45,14 @@ TextReader::TextReader(int width, int height, int x_offset, int y_offset, Gravit Close(); } +void TextReader::SetTextBackgroundColor(string color) { + text_background_color = color; + + // Open and Close the reader, to populate it's attributes (such as height, width, etc...) plus the text background color + Open(); + Close(); +} + // Open reader void TextReader::Open() { @@ -97,6 +105,10 @@ void TextReader::Open() lines.push_back(Magick::DrawablePointSize(size)); lines.push_back(Magick::DrawableText(x_offset, y_offset, text)); + if (!text_background_color.empty()) { + lines.push_back(Magick::DrawableTextUnderColor(Magick::Color(text_background_color))); + } + // Draw image image->draw(lines); @@ -190,6 +202,7 @@ Json::Value TextReader::JsonValue() { root["size"] = size; root["text_color"] = text_color; root["background_color"] = background_color; + root["text_background_color"] = text_background_color; root["gravity"] = gravity; // return JsonValue @@ -244,6 +257,8 @@ void TextReader::SetJsonValue(Json::Value root) { text_color = root["text_color"].asString(); if (!root["background_color"].isNull()) background_color = root["background_color"].asString(); + if (!root["text_background_color"].isNull()) + text_background_color = root["text_background_color"].asString(); if (!root["gravity"].isNull()) gravity = (GravityType) root["gravity"].asInt(); From 4dcc72a769f2d2f7c790fe7b769e896c11bb15d4 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Wed, 19 Dec 2018 09:12:15 -0800 Subject: [PATCH 069/223] Fixed bug compiling for older ffmpeg versions < 3.2 --- src/FFmpegReader.cpp | 18 ++++++++++++++++++ src/FFmpegWriter.cpp | 5 ++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index f541c295..2c181193 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -458,6 +458,24 @@ void FFmpegReader::Open() throw InvalidCodec("Hardware device reference create failed.", path); } /* + av_buffer_unref(&ist->hw_frames_ctx); + ist->hw_frames_ctx = av_hwframe_ctx_alloc(hw_device_ctx); + if (!ist->hw_frames_ctx) { + av_log(avctx, AV_LOG_ERROR, "Error creating a CUDA frames context\n"); + return AVERROR(ENOMEM); + } + + frames_ctx = (AVHWFramesContext*)ist->hw_frames_ctx->data; + + frames_ctx->format = AV_PIX_FMT_CUDA; + frames_ctx->sw_format = avctx->sw_pix_fmt; + frames_ctx->width = avctx->width; + frames_ctx->height = avctx->height; + + av_log(avctx, AV_LOG_DEBUG, "Initializing CUDA frames context: sw_format = %s, width = %d, height = %d\n", + av_get_pix_fmt_name(frames_ctx->sw_format), frames_ctx->width, frames_ctx->height); + + ret = av_hwframe_ctx_init(pCodecCtx->hw_device_ctx); ret = av_hwframe_ctx_init(ist->hw_frames_ctx); if (ret < 0) { diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index 66befe5c..c4f2ca43 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -1053,6 +1053,7 @@ AVStream* FFmpegWriter::add_video_stream() } #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 39, 101) else { +#if IS_FFMPEG_3_2 if (hw_en_on) { double mbs = 15000000.0; if (info.video_bit_rate > 0) { @@ -1065,7 +1066,9 @@ AVStream* FFmpegWriter::add_video_stream() } c->bit_rate = (int)(mbs); } - else { + else +#endif + { switch (c->codec_id) { #if (LIBAVCODEC_VERSION_MAJOR >= 58) case AV_CODEC_ID_AV1 : From e10695f9d480d917d8f25025e210ad4da025279f Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Thu, 20 Dec 2018 09:18:26 -0800 Subject: [PATCH 070/223] Fixed two memory leaks --- src/FFmpegReader.cpp | 1 + src/FFmpegWriter.cpp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 2c181193..859f4cf6 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -2426,6 +2426,7 @@ void FFmpegReader::RemoveAVFrame(AVFrame* remove_frame) { // Free memory av_freep(&remove_frame->data[0]); + AV_FREE_FRAME(&remove_frame); } } diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index c4f2ca43..8e29dc59 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -1615,8 +1615,8 @@ void FFmpegWriter::write_audio_packets(bool final) } else { // Create a new array - final_samples = new int16_t[audio_input_position * (av_get_bytes_per_sample(audio_codec->sample_fmt) / av_get_bytes_per_sample(AV_SAMPLE_FMT_S16))]; - + //final_samples = new int16_t[audio_input_position * (av_get_bytes_per_sample(audio_codec->sample_fmt) / av_get_bytes_per_sample(AV_SAMPLE_FMT_S16))]; + final_samples = (int16_t*)av_malloc(sizeof(int16_t) * audio_input_position * (av_get_bytes_per_sample(audio_codec->sample_fmt) / av_get_bytes_per_sample(AV_SAMPLE_FMT_S16))); // Copy audio into buffer for frame memcpy(final_samples, samples, audio_input_position * av_get_bytes_per_sample(audio_codec->sample_fmt)); From e0ec60396592780110e5ca1ab930a0eaa9a7ff29 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Wed, 9 Jan 2019 00:56:49 -0600 Subject: [PATCH 071/223] Fixing Scale Mode (None) in previews (#182) * Handle SCALE_NONE mode when optimized for previews (previews are often smaller than the project size) * Fixing 2 memory leaks (thanks PeterM) --- src/FFmpegReader.cpp | 1 + src/FFmpegWriter.cpp | 2 +- src/Timeline.cpp | 61 +++++++++++++++++++++++++++----------------- 3 files changed, 39 insertions(+), 25 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 243d7385..dc79be8f 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -1973,6 +1973,7 @@ void FFmpegReader::RemoveAVFrame(AVFrame* remove_frame) { // Free memory av_freep(&remove_frame->data[0]); + AV_FREE_FRAME(&remove_frame); } } diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index bd5486b9..e02c8803 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -1383,7 +1383,7 @@ void FFmpegWriter::write_audio_packets(bool final) } else { // Create a new array - final_samples = new int16_t[audio_input_position * (av_get_bytes_per_sample(audio_codec->sample_fmt) / av_get_bytes_per_sample(AV_SAMPLE_FMT_S16))]; + final_samples = (int16_t*)av_malloc(sizeof(int16_t) * audio_input_position * (av_get_bytes_per_sample(audio_codec->sample_fmt) / av_get_bytes_per_sample(AV_SAMPLE_FMT_S16))); // Copy audio into buffer for frame memcpy(final_samples, samples, audio_input_position * av_get_bytes_per_sample(audio_codec->sample_fmt)); diff --git a/src/Timeline.cpp b/src/Timeline.cpp index 6d1b0cde..d97b13e4 100644 --- a/src/Timeline.cpp +++ b/src/Timeline.cpp @@ -387,35 +387,48 @@ void Timeline::add_layer(std::shared_ptr new_frame, Clip* source_clip, in QSize source_size = source_image->size(); switch (source_clip->scale) { - case (SCALE_FIT): - // keep aspect ratio - source_size.scale(max_width, max_height, Qt::KeepAspectRatio); + case (SCALE_FIT): { + // keep aspect ratio + source_size.scale(max_width, max_height, Qt::KeepAspectRatio); - // Debug output - ZmqLogger::Instance()->AppendDebugMethod("Timeline::add_layer (Scale: SCALE_FIT)", "source_frame->number", source_frame->number, "source_width", source_size.width(), "source_height", source_size.height(), "", -1, "", -1, "", -1); - break; + // Debug output + ZmqLogger::Instance()->AppendDebugMethod("Timeline::add_layer (Scale: SCALE_FIT)", "source_frame->number", source_frame->number, "source_width", source_size.width(), "source_height", source_size.height(), "", -1, "", -1, "", -1); + break; + } + case (SCALE_STRETCH): { + // ignore aspect ratio + source_size.scale(max_width, max_height, Qt::IgnoreAspectRatio); - case (SCALE_STRETCH): - // ignore aspect ratio - source_size.scale(max_width, max_height, Qt::IgnoreAspectRatio); + // Debug output + ZmqLogger::Instance()->AppendDebugMethod("Timeline::add_layer (Scale: SCALE_STRETCH)", "source_frame->number", source_frame->number, "source_width", source_size.width(), "source_height", source_size.height(), "", -1, "", -1, "", -1); + break; + } + case (SCALE_CROP): { + QSize width_size(max_width, round(max_width / (float(source_size.width()) / float(source_size.height())))); + QSize height_size(round(max_height / (float(source_size.height()) / float(source_size.width()))), max_height); - // Debug output - ZmqLogger::Instance()->AppendDebugMethod("Timeline::add_layer (Scale: SCALE_STRETCH)", "source_frame->number", source_frame->number, "source_width", source_size.width(), "source_height", source_size.height(), "", -1, "", -1, "", -1); - break; + // respect aspect ratio + if (width_size.width() >= max_width && width_size.height() >= max_height) + source_size.scale(width_size.width(), width_size.height(), Qt::KeepAspectRatio); + else + source_size.scale(height_size.width(), height_size.height(), Qt::KeepAspectRatio); - case (SCALE_CROP): - QSize width_size(max_width, round(max_width / (float(source_size.width()) / float(source_size.height())))); - QSize height_size(round(max_height / (float(source_size.height()) / float(source_size.width()))), max_height); + // Debug output + ZmqLogger::Instance()->AppendDebugMethod("Timeline::add_layer (Scale: SCALE_CROP)", "source_frame->number", source_frame->number, "source_width", source_size.width(), "source_height", source_size.height(), "", -1, "", -1, "", -1); + break; + } + case (SCALE_NONE): { + // Calculate ratio of source size to project size + // Even with no scaling, previews need to be adjusted correctly + // (otherwise NONE scaling draws the frame image outside of the preview) + float source_width_ratio = source_size.width() / float(info.width); + float source_height_ratio = source_size.height() / float(info.height); + source_size.scale(max_width * source_width_ratio, max_height * source_height_ratio, Qt::KeepAspectRatio); - // respect aspect ratio - if (width_size.width() >= max_width && width_size.height() >= max_height) - source_size.scale(width_size.width(), width_size.height(), Qt::KeepAspectRatio); - else - source_size.scale(height_size.width(), height_size.height(), Qt::KeepAspectRatio); - - // Debug output - ZmqLogger::Instance()->AppendDebugMethod("Timeline::add_layer (Scale: SCALE_CROP)", "source_frame->number", source_frame->number, "source_width", source_size.width(), "source_height", source_size.height(), "", -1, "", -1, "", -1); - break; + // Debug output + ZmqLogger::Instance()->AppendDebugMethod("Timeline::add_layer (Scale: SCALE_NONE)", "source_frame->number", source_frame->number, "source_width", source_size.width(), "source_height", source_size.height(), "", -1, "", -1, "", -1); + break; + } } /* GRAVITY LOCATION - Initialize X & Y to the correct values (before applying location curves) */ From 13bd272eade3da6926cf81b5082a0a50451d4e23 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Wed, 9 Jan 2019 16:50:40 -0600 Subject: [PATCH 072/223] Adding new settings class to be used for changing realtime settings used by libopenshot, such as scaling mode for preview vs final render, or hardware decode, etc... --- include/FFmpegReader.h | 2 +- include/FFmpegWriter.h | 1 + include/OpenMPUtilities.h | 14 ----- include/OpenShot.h | 1 + include/Settings.h | 97 ++++++++++++++++++++++++++++++++++ include/ZmqLogger.h | 7 ++- src/CMakeLists.txt | 1 + src/FFmpegReader.cpp | 33 ++++++------ src/FFmpegWriter.cpp | 7 ++- src/Settings.cpp | 50 ++++++++++++++++++ src/bindings/python/openshot.i | 2 + src/bindings/ruby/openshot.i | 2 + tests/CMakeLists.txt | 1 + tests/Settings_Tests.cpp | 63 ++++++++++++++++++++++ 14 files changed, 245 insertions(+), 36 deletions(-) create mode 100644 include/Settings.h create mode 100644 src/Settings.cpp create mode 100644 tests/Settings_Tests.cpp diff --git a/include/FFmpegReader.h b/include/FFmpegReader.h index e2c4863a..6e88fc7b 100644 --- a/include/FFmpegReader.h +++ b/include/FFmpegReader.h @@ -44,6 +44,7 @@ #include "CacheMemory.h" #include "Exceptions.h" #include "OpenMPUtilities.h" +#include "Settings.h" using namespace std; @@ -105,7 +106,6 @@ namespace openshot bool check_interlace; bool check_fps; bool has_missing_frames; - bool use_omp_threads; CacheMemory working_cache; CacheMemory missing_frames; diff --git a/include/FFmpegWriter.h b/include/FFmpegWriter.h index 7eefacb7..e219f72c 100644 --- a/include/FFmpegWriter.h +++ b/include/FFmpegWriter.h @@ -51,6 +51,7 @@ #include "Exceptions.h" #include "OpenMPUtilities.h" #include "ZmqLogger.h" +#include "Settings.h" using namespace std; diff --git a/include/OpenMPUtilities.h b/include/OpenMPUtilities.h index 1525730d..65047c31 100644 --- a/include/OpenMPUtilities.h +++ b/include/OpenMPUtilities.h @@ -37,20 +37,6 @@ #define OPEN_MP_NUM_PROCESSORS (min(omp_get_num_procs(), 6)) #define FF_NUM_PROCESSORS (min(omp_get_num_procs(), 12)) -using namespace std; -namespace openshot { - - // Check if OS2_OMP_THREADS environment variable is present, and return - // if multiple threads should be used with OMP - static bool IsOMPEnabled() { - char* OS2_OMP_THREADS = getenv("OS2_OMP_THREADS"); - if (OS2_OMP_THREADS != NULL && strcmp(OS2_OMP_THREADS, "0") == 0) - return false; - else - return true; - } - -} #endif diff --git a/include/OpenShot.h b/include/OpenShot.h index e4b60f3e..207f4b42 100644 --- a/include/OpenShot.h +++ b/include/OpenShot.h @@ -134,5 +134,6 @@ #include "Profiles.h" #include "QtImageReader.h" #include "Timeline.h" +#include "Settings.h" #endif diff --git a/include/Settings.h b/include/Settings.h new file mode 100644 index 00000000..6a7940eb --- /dev/null +++ b/include/Settings.h @@ -0,0 +1,97 @@ +/** + * @file + * @brief Header file for global Settings class + * @author Jonathan Thomas + * + * @section LICENSE + * + * Copyright (c) 2008-2014 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 . + */ + +#ifndef OPENSHOT_SETTINGS_H +#define OPENSHOT_SETTINGS_H + + +#include "JuceLibraryCode/JuceHeader.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +using namespace std; + +namespace openshot { + + /** + * @brief This class is contains settings used by libopenshot (and can be safely toggled at any point) + * + * Settings class is used primarily to toggle scale settings between preview and rendering, and adjust + * other runtime related settings. + */ + class Settings { + private: + + /// Default constructor + Settings(){}; // Don't allow user to create an instance of this singleton + +#if __GNUC__ >=7 + /// Default copy method + Settings(Settings const&) = delete; // Don't allow the user to assign this instance + + /// Default assignment operator + Settings & operator=(Settings const&) = delete; // Don't allow the user to assign this instance +#else + /// Default copy method + Settings(Settings const&) {}; // Don't allow the user to assign this instance + + /// Default assignment operator + Settings & operator=(Settings const&); // Don't allow the user to assign this instance +#endif + + /// Private variable to keep track of singleton instance + static Settings * m_pInstance; + + public: + /// Use video card for faster video decoding (if supported) + bool HARDWARE_DECODE = false; + + /// Use video card for faster video encoding (if supported) + bool HARDWARE_ENCODE = false; + + /// Scale mode used in FFmpeg decoding and encoding (used as an optimization for faster previews) + bool HIGH_QUALITY_SCALING = false; + + /// Wait for OpenMP task to finish before continuing (used to limit threads on slower systems) + bool WAIT_FOR_VIDEO_PROCESSING_TASK = false; + + /// Create or get an instance of this logger singleton (invoke the class with this method) + static Settings * Instance(); + }; + +} + +#endif diff --git a/include/ZmqLogger.h b/include/ZmqLogger.h index e825ed0e..62773e68 100644 --- a/include/ZmqLogger.h +++ b/include/ZmqLogger.h @@ -47,11 +47,10 @@ using namespace std; namespace openshot { /** - * @brief This abstract class is the base class, used by all readers in libopenshot. + * @brief This class is used for logging and sending those logs over a ZemoMQ socket to a listener * - * Readers are types of classes that read video, audio, and image files, and - * return openshot::Frame objects. The only requirements for a 'reader', are to - * derive from this base class, implement the GetFrame method, and call the InitFileInfo() method. + * OpenShot desktop editor listens to this port, to receive libopenshot debug output. It both logs to + * a file and sends the stdout over a socket. */ class ZmqLogger { private: diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d71067a3..7752df15 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -244,6 +244,7 @@ SET ( OPENSHOT_SOURCE_FILES Profiles.cpp QtImageReader.cpp QtPlayer.cpp + Settings.cpp Timeline.cpp # Qt Video Player diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index dc79be8f..434b1ae4 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -38,7 +38,7 @@ FFmpegReader::FFmpegReader(string path) check_fps(false), enable_seek(true), is_open(false), seek_audio_frame_found(0), seek_video_frame_found(0), prev_samples(0), prev_pts(0), pts_total(0), pts_counter(0), is_duration_known(false), largest_frame_processed(0), current_video_frame(0), has_missing_frames(false), num_packets_since_video_frame(0), num_checks_since_final(0), - packet(NULL), use_omp_threads(true) { + packet(NULL) { // Initialize FFMpeg, and register all formats and codecs AV_REGISTER_ALL @@ -60,7 +60,7 @@ FFmpegReader::FFmpegReader(string path, bool inspect_reader) check_fps(false), enable_seek(true), is_open(false), seek_audio_frame_found(0), seek_video_frame_found(0), prev_samples(0), prev_pts(0), pts_total(0), pts_counter(0), is_duration_known(false), largest_frame_processed(0), current_video_frame(0), has_missing_frames(false), num_packets_since_video_frame(0), num_checks_since_final(0), - packet(NULL), use_omp_threads(true) { + packet(NULL) { // Initialize FFMpeg, and register all formats and codecs AV_REGISTER_ALL @@ -229,9 +229,6 @@ void FFmpegReader::Open() missing_frames.SetMaxBytesFromInfo(OPEN_MP_NUM_PROCESSORS * 2, info.width, info.height, info.sample_rate, info.channels); final_cache.SetMaxBytesFromInfo(OPEN_MP_NUM_PROCESSORS * 2, info.width, info.height, info.sample_rate, info.channels); - // Initialize OMP threading support - use_omp_threads = openshot::IsOMPEnabled(); - // Mark as "open" is_open = true; } @@ -613,7 +610,7 @@ std::shared_ptr FFmpegReader::ReadStream(int64_t requested_frame) // Process Video Packet ProcessVideoPacket(requested_frame); - if (!use_omp_threads) { + if (openshot::Settings::Instance()->WAIT_FOR_VIDEO_PROCESSING_TASK) { // Wait on each OMP task to complete before moving on to the next one. This slows // down processing considerably, but might be more stable on some systems. #pragma omp taskwait @@ -628,16 +625,16 @@ std::shared_ptr FFmpegReader::ReadStream(int64_t requested_frame) num_packets_since_video_frame++; // Check the status of a seek (if any) - if (is_seeking) - #pragma omp critical (openshot_seek) - check_seek = CheckSeek(false); - else - check_seek = false; + if (is_seeking) + #pragma omp critical (openshot_seek) + check_seek = CheckSeek(false); + else + check_seek = false; - if (check_seek) { - // Jump to the next iteration of this loop - continue; - } + if (check_seek) { + // Jump to the next iteration of this loop + continue; + } // Update PTS / Frame Offset (if any) UpdatePTSOffset(false); @@ -919,8 +916,12 @@ void FFmpegReader::ProcessVideoPacket(int64_t requested_frame) // Copy picture data from one AVFrame (or AVPicture) to another one. AV_COPY_PICTURE_DATA(pFrameRGB, buffer, PIX_FMT_RGBA, width, height); + int scale_mode = SWS_FAST_BILINEAR; + if (openshot::Settings::Instance()->HIGH_QUALITY_SCALING) { + scale_mode = SWS_LANCZOS; + } SwsContext *img_convert_ctx = sws_getContext(info.width, info.height, AV_GET_CODEC_PIXEL_FORMAT(pStream, pCodecCtx), width, - height, PIX_FMT_RGBA, SWS_LANCZOS, NULL, NULL, NULL); + height, PIX_FMT_RGBA, scale_mode, NULL, NULL, NULL); // Resize / Convert to RGB sws_scale(img_convert_ctx, my_frame->data, my_frame->linesize, 0, diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index e02c8803..56f98354 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -1748,11 +1748,16 @@ void FFmpegWriter::OutputStreamInfo() // Init a collection of software rescalers (thread safe) void FFmpegWriter::InitScalers(int source_width, int source_height) { + int scale_mode = SWS_FAST_BILINEAR; + if (openshot::Settings::Instance()->HIGH_QUALITY_SCALING) { + scale_mode = SWS_LANCZOS; + } + // Init software rescalers vector (many of them, one for each thread) for (int x = 0; x < num_of_rescalers; x++) { // Init the software scaler from FFMpeg - img_convert_ctx = sws_getContext(source_width, source_height, PIX_FMT_RGBA, info.width, info.height, AV_GET_CODEC_PIXEL_FORMAT(video_st, video_st->codec), SWS_LANCZOS, NULL, NULL, NULL); + img_convert_ctx = sws_getContext(source_width, source_height, PIX_FMT_RGBA, info.width, info.height, AV_GET_CODEC_PIXEL_FORMAT(video_st, video_st->codec), scale_mode, NULL, NULL, NULL); // Add rescaler to vector image_rescalers.push_back(img_convert_ctx); diff --git a/src/Settings.cpp b/src/Settings.cpp new file mode 100644 index 00000000..e6749d5c --- /dev/null +++ b/src/Settings.cpp @@ -0,0 +1,50 @@ +/** + * @file + * @brief Source file for global Settings class + * @author Jonathan Thomas + * + * @section LICENSE + * + * Copyright (c) 2008-2014 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/Settings.h" + +using namespace std; +using namespace openshot; + + +// Global reference to logger +Settings *Settings::m_pInstance = NULL; + +// Create or Get an instance of the logger singleton +Settings *Settings::Instance() +{ + if (!m_pInstance) { + // Create the actual instance of logger only once + m_pInstance = new Settings; + m_pInstance->HARDWARE_DECODE = false; + m_pInstance->HARDWARE_ENCODE = false; + m_pInstance->HIGH_QUALITY_SCALING = false; + m_pInstance->WAIT_FOR_VIDEO_PROCESSING_TASK = false; + } + + return m_pInstance; +} diff --git a/src/bindings/python/openshot.i b/src/bindings/python/openshot.i index f338f18a..de1f020c 100644 --- a/src/bindings/python/openshot.i +++ b/src/bindings/python/openshot.i @@ -84,6 +84,7 @@ #include "../../../include/QtPlayer.h" #include "../../../include/KeyFrame.h" #include "../../../include/RendererBase.h" +#include "../../../include/Settings.h" #include "../../../include/Timeline.h" #include "../../../include/ZmqLogger.h" @@ -150,6 +151,7 @@ %include "../../../include/QtPlayer.h" %include "../../../include/KeyFrame.h" %include "../../../include/RendererBase.h" +%include "../../../include/Settings.h" %include "../../../include/Timeline.h" %include "../../../include/ZmqLogger.h" diff --git a/src/bindings/ruby/openshot.i b/src/bindings/ruby/openshot.i index c2f6fdf9..b9a35d41 100644 --- a/src/bindings/ruby/openshot.i +++ b/src/bindings/ruby/openshot.i @@ -88,6 +88,7 @@ namespace std { #include "../../../include/QtPlayer.h" #include "../../../include/KeyFrame.h" #include "../../../include/RendererBase.h" +#include "../../../include/Settings.h" #include "../../../include/Timeline.h" #include "../../../include/ZmqLogger.h" @@ -143,6 +144,7 @@ namespace std { %include "../../../include/QtPlayer.h" %include "../../../include/KeyFrame.h" %include "../../../include/RendererBase.h" +%include "../../../include/Settings.h" %include "../../../include/Timeline.h" %include "../../../include/ZmqLogger.h" diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2c455503..45ce0906 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -172,6 +172,7 @@ IF (NOT DISABLE_TESTS) FrameMapper_Tests.cpp KeyFrame_Tests.cpp Point_Tests.cpp + Settings_Tests.cpp Timeline_Tests.cpp ) ################ TESTER EXECUTABLE ################# diff --git a/tests/Settings_Tests.cpp b/tests/Settings_Tests.cpp new file mode 100644 index 00000000..86790653 --- /dev/null +++ b/tests/Settings_Tests.cpp @@ -0,0 +1,63 @@ +/** + * @file + * @brief Unit tests for openshot::Color + * @author Jonathan Thomas + * + * @section LICENSE + * + * Copyright (c) 2008-2014 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" +#include "../include/OpenShot.h" + +using namespace std; +using namespace openshot; + +TEST(Settings_Default_Constructor) +{ + // Create an empty color + Settings *s = Settings::Instance(); + + CHECK_EQUAL(false, s->HARDWARE_DECODE); + CHECK_EQUAL(false, s->HARDWARE_ENCODE); + CHECK_EQUAL(false, s->HIGH_QUALITY_SCALING); + CHECK_EQUAL(false, s->WAIT_FOR_VIDEO_PROCESSING_TASK); +} + +TEST(Settings_Change_Settings) +{ + // Create an empty color + Settings *s = Settings::Instance(); + s->HARDWARE_DECODE = true; + s->HARDWARE_ENCODE = true; + s->HIGH_QUALITY_SCALING = true; + s->WAIT_FOR_VIDEO_PROCESSING_TASK = true; + + CHECK_EQUAL(true, s->HARDWARE_DECODE); + CHECK_EQUAL(true, s->HARDWARE_ENCODE); + CHECK_EQUAL(true, s->HIGH_QUALITY_SCALING); + CHECK_EQUAL(true, s->WAIT_FOR_VIDEO_PROCESSING_TASK); + + CHECK_EQUAL(true, s->HARDWARE_DECODE); + CHECK_EQUAL(true, s->HARDWARE_ENCODE); + CHECK_EQUAL(true, Settings::Instance()->HIGH_QUALITY_SCALING); + CHECK_EQUAL(true, Settings::Instance()->WAIT_FOR_VIDEO_PROCESSING_TASK); +} \ No newline at end of file From c56b5bf532f0349da5d42ee47ca2ad787c1aad3c Mon Sep 17 00:00:00 2001 From: Frank Dana Date: Mon, 14 Jan 2019 16:10:57 -0500 Subject: [PATCH 073/223] Remove Eclipse files and add to gitignore (#178) --- .cproject | 405 ----------------------------------------------------- .gitignore | 5 +- .project | 94 ------------- 3 files changed, 4 insertions(+), 500 deletions(-) delete mode 100644 .cproject delete mode 100644 .project diff --git a/.cproject b/.cproject deleted file mode 100644 index 1028fe17..00000000 --- a/.cproject +++ /dev/null @@ -1,405 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - cmake - -G "Unix Makefiles" ../ -D"CMAKE_BUILD_TYPE:STRING=Release" - true - false - true - - - cmake - -G "Unix Makefiles" ../ -D"MAGICKCORE_HDRI_ENABLE=0" -D"MAGICKCORE_QUANTUM_DEPTH=16" -D"OPENSHOT_IMAGEMAGICK_COMPATIBILITY=0" -D"ENABLE_BLACKMAGIC=1" -D"CMAKE_BUILD_TYPE:STRING=Debug" -D"DISABLE_TESTS=0" - - true - false - true - - - make - test - true - false - true - - - make - help - true - false - true - - - make - doc - true - false - true - - - cmake - -DCMAKE_CXX_COMPILER=/usr/local/opt/gcc48/bin/g++-4.8 -DCMAKE_C_COMPILER=/usr/local/opt/gcc48/bin/gcc-4.8 -DCMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.1.1/ -DPYTHON_INCLUDE_DIR=/usr/local/Cellar/python3/3.3.2/Frameworks/Python.framework/Versions/3.3/include/python3.3m/ -DPYTHON_LIBRARY=/usr/local/Cellar/python3/3.3.2/Frameworks/Python.framework/Versions/3.3/lib/libpython3.3.dylib -DPython_FRAMEWORKS=/usr/local/Cellar/python3/3.3.2/Frameworks/Python.framework/ ../ -D"CMAKE_BUILD_TYPE:STRING=Debug" - true - false - true - - - cmake - -DCMAKE_CXX_COMPILER=/usr/local/opt/gcc48/bin/g++-4.8 -DCMAKE_C_COMPILER=/usr/local/opt/gcc48/bin/gcc-4.8 -DCMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.1.1/ -DPYTHON_INCLUDE_DIR=/usr/local/Cellar/python3/3.3.2/Frameworks/Python.framework/Versions/3.3/include/python3.3m/ -DPYTHON_LIBRARY=/usr/local/Cellar/python3/3.3.2/Frameworks/Python.framework/Versions/3.3/lib/libpython3.3.dylib -DPython_FRAMEWORKS=/usr/local/Cellar/python3/3.3.2/Frameworks/Python.framework/ ../ -D"CMAKE_BUILD_TYPE:STRING=Release" - true - false - true - - - cmake - -G "MinGW Makefiles" ../ -D"CMAKE_BUILD_TYPE:STRING=Debug" - true - false - true - - - cmake - -G "MinGW Makefiles" ../ -D"CMAKE_BUILD_TYPE:STRING=Release" - true - false - true - - - - diff --git a/.gitignore b/.gitignore index e7f24925..a11656cf 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,7 @@ build/* *.DS_Store .pydevproject .settings -.idea/* \ No newline at end of file +.idea/* +.project +.cproject +/.metadata/ diff --git a/.project b/.project deleted file mode 100644 index 5e324854..00000000 --- a/.project +++ /dev/null @@ -1,94 +0,0 @@ - - - libopenshot - - - - - - com.aptana.ide.core.unifiedBuilder - - - - - org.python.pydev.PyDevBuilder - - - - - org.eclipse.cdt.managedbuilder.core.genmakebuilder - clean,full,incremental, - - - ?name? - - - - org.eclipse.cdt.make.core.append_environment - true - - - org.eclipse.cdt.make.core.autoBuildTarget - all - - - org.eclipse.cdt.make.core.buildArguments - - - - org.eclipse.cdt.make.core.buildCommand - make - - - org.eclipse.cdt.make.core.buildLocation - ${workspace_loc:/libopenshot/build} - - - org.eclipse.cdt.make.core.cleanBuildTarget - clean - - - org.eclipse.cdt.make.core.contents - org.eclipse.cdt.make.core.activeConfigSettings - - - org.eclipse.cdt.make.core.enableAutoBuild - false - - - org.eclipse.cdt.make.core.enableCleanBuild - true - - - org.eclipse.cdt.make.core.enableFullBuild - true - - - org.eclipse.cdt.make.core.fullBuildTarget - all - - - org.eclipse.cdt.make.core.stopOnError - true - - - org.eclipse.cdt.make.core.useDefaultBuildCmd - true - - - - - org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder - - - - - - org.eclipse.cdt.core.cnature - org.eclipse.cdt.core.ccnature - org.eclipse.cdt.managedbuilder.core.managedBuildNature - org.eclipse.cdt.managedbuilder.core.ScannerConfigNature - org.python.pydev.pythonNature - com.aptana.ruby.core.rubynature - - From a49979ebf8086585da3717a905ad1bd83be0bd30 Mon Sep 17 00:00:00 2001 From: Frank Dana Date: Mon, 14 Jan 2019 16:25:33 -0500 Subject: [PATCH 074/223] tests/CMakeLists: Use same dependencies as src/ (#163) * tests/CMakeFiles: Use FFMpeg like src/ * Use system jsoncpp in tests, too The tests/ build needs to use the same jsoncpp as the src/ build, or tests in Clip_Tests.cpp can fail. * Don't show FFMpeg version messages in tests/ Displaying the version-number messages twice seems like overkill --- tests/CMakeLists.txt | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 45ce0906..bb2f8c2a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -79,7 +79,33 @@ ENDIF (ImageMagick_FOUND) FIND_PACKAGE(FFmpeg REQUIRED) # Include FFmpeg headers (needed for compile) -include_directories(${FFMPEG_INCLUDE_DIR}) +IF (AVCODEC_FOUND) + include_directories(${AVCODEC_INCLUDE_DIRS}) +ENDIF (AVCODEC_FOUND) +IF (AVDEVICE_FOUND) + include_directories(${AVDEVICE_INCLUDE_DIRS}) +ENDIF (AVDEVICE_FOUND) +IF (AVFORMAT_FOUND) + include_directories(${AVFORMAT_INCLUDE_DIRS}) +ENDIF (AVFORMAT_FOUND) +IF (AVFILTER_FOUND) + include_directories(${AVFILTER_INCLUDE_DIRS}) +ENDIF (AVFILTER_FOUND) +IF (AVUTIL_FOUND) + include_directories(${AVUTIL_INCLUDE_DIRS}) +ENDIF (AVUTIL_FOUND) +IF (POSTPROC_FOUND) + include_directories(${POSTPROC_INCLUDE_DIRS}) +ENDIF (POSTPROC_FOUND) +IF (SWSCALE_FOUND) + include_directories(${SWSCALE_INCLUDE_DIRS}) +ENDIF (SWSCALE_FOUND) +IF (SWRESAMPLE_FOUND) + include_directories(${SWRESAMPLE_INCLUDE_DIRS}) +ENDIF (SWRESAMPLE_FOUND) +IF (AVRESAMPLE_FOUND) + include_directories(${AVRESAMPLE_INCLUDE_DIRS}) +ENDIF (AVRESAMPLE_FOUND) ################# LIBOPENSHOT-AUDIO ################### # Find JUCE-based openshot Audio libraries @@ -150,12 +176,18 @@ endif(OPENMP_FOUND) # Find ZeroMQ library (used for socket communication & logging) FIND_PACKAGE(ZMQ REQUIRED) -# Include FFmpeg headers (needed for compile) +# Include ZeroMQ headers (needed for compile) include_directories(${ZMQ_INCLUDE_DIRS}) ################### JSONCPP ##################### # Include jsoncpp headers (needed for JSON parsing) -include_directories("../thirdparty/jsoncpp/include") +if (USE_SYSTEM_JSONCPP) + find_package(JsonCpp REQUIRED) + include_directories(${JSONCPP_INCLUDE_DIRS}) +else() + message("Using embedded JsonCpp") + include_directories("../thirdparty/jsoncpp/include") +endif(USE_SYSTEM_JSONCPP) IF (NOT DISABLE_TESTS) ############### SET TEST SOURCE FILES ################# From 828990b103e04cd03fabc93f6e75b9e691ccfa98 Mon Sep 17 00:00:00 2001 From: Jeff Shillitto Date: Tue, 15 Jan 2019 08:40:07 +1100 Subject: [PATCH 075/223] Update readme to use markdown and update copyright to 2019 (#94) * Update readme to use markdown and update copyright to 2018 Add md extension to render in Github Wrap commercial details with horizontal rules Updating correct file and deleting duplicate * Update dates to 2019 --- README | 66 ------------------------------------------------------- README.md | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 66 deletions(-) delete mode 100644 README create mode 100644 README.md diff --git a/README b/README deleted file mode 100644 index 8c05f753..00000000 --- a/README +++ /dev/null @@ -1,66 +0,0 @@ -#################################################################### - OpenShot Library -#################################################################### - -OpenShot Library (libopenshot) is an open-source project dedicated to -delivering high quality video editing, animation, and playback solutions -to the world. For more information visit . - -#################################################################### - License -#################################################################### - -Copyright (c) 2008-2014 OpenShot Studios, LLC -. - -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 . - -#################################################################### - - To release a closed-source product which uses libopenshot (i.e. video - editing and playback), commercial licenses are available: contact - sales@openshot.org for more information. - - -#################################################################### - Install -#################################################################### - -Please see /doc/InstallationGuide.pdf for a very detailed -Linux, Mac, and Windows compiling instruction guide. An online version -is also available: -https://docs.google.com/document/d/1V6nq-IuS9zxqO1-OSt8iTS_cw_HMCpsUNofHLYtUNjM/pub - - -#################################################################### - Documentation -#################################################################### - -Documentation is auto-generated by Doxygen, and can be created with -$ make doc (Also available online: ) - - -#################################################################### - Authors -#################################################################### - -Please see AUTHORS file for a full list of authors. - - -#################################################################### - www.openshot.org | www.openshotstudios.com -#################################################################### - - Copyright (c) 2008-2014 OpenShot Studios, LLC - . diff --git a/README.md b/README.md new file mode 100644 index 00000000..1baf3cb4 --- /dev/null +++ b/README.md @@ -0,0 +1,60 @@ +# OpenShot Library + +OpenShot Library (libopenshot) is an open-source project dedicated to +delivering high quality video editing, animation, and playback solutions +to the world. For more information visit [http://www.openshot.org/](http://www.openshot.org/). + +### License + +Copyright (c) 2008-2019 OpenShot Studios, LLC +[http://www.openshotstudios.com/](http://www.openshotstudios.com/). + +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 [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/). + +--- +To release a closed-source product which uses libopenshot (i.e. video +editing and playback), commercial licenses are available: contact +sales@openshot.org for more information. + +--- + +### Install + +Please see /doc/InstallationGuide.pdf for a very detailed +Linux, Mac, and Windows compiling instruction guide. An online version +is also available: +[https://docs.google.com/document/d/1V6nq-IuS9zxqO1-OSt8iTS_cw_HMCpsUNofHLYtUNjM/pub](https://docs.google.com/document/d/1V6nq-IuS9zxqO1-OSt8iTS_cw_HMCpsUNofHLYtUNjM/pub) + + +### Documentation + +Documentation is auto-generated by Doxygen, and can be created with +``` +make doc +``` +(Also available online: [http://openshot.org/files/libopenshot/](http://openshot.org/files/libopenshot/)) + + +### Authors + +Please see AUTHORS file for a full list of authors. + + +--- +[www.openshot.org](http://www.openshot.org) | [www.openshotstudios.com](http://www.openshotstudios.com) + +--- +Copyright (c) 2008-2019 OpenShot Studios, LLC + +[www.openshotstudios.com](http://www.openshotstudios.com). From 64a53283c90d045240835fa17986c2a09a4eebb6 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Tue, 15 Jan 2019 14:58:01 -0600 Subject: [PATCH 076/223] Adding condition before calling av_frame_free (in AV_FREE_FRAME macro) (#184) --- include/FFmpegUtilities.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/FFmpegUtilities.h b/include/FFmpegUtilities.h index 346da541..0c086585 100644 --- a/include/FFmpegUtilities.h +++ b/include/FFmpegUtilities.h @@ -141,7 +141,7 @@ #define AV_ALLOCATE_FRAME() av_frame_alloc() #define AV_ALLOCATE_IMAGE(av_frame, pix_fmt, width, height) av_image_alloc(av_frame->data, av_frame->linesize, width, height, pix_fmt, 1) #define AV_RESET_FRAME(av_frame) av_frame_unref(av_frame) - #define AV_FREE_FRAME(av_frame) av_frame_free(av_frame) + #define AV_FREE_FRAME(av_frame) if (av_frame) av_frame_free(av_frame) #define AV_FREE_PACKET(av_packet) av_packet_unref(av_packet) #define AV_FREE_CONTEXT(av_context) avcodec_free_context(&av_context) #define AV_GET_CODEC_TYPE(av_stream) av_stream->codecpar->codec_type @@ -176,7 +176,7 @@ #define AV_ALLOCATE_FRAME() av_frame_alloc() #define AV_ALLOCATE_IMAGE(av_frame, pix_fmt, width, height) av_image_alloc(av_frame->data, av_frame->linesize, width, height, pix_fmt, 1) #define AV_RESET_FRAME(av_frame) av_frame_unref(av_frame) - #define AV_FREE_FRAME(av_frame) av_frame_free(av_frame) + #define AV_FREE_FRAME(av_frame) if(av_frame) av_frame_free(av_frame) #define AV_FREE_PACKET(av_packet) av_packet_unref(av_packet) #define AV_FREE_CONTEXT(av_context) avcodec_free_context(&av_context) #define AV_GET_CODEC_TYPE(av_stream) av_stream->codecpar->codec_type @@ -211,7 +211,7 @@ #define AV_ALLOCATE_FRAME() av_frame_alloc() #define AV_ALLOCATE_IMAGE(av_frame, pix_fmt, width, height) avpicture_alloc((AVPicture *) av_frame, pix_fmt, width, height) #define AV_RESET_FRAME(av_frame) av_frame_unref(av_frame) - #define AV_FREE_FRAME(av_frame) av_frame_free(av_frame) + #define AV_FREE_FRAME(av_frame) if (av_frame) av_frame_free(av_frame) #define AV_FREE_PACKET(av_packet) av_packet_unref(av_packet) #define AV_FREE_CONTEXT(av_context) avcodec_close(av_context) #define AV_GET_CODEC_TYPE(av_stream) av_stream->codec->codec_type @@ -241,7 +241,7 @@ #define AV_ALLOCATE_FRAME() avcodec_alloc_frame() #define AV_ALLOCATE_IMAGE(av_frame, pix_fmt, width, height) avpicture_alloc((AVPicture *) av_frame, pix_fmt, width, height) #define AV_RESET_FRAME(av_frame) avcodec_get_frame_defaults(av_frame) - #define AV_FREE_FRAME(av_frame) avcodec_free_frame(av_frame) + #define AV_FREE_FRAME(av_frame) if(av_frame) avcodec_free_frame(av_frame) #define AV_FREE_PACKET(av_packet) av_free_packet(av_packet) #define AV_FREE_CONTEXT(av_context) avcodec_close(av_context) #define AV_GET_CODEC_TYPE(av_stream) av_stream->codec->codec_type From 7b6eb9c21bd324da50507b74784fdf40f1419d8d Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sat, 19 Jan 2019 02:18:52 -0600 Subject: [PATCH 077/223] Integration of resvg SVG library (optional during build) (#185) * Integration of libresvg SVG library (optional during build) * Major refactor of max_width and max_height for preview optimization * Fixed many bugs related to preview resizing, with regards to cached frames * Updating gitlab CI to find RESVGDIR correctly for windows, and adding svgz support * Updating cmake findresvg module to search for windows locations first, to prevent an issue on our windows builders and updating some CMake output. * Removing folder path from resvg header, since it could be installed in different named folders. This is an attempt to fix Windows include issues. * Making call to AV_FREE_FRAME conditional for non windows systems (because it crashes on Windows for seemingly no reason). Needs more investigation. --- .gitlab-ci.yml | 1 + cmake/Modules/FindRESVG.cmake | 34 +++++++ include/ChunkReader.h | 4 +- include/Clip.h | 9 -- include/ClipBase.h | 7 +- include/FFmpegReader.h | 1 + include/FFmpegUtilities.h | 8 +- include/QtImageReader.h | 10 +- include/ReaderBase.h | 14 +-- include/Settings.h | 6 ++ include/Timeline.h | 5 + src/CMakeLists.txt | 28 ++++-- src/ChunkReader.cpp | 2 +- src/Clip.cpp | 47 ++++------ src/FFmpegReader.cpp | 55 ++++++++++- src/FrameMapper.cpp | 3 - src/QtImageReader.cpp | 171 +++++++++++++++++++++++++--------- src/ReaderBase.cpp | 15 ++- src/Settings.cpp | 2 + src/Timeline.cpp | 72 +++++++------- tests/CMakeLists.txt | 10 +- 21 files changed, 331 insertions(+), 173 deletions(-) create mode 100644 cmake/Modules/FindRESVG.cmake diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 66701244..12365c0d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -66,6 +66,7 @@ windows-builder-x86: - Expand-Archive -Path artifacts.zip -DestinationPath . - $env:LIBOPENSHOT_AUDIO_DIR = "$CI_PROJECT_DIR\build\install-x86" - $env:UNITTEST_DIR = "C:\msys32\usr" + - $env:RESVGDIR = "C:\msys32\usr\local" - $env:ZMQDIR = "C:\msys32\usr" - $env:Path = "C:\msys32\mingw32\bin;C:\msys32\mingw32\lib;C:\msys32\usr\lib\cmake\UnitTest++;C:\msys32\home\jonathan\depot_tools;C:\msys32\usr;C:\msys32\usr\lib;" + $env:Path; - New-Item -ItemType Directory -Force -Path build diff --git a/cmake/Modules/FindRESVG.cmake b/cmake/Modules/FindRESVG.cmake new file mode 100644 index 00000000..4a201747 --- /dev/null +++ b/cmake/Modules/FindRESVG.cmake @@ -0,0 +1,34 @@ +# - Try to find RESVG +# Once done this will define +# RESVG_FOUND - System has RESVG +# RESVG_INCLUDE_DIRS - The RESVG include directories +# RESVG_LIBRARIES - The libraries needed to use RESVG +# RESVG_DEFINITIONS - Compiler switches required for using RESVG +find_path ( RESVG_INCLUDE_DIR ResvgQt.h + PATHS ${RESVGDIR}/include/resvg + $ENV{RESVGDIR}/include/resvg + $ENV{RESVGDIR}/include + /usr/include/resvg + /usr/include + /usr/local/include/resvg + /usr/local/include ) + +find_library ( RESVG_LIBRARY NAMES resvg + PATHS /usr/lib + /usr/local/lib + $ENV{RESVGDIR}/lib ) + +set ( RESVG_LIBRARIES ${RESVG_LIBRARY} ) +set ( RESVG_INCLUDE_DIRS ${RESVG_INCLUDE_DIR} ) + +SET( RESVG_FOUND FALSE ) + +IF ( RESVG_INCLUDE_DIR AND RESVG_LIBRARY ) + SET ( RESVG_FOUND TRUE ) + + include ( FindPackageHandleStandardArgs ) + # handle the QUIETLY and REQUIRED arguments and set RESVG_FOUND to TRUE + # if all listed variables are TRUE + find_package_handle_standard_args ( RESVG DEFAULT_MSG RESVG_LIBRARY RESVG_INCLUDE_DIR ) +ENDIF ( RESVG_INCLUDE_DIR AND RESVG_LIBRARY ) + diff --git a/include/ChunkReader.h b/include/ChunkReader.h index aa151093..b780602b 100644 --- a/include/ChunkReader.h +++ b/include/ChunkReader.h @@ -29,8 +29,6 @@ #define OPENSHOT_CHUNK_READER_H #include "ReaderBase.h" -#include "FFmpegReader.h" - #include #include #include @@ -107,7 +105,7 @@ namespace openshot string path; bool is_open; int64_t chunk_size; - FFmpegReader *local_reader; + ReaderBase *local_reader; ChunkLocation previous_location; ChunkVersion version; std::shared_ptr last_frame; diff --git a/include/Clip.h b/include/Clip.h index f30844b2..26cdd211 100644 --- a/include/Clip.h +++ b/include/Clip.h @@ -44,18 +44,9 @@ #include "EffectBase.h" #include "Effects.h" #include "EffectInfo.h" -#include "FFmpegReader.h" #include "Fraction.h" -#include "FrameMapper.h" -#ifdef USE_IMAGEMAGICK - #include "ImageReader.h" - #include "TextReader.h" -#endif -#include "QtImageReader.h" -#include "ChunkReader.h" #include "KeyFrame.h" #include "ReaderBase.h" -#include "DummyReader.h" using namespace std; using namespace openshot; diff --git a/include/ClipBase.h b/include/ClipBase.h index 06341640..3dae8a53 100644 --- a/include/ClipBase.h +++ b/include/ClipBase.h @@ -58,8 +58,6 @@ namespace openshot { float start; ///< The position in seconds to start playing (used to trim the beginning of a clip) float end; ///< The position in seconds to end playing (used to trim the ending of a clip) string previous_properties; ///< This string contains the previous JSON properties - int max_width; ///< The maximum image width needed by this clip (used for optimizations) - int max_height; ///< The maximium image height needed by this clip (used for optimizations) /// Generate JSON for a property Json::Value add_property_json(string name, float value, string type, string memo, Keyframe* keyframe, float min_value, float max_value, bool readonly, int64_t requested_frame); @@ -70,7 +68,7 @@ namespace openshot { public: /// Constructor for the base clip - ClipBase() { max_width = 0; max_height = 0; }; + ClipBase() { }; // Compare a clip using the Position() property bool operator< ( ClipBase& a) { return (Position() < a.Position()); } @@ -93,9 +91,6 @@ namespace openshot { void Start(float value) { start = value; } ///< Set start position (in seconds) of clip (trim start of video) void End(float value) { end = value; } ///< Set end position (in seconds) of clip (trim end of video) - /// Set Max Image Size (used for performance optimization) - void SetMaxSize(int width, int height) { max_width = width; max_height = height; }; - /// Get and Set JSON methods virtual string Json() = 0; ///< Generate JSON string of this object virtual void SetJson(string value) = 0; ///< Load JSON string into this object diff --git a/include/FFmpegReader.h b/include/FFmpegReader.h index 6e88fc7b..eaa45943 100644 --- a/include/FFmpegReader.h +++ b/include/FFmpegReader.h @@ -42,6 +42,7 @@ #include #include #include "CacheMemory.h" +#include "Clip.h" #include "Exceptions.h" #include "OpenMPUtilities.h" #include "Settings.h" diff --git a/include/FFmpegUtilities.h b/include/FFmpegUtilities.h index 0c086585..346da541 100644 --- a/include/FFmpegUtilities.h +++ b/include/FFmpegUtilities.h @@ -141,7 +141,7 @@ #define AV_ALLOCATE_FRAME() av_frame_alloc() #define AV_ALLOCATE_IMAGE(av_frame, pix_fmt, width, height) av_image_alloc(av_frame->data, av_frame->linesize, width, height, pix_fmt, 1) #define AV_RESET_FRAME(av_frame) av_frame_unref(av_frame) - #define AV_FREE_FRAME(av_frame) if (av_frame) av_frame_free(av_frame) + #define AV_FREE_FRAME(av_frame) av_frame_free(av_frame) #define AV_FREE_PACKET(av_packet) av_packet_unref(av_packet) #define AV_FREE_CONTEXT(av_context) avcodec_free_context(&av_context) #define AV_GET_CODEC_TYPE(av_stream) av_stream->codecpar->codec_type @@ -176,7 +176,7 @@ #define AV_ALLOCATE_FRAME() av_frame_alloc() #define AV_ALLOCATE_IMAGE(av_frame, pix_fmt, width, height) av_image_alloc(av_frame->data, av_frame->linesize, width, height, pix_fmt, 1) #define AV_RESET_FRAME(av_frame) av_frame_unref(av_frame) - #define AV_FREE_FRAME(av_frame) if(av_frame) av_frame_free(av_frame) + #define AV_FREE_FRAME(av_frame) av_frame_free(av_frame) #define AV_FREE_PACKET(av_packet) av_packet_unref(av_packet) #define AV_FREE_CONTEXT(av_context) avcodec_free_context(&av_context) #define AV_GET_CODEC_TYPE(av_stream) av_stream->codecpar->codec_type @@ -211,7 +211,7 @@ #define AV_ALLOCATE_FRAME() av_frame_alloc() #define AV_ALLOCATE_IMAGE(av_frame, pix_fmt, width, height) avpicture_alloc((AVPicture *) av_frame, pix_fmt, width, height) #define AV_RESET_FRAME(av_frame) av_frame_unref(av_frame) - #define AV_FREE_FRAME(av_frame) if (av_frame) av_frame_free(av_frame) + #define AV_FREE_FRAME(av_frame) av_frame_free(av_frame) #define AV_FREE_PACKET(av_packet) av_packet_unref(av_packet) #define AV_FREE_CONTEXT(av_context) avcodec_close(av_context) #define AV_GET_CODEC_TYPE(av_stream) av_stream->codec->codec_type @@ -241,7 +241,7 @@ #define AV_ALLOCATE_FRAME() avcodec_alloc_frame() #define AV_ALLOCATE_IMAGE(av_frame, pix_fmt, width, height) avpicture_alloc((AVPicture *) av_frame, pix_fmt, width, height) #define AV_RESET_FRAME(av_frame) avcodec_get_frame_defaults(av_frame) - #define AV_FREE_FRAME(av_frame) if(av_frame) avcodec_free_frame(av_frame) + #define AV_FREE_FRAME(av_frame) avcodec_free_frame(av_frame) #define AV_FREE_PACKET(av_packet) av_free_packet(av_packet) #define AV_FREE_CONTEXT(av_context) avcodec_close(av_context) #define AV_GET_CODEC_TYPE(av_stream) av_stream->codec->codec_type diff --git a/include/QtImageReader.h b/include/QtImageReader.h index 772a879e..6b260f15 100644 --- a/include/QtImageReader.h +++ b/include/QtImageReader.h @@ -28,19 +28,14 @@ #ifndef OPENSHOT_QIMAGE_READER_H #define OPENSHOT_QIMAGE_READER_H -#include "ReaderBase.h" - #include #include #include #include #include #include -#include -#include -#include -#include "CacheMemory.h" #include "Exceptions.h" +#include "ReaderBase.h" using namespace std; @@ -110,9 +105,6 @@ namespace openshot Json::Value JsonValue(); ///< Generate Json::JsonValue for this object void SetJsonValue(Json::Value root); ///< Load Json::JsonValue into this object - /// Set Max Image Size (used for performance optimization) - void SetMaxSize(int width, int height); - /// Open File - which is called by the constructor automatically void Open(); }; diff --git a/include/ReaderBase.h b/include/ReaderBase.h index 2b3ee917..b0a1b3db 100644 --- a/include/ReaderBase.h +++ b/include/ReaderBase.h @@ -35,6 +35,7 @@ #include #include "CacheMemory.h" #include "ChannelLayouts.h" +#include "ClipBase.h" #include "Fraction.h" #include "Frame.h" #include "Json.h" @@ -99,9 +100,7 @@ namespace openshot /// Section lock for multiple threads CriticalSection getFrameCriticalSection; CriticalSection processingCriticalSection; - - int max_width; ///< The maximum image width needed by this clip (used for optimizations) - int max_height; ///< The maximium image height needed by this clip (used for optimizations) + ClipBase* parent; public: @@ -111,6 +110,12 @@ namespace openshot /// Information about the current media file ReaderInfo info; + /// Parent clip object of this reader (which can be unparented and NULL) + ClipBase* GetClip(); + + /// Set parent clip object of this reader + void SetClip(ClipBase* clip); + /// Close the reader (and any resources it was consuming) virtual void Close() = 0; @@ -140,9 +145,6 @@ namespace openshot virtual Json::Value JsonValue() = 0; ///< Generate Json::JsonValue for this object virtual void SetJsonValue(Json::Value root) = 0; ///< Load Json::JsonValue into this object - /// Set Max Image Size (used for performance optimization) - void SetMaxSize(int width, int height) { max_width = width; max_height = height; }; - /// Open the reader (and start consuming resources, such as images or video files) virtual void Open() = 0; }; diff --git a/include/Settings.h b/include/Settings.h index 6a7940eb..ec26338b 100644 --- a/include/Settings.h +++ b/include/Settings.h @@ -85,6 +85,12 @@ namespace openshot { /// Scale mode used in FFmpeg decoding and encoding (used as an optimization for faster previews) bool HIGH_QUALITY_SCALING = false; + /// Maximum width for image data (useful for optimzing for a smaller preview or render) + int MAX_WIDTH = 0; + + /// Maximum height for image data (useful for optimzing for a smaller preview or render) + int MAX_HEIGHT = 0; + /// Wait for OpenMP task to finish before continuing (used to limit threads on slower systems) bool WAIT_FOR_VIDEO_PROCESSING_TASK = false; diff --git a/include/Timeline.h b/include/Timeline.h index ed5c2ab3..312add2e 100644 --- a/include/Timeline.h +++ b/include/Timeline.h @@ -48,6 +48,7 @@ #include "KeyFrame.h" #include "OpenMPUtilities.h" #include "ReaderBase.h" +#include "Settings.h" using namespace std; using namespace openshot; @@ -265,6 +266,10 @@ namespace openshot { Json::Value JsonValue(); ///< Generate Json::JsonValue for this object void SetJsonValue(Json::Value root); ///< Load Json::JsonValue into this object + /// Set Max Image Size (used for performance optimization). Convenience function for setting + /// Settings::Instance()->MAX_WIDTH and Settings::Instance()->MAX_HEIGHT. + void SetMaxSize(int width, int height); + /// @brief Apply a special formatted JSON object, which represents a change to the timeline (add, update, delete) /// This is primarily designed to keep the timeline (and its child objects... such as clips and effects) in sync /// with another application... such as OpenShot Video Editor (http://www.openshot.org). diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7752df15..6f08b81f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -79,11 +79,6 @@ ENDIF (ImageMagick_FOUND) # Find FFmpeg libraries (used for video encoding / decoding) FIND_PACKAGE(FFmpeg REQUIRED) -# Include FFmpeg headers (needed for compile) -message('AVCODEC_FOUND: ${AVCODEC_FOUND}') -message('AVCODEC_INCLUDE_DIRS: ${AVCODEC_INCLUDE_DIRS}') -message('AVCODEC_LIBRARIES: ${AVCODEC_LIBRARIES}') - IF (AVCODEC_FOUND) include_directories(${AVCODEC_INCLUDE_DIRS}) ENDIF (AVCODEC_FOUND) @@ -116,8 +111,6 @@ ENDIF (AVRESAMPLE_FOUND) # Find JUCE-based openshot Audio libraries FIND_PACKAGE(OpenShotAudio REQUIRED) -message('LIBOPENSHOT_AUDIO_INCLUDE_DIRS: ${LIBOPENSHOT_AUDIO_INCLUDE_DIRS}') - # Include Juce headers (needed for compile) include_directories(${LIBOPENSHOT_AUDIO_INCLUDE_DIRS}) @@ -190,13 +183,28 @@ FIND_PACKAGE(ZMQ REQUIRED) # Include ZeroMQ headers (needed for compile) include_directories(${ZMQ_INCLUDE_DIRS}) +################### RESVG ##################### +# Find resvg library (used for rendering svg files) +FIND_PACKAGE(RESVG) + +# Include resvg headers (optional SVG library) +if (RESVG_FOUND) + include_directories(${RESVG_INCLUDE_DIRS}) + + # define a global var (used in the C++) + add_definitions( -DUSE_RESVG=1 ) + SET(CMAKE_SWIG_FLAGS "-DUSE_RESVG=1") +else(RESVG_FOUND) + message("-- Could NOT find libresvg (using Qt SVG parsing instead)") +endif(RESVG_FOUND) + ################### JSONCPP ##################### # Include jsoncpp headers (needed for JSON parsing) if (USE_SYSTEM_JSONCPP) find_package(JsonCpp REQUIRED) include_directories(${JSONCPP_INCLUDE_DIRS}) else() - message("Using embedded JsonCpp") + message("-- Could NOT find JsonCpp library (Using embedded JsonCpp instead)") include_directories("../thirdparty/jsoncpp/include") endif(USE_SYSTEM_JSONCPP) @@ -334,6 +342,10 @@ IF (AVRESAMPLE_FOUND) SET ( REQUIRED_LIBRARIES ${REQUIRED_LIBRARIES} ${AVRESAMPLE_LIBRARIES} ) ENDIF (AVRESAMPLE_FOUND) +IF (RESVG_FOUND) + SET ( REQUIRED_LIBRARIES ${REQUIRED_LIBRARIES} ${RESVG_LIBRARIES} ) +ENDIF(RESVG_FOUND) + IF (OPENMP_FOUND) SET ( REQUIRED_LIBRARIES ${REQUIRED_LIBRARIES} ${OpenMP_CXX_FLAGS} ) ENDIF (OPENMP_FOUND) diff --git a/src/ChunkReader.cpp b/src/ChunkReader.cpp index 8308a0c9..fe552243 100644 --- a/src/ChunkReader.cpp +++ b/src/ChunkReader.cpp @@ -26,6 +26,7 @@ */ #include "../include/ChunkReader.h" +#include "../include/FFmpegReader.h" using namespace openshot; @@ -227,7 +228,6 @@ std::shared_ptr ChunkReader::GetFrame(int64_t requested_frame) cout << "Load READER: " << chunk_video_path << endl; // Load new FFmpegReader local_reader = new FFmpegReader(chunk_video_path); - local_reader->enable_seek = false; // disable seeking local_reader->Open(); // open reader } catch (InvalidFile) diff --git a/src/Clip.cpp b/src/Clip.cpp index 8e33f84c..bd85d340 100644 --- a/src/Clip.cpp +++ b/src/Clip.cpp @@ -26,6 +26,15 @@ */ #include "../include/Clip.h" +#include "../include/FFmpegReader.h" +#include "../include/FrameMapper.h" +#ifdef USE_IMAGEMAGICK + #include "../include/ImageReader.h" + #include "../include/TextReader.h" +#endif +#include "../include/QtImageReader.h" +#include "../include/ChunkReader.h" +#include "../include/DummyReader.h" using namespace openshot; @@ -212,6 +221,9 @@ void Clip::Reader(ReaderBase* new_reader) // set reader pointer reader = new_reader; + // set parent + reader->SetClip(this); + // Init rotation (if any) init_reader_rotation(); } @@ -620,35 +632,6 @@ std::shared_ptr Clip::GetOrCreateFrame(int64_t number) // Debug output ZmqLogger::Instance()->AppendDebugMethod("Clip::GetOrCreateFrame (from reader)", "number", number, "samples_in_frame", samples_in_frame, "", -1, "", -1, "", -1, "", -1); - // Determine the max size of this clips source image (based on the timeline's size, the scaling mode, - // and the scaling keyframes). This is a performance improvement, to keep the images as small as possible, - // without losing quality. NOTE: We cannot go smaller than the timeline itself, or the add_layer timeline - // method will scale it back to timeline size before scaling it smaller again. This needs to be fixed in - // the future. - if (scale == SCALE_FIT || scale == SCALE_STRETCH) { - // Best fit or Stretch scaling (based on max timeline size * scaling keyframes) - float max_scale_x = scale_x.GetMaxPoint().co.Y; - float max_scale_y = scale_y.GetMaxPoint().co.Y; - reader->SetMaxSize(max(float(max_width), max_width * max_scale_x), max(float(max_height), max_height * max_scale_y)); - - } else if (scale == SCALE_CROP) { - // Cropping scale mode (based on max timeline size * cropped size * scaling keyframes) - float max_scale_x = scale_x.GetMaxPoint().co.Y; - float max_scale_y = scale_y.GetMaxPoint().co.Y; - QSize width_size(max_width * max_scale_x, round(max_width / (float(reader->info.width) / float(reader->info.height)))); - QSize height_size(round(max_height / (float(reader->info.height) / float(reader->info.width))), max_height * max_scale_y); - - // respect aspect ratio - if (width_size.width() >= max_width && width_size.height() >= max_height) - reader->SetMaxSize(max(max_width, width_size.width()), max(max_height, width_size.height())); - else - reader->SetMaxSize(max(max_width, height_size.width()), max(max_height, height_size.height())); - - } else { - // No scaling, use original image size (slower) - reader->SetMaxSize(0, 0); - } - // Attempt to get a frame (but this could fail if a reader has just been closed) new_frame = reader->GetFrame(number); @@ -996,9 +979,11 @@ void Clip::SetJsonValue(Json::Value root) { reader->SetJsonValue(root["reader"]); } - // mark as managed reader - if (reader) + // mark as managed reader and set parent + if (reader) { + reader->SetClip(this); manage_reader = true; + } // Re-Open reader (if needed) if (already_open) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 434b1ae4..547fbd99 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -886,9 +886,49 @@ void FFmpegReader::ProcessVideoPacket(int64_t requested_frame) if (pFrameRGB == NULL) throw OutOfBoundsFrame("Convert Image Broke!", current_frame, video_length); - // Determine if video needs to be scaled down (for performance reasons) - // Timelines pass their size to the clips, which pass their size to the readers (as max size) - // If a clip is being scaled larger, it will set max_width and max_height = 0 (which means don't down scale) + // Determine the max size of this source image (based on the timeline's size, the scaling mode, + // and the scaling keyframes). This is a performance improvement, to keep the images as small as possible, + // without losing quality. NOTE: We cannot go smaller than the timeline itself, or the add_layer timeline + // method will scale it back to timeline size before scaling it smaller again. This needs to be fixed in + // the future. + int max_width = Settings::Instance()->MAX_WIDTH; + int max_height = Settings::Instance()->MAX_HEIGHT; + + Clip* parent = (Clip*) GetClip(); + if (parent) { + if (parent->scale == SCALE_FIT || parent->scale == SCALE_STRETCH) { + // Best fit or Stretch scaling (based on max timeline size * scaling keyframes) + float max_scale_x = parent->scale_x.GetMaxPoint().co.Y; + float max_scale_y = parent->scale_y.GetMaxPoint().co.Y; + max_width = max(float(max_width), max_width * max_scale_x); + max_height = max(float(max_height), max_height * max_scale_y); + + } else if (parent->scale == SCALE_CROP) { + // Cropping scale mode (based on max timeline size * cropped size * scaling keyframes) + float max_scale_x = parent->scale_x.GetMaxPoint().co.Y; + float max_scale_y = parent->scale_y.GetMaxPoint().co.Y; + QSize width_size(max_width * max_scale_x, + round(max_width / (float(info.width) / float(info.height)))); + QSize height_size(round(max_height / (float(info.height) / float(info.width))), + max_height * max_scale_y); + // respect aspect ratio + if (width_size.width() >= max_width && width_size.height() >= max_height) { + max_width = max(max_width, width_size.width()); + max_height = max(max_height, width_size.height()); + } + else { + max_width = max(max_width, height_size.width()); + max_height = max(max_height, height_size.height()); + } + + } else { + // No scaling, use original image size (slower) + max_width = info.width; + max_height = info.height; + } + } + + // Determine if image needs to be scaled (for performance reasons) int original_height = height; if (max_width != 0 && max_height != 0 && max_width < width && max_height < height) { // Override width and height (but maintain aspect ratio) @@ -1973,8 +2013,13 @@ void FFmpegReader::RemoveAVFrame(AVFrame* remove_frame) if (remove_frame) { // Free memory - av_freep(&remove_frame->data[0]); - AV_FREE_FRAME(&remove_frame); + #pragma omp critical (packet_cache) + { + av_freep(&remove_frame->data[0]); +#ifndef WIN32 + AV_FREE_FRAME(&remove_frame); +#endif + } } } diff --git a/src/FrameMapper.cpp b/src/FrameMapper.cpp index 1817c049..2a5d4276 100644 --- a/src/FrameMapper.cpp +++ b/src/FrameMapper.cpp @@ -352,9 +352,6 @@ std::shared_ptr FrameMapper::GetOrCreateFrame(int64_t number) // Debug output ZmqLogger::Instance()->AppendDebugMethod("FrameMapper::GetOrCreateFrame (from reader)", "number", number, "samples_in_frame", samples_in_frame, "", -1, "", -1, "", -1, "", -1); - // Set max image size (used for performance optimization) - reader->SetMaxSize(max_width, max_height); - // Attempt to get a frame (but this could fail if a reader has just been closed) new_frame = reader->GetFrame(number); diff --git a/src/QtImageReader.cpp b/src/QtImageReader.cpp index 764ef6ed..80a8237d 100644 --- a/src/QtImageReader.cpp +++ b/src/QtImageReader.cpp @@ -26,6 +26,18 @@ */ #include "../include/QtImageReader.h" +#include "../include/Settings.h" +#include "../include/Clip.h" +#include "../include/CacheMemory.h" +#include +#include +#include + +#if USE_RESVG == 1 + // If defined and found in CMake, utilize the libresvg for parsing + // SVG files and rasterizing them to QImages. + #include "ResvgQt.h" +#endif using namespace openshot; @@ -51,17 +63,46 @@ void QtImageReader::Open() // Open reader if not already open if (!is_open) { - // Attempt to open file + bool success = true; image = std::shared_ptr(new QImage()); - bool success = image->load(QString::fromStdString(path)); + +#if USE_RESVG == 1 + // If defined and found in CMake, utilize the libresvg for parsing + // SVG files and rasterizing them to QImages. + // Only use resvg for files ending in '.svg' or '.svgz' + if (path.find(".svg") != std::string::npos || + path.find(".svgz") != std::string::npos) { + + ResvgRenderer renderer(QString::fromStdString(path)); + if (!renderer.isValid()) { + success = false; + } else { + + image = std::shared_ptr(new QImage(renderer.defaultSize(), QImage::Format_RGBA8888)); + image->fill(Qt::transparent); + + QPainter p(image.get()); + renderer.render(&p); + p.end(); + } + + } else { + // Attempt to open file (old method) + success = image->load(QString::fromStdString(path)); + if (success) + image = std::shared_ptr(new QImage(image->convertToFormat(QImage::Format_RGBA8888))); + } +#else + // Attempt to open file using Qt's build in image processing capabilities + success = image->load(QString::fromStdString(path)); + if (success) + image = std::shared_ptr(new QImage(image->convertToFormat(QImage::Format_RGBA8888))); +#endif if (!success) // raise exception throw InvalidFile("File could not be opened.", path); - // Set pixel format - image = std::shared_ptr(new QImage(image->convertToFormat(QImage::Format_RGBA8888))); - // Update image properties info.has_audio = false; info.has_video = true; @@ -111,21 +152,6 @@ void QtImageReader::Close() } } -void QtImageReader::SetMaxSize(int width, int height) -{ - // Determine if we need to scale the image (for performance reasons) - // The timeline passes its size to the clips, which pass their size to the readers, and eventually here - // A max_width/max_height = 0 means do not scale (probably because we are scaling the image larger than 100%) - - // Remove cache that is no longer valid (if needed) - if (cached_image && (cached_image->width() != width && cached_image->height() != height)) - // Expire this cache - cached_image.reset(); - - max_width = width; - max_height = height; -} - // Get an openshot::Frame object for a specific frame number of this reader. std::shared_ptr QtImageReader::GetFrame(int64_t requested_frame) { @@ -133,39 +159,92 @@ std::shared_ptr QtImageReader::GetFrame(int64_t requested_frame) if (!is_open) throw ReaderClosed("The Image is closed. Call Open() before calling this method.", path); - if (max_width != 0 && max_height != 0 && max_width < info.width && max_height < info.height) - { - // Scale image smaller (or use a previous scaled image) - if (!cached_image) { - // Create a scoped lock, allowing only a single thread to run the following code at one time - const GenericScopedLock lock(getFrameCriticalSection); + // Create a scoped lock, allowing only a single thread to run the following code at one time + const GenericScopedLock lock(getFrameCriticalSection); + // Determine the max size of this source image (based on the timeline's size, the scaling mode, + // and the scaling keyframes). This is a performance improvement, to keep the images as small as possible, + // without losing quality. NOTE: We cannot go smaller than the timeline itself, or the add_layer timeline + // method will scale it back to timeline size before scaling it smaller again. This needs to be fixed in + // the future. + int max_width = Settings::Instance()->MAX_WIDTH; + int max_height = Settings::Instance()->MAX_HEIGHT; + + Clip* parent = (Clip*) GetClip(); + if (parent) { + if (parent->scale == SCALE_FIT || parent->scale == SCALE_STRETCH) { + // Best fit or Stretch scaling (based on max timeline size * scaling keyframes) + float max_scale_x = parent->scale_x.GetMaxPoint().co.Y; + float max_scale_y = parent->scale_y.GetMaxPoint().co.Y; + max_width = max(float(max_width), max_width * max_scale_x); + max_height = max(float(max_height), max_height * max_scale_y); + + } else if (parent->scale == SCALE_CROP) { + // Cropping scale mode (based on max timeline size * cropped size * scaling keyframes) + float max_scale_x = parent->scale_x.GetMaxPoint().co.Y; + float max_scale_y = parent->scale_y.GetMaxPoint().co.Y; + QSize width_size(max_width * max_scale_x, + round(max_width / (float(info.width) / float(info.height)))); + QSize height_size(round(max_height / (float(info.height) / float(info.width))), + max_height * max_scale_y); + // respect aspect ratio + if (width_size.width() >= max_width && width_size.height() >= max_height) { + max_width = max(max_width, width_size.width()); + max_height = max(max_height, width_size.height()); + } + else { + max_width = max(max_width, height_size.width()); + max_height = max(max_height, height_size.height()); + } + + } else { + // No scaling, use original image size (slower) + max_width = info.width; + max_height = info.height; + } + } + + // Scale image smaller (or use a previous scaled image) + if (!cached_image || (cached_image && cached_image->width() != max_width || cached_image->height() != max_height)) { + +#if USE_RESVG == 1 + // If defined and found in CMake, utilize the libresvg for parsing + // SVG files and rasterizing them to QImages. + // Only use resvg for files ending in '.svg' or '.svgz' + if (path.find(".svg") != std::string::npos || + path.find(".svgz") != std::string::npos) { + ResvgRenderer renderer(QString::fromStdString(path)); + if (renderer.isValid()) { + + cached_image = std::shared_ptr(new QImage(QSize(max_width, max_height), QImage::Format_RGBA8888)); + cached_image->fill(Qt::transparent); + + QPainter p(cached_image.get()); + renderer.render(&p); + p.end(); + } + } else { // We need to resize the original image to a smaller image (for performance reasons) // Only do this once, to prevent tons of unneeded scaling operations cached_image = std::shared_ptr(new QImage(image->scaled(max_width, max_height, Qt::KeepAspectRatio, Qt::SmoothTransformation))); cached_image = std::shared_ptr(new QImage(cached_image->convertToFormat(QImage::Format_RGBA8888))); } - - // Create or get frame object - std::shared_ptr image_frame(new Frame(requested_frame, cached_image->width(), cached_image->height(), "#000000", Frame::GetSamplesPerFrame(requested_frame, info.fps, info.sample_rate, info.channels), info.channels)); - - // Add Image data to frame - image_frame->AddImage(cached_image); - - // return frame object - return image_frame; - - } else { - // Use original image (higher quality but slower) - // Create or get frame object - std::shared_ptr image_frame(new Frame(requested_frame, info.width, info.height, "#000000", Frame::GetSamplesPerFrame(requested_frame, info.fps, info.sample_rate, info.channels), info.channels)); - - // Add Image data to frame - image_frame->AddImage(image); - - // return frame object - return image_frame; +#else + // We need to resize the original image to a smaller image (for performance reasons) + // Only do this once, to prevent tons of unneeded scaling operations + cached_image = std::shared_ptr(new QImage(image->scaled(max_width, max_height, Qt::KeepAspectRatio, Qt::SmoothTransformation))); + cached_image = std::shared_ptr(new QImage(cached_image->convertToFormat(QImage::Format_RGBA8888))); +#endif } + + // Create or get frame object + std::shared_ptr image_frame(new Frame(requested_frame, cached_image->width(), cached_image->height(), "#000000", Frame::GetSamplesPerFrame(requested_frame, info.fps, info.sample_rate, info.channels), info.channels)); + + // Add Image data to frame + image_frame->AddImage(cached_image); + + // return frame object + return image_frame; } // Generate JSON string of this object diff --git a/src/ReaderBase.cpp b/src/ReaderBase.cpp index 5de6fdff..f2607cfd 100644 --- a/src/ReaderBase.cpp +++ b/src/ReaderBase.cpp @@ -58,8 +58,9 @@ ReaderBase::ReaderBase() info.channel_layout = LAYOUT_MONO; info.audio_stream_index = -1; info.audio_timebase = Fraction(); - max_width = 0; - max_height = 0; + + // Init parent clip + parent = NULL; } // Display file information @@ -246,3 +247,13 @@ void ReaderBase::SetJsonValue(Json::Value root) { } } } + +/// Parent clip object of this reader (which can be unparented and NULL) +ClipBase* ReaderBase::GetClip() { + return parent; +} + +/// Set parent clip object of this reader +void ReaderBase::SetClip(ClipBase* clip) { + parent = clip; +} diff --git a/src/Settings.cpp b/src/Settings.cpp index e6749d5c..b13f0f5a 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -43,6 +43,8 @@ Settings *Settings::Instance() m_pInstance->HARDWARE_DECODE = false; m_pInstance->HARDWARE_ENCODE = false; m_pInstance->HIGH_QUALITY_SCALING = false; + m_pInstance->MAX_WIDTH = 0; + m_pInstance->MAX_HEIGHT = 0; m_pInstance->WAIT_FOR_VIDEO_PROCESSING_TASK = false; } diff --git a/src/Timeline.cpp b/src/Timeline.cpp index d97b13e4..28c8956a 100644 --- a/src/Timeline.cpp +++ b/src/Timeline.cpp @@ -60,7 +60,7 @@ Timeline::Timeline(int width, int height, Fraction fps, int sample_rate, int cha info.video_length = info.fps.ToFloat() * info.duration; // Init max image size - SetMaxSize(info.width, info.height); + SetMaxSize(info.width, info.height); // Init cache final_cache = new CacheMemory(); @@ -213,9 +213,6 @@ std::shared_ptr Timeline::GetOrCreateFrame(Clip* clip, int64_t number) // Debug output ZmqLogger::Instance()->AppendDebugMethod("Timeline::GetOrCreateFrame (from reader)", "number", number, "samples_in_frame", samples_in_frame, "", -1, "", -1, "", -1, "", -1); - // Set max image size (used for performance optimization) - clip->SetMaxSize(info.width, info.height); - // Attempt to get a frame (but this could fail if a reader has just been closed) #pragma omp critical (T_GetOtCreateFrame) new_frame = std::shared_ptr(clip->GetFrame(number)); @@ -235,7 +232,7 @@ std::shared_ptr Timeline::GetOrCreateFrame(Clip* clip, int64_t number) ZmqLogger::Instance()->AppendDebugMethod("Timeline::GetOrCreateFrame (create blank)", "number", number, "samples_in_frame", samples_in_frame, "", -1, "", -1, "", -1, "", -1); // Create blank frame - new_frame = std::make_shared(number, max_width, max_height, "#000000", samples_in_frame, info.channels); + new_frame = std::make_shared(number, Settings::Instance()->MAX_WIDTH, Settings::Instance()->MAX_HEIGHT, "#000000", samples_in_frame, info.channels); #pragma omp critical (T_GetOtCreateFrame) { new_frame->SampleRate(info.sample_rate); @@ -274,7 +271,7 @@ void Timeline::add_layer(std::shared_ptr new_frame, Clip* source_clip, in // Generate Waveform Dynamically (the size of the timeline) std::shared_ptr source_image; #pragma omp critical (T_addLayer) - source_image = source_frame->GetWaveform(max_width, max_height, red, green, blue, alpha); + source_image = source_frame->GetWaveform(Settings::Instance()->MAX_WIDTH, Settings::Instance()->MAX_HEIGHT, red, green, blue, alpha); source_frame->AddImage(std::shared_ptr(source_image)); } @@ -389,7 +386,7 @@ void Timeline::add_layer(std::shared_ptr new_frame, Clip* source_clip, in { case (SCALE_FIT): { // keep aspect ratio - source_size.scale(max_width, max_height, Qt::KeepAspectRatio); + source_size.scale(Settings::Instance()->MAX_WIDTH, Settings::Instance()->MAX_HEIGHT, Qt::KeepAspectRatio); // Debug output ZmqLogger::Instance()->AppendDebugMethod("Timeline::add_layer (Scale: SCALE_FIT)", "source_frame->number", source_frame->number, "source_width", source_size.width(), "source_height", source_size.height(), "", -1, "", -1, "", -1); @@ -397,18 +394,18 @@ void Timeline::add_layer(std::shared_ptr new_frame, Clip* source_clip, in } case (SCALE_STRETCH): { // ignore aspect ratio - source_size.scale(max_width, max_height, Qt::IgnoreAspectRatio); + source_size.scale(Settings::Instance()->MAX_WIDTH, Settings::Instance()->MAX_HEIGHT, Qt::IgnoreAspectRatio); // Debug output ZmqLogger::Instance()->AppendDebugMethod("Timeline::add_layer (Scale: SCALE_STRETCH)", "source_frame->number", source_frame->number, "source_width", source_size.width(), "source_height", source_size.height(), "", -1, "", -1, "", -1); break; } case (SCALE_CROP): { - QSize width_size(max_width, round(max_width / (float(source_size.width()) / float(source_size.height())))); - QSize height_size(round(max_height / (float(source_size.height()) / float(source_size.width()))), max_height); + QSize width_size(Settings::Instance()->MAX_WIDTH, round(Settings::Instance()->MAX_WIDTH / (float(source_size.width()) / float(source_size.height())))); + QSize height_size(round(Settings::Instance()->MAX_HEIGHT / (float(source_size.height()) / float(source_size.width()))), Settings::Instance()->MAX_HEIGHT); // respect aspect ratio - if (width_size.width() >= max_width && width_size.height() >= max_height) + if (width_size.width() >= Settings::Instance()->MAX_WIDTH && width_size.height() >= Settings::Instance()->MAX_HEIGHT) source_size.scale(width_size.width(), width_size.height(), Qt::KeepAspectRatio); else source_size.scale(height_size.width(), height_size.height(), Qt::KeepAspectRatio); @@ -423,7 +420,7 @@ void Timeline::add_layer(std::shared_ptr new_frame, Clip* source_clip, in // (otherwise NONE scaling draws the frame image outside of the preview) float source_width_ratio = source_size.width() / float(info.width); float source_height_ratio = source_size.height() / float(info.height); - source_size.scale(max_width * source_width_ratio, max_height * source_height_ratio, Qt::KeepAspectRatio); + source_size.scale(Settings::Instance()->MAX_WIDTH * source_width_ratio, Settings::Instance()->MAX_HEIGHT * source_height_ratio, Qt::KeepAspectRatio); // Debug output ZmqLogger::Instance()->AppendDebugMethod("Timeline::add_layer (Scale: SCALE_NONE)", "source_frame->number", source_frame->number, "source_width", source_size.width(), "source_height", source_size.height(), "", -1, "", -1, "", -1); @@ -444,32 +441,32 @@ void Timeline::add_layer(std::shared_ptr new_frame, Clip* source_clip, in switch (source_clip->gravity) { case (GRAVITY_TOP): - x = (max_width - scaled_source_width) / 2.0; // center + x = (Settings::Instance()->MAX_WIDTH - scaled_source_width) / 2.0; // center break; case (GRAVITY_TOP_RIGHT): - x = max_width - scaled_source_width; // right + x = Settings::Instance()->MAX_WIDTH - scaled_source_width; // right break; case (GRAVITY_LEFT): - y = (max_height - scaled_source_height) / 2.0; // center + y = (Settings::Instance()->MAX_HEIGHT - scaled_source_height) / 2.0; // center break; case (GRAVITY_CENTER): - x = (max_width - scaled_source_width) / 2.0; // center - y = (max_height - scaled_source_height) / 2.0; // center + x = (Settings::Instance()->MAX_WIDTH - scaled_source_width) / 2.0; // center + y = (Settings::Instance()->MAX_HEIGHT - scaled_source_height) / 2.0; // center break; case (GRAVITY_RIGHT): - x = max_width - scaled_source_width; // right - y = (max_height - scaled_source_height) / 2.0; // center + x = Settings::Instance()->MAX_WIDTH - scaled_source_width; // right + y = (Settings::Instance()->MAX_HEIGHT - scaled_source_height) / 2.0; // center break; case (GRAVITY_BOTTOM_LEFT): - y = (max_height - scaled_source_height); // bottom + y = (Settings::Instance()->MAX_HEIGHT - scaled_source_height); // bottom break; case (GRAVITY_BOTTOM): - x = (max_width - scaled_source_width) / 2.0; // center - y = (max_height - scaled_source_height); // bottom + x = (Settings::Instance()->MAX_WIDTH - scaled_source_width) / 2.0; // center + y = (Settings::Instance()->MAX_HEIGHT - scaled_source_height); // bottom break; case (GRAVITY_BOTTOM_RIGHT): - x = max_width - scaled_source_width; // right - y = (max_height - scaled_source_height); // bottom + x = Settings::Instance()->MAX_WIDTH - scaled_source_width; // right + y = (Settings::Instance()->MAX_HEIGHT - scaled_source_height); // bottom break; } @@ -478,8 +475,8 @@ void Timeline::add_layer(std::shared_ptr new_frame, Clip* source_clip, in /* LOCATION, ROTATION, AND SCALE */ float r = source_clip->rotation.GetValue(clip_frame_number); // rotate in degrees - x += (max_width * source_clip->location_x.GetValue(clip_frame_number)); // move in percentage of final width - y += (max_height * source_clip->location_y.GetValue(clip_frame_number)); // move in percentage of final height + x += (Settings::Instance()->MAX_WIDTH * source_clip->location_x.GetValue(clip_frame_number)); // move in percentage of final width + y += (Settings::Instance()->MAX_HEIGHT * source_clip->location_y.GetValue(clip_frame_number)); // move in percentage of final height float shear_x = source_clip->shear_x.GetValue(clip_frame_number); float shear_y = source_clip->shear_y.GetValue(clip_frame_number); @@ -746,7 +743,7 @@ std::shared_ptr Timeline::GetFrame(int64_t requested_frame) int samples_in_frame = Frame::GetSamplesPerFrame(frame_number, info.fps, info.sample_rate, info.channels); // Create blank frame (which will become the requested frame) - std::shared_ptr new_frame(std::make_shared(frame_number, max_width, max_height, "#000000", samples_in_frame, info.channels)); + std::shared_ptr new_frame(std::make_shared(frame_number, Settings::Instance()->MAX_WIDTH, Settings::Instance()->MAX_HEIGHT, "#000000", samples_in_frame, info.channels)); #pragma omp critical (T_GetFrame) { new_frame->AddAudioSilence(samples_in_frame); @@ -760,7 +757,7 @@ std::shared_ptr Timeline::GetFrame(int64_t requested_frame) // Add Background Color to 1st layer (if animated or not black) if ((color.red.Points.size() > 1 || color.green.Points.size() > 1 || color.blue.Points.size() > 1) || (color.red.GetValue(frame_number) != 0.0 || color.green.GetValue(frame_number) != 0.0 || color.blue.GetValue(frame_number) != 0.0)) - new_frame->AddColor(max_width, max_height, color.GetColorHex(frame_number)); + new_frame->AddColor(Settings::Instance()->MAX_WIDTH, Settings::Instance()->MAX_HEIGHT, color.GetColorHex(frame_number)); // Debug output ZmqLogger::Instance()->AppendDebugMethod("Timeline::GetFrame (Loop through clips)", "frame_number", frame_number, "clips.size()", clips.size(), "nearby_clips.size()", nearby_clips.size(), "", -1, "", -1, "", -1); @@ -1195,17 +1192,6 @@ void Timeline::apply_json_to_clips(Json::Value change) { // Apply framemapper (or update existing framemapper) apply_mapper_to_clip(existing_clip); - - // Clear any cached image sizes (since size might have changed) - existing_clip->SetMaxSize(0, 0); // force clearing of cached image size - if (existing_clip->Reader()) { - existing_clip->Reader()->SetMaxSize(0, 0); - if (existing_clip->Reader()->Name() == "FrameMapper") { - FrameMapper *nested_reader = (FrameMapper *) existing_clip->Reader(); - if (nested_reader->Reader()) - nested_reader->Reader()->SetMaxSize(0, 0); - } - } } } else if (change_type == "delete") { @@ -1452,3 +1438,11 @@ void Timeline::ClearAllCache() { } } + +// Set Max Image Size (used for performance optimization). Convenience function for setting +// Settings::Instance()->MAX_WIDTH and Settings::Instance()->MAX_HEIGHT. +void Timeline::SetMaxSize(int width, int height) { + // Init max image size + Settings::Instance()->MAX_WIDTH = width; + Settings::Instance()->MAX_HEIGHT = height; +} \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index bb2f8c2a..ed6b25f3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -179,13 +179,21 @@ FIND_PACKAGE(ZMQ REQUIRED) # Include ZeroMQ headers (needed for compile) include_directories(${ZMQ_INCLUDE_DIRS}) +################### RESVG ##################### +# Find resvg library (used for rendering svg files) +FIND_PACKAGE(RESVG REQUIRED) + +# Include resvg headers (optional SVG library) +if (RESVG_FOUND) + include_directories(${RESVG_INCLUDE_DIRS}) +endif(RESVG_FOUND) + ################### JSONCPP ##################### # Include jsoncpp headers (needed for JSON parsing) if (USE_SYSTEM_JSONCPP) find_package(JsonCpp REQUIRED) include_directories(${JSONCPP_INCLUDE_DIRS}) else() - message("Using embedded JsonCpp") include_directories("../thirdparty/jsoncpp/include") endif(USE_SYSTEM_JSONCPP) From f009b0f46c7c7b41e95a4934cc12f5d2b6a0bed0 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Tue, 22 Jan 2019 22:14:30 -0600 Subject: [PATCH 078/223] Fix default sizes on readers without MAX_WIDTH and MAX_HEIGHT settings (#188) --- src/FFmpegReader.cpp | 4 ++++ src/QtImageReader.cpp | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 547fbd99..f6c69a8a 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -892,7 +892,11 @@ void FFmpegReader::ProcessVideoPacket(int64_t requested_frame) // method will scale it back to timeline size before scaling it smaller again. This needs to be fixed in // the future. int max_width = Settings::Instance()->MAX_WIDTH; + if (max_width <= 0) + max_width = info.width; int max_height = Settings::Instance()->MAX_HEIGHT; + if (max_height <= 0) + max_height = info.height; Clip* parent = (Clip*) GetClip(); if (parent) { diff --git a/src/QtImageReader.cpp b/src/QtImageReader.cpp index 80a8237d..c500d221 100644 --- a/src/QtImageReader.cpp +++ b/src/QtImageReader.cpp @@ -168,7 +168,11 @@ std::shared_ptr QtImageReader::GetFrame(int64_t requested_frame) // method will scale it back to timeline size before scaling it smaller again. This needs to be fixed in // the future. int max_width = Settings::Instance()->MAX_WIDTH; + if (max_width <= 0) + max_width = info.width; int max_height = Settings::Instance()->MAX_HEIGHT; + if (max_height <= 0) + max_height = info.height; Clip* parent = (Clip*) GetClip(); if (parent) { From c4321f3f8e4cc9f31735f2e62b922b98567184f8 Mon Sep 17 00:00:00 2001 From: Frank Dana Date: Thu, 24 Jan 2019 14:43:40 -0500 Subject: [PATCH 079/223] Some polishing for the cmake ReSVG discovery (#187) * Search for libresvg.so in RESVGDIR also This means that RESVGDIR can be pointed to the `target/release` dir where resvg was built, and both `libresvg.so` and `include/resvg.h` will be found. * ReSVG: Fix up discovery module `find_package_handle_standard_args` is supposed to set the `_FOUND` variable automatically (as the comment right above it says), as well as handling things like REQUIRED, QUIETLY, etc. It should always be run at the end of a module, for this reason. This change removes the conditionals around the call, lets it handle what it's meant to handle, and defines a custom failure message for discovery that replaces the one in `src/CMakeList.txt`. In addition, the `REQRUIRED` is removed from `tests/CMakeLists.txt`, since it's _supposed_ to mark the module as required (which ReSVG is not), and was only working due to the aforementioned improper conditional wrapping of the module's cleanup. * FindRESVG.cmake does not set RESVG_DEFINITIONS Remove the comment that claims it does. --- cmake/Modules/FindRESVG.cmake | 18 ++++++------------ src/CMakeLists.txt | 2 -- tests/CMakeLists.txt | 2 +- 3 files changed, 7 insertions(+), 15 deletions(-) diff --git a/cmake/Modules/FindRESVG.cmake b/cmake/Modules/FindRESVG.cmake index 4a201747..b03a0667 100644 --- a/cmake/Modules/FindRESVG.cmake +++ b/cmake/Modules/FindRESVG.cmake @@ -3,7 +3,6 @@ # RESVG_FOUND - System has RESVG # RESVG_INCLUDE_DIRS - The RESVG include directories # RESVG_LIBRARIES - The libraries needed to use RESVG -# RESVG_DEFINITIONS - Compiler switches required for using RESVG find_path ( RESVG_INCLUDE_DIR ResvgQt.h PATHS ${RESVGDIR}/include/resvg $ENV{RESVGDIR}/include/resvg @@ -16,19 +15,14 @@ find_path ( RESVG_INCLUDE_DIR ResvgQt.h find_library ( RESVG_LIBRARY NAMES resvg PATHS /usr/lib /usr/local/lib + $ENV{RESVGDIR} $ENV{RESVGDIR}/lib ) set ( RESVG_LIBRARIES ${RESVG_LIBRARY} ) set ( RESVG_INCLUDE_DIRS ${RESVG_INCLUDE_DIR} ) -SET( RESVG_FOUND FALSE ) - -IF ( RESVG_INCLUDE_DIR AND RESVG_LIBRARY ) - SET ( RESVG_FOUND TRUE ) - - include ( FindPackageHandleStandardArgs ) - # handle the QUIETLY and REQUIRED arguments and set RESVG_FOUND to TRUE - # if all listed variables are TRUE - find_package_handle_standard_args ( RESVG DEFAULT_MSG RESVG_LIBRARY RESVG_INCLUDE_DIR ) -ENDIF ( RESVG_INCLUDE_DIR AND RESVG_LIBRARY ) - +include ( FindPackageHandleStandardArgs ) +# handle the QUIETLY and REQUIRED arguments and set RESVG_FOUND to TRUE +# if all listed variables are TRUE +find_package_handle_standard_args ( RESVG "Could NOT find RESVG, using Qt SVG parsing instead" RESVG_LIBRARY RESVG_INCLUDE_DIR ) +mark_as_advanced( RESVG_LIBRARY RESVG_INCLUDE_DIR ) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6f08b81f..71541432 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -194,8 +194,6 @@ if (RESVG_FOUND) # define a global var (used in the C++) add_definitions( -DUSE_RESVG=1 ) SET(CMAKE_SWIG_FLAGS "-DUSE_RESVG=1") -else(RESVG_FOUND) - message("-- Could NOT find libresvg (using Qt SVG parsing instead)") endif(RESVG_FOUND) ################### JSONCPP ##################### diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ed6b25f3..f2ae9377 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -181,7 +181,7 @@ include_directories(${ZMQ_INCLUDE_DIRS}) ################### RESVG ##################### # Find resvg library (used for rendering svg files) -FIND_PACKAGE(RESVG REQUIRED) +FIND_PACKAGE(RESVG) # Include resvg headers (optional SVG library) if (RESVG_FOUND) From 0587ada6dc444270605f005dcb07c9799fc1f032 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Fri, 25 Jan 2019 00:15:07 -0600 Subject: [PATCH 080/223] Integrate Constant Rate Factor (CRF) for FFmpegWriter (#186) * First implementation of CRF (Constant Rate Factor) * Include AV1 in the codecs that can use crf * First version that uses SetOption to set crf quality * Clarify comments * Delete setting the bitrate for crf in SetVideoOption because it is not needed, it is set later with SetOptions. --- src/FFmpegWriter.cpp | 42 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index 56f98354..d8f07c83 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -146,7 +146,7 @@ void FFmpegWriter::SetVideoOptions(bool has_video, string codec, Fraction fps, i info.pixel_ratio.num = pixel_ratio.num; info.pixel_ratio.den = pixel_ratio.den; } - if (bit_rate >= 1000) + if (bit_rate >= 1000) // bit_rate is the bitrate in b/s info.video_bit_rate = bit_rate; info.interlaced_frame = interlaced; @@ -237,7 +237,8 @@ void FFmpegWriter::SetOption(StreamType stream, string name, string value) // Was option found? if (option || (name == "g" || name == "qmin" || name == "qmax" || name == "max_b_frames" || name == "mb_decision" || - name == "level" || name == "profile" || name == "slices" || name == "rc_min_rate" || name == "rc_max_rate")) + name == "level" || name == "profile" || name == "slices" || name == "rc_min_rate" || name == "rc_max_rate" || + name == "crf")) { // Check for specific named options if (name == "g") @@ -284,6 +285,39 @@ void FFmpegWriter::SetOption(StreamType stream, string name, string value) // Buffer size convert >> c->rc_buffer_size; + else if (name == "crf") { + // encode quality and special settings like lossless + // This might be better in an extra methods as more options + // and way to set quality are possible + #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 39, 101) + switch (c->codec_id) { + case AV_CODEC_ID_VP8 : + av_opt_set_int(c->priv_data, "crf", min(stoi(value),63), 0); + break; + case AV_CODEC_ID_VP9 : + av_opt_set_int(c->priv_data, "crf", min(stoi(value),63), 0); + if (stoi(value) == 0) { + av_opt_set_int(c->priv_data, "lossless", 1, 0); + } + break; + case AV_CODEC_ID_H264 : + av_opt_set_int(c->priv_data, "crf", min(stoi(value),51), 0); + break; + case AV_CODEC_ID_H265 : + av_opt_set_int(c->priv_data, "crf", min(stoi(value),51), 0); + if (stoi(value) == 0) { + av_opt_set_int(c->priv_data, "lossless", 1, 0); + } + break; + #ifdef AV_CODEC_ID_AV1 + case AV_CODEC_ID_AV1 : + av_opt_set_int(c->priv_data, "crf", min(stoi(value),63), 0); + break; + #endif + } + #endif + } + else // Set AVOption AV_OPTION_SET(st, c->priv_data, name.c_str(), value.c_str(), c); @@ -934,7 +968,9 @@ AVStream* FFmpegWriter::add_video_stream() #endif /* Init video encoder options */ - c->bit_rate = info.video_bit_rate; + if (info.video_bit_rate > 1000) { + c->bit_rate = info.video_bit_rate; + } //TODO: Implement variable bitrate feature (which actually works). This implementation throws //invalid bitrate errors and rc buffer underflow errors, etc... From f2db5fdb39bf1de2511857551a9c6d7916ede102 Mon Sep 17 00:00:00 2001 From: "FeRD (Frank Dana)" Date: Sat, 26 Jan 2019 11:50:21 -0500 Subject: [PATCH 081/223] FFmpegUtilities: Rename RSHIFT to FF_RSHIFT FFmpeg and Ruby have competing definitions of the RSHIFT macro, so building the Ruby bindings causes warnings to be thrown when it's redefined. This change defines FF_RSHIFT to replace FFmpeg's RSHIFT, which is undef'd --- include/FFmpegUtilities.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/FFmpegUtilities.h b/include/FFmpegUtilities.h index 346da541..e16466e3 100644 --- a/include/FFmpegUtilities.h +++ b/include/FFmpegUtilities.h @@ -114,6 +114,13 @@ #define PIX_FMT_YUV420P AV_PIX_FMT_YUV420P #endif + // FFmpeg's libavutil/common.h defines an RSHIFT incompatible with Ruby's + // definition in ruby/config.h, so we move it to FF_RSHIFT + #ifdef RSHIFT + #define FF_RSHIFT(a, b) RSHIFT(a, b) + #undef RSHIFT + #endif + #ifdef USE_SW #define SWR_CONVERT(ctx, out, linesize, out_count, in, linesize2, in_count) \ swr_convert(ctx, out, out_count, (const uint8_t **)in, in_count) From bb8efeb72b55d093349d950f533f32b1243d0852 Mon Sep 17 00:00:00 2001 From: "FeRD (Frank Dana)" Date: Sat, 26 Jan 2019 11:53:08 -0500 Subject: [PATCH 082/223] Ruby: Rename RSHIFT to RB_RSHIFT, temporarily When Ruby attempts to load the FFmpeg header files, it'll complain that RSHIFT is redefined if the Ruby definition is still in place. So, we define RB_RSHIFT to contain the Ruby definition, undef RSHIFT, load the FFmpeg headers, move its RSHIFT into FF_RSHIFT if necessary, and then restore Ruby's RSHIFT from RB_RSHIFT. --- src/bindings/ruby/openshot.i | 39 +++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/bindings/ruby/openshot.i b/src/bindings/ruby/openshot.i index b9a35d41..82925b71 100644 --- a/src/bindings/ruby/openshot.i +++ b/src/bindings/ruby/openshot.i @@ -57,6 +57,14 @@ namespace std { %{ +/* Ruby and FFmpeg define competing RSHIFT macros, + * so we move Ruby's out of the way for now. We'll + * restore it after dealing with FFmpeg's + */ +#ifdef RSHIFT + #define RB_RSHIFT(a, b) RSHIFT(a, b) + #undef RSHIFT +#endif #include "../../../include/Version.h" #include "../../../include/ReaderBase.h" #include "../../../include/WriterBase.h" @@ -91,7 +99,15 @@ namespace std { #include "../../../include/Settings.h" #include "../../../include/Timeline.h" #include "../../../include/ZmqLogger.h" - +/* Move FFmpeg's RSHIFT to FF_RSHIFT, if present */ +#ifdef RSHIFT + #define FF_RSHIFT(a, b) RSHIFT(a, b) + #undef RSHIFT +#endif +/* And restore Ruby's RSHIFT, if we captured it */ +#ifdef RB_RSHIFT + #define RSHIFT(a, b) RB_RSHIFT(a, b) +#endif %} #ifdef USE_BLACKMAGIC @@ -132,8 +148,29 @@ namespace std { %include "../../../include/EffectInfo.h" %include "../../../include/Enums.h" %include "../../../include/Exceptions.h" + +/* Ruby and FFmpeg define competing RSHIFT macros, + * so we move Ruby's out of the way for now. We'll + * restore it after dealing with FFmpeg's + */ +#ifdef RSHIFT + #define RB_RSHIFT(a, b) RSHIFT(a, b) + #undef RSHIFT +#endif + %include "../../../include/FFmpegReader.h" %include "../../../include/FFmpegWriter.h" + +/* Move FFmpeg's RSHIFT to FF_RSHIFT, if present */ +#ifdef RSHIFT + #define FF_RSHIFT(a, b) RSHIFT(a, b) + #undef RSHIFT +#endif +/* And restore Ruby's RSHIFT, if we captured it */ +#ifdef RB_RSHIFT + #define RSHIFT(a, b) RB_RSHIFT(a, b) +#endif + %include "../../../include/Fraction.h" %include "../../../include/Frame.h" %include "../../../include/FrameMapper.h" From 9aeec7d90ff172539add5c1363d8707fceabe4f1 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Sat, 26 Jan 2019 12:22:09 -0800 Subject: [PATCH 083/223] Set the bitrate to 0 if no valid value was given. It is needed for the crf lossless setting --- src/FFmpegReader.cpp | 4 +++- src/FFmpegWriter.cpp | 8 ++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 011e8fe4..a2a605ad 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -284,6 +284,8 @@ void FFmpegReader::Open() hw_de_on = (val[0] == '1')? 1 : 0; }*/ + //hw_de_on = Settings::Instance()->HARDWARE_DECODE; + // New version turn hardware decode on { char *decoder_hw = NULL; @@ -368,7 +370,7 @@ void FFmpegReader::Open() char adapter[256]; char *adapter_ptr = NULL; int adapter_num; - dev_hw = getenv( "HW_DE_DEVICE_SET" ); // The first card is 0 + dev_hw = getenv( "HW_DE_DEVICE_SET" ); // The first card is 0 if( dev_hw != NULL) { adapter_num = atoi(dev_hw); } else { diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index 8b5ee159..55816c23 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -247,8 +247,10 @@ void FFmpegWriter::SetVideoOptions(bool has_video, string codec, Fraction fps, i info.pixel_ratio.num = pixel_ratio.num; info.pixel_ratio.den = pixel_ratio.den; } - if (bit_rate >= 1000) // bit_rate is the bitrate in b/s + if (bit_rate >= 1000) // bit_rate is the bitrate in b/s info.video_bit_rate = bit_rate; + else + info.video_bit_rate = 0; info.interlaced_frame = interlaced; info.top_field_first = top_field_first; @@ -1083,7 +1085,9 @@ AVStream* FFmpegWriter::add_video_stream() if (info.video_bit_rate > 1000) { c->bit_rate = info.video_bit_rate; } - + else { + c->bit_rate = 0; + } //TODO: Implement variable bitrate feature (which actually works). This implementation throws //invalid bitrate errors and rc buffer underflow errors, etc... //c->rc_min_rate = info.video_bit_rate; From f66ccb11c456fbd8932b9b2bb73e7a2e6a25d3e9 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sat, 26 Jan 2019 17:56:15 -0600 Subject: [PATCH 084/223] Set video bit rate to 0 if an invalid bit rate detected (which happens when using crf) (#191) --- src/FFmpegWriter.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index d8f07c83..6bea8000 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -148,6 +148,8 @@ void FFmpegWriter::SetVideoOptions(bool has_video, string codec, Fraction fps, i } if (bit_rate >= 1000) // bit_rate is the bitrate in b/s info.video_bit_rate = bit_rate; + else + info.video_bit_rate = 0; info.interlaced_frame = interlaced; info.top_field_first = top_field_first; @@ -968,9 +970,12 @@ AVStream* FFmpegWriter::add_video_stream() #endif /* Init video encoder options */ - if (info.video_bit_rate > 1000) { + if (info.video_bit_rate >= 1000) { c->bit_rate = info.video_bit_rate; } + else { + c->bit_rate = 0; + } //TODO: Implement variable bitrate feature (which actually works). This implementation throws //invalid bitrate errors and rc buffer underflow errors, etc... From eeeec316758a0288427a11bf798a2b1f0341f1b3 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sat, 26 Jan 2019 22:15:18 -0600 Subject: [PATCH 085/223] Add git log file with commits up to the previous release (#192) --- .gitlab-ci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 12365c0d..f8b13908 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -22,6 +22,7 @@ linux-builder: - make install - mv /usr/local/lib/python3.4/dist-packages/*openshot* install-x64/python - echo -e "CI_PROJECT_NAME:$CI_PROJECT_NAME\nCI_COMMIT_REF_NAME:$CI_COMMIT_REF_NAME\nCI_COMMIT_SHA:$CI_COMMIT_SHA\nCI_JOB_ID:$CI_JOB_ID" > "install-x64/share/$CI_PROJECT_NAME" + - git log $(git describe --tags --abbrev=0)..HEAD --oneline --pretty=format:"%C(auto,yellow)%h%C(auto,magenta)% %C(auto,blue)%>(12,trunc)%ad %C(auto,green)%<(25,trunc)%aN%C(auto,reset)%s%C(auto,red)% gD% D" --date=short > "install-x64/share/$CI_PROJECT_NAME.log" when: always except: - tags @@ -48,6 +49,7 @@ mac-builder: - make install - mv /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/*openshot* install-x64/python - echo -e "CI_PROJECT_NAME:$CI_PROJECT_NAME\nCI_COMMIT_REF_NAME:$CI_COMMIT_REF_NAME\nCI_COMMIT_SHA:$CI_COMMIT_SHA\nCI_JOB_ID:$CI_JOB_ID" > "install-x64/share/$CI_PROJECT_NAME" + - git log $(git describe --tags --abbrev=0)..HEAD --oneline --pretty=format:"%C(auto,yellow)%h%C(auto,magenta)% %C(auto,blue)%>(12,trunc)%ad %C(auto,green)%<(25,trunc)%aN%C(auto,reset)%s%C(auto,red)% gD% D" --date=short > "install-x64/share/$CI_PROJECT_NAME.log" when: always except: - tags @@ -77,6 +79,8 @@ windows-builder-x86: - Move-Item -Force -path "C:\msys32\mingw32\lib\python3.6\site-packages\*openshot*" -destination "install-x86\python\" - cp src\libopenshot.dll install-x86\lib - New-Item -path "install-x86/share/" -Name "$CI_PROJECT_NAME" -Value "CI_PROJECT_NAME:$CI_PROJECT_NAME`nCI_COMMIT_REF_NAME:$CI_COMMIT_REF_NAME`nCI_COMMIT_SHA:$CI_COMMIT_SHA`nCI_JOB_ID:$CI_JOB_ID" -ItemType file -force + - $PREV_GIT_LABEL=(git describe --tags --abbrev=0) + - git log "$PREV_GIT_LABEL..HEAD" --oneline --pretty=format:"%C(auto,yellow)%h%C(auto,magenta)% %C(auto,blue)%>(12,trunc)%ad %C(auto,green)%<(25,trunc)%aN%C(auto,reset)%s%C(auto,red)% gD% D" --date=short > "install-x86/share/$CI_PROJECT_NAME.log" when: always except: - tags @@ -105,6 +109,8 @@ windows-builder-x64: - Move-Item -Force -path "C:\msys64\mingw64\lib\python3.6\site-packages\*openshot*" -destination "install-x64\python\" - cp src\libopenshot.dll install-x64\lib - New-Item -path "install-x64/share/" -Name "$CI_PROJECT_NAME" -Value "CI_PROJECT_NAME:$CI_PROJECT_NAME`nCI_COMMIT_REF_NAME:$CI_COMMIT_REF_NAME`nCI_COMMIT_SHA:$CI_COMMIT_SHA`nCI_JOB_ID:$CI_JOB_ID" -ItemType file -force + - $PREV_GIT_LABEL=(git describe --tags --abbrev=0) + - git log "$PREV_GIT_LABEL..HEAD" --oneline --pretty=format:"%C(auto,yellow)%h%C(auto,magenta)% %C(auto,blue)%>(12,trunc)%ad %C(auto,green)%<(25,trunc)%aN%C(auto,reset)%s%C(auto,red)% gD% D" --date=short > "install-x64/share/$CI_PROJECT_NAME.log" when: always except: - tags From 46051fbba10416ab1c3bd59388b4957c5727f63a Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Sun, 27 Jan 2019 10:07:40 -0800 Subject: [PATCH 086/223] Form follows function Moved crf back to SetVideoOptions and adjusted parameters Now h.264 and VP9 have working crf Some small changes in preparation for Settings --- src/FFmpegReader.cpp | 18 ++++----- src/FFmpegWriter.cpp | 91 +++++++++++++++++++++++++++++++++++++------- 2 files changed, 87 insertions(+), 22 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index a2a605ad..dd5f063b 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -291,7 +291,7 @@ void FFmpegReader::Open() char *decoder_hw = NULL; decoder_hw = getenv( "HW_DECODER" ); if(decoder_hw != NULL) { - if( strncmp(decoder_hw,"NONE",4) == 0) { + if( strncmp(decoder_hw,"0",4) == 0) { hw_de_on = 0; } else { hw_de_on = 1; @@ -382,15 +382,15 @@ void FFmpegReader::Open() adapter_ptr = adapter; decoder_hw = getenv( "HW_DECODER" ); if(decoder_hw != NULL) { - if (strncmp(decoder_hw,"NONE",4) == 0) { //Will never happen + if (strncmp(decoder_hw,"0",4) == 0) { //Will never happen hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; pCodecCtx->get_format = get_hw_dec_format_va; } - if (strncmp(decoder_hw,"HW_DE_VAAPI",11) == 0) { //Will never happen + if (strncmp(decoder_hw,"1",11) == 0) { hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; pCodecCtx->get_format = get_hw_dec_format_va; } - if (strncmp(decoder_hw,"HW_DE_NVDEC",11) == 0) { //Will never happen + if (strncmp(decoder_hw,"2",11) == 0) { hw_de_av_device_type = AV_HWDEVICE_TYPE_CUDA; pCodecCtx->get_format = get_hw_dec_format_cu; } @@ -403,15 +403,15 @@ void FFmpegReader::Open() adapter_ptr = NULL; decoder_hw = getenv( "HW_DECODER" ); if(decoder_hw != NULL) { - if (strncmp(decoder_hw,"NONE",4) == 0) { //Will never happen + if (strncmp(decoder_hw,"0",4) == 0) { //Will never happen hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2; pCodecCtx->get_format = get_hw_dec_format_dx; } - if (strncmp(decoder_hw,"HW_DE_WINDOWS_DXVA2",19) == 0) { //Will never happen + if (strncmp(decoder_hw,"3",19) == 0) { hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2; pCodecCtx->get_format = get_hw_dec_format_dx; } - if (strncmp(decoder_hw,"HW_DE_WINDOWS_D3D11",19) == 0) { //Will never happen + if (strncmp(decoder_hw,"4",19) == 0) { hw_de_av_device_type = AV_HWDEVICE_TYPE_D3D11VA; pCodecCtx->get_format = get_hw_dec_format_d3; } @@ -423,11 +423,11 @@ void FFmpegReader::Open() adapter_ptr = NULL; decoder_hw = getenv( "HW_DECODER" ); if(decoder_hw != NULL) { - if (strncmp(decoder_hw,"NONE",4) == 0) { //Will never happen + if (strncmp(decoder_hw,"0",4) == 0) { //Will never happen hw_de_av_device_type = AV_HWDEVICE_TYPE_QSV; pCodecCtx->get_format = get_hw_dec_format_qs; } - if (strncmp(decoder_hw,"HW_DE_MACOS",11) == 0) { //Will never happen + if (strncmp(decoder_hw,"5",11) == 0) { hw_de_av_device_type = AV_HWDEVICE_TYPE_QSV; pCodecCtx->get_format = get_hw_dec_format_qs; } diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index 55816c23..2c81cf96 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -249,8 +249,10 @@ void FFmpegWriter::SetVideoOptions(bool has_video, string codec, Fraction fps, i } if (bit_rate >= 1000) // bit_rate is the bitrate in b/s info.video_bit_rate = bit_rate; - else - info.video_bit_rate = 0; + if ((bit_rate >= 0) && (bit_rate < 64) ) // bit_rate is the bitrate in b/s + info.video_bit_rate = bit_rate; + //else + // info.video_bit_rate = 0; info.interlaced_frame = interlaced; info.top_field_first = top_field_first; @@ -392,29 +394,35 @@ void FFmpegWriter::SetOption(StreamType stream, string name, string value) // encode quality and special settings like lossless // This might be better in an extra methods as more options // and way to set quality are possible + int tempi = stoi(value); #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 39, 101) switch (c->codec_id) { case AV_CODEC_ID_VP8 : - av_opt_set_int(c->priv_data, "crf", min(stoi(value),63), 0); + av_opt_set_int(c->priv_data, "crf", min(tempi,63), 0); break; case AV_CODEC_ID_VP9 : - av_opt_set_int(c->priv_data, "crf", min(stoi(value),63), 0); - if (stoi(value) == 0) { + av_opt_set_int(c->priv_data, "crf", min(tempi,63), 0); + if (tempi == 0) { av_opt_set_int(c->priv_data, "lossless", 1, 0); + av_opt_set(c->priv_data, "preset", "veryslow", 0); } break; case AV_CODEC_ID_H264 : - av_opt_set_int(c->priv_data, "crf", min(stoi(value),51), 0); + av_opt_set_int(c->priv_data, "crf", min(tempi,51), 0); + if (tempi == 0) { + av_opt_set(c->priv_data, "preset", "veryslow", 0); + } break; case AV_CODEC_ID_H265 : - av_opt_set_int(c->priv_data, "crf", min(stoi(value),51), 0); - if (stoi(value) == 0) { + av_opt_set_int(c->priv_data, "crf", min(tempi,51), 0); + if (tempi == 0) { av_opt_set_int(c->priv_data, "lossless", 1, 0); + av_opt_set(c->priv_data, "preset", "veryslow", 0); } break; #ifdef AV_CODEC_ID_AV1 case AV_CODEC_ID_AV1 : - av_opt_set_int(c->priv_data, "crf", min(stoi(value),63), 0); + av_opt_set_int(c->priv_data, "crf", min(tempi,63), 0); break; #endif } @@ -1082,13 +1090,70 @@ AVStream* FFmpegWriter::add_video_stream() #endif /* Init video encoder options */ - if (info.video_bit_rate > 1000) { + if (info.video_bit_rate >= 1000) { c->bit_rate = info.video_bit_rate; } - else { - c->bit_rate = 0; +// else { +// c->bit_rate = 0; +// } + #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 39, 101) + else { +#if IS_FFMPEG_3_2 + if (hw_en_on) { + double mbs = 15000000.0; + if (info.video_bit_rate > 0) { + if (info.video_bit_rate > 42) { + mbs = 380.0; + } + else { + mbs *= pow(0.912,info.video_bit_rate); + } + } + c->bit_rate = (int)(mbs); + } + else +#endif + { + switch (c->codec_id) { +#if (LIBAVCODEC_VERSION_MAJOR >= 58) + case AV_CODEC_ID_AV1 : + av_opt_set_int(c->priv_data, "crf", min(info.video_bit_rate,63), 0); + c->bit_rate = 0; + break; +#endif + case AV_CODEC_ID_VP8 : + av_opt_set_int(c->priv_data, "crf", min(info.video_bit_rate,63), 0); + break; + case AV_CODEC_ID_VP9 : + av_opt_set_int(c->priv_data, "crf", min(info.video_bit_rate,63), 0); + c->bit_rate = 0; + if (info.video_bit_rate == 0) { + av_opt_set_int(c->priv_data, "lossless", 1, 0); + } + break; + case AV_CODEC_ID_H264 : + av_opt_set_int(c->priv_data, "crf", min(info.video_bit_rate,51), 0); + break; + case AV_CODEC_ID_H265 : + av_opt_set_int(c->priv_data, "crf", min(info.video_bit_rate,51), 0); + break; + default: + double mbs = 15000000.0; + if (info.video_bit_rate > 0) { + if (info.video_bit_rate > 42) { + mbs = 380.0; + } + else { + mbs *= pow(0.912,info.video_bit_rate); + } + } + c->bit_rate = (int)(mbs); + } + } } - //TODO: Implement variable bitrate feature (which actually works). This implementation throws +#endif + +//TODO: Implement variable bitrate feature (which actually works). This implementation throws //invalid bitrate errors and rc buffer underflow errors, etc... //c->rc_min_rate = info.video_bit_rate; //c->rc_max_rate = info.video_bit_rate; From 1a44bd789cf307937d712c89047251bd3a1351b5 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Sun, 27 Jan 2019 10:26:20 -0800 Subject: [PATCH 087/223] Make sure that crf is not set in SetOptions --- src/FFmpegWriter.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index 2c81cf96..c5c40685 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -390,7 +390,7 @@ void FFmpegWriter::SetOption(StreamType stream, string name, string value) // Buffer size convert >> c->rc_buffer_size; - else if (name == "crf") { +/* else if (name == "crf") { // encode quality and special settings like lossless // This might be better in an extra methods as more options // and way to set quality are possible @@ -428,7 +428,7 @@ void FFmpegWriter::SetOption(StreamType stream, string name, string value) } #endif } - +*/ else // Set AVOption AV_OPTION_SET(st, c->priv_data, name.c_str(), value.c_str(), c); @@ -1128,7 +1128,8 @@ AVStream* FFmpegWriter::add_video_stream() av_opt_set_int(c->priv_data, "crf", min(info.video_bit_rate,63), 0); c->bit_rate = 0; if (info.video_bit_rate == 0) { - av_opt_set_int(c->priv_data, "lossless", 1, 0); + av_opt_set(c->priv_data, "preset", "veryslow", 0); + av_opt_set_int(c->priv_data, "lossless", 1, 0); } break; case AV_CODEC_ID_H264 : From 39bf06b3d3ab7469583064362565fb6f1be7fb48 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Sun, 27 Jan 2019 10:50:23 -0800 Subject: [PATCH 088/223] Now VP8, VP9, h.264, h.265 have working crf --- src/FFmpegWriter.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index c5c40685..778c352e 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -1123,6 +1123,11 @@ AVStream* FFmpegWriter::add_video_stream() #endif case AV_CODEC_ID_VP8 : av_opt_set_int(c->priv_data, "crf", min(info.video_bit_rate,63), 0); + c->bit_rate = 10000000; + if (info.video_bit_rate == 0) { + av_opt_set(c->priv_data, "preset", "veryslow", 0); + c->bit_rate = 30000000; + } break; case AV_CODEC_ID_VP9 : av_opt_set_int(c->priv_data, "crf", min(info.video_bit_rate,63), 0); @@ -1137,6 +1142,10 @@ AVStream* FFmpegWriter::add_video_stream() break; case AV_CODEC_ID_H265 : av_opt_set_int(c->priv_data, "crf", min(info.video_bit_rate,51), 0); + if (info.video_bit_rate == 0) { + av_opt_set(c->priv_data, "preset", "veryslow", 0); + av_opt_set_int(c->priv_data, "lossless", 1, 0); + } break; default: double mbs = 15000000.0; From 1bf3e3756719d377e34c236a5d01b2d363ab5c8f Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Mon, 28 Jan 2019 12:28:35 -0600 Subject: [PATCH 089/223] Refactor FFmpegWriter Open() and PrepareStreams() methods, so that SetOption() is can be called between them, and allowed to adjust the codecs bit_rate and options before we call open_video(). This was primarily to allow CRF options to work. (#193) --- src/FFmpegWriter.cpp | 78 ++++++++++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 29 deletions(-) diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index 6bea8000..b9b6ba76 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -55,16 +55,24 @@ FFmpegWriter::FFmpegWriter(string path) : // Open the writer void FFmpegWriter::Open() { - // Open the writer - is_open = true; + if (!is_open) { + // Open the writer + is_open = true; - // Prepare streams (if needed) - if (!prepare_streams) - PrepareStreams(); + // Prepare streams (if needed) + if (!prepare_streams) + PrepareStreams(); - // Write header (if needed) - if (!write_header) - WriteHeader(); + // Now that all the parameters are set, we can open the audio and video codecs and allocate the necessary encode buffers + if (info.has_video && video_st) + open_video(oc, video_st); + if (info.has_audio && audio_st) + open_audio(oc, audio_st); + + // Write header (if needed) + if (!write_header) + WriteHeader(); + } } // auto detect format (from path) @@ -148,8 +156,8 @@ void FFmpegWriter::SetVideoOptions(bool has_video, string codec, Fraction fps, i } if (bit_rate >= 1000) // bit_rate is the bitrate in b/s info.video_bit_rate = bit_rate; - else - info.video_bit_rate = 0; + if ((bit_rate >= 0) && (bit_rate < 64) ) // bit_rate is the bitrate in crf + info.video_bit_rate = bit_rate; info.interlaced_frame = interlaced; info.top_field_first = top_field_first; @@ -293,29 +301,50 @@ void FFmpegWriter::SetOption(StreamType stream, string name, string value) // and way to set quality are possible #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 39, 101) switch (c->codec_id) { - case AV_CODEC_ID_VP8 : + #if (LIBAVCODEC_VERSION_MAJOR >= 58) + case AV_CODEC_ID_AV1 : + c->bit_rate = 0; av_opt_set_int(c->priv_data, "crf", min(stoi(value),63), 0); break; + #endif + case AV_CODEC_ID_VP8 : + c->bit_rate = 10000000; + av_opt_set_int(c->priv_data, "crf", max(min(stoi(value),63),4), 0); // 4-63 + break; case AV_CODEC_ID_VP9 : - av_opt_set_int(c->priv_data, "crf", min(stoi(value),63), 0); + c->bit_rate = 0; // Must be zero! + av_opt_set_int(c->priv_data, "crf", min(stoi(value),63), 0); // 0-63 if (stoi(value) == 0) { + av_opt_set(c->priv_data, "preset", "veryslow", 0); av_opt_set_int(c->priv_data, "lossless", 1, 0); } break; case AV_CODEC_ID_H264 : - av_opt_set_int(c->priv_data, "crf", min(stoi(value),51), 0); + av_opt_set_int(c->priv_data, "crf", min(stoi(value),51), 0); // 0-51 + if (stoi(value) == 0) { + av_opt_set(c->priv_data, "preset", "veryslow", 0); + } break; case AV_CODEC_ID_H265 : - av_opt_set_int(c->priv_data, "crf", min(stoi(value),51), 0); + av_opt_set_int(c->priv_data, "crf", min(stoi(value),51), 0); // 0-51 if (stoi(value) == 0) { + av_opt_set(c->priv_data, "preset", "veryslow", 0); av_opt_set_int(c->priv_data, "lossless", 1, 0); - } + } break; - #ifdef AV_CODEC_ID_AV1 - case AV_CODEC_ID_AV1 : - av_opt_set_int(c->priv_data, "crf", min(stoi(value),63), 0); - break; - #endif + default: + // If this codec doesn't support crf calculate a bitrate + // TODO: find better formula + double mbs = 15000000.0; + if (info.video_bit_rate > 0) { + if (info.video_bit_rate > 42) { + mbs = 380.0; + } + else { + mbs *= pow(0.912,info.video_bit_rate); + } + } + c->bit_rate = (int)(mbs); } #endif } @@ -355,12 +384,6 @@ void FFmpegWriter::PrepareStreams() // Initialize the streams (i.e. add the streams) initialize_streams(); - // Now that all the parameters are set, we can open the audio and video codecs and allocate the necessary encode buffers - if (info.has_video && video_st) - open_video(oc, video_st); - if (info.has_audio && audio_st) - open_audio(oc, audio_st); - // Mark as 'prepared' prepare_streams = true; } @@ -973,9 +996,6 @@ AVStream* FFmpegWriter::add_video_stream() if (info.video_bit_rate >= 1000) { c->bit_rate = info.video_bit_rate; } - else { - c->bit_rate = 0; - } //TODO: Implement variable bitrate feature (which actually works). This implementation throws //invalid bitrate errors and rc buffer underflow errors, etc... From 05fb797776086103d82c9a448dc5928b18de5bab Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Tue, 29 Jan 2019 10:26:10 -0800 Subject: [PATCH 090/223] Move the check if hw accell ecoding is used with crf to the right place --- src/FFmpegWriter.cpp | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index 3ae1dd24..d98d88d3 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -400,7 +400,24 @@ void FFmpegWriter::SetOption(StreamType stream, string name, string value) // encode quality and special settings like lossless // This might be better in an extra methods as more options // and way to set quality are possible - #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 39, 101) + #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 39, 101) + #if IS_FFMPEG_3_2 + if (hw_en_on) { + double mbs = 15000000.0; + if (info.video_bit_rate > 0) { + if (info.video_bit_rate > 42) { + mbs = 380000.0; + } + else { + mbs *= pow(0.912,info.video_bit_rate); + } + } + c->bit_rate = (int)(mbs); + } else + #endif +// #endif +// #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 39, 101) + { switch (c->codec_id) { #if (LIBAVCODEC_VERSION_MAJOR >= 58) case AV_CODEC_ID_AV1 : @@ -447,6 +464,7 @@ void FFmpegWriter::SetOption(StreamType stream, string name, string value) } c->bit_rate = (int)(mbs); } + } #endif } @@ -1108,24 +1126,6 @@ AVStream* FFmpegWriter::add_video_stream() if (info.video_bit_rate >= 1000) { c->bit_rate = info.video_bit_rate; } - #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 39, 101) - else { - #if IS_FFMPEG_3_2 - if (hw_en_on) { - double mbs = 15000000.0; - if (info.video_bit_rate > 0) { - if (info.video_bit_rate > 42) { - mbs = 380000.0; - } - else { - mbs *= pow(0.912,info.video_bit_rate); - } - } - c->bit_rate = (int)(mbs); - } - #endif - } - #endif //TODO: Implement variable bitrate feature (which actually works). This implementation throws //invalid bitrate errors and rc buffer underflow errors, etc... From 7e3669b620d96af79a98892eb9b7a11e2114853e Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Tue, 29 Jan 2019 12:38:52 -0800 Subject: [PATCH 091/223] Formating --- src/FFmpegWriter.cpp | 459 +++++++++++++++++++++---------------------- 1 file changed, 228 insertions(+), 231 deletions(-) diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index d98d88d3..6b8f240f 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -48,32 +48,32 @@ AVFrame *hw_frame = NULL; static int set_hwframe_ctx(AVCodecContext *ctx, AVBufferRef *hw_device_ctx, int64_t width, int64_t height) { - AVBufferRef *hw_frames_ref; - AVHWFramesContext *frames_ctx = NULL; - int err = 0; + AVBufferRef *hw_frames_ref; + AVHWFramesContext *frames_ctx = NULL; + int err = 0; - if (!(hw_frames_ref = av_hwframe_ctx_alloc(hw_device_ctx))) { - fprintf(stderr, "Failed to create HW frame context.\n"); - return -1; - } - frames_ctx = (AVHWFramesContext *)(hw_frames_ref->data); - frames_ctx->format = hw_en_av_pix_fmt; - frames_ctx->sw_format = AV_PIX_FMT_NV12; - frames_ctx->width = width; - frames_ctx->height = height; - frames_ctx->initial_pool_size = 20; - if ((err = av_hwframe_ctx_init(hw_frames_ref)) < 0) { - fprintf(stderr, "Failed to initialize HW frame context." - "Error code: %s\n",av_err2str(err)); - av_buffer_unref(&hw_frames_ref); - return err; - } - ctx->hw_frames_ctx = av_buffer_ref(hw_frames_ref); - if (!ctx->hw_frames_ctx) - err = AVERROR(ENOMEM); + if (!(hw_frames_ref = av_hwframe_ctx_alloc(hw_device_ctx))) { + fprintf(stderr, "Failed to create HW frame context.\n"); + return -1; + } + frames_ctx = (AVHWFramesContext *)(hw_frames_ref->data); + frames_ctx->format = hw_en_av_pix_fmt; + frames_ctx->sw_format = AV_PIX_FMT_NV12; + frames_ctx->width = width; + frames_ctx->height = height; + frames_ctx->initial_pool_size = 20; + if ((err = av_hwframe_ctx_init(hw_frames_ref)) < 0) { + fprintf(stderr, "Failed to initialize HW frame context." + "Error code: %s\n",av_err2str(err)); + av_buffer_unref(&hw_frames_ref); + return err; + } + ctx->hw_frames_ctx = av_buffer_ref(hw_frames_ref); + if (!ctx->hw_frames_ctx) + err = AVERROR(ENOMEM); - av_buffer_unref(&hw_frames_ref); - return err; + av_buffer_unref(&hw_frames_ref); + return err; } #endif @@ -176,49 +176,49 @@ void FFmpegWriter::SetVideoOptions(bool has_video, string codec, Fraction fps, i if ( (strcmp(codec.c_str(),"h264_vaapi") == 0)) { new_codec = avcodec_find_encoder_by_name(codec.c_str()); hw_en_on = 1; - hw_en_supported = 1; - hw_en_av_pix_fmt = AV_PIX_FMT_VAAPI; - hw_en_av_device_type = AV_HWDEVICE_TYPE_VAAPI; + hw_en_supported = 1; + hw_en_av_pix_fmt = AV_PIX_FMT_VAAPI; + hw_en_av_device_type = AV_HWDEVICE_TYPE_VAAPI; + } + else { + if ( (strcmp(codec.c_str(),"h264_nvenc") == 0)) { + new_codec = avcodec_find_encoder_by_name(codec.c_str()); + hw_en_on = 1; + hw_en_supported = 1; + hw_en_av_pix_fmt = AV_PIX_FMT_CUDA; + hw_en_av_device_type = AV_HWDEVICE_TYPE_CUDA; + } + else { + new_codec = avcodec_find_encoder_by_name(codec.c_str()); + hw_en_on = 0; + hw_en_supported = 0; + } } - else { - if ( (strcmp(codec.c_str(),"h264_nvenc") == 0)) { - new_codec = avcodec_find_encoder_by_name(codec.c_str()); - hw_en_on = 1; - hw_en_supported = 1; - hw_en_av_pix_fmt = AV_PIX_FMT_CUDA; - hw_en_av_device_type = AV_HWDEVICE_TYPE_CUDA; - } - else { - new_codec = avcodec_find_encoder_by_name(codec.c_str()); - hw_en_on = 0; - hw_en_supported = 0; - } - } #elif defined(_WIN32) if ( (strcmp(codec.c_str(),"h264_dxva2") == 0)) { new_codec = avcodec_find_encoder_by_name(codec.c_str()); hw_en_on = 1; - hw_en_supported = 1; - hw_en_av_pix_fmt = AV_PIX_FMT_DXVA2_VLD; - hw_en_av_device_type = AV_HWDEVICE_TYPE_DXVA2; + hw_en_supported = 1; + hw_en_av_pix_fmt = AV_PIX_FMT_DXVA2_VLD; + hw_en_av_device_type = AV_HWDEVICE_TYPE_DXVA2; } else { new_codec = avcodec_find_encoder_by_name(codec.c_str()); hw_en_on = 0; - hw_en_supported = 0; + hw_en_supported = 0; } - #elif defined(__APPLE__) + #elif defined(__APPLE__) if ( (strcmp(codec.c_str(),"h264_qsv") == 0)) { new_codec = avcodec_find_encoder_by_name(codec.c_str()); hw_en_on = 1; - hw_en_supported = 1; - hw_en_av_pix_fmt = AV_PIX_FMT_QSV; - hw_en_av_device_type = AV_HWDEVICE_TYPE_QSV; + hw_en_supported = 1; + hw_en_av_pix_fmt = AV_PIX_FMT_QSV; + hw_en_av_device_type = AV_HWDEVICE_TYPE_QSV; } else { new_codec = avcodec_find_encoder_by_name(codec.c_str()); hw_en_on = 0; - hw_en_supported = 0; + hw_en_supported = 0; } #else // is FFmpeg 3 but not linux new_codec = avcodec_find_encoder_by_name(codec.c_str()); @@ -255,7 +255,7 @@ void FFmpegWriter::SetVideoOptions(bool has_video, string codec, Fraction fps, i info.pixel_ratio.num = pixel_ratio.num; info.pixel_ratio.den = pixel_ratio.den; } - if (bit_rate >= 1000) // bit_rate is the bitrate in b/s + if (bit_rate >= 1000) // bit_rate is the bitrate in b/s info.video_bit_rate = bit_rate; if ((bit_rate >= 0) && (bit_rate < 64) ) // bit_rate is the bitrate in crf info.video_bit_rate = bit_rate; @@ -348,8 +348,8 @@ void FFmpegWriter::SetOption(StreamType stream, string name, string value) // Was option found? if (option || (name == "g" || name == "qmin" || name == "qmax" || name == "max_b_frames" || name == "mb_decision" || - name == "level" || name == "profile" || name == "slices" || name == "rc_min_rate" || name == "rc_max_rate" || - name == "crf")) + name == "level" || name == "profile" || name == "slices" || name == "rc_min_rate" || name == "rc_max_rate" || + name == "crf")) { // Check for specific named options if (name == "g") @@ -396,28 +396,26 @@ void FFmpegWriter::SetOption(StreamType stream, string name, string value) // Buffer size convert >> c->rc_buffer_size; - else if (name == "crf") { + else if (name == "crf") { // encode quality and special settings like lossless // This might be better in an extra methods as more options // and way to set quality are possible - #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 39, 101) - #if IS_FFMPEG_3_2 - if (hw_en_on) { - double mbs = 15000000.0; - if (info.video_bit_rate > 0) { - if (info.video_bit_rate > 42) { - mbs = 380000.0; - } - else { - mbs *= pow(0.912,info.video_bit_rate); - } - } - c->bit_rate = (int)(mbs); - } else - #endif -// #endif -// #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 39, 101) - { + #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 39, 101) + #if IS_FFMPEG_3_2 + if (hw_en_on) { + double mbs = 15000000.0; + if (info.video_bit_rate > 0) { + if (info.video_bit_rate > 42) { + mbs = 380000.0; + } + else { + mbs *= pow(0.912,info.video_bit_rate); + } + } + c->bit_rate = (int)(mbs); + } else + #endif + { switch (c->codec_id) { #if (LIBAVCODEC_VERSION_MAJOR >= 58) case AV_CODEC_ID_AV1 : @@ -519,7 +517,7 @@ void FFmpegWriter::WriteHeader() throw InvalidFile("Could not open or write file.", path); } - // Force the output filename (which doesn't always happen for some reason) + // Force the output filename (which doesn't always happen for some reason) snprintf(oc->AV_FILENAME, sizeof(oc->AV_FILENAME), "%s", path.c_str()); // Write the stream header, if any @@ -743,11 +741,11 @@ void FFmpegWriter::flush_encoders() return; #endif - int error_code = 0; - int stop_encoding = 1; + int error_code = 0; + int stop_encoding = 1; - // FLUSH VIDEO ENCODER - if (info.has_video) + // FLUSH VIDEO ENCODER + if (info.has_video) for (;;) { // Increment PTS (in frames and scaled to the codec's timebase) @@ -846,8 +844,8 @@ void FFmpegWriter::flush_encoders() av_freep(&video_outbuf); } - // FLUSH AUDIO ENCODER - if (info.has_audio) + // FLUSH AUDIO ENCODER + if (info.has_audio) for (;;) { // Increment PTS (in samples and scaled to the codec's timebase) @@ -914,16 +912,16 @@ void FFmpegWriter::close_video(AVFormatContext *oc, AVStream *st) { AV_FREE_CONTEXT(video_codec); video_codec = NULL; - #if IS_FFMPEG_3_2 + #if IS_FFMPEG_3_2 // #if defined(__linux__) - if (hw_en_on && hw_en_supported) { - if (hw_device_ctx) { - av_buffer_unref(&hw_device_ctx); - hw_device_ctx = NULL; - } - } + if (hw_en_on && hw_en_supported) { + if (hw_device_ctx) { + av_buffer_unref(&hw_device_ctx); + hw_device_ctx = NULL; + } + } // #endif - #endif + #endif } // Close the audio codec @@ -1042,15 +1040,15 @@ AVStream* FFmpegWriter::add_audio_stream() // Set valid sample rate (or throw error) if (codec->supported_samplerates) { int i; - for (i = 0; codec->supported_samplerates[i] != 0; i++) - if (info.sample_rate == codec->supported_samplerates[i]) - { - // Set the valid sample rate - c->sample_rate = info.sample_rate; - break; - } - if (codec->supported_samplerates[i] == 0) - throw InvalidSampleRate("An invalid sample rate was detected for this codec.", path); + for (i = 0; codec->supported_samplerates[i] != 0; i++) + if (info.sample_rate == codec->supported_samplerates[i]) + { + // Set the valid sample rate + c->sample_rate = info.sample_rate; + break; + } + if (codec->supported_samplerates[i] == 0) + throw InvalidSampleRate("An invalid sample rate was detected for this codec.", path); } else // Set sample rate c->sample_rate = info.sample_rate; @@ -1174,31 +1172,31 @@ AVStream* FFmpegWriter::add_video_stream() #endif // Find all supported pixel formats for this codec - const PixelFormat* supported_pixel_formats = codec->pix_fmts; - while (supported_pixel_formats != NULL && *supported_pixel_formats != PIX_FMT_NONE) { - // Assign the 1st valid pixel format (if one is missing) - if (c->pix_fmt == PIX_FMT_NONE) - c->pix_fmt = *supported_pixel_formats; - ++supported_pixel_formats; - } + const PixelFormat* supported_pixel_formats = codec->pix_fmts; + while (supported_pixel_formats != NULL && *supported_pixel_formats != PIX_FMT_NONE) { + // Assign the 1st valid pixel format (if one is missing) + if (c->pix_fmt == PIX_FMT_NONE) + c->pix_fmt = *supported_pixel_formats; + ++supported_pixel_formats; + } - // Codec doesn't have any pix formats? - if (c->pix_fmt == PIX_FMT_NONE) { - if(fmt->video_codec == AV_CODEC_ID_RAWVIDEO) { - // Raw video should use RGB24 - c->pix_fmt = PIX_FMT_RGB24; + // Codec doesn't have any pix formats? + if (c->pix_fmt == PIX_FMT_NONE) { + if(fmt->video_codec == AV_CODEC_ID_RAWVIDEO) { + // Raw video should use RGB24 + c->pix_fmt = PIX_FMT_RGB24; #if (LIBAVFORMAT_VERSION_MAJOR < 58) - if (strcmp(fmt->name, "gif") != 0) - // If not GIF format, skip the encoding process - // Set raw picture flag (so we don't encode this video) - oc->oformat->flags |= AVFMT_RAWPICTURE; + if (strcmp(fmt->name, "gif") != 0) + // If not GIF format, skip the encoding process + // Set raw picture flag (so we don't encode this video) + oc->oformat->flags |= AVFMT_RAWPICTURE; #endif - } else { - // Set the default codec - c->pix_fmt = PIX_FMT_YUV420P; - } - } + } else { + // Set the default codec + c->pix_fmt = PIX_FMT_YUV420P; + } + } AV_COPY_PARAMS_FROM_CONTEXT(st, c); #if (LIBAVFORMAT_VERSION_MAJOR < 58) @@ -1293,51 +1291,50 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) // Set number of threads equal to number of processors (not to exceed 16) video_codec->thread_count = min(FF_NUM_PROCESSORS, 16); - #if IS_FFMPEG_3_2 - if (hw_en_on && hw_en_supported) { - char *dev_hw = NULL; - char adapter[256]; - char *adapter_ptr = NULL; - int adapter_num; + #if IS_FFMPEG_3_2 + if (hw_en_on && hw_en_supported) { + char *dev_hw = NULL; + char adapter[256]; + char *adapter_ptr = NULL; + int adapter_num; // Use the hw device given in the environment variable HW_EN_DEVICE_SET or the default if not set dev_hw = getenv( "HW_EN_DEVICE_SET" ); - if( dev_hw != NULL) { - adapter_num = atoi(dev_hw); - if (adapter_num < 3 && adapter_num >=0) { - #if defined(__linux__) - snprintf(adapter,sizeof(adapter),"/dev/dri/renderD%d", adapter_num+128); - // Maybe 127 is better because the first card would be 1?! - adapter_ptr = adapter; - #elif defined(_WIN32) - adapter_ptr = NULL; - #elif defined(__APPLE__) - adapter_ptr = NULL; - #endif - } - else { - adapter_ptr = NULL; // Just to be sure - } - } + if( dev_hw != NULL) { + adapter_num = atoi(dev_hw); + if (adapter_num < 3 && adapter_num >=0) { + #if defined(__linux__) + snprintf(adapter,sizeof(adapter),"/dev/dri/renderD%d", adapter_num+128); + // Maybe 127 is better because the first card would be 1?! + adapter_ptr = adapter; + #elif defined(_WIN32) + adapter_ptr = NULL; + #elif defined(__APPLE__) + adapter_ptr = NULL; + #endif + } + else { + adapter_ptr = NULL; // Just to be sure + } + } // Check if it is there and writable - #if defined(__linux__) - if( adapter_ptr != NULL && access( adapter_ptr, W_OK ) == -1 ) { - #elif defined(_WIN32) - if( adapter_ptr != NULL ) { - #elif defined(__APPLE__) - if( adapter_ptr != NULL ) { - #endif - adapter_ptr = NULL; // use default - //cerr << "\n\n\nEncode Device not present using default\n\n\n"; - ZmqLogger::Instance()->AppendDebugMethod("Encode Device not present using default", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); - } - if (av_hwdevice_ctx_create(&hw_device_ctx, hw_en_av_device_type, - adapter_ptr, NULL, 0) < 0) { - ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::open_video : Codec name: ", info.vcodec.c_str(), -1, " ERROR creating\n", -1, "", -1, "", -1, "", -1, "", -1); - //cerr << "FFmpegWriter::open_video : Codec name: " << info.vcodec.c_str() << " ERROR creating\n"; - throw InvalidCodec("Could not create hwdevice", path); - } - } - #endif + #if defined(__linux__) + if( adapter_ptr != NULL && access( adapter_ptr, W_OK ) == -1 ) { + #elif defined(_WIN32) + if( adapter_ptr != NULL ) { + #elif defined(__APPLE__) + if( adapter_ptr != NULL ) { + #endif + adapter_ptr = NULL; // use default + //cerr << "\n\n\nEncode Device not present using default\n\n\n"; + ZmqLogger::Instance()->AppendDebugMethod("Encode Device not present using default", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); + } + if (av_hwdevice_ctx_create(&hw_device_ctx, hw_en_av_device_type, + adapter_ptr, NULL, 0) < 0) { + ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::open_video : Codec name: ", info.vcodec.c_str(), -1, " ERROR creating\n", -1, "", -1, "", -1, "", -1, "", -1); + throw InvalidCodec("Could not create hwdevice", path); + } + } + #endif /* find the video encoder */ codec = avcodec_find_encoder_by_name(info.vcodec.c_str()); if (!codec) @@ -1345,29 +1342,29 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) if (!codec) throw InvalidCodec("Could not find codec", path); - /* Force max_b_frames to 0 in some cases (i.e. for mjpeg image sequences */ - if(video_codec->max_b_frames && video_codec->codec_id != AV_CODEC_ID_MPEG4 && video_codec->codec_id != AV_CODEC_ID_MPEG1VIDEO && video_codec->codec_id != AV_CODEC_ID_MPEG2VIDEO) - video_codec->max_b_frames = 0; + /* Force max_b_frames to 0 in some cases (i.e. for mjpeg image sequences */ + if(video_codec->max_b_frames && video_codec->codec_id != AV_CODEC_ID_MPEG4 && video_codec->codec_id != AV_CODEC_ID_MPEG1VIDEO && video_codec->codec_id != AV_CODEC_ID_MPEG2VIDEO) + video_codec->max_b_frames = 0; // Init options AVDictionary *opts = NULL; av_dict_set(&opts, "strict", "experimental", 0); - #if IS_FFMPEG_3_2 - if (hw_en_on && hw_en_supported) { - video_codec->max_b_frames = 0; // At least this GPU doesn't support b-frames - video_codec->pix_fmt = hw_en_av_pix_fmt; - video_codec->profile = FF_PROFILE_H264_BASELINE | FF_PROFILE_H264_CONSTRAINED; - av_opt_set(video_codec->priv_data,"preset","slow",0); - av_opt_set(video_codec->priv_data,"tune","zerolatency",0); - av_opt_set(video_codec->priv_data, "vprofile", "baseline", AV_OPT_SEARCH_CHILDREN); - // set hw_frames_ctx for encoder's AVCodecContext - int err; - if ((err = set_hwframe_ctx(video_codec, hw_device_ctx, info.width, info.height)) < 0) { - fprintf(stderr, "Failed to set hwframe context.\n"); - } - } - #endif + #if IS_FFMPEG_3_2 + if (hw_en_on && hw_en_supported) { + video_codec->max_b_frames = 0; // At least this GPU doesn't support b-frames + video_codec->pix_fmt = hw_en_av_pix_fmt; + video_codec->profile = FF_PROFILE_H264_BASELINE | FF_PROFILE_H264_CONSTRAINED; + av_opt_set(video_codec->priv_data,"preset","slow",0); + av_opt_set(video_codec->priv_data,"tune","zerolatency",0); + av_opt_set(video_codec->priv_data, "vprofile", "baseline", AV_OPT_SEARCH_CHILDREN); + // set hw_frames_ctx for encoder's AVCodecContext + int err; + if ((err = set_hwframe_ctx(video_codec, hw_device_ctx, info.width, info.height)) < 0) { + fprintf(stderr, "Failed to set hwframe context.\n"); + } + } + #endif /* open the codec */ if (avcodec_open2(video_codec, codec, &opts) < 0) @@ -1627,9 +1624,9 @@ void FFmpegWriter::write_audio_packets(bool final) memcpy(samples, frame_final->data[0], nb_samples * av_get_bytes_per_sample(audio_codec->sample_fmt) * info.channels); // deallocate AVFrame - av_freep(&(audio_frame->data[0])); - AV_FREE_FRAME(&audio_frame); - all_queued_samples = NULL; // this array cleared with above call + av_freep(&(audio_frame->data[0])); + AV_FREE_FRAME(&audio_frame); + all_queued_samples = NULL; // this array cleared with above call ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::write_audio_packets (Successfully completed 2nd resampling for Planar formats)", "nb_samples", nb_samples, "", -1, "", -1, "", -1, "", -1, "", -1); @@ -1730,7 +1727,7 @@ void FFmpegWriter::write_audio_packets(bool final) // deallocate AVFrame av_freep(&(frame_final->data[0])); - AV_FREE_FRAME(&frame_final); + AV_FREE_FRAME(&frame_final); // deallocate memory for packet AV_FREE_PACKET(&pkt); @@ -1820,19 +1817,19 @@ void FFmpegWriter::process_video_packet(std::shared_ptr frame) #if IS_FFMPEG_3_2 AVFrame *frame_final; // #if defined(__linux__) - if (hw_en_on && hw_en_supported) { - frame_final = allocate_avframe(AV_PIX_FMT_NV12, info.width, info.height, &bytes_final, NULL); - } else + if (hw_en_on && hw_en_supported) { + frame_final = allocate_avframe(AV_PIX_FMT_NV12, info.width, info.height, &bytes_final, NULL); + } else // #endif - { - frame_final = allocate_avframe((AVPixelFormat)(video_st->codecpar->format), info.width, info.height, &bytes_final, NULL); - } + { + frame_final = allocate_avframe((AVPixelFormat)(video_st->codecpar->format), info.width, info.height, &bytes_final, NULL); + } #else AVFrame *frame_final = allocate_avframe(video_codec->pix_fmt, info.width, info.height, &bytes_final, NULL); #endif // Fill with data - AV_COPY_PICTURE_DATA(frame_source, (uint8_t*)pixels, PIX_FMT_RGBA, source_image_width, source_image_height); + AV_COPY_PICTURE_DATA(frame_source, (uint8_t*)pixels, PIX_FMT_RGBA, source_image_width, source_image_height); ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::process_video_packet", "frame->number", frame->number, "bytes_source", bytes_source, "bytes_final", bytes_final, "", -1, "", -1, "", -1); // Resize & convert pixel format @@ -1901,50 +1898,50 @@ bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame* fra // Assign the initial AVFrame PTS from the frame counter frame_final->pts = write_video_count; - #if IS_FFMPEG_3_2 + #if IS_FFMPEG_3_2 // #if defined(__linux__) - if (hw_en_on && hw_en_supported) { - if (!(hw_frame = av_frame_alloc())) { - fprintf(stderr, "Error code: av_hwframe_alloc\n"); - } - if (av_hwframe_get_buffer(video_codec->hw_frames_ctx, hw_frame, 0) < 0) { - fprintf(stderr, "Error code: av_hwframe_get_buffer\n"); - } - if (!hw_frame->hw_frames_ctx) { - fprintf(stderr, "Error hw_frames_ctx.\n"); - } - hw_frame->format = AV_PIX_FMT_NV12; - if ( av_hwframe_transfer_data(hw_frame, frame_final, 0) < 0) { - fprintf(stderr, "Error while transferring frame data to surface.\n"); - } - av_frame_copy_props(hw_frame, frame_final); - } + if (hw_en_on && hw_en_supported) { + if (!(hw_frame = av_frame_alloc())) { + fprintf(stderr, "Error code: av_hwframe_alloc\n"); + } + if (av_hwframe_get_buffer(video_codec->hw_frames_ctx, hw_frame, 0) < 0) { + fprintf(stderr, "Error code: av_hwframe_get_buffer\n"); + } + if (!hw_frame->hw_frames_ctx) { + fprintf(stderr, "Error hw_frames_ctx.\n"); + } + hw_frame->format = AV_PIX_FMT_NV12; + if ( av_hwframe_transfer_data(hw_frame, frame_final, 0) < 0) { + fprintf(stderr, "Error while transferring frame data to surface.\n"); + } + av_frame_copy_props(hw_frame, frame_final); + } // #endif - #endif + #endif /* encode the image */ int got_packet_ptr = 0; int error_code = 0; #if IS_FFMPEG_3_2 // Write video packet (latest version of FFmpeg) int frameFinished = 0; - int ret; + int ret; // #if defined(__linux__) - #if IS_FFMPEG_3_2 - if (hw_en_on && hw_en_supported) { - ret = avcodec_send_frame(video_codec, hw_frame); //hw_frame!!! - } else - #endif + #if IS_FFMPEG_3_2 + if (hw_en_on && hw_en_supported) { + ret = avcodec_send_frame(video_codec, hw_frame); //hw_frame!!! + } else + #endif // #endif - ret = avcodec_send_frame(video_codec, frame_final); + ret = avcodec_send_frame(video_codec, frame_final); error_code = ret; if (ret < 0 ) { ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::write_video_packet (Frame not sent)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); if (ret == AVERROR(EAGAIN) ) { cerr << "Frame EAGAIN" << "\n"; - } + } if (ret == AVERROR_EOF ) { cerr << "Frame AVERROR_EOF" << "\n"; - } + } avcodec_send_frame(video_codec, NULL); } else { @@ -1967,10 +1964,10 @@ bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame* fra error_code = avcodec_encode_video2(video_codec, &pkt, frame_final, &got_packet_ptr); if (error_code != 0 ) { cerr << "Frame AVERROR_EOF" << "\n"; - } + } if (got_packet_ptr == 0 ) { cerr << "Frame gotpacket error" << "\n"; - } + } #else // Write video packet (even older versions of FFmpeg) int video_outbuf_size = 200000; @@ -2026,14 +2023,14 @@ bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame* fra AV_FREE_PACKET(&pkt); #if IS_FFMPEG_3_2 // #if defined(__linux__) - if (hw_en_on && hw_en_supported) { - if (hw_frame) { - av_frame_free(&hw_frame); - hw_frame = NULL; - } - } + if (hw_en_on && hw_en_supported) { + if (hw_frame) { + av_frame_free(&hw_frame); + hw_frame = NULL; + } + } // #endif - #endif + #endif } // Success @@ -2061,14 +2058,14 @@ void FFmpegWriter::InitScalers(int source_width, int source_height) // Init the software scaler from FFMpeg #if IS_FFMPEG_3_2 // #if defined(__linux__) - if (hw_en_on && hw_en_supported) { - img_convert_ctx = sws_getContext(source_width, source_height, PIX_FMT_RGBA, info.width, info.height, AV_PIX_FMT_NV12, SWS_BILINEAR, NULL, NULL, NULL); - } else + if (hw_en_on && hw_en_supported) { + img_convert_ctx = sws_getContext(source_width, source_height, PIX_FMT_RGBA, info.width, info.height, AV_PIX_FMT_NV12, SWS_BILINEAR, NULL, NULL, NULL); + } else // #endif - #endif - { - img_convert_ctx = sws_getContext(source_width, source_height, PIX_FMT_RGBA, info.width, info.height, AV_GET_CODEC_PIXEL_FORMAT(video_st, video_st->codec), SWS_BILINEAR, NULL, NULL, NULL); - } + #endif + { + img_convert_ctx = sws_getContext(source_width, source_height, PIX_FMT_RGBA, info.width, info.height, AV_GET_CODEC_PIXEL_FORMAT(video_st, video_st->codec), SWS_BILINEAR, NULL, NULL, NULL); + } // Add rescaler to vector image_rescalers.push_back(img_convert_ctx); From 2ca84217bc18d7746183db4c76a32819af5f34d2 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Wed, 30 Jan 2019 09:58:54 -0800 Subject: [PATCH 092/223] First changes to use Settings instead of GetEnv --- include/Settings.h | 21 +++++++++++++ src/FFmpegReader.cpp | 75 +++++++++++++++++++++++++------------------- src/Settings.cpp | 8 +++++ 3 files changed, 71 insertions(+), 33 deletions(-) diff --git a/include/Settings.h b/include/Settings.h index ec26338b..0102479a 100644 --- a/include/Settings.h +++ b/include/Settings.h @@ -79,6 +79,9 @@ namespace openshot { /// Use video card for faster video decoding (if supported) bool HARDWARE_DECODE = false; + /// Use video codec for faster video decoding (if supported) + int HARDWARE_DECODER = 0; + /// Use video card for faster video encoding (if supported) bool HARDWARE_ENCODE = false; @@ -94,6 +97,24 @@ namespace openshot { /// Wait for OpenMP task to finish before continuing (used to limit threads on slower systems) bool WAIT_FOR_VIDEO_PROCESSING_TASK = false; + /// Number of threads of OpenMP + int OMP_THREADS = 6;//OPEN_MP_NUM_PROCESSORS + + /// Number of threads that ffmpeg uses + int FF_THREADS = 8;//FF_NUM_PROCESSORS + + /// Maximum rows that hardware decode can handle + int DE_LIMIT_HEIGHT_MAX = 1100; + + /// Maximum columns that hardware decode can handle + int DE_LIMIT_WIDTH_MAX = 1950; + + /// Which GPU to use to decode (0 is the first) + int HW_DE_DEVICE_SET = 0; + + /// Which GPU to use to encode (0 is the first) + int HW_EN_DEVICE_SET = 0; + /// Create or get an instance of this logger singleton (invoke the class with this method) static Settings * Instance(); }; diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index dd5f063b..03063d11 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -287,7 +287,7 @@ void FFmpegReader::Open() //hw_de_on = Settings::Instance()->HARDWARE_DECODE; // New version turn hardware decode on - { + /* { char *decoder_hw = NULL; decoder_hw = getenv( "HW_DECODER" ); if(decoder_hw != NULL) { @@ -299,7 +299,11 @@ void FFmpegReader::Open() } else { hw_de_on = 0; } - } + }*/ + // Newest versions + { + hw_de_on = (Settings::Instance()->HARDWARE_DECODER == 0 ? 0 : 1); + } // Open video file if (avformat_open_input(&pFormatCtx, path.c_str(), NULL, NULL) != 0) @@ -366,7 +370,8 @@ void FFmpegReader::Open() // Open Hardware Acceleration // Use the hw device given in the environment variable HW_DE_DEVICE_SET or the default if not set char *dev_hw = NULL; - char *decoder_hw = NULL; + //char *decoder_hw = NULL; + int i_decoder_hw = 0; char adapter[256]; char *adapter_ptr = NULL; int adapter_num; @@ -380,60 +385,62 @@ void FFmpegReader::Open() #if defined(__linux__) snprintf(adapter,sizeof(adapter),"/dev/dri/renderD%d", adapter_num+128); adapter_ptr = adapter; - decoder_hw = getenv( "HW_DECODER" ); - if(decoder_hw != NULL) { - if (strncmp(decoder_hw,"0",4) == 0) { //Will never happen + i_decoder_hw = Settings::Instance()->HARDWARE_DECODER; + switch (i_decoder_hw) { + case 0: hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; pCodecCtx->get_format = get_hw_dec_format_va; - } - if (strncmp(decoder_hw,"1",11) == 0) { + break; + case 1: hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; pCodecCtx->get_format = get_hw_dec_format_va; - } - if (strncmp(decoder_hw,"2",11) == 0) { + break; + case 2: hw_de_av_device_type = AV_HWDEVICE_TYPE_CUDA; pCodecCtx->get_format = get_hw_dec_format_cu; - } - } else { - hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; - pCodecCtx->get_format = get_hw_dec_format_va; - } + break; + default: + hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; + pCodecCtx->get_format = get_hw_dec_format_va; + break; + } #elif defined(_WIN32) adapter_ptr = NULL; - decoder_hw = getenv( "HW_DECODER" ); - if(decoder_hw != NULL) { - if (strncmp(decoder_hw,"0",4) == 0) { //Will never happen + i_decoder_hw = Settings::Instance()->HARDWARE_DECODER; + switch (i_decoder_hw) { + case 0: hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2; pCodecCtx->get_format = get_hw_dec_format_dx; - } - if (strncmp(decoder_hw,"3",19) == 0) { + break; + case 3: hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2; pCodecCtx->get_format = get_hw_dec_format_dx; - } - if (strncmp(decoder_hw,"4",19) == 0) { + break; + case 4: hw_de_av_device_type = AV_HWDEVICE_TYPE_D3D11VA; pCodecCtx->get_format = get_hw_dec_format_d3; - } - } else { + default: hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2; pCodecCtx->get_format = get_hw_dec_format_dx; + break; } #elif defined(__APPLE__) adapter_ptr = NULL; - decoder_hw = getenv( "HW_DECODER" ); - if(decoder_hw != NULL) { - if (strncmp(decoder_hw,"0",4) == 0) { //Will never happen + i_decoder_hw = Settings::Instance()->HARDWARE_DECODER; + switch (i_decoder_hw) { + case 0: hw_de_av_device_type = AV_HWDEVICE_TYPE_QSV; pCodecCtx->get_format = get_hw_dec_format_qs; - } - if (strncmp(decoder_hw,"5",11) == 0) { + break; + case 5: hw_de_av_device_type = AV_HWDEVICE_TYPE_QSV; pCodecCtx->get_format = get_hw_dec_format_qs; - } - } else { + break; + default: hw_de_av_device_type = AV_HWDEVICE_TYPE_QSV; pCodecCtx->get_format = get_hw_dec_format_qs; + break; } #endif } @@ -536,8 +543,10 @@ void FFmpegReader::Open() } else { int max_h, max_w; - max_h = ((getenv( "LIMIT_HEIGHT_MAX" )==NULL) ? MAX_SUPPORTED_HEIGHT : atoi(getenv( "LIMIT_HEIGHT_MAX" ))); - max_w = ((getenv( "LIMIT_WIDTH_MAX" )==NULL) ? MAX_SUPPORTED_WIDTH : atoi(getenv( "LIMIT_WIDTH_MAX" ))); + //max_h = ((getenv( "LIMIT_HEIGHT_MAX" )==NULL) ? MAX_SUPPORTED_HEIGHT : atoi(getenv( "LIMIT_HEIGHT_MAX" ))); + max_h = Settings::Instance()->DE_LIMIT_HEIGHT_MAX; + //max_w = ((getenv( "LIMIT_WIDTH_MAX" )==NULL) ? MAX_SUPPORTED_WIDTH : atoi(getenv( "LIMIT_WIDTH_MAX" ))); + max_w = Settings::Instance()->DE_LIMIT_WIDTH_MAX; ZmqLogger::Instance()->AppendDebugMethod("Constraints could not be found using default limit\n", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); //cerr << "Constraints could not be found using default limit\n"; if (pCodecCtx->coded_width < 0 || diff --git a/src/Settings.cpp b/src/Settings.cpp index b13f0f5a..961e3682 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -41,11 +41,19 @@ Settings *Settings::Instance() // Create the actual instance of logger only once m_pInstance = new Settings; m_pInstance->HARDWARE_DECODE = false; + m_pInstance->HARDWARE_DECODER = 0; m_pInstance->HARDWARE_ENCODE = false; m_pInstance->HIGH_QUALITY_SCALING = false; m_pInstance->MAX_WIDTH = 0; m_pInstance->MAX_HEIGHT = 0; m_pInstance->WAIT_FOR_VIDEO_PROCESSING_TASK = false; + m_pInstance->OMP_THREADS = 6;//OPEN_MP_NUM_PROCESSORS + m_pInstance->FF_THREADS = 8;//FF_NUM_PROCESSORS + m_pInstance->DE_LIMIT_HEIGHT_MAX = 1100; + m_pInstance->DE_LIMIT_WIDTH_MAX = 1950; + m_pInstance->HW_DE_DEVICE_SET = 0; + m_pInstance->HW_EN_DEVICE_SET = 0; + } return m_pInstance; From 596ae0efac24144aa85ceb5a8cd10b6b1e8d8a48 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Wed, 30 Jan 2019 20:44:36 -0800 Subject: [PATCH 093/223] More changes to move to Settings, still needs work --- include/OpenMPUtilities.h | 11 +++++++++-- include/Settings.h | 2 +- src/FFmpegReader.cpp | 29 +++++++++++++++++------------ src/FFmpegWriter.cpp | 17 +++++++++++------ src/Settings.cpp | 2 +- 5 files changed, 39 insertions(+), 22 deletions(-) diff --git a/include/OpenMPUtilities.h b/include/OpenMPUtilities.h index 9af58150..f0adfd4b 100644 --- a/include/OpenMPUtilities.h +++ b/include/OpenMPUtilities.h @@ -32,9 +32,16 @@ #include #include +#include "../include/Settings.h" + +using namespace std; +using namespace openshot; + // Calculate the # of OpenMP Threads to allow -#define OPEN_MP_NUM_PROCESSORS ((getenv( "LIMIT_OMP_THREADS" )==NULL) ? omp_get_num_procs() : (min(omp_get_num_procs(), max(2, atoi(getenv( "LIMIT_OMP_THREADS" ))) ))) -#define FF_NUM_PROCESSORS ((getenv( "LIMIT_FF_THREADS" )==NULL) ? omp_get_num_procs() : (min(omp_get_num_procs(), max(2, atoi(getenv( "LIMIT_FF_THREADS" ))) ))) +//#define OPEN_MP_NUM_PROCESSORS ((getenv( "LIMIT_OMP_THREADS" )==NULL) ? omp_get_num_procs() : (min(omp_get_num_procs(), max(2, atoi(getenv( "LIMIT_OMP_THREADS" ))) ))) +//#define FF_NUM_PROCESSORS ((getenv( "LIMIT_FF_THREADS" )==NULL) ? omp_get_num_procs() : (min(omp_get_num_procs(), max(2, atoi(getenv( "LIMIT_FF_THREADS" ))) ))) +#define OPEN_MP_NUM_PROCESSORS (min(omp_get_num_procs(), max(2, openshot::Settings::Instance()->OMP_THREADS) )) +#define FF_NUM_PROCESSORS (min(omp_get_num_procs(), max(2, openshot::Settings::Instance()->FF_THREADS) )) diff --git a/include/Settings.h b/include/Settings.h index 0102479a..15ff5fa3 100644 --- a/include/Settings.h +++ b/include/Settings.h @@ -98,7 +98,7 @@ namespace openshot { bool WAIT_FOR_VIDEO_PROCESSING_TASK = false; /// Number of threads of OpenMP - int OMP_THREADS = 6;//OPEN_MP_NUM_PROCESSORS + int OMP_THREADS = 12;//OPEN_MP_NUM_PROCESSORS /// Number of threads that ffmpeg uses int FF_THREADS = 8;//FF_NUM_PROCESSORS diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 03063d11..b45145b7 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -284,7 +284,7 @@ void FFmpegReader::Open() hw_de_on = (val[0] == '1')? 1 : 0; }*/ - //hw_de_on = Settings::Instance()->HARDWARE_DECODE; + //hw_de_on = openshot::Settings::Instance()->HARDWARE_DECODE; // New version turn hardware decode on /* { @@ -302,7 +302,7 @@ void FFmpegReader::Open() }*/ // Newest versions { - hw_de_on = (Settings::Instance()->HARDWARE_DECODER == 0 ? 0 : 1); + hw_de_on = (openshot::Settings::Instance()->HARDWARE_DECODER == 0 ? 0 : 1); } // Open video file @@ -369,23 +369,25 @@ void FFmpegReader::Open() if (hw_de_on && hw_de_supported) { // Open Hardware Acceleration // Use the hw device given in the environment variable HW_DE_DEVICE_SET or the default if not set - char *dev_hw = NULL; + //char *dev_hw = NULL; //char *decoder_hw = NULL; int i_decoder_hw = 0; char adapter[256]; char *adapter_ptr = NULL; int adapter_num; - dev_hw = getenv( "HW_DE_DEVICE_SET" ); // The first card is 0 +/* dev_hw = getenv( "HW_DE_DEVICE_SET" ); // The first card is 0 if( dev_hw != NULL) { adapter_num = atoi(dev_hw); } else { adapter_num = 0; - } + }*/ + adapter_num = openshot::Settings::Instance()->HW_DE_DEVICE_SET; + fprintf(stderr, "\n\nDecodiing Device Nr: %d\n", adapter_num); if (adapter_num < 3 && adapter_num >=0) { #if defined(__linux__) snprintf(adapter,sizeof(adapter),"/dev/dri/renderD%d", adapter_num+128); adapter_ptr = adapter; - i_decoder_hw = Settings::Instance()->HARDWARE_DECODER; + i_decoder_hw = openshot::Settings::Instance()->HARDWARE_DECODER; switch (i_decoder_hw) { case 0: hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; @@ -407,7 +409,7 @@ void FFmpegReader::Open() #elif defined(_WIN32) adapter_ptr = NULL; - i_decoder_hw = Settings::Instance()->HARDWARE_DECODER; + i_decoder_hw = openshot::Settings::Instance()->HARDWARE_DECODER; switch (i_decoder_hw) { case 0: hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2; @@ -427,7 +429,7 @@ void FFmpegReader::Open() } #elif defined(__APPLE__) adapter_ptr = NULL; - i_decoder_hw = Settings::Instance()->HARDWARE_DECODER; + i_decoder_hw = openshot::Settings::Instance()->HARDWARE_DECODER; switch (i_decoder_hw) { case 0: hw_de_av_device_type = AV_HWDEVICE_TYPE_QSV; @@ -456,6 +458,9 @@ void FFmpegReader::Open() #elif defined(__APPLE__) if( adapter_ptr != NULL ) { #endif + ZmqLogger::Instance()->AppendDebugMethod("Decode Device present using device", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); + } + else { adapter_ptr = NULL; // use default ZmqLogger::Instance()->AppendDebugMethod("Decode Device not present using default", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); } @@ -544,9 +549,9 @@ void FFmpegReader::Open() else { int max_h, max_w; //max_h = ((getenv( "LIMIT_HEIGHT_MAX" )==NULL) ? MAX_SUPPORTED_HEIGHT : atoi(getenv( "LIMIT_HEIGHT_MAX" ))); - max_h = Settings::Instance()->DE_LIMIT_HEIGHT_MAX; + max_h = openshot::Settings::Instance()->DE_LIMIT_HEIGHT_MAX; //max_w = ((getenv( "LIMIT_WIDTH_MAX" )==NULL) ? MAX_SUPPORTED_WIDTH : atoi(getenv( "LIMIT_WIDTH_MAX" ))); - max_w = Settings::Instance()->DE_LIMIT_WIDTH_MAX; + max_w = openshot::Settings::Instance()->DE_LIMIT_WIDTH_MAX; ZmqLogger::Instance()->AppendDebugMethod("Constraints could not be found using default limit\n", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); //cerr << "Constraints could not be found using default limit\n"; if (pCodecCtx->coded_width < 0 || @@ -1354,10 +1359,10 @@ void FFmpegReader::ProcessVideoPacket(int64_t requested_frame) // without losing quality. NOTE: We cannot go smaller than the timeline itself, or the add_layer timeline // method will scale it back to timeline size before scaling it smaller again. This needs to be fixed in // the future. - int max_width = Settings::Instance()->MAX_WIDTH; + int max_width = openshot::Settings::Instance()->MAX_WIDTH; if (max_width <= 0) max_width = info.width; - int max_height = Settings::Instance()->MAX_HEIGHT; + int max_height = openshot::Settings::Instance()->MAX_HEIGHT; if (max_height <= 0) max_height = info.height; diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index 6b8f240f..6a947d4c 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -1293,15 +1293,17 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) #if IS_FFMPEG_3_2 if (hw_en_on && hw_en_supported) { - char *dev_hw = NULL; + //char *dev_hw = NULL; char adapter[256]; char *adapter_ptr = NULL; int adapter_num; // Use the hw device given in the environment variable HW_EN_DEVICE_SET or the default if not set - dev_hw = getenv( "HW_EN_DEVICE_SET" ); - if( dev_hw != NULL) { - adapter_num = atoi(dev_hw); - if (adapter_num < 3 && adapter_num >=0) { + //dev_hw = getenv( "HW_EN_DEVICE_SET" ); + //if( dev_hw != NULL) { + // adapter_num = atoi(dev_hw); + adapter_num = openshot::Settings::Instance()->HW_EN_DEVICE_SET; + fprintf(stderr, "\n\nEncodiing Device Nr: %d\n", adapter_num); + if (adapter_num < 3 && adapter_num >=0) { #if defined(__linux__) snprintf(adapter,sizeof(adapter),"/dev/dri/renderD%d", adapter_num+128); // Maybe 127 is better because the first card would be 1?! @@ -1315,7 +1317,7 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) else { adapter_ptr = NULL; // Just to be sure } - } +// } // Check if it is there and writable #if defined(__linux__) if( adapter_ptr != NULL && access( adapter_ptr, W_OK ) == -1 ) { @@ -1324,6 +1326,9 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) #elif defined(__APPLE__) if( adapter_ptr != NULL ) { #endif + ZmqLogger::Instance()->AppendDebugMethod("Encode Device present using device", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); + } + else { adapter_ptr = NULL; // use default //cerr << "\n\n\nEncode Device not present using default\n\n\n"; ZmqLogger::Instance()->AppendDebugMethod("Encode Device not present using default", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); diff --git a/src/Settings.cpp b/src/Settings.cpp index 961e3682..4f502341 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -47,7 +47,7 @@ Settings *Settings::Instance() m_pInstance->MAX_WIDTH = 0; m_pInstance->MAX_HEIGHT = 0; m_pInstance->WAIT_FOR_VIDEO_PROCESSING_TASK = false; - m_pInstance->OMP_THREADS = 6;//OPEN_MP_NUM_PROCESSORS + m_pInstance->OMP_THREADS = 12;//OPEN_MP_NUM_PROCESSORS m_pInstance->FF_THREADS = 8;//FF_NUM_PROCESSORS m_pInstance->DE_LIMIT_HEIGHT_MAX = 1100; m_pInstance->DE_LIMIT_WIDTH_MAX = 1950; From 2e635e3d87a4ef2f902b097bd9914f77a9ce87cf Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Thu, 31 Jan 2019 09:42:26 -0800 Subject: [PATCH 094/223] Formating and Cleanup Fix forgotten break in switch --- include/OpenMPUtilities.h | 2 - include/Settings.h | 4 +- src/FFmpegReader.cpp | 317 ++++++++++++++++---------------------- src/FFmpegWriter.cpp | 27 +--- src/Settings.cpp | 4 +- 5 files changed, 144 insertions(+), 210 deletions(-) diff --git a/include/OpenMPUtilities.h b/include/OpenMPUtilities.h index f0adfd4b..0411b6ba 100644 --- a/include/OpenMPUtilities.h +++ b/include/OpenMPUtilities.h @@ -38,8 +38,6 @@ using namespace std; using namespace openshot; // Calculate the # of OpenMP Threads to allow -//#define OPEN_MP_NUM_PROCESSORS ((getenv( "LIMIT_OMP_THREADS" )==NULL) ? omp_get_num_procs() : (min(omp_get_num_procs(), max(2, atoi(getenv( "LIMIT_OMP_THREADS" ))) ))) -//#define FF_NUM_PROCESSORS ((getenv( "LIMIT_FF_THREADS" )==NULL) ? omp_get_num_procs() : (min(omp_get_num_procs(), max(2, atoi(getenv( "LIMIT_FF_THREADS" ))) ))) #define OPEN_MP_NUM_PROCESSORS (min(omp_get_num_procs(), max(2, openshot::Settings::Instance()->OMP_THREADS) )) #define FF_NUM_PROCESSORS (min(omp_get_num_procs(), max(2, openshot::Settings::Instance()->FF_THREADS) )) diff --git a/include/Settings.h b/include/Settings.h index 15ff5fa3..b01d9590 100644 --- a/include/Settings.h +++ b/include/Settings.h @@ -98,10 +98,10 @@ namespace openshot { bool WAIT_FOR_VIDEO_PROCESSING_TASK = false; /// Number of threads of OpenMP - int OMP_THREADS = 12;//OPEN_MP_NUM_PROCESSORS + int OMP_THREADS = 12; /// Number of threads that ffmpeg uses - int FF_THREADS = 8;//FF_NUM_PROCESSORS + int FF_THREADS = 8; /// Maximum rows that hardware decode can handle int DE_LIMIT_HEIGHT_MAX = 1100; diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index b45145b7..2e938f35 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -76,7 +76,6 @@ typedef struct VAAPIDecodeContext { using namespace openshot; int hw_de_on = 1; // Is set in UI -//int hw_de_supported = 0; // Is set by FFmpegReader #if IS_FFMPEG_3_2 AVPixelFormat hw_de_av_pix_fmt_global = AV_PIX_FMT_NONE; AVHWDeviceType hw_de_av_device_type_global = AV_HWDEVICE_TYPE_NONE; @@ -158,91 +157,91 @@ bool AudioLocation::is_near(AudioLocation location, int samples_per_frame, int64 #if defined(__linux__) static enum AVPixelFormat get_hw_dec_format_va(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) { - const enum AVPixelFormat *p; + const enum AVPixelFormat *p; - for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { + for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { switch (*p) { case AV_PIX_FMT_VAAPI: hw_de_av_pix_fmt_global = AV_PIX_FMT_VAAPI; hw_de_av_device_type_global = AV_HWDEVICE_TYPE_VAAPI; - return *p; + return *p; break; - } - } - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); - return AV_PIX_FMT_NONE; - } + } + } + ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); + return AV_PIX_FMT_NONE; +} static enum AVPixelFormat get_hw_dec_format_cu(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) { - const enum AVPixelFormat *p; + const enum AVPixelFormat *p; - for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { + for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { switch (*p) { case AV_PIX_FMT_CUDA: hw_de_av_pix_fmt_global = AV_PIX_FMT_CUDA; hw_de_av_device_type_global = AV_HWDEVICE_TYPE_CUDA; - return *p; + return *p; break; - } - } - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); - return AV_PIX_FMT_NONE; - } + } + } + ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); + return AV_PIX_FMT_NONE; +} #endif #if defined(_WIN32) static enum AVPixelFormat get_hw_dec_format_dx(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) { - const enum AVPixelFormat *p; + const enum AVPixelFormat *p; - for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { + for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { switch (*p) { case AV_PIX_FMT_DXVA2_VLD: hw_de_av_pix_fmt_global = AV_PIX_FMT_DXVA2_VLD; hw_de_av_device_type_global = AV_HWDEVICE_TYPE_DXVA2; - return *p; + return *p; break; - } - } - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); - return AV_PIX_FMT_NONE; - } + } + } + ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); + return AV_PIX_FMT_NONE; +} static enum AVPixelFormat get_hw_dec_format_d3(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) { - const enum AVPixelFormat *p; + const enum AVPixelFormat *p; - for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { + for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { switch (*p) { case AV_PIX_FMT_D3D11: hw_de_av_pix_fmt_global = AV_PIX_FMT_D3D11; hw_de_av_device_type_global = AV_HWDEVICE_TYPE_D3D11VA; - return *p; + return *p; break; - } - } - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); - return AV_PIX_FMT_NONE; - } + } + } + ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); + return AV_PIX_FMT_NONE; +} #endif #if defined(__APPLE__) static enum AVPixelFormat get_hw_dec_format_qs(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) { - const enum AVPixelFormat *p; + const enum AVPixelFormat *p; - for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { + for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { switch (*p) { case AV_PIX_FMT_QSV: hw_de_av_pix_fmt_global = AV_PIX_FMT_QSV; hw_de_av_device_type_global = AV_HWDEVICE_TYPE_QSV; - return *p; + return *p; break; } - } + } ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); - return AV_PIX_FMT_NONE; + return AV_PIX_FMT_NONE; } #endif @@ -274,33 +273,6 @@ void FFmpegReader::Open() { // Initialize format context pFormatCtx = NULL; - - // Old version turn hardware decode on - /*char * val = getenv( "OS2_DECODE_HW" ); - if (val == NULL) { - hw_de_on = 0; - } - else{ - hw_de_on = (val[0] == '1')? 1 : 0; - }*/ - - //hw_de_on = openshot::Settings::Instance()->HARDWARE_DECODE; - - // New version turn hardware decode on - /* { - char *decoder_hw = NULL; - decoder_hw = getenv( "HW_DECODER" ); - if(decoder_hw != NULL) { - if( strncmp(decoder_hw,"0",4) == 0) { - hw_de_on = 0; - } else { - hw_de_on = 1; - } - } else { - hw_de_on = 0; - } - }*/ - // Newest versions { hw_de_on = (openshot::Settings::Instance()->HARDWARE_DECODER == 0 ? 0 : 1); } @@ -368,106 +340,96 @@ void FFmpegReader::Open() #if IS_FFMPEG_3_2 if (hw_de_on && hw_de_supported) { // Open Hardware Acceleration - // Use the hw device given in the environment variable HW_DE_DEVICE_SET or the default if not set - //char *dev_hw = NULL; - //char *decoder_hw = NULL; int i_decoder_hw = 0; - char adapter[256]; - char *adapter_ptr = NULL; - int adapter_num; -/* dev_hw = getenv( "HW_DE_DEVICE_SET" ); // The first card is 0 - if( dev_hw != NULL) { - adapter_num = atoi(dev_hw); - } else { - adapter_num = 0; - }*/ + char adapter[256]; + char *adapter_ptr = NULL; + int adapter_num; adapter_num = openshot::Settings::Instance()->HW_DE_DEVICE_SET; fprintf(stderr, "\n\nDecodiing Device Nr: %d\n", adapter_num); - if (adapter_num < 3 && adapter_num >=0) { - #if defined(__linux__) - snprintf(adapter,sizeof(adapter),"/dev/dri/renderD%d", adapter_num+128); - adapter_ptr = adapter; + if (adapter_num < 3 && adapter_num >=0) { + #if defined(__linux__) + snprintf(adapter,sizeof(adapter),"/dev/dri/renderD%d", adapter_num+128); + adapter_ptr = adapter; i_decoder_hw = openshot::Settings::Instance()->HARDWARE_DECODER; switch (i_decoder_hw) { case 0: - hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; - pCodecCtx->get_format = get_hw_dec_format_va; + hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; + pCodecCtx->get_format = get_hw_dec_format_va; break; case 1: - hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; - pCodecCtx->get_format = get_hw_dec_format_va; + hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; + pCodecCtx->get_format = get_hw_dec_format_va; break; case 2: - hw_de_av_device_type = AV_HWDEVICE_TYPE_CUDA; - pCodecCtx->get_format = get_hw_dec_format_cu; + hw_de_av_device_type = AV_HWDEVICE_TYPE_CUDA; + pCodecCtx->get_format = get_hw_dec_format_cu; break; default: - hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; - pCodecCtx->get_format = get_hw_dec_format_va; + hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; + pCodecCtx->get_format = get_hw_dec_format_va; break; } - #elif defined(_WIN32) - adapter_ptr = NULL; + #elif defined(_WIN32) + adapter_ptr = NULL; i_decoder_hw = openshot::Settings::Instance()->HARDWARE_DECODER; switch (i_decoder_hw) { case 0: - hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2; - pCodecCtx->get_format = get_hw_dec_format_dx; - break; + hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2; + pCodecCtx->get_format = get_hw_dec_format_dx; + break; case 3: - hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2; - pCodecCtx->get_format = get_hw_dec_format_dx; - break; - case 4: - hw_de_av_device_type = AV_HWDEVICE_TYPE_D3D11VA; - pCodecCtx->get_format = get_hw_dec_format_d3; - default: - hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2; - pCodecCtx->get_format = get_hw_dec_format_dx; + hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2; + pCodecCtx->get_format = get_hw_dec_format_dx; break; - } - #elif defined(__APPLE__) - adapter_ptr = NULL; + case 4: + hw_de_av_device_type = AV_HWDEVICE_TYPE_D3D11VA; + pCodecCtx->get_format = get_hw_dec_format_d3; + break; + default: + hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2; + pCodecCtx->get_format = get_hw_dec_format_dx; + break; + } + #elif defined(__APPLE__) + adapter_ptr = NULL; i_decoder_hw = openshot::Settings::Instance()->HARDWARE_DECODER; switch (i_decoder_hw) { case 0: - hw_de_av_device_type = AV_HWDEVICE_TYPE_QSV; - pCodecCtx->get_format = get_hw_dec_format_qs; - break; - case 5: - hw_de_av_device_type = AV_HWDEVICE_TYPE_QSV; - pCodecCtx->get_format = get_hw_dec_format_qs; - break; - default: - hw_de_av_device_type = AV_HWDEVICE_TYPE_QSV; - pCodecCtx->get_format = get_hw_dec_format_qs; + hw_de_av_device_type = AV_HWDEVICE_TYPE_QSV; + pCodecCtx->get_format = get_hw_dec_format_qs; break; - } - #endif - } - else { - adapter_ptr = NULL; // Just to be sure - } - //} - // Check if it is there and writable - #if defined(__linux__) - if( adapter_ptr != NULL && access( adapter_ptr, W_OK ) == -1 ) { - #elif defined(_WIN32) - if( adapter_ptr != NULL ) { - #elif defined(__APPLE__) - if( adapter_ptr != NULL ) { - #endif + case 5: + hw_de_av_device_type = AV_HWDEVICE_TYPE_QSV; + pCodecCtx->get_format = get_hw_dec_format_qs; + break; + default: + hw_de_av_device_type = AV_HWDEVICE_TYPE_QSV; + pCodecCtx->get_format = get_hw_dec_format_qs; + break; + } + #endif + } + else { + adapter_ptr = NULL; // Just to be sure + } + // Check if it is there and writable + #if defined(__linux__) + if( adapter_ptr != NULL && access( adapter_ptr, W_OK ) == -1 ) { + #elif defined(_WIN32) + if( adapter_ptr != NULL ) { + #elif defined(__APPLE__) + if( adapter_ptr != NULL ) { + #endif ZmqLogger::Instance()->AppendDebugMethod("Decode Device present using device", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); } else { - adapter_ptr = NULL; // use default - ZmqLogger::Instance()->AppendDebugMethod("Decode Device not present using default", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); - } + adapter_ptr = NULL; // use default + ZmqLogger::Instance()->AppendDebugMethod("Decode Device not present using default", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); + } hw_device_ctx = NULL; // Here the first hardware initialisations are made if (av_hwdevice_ctx_create(&hw_device_ctx, hw_de_av_device_type, adapter_ptr, NULL, 0) >= 0) { - cerr << "\n\n**** HW device create OK ******** \n\n"; if (!(pCodecCtx->hw_device_ctx = av_buffer_ref(hw_device_ctx))) { throw InvalidCodec("Hardware device reference create failed.", path); } @@ -523,8 +485,7 @@ void FFmpegReader::Open() pCodecCtx->coded_height < constraints->min_height || pCodecCtx->coded_width > constraints->max_width || pCodecCtx->coded_height > constraints->max_height) { - ZmqLogger::Instance()->AppendDebugMethod("DIMENSIONS ARE TOO LARGE for hardware acceleration\n", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); - //cerr << "DIMENSIONS ARE TOO LARGE for hardware acceleration\n"; + ZmqLogger::Instance()->AppendDebugMethod("DIMENSIONS ARE TOO LARGE for hardware acceleration\n", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); hw_de_supported = 0; retry_decode_open = 1; AV_FREE_CONTEXT(pCodecCtx); @@ -535,10 +496,7 @@ void FFmpegReader::Open() } else { // All is just peachy - ZmqLogger::Instance()->AppendDebugMethod("\nDecode hardware acceleration is used\n", "Min width :", constraints->min_width, "Min Height :", constraints->min_height, "MaxWidth :", constraints->max_width, "MaxHeight :", constraints->max_height, "Frame width :", pCodecCtx->coded_width, "Frame height :", pCodecCtx->coded_height); - //cerr << "\nDecode hardware acceleration is used\n"; - //cerr << "Min width : " << constraints->min_width << " MinHeight : " << constraints->min_height << "MaxWidth : " << constraints->max_width << "MaxHeight : " << constraints->max_height << "\n"; - //cerr << "Frame width : " << pCodecCtx->coded_width << " Frame height : " << pCodecCtx->coded_height << "\n"; + ZmqLogger::Instance()->AppendDebugMethod("\nDecode hardware acceleration is used\n", "Min width :", constraints->min_width, "Min Height :", constraints->min_height, "MaxWidth :", constraints->max_width, "MaxHeight :", constraints->max_height, "Frame width :", pCodecCtx->coded_width, "Frame height :", pCodecCtx->coded_height); retry_decode_open = 0; } av_hwframe_constraints_free(&constraints); @@ -552,16 +510,13 @@ void FFmpegReader::Open() max_h = openshot::Settings::Instance()->DE_LIMIT_HEIGHT_MAX; //max_w = ((getenv( "LIMIT_WIDTH_MAX" )==NULL) ? MAX_SUPPORTED_WIDTH : atoi(getenv( "LIMIT_WIDTH_MAX" ))); max_w = openshot::Settings::Instance()->DE_LIMIT_WIDTH_MAX; - ZmqLogger::Instance()->AppendDebugMethod("Constraints could not be found using default limit\n", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); + ZmqLogger::Instance()->AppendDebugMethod("Constraints could not be found using default limit\n", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); //cerr << "Constraints could not be found using default limit\n"; if (pCodecCtx->coded_width < 0 || pCodecCtx->coded_height < 0 || pCodecCtx->coded_width > max_w || pCodecCtx->coded_height > max_h ) { - ZmqLogger::Instance()->AppendDebugMethod("DIMENSIONS ARE TOO LARGE for hardware acceleration\n", "Max Width :", max_w, "Max Height :", max_h, "Frame width :", pCodecCtx->coded_width, "Frame height :", pCodecCtx->coded_height, "", -1, "", -1); - //cerr << "DIMENSIONS ARE TOO LARGE for hardware acceleration\n"; - //cerr << " Max Width : " << max_w << " Height : " << max_h << "\n"; - //cerr << "Frame width : " << pCodecCtx->coded_width << " Frame height : " << pCodecCtx->coded_height << "\n"; + ZmqLogger::Instance()->AppendDebugMethod("DIMENSIONS ARE TOO LARGE for hardware acceleration\n", "Max Width :", max_w, "Max Height :", max_h, "Frame width :", pCodecCtx->coded_width, "Frame height :", pCodecCtx->coded_height, "", -1, "", -1); hw_de_supported = 0; retry_decode_open = 1; AV_FREE_CONTEXT(pCodecCtx); @@ -571,18 +526,14 @@ void FFmpegReader::Open() } } else { - ZmqLogger::Instance()->AppendDebugMethod("\nDecode hardware acceleration is used\n", "Max Width :", max_w, "Max Height :", max_h, "Frame width :", pCodecCtx->coded_width, "Frame height :", pCodecCtx->coded_height, "", -1, "", -1); - //cerr << "\nDecode hardware acceleration is used\n"; - //cerr << " Max Width : " << max_w << " Height : " << max_h << "\n"; - //cerr << "Frame width : " << pCodecCtx->coded_width << " Frame height : " << pCodecCtx->coded_height << "\n"; + ZmqLogger::Instance()->AppendDebugMethod("\nDecode hardware acceleration is used\n", "Max Width :", max_w, "Max Height :", max_h, "Frame width :", pCodecCtx->coded_width, "Frame height :", pCodecCtx->coded_height, "", -1, "", -1); retry_decode_open = 0; } } } // if hw_de_on && hw_de_supported - else { - ZmqLogger::Instance()->AppendDebugMethod("\nDecode in software is used\n", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); - //cerr << "\nDecode in software is used\n"; - } + else { + ZmqLogger::Instance()->AppendDebugMethod("\nDecode in software is used\n", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); + } #else retry_decode_open = 0; #endif @@ -897,8 +848,8 @@ std::shared_ptr FFmpegReader::GetFrame(int64_t requested_frame) } else { - #pragma omp critical (ReadStream) - { + #pragma omp critical (ReadStream) + { // Check the cache a 2nd time (due to a potential previous lock) if (has_missing_frames) CheckMissingFrame(requested_frame); @@ -944,8 +895,8 @@ std::shared_ptr FFmpegReader::GetFrame(int64_t requested_frame) frame = ReadStream(requested_frame); } } - } //omp critical - return frame; + } //omp critical + return frame; } } @@ -1141,11 +1092,11 @@ int FFmpegReader::GetNextPacket() found_packet = av_read_frame(pFormatCtx, next_packet); - if (packet) { - // Remove previous packet before getting next one - RemoveAVPacket(packet); - packet = NULL; - } + if (packet) { + // Remove previous packet before getting next one + RemoveAVPacket(packet); + packet = NULL; + } if (found_packet >= 0) { @@ -1191,15 +1142,15 @@ bool FFmpegReader::GetAVFrame() pFrame = new AVFrame(); while (ret >= 0) { ret = avcodec_receive_frame(pCodecCtx, next_frame2); - if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { - break; + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + break; } if (ret != 0) { ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (invalid return frame received)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); } if (hw_de_on && hw_de_supported) { int err; - if (next_frame2->format == hw_de_av_pix_fmt) { + if (next_frame2->format == hw_de_av_pix_fmt) { next_frame->format = AV_PIX_FMT_YUV420P; if ((err = av_hwframe_transfer_data(next_frame,next_frame2,0)) < 0) { ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (Failed to transfer data to output frame)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); @@ -1437,7 +1388,7 @@ void FFmpegReader::ProcessVideoPacket(int64_t requested_frame) // Resize / Convert to RGB sws_scale(img_convert_ctx, my_frame->data, my_frame->linesize, 0, - original_height, pFrameRGB->data, pFrameRGB->linesize); + original_height, pFrameRGB->data, pFrameRGB->linesize); // Create or get the existing frame object std::shared_ptr f = CreateFrame(current_frame); @@ -2257,8 +2208,8 @@ bool FFmpegReader::CheckMissingFrame(int64_t requested_frame) void FFmpegReader::CheckWorkingFrames(bool end_of_stream, int64_t requested_frame) { // Loop through all working queue frames - bool checked_count_tripped = false; - int max_checked_count = 80; + bool checked_count_tripped = false; + int max_checked_count = 80; while (true) { @@ -2291,11 +2242,11 @@ void FFmpegReader::CheckWorkingFrames(bool end_of_stream, int64_t requested_fram // Get check count for this frame checked_frames_size = checked_frames.size(); - if (!checked_count_tripped || f->number >= requested_frame) - checked_count = checked_frames[f->number]; - else - // Force checked count over the limit - checked_count = max_checked_count; + if (!checked_count_tripped || f->number >= requested_frame) + checked_count = checked_frames[f->number]; + else + // Force checked count over the limit + checked_count = max_checked_count; } if (previous_packet_location.frame == f->number && !end_of_stream) @@ -2311,8 +2262,8 @@ void FFmpegReader::CheckWorkingFrames(bool end_of_stream, int64_t requested_fram // Debug output ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::CheckWorkingFrames (exceeded checked_count)", "requested_frame", requested_frame, "frame_number", f->number, "is_video_ready", is_video_ready, "is_audio_ready", is_audio_ready, "checked_count", checked_count, "checked_frames_size", checked_frames_size); - // Trigger checked count tripped mode (clear out all frames before requested frame) - checked_count_tripped = true; + // Trigger checked count tripped mode (clear out all frames before requested frame) + checked_count_tripped = true; if (info.has_video && !is_video_ready && last_video_frame) { // Copy image from last frame @@ -2357,8 +2308,8 @@ void FFmpegReader::CheckWorkingFrames(bool end_of_stream, int64_t requested_fram missing_frames.Add(f); } - // Remove from 'checked' count - checked_frames.erase(f->number); + // Remove from 'checked' count + checked_frames.erase(f->number); } // Remove frame from working cache @@ -2482,10 +2433,10 @@ void FFmpegReader::CheckFPS() // Remove AVFrame from cache (and deallocate it's memory) void FFmpegReader::RemoveAVFrame(AVFrame* remove_frame) { - // Remove pFrame (if exists) - if (remove_frame) - { - // Free memory + // Remove pFrame (if exists) + if (remove_frame) + { + // Free memory #pragma omp critical (packet_cache) { av_freep(&remove_frame->data[0]); @@ -2500,7 +2451,7 @@ void FFmpegReader::RemoveAVFrame(AVFrame* remove_frame) void FFmpegReader::RemoveAVPacket(AVPacket* remove_packet) { // deallocate memory for packet - AV_FREE_PACKET(remove_packet); + AV_FREE_PACKET(remove_packet); // Delete the object delete remove_packet; diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index 6a947d4c..14171894 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -462,7 +462,7 @@ void FFmpegWriter::SetOption(StreamType stream, string name, string value) } c->bit_rate = (int)(mbs); } - } + } #endif } @@ -1298,9 +1298,6 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) char *adapter_ptr = NULL; int adapter_num; // Use the hw device given in the environment variable HW_EN_DEVICE_SET or the default if not set - //dev_hw = getenv( "HW_EN_DEVICE_SET" ); - //if( dev_hw != NULL) { - // adapter_num = atoi(dev_hw); adapter_num = openshot::Settings::Instance()->HW_EN_DEVICE_SET; fprintf(stderr, "\n\nEncodiing Device Nr: %d\n", adapter_num); if (adapter_num < 3 && adapter_num >=0) { @@ -1317,7 +1314,6 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) else { adapter_ptr = NULL; // Just to be sure } -// } // Check if it is there and writable #if defined(__linux__) if( adapter_ptr != NULL && access( adapter_ptr, W_OK ) == -1 ) { @@ -1539,9 +1535,9 @@ void FFmpegWriter::write_audio_packets(bool final) // Remove converted audio av_freep(&(audio_frame->data[0])); - AV_FREE_FRAME(&audio_frame); + AV_FREE_FRAME(&audio_frame); av_freep(&audio_converted->data[0]); - AV_FREE_FRAME(&audio_converted); + AV_FREE_FRAME(&audio_converted); all_queued_samples = NULL; // this array cleared with above call ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::write_audio_packets (Successfully completed 1st resampling)", "nb_samples", nb_samples, "remaining_frame_samples", remaining_frame_samples, "", -1, "", -1, "", -1, "", -1); @@ -1732,7 +1728,7 @@ void FFmpegWriter::write_audio_packets(bool final) // deallocate AVFrame av_freep(&(frame_final->data[0])); - AV_FREE_FRAME(&frame_final); + AV_FREE_FRAME(&frame_final); // deallocate memory for packet AV_FREE_PACKET(&pkt); @@ -1821,11 +1817,9 @@ void FFmpegWriter::process_video_packet(std::shared_ptr frame) frame_source = allocate_avframe(PIX_FMT_RGBA, source_image_width, source_image_height, &bytes_source, (uint8_t*) pixels); #if IS_FFMPEG_3_2 AVFrame *frame_final; -// #if defined(__linux__) if (hw_en_on && hw_en_supported) { frame_final = allocate_avframe(AV_PIX_FMT_NV12, info.width, info.height, &bytes_final, NULL); } else -// #endif { frame_final = allocate_avframe((AVPixelFormat)(video_st->codecpar->format), info.width, info.height, &bytes_final, NULL); } @@ -1887,7 +1881,7 @@ bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame* fra } else #endif - { + { AVPacket pkt; av_init_packet(&pkt); @@ -1904,7 +1898,6 @@ bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame* fra // Assign the initial AVFrame PTS from the frame counter frame_final->pts = write_video_count; #if IS_FFMPEG_3_2 -// #if defined(__linux__) if (hw_en_on && hw_en_supported) { if (!(hw_frame = av_frame_alloc())) { fprintf(stderr, "Error code: av_hwframe_alloc\n"); @@ -1921,7 +1914,6 @@ bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame* fra } av_frame_copy_props(hw_frame, frame_final); } -// #endif #endif /* encode the image */ int got_packet_ptr = 0; @@ -1930,13 +1922,11 @@ bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame* fra // Write video packet (latest version of FFmpeg) int frameFinished = 0; int ret; -// #if defined(__linux__) #if IS_FFMPEG_3_2 if (hw_en_on && hw_en_supported) { ret = avcodec_send_frame(video_codec, hw_frame); //hw_frame!!! } else #endif -// #endif ret = avcodec_send_frame(video_codec, frame_final); error_code = ret; if (ret < 0 ) { @@ -2002,7 +1992,6 @@ bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame* fra //pkt.pts = pkt.dts = write_video_count; // set the timestamp -// av_packet_rescale_ts(&pkt, video_st->time_base,video_codec->time_base); if (pkt.pts != AV_NOPTS_VALUE) pkt.pts = av_rescale_q(pkt.pts, video_codec->time_base, video_st->time_base); if (pkt.dts != AV_NOPTS_VALUE) @@ -2026,15 +2015,13 @@ bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame* fra // Deallocate packet AV_FREE_PACKET(&pkt); - #if IS_FFMPEG_3_2 -// #if defined(__linux__) + #if IS_FFMPEG_3_2 if (hw_en_on && hw_en_supported) { if (hw_frame) { av_frame_free(&hw_frame); hw_frame = NULL; } } -// #endif #endif } @@ -2062,11 +2049,9 @@ void FFmpegWriter::InitScalers(int source_width, int source_height) { // Init the software scaler from FFMpeg #if IS_FFMPEG_3_2 -// #if defined(__linux__) if (hw_en_on && hw_en_supported) { img_convert_ctx = sws_getContext(source_width, source_height, PIX_FMT_RGBA, info.width, info.height, AV_PIX_FMT_NV12, SWS_BILINEAR, NULL, NULL, NULL); } else -// #endif #endif { img_convert_ctx = sws_getContext(source_width, source_height, PIX_FMT_RGBA, info.width, info.height, AV_GET_CODEC_PIXEL_FORMAT(video_st, video_st->codec), SWS_BILINEAR, NULL, NULL, NULL); diff --git a/src/Settings.cpp b/src/Settings.cpp index 4f502341..461f9183 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -47,8 +47,8 @@ Settings *Settings::Instance() m_pInstance->MAX_WIDTH = 0; m_pInstance->MAX_HEIGHT = 0; m_pInstance->WAIT_FOR_VIDEO_PROCESSING_TASK = false; - m_pInstance->OMP_THREADS = 12;//OPEN_MP_NUM_PROCESSORS - m_pInstance->FF_THREADS = 8;//FF_NUM_PROCESSORS + m_pInstance->OMP_THREADS = 12; + m_pInstance->FF_THREADS = 8; m_pInstance->DE_LIMIT_HEIGHT_MAX = 1100; m_pInstance->DE_LIMIT_WIDTH_MAX = 1950; m_pInstance->HW_DE_DEVICE_SET = 0; From 334a46cc5de5a0331bf376a02188d5dba50987fa Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Fri, 1 Feb 2019 03:38:44 -0800 Subject: [PATCH 095/223] Fix check if GPU can be used for encoding and decoding --- src/FFmpegReader.cpp | 2 +- src/FFmpegWriter.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 2e938f35..7439db4d 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -415,7 +415,7 @@ void FFmpegReader::Open() } // Check if it is there and writable #if defined(__linux__) - if( adapter_ptr != NULL && access( adapter_ptr, W_OK ) == -1 ) { + if( adapter_ptr != NULL && access( adapter_ptr, W_OK ) == 0 ) { #elif defined(_WIN32) if( adapter_ptr != NULL ) { #elif defined(__APPLE__) diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index 14171894..41285314 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -1316,7 +1316,7 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) } // Check if it is there and writable #if defined(__linux__) - if( adapter_ptr != NULL && access( adapter_ptr, W_OK ) == -1 ) { + if( adapter_ptr != NULL && access( adapter_ptr, W_OK ) == 0 ) { #elif defined(_WIN32) if( adapter_ptr != NULL ) { #elif defined(__APPLE__) From 62fa7176a49426e5b9ddf6140f1f0779036f0938 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Fri, 8 Feb 2019 15:30:56 -0600 Subject: [PATCH 096/223] Update README and INSTALL files (including build instructions) (#194) * Update README.md * Create INSTALL.md * Delete InstallationGuide.pdf * Create INSTALL-LINUX.md * Create INSTALL-MAC.md * Create INSTALL-WINDOWS.md --- INSTALL.md | 153 ++++++++++++++ README.md | 107 ++++++---- cmake/Windows/README | 85 -------- cmake/Windows/build-imagemagick.sh | 274 ------------------------ cmake/Windows/urls.txt | 10 - doc/INSTALL-LINUX.md | 225 ++++++++++++++++++++ doc/INSTALL-MAC.md | 218 +++++++++++++++++++ doc/INSTALL-WINDOWS.md | 329 +++++++++++++++++++++++++++++ doc/InstallationGuide.pdf | Bin 834146 -> 0 bytes 9 files changed, 991 insertions(+), 410 deletions(-) create mode 100644 INSTALL.md delete mode 100644 cmake/Windows/README delete mode 100644 cmake/Windows/build-imagemagick.sh delete mode 100644 cmake/Windows/urls.txt create mode 100644 doc/INSTALL-LINUX.md create mode 100644 doc/INSTALL-MAC.md create mode 100644 doc/INSTALL-WINDOWS.md delete mode 100644 doc/InstallationGuide.pdf diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 00000000..474fb9c2 --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,153 @@ +## Detailed Install Instructions + +Operating system specific install instructions are located in: + +* doc/INSTALL-LINUX.md +* doc/INSTALL-MAC.md +* doc/INSTALL-WINDOWS.md + +## Getting Started + +The best way to get started with libopenshot, is to learn about our build system, obtain all the source code, +install a development IDE and tools, and better understand our dependencies. So, please read through the +following sections, and follow the instructions. And keep in mind, that your computer is likely different +than the one used when writing these instructions. Your file paths and versions of applications might be +slightly different, so keep an eye out for subtle file path differences in the commands you type. + +## Build Tools + +CMake is the backbone of our build system. It is a cross-platform build system, which checks for dependencies, +locates header files and libraries, generates makefiles, and supports the cross-platform compiling of +libopenshot and libopenshot-audio. CMake uses an out-of-source build concept, where all temporary build +files, such as makefiles, object files, and even the final binaries, are created outside of the source +code folder, inside a /build/ sub-folder. This prevents the build process from cluttering up the source +code. These instructions have only been tested with the GNU compiler (including MSYS2/MinGW for Windows). + +## Dependencies + +The following libraries are required to build libopenshot. Instructions on how to install these +dependencies vary for each operating system. Libraries and Executables have been labeled in the +list below to help distinguish between them. + +* ### FFmpeg (libavformat, libavcodec, libavutil, libavdevice, libavresample, libswscale) + * http://www.ffmpeg.org/ `(Library)` + * This library is used to decode and encode video, audio, and image files. It is also used to obtain information about media files, such as frame rate, sample rate, aspect ratio, and other common attributes. + +* ### ImageMagick++ (libMagick++, libMagickWand, libMagickCore) + * http://www.imagemagick.org/script/magick++.php `(Library)` + * This library is **optional**, and used to decode and encode images. + +* ### OpenShot Audio Library (libopenshot-audio) + * https://github.com/OpenShot/libopenshot-audio/ `(Library)` + * This library is used to mix, resample, host plug-ins, and play audio. It is based on the JUCE project, which is an outstanding audio library used by many different applications + +* ### Qt 5 (libqt5) + * http://www.qt.io/qt5/ `(Library)` + * Qt5 is used to display video, store image data, composite images, apply image effects, and many other utility functions, such as file system manipulation, high resolution timers, etc... + +* ### CMake (cmake) + * http://www.cmake.org/ `(Executable)` + * This executable is used to automate the generation of Makefiles, check for dependencies, and is the backbone of libopenshot’s cross-platform build process. + +* ### SWIG (swig) + * http://www.swig.org/ `(Executable)` + * This executable is used to generate the Python and Ruby bindings for libopenshot. It is a simple and powerful wrapper for C++ libraries, and supports many languages. + +* ### Python 3 (libpython) + * http://www.python.org/ `(Executable and Library)` + * This library is used by swig to create the Python (version 3+) bindings for libopenshot. This is also the official language used by OpenShot Video Editor (a graphical interface to libopenshot). + +* ### Doxygen (doxygen) + * http://www.stack.nl/~dimitri/doxygen/ `(Executable)` + * This executable is used to auto-generate the documentation used by libopenshot. + +* ### UnitTest++ (libunittest++) + * https://github.com/unittest-cpp/ `(Library)` + * This library is used to execute unit tests for libopenshot. It contains many macros used to keep our unit testing code very clean and simple. + +* ### ZeroMQ (libzmq) + * http://zeromq.org/ `(Library)` + * This library is used to communicate between libopenshot and other applications (publisher / subscriber). Primarily used to send debug data from libopenshot. + +* ### OpenMP (-fopenmp) + * http://openmp.org/wp/ `(Compiler Flag)` + * If your compiler supports this flag (GCC, Clang, and most other compilers), it provides libopenshot with easy methods of using parallel programming techniques to improve performance and take advantage of multi-core processors. + +## CMake Flags (Optional) +There are many different build flags that can be passed to cmake to adjust how libopenshot is compiled. Some of these flags might be required when compiling on certain OSes, just depending on how your build environment is setup. To add a build flag, follow this general syntax: $ cmake -DMAGICKCORE_HDRI_ENABLE=1 -DENABLE_TESTS=1 ../ + +* MAGICKCORE_HDRI_ENABLE (default 0) +* MAGICKCORE_QUANTUM_DEPTH (default 0) +* OPENSHOT_IMAGEMAGICK_COMPATIBILITY (default 0) +* DISABLE_TESTS (default 0) +* CMAKE_PREFIX_PATH (`/location/to/missing/library/`) +* PYTHON_INCLUDE_DIR (`/location/to/python/include/`) +* PYTHON_LIBRARY (`/location/to/python/lib.a`) +* PYTHON_FRAMEWORKS (`/usr/local/Cellar/python3/3.3.2/Frameworks/Python.framework/`) +* CMAKE_CXX_COMPILER (`/location/to/mingw/g++`) +* CMAKE_C_COMPILER (`/location/to/mingw/gcc`) + +## Obtaining Source Code + +The first step in installing libopenshot is to obtain the most recent source code. The source code is available on [GitHub](https://github.com/OpenShot/libopenshot). Use the following command to obtain the latest libopenshot source code. + +``` +git clone https://github.com/OpenShot/libopenshot.git +git clone https://github.com/OpenShot/libopenshot-audio.git +``` + +## Folder Structure (libopenshot) + +The source code is divided up into the following folders. + +* ### build/ + * This folder needs to be manually created, and is used by cmake to store the temporary build files, such as makefiles, as well as the final binaries (library and test executables). + +* ### cmake/ + * This folder contains custom modules not included by default in cmake, used to find dependency libraries and headers and determine if these libraries are installed. + +* ### doc/ + * This folder contains documentation and related files, such as logos and images required by the doxygen auto-generated documentation. + +* ### include/ + * This folder contains all headers (*.h) used by libopenshot. + +* ### src/ + * This folder contains all source code (*.cpp) used by libopenshot. + +* ### tests/ + * This folder contains all unit test code. Each class has it’s own test file (*.cpp), and uses UnitTest++ macros to keep the test code simple and manageable. + +* ### thirdparty/ + * This folder contains code not written by the OpenShot team. For example, jsoncpp, an open-source JSON parser. + +## Linux Build Instructions (libopenshot-audio) +To compile libopenshot-audio, we need to go through a few additional steps to manually build and install it. Launch a terminal and enter: + +``` +cd [libopenshot-audio repo folder] +mkdir build +cd build +cmake ../ +make +make install +./src/openshot-audio-test-sound (This should play a test sound) +``` + +## Linux Build Instructions (libopenshot) +Run the following commands to compile libopenshot: + +``` +cd [libopenshot repo directory] +mkdir -p build +cd build +cmake ../ +make +make install +``` + +For more detailed instructions, please see: + +* doc/INSTALL-LINUX.md +* doc/INSTALL-MAC.md +* doc/INSTALL-WINDOWS.md diff --git a/README.md b/README.md index 1baf3cb4..8deb86a1 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,71 @@ -# OpenShot Library +OpenShot Video Library (libopenshot) is a free, open-source C++ library dedicated to +delivering high quality video editing, animation, and playback solutions to the +world. -OpenShot Library (libopenshot) is an open-source project dedicated to -delivering high quality video editing, animation, and playback solutions -to the world. For more information visit [http://www.openshot.org/](http://www.openshot.org/). +## Build Status + +[![Build Status](https://img.shields.io/travis/OpenShot/libopenshot/develop.svg?label=libopenshot)](https://travis-ci.org/OpenShot/libopenshot) [![Build Status](https://img.shields.io/travis/OpenShot/libopenshot-audio/develop.svg?label=libopenshot-audio)](https://travis-ci.org/OpenShot/libopenshot-audio) + +## Features + +* Cross-Platform (Linux, Mac, and Windows) +* Multi-Layer Compositing +* Video and Audio Effects (Chroma Key, Color Adjustment, Grayscale, etc…) +* Animation Curves (Bézier, Linear, Constant) +* Time Mapping (Curve-based Slow Down, Speed Up, Reverse) +* Audio Mixing & Resampling (Curve-based) +* Audio Plug-ins (VST & AU) +* Audio Drivers (ASIO, WASAPI, DirectSound, CoreAudio, iPhone Audio, ALSA, JACK, and Android) +* Telecine and Inverse Telecine (Film to TV, TV to Film) +* Frame Rate Conversions +* Multi-Processor Support (Performance) +* Python and Ruby Bindings (All Features Supported) +* Qt Video Player Included (Ability to display video on any QWidget) +* Unit Tests (Stability) +* All FFmpeg Formats and Codecs Supported (Images, Videos, and Audio files) +* Full Documentation with Examples (Doxygen Generated) + +## Install + +Detailed instructions for building libopenshot and libopenshot-audio for each OS. These instructions +are also available in the /docs/ source folder. + + * [Linux](https://github.com/OpenShot/libopenshot/wiki/Linux-Build-Instructions) + * [Mac](https://github.com/OpenShot/libopenshot/wiki/Mac-Build-Instructions) + * [Windows](https://github.com/OpenShot/libopenshot/wiki/Windows-Build-Instructions) + +## Documentation + +Beautiful HTML documentation can be generated using Doxygen. +``` +make doc +``` +(Also available online: http://openshot.org/files/libopenshot/) + +## Developers + +Are you interested in becoming more involved in the development of +OpenShot? Build exciting new features, fix bugs, make friends, and become a hero! +Please read the [step-by-step](https://github.com/OpenShot/openshot-qt/wiki/Become-a-Developer) +instructions for getting source code, configuring dependencies, and building OpenShot. + +## Report a bug + +You can report a new libopenshot issue directly on GitHub: + +https://github.com/OpenShot/libopenshot/issues + +## Websites + +- https://www.openshot.org/ (Official website and blog) +- https://github.com/OpenShot/libopenshot/ (source code and issue tracker) +- https://github.com/OpenShot/libopenshot-audio/ (source code for audio library) +- https://github.com/OpenShot/openshot-qt/ (source code for Qt client) +- https://launchpad.net/openshot/ ### License -Copyright (c) 2008-2019 OpenShot Studios, LLC -[http://www.openshotstudios.com/](http://www.openshotstudios.com/). +Copyright (c) 2008-2019 OpenShot Studios, LLC. OpenShot Library (libopenshot) is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License @@ -20,41 +78,8 @@ 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 [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/). +along with OpenShot Library. If not, see http://www.gnu.org/licenses/. ---- To release a closed-source product which uses libopenshot (i.e. video -editing and playback), commercial licenses are available: contact +editing and playback), commercial licenses are also available: contact sales@openshot.org for more information. - ---- - -### Install - -Please see /doc/InstallationGuide.pdf for a very detailed -Linux, Mac, and Windows compiling instruction guide. An online version -is also available: -[https://docs.google.com/document/d/1V6nq-IuS9zxqO1-OSt8iTS_cw_HMCpsUNofHLYtUNjM/pub](https://docs.google.com/document/d/1V6nq-IuS9zxqO1-OSt8iTS_cw_HMCpsUNofHLYtUNjM/pub) - - -### Documentation - -Documentation is auto-generated by Doxygen, and can be created with -``` -make doc -``` -(Also available online: [http://openshot.org/files/libopenshot/](http://openshot.org/files/libopenshot/)) - - -### Authors - -Please see AUTHORS file for a full list of authors. - - ---- -[www.openshot.org](http://www.openshot.org) | [www.openshotstudios.com](http://www.openshotstudios.com) - ---- -Copyright (c) 2008-2019 OpenShot Studios, LLC - -[www.openshotstudios.com](http://www.openshotstudios.com). diff --git a/cmake/Windows/README b/cmake/Windows/README deleted file mode 100644 index 44dd56d7..00000000 --- a/cmake/Windows/README +++ /dev/null @@ -1,85 +0,0 @@ -#################################################################### - Install Dependencies for Windows -#################################################################### - -Most Windows dependencies needed for libopenshot-audio, libopenshot, and openshot-qt -can be installed easily with MSYS2 and the pacman package manager. Follow these -directions to setup a Windows build environment for OpenShot: - -#################################################################### - -1) Install MSYS2 (http://www.msys2.org/) - -2) Run MSYS2 command prompt (for example: C:\msys64\msys2_shell.cmd) - -3) Append PATH (so MSYS2 can find executables and libraries): - $ PATH=$PATH:/c/msys64/mingw64/bin:/c/msys64/mingw64/lib (64-bit PATH) - or - $ PATH=$PATH:/c/msys32/mingw32/bin:/c/msys32/mingw32/lib (32-bit PATH) - -4) Update and upgrade all packages - $ pacman -Syu - -5a) Install the following packages: -*** for 64-BIT support *** - - $ pacman -S --needed base-devel mingw-w64-x86_64-toolchain - $ pacman -S mingw64/mingw-w64-x86_64-ffmpeg - $ pacman -S mingw64/mingw-w64-x86_64-python3-pyqt5 - $ pacman -S mingw64/mingw-w64-x86_64-swig - $ pacman -S mingw64/mingw-w64-x86_64-cmake - $ pacman -S mingw64/mingw-w64-x86_64-doxygen - $ pacman -S mingw64/mingw-w64-x86_64-python3-pip - $ pacman -S mingw32/mingw-w64-i686-zeromq - $ pacman -S mingw64/mingw-w64-x86_64-python3-pyzmq - $ pacman -S mingw64/mingw-w64-x86_64-python3-cx_Freeze - $ pacman -S git - - Install ImageMagick if needed (OPTIONAL and NOT NEEDED) - $ pacman -S mingw64/mingw-w64-x86_64-imagemagick - -5b) Install the following packages: -*** for 32-BIT support *** - - $ pacman -S --needed base-devel mingw32/mingw-w64-i686-toolchain - $ pacman -S mingw32/mingw-w64-i686-ffmpeg - $ pacman -S mingw32/mingw-w64-i686-python3-pyqt5 - $ pacman -S mingw32/mingw-w64-i686-swig - $ pacman -S mingw32/mingw-w64-i686-cmake - $ pacman -S mingw32/mingw-w64-i686-doxygen - $ pacman -S mingw32/mingw-w64-i686-python3-pip - $ pacman -S mingw32/mingw-w64-i686-zeromq - $ pacman -S mingw32/mingw-w64-i686-python3-pyzmq - $ pacman -S mingw32/mingw-w64-i686-python3-cx_Freeze - $ pacman -S git - - Install ImageMagick if needed (OPTIONAL and NOT NEEDED) - $ pacman -S mingw32/mingw-w32-x86_32-imagemagick - -6) Install Python PIP Dependencies - $ pip3 install httplib2 - $ pip3 install slacker - $ pip3 install tinys3 - $ pip3 install github3.py - $ pip3 install requests - -7) Download Unittest++ (https://github.com/unittest-cpp/unittest-cpp) into /c/home/USER/unittest-cpp-master/ - Configure Unittest++: - $ cmake -G "MSYS Makefiles" ../ -DCMAKE_MAKE_PROGRAM=mingw32-make -DCMAKE_INSTALL_PREFIX:PATH=/usr - Build Unittest++ - $ mingw32-make install - -8) ZMQ++ Header (This might not be needed anymore) - NOTE: Download and copy zmq.hpp into the /c/msys64/mingw64/include/ folder - - -#################################################################### - OPTIONAL: Installing ImageMagick on Windows -#################################################################### - -If you would rather install ImageMagick from source code yourself, follow these steps: - -Step 1) Copy [build-imagemagick.sh and urls.txt] into your local MSYS2 environment -Step 2) Run MSYS2 Shell -Step 3) Execute this command - $ ./build-imagemagick.sh \ No newline at end of file diff --git a/cmake/Windows/build-imagemagick.sh b/cmake/Windows/build-imagemagick.sh deleted file mode 100644 index b3814dd9..00000000 --- a/cmake/Windows/build-imagemagick.sh +++ /dev/null @@ -1,274 +0,0 @@ -#!/bin/bash -# xml2 build ok but failed test -# libfpx build error - -function ised() { - IN=$1 - shift - tmp=$RANDOM.$$ - <$IN sed "$@" >$tmp && cat $tmp > $IN - rm $tmp -} - -function ask() { - read -p "${1:-Are you sure?]} [Y/n] " response - case $response in - y|Y|"") - true;; - *) - false;; - esac -} - -function download() { - while IFS=\; read url md5 <&3; do - fileName=${url##*/} - - echo "Downloading ${fileName}..." - while true; do - if [[ ! -e $fileName ]]; then - wget ${url} -O ${fileName} - else - echo "File exists!" - fi - - localMd5=$(md5sum ${fileName} | cut -d\ -f1) - - if [[ ${localMd5} != ${md5} ]]; then - ask "Checksum failed. Do you want to download this file again? [Y/n] " - if [[ $? -ne 0 ]]; then - exit 1 - fi - rm ${fileName} - else - break - fi - done - done 3< urls.txt -} - -function extract() { - file=$1 - if [[ ! -e ${file} ]]; then - return - fi - - case $file in - *.tar.gz) - tar xzf $file - ;; - *.tar.xz|*.tar.lzma) - tar xJf $file - ;; - *.tar.bz2) - tar xjf $file - ;; - *) - "Don't know how to extract $file" - esac -} - -function isLibsInstalled() { - libs="$@" - notfound=false - for l in "${libs}"; do - ld -L/usr/local/lib -l"${l}" 2>/dev/null - if [[ $? -ne 0 ]]; then - notfound=true - fi - done - - ! ${notfound} -} - -function isDirExists() { - dir="$@" - found=false - for d in ${dir}; do - if [[ -d "${d}" ]]; then - found=true - break - fi - done - - ${found} -} - -function extractIfNeeded() { - file=$1 - isDirExists ${file%%-*}-* - if [[ $? -ne 0 ]]; then - echo "Extracting $file" - extract $file - fi -} - -function buildbzip2() { - if isLibsInstalled "bz2"; then - if ask "Found bzip2 installed. Do you want to reinstall it?"; then : - else - return 0 - fi - fi - - extractIfNeeded bzip2-*.tar.lzma - - cd bzip2-*/ - tar xzf bzip2-1.0.6.tar.gz - tar xzf cygming-autotools-buildfiles.tar.gz - cd bzip2-*/ - autoconf - mkdir ../build - cd ../build - ../bzip2-*/configure - make - make install - cd ../.. -} - -function buildzlib() { - if isLibsInstalled "z"; then - if ask "Found zlib installed. Do you want to reinstall it?"; then : - else - return 0 - fi - fi - - extractIfNeeded zlib-*.tar.xz - - cd zlib-*/ - INCLUDE_PATH=/usr/local/include LIBRARY_PATH=/usr/local/lib BINARY_PATH=/usr/local/bin make install -f win32/Makefile.gcc SHARED_MODE=1 - cd .. -} - -function buildlibxml2() { - if isLibsInstalled "xml2"; then - if ask "Found libxml2 installed. Do you want to reinstall it?"; then : - else - return 0 - fi - fi - extractIfNeeded libxml2-*.tar.gz - cd libxml2-*/win32/ - ised configure.js 's/dirSep = "\\\\";/dirSep = "\/";/' - cscript.exe configure.js compiler=mingw prefix=/usr/local - # ised ../dict.c '/typedef.*uint32_t;$/d' - ised Makefile.mingw 's/cmd.exe \/C "\?if not exist \(.*\) mkdir \1"\?/mkdir -p \1/' - ised Makefile.mingw 's/cmd.exe \/C "copy\(.*\)"/cp\1/' - ised Makefile.mingw '/cp/{y/\\/\//;}' - ised Makefile.mingw '/PREFIX/{y/\\/\//;}' - make -f Makefile.mingw - make -f Makefile.mingw install - cd ../../ -} - -function buildlibpng() { - if isLibsInstalled "png"; then - if ask "Found libpng installed. Do you want to reinstall it?"; then : - else - return 0 - fi - fi - - extractIfNeeded libpng-*.tar.xz - - cd libpng-*/ - make -f scripts/makefile.msys - make install -f scripts/makefile.msys - cd .. -} - -function buildjpegsrc() { - if isLibsInstalled "jpeg"; then - if ask "Found jpegsrc installed. Do you want to reinstall it?"; then : - else - return 0 - fi - fi - - extract jpegsrc*.tar.gz - - cd jpeg-*/ - ./configure - make - make install - cd .. -} - -function buildfreetype() { - if isLibsInstalled "freetype"; then - if ask "Found freetype installed. Do you want to reinstall it?"; then : - else - return 0 - fi - fi - extract freetype*.tar.bz2 - - INCLUDE_PATH=/usr/local/include - LIBRARY_PATH=/usr/local/lib - BINARY_PATH=/usr/local/bin - cd freetype-*/ - ./configure - make - make install - cd .. -} - -function buildlibwmf() { - if isLibsInstalled "wmf"; then - if ask "Found libwmf installed. Do you want to reinstall it?"; then : - else - return 0 - fi - fi - extract libwmf*.tar.gz - - cd libwmf-*/ - ./configure CFLAGS="-I/usr/local/include" LDFLAGS="-L/usr/local/lib" - make - make install - cd .. -} - -function buildlibwebp() { - if isLibsInstalled "webp"; then - if ask "Found libwebp installed. Do you want to reinstall it?"; then : - else - return 0 - fi - fi - extract libwebp*.tar.gz - - cd libwebp-*/ - ./configure CFLAGS="-I/usr/local/include" LDFLAGS="-L/usr/local/lib" - make - make install - cd .. -} - -function buildDelegate() { - delegates="bzip2 zlib libxml2 libpng jpegsrc freetype libwmf libwebp" - for d in ${delegates}; do - echo "**********************************************************" - echo "Building $d" - build${d} - done -} - -function build() { - extractIfNeeded ImageMagick-*.tar.xz - - local oldPwd=$(pwd -L) - cd ImageMagick-*/ - # patch configure - #sed -i 's/${GDI32_LIBS}x" !=/${GDI32_LIBS} ==/' configure - ised configure 's/${GDI32_LIBS}x" !=/${GDI32_LIBS} ==/' - ./configure --enable-shared --disable-static --enable-delegate-build --without-modules CFLAGS="-I/usr/local/include" LDFLAGS="-L/usr/local/lib" - make - make install - cd ${oldPwd} -} - -download -buildDelegate -build diff --git a/cmake/Windows/urls.txt b/cmake/Windows/urls.txt deleted file mode 100644 index 0a45d8af..00000000 --- a/cmake/Windows/urls.txt +++ /dev/null @@ -1,10 +0,0 @@ -ftp://ftp.imagemagick.org/pub/ImageMagick/releases/ImageMagick-6.8.8-10.tar.xz;ab9b397c1d4798a9f6ae6cc94aa292fe -ftp://ftp.imagemagick.org/pub/ImageMagick/delegates/libpng-1.6.20.tar.xz;3968acb7c66ef81a9dab867f35d0eb4b -ftp://ftp.imagemagick.org/pub/ImageMagick/delegates/libwebp-0.4.4.tar.gz;b737062cf688e502b940b460ddc3015f -ftp://ftp.imagemagick.org/pub/ImageMagick/delegates/libwmf-0.2.8.4.tar.gz;d1177739bf1ceb07f57421f0cee191e0 -ftp://ftp.imagemagick.org/pub/ImageMagick/delegates/libxml2-2.9.3.tar.gz;daece17e045f1c107610e137ab50c179 -ftp://ftp.imagemagick.org/pub/ImageMagick/delegates/zlib-1.2.8.tar.xz;28f1205d8dd2001f26fec1e8c2cebe37 -ftp://ftp.imagemagick.org/pub/ImageMagick/delegates/freetype-2.6.2.tar.bz2;86109d0c998787d81ac582bad9adf82e -http://ncu.dl.sourceforge.net/project/mingw/MinGW/Extension/bzip2/bzip2-1.0.6-4/bzip2-1.0.6-4-mingw32-src.tar.lzma;2a25de4331d1e6e1458d8632dff55fad -ftp://ftp.imagemagick.org/pub/ImageMagick/delegates/libfpx-1.3.1-4.tar.xz;65e2cf8dcf230ad0b90aead35553bbda -ftp://ftp.imagemagick.org/pub/ImageMagick/delegates/jpegsrc.v9a.tar.gz;3353992aecaee1805ef4109aadd433e7 diff --git a/doc/INSTALL-LINUX.md b/doc/INSTALL-LINUX.md new file mode 100644 index 00000000..6ed4f3f6 --- /dev/null +++ b/doc/INSTALL-LINUX.md @@ -0,0 +1,225 @@ +## Getting Started + +The best way to get started with libopenshot, is to learn about our build system, obtain all the source code, +install a development IDE and tools, and better understand our dependencies. So, please read through the +following sections, and follow the instructions. And keep in mind, that your computer is likely different +than the one used when writing these instructions. Your file paths and versions of applications might be +slightly different, so keep an eye out for subtle file path differences in the commands you type. + +## Build Tools + +CMake is the backbone of our build system. It is a cross-platform build system, which checks for +dependencies, locates header files and libraries, generates makefiles, and supports the cross-platform +compiling of libopenshot and libopenshot-audio. CMake uses an out-of-source build concept, where +all temporary build files, such as makefiles, object files, and even the final binaries, are created +outside of the source code folder, inside a /build/ sub-folder. This prevents the build process +from cluttering up the source code. These instructions have only been tested with the GNU compiler +(including MSYS2/MinGW for Windows). + +## Dependencies + +The following libraries are required to build libopenshot. Instructions on how to install these +dependencies vary for each operating system. Libraries and Executables have been labeled in the +list below to help distinguish between them. + +* ### FFmpeg (libavformat, libavcodec, libavutil, libavdevice, libavresample, libswscale) + * http://www.ffmpeg.org/ `(Library)` + * This library is used to decode and encode video, audio, and image files. It is also used to obtain information about media files, such as frame rate, sample rate, aspect ratio, and other common attributes. + +* ### ImageMagick++ (libMagick++, libMagickWand, libMagickCore) + * http://www.imagemagick.org/script/magick++.php `(Library)` + * This library is **optional**, and used to decode and encode images. + +* ### OpenShot Audio Library (libopenshot-audio) + * https://github.com/OpenShot/libopenshot-audio/ `(Library)` + * This library is used to mix, resample, host plug-ins, and play audio. It is based on the JUCE project, which is an outstanding audio library used by many different applications + +* ### Qt 5 (libqt5) + * http://www.qt.io/qt5/ `(Library)` + * Qt5 is used to display video, store image data, composite images, apply image effects, and many other utility functions, such as file system manipulation, high resolution timers, etc... + +* ### CMake (cmake) + * http://www.cmake.org/ `(Executable)` + * This executable is used to automate the generation of Makefiles, check for dependencies, and is the backbone of libopenshot’s cross-platform build process. + +* ### SWIG (swig) + * http://www.swig.org/ `(Executable)` + * This executable is used to generate the Python and Ruby bindings for libopenshot. It is a simple and powerful wrapper for C++ libraries, and supports many languages. + +* ### Python 3 (libpython) + * http://www.python.org/ `(Executable and Library)` + * This library is used by swig to create the Python (version 3+) bindings for libopenshot. This is also the official language used by OpenShot Video Editor (a graphical interface to libopenshot). + +* ### Doxygen (doxygen) + * http://www.stack.nl/~dimitri/doxygen/ `(Executable)` + * This executable is used to auto-generate the documentation used by libopenshot. + +* ### UnitTest++ (libunittest++) + * https://github.com/unittest-cpp/ `(Library)` + * This library is used to execute unit tests for libopenshot. It contains many macros used to keep our unit testing code very clean and simple. + +* ### ZeroMQ (libzmq) + * http://zeromq.org/ `(Library)` + * This library is used to communicate between libopenshot and other applications (publisher / subscriber). Primarily used to send debug data from libopenshot. + +* ### OpenMP (-fopenmp) + * http://openmp.org/wp/ `(Compiler Flag)` + * If your compiler supports this flag (GCC, Clang, and most other compilers), it provides libopenshot with easy methods of using parallel programming techniques to improve performance and take advantage of multi-core processors. + + +## CMake Flags (Optional) +There are many different build flags that can be passed to cmake to adjust how libopenshot is +compiled. Some of these flags might be required when compiling on certain OSes, just depending +on how your build environment is setup. To add a build flag, follow this general syntax: +`cmake -DMAGICKCORE_HDRI_ENABLE=1 -DENABLE_TESTS=1 ../` + +* MAGICKCORE_HDRI_ENABLE (default 0) +* MAGICKCORE_QUANTUM_DEPTH (default 0) +* OPENSHOT_IMAGEMAGICK_COMPATIBILITY (default 0) +* DISABLE_TESTS (default 0) +* CMAKE_PREFIX_PATH (`/location/to/missing/library/`) +* PYTHON_INCLUDE_DIR (`/location/to/python/include/`) +* PYTHON_LIBRARY (`/location/to/python/lib.a`) +* PYTHON_FRAMEWORKS (`/usr/local/Cellar/python3/3.3.2/Frameworks/Python.framework/`) +* CMAKE_CXX_COMPILER (`/location/to/mingw/g++`) +* CMAKE_C_COMPILER (`/location/to/mingw/gcc`) + +## Obtaining Source Code + +The first step in installing libopenshot is to obtain the most recent source code. The source code is +available on [GitHub](https://github.com/OpenShot/libopenshot). Use the following command +to obtain the latest libopenshot source code. + +``` +git clone https://github.com/OpenShot/libopenshot.git +git clone https://github.com/OpenShot/libopenshot-audio.git +``` + +## Folder Structure (libopenshot) + +The source code is divided up into the following folders. + +* ### build/ + * This folder needs to be manually created, and is used by cmake to store the temporary build files, such as makefiles, as well as the final binaries (library and test executables). + +* ### cmake/ + * This folder contains custom modules not included by default in cmake, used to find dependency libraries and headers and determine if these libraries are installed. + +* ### doc/ + * This folder contains documentation and related files, such as logos and images required by the doxygen auto-generated documentation. + +* ### include/ + * This folder contains all headers (*.h) used by libopenshot. + +* ### src/ + * This folder contains all source code (*.cpp) used by libopenshot. + +* ### tests/ + * This folder contains all unit test code. Each class has it’s own test file (*.cpp), and uses UnitTest++ macros to keep the test code simple and manageable. + +* ### thirdparty/ + * This folder contains code not written by the OpenShot team. For example, jsoncpp, an open-source JSON parser. + +## Install Dependencies + +In order to actually compile libopenshot, we need to install some dependencies on your system. The easiest +way to accomplish this is with our Daily PPA. A PPA is an unofficial Ubuntu repository, which has our +software packages available to download and install. + +``` + sudo add-apt-repository ppa:openshot.developers/libopenshot-daily + sudo apt-get update + sudo apt-get install openshot-qt \ + cmake \ + libx11-dev \ + libasound2-dev \ + libavcodec-dev \ + libavdevice-dev \ + libavfilter-dev \ + libavformat-dev \ + libavresample-dev \ + libavutil-dev \ + libfdk-aac-dev \ + libfreetype6-dev \ + libjsoncpp-dev \ + libmagick++-dev \ + libopenshot-audio-dev \ + libswscale-dev \ + libunittest++-dev \ + libxcursor-dev \ + libxinerama-dev \ + libxrandr-dev \ + libzmq3-dev \ + pkg-config \ + python3-dev \ + qtbase5-dev \ + qtmultimedia5-dev \ + swig +``` + +## Linux Build Instructions (libopenshot-audio) +To compile libopenshot-audio, we need to go through a few additional steps to manually build and +install it. Launch a terminal and enter: + +``` +cd [libopenshot-audio repo folder] +mkdir build +cd build +cmake ../ +make +make install +./src/openshot-audio-test-sound (This should play a test sound) +``` + +## Linux Build Instructions (libopenshot) +Run the following commands to compile libopenshot: + +``` +cd [libopenshot repo directory] +mkdir -p build +cd build +cmake ../ +make +``` + +If you are missing any dependencies for libopenshot, you might receive error messages at this point. +Just install the missing packages (usually with a -dev suffix), and run the above commands again. +Repeat until no error messages are displayed, and the build process completes. Also, if you manually +install Qt 5, you might need to specify the location for cmake: + +``` +cmake -DCMAKE_PREFIX_PATH=/qt5_path/qt5/5.2.0/ ../ +``` + +To run all unit tests (and verify everything is working correctly), launch a terminal, and enter: + +``` +make test +``` + +To auto-generate documentation for libopenshot, launch a terminal, and enter: + +``` +make doc +``` + +This will use doxygen to generate a folder of HTML files, with all classes and methods documented. The +folder is located at **build/doc/html/**. Once libopenshot has been successfully built, we need to +install it (i.e. copy it to the correct folder, so other libraries can find it). + +``` +make install +``` + +This will copy the binary files to /usr/local/lib/, and the header files to /usr/local/include/openshot/... +This is where other projects will look for the libopenshot files when building. Python 3 bindings are +also installed at this point. let's verify the python bindings work: + +``` +python3 +>>> import openshot +``` + +If no errors are displayed, you have successfully compiled and installed libopenshot on your system. +Congratulations and be sure to read our wiki on [Becoming an OpenShot Developer](https://github.com/OpenShot/openshot-qt/wiki/Become-a-Developer)! +Welcome to the OpenShot developer community! We look forward to meeting you! diff --git a/doc/INSTALL-MAC.md b/doc/INSTALL-MAC.md new file mode 100644 index 00000000..ab7f79c3 --- /dev/null +++ b/doc/INSTALL-MAC.md @@ -0,0 +1,218 @@ +## Getting Started + +The best way to get started with libopenshot, is to learn about our build system, obtain all the source code, +install a development IDE and tools, and better understand our dependencies. So, please read through the +following sections, and follow the instructions. And keep in mind, that your computer is likely different +than the one used when writing these instructions. Your file paths and versions of applications might be +slightly different, so keep an eye out for subtle file path differences in the commands you type. + +## Build Tools + +CMake is the backbone of our build system. It is a cross-platform build system, which checks for +dependencies, locates header files and libraries, generates makefiles, and supports the cross-platform +compiling of libopenshot and libopenshot-audio. CMake uses an out-of-source build concept, where +all temporary build files, such as makefiles, object files, and even the final binaries, are created +outside of the source code folder, inside a /build/ sub-folder. This prevents the build process +from cluttering up the source code. These instructions have only been tested with the GNU compiler +(including MSYS2/MinGW for Windows). + +## Dependencies + +The following libraries are required to build libopenshot. Instructions on how to install these +dependencies vary for each operating system. Libraries and Executables have been labeled in the +list below to help distinguish between them. + +* ### FFmpeg (libavformat, libavcodec, libavutil, libavdevice, libavresample, libswscale) + * http://www.ffmpeg.org/ `(Library)` + * This library is used to decode and encode video, audio, and image files. It is also used to obtain information about media files, such as frame rate, sample rate, aspect ratio, and other common attributes. + +* ### ImageMagick++ (libMagick++, libMagickWand, libMagickCore) + * http://www.imagemagick.org/script/magick++.php `(Library)` + * This library is **optional**, and used to decode and encode images. + +* ### OpenShot Audio Library (libopenshot-audio) + * https://github.com/OpenShot/libopenshot-audio/ `(Library)` + * This library is used to mix, resample, host plug-ins, and play audio. It is based on the JUCE project, which is an outstanding audio library used by many different applications + +* ### Qt 5 (libqt5) + * http://www.qt.io/qt5/ `(Library)` + * Qt5 is used to display video, store image data, composite images, apply image effects, and many other utility functions, such as file system manipulation, high resolution timers, etc... + +* ### CMake (cmake) + * http://www.cmake.org/ `(Executable)` + * This executable is used to automate the generation of Makefiles, check for dependencies, and is the backbone of libopenshot’s cross-platform build process. + +* ### SWIG (swig) + * http://www.swig.org/ `(Executable)` + * This executable is used to generate the Python and Ruby bindings for libopenshot. It is a simple and powerful wrapper for C++ libraries, and supports many languages. + +* ### Python 3 (libpython) + * http://www.python.org/ `(Executable and Library)` + * This library is used by swig to create the Python (version 3+) bindings for libopenshot. This is also the official language used by OpenShot Video Editor (a graphical interface to libopenshot). + +* ### Doxygen (doxygen) + * http://www.stack.nl/~dimitri/doxygen/ `(Executable)` + * This executable is used to auto-generate the documentation used by libopenshot. + +* ### UnitTest++ (libunittest++) + * https://github.com/unittest-cpp/ `(Library)` + * This library is used to execute unit tests for libopenshot. It contains many macros used to keep our unit testing code very clean and simple. + +* ### ZeroMQ (libzmq) + * http://zeromq.org/ `(Library)` + * This library is used to communicate between libopenshot and other applications (publisher / subscriber). Primarily used to send debug data from libopenshot. + +* ### OpenMP (-fopenmp) + * http://openmp.org/wp/ `(Compiler Flag)` + * If your compiler supports this flag (GCC, Clang, and most other compilers), it provides libopenshot with easy methods of using parallel programming techniques to improve performance and take advantage of multi-core processors. + +## CMake Flags (Optional) +There are many different build flags that can be passed to cmake to adjust how libopenshot is compiled. +Some of these flags might be required when compiling on certain OSes, just depending on how your build +environment is setup. To add a build flag, follow this general syntax: +`cmake -DMAGICKCORE_HDRI_ENABLE=1 -DENABLE_TESTS=1 ../` + +* MAGICKCORE_HDRI_ENABLE (default 0) +* MAGICKCORE_QUANTUM_DEPTH (default 0) +* OPENSHOT_IMAGEMAGICK_COMPATIBILITY (default 0) +* DISABLE_TESTS (default 0) +* CMAKE_PREFIX_PATH (`/location/to/missing/library/`) +* PYTHON_INCLUDE_DIR (`/location/to/python/include/`) +* PYTHON_LIBRARY (`/location/to/python/lib.a`) +* PYTHON_FRAMEWORKS (`/usr/local/Cellar/python3/3.3.2/Frameworks/Python.framework/`) +* CMAKE_CXX_COMPILER (`/location/to/mingw/g++`) +* CMAKE_C_COMPILER (`/location/to/mingw/gcc`) + +## Obtaining Source Code + +The first step in installing libopenshot is to obtain the most recent source code. The source code +is available on [GitHub](https://github.com/OpenShot/libopenshot). Use the following command to +obtain the latest libopenshot source code. + +``` +git clone https://github.com/OpenShot/libopenshot.git +git clone https://github.com/OpenShot/libopenshot-audio.git +``` + +## Folder Structure (libopenshot) + +The source code is divided up into the following folders. + +* ### build/ + * This folder needs to be manually created, and is used by cmake to store the temporary build files, such as makefiles, as well as the final binaries (library and test executables). + +* ### cmake/ + * This folder contains custom modules not included by default in cmake, used to find dependency libraries and headers and determine if these libraries are installed. + +* ### doc/ + * This folder contains documentation and related files, such as logos and images required by the doxygen auto-generated documentation. + +* ### include/ + * This folder contains all headers (*.h) used by libopenshot. + +* ### src/ + * This folder contains all source code (*.cpp) used by libopenshot. + +* ### tests/ + * This folder contains all unit test code. Each class has it’s own test file (*.cpp), and uses UnitTest++ macros to keep the test code simple and manageable. + +* ### thirdparty/ + * This folder contains code not written by the OpenShot team. For example, jsoncpp, an open-source JSON parser. + +## Install Dependencies + +In order to actually compile libopenshot and libopenshot-audio, we need to install some dependencies on +your system. Most packages needed by libopenshot can be installed easily with Homebrew. However, first +install Xcode with the following options ("UNIX Development", "System Tools", "Command Line Tools", or +"Command Line Support"). Be sure to refresh your list of Homebrew packages with the “brew update” command. + +**NOTE:** Homebrew seems to work much better for most users (compared to MacPorts), so I am going to +focus on brew for this guide. + +Install the following packages using the Homebrew package installer (http://brew.sh/). Pay close attention +to any warnings or errors during these brew installs. NOTE: You might have some conflicting libraries in +your /usr/local/ folders, so follow the directions from brew if these are detected. + +``` +brew install gcc48 --enable-all-languages +brew install ffmpeg +brew install librsvg +brew install swig +brew install doxygen +brew install unittest-cpp --cc=gcc-4.8. You must specify the c++ compiler with the --cc flag to be 4.7 or 4.8. +brew install qt5 +brew install cmake +brew install zeromq +``` + +## Mac Build Instructions (libopenshot-audio) +Since libopenshot-audio is not available in a Homebrew or MacPorts package, we need to go through a +few additional steps to manually build and install it. Launch a terminal and enter: + +``` +cd [libopenshot-audio repo folder] +mkdir build +cd build +cmake -d -G "Unix Makefiles" -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang ../ (CLang must be used due to GNU incompatible Objective-C code in some of the Apple frameworks) +make +make install +./src/openshot-audio-test-sound (This should play a test sound) +``` + +## Mac Build Instructions (libopenshot) +Run the following commands to build libopenshot: + +``` +$ cd [libopenshot repo folder] +$ mkdir build +$ cd build +$ cmake -G "Unix Makefiles" -DCMAKE_CXX_COMPILER=/usr/local/opt/gcc48/bin/g++-4.8 -DCMAKE_C_COMPILER=/usr/local/opt/gcc48/bin/gcc-4.8 -DCMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.4.2/ -DPYTHON_INCLUDE_DIR=/usr/local/Cellar/python3/3.3.2/Frameworks/Python.framework/Versions/3.3/include/python3.3m/ -DPYTHON_LIBRARY=/usr/local/Cellar/python3/3.3.2/Frameworks/Python.framework/Versions/3.3/lib/libpython3.3.dylib -DPython_FRAMEWORKS=/usr/local/Cellar/python3/3.3.2/Frameworks/Python.framework/ ../ -D"CMAKE_BUILD_TYPE:STRING=Debug" +``` + +The extra arguments on the cmake command make sure the compiler will be gcc4.8 and that cmake +knows where to look for the Qt header files and Python library. Double check these file paths, +as yours will likely be different. + +``` +make +``` + +If you are missing any dependencies for libopenshot, you will receive error messages at this point. +Just install the missing dependencies, and run the above commands again. Repeat until no error +messages are displayed and the build process completes. + +Also, if you are having trouble building, please see the CMake Flags section above, as it might +provide a solution for finding a missing folder path, missing Python 3 library, etc... + +To run all unit tests (and verify everything is working correctly), launch a terminal, and enter: + +``` +make test +``` + +To auto-generate the documentation for libopenshot, launch a terminal, and enter: + +``` +make doc +``` + +This will use doxygen to generate a folder of HTML files, with all classes and methods documented. +The folder is located at build/doc/html/. Once libopenshot has been successfully built, we need +to install it (i.e. copy it to the correct folder, so other libraries can find it). + +``` +make install +``` + +This should copy the binary files to /usr/local/lib/, and the header files to /usr/local/include/openshot/... +This is where other projects will look for the libopenshot files when building. Python 3 bindings are +also installed at this point. let's verify the python bindings work: + +``` +python3 (or python) +>>> import openshot +``` + +If no errors are displayed, you have successfully compiled and installed libopenshot on your +system. Congratulations and be sure to read our wiki on [Becoming an OpenShot Developer](https://github.com/OpenShot/openshot-qt/wiki/Become-a-Developer)! +Welcome to the OpenShot developer community! We look forward to meeting you! diff --git a/doc/INSTALL-WINDOWS.md b/doc/INSTALL-WINDOWS.md new file mode 100644 index 00000000..7f5b8f78 --- /dev/null +++ b/doc/INSTALL-WINDOWS.md @@ -0,0 +1,329 @@ +## Getting Started + +The best way to get started with libopenshot, is to learn about our build system, obtain all the +source code, install a development IDE and tools, and better understand our dependencies. So, +please read through the following sections, and follow the instructions. And keep in mind, +that your computer is likely different than the one used when writing these instructions. +Your file paths and versions of applications might be slightly different, so keep an eye out +for subtle file path differences in the commands you type. + +## Build Tools + +CMake is the backbone of our build system. It is a cross-platform build system, which +checks for dependencies, locates header files and libraries, generates makefiles, and +supports the cross-platform compiling of libopenshot and libopenshot-audio. CMake uses +an out-of-source build concept, where all temporary build files, such as makefiles, +object files, and even the final binaries, are created outside of the source code +folder, inside a /build/ sub-folder. This prevents the build process from cluttering +up the source code. These instructions have only been tested with the GNU compiler +(including MSYS2/MinGW for Windows). + +## Dependencies + +The following libraries are required to build libopenshot. Instructions on how to +install these dependencies vary for each operating system. Libraries and Executables +have been labeled in the list below to help distinguish between them. + +* ### FFmpeg (libavformat, libavcodec, libavutil, libavdevice, libavresample, libswscale) + * http://www.ffmpeg.org/ `(Library)` + * This library is used to decode and encode video, audio, and image files. It is also used to obtain information about media files, such as frame rate, sample rate, aspect ratio, and other common attributes. + +* ### ImageMagick++ (libMagick++, libMagickWand, libMagickCore) + * http://www.imagemagick.org/script/magick++.php `(Library)` + * This library is **optional**, and used to decode and encode images. + +* ### OpenShot Audio Library (libopenshot-audio) + * https://github.com/OpenShot/libopenshot-audio/ `(Library)` + * This library is used to mix, resample, host plug-ins, and play audio. It is based on the JUCE project, which is an outstanding audio library used by many different applications + +* ### Qt 5 (libqt5) + * http://www.qt.io/qt5/ `(Library)` + * Qt5 is used to display video, store image data, composite images, apply image effects, and many other utility functions, such as file system manipulation, high resolution timers, etc... + +* ### CMake (cmake) + * http://www.cmake.org/ `(Executable)` + * This executable is used to automate the generation of Makefiles, check for dependencies, and is the backbone of libopenshot’s cross-platform build process. + +* ### SWIG (swig) + * http://www.swig.org/ `(Executable)` + * This executable is used to generate the Python and Ruby bindings for libopenshot. It is a simple and powerful wrapper for C++ libraries, and supports many languages. + +* ### Python 3 (libpython) + * http://www.python.org/ `(Executable and Library)` + * This library is used by swig to create the Python (version 3+) bindings for libopenshot. This is also the official language used by OpenShot Video Editor (a graphical interface to libopenshot). + +* ### Doxygen (doxygen) + * http://www.stack.nl/~dimitri/doxygen/ `(Executable)` + * This executable is used to auto-generate the documentation used by libopenshot. + +* ### UnitTest++ (libunittest++) + * https://github.com/unittest-cpp/ `(Library)` + * This library is used to execute unit tests for libopenshot. It contains many macros used to keep our unit testing code very clean and simple. + +* ### ZeroMQ (libzmq) + * http://zeromq.org/ `(Library)` + * This library is used to communicate between libopenshot and other applications (publisher / subscriber). Primarily used to send debug data from libopenshot. + +* ### OpenMP (-fopenmp) + * http://openmp.org/wp/ `(Compiler Flag)` + * If your compiler supports this flag (GCC, Clang, and most other compilers), it provides libopenshot with easy methods of using parallel programming techniques to improve performance and take advantage of multi-core processors. + +## CMake Flags (Optional) +There are many different build flags that can be passed to cmake to adjust how libopenshot +is compiled. Some of these flags might be required when compiling on certain OSes, just +depending on how your build environment is setup. To add a build flag, follow this general +syntax: `cmake -DMAGICKCORE_HDRI_ENABLE=1 -DENABLE_TESTS=1 ../` + +* MAGICKCORE_HDRI_ENABLE (default 0) +* MAGICKCORE_QUANTUM_DEPTH (default 0) +* OPENSHOT_IMAGEMAGICK_COMPATIBILITY (default 0) +* DISABLE_TESTS (default 0) +* CMAKE_PREFIX_PATH (`/location/to/missing/library/`) +* PYTHON_INCLUDE_DIR (`/location/to/python/include/`) +* PYTHON_LIBRARY (`/location/to/python/lib.a`) +* PYTHON_FRAMEWORKS (`/usr/local/Cellar/python3/3.3.2/Frameworks/Python.framework/`) +* CMAKE_CXX_COMPILER (`/location/to/mingw/g++`) +* CMAKE_C_COMPILER (`/location/to/mingw/gcc`) + +## Environment Variables + +Many environment variables will need to be set during this Windows installation guide. +The command line will need to be closed and re-launched after any changes to your environment +variables. Also, dependency libraries will not be found during linking or execution without +being found in the PATH environment variable. So, if you get errors related to missing +commands or libraries, double check the PATH variable. + +The following environment variables need to be added to your “System Variables”. Be sure to +check each folder path for accuracy, as your paths will likely be different than this list. + +### Example Variables + +* DL_DIR (`C:\libdl`) +* DXSDK_DIR (`C:\Program Files\Microsoft DirectX SDK (June 2010)\`) +* FFMPEGDIR (`C:\ffmpeg-git-95f163b-win32-dev`) +* FREETYPE_DIR (`C:\Program Files\GnuWin32`) +* HOME (`C:\msys\1.0\home`) +* LIBOPENSHOT_AUDIO_DIR (`C:\Program Files\libopenshot-audio`) +* QTDIR (`C:\qt5`) +* SNDFILE_DIR (`C:\Program Files\libsndfile`) +* UNITTEST_DIR (`C:\UnitTest++`) +* ZMQDIR (`C:\msys2\usr\local\`) +* PATH (`The following paths are an example`) + * C:\Qt5\bin; C:\Qt5\MinGW\bin\; C:\msys\1.0\local\lib; C:\Program Files\CMake 2.8\bin; C:\UnitTest++\build; C:\libopenshot\build\src; C:\Program Files\doxygen\bin; C:\ffmpeg-git-95f163b-win32-dev\lib; C:\swigwin-2.0.4; C:\Python33; C:\Program Files\Project\lib; C:\msys2\usr\local\ + + + + + +## Obtaining Source Code + +The first step in installing libopenshot is to obtain the most recent source code. The source code +is available on [GitHub](https://github.com/OpenShot/libopenshot). Use the following command to +obtain the latest libopenshot source code. + +``` +git clone https://github.com/OpenShot/libopenshot.git +git clone https://github.com/OpenShot/libopenshot-audio.git +``` + +## Folder Structure (libopenshot) + +The source code is divided up into the following folders. + +* ### build/ + * This folder needs to be manually created, and is used by cmake to store the temporary + build files, such as makefiles, as well as the final binaries (library and test executables). + +* ### cmake/ + * This folder contains custom modules not included by default in cmake, used to find + dependency libraries and headers and determine if these libraries are installed. + +* ### doc/ + * This folder contains documentation and related files, such as logos and images + required by the doxygen auto-generated documentation. + +* ### include/ + * This folder contains all headers (*.h) used by libopenshot. + +* ### src/ + * This folder contains all source code (*.cpp) used by libopenshot. + +* ### tests/ + * This folder contains all unit test code. Each class has it’s own test file (*.cpp), and + uses UnitTest++ macros to keep the test code simple and manageable. + +* ### thirdparty/ + * This folder contains code not written by the OpenShot team. For example, jsoncpp, an + open-source JSON parser. + +## Install MSYS2 Dependencies + +Most Windows dependencies needed for libopenshot-audio, libopenshot, and openshot-qt +can be installed easily with MSYS2 and the pacman package manager. Follow these +directions to setup a Windows build environment for OpenShot. + +1) Install MSYS2: http://www.msys2.org/ + +2) Run MSYS2 command prompt (for example: `C:\msys64\msys2_shell.cmd`) + +3) Append PATH (so MSYS2 can find executables and libraries): + +``` +PATH=$PATH:/c/msys64/mingw64/bin:/c/msys64/mingw64/lib (64-bit PATH) + or +PATH=$PATH:/c/msys32/mingw32/bin:/c/msys32/mingw32/lib (32-bit PATH) +``` + +4) Update and upgrade all packages + +``` +pacman -Syu +``` + +5a) Install the following packages (**64-Bit**) + +``` +pacman -S --needed base-devel mingw-w64-x86_64-toolchain +pacman -S mingw64/mingw-w64-x86_64-ffmpeg +pacman -S mingw64/mingw-w64-x86_64-python3-pyqt5 +pacman -S mingw64/mingw-w64-x86_64-swig +pacman -S mingw64/mingw-w64-x86_64-cmake +pacman -S mingw64/mingw-w64-x86_64-doxygen +pacman -S mingw64/mingw-w64-x86_64-python3-pip +pacman -S mingw32/mingw-w64-i686-zeromq +pacman -S mingw64/mingw-w64-x86_64-python3-pyzmq +pacman -S mingw64/mingw-w64-x86_64-python3-cx_Freeze +pacman -S git + +# Install ImageMagick if needed (OPTIONAL and NOT NEEDED) +pacman -S mingw64/mingw-w64-x86_64-imagemagick +``` + +5b) **Or** Install the following packages (**32-Bit**) + +``` +pacman -S --needed base-devel mingw32/mingw-w64-i686-toolchain +pacman -S mingw32/mingw-w64-i686-ffmpeg +pacman -S mingw32/mingw-w64-i686-python3-pyqt5 +pacman -S mingw32/mingw-w64-i686-swig +pacman -S mingw32/mingw-w64-i686-cmake +pacman -S mingw32/mingw-w64-i686-doxygen +pacman -S mingw32/mingw-w64-i686-python3-pip +pacman -S mingw32/mingw-w64-i686-zeromq +pacman -S mingw32/mingw-w64-i686-python3-pyzmq +pacman -S mingw32/mingw-w64-i686-python3-cx_Freeze +pacman -S git + +# Install ImageMagick if needed (OPTIONAL and NOT NEEDED) +pacman -S mingw32/mingw-w32-x86_32-imagemagick +``` + +6) Install Python PIP Dependencies + +``` +pip3 install httplib2 +pip3 install slacker +pip3 install tinys3 +pip3 install github3.py +pip3 install requests +``` + +7) Download Unittest++ (https://github.com/unittest-cpp/unittest-cpp) into /MSYS2/[USER]/unittest-cpp-master/ + +``` +cmake -G "MSYS Makefiles" ../ -DCMAKE_MAKE_PROGRAM=mingw32-make -DCMAKE_INSTALL_PREFIX:PATH=/usr +mingw32-make install +``` + +8) ZMQ++ Header (This might not be needed anymore) + NOTE: Download and copy zmq.hpp into the /c/msys64/mingw64/include/ folder + +## Manual Dependencies + +* ### DLfcn + * https://github.com/dlfcn-win32/dlfcn-win32 + * Download and Extract the Win32 Static (.tar.bz2) archive to a local folder: C:\libdl\ + * Create an environment variable called DL_DIR and set the value to C:\libdl\. This environment variable will be used by CMake to find the binary and header file. + +* ### DirectX SDK / Windows SDK + * Windows 7: (DirectX SDK) http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=6812 + * Windows 8: (Windows SDK) + * https://msdn.microsoft.com/en-us/windows/desktop/aa904949 + * Download and Install the SDK Setup program. This is needed for the JUCE library to play audio on Windows. +Create an environment variable called DXSDK_DIR and set the value to C:\Program Files\Microsoft DirectX SDK (June 2010)\ (your path might be different). This environment variable will be used by CMake to find the binaries and header files. + +* ### libSndFile + * http://www.mega-nerd.com/libsndfile/#Download + * Download and Install the Win32 Setup program. + * Create an environment variable called SNDFILE_DIR and set the value to C:\Program Files\libsndfile. This environment variable will be used by CMake to find the binary and header files. + +* ### libzmq + * http://zeromq.org/intro:get-the-software + * Download source code (zip) + * Follow their instructions, and build with mingw + * Create an environment variable called ZMQDIR and set the value to C:\libzmq\build\ (the location of the compiled version). This environment variable will be used by CMake to find the binary and header files. + +## Windows Build Instructions (libopenshot-audio) +In order to compile libopenshot-audio, launch a command prompt and enter the following commands. This does not require the MSYS2 prompt, but it should work in both the Windows command prompt and the MSYS2 prompt. + +``` +cd [libopenshot-audio repo folder] +mkdir build +cd build +cmake -G “MinGW Makefiles” ../ +mingw32-make +mingw32-make install +openshot-audio-test-sound (This should play a test sound) +``` + +## Windows Build Instructions (libopenshot) +Run the following commands to build libopenshot: + +``` +cd [libopenshot repo folder] +mkdir build +cd build +cmake -G "MinGW Makefiles" -DPYTHON_INCLUDE_DIR="C:/Python34/include/" -DPYTHON_LIBRARY="C:/Python34/libs/libpython34.a" ../ +mingw32-make +``` + +If you are missing any dependencies for libopenshot, you will receive error messages at this point. +Just install the missing dependencies, and run the above commands again. Repeat until no error +messages are displayed and the build process completes. + +Also, if you are having trouble building, please see the CMake Flags section above, as +it might provide a solution for finding a missing folder path, missing Python 3 library, etc... + +To run all unit tests (and verify everything is working correctly), launch a terminal, and enter: + +``` +mingw32-make test +``` + +To auto-generate the documentation for libopenshot, launch a terminal, and enter: + +``` +mingw32-make doc +``` + +This will use doxygen to generate a folder of HTML files, with all classes and methods +documented. The folder is located at build/doc/html/. Once libopenshot has been successfully +built, we need to install it (i.e. copy it to the correct folder, so other libraries can find it). + +``` +mingw32-make install +``` + +This should copy the binary files to C:\Program Files\openshot\lib\, and the header +files to C:\Program Files\openshot\include\... This is where other projects will +look for the libopenshot files when building.. Python 3 bindings are also installed +at this point. let's verify the python bindings work: + +``` +python3 +>>> import openshot +``` + +If no errors are displayed, you have successfully compiled and installed libopenshot on +your system. Congratulations and be sure to read our wiki on [Becoming an OpenShot Developer](https://github.com/OpenShot/openshot-qt/wiki/Become-a-Developer)! +Welcome to the OpenShot developer community! We look forward to meeting you! diff --git a/doc/InstallationGuide.pdf b/doc/InstallationGuide.pdf deleted file mode 100644 index 4cd294e5216f6e9ef1ce43b7dc9b04422ca73182..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 834146 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ÐLLwK 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 Uo%Xjy5soYS~ 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 z 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

{MaB$a%86 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 Cfm3t< From a2b8eaff37896d0b5e97a203d611ca000f2bcbee Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Fri, 15 Feb 2019 10:11:45 -0800 Subject: [PATCH 097/223] Allow to use nvenc and nvdec in Windows for nVidia cards. nVidia card don't use the DX API like intel or AMD cards. If ffmpeg and the libraries are compiled with nvenc and nvdec support on WIndows this should(!) now work. --- src/FFmpegReader.cpp | 21 +++++++++++++++++++++ src/FFmpegWriter.cpp | 15 ++++++++++++--- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 7439db4d..1c80bb84 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -224,6 +224,23 @@ static enum AVPixelFormat get_hw_dec_format_d3(AVCodecContext *ctx, const enum A ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); return AV_PIX_FMT_NONE; } + +static enum AVPixelFormat get_hw_dec_format_cu(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) +{ + const enum AVPixelFormat *p; + + for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { + switch (*p) { + case AV_PIX_FMT_CUDA: + hw_de_av_pix_fmt_global = AV_PIX_FMT_CUDA; + hw_de_av_device_type_global = AV_HWDEVICE_TYPE_CUDA; + return *p; + break; + } + } + ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); + return AV_PIX_FMT_NONE; +} #endif #if defined(__APPLE__) @@ -378,6 +395,10 @@ void FFmpegReader::Open() hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2; pCodecCtx->get_format = get_hw_dec_format_dx; break; + case 2: + hw_de_av_device_type = AV_HWDEVICE_TYPE_CUDA; + pCodecCtx->get_format = get_hw_dec_format_cu; + break; case 3: hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2; pCodecCtx->get_format = get_hw_dec_format_dx; diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index 41285314..fb8040a8 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -203,9 +203,18 @@ void FFmpegWriter::SetVideoOptions(bool has_video, string codec, Fraction fps, i hw_en_av_device_type = AV_HWDEVICE_TYPE_DXVA2; } else { - new_codec = avcodec_find_encoder_by_name(codec.c_str()); - hw_en_on = 0; - hw_en_supported = 0; + if ( (strcmp(codec.c_str(),"h264_nvenc") == 0)) { + new_codec = avcodec_find_encoder_by_name(codec.c_str()); + hw_en_on = 1; + hw_en_supported = 1; + hw_en_av_pix_fmt = AV_PIX_FMT_CUDA; + hw_en_av_device_type = AV_HWDEVICE_TYPE_CUDA; + } + else { + new_codec = avcodec_find_encoder_by_name(codec.c_str()); + hw_en_on = 0; + hw_en_supported = 0; + } } #elif defined(__APPLE__) if ( (strcmp(codec.c_str(),"h264_qsv") == 0)) { From 728e0022a248e8ae7a7b169497e1fc5542b599c8 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Wed, 27 Feb 2019 23:25:54 -0600 Subject: [PATCH 098/223] MP3 Special Handling & Missing Frame Refactor (#196) * Handle MP3 files with special logic, since they typically only have 1 frame of video * Better support for missing frames (handling of missing audio and missing video data separated), and fixed a missing audio regression. --- src/FFmpegReader.cpp | 63 +++++++++++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index f6c69a8a..a8d1d746 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -472,8 +472,6 @@ std::shared_ptr FFmpegReader::GetFrame(int64_t requested_frame) #pragma omp critical (ReadStream) { // Check the cache a 2nd time (due to a potential previous lock) - if (has_missing_frames) - CheckMissingFrame(requested_frame); frame = final_cache.GetFrame(requested_frame); if (frame) { // Debug output @@ -648,9 +646,6 @@ std::shared_ptr FFmpegReader::ReadStream(int64_t requested_frame) // Check if working frames are 'finished' if (!is_seeking) { - // Check for any missing frames - CheckMissingFrame(requested_frame); - // Check for final frames CheckWorkingFrames(false, requested_frame); } @@ -1651,7 +1646,7 @@ AudioLocation FFmpegReader::GetAudioPTSLocation(int64_t pts) for (int64_t audio_frame = previous_packet_location.frame; audio_frame < location.frame; audio_frame++) { if (!missing_audio_frames.count(audio_frame)) { ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAudioPTSLocation (tracking missing frame)", "missing_audio_frame", audio_frame, "previous_audio_frame", previous_packet_location.frame, "new location frame", location.frame, "", -1, "", -1, "", -1); - missing_audio_frames.insert(pair(previous_packet_location.frame - 1, audio_frame)); + missing_audio_frames.insert(pair(audio_frame, previous_packet_location.frame - 1)); } } } @@ -1726,13 +1721,25 @@ bool FFmpegReader::CheckMissingFrame(int64_t requested_frame) map::iterator itr; bool found_missing_frame = false; - // Check if requested frame is a missing frame - if (missing_video_frames.count(requested_frame) || missing_audio_frames.count(requested_frame)) { - int64_t missing_source_frame = -1; - if (missing_video_frames.count(requested_frame)) - missing_source_frame = missing_video_frames.find(requested_frame)->second; - else if (missing_audio_frames.count(requested_frame)) - missing_source_frame = missing_audio_frames.find(requested_frame)->second; + // Special MP3 Handling (ignore more than 1 video frame) + if (info.has_audio and info.has_video) { + AVCodecID aCodecId = AV_FIND_DECODER_CODEC_ID(aStream); + AVCodecID vCodecId = AV_FIND_DECODER_CODEC_ID(pStream); + // If MP3 with single video frame, handle this special case by copying the previously + // decoded image to the new frame. Otherwise, it will spend a huge amount of + // CPU time looking for missing images for all the audio-only frames. + if (checked_count > 8 && !missing_video_frames.count(requested_frame) && + !processing_audio_frames.count(requested_frame) && processed_audio_frames.count(requested_frame) && + last_frame && last_video_frame->has_image_data && aCodecId == AV_CODEC_ID_MP3 && (vCodecId == AV_CODEC_ID_MJPEGB || vCodecId == AV_CODEC_ID_MJPEG)) { + missing_video_frames.insert(pair(requested_frame, last_video_frame->number)); + missing_video_frames_source.insert(pair(last_video_frame->number, requested_frame)); + missing_frames.Add(last_video_frame); + } + } + + // Check if requested video frame is a missing + if (missing_video_frames.count(requested_frame)) { + int64_t missing_source_frame = missing_video_frames.find(requested_frame)->second; // Increment missing source frame check count (or init to 1) if (checked_frames.count(missing_source_frame) == 0) @@ -1765,21 +1772,26 @@ bool FFmpegReader::CheckMissingFrame(int64_t requested_frame) std::shared_ptr parent_image = parent_frame->GetImage(); if (parent_image) { missing_frame->AddImage(std::shared_ptr(new QImage(*parent_image))); - processed_video_frames[missing_frame->number] = missing_frame->number; - processed_audio_frames[missing_frame->number] = missing_frame->number; - - // Move frame to final cache - final_cache.Add(missing_frame); - - // Remove frame from working cache - working_cache.Remove(missing_frame->number); - - // Update last_frame processed - last_frame = missing_frame->number; } } + } + // Check if requested audio frame is a missing + if (missing_audio_frames.count(requested_frame)) { + + // Create blank missing frame + std::shared_ptr missing_frame = CreateFrame(requested_frame); + + // Get Samples per frame (for this frame number) + int samples_per_frame = Frame::GetSamplesPerFrame(missing_frame->number, info.fps, info.sample_rate, info.channels); + + // Debug output + ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::CheckMissingFrame (Add Silence for Missing Audio Frame)", "requested_frame", requested_frame, "missing_frame->number", missing_frame->number, "samples_per_frame", samples_per_frame, "", -1, "", -1, "", -1); + + // Add this frame to the processed map (since it's already done) + missing_frame->AddAudioSilence(samples_per_frame); + processed_audio_frames[missing_frame->number] = missing_frame->number; } return found_missing_frame; @@ -1792,6 +1804,9 @@ void FFmpegReader::CheckWorkingFrames(bool end_of_stream, int64_t requested_fram bool checked_count_tripped = false; int max_checked_count = 80; + // Check if requested frame is 'missing' + CheckMissingFrame(requested_frame); + while (true) { // Get the front frame of working cache From 48a2656080931cde020d1fbe61fb95cca25b5222 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Fri, 1 Mar 2019 16:32:52 -0800 Subject: [PATCH 099/223] AVoid crashes with mp3 that are tagged by removing AV_ALLOCATE_IMAGE(pFrame, AV_GET_CODEC_PIXEL_FORMAT( ... --- src/FFmpegReader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index dae538f2..1b2c6b82 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -2369,7 +2369,7 @@ void FFmpegReader::CheckWorkingFrames(bool end_of_stream, int64_t requested_fram void FFmpegReader::CheckFPS() { check_fps = true; - AV_ALLOCATE_IMAGE(pFrame, AV_GET_CODEC_PIXEL_FORMAT(pStream, pCodecCtx), info.width, info.height); + int first_second_counter = 0; int second_second_counter = 0; From 8f385c112a4705b919e01a40a70239ba6a286852 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Wed, 6 Mar 2019 15:35:03 -0600 Subject: [PATCH 100/223] Improved Keyframe Performance (#197) * Refactor of Coorindate and Keyframe optimized for performance (much faster than previously). Also refactored FrameMapper to not use a Keyframe, and to only process frame mapping when needed... speeding up Json loading of project files. * Fixing FrameMapper linear calculation to match existing Keyframe calculation * Fixing Keyframe to pass original unit tests, and correctly calculate the Keyframe repeat fractions and deltas. * Small refactor of time mapped logic in Clip.cpp --- include/Clip.h | 2 +- include/Coordinate.h | 26 ----- src/Clip.cpp | 32 ++---- src/Coordinate.cpp | 15 +-- src/FrameMapper.cpp | 33 +++--- src/KeyFrame.cpp | 213 ++++++++++++++++++++++----------------- tests/KeyFrame_Tests.cpp | 16 +++ 7 files changed, 166 insertions(+), 171 deletions(-) diff --git a/include/Clip.h b/include/Clip.h index 26cdd211..3cd33b3a 100644 --- a/include/Clip.h +++ b/include/Clip.h @@ -127,7 +127,7 @@ namespace openshot { std::shared_ptr GetOrCreateFrame(int64_t number); /// Adjust the audio and image of a time mapped frame - std::shared_ptr get_time_mapped_frame(std::shared_ptr frame, int64_t frame_number); + void get_time_mapped_frame(std::shared_ptr frame, int64_t frame_number); /// Init default settings for a clip void init_settings(); diff --git a/include/Coordinate.h b/include/Coordinate.h index 0ca6b7b8..bf561084 100644 --- a/include/Coordinate.h +++ b/include/Coordinate.h @@ -52,11 +52,6 @@ namespace openshot { * \endcode */ class Coordinate { - private: - bool increasing; ///< Is the Y value increasing or decreasing? - Fraction repeated; ///< Fraction of repeated Y values (for example, 1/3 would be the first Y value of 3 repeated values) - double delta; ///< This difference in Y value (from the previous unique Y value) - public: double X; ///< The X value of the coordinate (usually representing the frame #) double Y; ///< The Y value of the coordinate (usually representing the value of the property being animated) @@ -69,27 +64,6 @@ namespace openshot { /// @param y The Y coordinate (usually representing the value of the property being animated) Coordinate(double x, double y); - /// @brief Set the repeating Fraction (used internally on the timeline, to track changes to coordinates) - /// @param is_repeated The fraction representing how many times this coordinate Y value repeats (only used on the timeline) - void Repeat(Fraction is_repeated) { repeated=is_repeated; } - - /// Get the repeating Fraction (used internally on the timeline, to track changes to coordinates) - Fraction Repeat() { return repeated; } - - /// @brief Set the increasing flag (used internally on the timeline, to track changes to coordinates) - /// @param is_increasing Indicates if this coordinate Y value is increasing (when compared to the previous coordinate) - void IsIncreasing(bool is_increasing) { increasing = is_increasing; } - - /// Get the increasing flag (used internally on the timeline, to track changes to coordinates) - bool IsIncreasing() { return increasing; } - - /// @brief Set the delta / difference between previous coordinate value (used internally on the timeline, to track changes to coordinates) - /// @param new_delta Indicates how much this Y value differs from the previous Y value - void Delta(double new_delta) { delta=new_delta; } - - /// Get the delta / difference between previous coordinate value (used internally on the timeline, to track changes to coordinates) - float Delta() { return delta; } - /// Get and Set JSON methods string Json(); ///< Generate JSON string of this object Json::Value JsonValue(); ///< Generate Json::JsonValue for this object diff --git a/src/Clip.cpp b/src/Clip.cpp index bd85d340..207494e3 100644 --- a/src/Clip.cpp +++ b/src/Clip.cpp @@ -340,13 +340,13 @@ std::shared_ptr Clip::GetFrame(int64_t requested_frame) frame->AddAudio(true, channel, 0, original_frame->GetAudioSamples(channel), original_frame->GetAudioSamplesCount(), 1.0); // Get time mapped frame number (used to increase speed, change direction, etc...) - std::shared_ptr new_frame = get_time_mapped_frame(frame, requested_frame); + get_time_mapped_frame(frame, requested_frame); // Apply effects to the frame (if any) - apply_effects(new_frame); + apply_effects(frame); // Return processed 'frame' - return new_frame; + return frame; } else // Throw error if reader not initialized @@ -389,7 +389,7 @@ void Clip::reverse_buffer(juce::AudioSampleBuffer* buffer) } // Adjust the audio and image of a time mapped frame -std::shared_ptr Clip::get_time_mapped_frame(std::shared_ptr frame, int64_t frame_number) +void Clip::get_time_mapped_frame(std::shared_ptr frame, int64_t frame_number) { // Check for valid reader if (!reader) @@ -400,7 +400,6 @@ std::shared_ptr Clip::get_time_mapped_frame(std::shared_ptr frame, if (time.Values.size() > 1) { const GenericScopedLock lock(getFrameCriticalSection); - std::shared_ptr new_frame; // create buffer and resampler juce::AudioSampleBuffer *samples = NULL; @@ -408,14 +407,7 @@ std::shared_ptr Clip::get_time_mapped_frame(std::shared_ptr frame, resampler = new AudioResampler(); // Get new frame number - int new_frame_number = adjust_frame_number_minimum(round(time.GetValue(frame_number))); - - // Create a new frame - int samples_in_frame = Frame::GetSamplesPerFrame(new_frame_number, reader->info.fps, reader->info.sample_rate, frame->GetAudioChannelsCount()); - new_frame = std::make_shared(new_frame_number, 1, 1, "#000000", samples_in_frame, frame->GetAudioChannelsCount()); - - // Copy the image from the new frame - new_frame->AddImage(std::shared_ptr(new QImage(*GetOrCreateFrame(new_frame_number)->GetImage()))); + int new_frame_number = frame->number; // Get delta (difference in previous Y value) int delta = int(round(time.GetDelta(frame_number))); @@ -463,7 +455,7 @@ std::shared_ptr Clip::get_time_mapped_frame(std::shared_ptr frame, start -= 1; for (int channel = 0; channel < channels; channel++) // Add new (slower) samples, to the frame object - new_frame->AddAudio(true, channel, 0, resampled_buffer->getReadPointer(channel, start), + frame->AddAudio(true, channel, 0, resampled_buffer->getReadPointer(channel, start), number_of_samples, 1.0f); // Clean up @@ -571,7 +563,7 @@ std::shared_ptr Clip::get_time_mapped_frame(std::shared_ptr frame, // Add the newly resized audio samples to the current frame for (int channel = 0; channel < channels; channel++) // Add new (slower) samples, to the frame object - new_frame->AddAudio(true, channel, 0, buffer->getReadPointer(channel), number_of_samples, 1.0f); + frame->AddAudio(true, channel, 0, buffer->getReadPointer(channel), number_of_samples, 1.0f); // Clean up buffer = NULL; @@ -592,7 +584,7 @@ std::shared_ptr Clip::get_time_mapped_frame(std::shared_ptr frame, // Add reversed samples to the frame object for (int channel = 0; channel < channels; channel++) - new_frame->AddAudio(true, channel, 0, samples->getReadPointer(channel), number_of_samples, 1.0f); + frame->AddAudio(true, channel, 0, samples->getReadPointer(channel), number_of_samples, 1.0f); } @@ -600,13 +592,7 @@ std::shared_ptr Clip::get_time_mapped_frame(std::shared_ptr frame, delete samples; samples = NULL; } - - // Return new time mapped frame - return new_frame; - - } else - // Use original frame - return frame; + } } // Adjust frame number minimum value diff --git a/src/Coordinate.cpp b/src/Coordinate.cpp index 41ee420a..60ea90b2 100644 --- a/src/Coordinate.cpp +++ b/src/Coordinate.cpp @@ -32,12 +32,12 @@ using namespace openshot; // Default constructor for a coordinate, which defaults the X and Y to zero (0,0) Coordinate::Coordinate() : - X(0), Y(0), increasing(true), repeated(1,1), delta(0.0) { + X(0), Y(0) { } // Constructor which also allows the user to set the X and Y Coordinate::Coordinate(double x, double y) : - X(x), Y(y), increasing(true), repeated(1,1), delta(0.0) { + X(x), Y(y) { } @@ -96,15 +96,4 @@ void Coordinate::SetJsonValue(Json::Value root) { X = root["X"].asDouble(); if (!root["Y"].isNull()) Y = root["Y"].asDouble(); - if (!root["increasing"].isNull()) - increasing = root["increasing"].asBool(); - if (!root["repeated"].isNull() && root["repeated"].isObject()) - { - if (!root["repeated"]["num"].isNull()) - repeated.num = root["repeated"]["num"].asInt(); - if (!root["repeated"]["den"].isNull()) - repeated.den = root["repeated"]["den"].asInt(); - } - if (!root["delta"].isNull()) - delta = root["delta"].asDouble(); } diff --git a/src/FrameMapper.cpp b/src/FrameMapper.cpp index 2a5d4276..73b7bb22 100644 --- a/src/FrameMapper.cpp +++ b/src/FrameMapper.cpp @@ -54,9 +54,6 @@ FrameMapper::FrameMapper(ReaderBase *reader, Fraction target, PulldownType targe // Adjust cache size based on size of frame and audio final_cache.SetMaxBytesFromInfo(OPEN_MP_NUM_PROCESSORS * 2, info.width, info.height, info.sample_rate, info.channels); - - // init mapping between original and target frames - Init(); } // Destructor @@ -205,22 +202,23 @@ void FrameMapper::Init() } } else { - // Map the remaining framerates using a simple Keyframe curve - // Calculate the difference (to be used as a multiplier) + // Map the remaining framerates using a linear algorithm double rate_diff = target.ToDouble() / original.ToDouble(); int64_t new_length = reader->info.video_length * rate_diff; - // Build curve for framerate mapping - Keyframe rate_curve; - rate_curve.AddPoint(1, 1, LINEAR); - rate_curve.AddPoint(new_length, reader->info.video_length, LINEAR); + // Calculate the value difference + double value_increment = (reader->info.video_length + 1) / (double) (new_length); // Loop through curve, and build list of frames + double original_frame_num = 1.0f; for (int64_t frame_num = 1; frame_num <= new_length; frame_num++) { // Add 2 fields per frame - AddField(rate_curve.GetInt(frame_num)); - AddField(rate_curve.GetInt(frame_num)); + AddField(round(original_frame_num)); + AddField(round(original_frame_num)); + + // Increment original frame number + original_frame_num += value_increment; } } @@ -310,6 +308,11 @@ void FrameMapper::Init() MappedFrame FrameMapper::GetMappedFrame(int64_t TargetFrameNumber) { + // Check if mappings are dirty (and need to be recalculated) + if (is_dirty) + // Recalculate mappings + Init(); + // Ignore mapping on single image readers if (info.has_video and !info.has_audio and info.has_single_image) { // Return the same number @@ -743,14 +746,16 @@ void FrameMapper::ChangeMapping(Fraction target_fps, PulldownType target_pulldow SWR_FREE(&avr); avr = NULL; } - - // Re-init mapping - Init(); } // Resample audio and map channels (if needed) void FrameMapper::ResampleMappedAudio(std::shared_ptr frame, int64_t original_frame_number) { + // Check if mappings are dirty (and need to be recalculated) + if (is_dirty) + // Recalculate mappings + Init(); + // Init audio buffers / variables int total_frame_samples = 0; int channels_in_frame = frame->GetAudioChannelsCount(); diff --git a/src/KeyFrame.cpp b/src/KeyFrame.cpp index 05a8a769..025484a3 100644 --- a/src/KeyFrame.cpp +++ b/src/KeyFrame.cpp @@ -296,17 +296,48 @@ bool Keyframe::IsIncreasing(int index) Process(); // Is index a valid point? - if (index >= 0 && index < Values.size()) - // Return value - return long(round(Values[index].IsIncreasing())); - else if (index < 0 && Values.size() > 0) - // Return the minimum value - return long(round(Values[0].IsIncreasing())); - else if (index >= Values.size() && Values.size() > 0) - // return the maximum value - return long(round(Values[Values.size() - 1].IsIncreasing())); + if (index >= 1 && (index + 1) < Values.size()) { + int64_t current_value = GetLong(index); + int64_t previous_value = 0; + int64_t next_value = 0; + int64_t previous_repeats = 0; + int64_t next_repeats = 0; + + // Loop backwards and look for the next unique value + for (vector::iterator backwards_it = Values.begin() + index; backwards_it != Values.begin(); backwards_it--) { + previous_value = long(round((*backwards_it).Y)); + if (previous_value == current_value) { + // Found same value + previous_repeats++; + } else { + // Found non repeating value, no more repeats found + break; + } + } + + // Loop forwards and look for the next unique value + for (vector::iterator forwards_it = Values.begin() + (index + 1); forwards_it != Values.end(); forwards_it++) { + next_value = long(round((*forwards_it).Y)); + if (next_value == current_value) { + // Found same value + next_repeats++; + } else { + // Found non repeating value, no more repeats found + break; + } + } + + if (current_value < next_value) { + // Increasing + return true; + } + else if (current_value >= next_value) { + // Decreasing + return false; + } + } else - // return the default direction of most curves (i.e. increasing is true) + // return default true (since most curves increase) return true; } @@ -385,6 +416,7 @@ void Keyframe::SetJsonValue(Json::Value root) { } // Get the fraction that represents how many times this value is repeated in the curve +// This is depreciated and will be removed soon. Fraction Keyframe::GetRepeatFraction(int64_t index) { // Check if it needs to be processed @@ -392,17 +424,42 @@ Fraction Keyframe::GetRepeatFraction(int64_t index) Process(); // Is index a valid point? - if (index >= 0 && index < Values.size()) - // Return value - return Values[index].Repeat(); - else if (index < 0 && Values.size() > 0) - // Return the minimum value - return Values[0].Repeat(); - else if (index >= Values.size() && Values.size() > 0) - // return the maximum value - return Values[Values.size() - 1].Repeat(); + if (index >= 1 && (index + 1) < Values.size()) { + int64_t current_value = GetLong(index); + int64_t previous_value = 0; + int64_t next_value = 0; + int64_t previous_repeats = 0; + int64_t next_repeats = 0; + + // Loop backwards and look for the next unique value + for (vector::iterator backwards_it = Values.begin() + index; backwards_it != Values.begin(); backwards_it--) { + previous_value = long(round((*backwards_it).Y)); + if (previous_value == current_value) { + // Found same value + previous_repeats++; + } else { + // Found non repeating value, no more repeats found + break; + } + } + + // Loop forwards and look for the next unique value + for (vector::iterator forwards_it = Values.begin() + (index + 1); forwards_it != Values.end(); forwards_it++) { + next_value = long(round((*forwards_it).Y)); + if (next_value == current_value) { + // Found same value + next_repeats++; + } else { + // Found non repeating value, no more repeats found + break; + } + } + + int64_t total_repeats = previous_repeats + next_repeats; + return Fraction(previous_repeats, total_repeats); + } else - // return a blank coordinate (0,0) + // return a blank coordinate return Fraction(1,1); } @@ -414,17 +471,48 @@ double Keyframe::GetDelta(int64_t index) Process(); // Is index a valid point? - if (index >= 0 && index < Values.size()) - // Return value - return Values[index].Delta(); - else if (index < 0 && Values.size() > 0) - // Return the minimum value - return Values[0].Delta(); - else if (index >= Values.size() && Values.size() > 0) - // return the maximum value - return Values[Values.size() - 1].Delta(); + if (index >= 1 && (index + 1) < Values.size()) { + int64_t current_value = GetLong(index); + int64_t previous_value = 0; + int64_t next_value = 0; + int64_t previous_repeats = 0; + int64_t next_repeats = 0; + + // Loop backwards and look for the next unique value + for (vector::iterator backwards_it = Values.begin() + index; backwards_it != Values.begin(); backwards_it--) { + previous_value = long(round((*backwards_it).Y)); + if (previous_value == current_value) { + // Found same value + previous_repeats++; + } else { + // Found non repeating value, no more repeats found + break; + } + } + + // Loop forwards and look for the next unique value + for (vector::iterator forwards_it = Values.begin() + (index + 1); forwards_it != Values.end(); forwards_it++) { + next_value = long(round((*forwards_it).Y)); + if (next_value == current_value) { + // Found same value + next_repeats++; + } else { + // Found non repeating value, no more repeats found + break; + } + } + + // Check for matching previous value (special case for 1st element) + if (current_value == previous_value) + previous_value = 0; + + if (previous_repeats == 1) + return current_value - previous_value; + else + return 0.0; + } else - // return a blank coordinate (0,0) + // return a blank coordinate return 0.0; } @@ -529,7 +617,7 @@ void Keyframe::PrintValues() { for (vector::iterator it = Values.begin() + 1; it != Values.end(); it++) { Coordinate c = *it; - cout << long(round(c.X)) << "\t" << c.Y << "\t" << c.IsIncreasing() << "\t" << c.Repeat().num << "\t" << c.Repeat().den << "\t" << c.Delta() << endl; + cout << long(round(c.X)) << "\t" << c.Y << "\t" << IsIncreasing(c.X) << "\t" << GetRepeatFraction(c.X).num << "\t" << GetRepeatFraction(c.X).den << "\t" << GetDelta(c.X) << endl; } } @@ -567,69 +655,6 @@ void Keyframe::Process() { // process segment p1,p2 ProcessSegment(x, p1, p2); } - - // Loop through each Value, and set the direction of the coordinate. This is used - // when time mapping, to determine what direction the audio waveforms play. - bool increasing = true; - int repeat_count = 1; - int64_t last_value = 0; - for (vector::iterator it = Values.begin() + 1; it != Values.end(); it++) { - int current_value = long(round((*it).Y)); - int64_t next_value = long(round((*it).Y)); - int64_t prev_value = long(round((*it).Y)); - if (it + 1 != Values.end()) - next_value = long(round((*(it + 1)).Y)); - if (it - 1 >= Values.begin()) - prev_value = long(round((*(it - 1)).Y)); - - // Loop forward and look for the next unique value (to determine direction) - for (vector::iterator direction_it = it + 1; direction_it != Values.end(); direction_it++) { - int64_t next = long(round((*direction_it).Y)); - - // Detect direction - if (current_value < next) - { - increasing = true; - break; - } - else if (current_value > next) - { - increasing = false; - break; - } - } - - // Set direction - (*it).IsIncreasing(increasing); - - // Detect repeated Y value - if (current_value == last_value) - // repeated, so increment count - repeat_count++; - else - // reset repeat counter - repeat_count = 1; - - // Detect how many 'more' times it's repeated - int additional_repeats = 0; - for (vector::iterator repeat_it = it + 1; repeat_it != Values.end(); repeat_it++) { - int64_t next = long(round((*repeat_it).Y)); - if (next == current_value) - // repeated, so increment count - additional_repeats++; - else - break; // stop looping - } - - // Set repeat fraction - (*it).Repeat(Fraction(repeat_count, repeat_count + additional_repeats)); - - // Set delta (i.e. different from previous unique Y value) - (*it).Delta(current_value - last_value); - - // track the last value - last_value = current_value; - } } // reset flag diff --git a/tests/KeyFrame_Tests.cpp b/tests/KeyFrame_Tests.cpp index fe96ea5a..cbd1a0e0 100644 --- a/tests/KeyFrame_Tests.cpp +++ b/tests/KeyFrame_Tests.cpp @@ -377,4 +377,20 @@ TEST(Keyframe_Remove_Duplicate_Point) // Spot check values from the curve CHECK_EQUAL(kf.GetLength(), 1); CHECK_CLOSE(kf.GetPoint(0).co.Y, 2.0, 0.01); +} + +TEST(Keyframe_Large_Number_Values) +{ + // Large value + int64_t 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(kf.GetLength(), large_value + 1); + CHECK_CLOSE(kf.GetPoint(0).co.Y, 1.0, 0.01); + CHECK_CLOSE(kf.GetPoint(1).co.Y, 100.0, 0.01); } \ No newline at end of file From 16c3d53d038b72002d51a7d0605d1223c521ecdb Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Fri, 8 Mar 2019 14:26:14 -0800 Subject: [PATCH 101/223] Fix problem with q values for crf quality setting. DOTO adjust q values according to desired quality --- src/FFmpegWriter.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index fb8040a8..90266ffa 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -1132,6 +1132,12 @@ AVStream* FFmpegWriter::add_video_stream() /* Init video encoder options */ if (info.video_bit_rate >= 1000) { c->bit_rate = info.video_bit_rate; + c->qmin = 2; + c->qmax = 30; + } + else { + c->qmin = 0; + c->qmax = 63; } //TODO: Implement variable bitrate feature (which actually works). This implementation throws @@ -1141,8 +1147,8 @@ AVStream* FFmpegWriter::add_video_stream() //c->rc_buffer_size = FFMAX(c->rc_max_rate, 15000000) * 112L / 15000000 * 16384; //if ( !c->rc_initial_buffer_occupancy ) // c->rc_initial_buffer_occupancy = c->rc_buffer_size * 3/4; - c->qmin = 2; - c->qmax = 30; +// c->qmin = 2; +// c->qmax = 30; /* resolution must be a multiple of two */ // TODO: require /2 height and width From 6a21c984e9898e84b6d58a6ef936c7ce9dca7b26 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Fri, 8 Mar 2019 14:41:40 -0800 Subject: [PATCH 102/223] Fixed q values for low fixed bitrates. Low bitrates should now be produced if desired. DOTO fine tune the q values --- src/FFmpegWriter.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index 90266ffa..17290bce 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -1132,8 +1132,14 @@ AVStream* FFmpegWriter::add_video_stream() /* Init video encoder options */ if (info.video_bit_rate >= 1000) { c->bit_rate = info.video_bit_rate; - c->qmin = 2; - c->qmax = 30; + if (info.video_bit_rate >= 1500000) { + c->qmin = 2; + c->qmax = 30; + } + else { + c->qmin = 0; + c->qmax = 63; + } } else { c->qmin = 0; From 6b9a9ca6ff1022ad60956d8643ca09decc839d38 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Fri, 8 Mar 2019 16:52:34 -0800 Subject: [PATCH 103/223] Removed the branch for low fixed bitrate q values as it did not work with mpeg2 export. Now for low fixed bitrates no presets for the q values are set. TODO find the optimum q values for each codec for low and high bitrates --- src/FFmpegWriter.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index 17290bce..cf4004c4 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -1136,10 +1136,8 @@ AVStream* FFmpegWriter::add_video_stream() c->qmin = 2; c->qmax = 30; } - else { - c->qmin = 0; - c->qmax = 63; - } + // Here should be the setting for low fixed bitrate + // Defaults are used because mpeg2 otherwise had problems } else { c->qmin = 0; @@ -1153,8 +1151,6 @@ AVStream* FFmpegWriter::add_video_stream() //c->rc_buffer_size = FFMAX(c->rc_max_rate, 15000000) * 112L / 15000000 * 16384; //if ( !c->rc_initial_buffer_occupancy ) // c->rc_initial_buffer_occupancy = c->rc_buffer_size * 3/4; -// c->qmin = 2; -// c->qmax = 30; /* resolution must be a multiple of two */ // TODO: require /2 height and width From 3f17601db6f7a46bc37e93fe9ab08ec85d1b819d Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sat, 9 Mar 2019 13:19:07 -0600 Subject: [PATCH 104/223] Invalid SetMaxSize Logic and Invalid CRF q settings in FFmpegWriter (#198) * Limit max size of preview to the timeline size (this renders very small profiles correctly) * Fixing CRF quality setting to allow "low" quality without breaking --- src/FFmpegWriter.cpp | 12 ++++++++++-- src/Timeline.cpp | 6 +++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index b9b6ba76..5d09341c 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -995,6 +995,16 @@ AVStream* FFmpegWriter::add_video_stream() /* Init video encoder options */ if (info.video_bit_rate >= 1000) { c->bit_rate = info.video_bit_rate; + if (info.video_bit_rate >= 1500000) { + c->qmin = 2; + c->qmax = 30; + } + // Here should be the setting for low fixed bitrate + // Defaults are used because mpeg2 otherwise had problems + } + else { + c->qmin = 0; + c->qmax = 63; } //TODO: Implement variable bitrate feature (which actually works). This implementation throws @@ -1004,8 +1014,6 @@ AVStream* FFmpegWriter::add_video_stream() //c->rc_buffer_size = FFMAX(c->rc_max_rate, 15000000) * 112L / 15000000 * 16384; //if ( !c->rc_initial_buffer_occupancy ) // c->rc_initial_buffer_occupancy = c->rc_buffer_size * 3/4; - c->qmin = 2; - c->qmax = 30; /* resolution must be a multiple of two */ // TODO: require /2 height and width diff --git a/src/Timeline.cpp b/src/Timeline.cpp index 28c8956a..384273bd 100644 --- a/src/Timeline.cpp +++ b/src/Timeline.cpp @@ -1442,7 +1442,7 @@ void Timeline::ClearAllCache() { // Set Max Image Size (used for performance optimization). Convenience function for setting // Settings::Instance()->MAX_WIDTH and Settings::Instance()->MAX_HEIGHT. void Timeline::SetMaxSize(int width, int height) { - // Init max image size - Settings::Instance()->MAX_WIDTH = width; - Settings::Instance()->MAX_HEIGHT = height; + // Init max image size (choose the smallest one) + Settings::Instance()->MAX_WIDTH = min(width, info.width); + Settings::Instance()->MAX_HEIGHT = min(height, info.height); } \ No newline at end of file From b5ebc996eefde4aace92bf287a8a3297488cdd75 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Sun, 10 Mar 2019 10:42:48 -0700 Subject: [PATCH 105/223] Adjust the q values for low quality crf settings --- src/FFmpegWriter.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index cf4004c4..b1eb7ec3 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -1140,8 +1140,14 @@ AVStream* FFmpegWriter::add_video_stream() // Defaults are used because mpeg2 otherwise had problems } else { - c->qmin = 0; - c->qmax = 63; + if (info.video_bit_rate < 40) { + c->qmin = 0; + c->qmax = 63; + } + else { + c->qmin = info.video_bit_rate - 5; + c->qmax = 63; + } } //TODO: Implement variable bitrate feature (which actually works). This implementation throws From a170d7db38f6a3798bec89bb8dd77ee3c8c5f384 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Sun, 10 Mar 2019 13:09:47 -0700 Subject: [PATCH 106/223] Check if the codec supports CRF when setting q values --- src/FFmpegWriter.cpp | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index b1eb7ec3..12bfda54 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -1140,13 +1140,28 @@ AVStream* FFmpegWriter::add_video_stream() // Defaults are used because mpeg2 otherwise had problems } else { - if (info.video_bit_rate < 40) { - c->qmin = 0; - c->qmax = 63; - } - else { - c->qmin = info.video_bit_rate - 5; - c->qmax = 63; + // Check if codec supports crf + switch (c->codec_id) { + #if (LIBAVCODEC_VERSION_MAJOR >= 58) + case AV_CODEC_ID_AV1 : + #endif + case AV_CODEC_ID_VP8 : + case AV_CODEC_ID_VP9 : + case AV_CODEC_ID_H264 : + case AV_CODEC_ID_H265 : + if (info.video_bit_rate < 40) { + c->qmin = 0; + c->qmax = 63; + } + else { + c->qmin = info.video_bit_rate - 5; + c->qmax = 63; + } + break; + default: + // Here should be the setting for codecs that don't support crf + // For now defaults are used + break; } } From cd4e25ea6782748b18bbf5ba9709b727b029ac9b Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Sun, 10 Mar 2019 13:28:56 -0700 Subject: [PATCH 107/223] Fix for FFmpeg 2.x --- src/FFmpegWriter.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index 12bfda54..1c9094a6 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -1142,13 +1142,15 @@ AVStream* FFmpegWriter::add_video_stream() else { // Check if codec supports crf switch (c->codec_id) { + #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 39, 101) #if (LIBAVCODEC_VERSION_MAJOR >= 58) case AV_CODEC_ID_AV1 : #endif - case AV_CODEC_ID_VP8 : case AV_CODEC_ID_VP9 : - case AV_CODEC_ID_H264 : case AV_CODEC_ID_H265 : + #endif + case AV_CODEC_ID_VP8 : + case AV_CODEC_ID_H264 : if (info.video_bit_rate < 40) { c->qmin = 0; c->qmax = 63; From 650d3ec820b3a14d77d2d3642dfacd2be7dbffe0 Mon Sep 17 00:00:00 2001 From: Chris Kirmse Date: Thu, 14 Mar 2019 09:26:56 -0700 Subject: [PATCH 108/223] fix grammar error with possessive its and update sample for audio parameter --- CMakeLists.txt | 8 ++++---- include/Clip.h | 4 ++-- include/EffectBase.h | 2 +- include/FFmpegReader.h | 8 ++++---- include/FFmpegWriter.h | 4 ++-- include/Frame.h | 2 +- include/ImageReader.h | 2 +- include/ImageWriter.h | 2 +- include/QtImageReader.h | 2 +- include/Timeline.h | 1 + include/effects/Bars.h | 2 +- include/effects/Blur.h | 2 +- include/effects/Brightness.h | 2 +- include/effects/ChromaKey.h | 2 +- include/effects/ColorShift.h | 2 +- include/effects/Crop.h | 2 +- include/effects/Deinterlace.h | 2 +- include/effects/Hue.h | 2 +- include/effects/Mask.h | 2 +- include/effects/Negate.h | 2 +- include/effects/Pixelate.h | 2 +- include/effects/Saturation.h | 2 +- include/effects/Shift.h | 2 +- include/effects/Wave.h | 2 +- src/ChunkReader.cpp | 2 +- src/Clip.cpp | 2 +- src/DummyReader.cpp | 2 +- src/FFmpegReader.cpp | 10 +++++----- src/Frame.cpp | 2 +- src/ImageReader.cpp | 6 +++--- src/QtImageReader.cpp | 6 +++--- src/TextReader.cpp | 4 ++-- 32 files changed, 49 insertions(+), 48 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index eaf7d65f..a4926953 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,8 @@ # # Copyright (c) 2008-2014 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 +# 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 @@ -41,8 +41,8 @@ MESSAGE("Determining Version Number (from Version.h file)") #### Get the lines related to libopenshot version from the Version.h header file(STRINGS ${CMAKE_CURRENT_SOURCE_DIR}/include/Version.h OPENSHOT_VERSION_LINES REGEX "#define[ ]+OPENSHOT_VERSION_.*[0-9]+;.*") - -#### Set each line into it's own variable + +#### Set each line into its own variable list (GET OPENSHOT_VERSION_LINES 0 LINE_MAJOR) list (GET OPENSHOT_VERSION_LINES 1 LINE_MINOR) list (GET OPENSHOT_VERSION_LINES 2 LINE_BUILD) diff --git a/include/Clip.h b/include/Clip.h index 3cd33b3a..83387fac 100644 --- a/include/Clip.h +++ b/include/Clip.h @@ -142,8 +142,8 @@ namespace openshot { void reverse_buffer(juce::AudioSampleBuffer* buffer); public: - GravityType gravity; ///< The gravity of a clip determines where it snaps to it's parent - ScaleType scale; ///< The scale determines how a clip should be resized to fit it's parent + GravityType gravity; ///< The gravity of a clip determines where it snaps to its parent + ScaleType scale; ///< The scale determines how a clip should be resized to fit its parent AnchorType anchor; ///< The anchor determines what parent a clip should snap to FrameDisplayType display; ///< The format to display the frame number (if any) VolumeMixType mixing; ///< What strategy should be followed when mixing audio with other clips diff --git a/include/EffectBase.h b/include/EffectBase.h index 209369a8..8df7fabf 100644 --- a/include/EffectBase.h +++ b/include/EffectBase.h @@ -82,7 +82,7 @@ namespace openshot /// modified openshot::Frame object /// /// The frame object is passed into this method, and a frame_number is passed in which - /// tells the effect which settings to use from it's keyframes (starting at 1). + /// tells the effect which settings to use from its keyframes (starting at 1). /// /// @returns The modified openshot::Frame object /// @param frame The frame object that needs the effect applied to it diff --git a/include/FFmpegReader.h b/include/FFmpegReader.h index eaa45943..f24dac9f 100644 --- a/include/FFmpegReader.h +++ b/include/FFmpegReader.h @@ -150,7 +150,7 @@ namespace openshot /// Check the current seek position and determine if we need to seek again bool CheckSeek(bool is_video); - /// Check if a frame is missing and attempt to replace it's frame image (and + /// Check if a frame is missing and attempt to replace its frame image (and bool CheckMissingFrame(int64_t requested_frame); /// Check the working queue, and move finished frames to the finished queue @@ -198,10 +198,10 @@ namespace openshot /// Read the stream until we find the requested Frame std::shared_ptr ReadStream(int64_t requested_frame); - /// Remove AVFrame from cache (and deallocate it's memory) + /// Remove AVFrame from cache (and deallocate its memory) void RemoveAVFrame(AVFrame*); - /// Remove AVPacket from cache (and deallocate it's memory) + /// Remove AVPacket from cache (and deallocate its memory) void RemoveAVPacket(AVPacket*); /// Seek to a specific Frame. This is not always frame accurate, it's more of an estimation on many codecs. @@ -228,7 +228,7 @@ namespace openshot /// frame 1, or it throws one of the following exceptions. FFmpegReader(string path); - /// Constructor for FFmpegReader. This only opens the media file to inspect it's properties + /// Constructor for FFmpegReader. This only opens the media file to inspect its properties /// if inspect_reader=true. When not inspecting the media file, it's much faster, and useful /// when you are inflating the object using JSON after instantiating it. FFmpegReader(string path, bool inspect_reader); diff --git a/include/FFmpegWriter.h b/include/FFmpegWriter.h index e219f72c..c9351af7 100644 --- a/include/FFmpegWriter.h +++ b/include/FFmpegWriter.h @@ -83,7 +83,7 @@ namespace openshot * FFmpegWriter w("/home/jonathan/NewVideo.webm"); * * // Set options - * w.SetAudioOptions(true, "libvorbis", 44100, 2, 128000); // Sample Rate: 44100, Channels: 2, Bitrate: 128000 + * w.SetAudioOptions(true, "libvorbis", 44100, 2, ChannelLayout::LAYOUT_STEREO, 128000); // Sample Rate: 44100, Channels: 2, Bitrate: 128000 * w.SetVideoOptions(true, "libvpx", openshot::Fraction(24,1), 720, 480, openshot::Fraction(1,1), false, false, 300000); // FPS: 24, Size: 720x480, Pixel Ratio: 1/1, Bitrate: 300000 * * // Open the writer @@ -110,7 +110,7 @@ namespace openshot * FFmpegWriter w("/home/jonathan/NewVideo.webm"); * * // Set options - * w.SetAudioOptions(true, "libvorbis", 44100, 2, 128000); // Sample Rate: 44100, Channels: 2, Bitrate: 128000 + * w.SetAudioOptions(true, "libvorbis", 44100, 2, ChannelLayout::LAYOUT_STEREO, 128000); // Sample Rate: 44100, Channels: 2, Bitrate: 128000 * w.SetVideoOptions(true, "libvpx", openshot::Fraction(24,1), 720, 480, openshot::Fraction(1,1), false, false, 300000); // FPS: 24, Size: 720x480, Pixel Ratio: 1/1, Bitrate: 300000 * * // Prepare Streams (Optional method that must be called before any SetOption calls) diff --git a/include/Frame.h b/include/Frame.h index eba7f8bb..f89149e2 100644 --- a/include/Frame.h +++ b/include/Frame.h @@ -197,7 +197,7 @@ namespace openshot /// Clean up buffer after QImage is deleted static void cleanUpBuffer(void *info); - /// Clear the waveform image (and deallocate it's memory) + /// Clear the waveform image (and deallocate its memory) void ClearWaveform(); /// Copy data and pointers from another Frame instance diff --git a/include/ImageReader.h b/include/ImageReader.h index e698e0c1..a44cbf07 100644 --- a/include/ImageReader.h +++ b/include/ImageReader.h @@ -77,7 +77,7 @@ namespace openshot /// frame 1, or it throws one of the following exceptions. ImageReader(string path); - /// Constructor for ImageReader. This only opens the media file to inspect it's properties + /// Constructor for ImageReader. This only opens the media file to inspect its properties /// if inspect_reader=true. When not inspecting the media file, it's much faster, and useful /// when you are inflating the object using JSON after instantiating it. ImageReader(string path, bool inspect_reader); diff --git a/include/ImageWriter.h b/include/ImageWriter.h index 25177134..ded29a5b 100644 --- a/include/ImageWriter.h +++ b/include/ImageWriter.h @@ -127,7 +127,7 @@ namespace openshot /// @param height Height in pixels of image /// @param quality Quality of image (0 to 100, 70 is default) /// @param loops Number of times to repeat the image (used on certain multi-frame image formats, such as GIF) - /// @param combine Combine frames into a single image (if possible), or save each frame as it's own image + /// @param combine Combine frames into a single image (if possible), or save each frame as its own image void SetVideoOptions(string format, Fraction fps, int width, int height, int quality, int loops, bool combine); diff --git a/include/QtImageReader.h b/include/QtImageReader.h index 6b260f15..a7be1ab6 100644 --- a/include/QtImageReader.h +++ b/include/QtImageReader.h @@ -75,7 +75,7 @@ namespace openshot /// frame 1, or it throws one of the following exceptions. QtImageReader(string path); - /// Constructor for QtImageReader. This only opens the media file to inspect it's properties + /// Constructor for QtImageReader. This only opens the media file to inspect its properties /// if inspect_reader=true. When not inspecting the media file, it's much faster, and useful /// when you are inflating the object using JSON after instantiating it. QtImageReader(string path, bool inspect_reader); diff --git a/include/Timeline.h b/include/Timeline.h index 312add2e..6fd88796 100644 --- a/include/Timeline.h +++ b/include/Timeline.h @@ -100,6 +100,7 @@ namespace openshot { * Fraction(25,1), // framerate * 44100, // sample rate * 2 // channels + * ChannelLayout::LAYOUT_STEREO, * ); * * // Create some clips diff --git a/include/effects/Bars.h b/include/effects/Bars.h index 27d21725..247a914a 100644 --- a/include/effects/Bars.h +++ b/include/effects/Bars.h @@ -80,7 +80,7 @@ namespace openshot /// modified openshot::Frame object /// /// The frame object is passed into this method, and a frame_number is passed in which - /// tells the effect which settings to use from it's keyframes (starting at 1). + /// tells the effect which settings to use from its keyframes (starting at 1). /// /// @returns The modified openshot::Frame object /// @param frame The frame object that needs the effect applied to it diff --git a/include/effects/Blur.h b/include/effects/Blur.h index 314dabbe..f72bf484 100644 --- a/include/effects/Blur.h +++ b/include/effects/Blur.h @@ -93,7 +93,7 @@ namespace openshot /// modified openshot::Frame object /// /// The frame object is passed into this method, and a frame_number is passed in which - /// tells the effect which settings to use from it's keyframes (starting at 1). + /// tells the effect which settings to use from its keyframes (starting at 1). /// /// @returns The modified openshot::Frame object /// @param frame The frame object that needs the effect applied to it diff --git a/include/effects/Brightness.h b/include/effects/Brightness.h index 67ab4c9c..eb74c58f 100644 --- a/include/effects/Brightness.h +++ b/include/effects/Brightness.h @@ -80,7 +80,7 @@ namespace openshot /// modified openshot::Frame object /// /// The frame object is passed into this method, and a frame_number is passed in which - /// tells the effect which settings to use from it's keyframes (starting at 1). + /// tells the effect which settings to use from its keyframes (starting at 1). /// /// @returns The modified openshot::Frame object /// @param frame The frame object that needs the effect applied to it diff --git a/include/effects/ChromaKey.h b/include/effects/ChromaKey.h index 000dbba4..b93b8f51 100644 --- a/include/effects/ChromaKey.h +++ b/include/effects/ChromaKey.h @@ -77,7 +77,7 @@ namespace openshot /// modified openshot::Frame object /// /// The frame object is passed into this method, and a frame_number is passed in which - /// tells the effect which settings to use from it's keyframes (starting at 1). + /// tells the effect which settings to use from its keyframes (starting at 1). /// /// @returns The modified openshot::Frame object /// @param frame The frame object that needs the effect applied to it diff --git a/include/effects/ColorShift.h b/include/effects/ColorShift.h index 4b3de2bb..88d6e4d0 100644 --- a/include/effects/ColorShift.h +++ b/include/effects/ColorShift.h @@ -84,7 +84,7 @@ namespace openshot /// modified openshot::Frame object /// /// The frame object is passed into this method, and a frame_number is passed in which - /// tells the effect which settings to use from it's keyframes (starting at 1). + /// tells the effect which settings to use from its keyframes (starting at 1). /// /// @returns The modified openshot::Frame object /// @param frame The frame object that needs the effect applied to it diff --git a/include/effects/Crop.h b/include/effects/Crop.h index 7921a78d..c9836824 100644 --- a/include/effects/Crop.h +++ b/include/effects/Crop.h @@ -80,7 +80,7 @@ namespace openshot /// modified openshot::Frame object /// /// The frame object is passed into this method, and a frame_number is passed in which - /// tells the effect which settings to use from it's keyframes (starting at 1). + /// tells the effect which settings to use from its keyframes (starting at 1). /// /// @returns The modified openshot::Frame object /// @param frame The frame object that needs the effect applied to it diff --git a/include/effects/Deinterlace.h b/include/effects/Deinterlace.h index c1fb7227..1bb29062 100644 --- a/include/effects/Deinterlace.h +++ b/include/effects/Deinterlace.h @@ -73,7 +73,7 @@ namespace openshot /// modified openshot::Frame object /// /// The frame object is passed into this method, and a frame_number is passed in which - /// tells the effect which settings to use from it's keyframes (starting at 1). + /// tells the effect which settings to use from its keyframes (starting at 1). /// /// @returns The modified openshot::Frame object /// @param frame The frame object that needs the effect applied to it diff --git a/include/effects/Hue.h b/include/effects/Hue.h index 4f680047..fa59ab4e 100644 --- a/include/effects/Hue.h +++ b/include/effects/Hue.h @@ -70,7 +70,7 @@ namespace openshot /// modified openshot::Frame object /// /// The frame object is passed into this method, and a frame_number is passed in which - /// tells the effect which settings to use from it's keyframes (starting at 1). + /// tells the effect which settings to use from its keyframes (starting at 1). /// /// @returns The modified openshot::Frame object /// @param frame The frame object that needs the effect applied to it diff --git a/include/effects/Mask.h b/include/effects/Mask.h index ef707f5f..210ffe9c 100644 --- a/include/effects/Mask.h +++ b/include/effects/Mask.h @@ -91,7 +91,7 @@ namespace openshot /// modified openshot::Frame object /// /// The frame object is passed into this method, and a frame_number is passed in which - /// tells the effect which settings to use from it's keyframes (starting at 1). + /// tells the effect which settings to use from its keyframes (starting at 1). /// /// @returns The modified openshot::Frame object /// @param frame The frame object that needs the effect applied to it diff --git a/include/effects/Negate.h b/include/effects/Negate.h index 84621132..2397e331 100644 --- a/include/effects/Negate.h +++ b/include/effects/Negate.h @@ -61,7 +61,7 @@ namespace openshot /// modified openshot::Frame object /// /// The frame object is passed into this method, and a frame_number is passed in which - /// tells the effect which settings to use from it's keyframes (starting at 1). + /// tells the effect which settings to use from its keyframes (starting at 1). /// /// @returns The modified openshot::Frame object /// @param frame The frame object that needs the effect applied to it diff --git a/include/effects/Pixelate.h b/include/effects/Pixelate.h index b8ca2998..1d62192d 100644 --- a/include/effects/Pixelate.h +++ b/include/effects/Pixelate.h @@ -79,7 +79,7 @@ namespace openshot /// modified openshot::Frame object /// /// The frame object is passed into this method, and a frame_number is passed in which - /// tells the effect which settings to use from it's keyframes (starting at 1). + /// tells the effect which settings to use from its keyframes (starting at 1). /// /// @returns The modified openshot::Frame object /// @param frame The frame object that needs the effect applied to it diff --git a/include/effects/Saturation.h b/include/effects/Saturation.h index d49069a6..f7c20341 100644 --- a/include/effects/Saturation.h +++ b/include/effects/Saturation.h @@ -77,7 +77,7 @@ namespace openshot /// modified openshot::Frame object /// /// The frame object is passed into this method, and a frame_number is passed in which - /// tells the effect which settings to use from it's keyframes (starting at 1). + /// tells the effect which settings to use from its keyframes (starting at 1). /// /// @returns The modified openshot::Frame object /// @param frame The frame object that needs the effect applied to it diff --git a/include/effects/Shift.h b/include/effects/Shift.h index 86ccf7a4..875c736b 100644 --- a/include/effects/Shift.h +++ b/include/effects/Shift.h @@ -73,7 +73,7 @@ namespace openshot /// modified openshot::Frame object /// /// The frame object is passed into this method, and a frame_number is passed in which - /// tells the effect which settings to use from it's keyframes (starting at 1). + /// tells the effect which settings to use from its keyframes (starting at 1). /// /// @returns The modified openshot::Frame object /// @param frame The frame object that needs the effect applied to it diff --git a/include/effects/Wave.h b/include/effects/Wave.h index 04c1620f..11047097 100644 --- a/include/effects/Wave.h +++ b/include/effects/Wave.h @@ -79,7 +79,7 @@ namespace openshot /// modified openshot::Frame object /// /// The frame object is passed into this method, and a frame_number is passed in which - /// tells the effect which settings to use from it's keyframes (starting at 1). + /// tells the effect which settings to use from its keyframes (starting at 1). /// /// @returns The modified openshot::Frame object /// @param frame The frame object that needs the effect applied to it diff --git a/src/ChunkReader.cpp b/src/ChunkReader.cpp index fe552243..ac6835d0 100644 --- a/src/ChunkReader.cpp +++ b/src/ChunkReader.cpp @@ -42,7 +42,7 @@ ChunkReader::ChunkReader(string path, ChunkVersion chunk_version) previous_location.number = 0; previous_location.frame = 0; - // Open and Close the reader, to populate it's attributes (such as height, width, etc...) + // Open and Close the reader, to populate its attributes (such as height, width, etc...) Open(); Close(); } diff --git a/src/Clip.cpp b/src/Clip.cpp index 207494e3..33ff5093 100644 --- a/src/Clip.cpp +++ b/src/Clip.cpp @@ -272,7 +272,7 @@ void Clip::Close() // Get end position of clip (trim end of video), which can be affected by the time curve. float Clip::End() { - // if a time curve is present, use it's length + // if a time curve is present, use its length if (time.Points.size() > 1) { // Determine the FPS fo this clip diff --git a/src/DummyReader.cpp b/src/DummyReader.cpp index 8fe039ab..25c45f5c 100644 --- a/src/DummyReader.cpp +++ b/src/DummyReader.cpp @@ -66,7 +66,7 @@ DummyReader::DummyReader(Fraction fps, int width, int height, int sample_rate, i info.display_ratio.num = size.num; info.display_ratio.den = size.den; - // Open and Close the reader, to populate it's attributes (such as height, width, etc...) + // Open and Close the reader, to populate its attributes (such as height, width, etc...) Open(); Close(); } diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index a8d1d746..d5f69b0c 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -49,7 +49,7 @@ FFmpegReader::FFmpegReader(string path) missing_frames.SetMaxBytesFromInfo(OPEN_MP_NUM_PROCESSORS * 2, info.width, info.height, info.sample_rate, info.channels); final_cache.SetMaxBytesFromInfo(OPEN_MP_NUM_PROCESSORS * 2, info.width, info.height, info.sample_rate, info.channels); - // Open and Close the reader, to populate it's attributes (such as height, width, etc...) + // Open and Close the reader, to populate its attributes (such as height, width, etc...) Open(); Close(); } @@ -71,7 +71,7 @@ FFmpegReader::FFmpegReader(string path, bool inspect_reader) missing_frames.SetMaxBytesFromInfo(OPEN_MP_NUM_PROCESSORS * 2, info.width, info.height, info.sample_rate, info.channels); final_cache.SetMaxBytesFromInfo(OPEN_MP_NUM_PROCESSORS * 2, info.width, info.height, info.sample_rate, info.channels); - // Open and Close the reader, to populate it's attributes (such as height, width, etc...) + // Open and Close the reader, to populate its attributes (such as height, width, etc...) if (inspect_reader) { Open(); Close(); @@ -1698,7 +1698,7 @@ bool FFmpegReader::IsPartialFrame(int64_t requested_frame) { return seek_trash; } -// Check if a frame is missing and attempt to replace it's frame image (and +// Check if a frame is missing and attempt to replace its frame image (and bool FFmpegReader::CheckMissingFrame(int64_t requested_frame) { // Lock @@ -2025,7 +2025,7 @@ void FFmpegReader::CheckFPS() } } -// Remove AVFrame from cache (and deallocate it's memory) +// Remove AVFrame from cache (and deallocate its memory) void FFmpegReader::RemoveAVFrame(AVFrame* remove_frame) { // Remove pFrame (if exists) @@ -2042,7 +2042,7 @@ void FFmpegReader::RemoveAVFrame(AVFrame* remove_frame) } } -// Remove AVPacket from cache (and deallocate it's memory) +// Remove AVPacket from cache (and deallocate its memory) void FFmpegReader::RemoveAVPacket(AVPacket* remove_packet) { // deallocate memory for packet diff --git a/src/Frame.cpp b/src/Frame.cpp index a00fc232..263eb5af 100644 --- a/src/Frame.cpp +++ b/src/Frame.cpp @@ -263,7 +263,7 @@ std::shared_ptr Frame::GetWaveform(int width, int height, int Red, int G return wave_image; } -// Clear the waveform image (and deallocate it's memory) +// Clear the waveform image (and deallocate its memory) void Frame::ClearWaveform() { if (wave_image) diff --git a/src/ImageReader.cpp b/src/ImageReader.cpp index f535666a..937457b0 100644 --- a/src/ImageReader.cpp +++ b/src/ImageReader.cpp @@ -31,14 +31,14 @@ using namespace openshot; ImageReader::ImageReader(string path) : path(path), is_open(false) { - // Open and Close the reader, to populate it's attributes (such as height, width, etc...) + // Open and Close the reader, to populate its attributes (such as height, width, etc...) Open(); Close(); } ImageReader::ImageReader(string path, bool inspect_reader) : path(path), is_open(false) { - // Open and Close the reader, to populate it's attributes (such as height, width, etc...) + // Open and Close the reader, to populate its attributes (such as height, width, etc...) if (inspect_reader) { Open(); Close(); @@ -106,7 +106,7 @@ void ImageReader::Close() { // Mark as "closed" is_open = false; - + // Delete the image image.reset(); } diff --git a/src/QtImageReader.cpp b/src/QtImageReader.cpp index c500d221..a0108f48 100644 --- a/src/QtImageReader.cpp +++ b/src/QtImageReader.cpp @@ -43,14 +43,14 @@ using namespace openshot; QtImageReader::QtImageReader(string path) : path(path), is_open(false) { - // Open and Close the reader, to populate it's attributes (such as height, width, etc...) + // Open and Close the reader, to populate its attributes (such as height, width, etc...) Open(); Close(); } QtImageReader::QtImageReader(string path, bool inspect_reader) : path(path), is_open(false) { - // Open and Close the reader, to populate it's attributes (such as height, width, etc...) + // Open and Close the reader, to populate its attributes (such as height, width, etc...) if (inspect_reader) { Open(); Close(); @@ -143,7 +143,7 @@ void QtImageReader::Close() { // Mark as "closed" is_open = false; - + // Delete the image image.reset(); diff --git a/src/TextReader.cpp b/src/TextReader.cpp index 8234aa5d..5c8256b6 100644 --- a/src/TextReader.cpp +++ b/src/TextReader.cpp @@ -32,7 +32,7 @@ using namespace openshot; /// Default constructor (blank text) TextReader::TextReader() : width(1024), height(768), x_offset(0), y_offset(0), text(""), font("Arial"), size(10.0), text_color("#ffffff"), background_color("#000000"), is_open(false), gravity(GRAVITY_CENTER) { - // Open and Close the reader, to populate it's attributes (such as height, width, etc...) + // Open and Close the reader, to populate its attributes (such as height, width, etc...) Open(); Close(); } @@ -40,7 +40,7 @@ TextReader::TextReader() : width(1024), height(768), x_offset(0), y_offset(0), t TextReader::TextReader(int width, int height, int x_offset, int y_offset, GravityType gravity, string text, string font, double size, string text_color, string background_color) : width(width), height(height), x_offset(x_offset), y_offset(y_offset), text(text), font(font), size(size), text_color(text_color), background_color(background_color), is_open(false), gravity(gravity) { - // Open and Close the reader, to populate it's attributes (such as height, width, etc...) + // Open and Close the reader, to populate its attributes (such as height, width, etc...) Open(); Close(); } From abe44ade73f1f10850985c62f8114c2f46224e43 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Fri, 15 Mar 2019 13:14:52 -0500 Subject: [PATCH 109/223] Bumping version to 0.2.3 (SO 17) --- include/Version.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/Version.h b/include/Version.h index 4ff2c36c..6077cde5 100644 --- a/include/Version.h +++ b/include/Version.h @@ -36,8 +36,8 @@ #define OPENSHOT_VERSION_MAJOR 0; /// Major version number is incremented when huge features are added or improved. #define OPENSHOT_VERSION_MINOR 2; /// Minor version is incremented when smaller (but still very important) improvements are added. -#define OPENSHOT_VERSION_BUILD 2; /// Build number is incremented when minor bug fixes and less important improvements are added. -#define OPENSHOT_VERSION_SO 16; /// Shared object version number. This increments any time the API and ABI changes (so old apps will no longer link) +#define OPENSHOT_VERSION_BUILD 3; /// Build number is incremented when minor bug fixes and less important improvements are added. +#define OPENSHOT_VERSION_SO 17; /// Shared object version number. This increments any time the API and ABI changes (so old apps will no longer link) #define OPENSHOT_VERSION_MAJOR_MINOR STRINGIZE(OPENSHOT_VERSION_MAJOR) "." STRINGIZE(OPENSHOT_VERSION_MINOR); /// A string of the "Major.Minor" version #define OPENSHOT_VERSION_ALL STRINGIZE(OPENSHOT_VERSION_MAJOR) "." STRINGIZE(OPENSHOT_VERSION_MINOR) "." STRINGIZE(OPENSHOT_VERSION_BUILD); /// A string of the entire version "Major.Minor.Build" From 3e5dc1d7278504eae20427f0b5f0bcce7a4a97e5 Mon Sep 17 00:00:00 2001 From: "FeRD (Frank Dana)" Date: Fri, 29 Mar 2019 08:10:31 -0400 Subject: [PATCH 110/223] Streamline libopenshot-audio discovery --- cmake/Modules/FindOpenShotAudio.cmake | 27 +++------------------------ 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/cmake/Modules/FindOpenShotAudio.cmake b/cmake/Modules/FindOpenShotAudio.cmake index 1de4529b..0aeb0e1f 100644 --- a/cmake/Modules/FindOpenShotAudio.cmake +++ b/cmake/Modules/FindOpenShotAudio.cmake @@ -7,31 +7,12 @@ message("$ENV{LIBOPENSHOT_AUDIO_DIR}") -# Find the base directory of juce includes -find_path(LIBOPENSHOT_AUDIO_BASE_DIR JuceHeader.h +# Find the libopenshot-audio header files +find_path(LIBOPENSHOT_AUDIO_INCLUDE_DIR JuceHeader.h PATHS $ENV{LIBOPENSHOT_AUDIO_DIR}/include/libopenshot-audio/ /usr/include/libopenshot-audio/ /usr/local/include/libopenshot-audio/ ) -# Get a list of all header file paths -FILE(GLOB_RECURSE JUCE_HEADER_FILES - ${LIBOPENSHOT_AUDIO_BASE_DIR}/*.h -) - -# Loop through each header file -FOREACH(HEADER_PATH ${JUCE_HEADER_FILES}) - # Get the directory of each header file - get_filename_component(HEADER_DIRECTORY ${HEADER_PATH} - PATH - ) - - # Append each directory into the HEADER_DIRECTORIES list - LIST(APPEND HEADER_DIRECTORIES ${HEADER_DIRECTORY}) -ENDFOREACH(HEADER_PATH) - -# Remove duplicates from the header directories list -LIST(REMOVE_DUPLICATES HEADER_DIRECTORIES) - # Find the libopenshot-audio.so (check env var first) find_library(LIBOPENSHOT_AUDIO_LIBRARY NAMES libopenshot-audio openshot-audio @@ -48,9 +29,7 @@ find_library(LIBOPENSHOT_AUDIO_LIBRARY set(LIBOPENSHOT_AUDIO_LIBRARIES ${LIBOPENSHOT_AUDIO_LIBRARY}) set(LIBOPENSHOT_AUDIO_LIBRARY ${LIBOPENSHOT_AUDIO_LIBRARIES}) -# Seems to work fine with just the base dir (rather than all the actual include folders) -set(LIBOPENSHOT_AUDIO_INCLUDE_DIR ${LIBOPENSHOT_AUDIO_BASE_DIR} ) -set(LIBOPENSHOT_AUDIO_INCLUDE_DIRS ${LIBOPENSHOT_AUDIO_BASE_DIR} ) +set(LIBOPENSHOT_AUDIO_INCLUDE_DIRS ${LIBOPENSHOT_AUDIO_INCLUDE_DIR} ) include(FindPackageHandleStandardArgs) # handle the QUIETLY and REQUIRED arguments and set LIBOPENSHOT_AUDIO_FOUND to TRUE From 7e7f5c341ac1e1bf5342eaaed72abcf8864bfb18 Mon Sep 17 00:00:00 2001 From: "FeRD (Frank Dana)" Date: Fri, 29 Mar 2019 08:11:19 -0400 Subject: [PATCH 111/223] Use new Juce header file location --- include/AudioBufferSource.h | 2 +- include/AudioReaderSource.h | 2 +- include/AudioResampler.h | 2 +- include/Clip.h | 2 +- include/Frame.h | 2 +- include/Settings.h | 2 +- include/ZmqLogger.h | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/include/AudioBufferSource.h b/include/AudioBufferSource.h index 57826f66..b1571c8d 100644 --- a/include/AudioBufferSource.h +++ b/include/AudioBufferSource.h @@ -37,7 +37,7 @@ #endif #include -#include "JuceLibraryCode/JuceHeader.h" +#include "JuceHeader.h" using namespace std; diff --git a/include/AudioReaderSource.h b/include/AudioReaderSource.h index 31b17d80..3c82e7ad 100644 --- a/include/AudioReaderSource.h +++ b/include/AudioReaderSource.h @@ -37,7 +37,7 @@ #endif #include -#include "JuceLibraryCode/JuceHeader.h" +#include "JuceHeader.h" #include "ReaderBase.h" using namespace std; diff --git a/include/AudioResampler.h b/include/AudioResampler.h index 412d49b1..f084329c 100644 --- a/include/AudioResampler.h +++ b/include/AudioResampler.h @@ -38,7 +38,7 @@ #define _NDEBUG #endif -#include "JuceLibraryCode/JuceHeader.h" +#include "JuceHeader.h" #include "AudioBufferSource.h" #include "Exceptions.h" diff --git a/include/Clip.h b/include/Clip.h index 3cd33b3a..c9b914b8 100644 --- a/include/Clip.h +++ b/include/Clip.h @@ -36,7 +36,7 @@ #include #include #include -#include "JuceLibraryCode/JuceHeader.h" +#include "JuceHeader.h" #include "AudioResampler.h" #include "ClipBase.h" #include "Color.h" diff --git a/include/Frame.h b/include/Frame.h index eba7f8bb..67c5ca02 100644 --- a/include/Frame.h +++ b/include/Frame.h @@ -56,7 +56,7 @@ #ifdef USE_IMAGEMAGICK #include "Magick++.h" #endif -#include "JuceLibraryCode/JuceHeader.h" +#include "JuceHeader.h" #include "ChannelLayouts.h" #include "AudioBufferSource.h" #include "AudioResampler.h" diff --git a/include/Settings.h b/include/Settings.h index ec26338b..0629cc4f 100644 --- a/include/Settings.h +++ b/include/Settings.h @@ -29,7 +29,7 @@ #define OPENSHOT_SETTINGS_H -#include "JuceLibraryCode/JuceHeader.h" +#include "JuceHeader.h" #include #include #include diff --git a/include/ZmqLogger.h b/include/ZmqLogger.h index 62773e68..112b2327 100644 --- a/include/ZmqLogger.h +++ b/include/ZmqLogger.h @@ -29,7 +29,7 @@ #define OPENSHOT_LOGGER_H -#include "JuceLibraryCode/JuceHeader.h" +#include "JuceHeader.h" #include #include #include From 13ab8b48bd99c9e76fbc835a0ed592e1ce565a85 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sat, 30 Mar 2019 01:47:49 -0500 Subject: [PATCH 112/223] Moving JuceHeader.h in ZmqLogger.h, to come after system libraries (to prevent error on Mac related to Point declaration) --- include/ZmqLogger.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/ZmqLogger.h b/include/ZmqLogger.h index 112b2327..8ababac0 100644 --- a/include/ZmqLogger.h +++ b/include/ZmqLogger.h @@ -29,7 +29,6 @@ #define OPENSHOT_LOGGER_H -#include "JuceHeader.h" #include #include #include @@ -40,6 +39,7 @@ #include #include #include +#include "JuceHeader.h" using namespace std; From 6e2600dcd4e949b8208bf9571b7d1811e84729a6 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sat, 30 Mar 2019 14:23:50 -0500 Subject: [PATCH 113/223] Moving JuceHeader.h below other includes, to be sure it is always included after system libraries (for Mac Point build failure) --- include/AudioReaderSource.h | 2 +- include/AudioResampler.h | 2 +- include/Clip.h | 2 +- include/EffectBase.h | 2 +- include/Frame.h | 2 +- include/Settings.h | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/AudioReaderSource.h b/include/AudioReaderSource.h index 3c82e7ad..76c3dc7d 100644 --- a/include/AudioReaderSource.h +++ b/include/AudioReaderSource.h @@ -37,8 +37,8 @@ #endif #include -#include "JuceHeader.h" #include "ReaderBase.h" +#include "JuceHeader.h" using namespace std; diff --git a/include/AudioResampler.h b/include/AudioResampler.h index f084329c..b81bfc3e 100644 --- a/include/AudioResampler.h +++ b/include/AudioResampler.h @@ -38,9 +38,9 @@ #define _NDEBUG #endif -#include "JuceHeader.h" #include "AudioBufferSource.h" #include "Exceptions.h" +#include "JuceHeader.h" namespace openshot { diff --git a/include/Clip.h b/include/Clip.h index c9b914b8..346629e4 100644 --- a/include/Clip.h +++ b/include/Clip.h @@ -36,7 +36,6 @@ #include #include #include -#include "JuceHeader.h" #include "AudioResampler.h" #include "ClipBase.h" #include "Color.h" @@ -47,6 +46,7 @@ #include "Fraction.h" #include "KeyFrame.h" #include "ReaderBase.h" +#include "JuceHeader.h" using namespace std; using namespace openshot; diff --git a/include/EffectBase.h b/include/EffectBase.h index 209369a8..d38e3f45 100644 --- a/include/EffectBase.h +++ b/include/EffectBase.h @@ -32,8 +32,8 @@ #include #include #include "ClipBase.h" -#include "Frame.h" #include "Json.h" +#include "Frame.h" using namespace std; diff --git a/include/Frame.h b/include/Frame.h index 67c5ca02..66d8ccfa 100644 --- a/include/Frame.h +++ b/include/Frame.h @@ -56,11 +56,11 @@ #ifdef USE_IMAGEMAGICK #include "Magick++.h" #endif -#include "JuceHeader.h" #include "ChannelLayouts.h" #include "AudioBufferSource.h" #include "AudioResampler.h" #include "Fraction.h" +#include "JuceHeader.h" #pragma SWIG nowarn=362 using namespace std; diff --git a/include/Settings.h b/include/Settings.h index 0629cc4f..e46f12e0 100644 --- a/include/Settings.h +++ b/include/Settings.h @@ -29,7 +29,6 @@ #define OPENSHOT_SETTINGS_H -#include "JuceHeader.h" #include #include #include @@ -40,6 +39,7 @@ #include #include #include +#include "JuceHeader.h" using namespace std; From 76e87e6145a71f58839f7527de406669a68cbfb9 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Wed, 3 Apr 2019 23:07:47 -0500 Subject: [PATCH 114/223] Reorder x64 windows build before x86 build --- .gitlab-ci.yml | 60 +++++++++++++++++++++++++------------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f8b13908..f0f868c9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -56,6 +56,36 @@ mac-builder: tags: - mac +windows-builder-x64: + stage: build-libopenshot + artifacts: + expire_in: 6 months + paths: + - build\install-x64\* + script: + - try { Invoke-WebRequest -Uri "http://gitlab.openshot.org/OpenShot/libopenshot-audio/-/jobs/artifacts/$CI_COMMIT_REF_NAME/download?job=windows-builder-x64" -Headers @{"PRIVATE-TOKEN"="$ACCESS_TOKEN"} -OutFile "artifacts.zip" } catch { $_.Exception.Response.StatusCode.Value__ } + - if (-not (Test-Path "artifacts.zip")) { Invoke-WebRequest -Uri "http://gitlab.openshot.org/OpenShot/libopenshot-audio/-/jobs/artifacts/develop/download?job=windows-builder-x64" -Headers @{"PRIVATE-TOKEN"="$ACCESS_TOKEN"} -OutFile "artifacts.zip" } + - Expand-Archive -Path artifacts.zip -DestinationPath . + - $env:LIBOPENSHOT_AUDIO_DIR = "$CI_PROJECT_DIR\build\install-x64" + - $env:UNITTEST_DIR = "C:\msys64\usr" + - $env:ZMQDIR = "C:\msys64\usr" + - $env:Path = "C:\msys64\mingw64\bin;C:\msys64\mingw64\lib;C:\msys64\usr\lib\cmake\UnitTest++;C:\msys64\home\jonathan\depot_tools;C:\msys64\usr;C:\msys64\usr\lib;" + $env:Path; + - New-Item -ItemType Directory -Force -Path build + - New-Item -ItemType Directory -Force -Path build\install-x64\python + - cd build + - cmake -D"CMAKE_INSTALL_PREFIX:PATH=$CI_PROJECT_DIR\build\install-x64" -G "MinGW Makefiles" -D"CMAKE_BUILD_TYPE:STRING=Release" ../ + - mingw32-make install + - Move-Item -Force -path "C:\msys64\mingw64\lib\python3.6\site-packages\*openshot*" -destination "install-x64\python\" + - cp src\libopenshot.dll install-x64\lib + - New-Item -path "install-x64/share/" -Name "$CI_PROJECT_NAME" -Value "CI_PROJECT_NAME:$CI_PROJECT_NAME`nCI_COMMIT_REF_NAME:$CI_COMMIT_REF_NAME`nCI_COMMIT_SHA:$CI_COMMIT_SHA`nCI_JOB_ID:$CI_JOB_ID" -ItemType file -force + - $PREV_GIT_LABEL=(git describe --tags --abbrev=0) + - git log "$PREV_GIT_LABEL..HEAD" --oneline --pretty=format:"%C(auto,yellow)%h%C(auto,magenta)% %C(auto,blue)%>(12,trunc)%ad %C(auto,green)%<(25,trunc)%aN%C(auto,reset)%s%C(auto,red)% gD% D" --date=short > "install-x64/share/$CI_PROJECT_NAME.log" + when: always + except: + - tags + tags: + - windows + windows-builder-x86: stage: build-libopenshot artifacts: @@ -87,36 +117,6 @@ windows-builder-x86: tags: - windows -windows-builder-x64: - stage: build-libopenshot - artifacts: - expire_in: 6 months - paths: - - build\install-x64\* - script: - - try { Invoke-WebRequest -Uri "http://gitlab.openshot.org/OpenShot/libopenshot-audio/-/jobs/artifacts/$CI_COMMIT_REF_NAME/download?job=windows-builder-x64" -Headers @{"PRIVATE-TOKEN"="$ACCESS_TOKEN"} -OutFile "artifacts.zip" } catch { $_.Exception.Response.StatusCode.Value__ } - - if (-not (Test-Path "artifacts.zip")) { Invoke-WebRequest -Uri "http://gitlab.openshot.org/OpenShot/libopenshot-audio/-/jobs/artifacts/develop/download?job=windows-builder-x64" -Headers @{"PRIVATE-TOKEN"="$ACCESS_TOKEN"} -OutFile "artifacts.zip" } - - Expand-Archive -Path artifacts.zip -DestinationPath . - - $env:LIBOPENSHOT_AUDIO_DIR = "$CI_PROJECT_DIR\build\install-x64" - - $env:UNITTEST_DIR = "C:\msys64\usr" - - $env:ZMQDIR = "C:\msys64\usr" - - $env:Path = "C:\msys64\mingw64\bin;C:\msys64\mingw64\lib;C:\msys64\usr\lib\cmake\UnitTest++;C:\msys64\home\jonathan\depot_tools;C:\msys64\usr;C:\msys64\usr\lib;" + $env:Path; - - New-Item -ItemType Directory -Force -Path build - - New-Item -ItemType Directory -Force -Path build\install-x64\python - - cd build - - cmake -D"CMAKE_INSTALL_PREFIX:PATH=$CI_PROJECT_DIR\build\install-x64" -G "MinGW Makefiles" -D"CMAKE_BUILD_TYPE:STRING=Release" ../ - - mingw32-make install - - Move-Item -Force -path "C:\msys64\mingw64\lib\python3.6\site-packages\*openshot*" -destination "install-x64\python\" - - cp src\libopenshot.dll install-x64\lib - - New-Item -path "install-x64/share/" -Name "$CI_PROJECT_NAME" -Value "CI_PROJECT_NAME:$CI_PROJECT_NAME`nCI_COMMIT_REF_NAME:$CI_COMMIT_REF_NAME`nCI_COMMIT_SHA:$CI_COMMIT_SHA`nCI_JOB_ID:$CI_JOB_ID" -ItemType file -force - - $PREV_GIT_LABEL=(git describe --tags --abbrev=0) - - git log "$PREV_GIT_LABEL..HEAD" --oneline --pretty=format:"%C(auto,yellow)%h%C(auto,magenta)% %C(auto,blue)%>(12,trunc)%ad %C(auto,green)%<(25,trunc)%aN%C(auto,reset)%s%C(auto,red)% gD% D" --date=short > "install-x64/share/$CI_PROJECT_NAME.log" - when: always - except: - - tags - tags: - - windows - trigger-pipeline: stage: trigger-openshot-qt script: From 9dbb063dedd3a759f11e9abd14ab4f89ffddfc40 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Thu, 4 Apr 2019 00:55:47 -0500 Subject: [PATCH 115/223] Persist any error returned by JUCE during initialise() method, such as sample rate issues on Windows (when playback and recording sample rates do not match, which breaks WASAPI) --- include/Qt/AudioPlaybackThread.h | 82 +++++++++++++++++--------------- include/QtPlayer.h | 3 ++ src/Frame.cpp | 8 +++- src/Qt/AudioPlaybackThread.cpp | 9 +++- src/QtPlayer.cpp | 14 ++++-- 5 files changed, 71 insertions(+), 45 deletions(-) diff --git a/include/Qt/AudioPlaybackThread.h b/include/Qt/AudioPlaybackThread.h index 68a2be3b..1d654756 100644 --- a/include/Qt/AudioPlaybackThread.h +++ b/include/Qt/AudioPlaybackThread.h @@ -57,12 +57,15 @@ namespace openshot class AudioDeviceManagerSingleton { private: /// Default constructor (Don't allow user to create an instance of this singleton) - AudioDeviceManagerSingleton(){}; + AudioDeviceManagerSingleton(){ initialise_error=""; }; /// Private variable to keep track of singleton instance static AudioDeviceManagerSingleton * m_pInstance; public: + /// Error found during JUCE initialise method + string initialise_error; + /// Create or get an instance of this singleton (invoke the class with this method) static AudioDeviceManagerSingleton * Instance(int numChannels); @@ -78,52 +81,55 @@ namespace openshot */ class AudioPlaybackThread : Thread { - AudioSourcePlayer player; - AudioTransportSource transport; - MixerAudioSource mixer; - AudioReaderSource *source; - double sampleRate; - int numChannels; - WaitableEvent play; - WaitableEvent played; - int buffer_size; - bool is_playing; - SafeTimeSliceThread time_thread; - - /// Constructor - AudioPlaybackThread(); - /// Destructor - ~AudioPlaybackThread(); + AudioSourcePlayer player; + AudioTransportSource transport; + MixerAudioSource mixer; + AudioReaderSource *source; + double sampleRate; + int numChannels; + WaitableEvent play; + WaitableEvent played; + int buffer_size; + bool is_playing; + SafeTimeSliceThread time_thread; - /// Set the current thread's reader - void Reader(ReaderBase *reader); + /// Constructor + AudioPlaybackThread(); + /// Destructor + ~AudioPlaybackThread(); - /// Get the current frame object (which is filling the buffer) - std::shared_ptr getFrame(); + /// Set the current thread's reader + void Reader(ReaderBase *reader); - /// Get the current frame number being played - int64_t getCurrentFramePosition(); + /// Get the current frame object (which is filling the buffer) + std::shared_ptr getFrame(); - /// Play the audio - void Play(); + /// Get the current frame number being played + int64_t getCurrentFramePosition(); - /// Seek the audio thread - void Seek(int64_t new_position); + /// Play the audio + void Play(); - /// Stop the audio playback - void Stop(); + /// Seek the audio thread + void Seek(int64_t new_position); - /// Start thread - void run(); - - /// Set Speed (The speed and direction to playback a reader (1=normal, 2=fast, 3=faster, -1=rewind, etc...) - void setSpeed(int new_speed) { if (source) source->setSpeed(new_speed); } + /// Stop the audio playback + void Stop(); - /// Get Speed (The speed and direction to playback a reader (1=normal, 2=fast, 3=faster, -1=rewind, etc...) - int getSpeed() const { if (source) return source->getSpeed(); else return 1; } + /// Start thread + void run(); - friend class PlayerPrivate; - friend class QtPlayer; + /// Set Speed (The speed and direction to playback a reader (1=normal, 2=fast, 3=faster, -1=rewind, etc...) + void setSpeed(int new_speed) { if (source) source->setSpeed(new_speed); } + + /// Get Speed (The speed and direction to playback a reader (1=normal, 2=fast, 3=faster, -1=rewind, etc...) + int getSpeed() const { if (source) return source->getSpeed(); else return 1; } + + /// Get Audio Error (if any) + string getError() { return AudioDeviceManagerSingleton::Instance(numChannels)->initialise_error; } + + friend class PlayerPrivate; + friend class QtPlayer; }; } diff --git a/include/QtPlayer.h b/include/QtPlayer.h index 354bbfc8..a1a7ee0c 100644 --- a/include/QtPlayer.h +++ b/include/QtPlayer.h @@ -59,6 +59,9 @@ namespace openshot /// Close audio device void CloseAudioDevice(); + /// Get Error (if any) + string GetError(); + /// Play the video void Play(); diff --git a/src/Frame.cpp b/src/Frame.cpp index a00fc232..24b653a9 100644 --- a/src/Frame.cpp +++ b/src/Frame.cpp @@ -953,11 +953,15 @@ void Frame::Play() return; AudioDeviceManager deviceManager; - deviceManager.initialise (0, /* number of input channels */ + String error = deviceManager.initialise (0, /* number of input channels */ 2, /* number of output channels */ 0, /* no XML settings.. */ true /* select default device on failure */); - //deviceManager.playTestSound(); + + // Output error (if any) + if (error.isNotEmpty()) { + cout << "Error on initialise(): " << error.toStdString() << endl; + } AudioSourcePlayer audioSourcePlayer; deviceManager.addAudioCallback (&audioSourcePlayer); diff --git a/src/Qt/AudioPlaybackThread.cpp b/src/Qt/AudioPlaybackThread.cpp index fac2e3fc..c64bd688 100644 --- a/src/Qt/AudioPlaybackThread.cpp +++ b/src/Qt/AudioPlaybackThread.cpp @@ -42,11 +42,18 @@ namespace openshot m_pInstance = new AudioDeviceManagerSingleton; // Initialize audio device only 1 time - m_pInstance->audioDeviceManager.initialise ( + String error = m_pInstance->audioDeviceManager.initialise ( 0, /* number of input channels */ numChannels, /* number of output channels */ 0, /* no XML settings.. */ true /* select default device on failure */); + + // Persist any errors detected + if (error.isNotEmpty()) { + m_pInstance->initialise_error = error.toStdString(); + } else { + m_pInstance->initialise_error = ""; + } } return m_pInstance; diff --git a/src/QtPlayer.cpp b/src/QtPlayer.cpp index 028a9b70..4f53c7ca 100644 --- a/src/QtPlayer.cpp +++ b/src/QtPlayer.cpp @@ -59,12 +59,21 @@ void QtPlayer::CloseAudioDevice() AudioDeviceManagerSingleton::Instance(0)->CloseAudioDevice(); } +// Return any error string during initialization +string QtPlayer::GetError() { + if (reader && threads_started) { + // Get error from audio thread (if any) + return p->audioPlayback->getError(); + } else { + return ""; + } +} + void QtPlayer::SetSource(const std::string &source) { FFmpegReader *ffreader = new FFmpegReader(source); ffreader->DisplayInfo(); - //reader = new FrameMapper(ffreader, ffreader->info.fps, PULLDOWN_NONE, ffreader->info.sample_rate, ffreader->info.channels, ffreader->info.channel_layout); reader = new Timeline(ffreader->info.width, ffreader->info.height, ffreader->info.fps, ffreader->info.sample_rate, ffreader->info.channels, ffreader->info.channel_layout); Clip *c = new Clip(source); @@ -72,9 +81,6 @@ void QtPlayer::SetSource(const std::string &source) tm->AddClip(c); tm->Open(); -// ZmqLogger::Instance()->Path("/home/jonathan/.openshot_qt/libopenshot.log"); -// ZmqLogger::Instance()->Enable(true); - // Set the reader Reader(reader); } From 4c532fe1016a33c7262f565c511ac3ac1e888c7e Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Thu, 4 Apr 2019 20:30:15 -0500 Subject: [PATCH 116/223] Change travis ci to use xenial dist (instead of trusty) --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index fa191b2b..6954814f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,4 @@ -dist: trusty +dist: xenial matrix: include: From 6fb49710a3056e228722c9a4215f2179c8f5377a Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Thu, 4 Apr 2019 20:33:58 -0500 Subject: [PATCH 117/223] Requiring sudo for Travis ci --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 6954814f..06d0a39e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ dist: xenial +sudo: required matrix: include: From 85a10291f61d6956028bc170c50b6a2c6dfd714f Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Thu, 4 Apr 2019 20:38:39 -0500 Subject: [PATCH 118/223] Updating Qt apt repository for xenial --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 06d0a39e..879a8190 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ matrix: name: "FFmpeg 2" before_script: - sudo add-apt-repository ppa:openshot.developers/libopenshot-daily -y - - sudo add-apt-repository ppa:beineri/opt-qt58-trusty -y + - sudo add-apt-repository ppa:beineri/opt-qt-5.10.0-xenial -y - sudo apt-get update -qq - sudo apt-get install gcc-4.8 cmake libavcodec-dev libavformat-dev libswscale-dev libavresample-dev libavutil-dev libopenshot-audio-dev libopenshot-dev libfdk-aac-dev libfdk-aac-dev libjsoncpp-dev libmagick++-dev libopenshot-audio-dev libunittest++-dev libzmq3-dev pkg-config python3-dev qtbase5-dev qtmultimedia5-dev swig -y - sudo apt autoremove -y @@ -21,7 +21,7 @@ matrix: name: "FFmpeg 3" before_script: - sudo add-apt-repository ppa:openshot.developers/libopenshot-daily -y - - sudo add-apt-repository ppa:beineri/opt-qt58-trusty -y + - sudo add-apt-repository ppa:beineri/opt-qt-5.10.0-xenial -y - sudo add-apt-repository ppa:jonathonf/ffmpeg-3 -y - sudo apt-get update -qq - sudo apt-get install gcc-4.8 cmake libavcodec-dev libavformat-dev libswscale-dev libavresample-dev libavutil-dev libopenshot-audio-dev libopenshot-dev libfdk-aac-dev libfdk-aac-dev libjsoncpp-dev libmagick++-dev libopenshot-audio-dev libunittest++-dev libzmq3-dev pkg-config python3-dev qtbase5-dev qtmultimedia5-dev swig -y @@ -36,7 +36,7 @@ matrix: name: "FFmpeg 4" before_script: - sudo add-apt-repository ppa:openshot.developers/libopenshot-daily -y - - sudo add-apt-repository ppa:beineri/opt-qt58-trusty -y + - sudo add-apt-repository ppa:beineri/opt-qt-5.10.0-xenial -y - sudo add-apt-repository ppa:jonathonf/ffmpeg -y - sudo add-apt-repository ppa:jonathonf/ffmpeg-4 -y - sudo add-apt-repository ppa:jonathonf/backports -y From 6ee1ab17a497993f207a6d08eb035b3b18bb2216 Mon Sep 17 00:00:00 2001 From: "FeRD (Frank Dana)" Date: Thu, 5 Jul 2018 15:49:45 -0400 Subject: [PATCH 119/223] Use updated, improved UseDoxygen.cmake --- cmake/Modules/UseDoxygen.cmake | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/cmake/Modules/UseDoxygen.cmake b/cmake/Modules/UseDoxygen.cmake index 48480e4d..b261d431 100644 --- a/cmake/Modules/UseDoxygen.cmake +++ b/cmake/Modules/UseDoxygen.cmake @@ -1,4 +1,30 @@ -# - Run Doxygen +# Redistribution and use is allowed according to the terms of the New +# BSD license: +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# - Run Doxygen # # Adds a doxygen target that runs doxygen to generate the html # and optionally the LaTeX API documentation. @@ -48,7 +74,6 @@ # # Redistribution and use is allowed according to the terms of the New # BSD license. -# For details see the accompanying COPYING-CMAKE-SCRIPTS file. # macro(usedoxygen_set_default name value type docstring) @@ -134,7 +159,9 @@ if(DOXYGEN_FOUND AND DOXYFILE_IN_FOUND) configure_file("${DOXYFILE_IN}" "${DOXYFILE}" @ONLY) - get_target_property(DOC_TARGET doc TYPE) + if(TARGET doc) + get_target_property(DOC_TARGET doc TYPE) + endif() if(NOT DOC_TARGET) add_custom_target(doc) endif() From 708f3254ca42aaa9a5f12178c0d5a59f87cc37aa Mon Sep 17 00:00:00 2001 From: "FeRD (Frank Dana)" Date: Thu, 4 Apr 2019 23:09:44 -0400 Subject: [PATCH 120/223] Modernize project for CMake 3.1+ --- CMakeLists.txt | 52 ++++++++++++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index eaf7d65f..cf1ae4ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,8 @@ # # Copyright (c) 2008-2014 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 +# 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 @@ -24,24 +24,29 @@ # along with OpenShot Library. If not, see . ################################################################################ -cmake_minimum_required(VERSION 2.8.11) +cmake_minimum_required(VERSION 3.1...3.14 FATAL_ERROR) -MESSAGE("--------------------------------------------------------------") -MESSAGE("Welcome to the OpenShot Build System! CMake will now check for all required build") -MESSAGE("dependencies and notify you of any missing files or other issues. If you have any") -MESSAGE("questions or issues, please visit .") +message("\ +----------------------------------------------------------------- + Welcome to the OpenShot Build System! + +CMake will now check libopenshot's build dependencies and inform +you of any missing files or other issues. + +For more information, please visit . +-----------------------------------------------------------------") ################ ADD CMAKE MODULES ################## set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/Modules") ################ GET VERSION INFORMATION FROM VERSION.H ################## -MESSAGE("--------------------------------------------------------------") -MESSAGE("Determining Version Number (from Version.h file)") +message(STATUS "Determining Version Number (from Version.h file)") #### Get the lines related to libopenshot version from the Version.h header -file(STRINGS ${CMAKE_CURRENT_SOURCE_DIR}/include/Version.h OPENSHOT_VERSION_LINES - REGEX "#define[ ]+OPENSHOT_VERSION_.*[0-9]+;.*") - +file(STRINGS ${CMAKE_CURRENT_SOURCE_DIR}/include/Version.h + OPENSHOT_VERSION_LINES + REGEX "#define[ ]+OPENSHOT_VERSION_.*[0-9]+;.*") + #### Set each line into it's own variable list (GET OPENSHOT_VERSION_LINES 0 LINE_MAJOR) list (GET OPENSHOT_VERSION_LINES 1 LINE_MINOR) @@ -53,22 +58,23 @@ STRING(REGEX REPLACE "#define[ ]+OPENSHOT_VERSION_MAJOR[ ]+([0-9]+);(.*)" "\\1" STRING(REGEX REPLACE "#define[ ]+OPENSHOT_VERSION_MINOR[ ]+([0-9]+);(.*)" "\\1" MINOR_VERSION "${LINE_MINOR}") STRING(REGEX REPLACE "#define[ ]+OPENSHOT_VERSION_BUILD[ ]+([0-9]+);(.*)" "\\1" BUILD_VERSION "${LINE_BUILD}") STRING(REGEX REPLACE "#define[ ]+OPENSHOT_VERSION_SO[ ]+([0-9]+);(.*)" "\\1" SO_VERSION "${LINE_SO}") -set(PROJECT_VERSION "${MAJOR_VERSION}.${MINOR_VERSION}.${BUILD_VERSION}") -MESSAGE("--> MAJOR Version: ${MAJOR_VERSION}") -MESSAGE("--> MINOR Version: ${MINOR_VERSION}") -MESSAGE("--> BUILD Version: ${BUILD_VERSION}") -MESSAGE("--> SO/API/ABI Version: ${SO_VERSION}") -MESSAGE("--> VERSION: ${PROJECT_VERSION}") -MESSAGE("") +message(STATUS "Determining Version Number - done") ################### SETUP PROJECT ################### -PROJECT(openshot) -MESSAGE("--------------------------------------------------------------") -MESSAGE("Generating build files for ${PROJECT_NAME} (${PROJECT_VERSION})") +PROJECT(libopenshot LANGUAGES CXX + VERSION ${MAJOR_VERSION}.${MINOR_VERSION}.${BUILD_VERSION}) + +message("\ +Generating build files for OpenShot + Building ${PROJECT_NAME} (version ${PROJECT_VERSION}) + SO/API/ABI Version: ${SO_VERSION} +") #### Enable C++11 (for std::shared_ptr support) -set(CMAKE_CXX_FLAGS "-std=c++11") +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) IF (WIN32) SET_PROPERTY(GLOBAL PROPERTY WIN32 "WIN32") From 3d8c2412f0b42471437d9ab7af44db1c2a790f55 Mon Sep 17 00:00:00 2001 From: "FeRD (Frank Dana)" Date: Thu, 4 Apr 2019 23:10:53 -0400 Subject: [PATCH 121/223] Bindings build in CMake 3.1-3.14+ --- src/bindings/python/CMakeLists.txt | 77 +++++++++++++++++++----------- src/bindings/ruby/CMakeLists.txt | 40 +++++++++++----- 2 files changed, 75 insertions(+), 42 deletions(-) diff --git a/src/bindings/python/CMakeLists.txt b/src/bindings/python/CMakeLists.txt index 80f012ee..96dbf13f 100644 --- a/src/bindings/python/CMakeLists.txt +++ b/src/bindings/python/CMakeLists.txt @@ -6,8 +6,8 @@ # # Copyright (c) 2008-2014 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 +# 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 @@ -29,37 +29,56 @@ FIND_PACKAGE(SWIG 2.0 REQUIRED) INCLUDE(${SWIG_USE_FILE}) +### Enable some legacy SWIG behaviors, in newer CMAKE +cmake_policy(SET CMP0078 OLD) +cmake_policy(SET CMP0086 OLD) + FIND_PACKAGE(PythonLibs 3) FIND_PACKAGE(PythonInterp 3) -IF (PYTHONLIBS_FOUND) - IF (PYTHONINTERP_FOUND) +if (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) - ### Include Python header files - INCLUDE_DIRECTORIES(${PYTHON_INCLUDE_PATH}) - INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) - - ### Enable C++ support in SWIG - SET_SOURCE_FILES_PROPERTIES(openshot.i PROPERTIES CPLUSPLUS ON) - SET(CMAKE_SWIG_FLAGS "") - - ### Add the SWIG interface file (which defines all the SWIG methods) - SWIG_ADD_MODULE(openshot python openshot.i) + ### Include Python header files + INCLUDE_DIRECTORIES(${PYTHON_INCLUDE_PATH}) + INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) - ### Link the new python wrapper library with libopenshot - SWIG_LINK_LIBRARIES(openshot ${PYTHON_LIBRARIES} openshot) + ### Enable C++ support in SWIG + set_property(SOURCE openshot.i PROPERTY CPLUSPLUS ON) + set_property(SOURCE openshot.i PROPERTY SWIG_MODULE_NAME openshot) + SET(CMAKE_SWIG_FLAGS "") - ### FIND THE PYTHON INTERPRETER (AND THE SITE PACKAGES FOLDER) - EXECUTE_PROCESS ( COMMAND ${PYTHON_EXECUTABLE} -c "import site; print(site.getsitepackages()[0])" - OUTPUT_VARIABLE _ABS_PYTHON_MODULE_PATH - OUTPUT_STRIP_TRAILING_WHITESPACE ) - GET_FILENAME_COMPONENT(_ABS_PYTHON_MODULE_PATH "${_ABS_PYTHON_MODULE_PATH}" ABSOLUTE) - FILE(RELATIVE_PATH _REL_PYTHON_MODULE_PATH ${CMAKE_INSTALL_PREFIX} ${_ABS_PYTHON_MODULE_PATH}) - SET(PYTHON_MODULE_PATH ${_REL_PYTHON_MODULE_PATH}) + ### Add the SWIG interface file (which defines all the SWIG methods) + if (CMAKE_VERSION VERSION_LESS 3.8.0) + swig_add_module(pyopenshot python openshot.i) + else() + swig_add_library(pyopenshot LANGUAGE python SOURCES openshot.i) + endif() - ############### INSTALL HEADERS & LIBRARY ################ - ### Install Python bindings - INSTALL(TARGETS _openshot DESTINATION ${PYTHON_MODULE_PATH} ) - INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/openshot.py DESTINATION ${PYTHON_MODULE_PATH} ) + ### Set output name of target + set_target_properties(${SWIG_MODULE_pyopenshot_REAL_NAME} PROPERTIES + PREFIX "_" OUTPUT_NAME "openshot") - ENDIF(PYTHONINTERP_FOUND) -ENDIF (PYTHONLIBS_FOUND) + ### Link the new python wrapper library with libopenshot + target_link_libraries(${SWIG_MODULE_pyopenshot_REAL_NAME} + ${PYTHON_LIBRARIES} openshot) + + ### FIND THE PYTHON INTERPRETER (AND THE SITE PACKAGES FOLDER) + execute_process ( COMMAND ${PYTHON_EXECUTABLE} -c "\ +from distutils.sysconfig import get_python_lib; \ +print( get_python_lib( plat_specific=True, prefix='${CMAKE_INSTALL_PREFIX}' ) )" + OUTPUT_VARIABLE _ABS_PYTHON_MODULE_PATH + OUTPUT_STRIP_TRAILING_WHITESPACE ) + + GET_FILENAME_COMPONENT(_ABS_PYTHON_MODULE_PATH + "${_ABS_PYTHON_MODULE_PATH}" ABSOLUTE) + FILE(RELATIVE_PATH _REL_PYTHON_MODULE_PATH + ${CMAKE_INSTALL_PREFIX} ${_ABS_PYTHON_MODULE_PATH}) + SET(PYTHON_MODULE_PATH ${_REL_PYTHON_MODULE_PATH}) + + ############### INSTALL HEADERS & LIBRARY ################ + ### Install Python bindings + INSTALL(TARGETS ${SWIG_MODULE_pyopenshot_REAL_NAME} + LIBRARY DESTINATION ${PYTHON_MODULE_PATH} ) + INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/openshot.py + DESTINATION ${PYTHON_MODULE_PATH} ) + +endif () diff --git a/src/bindings/ruby/CMakeLists.txt b/src/bindings/ruby/CMakeLists.txt index 75cee048..7ae8440b 100644 --- a/src/bindings/ruby/CMakeLists.txt +++ b/src/bindings/ruby/CMakeLists.txt @@ -6,8 +6,8 @@ # # Copyright (c) 2008-2014 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 +# 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 @@ -29,35 +29,49 @@ FIND_PACKAGE(SWIG 2.0 REQUIRED) INCLUDE(${SWIG_USE_FILE}) +### Enable some legacy SWIG behaviors, in newer CMAKE +cmake_policy(SET CMP0078 OLD) +cmake_policy(SET CMP0086 OLD) + FIND_PACKAGE(Ruby) IF (RUBY_FOUND) ### Include the Ruby header files INCLUDE_DIRECTORIES(${RUBY_INCLUDE_DIRS}) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) - + ### Enable C++ in SWIG - SET_SOURCE_FILES_PROPERTIES(openshot.i PROPERTIES CPLUSPLUS ON) + set_property(SOURCE openshot.i PROPERTY CPLUSPLUS ON) + set_property(SOURCE openshot.i PROPERTY SWIG_MODULE_NAME openshot) + SET(CMAKE_SWIG_FLAGS "") ### Add the SWIG interface file (which defines all the SWIG methods) - SWIG_ADD_MODULE(rbopenshot ruby openshot.i) - + if (CMAKE_VERSION VERSION_LESS 3.8.0) + swig_add_module(rbopenshot ruby openshot.i) + else() + swig_add_library(rbopenshot LANGUAGE ruby SOURCES openshot.i) + endif() + ### Set name of target (with no prefix, since Ruby does not like that) - SET_TARGET_PROPERTIES(rbopenshot PROPERTIES PREFIX "" OUTPUT_NAME "openshot") + SET_TARGET_PROPERTIES(${SWIG_MODULE_rbopenshot_REAL_NAME} PROPERTIES + PREFIX "" OUTPUT_NAME "openshot") ### Link the new Ruby wrapper library with libopenshot - SWIG_LINK_LIBRARIES(rbopenshot ${RUBY_LIBRARY} openshot) - + target_link_libraries(${SWIG_MODULE_rbopenshot_REAL_NAME} + ${RUBY_LIBRARY} openshot) + ### FIND THE RUBY INTERPRETER (AND THE LOAD_PATH FOLDER) - EXECUTE_PROCESS(COMMAND ${RUBY_EXECUTABLE} -r rbconfig -e "print RbConfig::CONFIG['vendorarchdir']" OUTPUT_VARIABLE RUBY_VENDOR_ARCH_DIR) + EXECUTE_PROCESS(COMMAND ${RUBY_EXECUTABLE} + -r rbconfig -e "print RbConfig::CONFIG['vendorarchdir']" + OUTPUT_VARIABLE RUBY_VENDOR_ARCH_DIR) MESSAGE(STATUS "Ruby executable: ${RUBY_EXECUTABLE}") MESSAGE(STATUS "Ruby vendor arch dir: ${RUBY_VENDOR_ARCH_DIR}") MESSAGE(STATUS "Ruby include path: ${RUBY_INCLUDE_PATH}") - ############### INSTALL HEADERS & LIBRARY ################ # Install Ruby bindings - INSTALL(TARGETS rbopenshot LIBRARY DESTINATION ${RUBY_VENDOR_ARCH_DIR}) - + install(TARGETS ${SWIG_MODULE_rbopenshot_REAL_NAME} + LIBRARY DESTINATION ${RUBY_VENDOR_ARCH_DIR} ) + ENDIF (RUBY_FOUND) From 268e72aaf3383b72792c27b9c6945032d086a043 Mon Sep 17 00:00:00 2001 From: "FeRD (Frank Dana)" Date: Thu, 4 Apr 2019 23:30:28 -0400 Subject: [PATCH 122/223] Update copyright and cmake output --- CMakeLists.txt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cf1ae4ff..71c56f37 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ # # @section LICENSE # -# Copyright (c) 2008-2014 OpenShot Studios, LLC +# 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 @@ -59,13 +59,17 @@ STRING(REGEX REPLACE "#define[ ]+OPENSHOT_VERSION_MINOR[ ]+([0-9]+);(.*)" "\\1" STRING(REGEX REPLACE "#define[ ]+OPENSHOT_VERSION_BUILD[ ]+([0-9]+);(.*)" "\\1" BUILD_VERSION "${LINE_BUILD}") STRING(REGEX REPLACE "#define[ ]+OPENSHOT_VERSION_SO[ ]+([0-9]+);(.*)" "\\1" SO_VERSION "${LINE_SO}") +message(STATUS "MAJOR Version: ${MAJOR_VERSION}") +message(STATUS "MINOR Version: ${MINOR_VERSION}") +message(STATUS "BUILD Version: ${BUILD_VERSION}") +message(STATUS "SO/API/ABI Version: ${SO_VERSION}") message(STATUS "Determining Version Number - done") ################### SETUP PROJECT ################### PROJECT(libopenshot LANGUAGES CXX VERSION ${MAJOR_VERSION}.${MINOR_VERSION}.${BUILD_VERSION}) -message("\ +message(" Generating build files for OpenShot Building ${PROJECT_NAME} (version ${PROJECT_VERSION}) SO/API/ABI Version: ${SO_VERSION} From f3c35da5c80a77fadeb8298a07d179fbb2bc189a Mon Sep 17 00:00:00 2001 From: "FeRD (Frank Dana)" Date: Fri, 5 Apr 2019 00:49:27 -0400 Subject: [PATCH 123/223] Don't break older cmake with new policy --- src/bindings/python/CMakeLists.txt | 10 ++-- src/bindings/python/CMakeLists.txt~ | 72 +++++++++++++++++++++++++++++ src/bindings/ruby/CMakeLists.txt | 10 ++-- src/bindings/ruby/CMakeLists.txt~ | 66 ++++++++++++++++++++++++++ 4 files changed, 152 insertions(+), 6 deletions(-) create mode 100644 src/bindings/python/CMakeLists.txt~ create mode 100644 src/bindings/ruby/CMakeLists.txt~ diff --git a/src/bindings/python/CMakeLists.txt b/src/bindings/python/CMakeLists.txt index 96dbf13f..93ae9360 100644 --- a/src/bindings/python/CMakeLists.txt +++ b/src/bindings/python/CMakeLists.txt @@ -29,9 +29,13 @@ FIND_PACKAGE(SWIG 2.0 REQUIRED) INCLUDE(${SWIG_USE_FILE}) -### Enable some legacy SWIG behaviors, in newer CMAKE -cmake_policy(SET CMP0078 OLD) -cmake_policy(SET CMP0086 OLD) +### Enable some legacy SWIG behaviors, in newer CMAKEs +if (CMAKE_VERSION VERSION_GREATER 3.13) + cmake_policy(SET CMP0078 OLD) +endif() +if (CMAKE_VERSION VERSION_GREATER 3.14) + cmake_policy(SET CMP0086 OLD) +endif() FIND_PACKAGE(PythonLibs 3) FIND_PACKAGE(PythonInterp 3) diff --git a/src/bindings/python/CMakeLists.txt~ b/src/bindings/python/CMakeLists.txt~ new file mode 100644 index 00000000..43c30a0e --- /dev/null +++ b/src/bindings/python/CMakeLists.txt~ @@ -0,0 +1,72 @@ +####################### CMakeLists.txt (libopenshot) ######################### +# @brief CMake build file for libopenshot (used to generate Python SWIG bindings) +# @author Jonathan Thomas +# +# @section LICENSE +# +# Copyright (c) 2008-2014 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 . +################################################################################ + + +############### SWIG PYTHON BINDINGS ################ +FIND_PACKAGE(SWIG 2.0 REQUIRED) +INCLUDE(${SWIG_USE_FILE}) + +FIND_PACKAGE(PythonLibs 3) +FIND_PACKAGE(PythonInterp 3) +if (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) + + ### Include Python header files + INCLUDE_DIRECTORIES(${PYTHON_INCLUDE_PATH}) + INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) + + ### Enable C++ support in SWIG + set_property(SOURCE openshot.i PROPERTY CPLUSPLUS ON) + set_property(SOURCE openshot.i PROPERTY SWIG_MODULE_NAME openshot) + SET(CMAKE_SWIG_FLAGS "") + + ### Add the SWIG interface file (which defines all the SWIG methods) + swig_add_library(pyopenshot LANGUAGE python SOURCES openshot.i) + + ### Set output name of target + set_target_properties(${SWIG_MODULE_pyopenshot_REAL_NAME} PROPERTIES + PREFIX "_" OUTPUT_NAME "openshot") + + ### Link the new python wrapper library with libopenshot + target_link_libraries(${SWIG_MODULE_pyopenshot_REAL_NAME} ${PYTHON_LIBRARIES} openshot) + + ### FIND THE PYTHON INTERPRETER (AND THE SITE PACKAGES FOLDER) + execute_process ( COMMAND ${PYTHON_EXECUTABLE} -c + "from distutils.sysconfig import get_python_lib; print( get_python_lib( plat_specific=True, prefix='${CMAKE_INSTALL_PREFIX}' ) )" + OUTPUT_VARIABLE _ABS_PYTHON_MODULE_PATH + OUTPUT_STRIP_TRAILING_WHITESPACE ) + + GET_FILENAME_COMPONENT(_ABS_PYTHON_MODULE_PATH "${_ABS_PYTHON_MODULE_PATH}" ABSOLUTE) + FILE(RELATIVE_PATH _REL_PYTHON_MODULE_PATH ${CMAKE_INSTALL_PREFIX} ${_ABS_PYTHON_MODULE_PATH}) + SET(PYTHON_MODULE_PATH ${_REL_PYTHON_MODULE_PATH}) + + ############### INSTALL HEADERS & LIBRARY ################ + ### Install Python bindings + INSTALL(TARGETS ${SWIG_MODULE_pyopenshot_REAL_NAME} + LIBRARY DESTINATION ${PYTHON_MODULE_PATH} ) + INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/openshot.py + DESTINATION ${PYTHON_MODULE_PATH} ) + +endif () diff --git a/src/bindings/ruby/CMakeLists.txt b/src/bindings/ruby/CMakeLists.txt index 7ae8440b..82c9d5d5 100644 --- a/src/bindings/ruby/CMakeLists.txt +++ b/src/bindings/ruby/CMakeLists.txt @@ -29,9 +29,13 @@ FIND_PACKAGE(SWIG 2.0 REQUIRED) INCLUDE(${SWIG_USE_FILE}) -### Enable some legacy SWIG behaviors, in newer CMAKE -cmake_policy(SET CMP0078 OLD) -cmake_policy(SET CMP0086 OLD) +### Enable some legacy SWIG behaviors, in newer CMAKEs +if (CMAKE_VERSION VERSION_GREATER 3.13) + cmake_policy(SET CMP0078 OLD) +endif() +if (CMAKE_VERSION VERSION_GREATER 3.14) + cmake_policy(SET CMP0086 OLD) +endif() FIND_PACKAGE(Ruby) IF (RUBY_FOUND) diff --git a/src/bindings/ruby/CMakeLists.txt~ b/src/bindings/ruby/CMakeLists.txt~ new file mode 100644 index 00000000..e1b36449 --- /dev/null +++ b/src/bindings/ruby/CMakeLists.txt~ @@ -0,0 +1,66 @@ +####################### CMakeLists.txt (libopenshot) ######################### +# @brief CMake build file for libopenshot (used to generate Ruby SWIG bindings) +# @author Jonathan Thomas +# +# @section LICENSE +# +# Copyright (c) 2008-2014 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 . +################################################################################ + + +############### RUBY BINDINGS ################ +FIND_PACKAGE(SWIG 2.0 REQUIRED) +INCLUDE(${SWIG_USE_FILE}) + +FIND_PACKAGE(Ruby) +IF (RUBY_FOUND) + + ### Include the Ruby header files + INCLUDE_DIRECTORIES(${RUBY_INCLUDE_DIRS}) + INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) + + ### Enable C++ in SWIG + set_property(SOURCE openshot.i PROPERTY CPLUSPLUS ON) + set_property(SOURCE openshot.i PROPERTY SWIG_MODULE_NAME openshot) + + SET(CMAKE_SWIG_FLAGS "") + + ### Add the SWIG interface file (which defines all the SWIG methods) + swig_add_library(rbopenshot LANGUAGE ruby SOURCES openshot.i) + + ### Set name of target (with no prefix, since Ruby does not like that) + SET_TARGET_PROPERTIES(${SWIG_MODULE_rbopenshot_REAL_NAME} + PROPERTIES PREFIX "" OUTPUT_NAME "openshot") + + ### Link the new Ruby wrapper library with libopenshot + target_link_libraries(${SWIG_MODULE_rbopenshot_REAL_NAME} ${RUBY_LIBRARY} openshot) + + ### FIND THE RUBY INTERPRETER (AND THE LOAD_PATH FOLDER) + EXECUTE_PROCESS(COMMAND ${RUBY_EXECUTABLE} -r rbconfig -e "print RbConfig::CONFIG['vendorarchdir']" OUTPUT_VARIABLE RUBY_VENDOR_ARCH_DIR) + MESSAGE(STATUS "Ruby executable: ${RUBY_EXECUTABLE}") + MESSAGE(STATUS "Ruby vendor arch dir: ${RUBY_VENDOR_ARCH_DIR}") + MESSAGE(STATUS "Ruby include path: ${RUBY_INCLUDE_PATH}") + + + ############### INSTALL HEADERS & LIBRARY ################ + # Install Ruby bindings + install(TARGETS ${SWIG_MODULE_rbopenshot_REAL_NAME} LIBRARY DESTINATION ${RUBY_VENDOR_ARCH_DIR} ) + +ENDIF (RUBY_FOUND) From f26978d889932f9db8b4ef15b19980111b011923 Mon Sep 17 00:00:00 2001 From: "FeRD (Frank Dana)" Date: Fri, 5 Apr 2019 01:08:38 -0400 Subject: [PATCH 124/223] tests/CMakeLists.txt: Use generic PROJECT_SOURCE_DIR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (Previously it was using the variable `${openshot_SOURCE_DIR}` which assumes the project name is "openshot" — but it changed.) --- tests/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f2ae9377..2d2a0122 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -24,7 +24,7 @@ # along with OpenShot Library. If not, see . ################################################################################ -SET(TEST_MEDIA_PATH "${openshot_SOURCE_DIR}/src/examples/") +SET(TEST_MEDIA_PATH "${PROJECT_SOURCE_DIR}/src/examples/") ################ WINDOWS ################## # Set some compiler options for Windows From 42e2c9912b572d3d62f2424a17980aff5b669557 Mon Sep 17 00:00:00 2001 From: "FeRD (Frank Dana)" Date: Fri, 5 Apr 2019 17:32:34 -0400 Subject: [PATCH 125/223] Re-enable C lang for old CMake --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 71c56f37..2e3a49c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,7 +66,7 @@ message(STATUS "SO/API/ABI Version: ${SO_VERSION}") message(STATUS "Determining Version Number - done") ################### SETUP PROJECT ################### -PROJECT(libopenshot LANGUAGES CXX +PROJECT(libopenshot LANGUAGES C CXX VERSION ${MAJOR_VERSION}.${MINOR_VERSION}.${BUILD_VERSION}) message(" From b1f1df7dcd0ab45b30741303d4b99f0511399723 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Fri, 5 Apr 2019 17:10:29 -0500 Subject: [PATCH 126/223] Attempt to fix cmake "test" reserved word error --- tests/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2d2a0122..02c8f9b4 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -224,8 +224,8 @@ IF (NOT DISABLE_TESTS) # Link libraries to the new executable target_link_libraries(openshot-test openshot ${UNITTEST++_LIBRARY}) - #################### MAKE TEST ###################### # Hook up the 'make test' target to the 'openshot-test' executable - ADD_CUSTOM_TARGET(test ${CMAKE_CURRENT_BINARY_DIR}/openshot-test) + ADD_TEST(NAME openshot-test + COMMAND ${CMAKE_CURRENT_BINARY_DIR}/openshot-test) ENDIF (NOT DISABLE_TESTS) From dbc6e8ec61d2bb67cdf1d8d1b58a14484bb151ff Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Fri, 5 Apr 2019 17:40:38 -0500 Subject: [PATCH 127/223] Attempt to fix cmake "test" reserved word error --- tests/CMakeLists.txt | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 02c8f9b4..4b3e4cfb 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -224,8 +224,16 @@ IF (NOT DISABLE_TESTS) # Link libraries to the new executable target_link_libraries(openshot-test openshot ${UNITTEST++_LIBRARY}) + # Add cmake policy + cmake_policy(PUSH) + if(POLICY CMP0037) + cmake_policy(SET CMP0037 OLD) + endif() + #################### MAKE TEST ###################### # Hook up the 'make test' target to the 'openshot-test' executable - ADD_TEST(NAME openshot-test - COMMAND ${CMAKE_CURRENT_BINARY_DIR}/openshot-test) + ADD_CUSTOM_TARGET(test ${CMAKE_CURRENT_BINARY_DIR}/openshot-test) + + # Remove cmake policy + cmake_policy(POP) ENDIF (NOT DISABLE_TESTS) From ab46eea1c42c64c05a79ebc1fa78c31df002bd0a Mon Sep 17 00:00:00 2001 From: "FeRD (Frank Dana)" Date: Sat, 6 Apr 2019 06:17:44 -0400 Subject: [PATCH 128/223] Remove accidentally-committed tilde files I snuck `src/bindings/*/CMakeLists.txt~` files into one of my recent commits somehow. This commit removes them, and adds an explicit `*~` to `.gitignore` to help prevent a repeat occurrence. --- .gitignore | 1 + src/bindings/python/CMakeLists.txt~ | 72 ----------------------------- src/bindings/ruby/CMakeLists.txt~ | 66 -------------------------- 3 files changed, 1 insertion(+), 138 deletions(-) delete mode 100644 src/bindings/python/CMakeLists.txt~ delete mode 100644 src/bindings/ruby/CMakeLists.txt~ diff --git a/.gitignore b/.gitignore index a11656cf..022991be 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ build/* .project .cproject /.metadata/ +*~ diff --git a/src/bindings/python/CMakeLists.txt~ b/src/bindings/python/CMakeLists.txt~ deleted file mode 100644 index 43c30a0e..00000000 --- a/src/bindings/python/CMakeLists.txt~ +++ /dev/null @@ -1,72 +0,0 @@ -####################### CMakeLists.txt (libopenshot) ######################### -# @brief CMake build file for libopenshot (used to generate Python SWIG bindings) -# @author Jonathan Thomas -# -# @section LICENSE -# -# Copyright (c) 2008-2014 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 . -################################################################################ - - -############### SWIG PYTHON BINDINGS ################ -FIND_PACKAGE(SWIG 2.0 REQUIRED) -INCLUDE(${SWIG_USE_FILE}) - -FIND_PACKAGE(PythonLibs 3) -FIND_PACKAGE(PythonInterp 3) -if (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) - - ### Include Python header files - INCLUDE_DIRECTORIES(${PYTHON_INCLUDE_PATH}) - INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) - - ### Enable C++ support in SWIG - set_property(SOURCE openshot.i PROPERTY CPLUSPLUS ON) - set_property(SOURCE openshot.i PROPERTY SWIG_MODULE_NAME openshot) - SET(CMAKE_SWIG_FLAGS "") - - ### Add the SWIG interface file (which defines all the SWIG methods) - swig_add_library(pyopenshot LANGUAGE python SOURCES openshot.i) - - ### Set output name of target - set_target_properties(${SWIG_MODULE_pyopenshot_REAL_NAME} PROPERTIES - PREFIX "_" OUTPUT_NAME "openshot") - - ### Link the new python wrapper library with libopenshot - target_link_libraries(${SWIG_MODULE_pyopenshot_REAL_NAME} ${PYTHON_LIBRARIES} openshot) - - ### FIND THE PYTHON INTERPRETER (AND THE SITE PACKAGES FOLDER) - execute_process ( COMMAND ${PYTHON_EXECUTABLE} -c - "from distutils.sysconfig import get_python_lib; print( get_python_lib( plat_specific=True, prefix='${CMAKE_INSTALL_PREFIX}' ) )" - OUTPUT_VARIABLE _ABS_PYTHON_MODULE_PATH - OUTPUT_STRIP_TRAILING_WHITESPACE ) - - GET_FILENAME_COMPONENT(_ABS_PYTHON_MODULE_PATH "${_ABS_PYTHON_MODULE_PATH}" ABSOLUTE) - FILE(RELATIVE_PATH _REL_PYTHON_MODULE_PATH ${CMAKE_INSTALL_PREFIX} ${_ABS_PYTHON_MODULE_PATH}) - SET(PYTHON_MODULE_PATH ${_REL_PYTHON_MODULE_PATH}) - - ############### INSTALL HEADERS & LIBRARY ################ - ### Install Python bindings - INSTALL(TARGETS ${SWIG_MODULE_pyopenshot_REAL_NAME} - LIBRARY DESTINATION ${PYTHON_MODULE_PATH} ) - INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/openshot.py - DESTINATION ${PYTHON_MODULE_PATH} ) - -endif () diff --git a/src/bindings/ruby/CMakeLists.txt~ b/src/bindings/ruby/CMakeLists.txt~ deleted file mode 100644 index e1b36449..00000000 --- a/src/bindings/ruby/CMakeLists.txt~ +++ /dev/null @@ -1,66 +0,0 @@ -####################### CMakeLists.txt (libopenshot) ######################### -# @brief CMake build file for libopenshot (used to generate Ruby SWIG bindings) -# @author Jonathan Thomas -# -# @section LICENSE -# -# Copyright (c) 2008-2014 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 . -################################################################################ - - -############### RUBY BINDINGS ################ -FIND_PACKAGE(SWIG 2.0 REQUIRED) -INCLUDE(${SWIG_USE_FILE}) - -FIND_PACKAGE(Ruby) -IF (RUBY_FOUND) - - ### Include the Ruby header files - INCLUDE_DIRECTORIES(${RUBY_INCLUDE_DIRS}) - INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) - - ### Enable C++ in SWIG - set_property(SOURCE openshot.i PROPERTY CPLUSPLUS ON) - set_property(SOURCE openshot.i PROPERTY SWIG_MODULE_NAME openshot) - - SET(CMAKE_SWIG_FLAGS "") - - ### Add the SWIG interface file (which defines all the SWIG methods) - swig_add_library(rbopenshot LANGUAGE ruby SOURCES openshot.i) - - ### Set name of target (with no prefix, since Ruby does not like that) - SET_TARGET_PROPERTIES(${SWIG_MODULE_rbopenshot_REAL_NAME} - PROPERTIES PREFIX "" OUTPUT_NAME "openshot") - - ### Link the new Ruby wrapper library with libopenshot - target_link_libraries(${SWIG_MODULE_rbopenshot_REAL_NAME} ${RUBY_LIBRARY} openshot) - - ### FIND THE RUBY INTERPRETER (AND THE LOAD_PATH FOLDER) - EXECUTE_PROCESS(COMMAND ${RUBY_EXECUTABLE} -r rbconfig -e "print RbConfig::CONFIG['vendorarchdir']" OUTPUT_VARIABLE RUBY_VENDOR_ARCH_DIR) - MESSAGE(STATUS "Ruby executable: ${RUBY_EXECUTABLE}") - MESSAGE(STATUS "Ruby vendor arch dir: ${RUBY_VENDOR_ARCH_DIR}") - MESSAGE(STATUS "Ruby include path: ${RUBY_INCLUDE_PATH}") - - - ############### INSTALL HEADERS & LIBRARY ################ - # Install Ruby bindings - install(TARGETS ${SWIG_MODULE_rbopenshot_REAL_NAME} LIBRARY DESTINATION ${RUBY_VENDOR_ARCH_DIR} ) - -ENDIF (RUBY_FOUND) From 496183ce90aac265aba6e86416ddb7bbc3207e54 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sat, 6 Apr 2019 11:05:59 -0500 Subject: [PATCH 129/223] change `make test` to `make os_test` (due to changes in cmake 3) --- .travis.yml | 6 +++--- tests/CMakeLists.txt | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 879a8190..4afd8467 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ matrix: - mkdir -p build; cd build; - cmake -D"CMAKE_BUILD_TYPE:STRING=Debug" ../ - make VERBOSE=1 - - make test + - make os_test - language: cpp name: "FFmpeg 3" @@ -30,7 +30,7 @@ matrix: - mkdir -p build; cd build; - cmake -D"CMAKE_BUILD_TYPE:STRING=Debug" ../ - make VERBOSE=1 - - make test + - make os_test - language: cpp name: "FFmpeg 4" @@ -47,4 +47,4 @@ matrix: - mkdir -p build; cd build; - cmake -D"CMAKE_BUILD_TYPE:STRING=Debug" ../ - make VERBOSE=1 - - make test + - make os_test diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4b3e4cfb..18a5d26e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -231,8 +231,8 @@ IF (NOT DISABLE_TESTS) endif() #################### MAKE TEST ###################### - # Hook up the 'make test' target to the 'openshot-test' executable - ADD_CUSTOM_TARGET(test ${CMAKE_CURRENT_BINARY_DIR}/openshot-test) + # Hook up the 'make os_test' target to the 'openshot-test' executable + ADD_CUSTOM_TARGET(os_test ${CMAKE_CURRENT_BINARY_DIR}/openshot-test) # Remove cmake policy cmake_policy(POP) From 999d2021cfc88156ab99e8aa6607fcbd9587f200 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Sat, 6 Apr 2019 09:54:58 -0700 Subject: [PATCH 130/223] cmake target test renamed to os_test (test is predefined in cmake 3) --- tests/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2d2a0122..07bb23c3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -227,5 +227,5 @@ IF (NOT DISABLE_TESTS) #################### MAKE TEST ###################### # Hook up the 'make test' target to the 'openshot-test' executable - ADD_CUSTOM_TARGET(test ${CMAKE_CURRENT_BINARY_DIR}/openshot-test) + ADD_CUSTOM_TARGET(os_test ${CMAKE_CURRENT_BINARY_DIR}/openshot-test) ENDIF (NOT DISABLE_TESTS) From 9a7a720e3c3fe961698de779787ac83b5b71c53b Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Sat, 6 Apr 2019 10:26:04 -0700 Subject: [PATCH 131/223] change target of test to os_test in travis --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 879a8190..4afd8467 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ matrix: - mkdir -p build; cd build; - cmake -D"CMAKE_BUILD_TYPE:STRING=Debug" ../ - make VERBOSE=1 - - make test + - make os_test - language: cpp name: "FFmpeg 3" @@ -30,7 +30,7 @@ matrix: - mkdir -p build; cd build; - cmake -D"CMAKE_BUILD_TYPE:STRING=Debug" ../ - make VERBOSE=1 - - make test + - make os_test - language: cpp name: "FFmpeg 4" @@ -47,4 +47,4 @@ matrix: - mkdir -p build; cd build; - cmake -D"CMAKE_BUILD_TYPE:STRING=Debug" ../ - make VERBOSE=1 - - make test + - make os_test From 6e7b989b93960529e2700ba276fd947fa230d8d9 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sat, 6 Apr 2019 12:34:29 -0500 Subject: [PATCH 132/223] Removing policy experiment --- tests/CMakeLists.txt | 9 --------- 1 file changed, 9 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 18a5d26e..1ec36460 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -224,16 +224,7 @@ IF (NOT DISABLE_TESTS) # Link libraries to the new executable target_link_libraries(openshot-test openshot ${UNITTEST++_LIBRARY}) - # Add cmake policy - cmake_policy(PUSH) - if(POLICY CMP0037) - cmake_policy(SET CMP0037 OLD) - endif() - #################### MAKE TEST ###################### # Hook up the 'make os_test' target to the 'openshot-test' executable ADD_CUSTOM_TARGET(os_test ${CMAKE_CURRENT_BINARY_DIR}/openshot-test) - - # Remove cmake policy - cmake_policy(POP) ENDIF (NOT DISABLE_TESTS) From 62a68aac13ca7f510192fd320f7b33e3f561d7cd Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sat, 6 Apr 2019 13:19:06 -0500 Subject: [PATCH 133/223] Fixing python install logic in gitlab (since we've changed the prefix of where it's installed) --- .gitlab-ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f0f868c9..42656302 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -20,7 +20,7 @@ linux-builder: - cmake -D"CMAKE_INSTALL_PREFIX:PATH=$CI_PROJECT_DIR/build/install-x64" -D"CMAKE_BUILD_TYPE:STRING=Release" ../ - make - make install - - mv /usr/local/lib/python3.4/dist-packages/*openshot* install-x64/python + - mv install-x64/lib/python3.4/site-packages/*openshot* install-x64/python - echo -e "CI_PROJECT_NAME:$CI_PROJECT_NAME\nCI_COMMIT_REF_NAME:$CI_COMMIT_REF_NAME\nCI_COMMIT_SHA:$CI_COMMIT_SHA\nCI_JOB_ID:$CI_JOB_ID" > "install-x64/share/$CI_PROJECT_NAME" - git log $(git describe --tags --abbrev=0)..HEAD --oneline --pretty=format:"%C(auto,yellow)%h%C(auto,magenta)% %C(auto,blue)%>(12,trunc)%ad %C(auto,green)%<(25,trunc)%aN%C(auto,reset)%s%C(auto,red)% gD% D" --date=short > "install-x64/share/$CI_PROJECT_NAME.log" when: always @@ -47,7 +47,7 @@ mac-builder: - cmake -D"CMAKE_INSTALL_PREFIX:PATH=$CI_PROJECT_DIR/build/install-x64" -DCMAKE_CXX_COMPILER=/usr/local/opt/gcc48/bin/g++-4.8 -DCMAKE_C_COMPILER=/usr/local/opt/gcc48/bin/gcc-4.8 -DCMAKE_PREFIX_PATH=/usr/local/qt5/5.5/clang_64 -DPYTHON_INCLUDE_DIR=/Library/Frameworks/Python.framework/Versions/3.6/include/python3.6m -DPYTHON_LIBRARY=/Library/Frameworks/Python.framework/Versions/3.6/lib/libpython3.6.dylib -DPython_FRAMEWORKS=/Library/Frameworks/Python.framework/ -D"CMAKE_BUILD_TYPE:STRING=Debug" -D"CMAKE_OSX_SYSROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk" -D"CMAKE_OSX_DEPLOYMENT_TARGET=10.9" -D"CMAKE_INSTALL_RPATH_USE_LINK_PATH=1" -D"ENABLE_RUBY=0" ../ - make - make install - - mv /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/*openshot* install-x64/python + - mv install-x64/lib/python3.6/site-packages/*openshot* install-x64/python - echo -e "CI_PROJECT_NAME:$CI_PROJECT_NAME\nCI_COMMIT_REF_NAME:$CI_COMMIT_REF_NAME\nCI_COMMIT_SHA:$CI_COMMIT_SHA\nCI_JOB_ID:$CI_JOB_ID" > "install-x64/share/$CI_PROJECT_NAME" - git log $(git describe --tags --abbrev=0)..HEAD --oneline --pretty=format:"%C(auto,yellow)%h%C(auto,magenta)% %C(auto,blue)%>(12,trunc)%ad %C(auto,green)%<(25,trunc)%aN%C(auto,reset)%s%C(auto,red)% gD% D" --date=short > "install-x64/share/$CI_PROJECT_NAME.log" when: always @@ -75,7 +75,7 @@ windows-builder-x64: - cd build - cmake -D"CMAKE_INSTALL_PREFIX:PATH=$CI_PROJECT_DIR\build\install-x64" -G "MinGW Makefiles" -D"CMAKE_BUILD_TYPE:STRING=Release" ../ - mingw32-make install - - Move-Item -Force -path "C:\msys64\mingw64\lib\python3.6\site-packages\*openshot*" -destination "install-x64\python\" + - Move-Item -Force -path "install-x64\lib\python3.6\site-packages\*openshot*" -destination "install-x64\python\" - cp src\libopenshot.dll install-x64\lib - New-Item -path "install-x64/share/" -Name "$CI_PROJECT_NAME" -Value "CI_PROJECT_NAME:$CI_PROJECT_NAME`nCI_COMMIT_REF_NAME:$CI_COMMIT_REF_NAME`nCI_COMMIT_SHA:$CI_COMMIT_SHA`nCI_JOB_ID:$CI_JOB_ID" -ItemType file -force - $PREV_GIT_LABEL=(git describe --tags --abbrev=0) @@ -106,7 +106,7 @@ windows-builder-x86: - cd build - cmake -D"CMAKE_INSTALL_PREFIX:PATH=$CI_PROJECT_DIR\build\install-x86" -G "MinGW Makefiles" -D"CMAKE_BUILD_TYPE:STRING=Release" -D"CMAKE_CXX_FLAGS=-m32" -D"CMAKE_EXE_LINKER_FLAGS=-Wl,--large-address-aware" -D"CMAKE_C_FLAGS=-m32" ../ - mingw32-make install - - Move-Item -Force -path "C:\msys32\mingw32\lib\python3.6\site-packages\*openshot*" -destination "install-x86\python\" + - Move-Item -Force -path "install-x86\lib\python3.6\site-packages\*openshot*" -destination "install-x86\python\" - cp src\libopenshot.dll install-x86\lib - New-Item -path "install-x86/share/" -Name "$CI_PROJECT_NAME" -Value "CI_PROJECT_NAME:$CI_PROJECT_NAME`nCI_COMMIT_REF_NAME:$CI_COMMIT_REF_NAME`nCI_COMMIT_SHA:$CI_COMMIT_SHA`nCI_JOB_ID:$CI_JOB_ID" -ItemType file -force - $PREV_GIT_LABEL=(git describe --tags --abbrev=0) From 2748e9a2a285f11639e71c1502e88bbead733cf7 Mon Sep 17 00:00:00 2001 From: "FeRD (Frank Dana)" Date: Sat, 6 Apr 2019 19:24:32 -0400 Subject: [PATCH 134/223] Use if(POLICY) I somehow missed that `if(POLICY CMPxxxx)` exists to wrap `cmp_policy()` calls so only CMake versions that understand that policy attempt to set it. Which is way smarter than the version-based logic I was using. --- src/bindings/python/CMakeLists.txt | 4 ++-- src/bindings/ruby/CMakeLists.txt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/bindings/python/CMakeLists.txt b/src/bindings/python/CMakeLists.txt index 93ae9360..3418d2de 100644 --- a/src/bindings/python/CMakeLists.txt +++ b/src/bindings/python/CMakeLists.txt @@ -30,10 +30,10 @@ FIND_PACKAGE(SWIG 2.0 REQUIRED) INCLUDE(${SWIG_USE_FILE}) ### Enable some legacy SWIG behaviors, in newer CMAKEs -if (CMAKE_VERSION VERSION_GREATER 3.13) +if (POLICY CMP0078) cmake_policy(SET CMP0078 OLD) endif() -if (CMAKE_VERSION VERSION_GREATER 3.14) +if (POLICY CMP0086) cmake_policy(SET CMP0086 OLD) endif() diff --git a/src/bindings/ruby/CMakeLists.txt b/src/bindings/ruby/CMakeLists.txt index 82c9d5d5..7e3bce99 100644 --- a/src/bindings/ruby/CMakeLists.txt +++ b/src/bindings/ruby/CMakeLists.txt @@ -30,10 +30,10 @@ FIND_PACKAGE(SWIG 2.0 REQUIRED) INCLUDE(${SWIG_USE_FILE}) ### Enable some legacy SWIG behaviors, in newer CMAKEs -if (CMAKE_VERSION VERSION_GREATER 3.13) +if (POLICY CMP0078) cmake_policy(SET CMP0078 OLD) endif() -if (CMAKE_VERSION VERSION_GREATER 3.14) +if (POLICY CMP0086) cmake_policy(SET CMP0086 OLD) endif() From 04b3d2f6b1c61e99884efc6a363a8d24857f1304 Mon Sep 17 00:00:00 2001 From: "FeRD (Frank Dana)" Date: Sat, 6 Apr 2019 19:38:44 -0400 Subject: [PATCH 135/223] Fix up JsonCPP discovery messages The JsonCPP discovery previously claimed it "Could NOT find" the system JsonCPP, even when it never checked because USE_SYSTEM_JSONCPP was not enabled. With this change it correctly reports whether it's looking for the system install or just using the built-in copy. For convenience, the message includes the name of the option that controls this behavior. --- src/CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 71541432..d793ffa2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -199,10 +199,12 @@ endif(RESVG_FOUND) ################### JSONCPP ##################### # Include jsoncpp headers (needed for JSON parsing) if (USE_SYSTEM_JSONCPP) + message(STATUS "Discovering system JsonCPP (USE_SYSTEM_JSONCPP enabled)") find_package(JsonCpp REQUIRED) include_directories(${JSONCPP_INCLUDE_DIRS}) + message(STATUS "Discovering system JsonCPP - done") else() - message("-- Could NOT find JsonCpp library (Using embedded JsonCpp instead)") + message(STATUS "Using embedded JsonCpp (USE_SYSTEM_JSONCPP not enabled)") include_directories("../thirdparty/jsoncpp/include") endif(USE_SYSTEM_JSONCPP) From 195b5762eed0760c512ede22582e1ea21504d7bb Mon Sep 17 00:00:00 2001 From: "FeRD (Frank Dana)" Date: Sat, 6 Apr 2019 20:09:20 -0400 Subject: [PATCH 136/223] Make os_test use openshot-test target By specifying `COMMAND openshot-test` for the custom target, the path to the compiled executable is automatically used and the openshot-test target is automatically made a dependency. --- tests/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1ec36460..72884f1f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -226,5 +226,5 @@ IF (NOT DISABLE_TESTS) #################### MAKE TEST ###################### # Hook up the 'make os_test' target to the 'openshot-test' executable - ADD_CUSTOM_TARGET(os_test ${CMAKE_CURRENT_BINARY_DIR}/openshot-test) + ADD_CUSTOM_TARGET(os_test COMMAND openshot-test) ENDIF (NOT DISABLE_TESTS) From f61d054a74ff8124c77d263b933d5e7d24fd4245 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Thu, 11 Apr 2019 07:39:01 -0700 Subject: [PATCH 137/223] cmake hack Find the right install directory. I hope someone will come up with a more elegant way. --- src/bindings/python/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bindings/python/CMakeLists.txt b/src/bindings/python/CMakeLists.txt index 93ae9360..d4358b5e 100644 --- a/src/bindings/python/CMakeLists.txt +++ b/src/bindings/python/CMakeLists.txt @@ -67,8 +67,8 @@ if (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) ### FIND THE PYTHON INTERPRETER (AND THE SITE PACKAGES FOLDER) execute_process ( COMMAND ${PYTHON_EXECUTABLE} -c "\ -from distutils.sysconfig import get_python_lib; \ -print( get_python_lib( plat_specific=True, prefix='${CMAKE_INSTALL_PREFIX}' ) )" +import site; from distutils.sysconfig import get_python_lib; \ +print( get_python_lib( plat_specific=True, standard_lib=True, prefix='${CMAKE_INSTALL_PREFIX}' ) + '/' + site.getsitepackages()[0].split('/')[-1] )" OUTPUT_VARIABLE _ABS_PYTHON_MODULE_PATH OUTPUT_STRIP_TRAILING_WHITESPACE ) From 94d4de48db00921c09bc6caa5c4cff316bed5eca Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Thu, 11 Apr 2019 08:06:11 -0700 Subject: [PATCH 138/223] 2nd attempt --- src/bindings/python/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings/python/CMakeLists.txt b/src/bindings/python/CMakeLists.txt index d4358b5e..0e2f6eee 100644 --- a/src/bindings/python/CMakeLists.txt +++ b/src/bindings/python/CMakeLists.txt @@ -68,7 +68,7 @@ if (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) ### FIND THE PYTHON INTERPRETER (AND THE SITE PACKAGES FOLDER) execute_process ( COMMAND ${PYTHON_EXECUTABLE} -c "\ import site; from distutils.sysconfig import get_python_lib; \ -print( get_python_lib( plat_specific=True, standard_lib=True, prefix='${CMAKE_INSTALL_PREFIX}' ) + '/' + site.getsitepackages()[0].split('/')[-1] )" +print( get_python_lib( plat_specific=True, standard_lib=True, prefix='${CMAKE_INSTALL_PREFIX}' ) + '/' + get_python_lib( plat_specific=False, standard_lib=False, prefix='${CMAKE_INSTALL_PREFIX}' ).split('/')[-1] )" OUTPUT_VARIABLE _ABS_PYTHON_MODULE_PATH OUTPUT_STRIP_TRAILING_WHITESPACE ) From 2dd19696000243ce7335dffc44f9924b97f0b280 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Thu, 11 Apr 2019 15:42:03 -0700 Subject: [PATCH 139/223] Alternate version --- src/bindings/python/CMakeLists.txt | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/bindings/python/CMakeLists.txt b/src/bindings/python/CMakeLists.txt index 0e2f6eee..da8c1afa 100644 --- a/src/bindings/python/CMakeLists.txt +++ b/src/bindings/python/CMakeLists.txt @@ -66,11 +66,18 @@ if (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) ${PYTHON_LIBRARIES} openshot) ### FIND THE PYTHON INTERPRETER (AND THE SITE PACKAGES FOLDER) - execute_process ( COMMAND ${PYTHON_EXECUTABLE} -c "\ -import site; from distutils.sysconfig import get_python_lib; \ -print( get_python_lib( plat_specific=True, standard_lib=True, prefix='${CMAKE_INSTALL_PREFIX}' ) + '/' + get_python_lib( plat_specific=False, standard_lib=False, prefix='${CMAKE_INSTALL_PREFIX}' ).split('/')[-1] )" - OUTPUT_VARIABLE _ABS_PYTHON_MODULE_PATH - OUTPUT_STRIP_TRAILING_WHITESPACE ) + if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + # If not prefix found, detect python site package folder + EXECUTE_PROCESS ( COMMAND ${PYTHON_EXECUTABLE} -c "import site; print(site.getsitepackages()[0])" + OUTPUT_VARIABLE _ABS_PYTHON_MODULE_PATH + OUTPUT_STRIP_TRAILING_WHITESPACE ) + else() + execute_process ( COMMAND ${PYTHON_EXECUTABLE} -c "\ + from distutils.sysconfig import get_python_lib; \ + print( get_python_lib( plat_specific=True, prefix='${CMAKE_INSTALL_PREFIX}' ) )" + OUTPUT_VARIABLE _ABS_PYTHON_MODULE_PATH + OUTPUT_STRIP_TRAILING_WHITESPACE ) + endif() GET_FILENAME_COMPONENT(_ABS_PYTHON_MODULE_PATH "${_ABS_PYTHON_MODULE_PATH}" ABSOLUTE) From dcff7245b3a651e4e8293fb8da6e9484c387c8f9 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Thu, 11 Apr 2019 16:01:26 -0700 Subject: [PATCH 140/223] Revert to older version plus add slash --- src/bindings/python/CMakeLists.txt | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/bindings/python/CMakeLists.txt b/src/bindings/python/CMakeLists.txt index da8c1afa..c8686097 100644 --- a/src/bindings/python/CMakeLists.txt +++ b/src/bindings/python/CMakeLists.txt @@ -66,18 +66,13 @@ if (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) ${PYTHON_LIBRARIES} openshot) ### FIND THE PYTHON INTERPRETER (AND THE SITE PACKAGES FOLDER) - if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) - # If not prefix found, detect python site package folder - EXECUTE_PROCESS ( COMMAND ${PYTHON_EXECUTABLE} -c "import site; print(site.getsitepackages()[0])" - OUTPUT_VARIABLE _ABS_PYTHON_MODULE_PATH - OUTPUT_STRIP_TRAILING_WHITESPACE ) - else() - execute_process ( COMMAND ${PYTHON_EXECUTABLE} -c "\ - from distutils.sysconfig import get_python_lib; \ - print( get_python_lib( plat_specific=True, prefix='${CMAKE_INSTALL_PREFIX}' ) )" - OUTPUT_VARIABLE _ABS_PYTHON_MODULE_PATH - OUTPUT_STRIP_TRAILING_WHITESPACE ) - endif() + execute_process ( COMMAND ${PYTHON_EXECUTABLE} -c "\ +import site; from distutils.sysconfig import get_python_lib; \ +print( get_python_lib( plat_specific=True, standard_lib=True, prefix='${CMAKE_INSTALL_PREFIX}' ) \ + + '/' + get_python_lib( plat_specific=False, standard_lib=False, prefix='${CMAKE_INSTALL_PREFIX}' ).split('/')[-1] \ + + '/' )" + OUTPUT_VARIABLE _ABS_PYTHON_MODULE_PATH + OUTPUT_STRIP_TRAILING_WHITESPACE ) GET_FILENAME_COMPONENT(_ABS_PYTHON_MODULE_PATH "${_ABS_PYTHON_MODULE_PATH}" ABSOLUTE) From 893b91b528b6c0447df8f3931b452a352f234b4c Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Thu, 18 Apr 2019 01:07:57 -0500 Subject: [PATCH 141/223] Adding doc/HW-ACCEL.md document, code reformatting, some variable renaming --- README.md | 7 + doc/HW-ACCEL.md | 84 +++++ include/FFmpegReader.h | 30 +- include/FFmpegWriter.h | 89 +++-- src/FFmpegReader.cpp | 778 ++++++++++++++++++----------------------- 5 files changed, 493 insertions(+), 495 deletions(-) create mode 100644 doc/HW-ACCEL.md diff --git a/README.md b/README.md index 8deb86a1..cf69c1cf 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,13 @@ are also available in the /docs/ source folder. * [Mac](https://github.com/OpenShot/libopenshot/wiki/Mac-Build-Instructions) * [Windows](https://github.com/OpenShot/libopenshot/wiki/Windows-Build-Instructions) +## Hardware Acceleration + +OpenShot now supports experimental hardware acceleration, both for encoding and +decoding videos. When enabled, this can either speed up those operations or slow +them down, depending on the power and features supported by your graphics card. +Please see [doc/HW-ACCELL.md](doc/HW-ACCEL.md) for more information. + ## Documentation Beautiful HTML documentation can be generated using Doxygen. diff --git a/doc/HW-ACCEL.md b/doc/HW-ACCEL.md new file mode 100644 index 00000000..edbe85b4 --- /dev/null +++ b/doc/HW-ACCEL.md @@ -0,0 +1,84 @@ +## Hardware Acceleration + +Observations for developers wanting to make hardware acceleration work. + +*All observations are for Linux (but contributions welcome).* + +## Supported FFmpeg Versions + +* HW accel is supported from ffmpeg version 3.2 (3.3 for nVidia drivers) +* HW accel was removed for nVidia drivers in Ubuntu for ffmpeg 4+ +* I could not manage to build a version of ffmpeg 4.1 with the nVidia SDK +that worked with nVidia cards. There might be a problem in ffmpeg 4+ +that prohibits this. + +**Notice:** The ffmpeg versions of Ubuntu and PPAs for Ubuntu show the +same behaviour. ffmpeg 3 has working nVidia hardware acceleration while +ffmpeg 4+ has no support for nVidia hardware acceleration +included. + +## OpenShot Settings + +The following settings are use by libopenshot to enable, disable, and control +the various hardware acceleration features. + +``` +/// Use video card for faster video decoding (if supported) +bool HARDWARE_DECODE = false; + +/// Use video codec for faster video decoding (if supported) +int HARDWARE_DECODER = 0; + +/// Use video card for faster video encoding (if supported) +bool HARDWARE_ENCODE = false; + +/// Number of threads of OpenMP +int OMP_THREADS = 12; + +/// Number of threads that ffmpeg uses +int FF_THREADS = 8; + +/// Maximum rows that hardware decode can handle +int DE_LIMIT_HEIGHT_MAX = 1100; + +/// Maximum columns that hardware decode can handle +int DE_LIMIT_WIDTH_MAX = 1950; + +/// Which GPU to use to decode (0 is the first) +int HW_DE_DEVICE_SET = 0; + +/// Which GPU to use to encode (0 is the first) +int HW_EN_DEVICE_SET = 0; +``` + +## Libva / VA-API (Video Acceleration API) + +The correct version of libva is needed (libva in Ubuntu 16.04 or libva2 +in Ubuntu 18.04) for the AppImage to work with hardware acceleration. +An AppImage that works on both systems (supporting libva and libva2), +might be possible when no libva is included in the AppImage. + +* vaapi is working for intel and AMD +* vaapi is working for decode only for nouveau +* nVidia driver is working for export only + +## AMD Graphics Cards (RadeonOpenCompute/ROCm) + +Decoding and encoding on the (AMD) GPU can be done on systems where ROCm +is installed and run. Possible future use for GPU acceleration of effects (contributions +welcome). + +## Multiple Graphics Cards + +If the computer has multiple graphics cards installed, you can choose which +should be used by libopenshot. Also, you can optionally use one card for +decoding and the other for encoding (if both cards support acceleration). + +## Help Us Improve Hardware Support + +This information might be wrong, and we would love to continue improving +our support for hardware acceleration in OpenShot. Please help us update +this document if you find an error or discover some new information. + +**Desperately Needed:** a way to compile ffmpeg 4.0 and up with working nVidia +hardware acceleration support on Ubuntu Linux! diff --git a/include/FFmpegReader.h b/include/FFmpegReader.h index acbec206..abf1af57 100644 --- a/include/FFmpegReader.h +++ b/include/FFmpegReader.h @@ -50,18 +50,17 @@ using namespace std; -namespace openshot -{ +namespace openshot { /** * @brief This struct holds the associated video frame and starting sample # for an audio packet. * * Because audio packets do not match up with video frames, this helps determine exactly * where the audio packet's samples belong. */ - struct AudioLocation - { + struct AudioLocation { int64_t frame; int sample_start; + bool is_near(AudioLocation location, int samples_per_frame, int64_t amount); }; @@ -91,17 +90,16 @@ namespace openshot * r.Close(); * @endcode */ - class FFmpegReader : public ReaderBase - { + class FFmpegReader : public ReaderBase { private: string path; AVFormatContext *pFormatCtx; int i, videoStream, audioStream; AVCodecContext *pCodecCtx, *aCodecCtx; - #if (LIBAVFORMAT_VERSION_MAJOR >= 57) +#if (LIBAVFORMAT_VERSION_MAJOR >= 57) AVBufferRef *hw_device_ctx = NULL; //PM - #endif +#endif AVStream *pStream, *aStream; AVPacket *packet; AVFrame *pFrame; @@ -145,15 +143,15 @@ namespace openshot int64_t video_pts_offset; int64_t last_frame; int64_t largest_frame_processed; - int64_t current_video_frame; // can't reliably use PTS of video to determine this + int64_t current_video_frame; // can't reliably use PTS of video to determine this - int hw_de_supported = 0; // Is set by FFmpegReader - #if IS_FFMPEG_3_2 + int hw_de_supported = 0; // Is set by FFmpegReader +#if IS_FFMPEG_3_2 AVPixelFormat hw_de_av_pix_fmt = AV_PIX_FMT_NONE; AVHWDeviceType hw_de_av_device_type = AV_HWDEVICE_TYPE_NONE; - #endif +#endif - int is_hardware_decode_supported(int codecid); + int IsHardwareDecodeSupported(int codecid); /// Check for the correct frames per second value by scanning the 1st few seconds of video packets. void CheckFPS(); @@ -210,10 +208,10 @@ namespace openshot std::shared_ptr ReadStream(int64_t requested_frame); /// Remove AVFrame from cache (and deallocate it's memory) - void RemoveAVFrame(AVFrame*); + void RemoveAVFrame(AVFrame *); /// Remove AVPacket from cache (and deallocate it's memory) - void RemoveAVPacket(AVPacket*); + void RemoveAVPacket(AVPacket *); /// Seek to a specific Frame. This is not always frame accurate, it's more of an estimation on many codecs. void Seek(int64_t requested_frame); @@ -251,7 +249,7 @@ namespace openshot void Close(); /// Get the cache object used by this reader - CacheMemory* GetCache() { return &final_cache; }; + CacheMemory *GetCache() { return &final_cache; }; /// Get a shared pointer to a openshot::Frame object for a specific frame number of this reader. /// diff --git a/include/FFmpegWriter.h b/include/FFmpegWriter.h index e219f72c..b93ef7b3 100644 --- a/include/FFmpegWriter.h +++ b/include/FFmpegWriter.h @@ -56,14 +56,12 @@ using namespace std; -namespace openshot -{ +namespace openshot { /// This enumeration designates the type of stream when encoding (video or audio) - enum StreamType - { - VIDEO_STREAM, ///< A video stream (used to determine which type of stream) - AUDIO_STREAM ///< An audio stream (used to determine which type of stream) + enum StreamType { + VIDEO_STREAM, ///< A video stream (used to determine which type of stream) + AUDIO_STREAM ///< An audio stream (used to determine which type of stream) }; /** @@ -141,8 +139,7 @@ namespace openshot * r.Close(); * @endcode */ - class FFmpegWriter : public WriterBase - { + class FFmpegWriter : public WriterBase { private: string path; int cache_size; @@ -155,56 +152,56 @@ namespace openshot bool write_header; bool write_trailer; - AVOutputFormat *fmt; - AVFormatContext *oc; - AVStream *audio_st, *video_st; - AVCodecContext *video_codec; - AVCodecContext *audio_codec; - SwsContext *img_convert_ctx; - double audio_pts, video_pts; - int16_t *samples; - uint8_t *audio_outbuf; - uint8_t *audio_encoder_buffer; + AVOutputFormat *fmt; + AVFormatContext *oc; + AVStream *audio_st, *video_st; + AVCodecContext *video_codec; + AVCodecContext *audio_codec; + SwsContext *img_convert_ctx; + double audio_pts, video_pts; + int16_t *samples; + uint8_t *audio_outbuf; + uint8_t *audio_encoder_buffer; - int num_of_rescalers; + int num_of_rescalers; int rescaler_position; - vector image_rescalers; + vector image_rescalers; - int audio_outbuf_size; - int audio_input_frame_size; - int initial_audio_input_frame_size; - int audio_input_position; - int audio_encoder_buffer_size; - SWRCONTEXT *avr; - SWRCONTEXT *avr_planar; + int audio_outbuf_size; + int audio_input_frame_size; + int initial_audio_input_frame_size; + int audio_input_position; + int audio_encoder_buffer_size; + SWRCONTEXT *avr; + SWRCONTEXT *avr_planar; - /* Resample options */ - int original_sample_rate; - int original_channels; + /* Resample options */ + int original_sample_rate; + int original_channels; - std::shared_ptr last_frame; - deque > spooled_audio_frames; - deque > spooled_video_frames; + std::shared_ptr last_frame; + deque > spooled_audio_frames; + deque > spooled_video_frames; - deque > queued_audio_frames; - deque > queued_video_frames; + deque > queued_audio_frames; + deque > queued_video_frames; - deque > processed_frames; - deque > deallocate_frames; + deque > processed_frames; + deque > deallocate_frames; - map, AVFrame*> av_frames; + map, AVFrame *> av_frames; - /// Add an AVFrame to the cache - void add_avframe(std::shared_ptr frame, AVFrame* av_frame); + /// Add an AVFrame to the cache + void add_avframe(std::shared_ptr frame, AVFrame *av_frame); /// Add an audio output stream - AVStream* add_audio_stream(); + AVStream *add_audio_stream(); /// Add a video output stream - AVStream* add_video_stream(); + AVStream *add_video_stream(); /// Allocate an AVFrame object - AVFrame* allocate_avframe(PixelFormat pix_fmt, int width, int height, int *buffer_size, uint8_t *new_buffer); + AVFrame *allocate_avframe(PixelFormat pix_fmt, int width, int height, int *buffer_size, uint8_t *new_buffer); /// Auto detect format (from path) void auto_detect_format(); @@ -239,7 +236,7 @@ namespace openshot void write_audio_packets(bool final); /// write video frame - bool write_video_packet(std::shared_ptr frame, AVFrame* frame_final); + bool write_video_packet(std::shared_ptr frame, AVFrame *frame_final); /// write all queued frames void write_queued_frames(); @@ -303,7 +300,7 @@ namespace openshot /// @param interlaced Does this video need to be interlaced? /// @param top_field_first Which frame should be used as the top field? /// @param bit_rate The video bit rate used during encoding - void SetVideoOptions(bool has_video, string codec, Fraction fps, int width, int height,Fraction pixel_ratio, bool interlaced, bool top_field_first, int bit_rate); + void SetVideoOptions(bool has_video, string codec, Fraction fps, int width, int height, Fraction pixel_ratio, bool interlaced, bool top_field_first, int bit_rate); /// @brief Set custom options (some codecs accept additional params). This must be called after the /// PrepareStreams() method, otherwise the streams have not been initialized yet. @@ -324,7 +321,7 @@ namespace openshot /// @param reader A openshot::ReaderBase object which will provide frames to be written /// @param start The starting frame number of the reader /// @param length The number of frames to write - void WriteFrame(ReaderBase* reader, int64_t start, int64_t length); + void WriteFrame(ReaderBase *reader, int64_t start, int64_t length); /// @brief Write the file trailer (after all frames are written). This is called automatically /// by the Close() method if this method has not yet been called. diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 1b2c6b82..3af851e1 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -30,7 +30,7 @@ #include "../include/FFmpegReader.h" -#define PRAYFORAWONDER 0 +#define ENABLE_VAAPI 0 #if IS_FFMPEG_3_2 #pragma message "You are compiling with experimental hardware decode" @@ -42,52 +42,51 @@ #define MAX_SUPPORTED_WIDTH 1950 #define MAX_SUPPORTED_HEIGHT 1100 -#if PRAYFORAWONDER +#if ENABLE_VAAPI #include "libavutil/hwcontext_vaapi.h" typedef struct VAAPIDecodeContext { - VAProfile va_profile; - VAEntrypoint va_entrypoint; - VAConfigID va_config; - VAContextID va_context; + VAProfile va_profile; + VAEntrypoint va_entrypoint; + VAConfigID va_config; + VAContextID va_context; - #if FF_API_STRUCT_VAAPI_CONTEXT -// FF_DISABLE_DEPRECATION_WARNINGS - int have_old_context; - struct vaapi_context *old_context; - AVBufferRef *device_ref; -// FF_ENABLE_DEPRECATION_WARNINGS - #endif +#if FF_API_STRUCT_VAAPI_CONTEXT + // FF_DISABLE_DEPRECATION_WARNINGS + int have_old_context; + struct vaapi_context *old_context; + AVBufferRef *device_ref; + // FF_ENABLE_DEPRECATION_WARNINGS +#endif - AVHWDeviceContext *device; - AVVAAPIDeviceContext *hwctx; + AVHWDeviceContext *device; + AVVAAPIDeviceContext *hwctx; - AVHWFramesContext *frames; - AVVAAPIFramesContext *hwfc; + AVHWFramesContext *frames; + AVVAAPIFramesContext *hwfc; - enum AVPixelFormat surface_format; - int surface_count; + enum AVPixelFormat surface_format; + int surface_count; } VAAPIDecodeContext; - - #endif +#endif #endif using namespace openshot; -int hw_de_on = 1; // Is set in UI +int hw_de_on = 0; #if IS_FFMPEG_3_2 AVPixelFormat hw_de_av_pix_fmt_global = AV_PIX_FMT_NONE; -AVHWDeviceType hw_de_av_device_type_global = AV_HWDEVICE_TYPE_NONE; + AVHWDeviceType hw_de_av_device_type_global = AV_HWDEVICE_TYPE_NONE; #endif FFmpegReader::FFmpegReader(string path) - : last_frame(0), is_seeking(0), seeking_pts(0), seeking_frame(0), seek_count(0), - audio_pts_offset(99999), video_pts_offset(99999), path(path), is_video_seek(true), check_interlace(false), - check_fps(false), enable_seek(true), is_open(false), seek_audio_frame_found(0), seek_video_frame_found(0), - prev_samples(0), prev_pts(0), pts_total(0), pts_counter(0), is_duration_known(false), largest_frame_processed(0), - current_video_frame(0), has_missing_frames(false), num_packets_since_video_frame(0), num_checks_since_final(0), - packet(NULL) { + : last_frame(0), is_seeking(0), seeking_pts(0), seeking_frame(0), seek_count(0), + audio_pts_offset(99999), video_pts_offset(99999), path(path), is_video_seek(true), check_interlace(false), + check_fps(false), enable_seek(true), is_open(false), seek_audio_frame_found(0), seek_video_frame_found(0), + prev_samples(0), prev_pts(0), pts_total(0), pts_counter(0), is_duration_known(false), largest_frame_processed(0), + current_video_frame(0), has_missing_frames(false), num_packets_since_video_frame(0), num_checks_since_final(0), + packet(NULL) { // Initialize FFMpeg, and register all formats and codecs AV_REGISTER_ALL @@ -134,8 +133,7 @@ FFmpegReader::~FFmpegReader() { } // This struct holds the associated video frame and starting sample # for an audio packet. -bool AudioLocation::is_near(AudioLocation location, int samples_per_frame, int64_t amount) -{ +bool AudioLocation::is_near(AudioLocation location, int samples_per_frame, int64_t amount) { // Is frame even close to this one? if (abs(location.frame - frame) >= 2) // This is too far away to be considered @@ -168,7 +166,7 @@ static enum AVPixelFormat get_hw_dec_format_va(AVCodecContext *ctx, const enum A break; } } - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); + ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::get_hw_dec_format_va (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); return AV_PIX_FMT_NONE; } @@ -185,7 +183,7 @@ static enum AVPixelFormat get_hw_dec_format_cu(AVCodecContext *ctx, const enum A break; } } - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); + ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::get_hw_dec_format_cu (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); return AV_PIX_FMT_NONE; } #endif @@ -204,7 +202,7 @@ static enum AVPixelFormat get_hw_dec_format_dx(AVCodecContext *ctx, const enum A break; } } - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); + ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::get_hw_dec_format_dx (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); return AV_PIX_FMT_NONE; } @@ -221,7 +219,7 @@ static enum AVPixelFormat get_hw_dec_format_d3(AVCodecContext *ctx, const enum A break; } } - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); + ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::get_hw_dec_format_d3 (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); return AV_PIX_FMT_NONE; } @@ -238,7 +236,7 @@ static enum AVPixelFormat get_hw_dec_format_cu(AVCodecContext *ctx, const enum A break; } } - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); + ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::get_hw_dec_format_cu (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); return AV_PIX_FMT_NONE; } #endif @@ -257,12 +255,12 @@ static enum AVPixelFormat get_hw_dec_format_qs(AVCodecContext *ctx, const enum A break; } } - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); + ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::get_hw_dec_format_qs (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); return AV_PIX_FMT_NONE; } #endif -int FFmpegReader::is_hardware_decode_supported(int codecid) +int FFmpegReader::IsHardwareDecodeSupported(int codecid) { int ret; switch (codecid) { @@ -283,15 +281,13 @@ int FFmpegReader::is_hardware_decode_supported(int codecid) #endif -void FFmpegReader::Open() -{ +void FFmpegReader::Open() { // Open reader if not already open - if (!is_open) - { + if (!is_open) { // Initialize format context pFormatCtx = NULL; { - hw_de_on = (openshot::Settings::Instance()->HARDWARE_DECODER == 0 ? 0 : 1); + hw_de_on = (openshot::Settings::Instance()->HARDWARE_DECODER == 0 ? 0 : 1); } // Open video file @@ -305,8 +301,7 @@ void FFmpegReader::Open() videoStream = -1; audioStream = -1; // Loop through each stream, and identify the video and audio stream index - for (unsigned int i = 0; i < pFormatCtx->nb_streams; i++) - { + for (unsigned int i = 0; i < pFormatCtx->nb_streams; i++) { // Is this a video stream? if (AV_GET_CODEC_TYPE(pFormatCtx->streams[i]) == AVMEDIA_TYPE_VIDEO && videoStream < 0) { videoStream = i; @@ -320,8 +315,7 @@ void FFmpegReader::Open() throw NoStreamsFound("No video or audio streams found in this file.", path); // Is there a video stream? - if (videoStream != -1) - { + if (videoStream != -1) { // Set the stream index info.video_stream_index = videoStream; @@ -335,16 +329,17 @@ void FFmpegReader::Open() AVCodec *pCodec = avcodec_find_decoder(codecId); AVDictionary *opts = NULL; int retry_decode_open = 2; - // If hw accel is selected but hardware connot handle repeat with software decoding + // If hw accel is selected but hardware cannot handle repeat with software decoding do { pCodecCtx = AV_GET_CODEC_CONTEXT(pStream, pCodec); - #if IS_FFMPEG_3_2 +#if IS_FFMPEG_3_2 if (hw_de_on && (retry_decode_open==2)) { // Up to here no decision is made if hardware or software decode - hw_de_supported = is_hardware_decode_supported(pCodecCtx->codec_id); + hw_de_supported = IsHardwareDecodeSupported(pCodecCtx->codec_id); } - #endif +#endif retry_decode_open = 0; + // Set number of threads equal to number of processors (not to exceed 16) pCodecCtx->thread_count = min(FF_NUM_PROCESSORS, 16); @@ -354,7 +349,7 @@ void FFmpegReader::Open() // Init options av_dict_set(&opts, "strict", "experimental", 0); - #if IS_FFMPEG_3_2 +#if IS_FFMPEG_3_2 if (hw_de_on && hw_de_supported) { // Open Hardware Acceleration int i_decoder_hw = 0; @@ -363,143 +358,148 @@ void FFmpegReader::Open() int adapter_num; adapter_num = openshot::Settings::Instance()->HW_DE_DEVICE_SET; fprintf(stderr, "\n\nDecodiing Device Nr: %d\n", adapter_num); + if (adapter_num < 3 && adapter_num >=0) { - #if defined(__linux__) - snprintf(adapter,sizeof(adapter),"/dev/dri/renderD%d", adapter_num+128); - adapter_ptr = adapter; - i_decoder_hw = openshot::Settings::Instance()->HARDWARE_DECODER; - switch (i_decoder_hw) { +#if defined(__linux__) + snprintf(adapter,sizeof(adapter),"/dev/dri/renderD%d", adapter_num+128); + adapter_ptr = adapter; + i_decoder_hw = openshot::Settings::Instance()->HARDWARE_DECODER; + switch (i_decoder_hw) { + case 0: + hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; + pCodecCtx->get_format = get_hw_dec_format_va; + break; + case 1: + hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; + pCodecCtx->get_format = get_hw_dec_format_va; + break; + case 2: + hw_de_av_device_type = AV_HWDEVICE_TYPE_CUDA; + pCodecCtx->get_format = get_hw_dec_format_cu; + break; + default: + hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; + pCodecCtx->get_format = get_hw_dec_format_va; + break; + } + +#elif defined(_WIN32) + adapter_ptr = NULL; + i_decoder_hw = openshot::Settings::Instance()->HARDWARE_DECODER; + switch (i_decoder_hw) { case 0: - hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; - pCodecCtx->get_format = get_hw_dec_format_va; - break; - case 1: - hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; - pCodecCtx->get_format = get_hw_dec_format_va; + hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2; + pCodecCtx->get_format = get_hw_dec_format_dx; break; case 2: hw_de_av_device_type = AV_HWDEVICE_TYPE_CUDA; pCodecCtx->get_format = get_hw_dec_format_cu; break; + case 3: + hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2; + pCodecCtx->get_format = get_hw_dec_format_dx; + break; + case 4: + hw_de_av_device_type = AV_HWDEVICE_TYPE_D3D11VA; + pCodecCtx->get_format = get_hw_dec_format_d3; + break; default: - hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; - pCodecCtx->get_format = get_hw_dec_format_va; + hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2; + pCodecCtx->get_format = get_hw_dec_format_dx; break; } +#elif defined(__APPLE__) + adapter_ptr = NULL; + i_decoder_hw = openshot::Settings::Instance()->HARDWARE_DECODER; + switch (i_decoder_hw) { + case 0: + hw_de_av_device_type = AV_HWDEVICE_TYPE_QSV; + pCodecCtx->get_format = get_hw_dec_format_qs; + break; + case 5: + hw_de_av_device_type = AV_HWDEVICE_TYPE_QSV; + pCodecCtx->get_format = get_hw_dec_format_qs; + break; + default: + hw_de_av_device_type = AV_HWDEVICE_TYPE_QSV; + pCodecCtx->get_format = get_hw_dec_format_qs; + break; + } +#endif - #elif defined(_WIN32) - adapter_ptr = NULL; - i_decoder_hw = openshot::Settings::Instance()->HARDWARE_DECODER; - switch (i_decoder_hw) { - case 0: - hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2; - pCodecCtx->get_format = get_hw_dec_format_dx; - break; - case 2: - hw_de_av_device_type = AV_HWDEVICE_TYPE_CUDA; - pCodecCtx->get_format = get_hw_dec_format_cu; - break; - case 3: - hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2; - pCodecCtx->get_format = get_hw_dec_format_dx; - break; - case 4: - hw_de_av_device_type = AV_HWDEVICE_TYPE_D3D11VA; - pCodecCtx->get_format = get_hw_dec_format_d3; - break; - default: - hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2; - pCodecCtx->get_format = get_hw_dec_format_dx; - break; - } - #elif defined(__APPLE__) - adapter_ptr = NULL; - i_decoder_hw = openshot::Settings::Instance()->HARDWARE_DECODER; - switch (i_decoder_hw) { - case 0: - hw_de_av_device_type = AV_HWDEVICE_TYPE_QSV; - pCodecCtx->get_format = get_hw_dec_format_qs; - break; - case 5: - hw_de_av_device_type = AV_HWDEVICE_TYPE_QSV; - pCodecCtx->get_format = get_hw_dec_format_qs; - break; - default: - hw_de_av_device_type = AV_HWDEVICE_TYPE_QSV; - pCodecCtx->get_format = get_hw_dec_format_qs; - break; - } - #endif - } - else { + } else { adapter_ptr = NULL; // Just to be sure } + // Check if it is there and writable - #if defined(__linux__) +#if defined(__linux__) if( adapter_ptr != NULL && access( adapter_ptr, W_OK ) == 0 ) { - #elif defined(_WIN32) +#elif defined(_WIN32) if( adapter_ptr != NULL ) { - #elif defined(__APPLE__) +#elif defined(__APPLE__) if( adapter_ptr != NULL ) { - #endif +#endif ZmqLogger::Instance()->AppendDebugMethod("Decode Device present using device", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); } else { adapter_ptr = NULL; // use default ZmqLogger::Instance()->AppendDebugMethod("Decode Device not present using default", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); } + hw_device_ctx = NULL; // Here the first hardware initialisations are made if (av_hwdevice_ctx_create(&hw_device_ctx, hw_de_av_device_type, adapter_ptr, NULL, 0) >= 0) { if (!(pCodecCtx->hw_device_ctx = av_buffer_ref(hw_device_ctx))) { throw InvalidCodec("Hardware device reference create failed.", path); } - /* - av_buffer_unref(&ist->hw_frames_ctx); - ist->hw_frames_ctx = av_hwframe_ctx_alloc(hw_device_ctx); - if (!ist->hw_frames_ctx) { - av_log(avctx, AV_LOG_ERROR, "Error creating a CUDA frames context\n"); - return AVERROR(ENOMEM); - } - frames_ctx = (AVHWFramesContext*)ist->hw_frames_ctx->data; + /* + av_buffer_unref(&ist->hw_frames_ctx); + ist->hw_frames_ctx = av_hwframe_ctx_alloc(hw_device_ctx); + if (!ist->hw_frames_ctx) { + av_log(avctx, AV_LOG_ERROR, "Error creating a CUDA frames context\n"); + return AVERROR(ENOMEM); + } - frames_ctx->format = AV_PIX_FMT_CUDA; - frames_ctx->sw_format = avctx->sw_pix_fmt; - frames_ctx->width = avctx->width; - frames_ctx->height = avctx->height; + frames_ctx = (AVHWFramesContext*)ist->hw_frames_ctx->data; - av_log(avctx, AV_LOG_DEBUG, "Initializing CUDA frames context: sw_format = %s, width = %d, height = %d\n", - av_get_pix_fmt_name(frames_ctx->sw_format), frames_ctx->width, frames_ctx->height); + frames_ctx->format = AV_PIX_FMT_CUDA; + frames_ctx->sw_format = avctx->sw_pix_fmt; + frames_ctx->width = avctx->width; + frames_ctx->height = avctx->height; + + av_log(avctx, AV_LOG_DEBUG, "Initializing CUDA frames context: sw_format = %s, width = %d, height = %d\n", + av_get_pix_fmt_name(frames_ctx->sw_format), frames_ctx->width, frames_ctx->height); - ret = av_hwframe_ctx_init(pCodecCtx->hw_device_ctx); - ret = av_hwframe_ctx_init(ist->hw_frames_ctx); - if (ret < 0) { - av_log(avctx, AV_LOG_ERROR, "Error initializing a CUDA frame pool\n"); - return ret; - } - */ + ret = av_hwframe_ctx_init(pCodecCtx->hw_device_ctx); + ret = av_hwframe_ctx_init(ist->hw_frames_ctx); + if (ret < 0) { + av_log(avctx, AV_LOG_ERROR, "Error initializing a CUDA frame pool\n"); + return ret; + } + */ } else { throw InvalidCodec("Hardware device create failed.", path); } - } - #endif +#endif + // Open video codec if (avcodec_open2(pCodecCtx, pCodec, &opts) < 0) throw InvalidCodec("A video codec was found, but could not be opened.", path); - #if IS_FFMPEG_3_2 +#if IS_FFMPEG_3_2 if (hw_de_on && hw_de_supported) { AVHWFramesConstraints *constraints = NULL; void *hwconfig = NULL; hwconfig = av_hwdevice_hwconfig_alloc(hw_device_ctx); - // NOT WORKING needs va_config ! - #if PRAYFORAWONDER + +// TODO: needs va_config! +#if ENABLE_VAAPI ((AVVAAPIHWConfig *)hwconfig)->config_id = ((VAAPIDecodeContext *)(pCodecCtx->priv_data))->va_config; - #endif +#endif constraints = av_hwdevice_get_hwframe_constraints(hw_device_ctx,hwconfig); if (constraints) { if (pCodecCtx->coded_width < constraints->min_width || @@ -555,9 +555,9 @@ void FFmpegReader::Open() else { ZmqLogger::Instance()->AppendDebugMethod("\nDecode in software is used\n", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); } - #else +#else retry_decode_open = 0; - #endif +#endif } while (retry_decode_open); // retry_decode_open // Free options av_dict_free(&opts); @@ -567,8 +567,7 @@ void FFmpegReader::Open() } // Is there an audio stream? - if (audioStream != -1) - { + if (audioStream != -1) { // Set the stream index info.audio_stream_index = audioStream; @@ -626,32 +625,28 @@ void FFmpegReader::Open() } } -void FFmpegReader::Close() -{ +void FFmpegReader::Close() { // Close all objects, if reader is 'open' - if (is_open) - { + if (is_open) { // Mark as "closed" is_open = false; ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::Close", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); // Close the codec - if (info.has_video) - { + if (info.has_video) { avcodec_flush_buffers(pCodecCtx); AV_FREE_CONTEXT(pCodecCtx); - #if IS_FFMPEG_3_2 +#if IS_FFMPEG_3_2 if (hw_de_on) { if (hw_device_ctx) { av_buffer_unref(&hw_device_ctx); hw_device_ctx = NULL; } } - #endif +#endif } - if (info.has_audio) - { + if (info.has_audio) { avcodec_flush_buffers(aCodecCtx); AV_FREE_CONTEXT(aCodecCtx); } @@ -663,7 +658,7 @@ void FFmpegReader::Close() // Clear processed lists { - const GenericScopedLock lock(processingCriticalSection); + const GenericScopedLock lock(processingCriticalSection); processed_video_frames.clear(); processed_audio_frames.clear(); processing_video_frames.clear(); @@ -689,15 +684,14 @@ void FFmpegReader::Close() } } -void FFmpegReader::UpdateAudioInfo() -{ +void FFmpegReader::UpdateAudioInfo() { // Set values of FileInfo struct info.has_audio = true; info.file_size = pFormatCtx->pb ? avio_size(pFormatCtx->pb) : -1; info.acodec = aCodecCtx->codec->name; info.channels = AV_GET_CODEC_ATTRIBUTES(aStream, aCodecCtx)->channels; if (AV_GET_CODEC_ATTRIBUTES(aStream, aCodecCtx)->channel_layout == 0) - AV_GET_CODEC_ATTRIBUTES(aStream, aCodecCtx)->channel_layout = av_get_default_channel_layout( AV_GET_CODEC_ATTRIBUTES(aStream, aCodecCtx)->channels ); + AV_GET_CODEC_ATTRIBUTES(aStream, aCodecCtx)->channel_layout = av_get_default_channel_layout(AV_GET_CODEC_ATTRIBUTES(aStream, aCodecCtx)->channels); info.channel_layout = (ChannelLayout) AV_GET_CODEC_ATTRIBUTES(aStream, aCodecCtx)->channel_layout; info.sample_rate = AV_GET_CODEC_ATTRIBUTES(aStream, aCodecCtx)->sample_rate; info.audio_bit_rate = AV_GET_CODEC_ATTRIBUTES(aStream, aCodecCtx)->bit_rate; @@ -711,15 +705,13 @@ void FFmpegReader::UpdateAudioInfo() info.duration = aStream->duration * info.audio_timebase.ToDouble(); // Check for an invalid video length - if (info.has_video && info.video_length <= 0) - { + if (info.has_video && info.video_length <= 0) { // Calculate the video length from the audio duration info.video_length = info.duration * info.fps.ToDouble(); } // Set video timebase (if no video stream was found) - if (!info.has_video) - { + if (!info.has_video) { // Set a few important default video settings (so audio can be divided into frames) info.fps.num = 24; info.fps.den = 1; @@ -744,8 +736,7 @@ void FFmpegReader::UpdateAudioInfo() } } -void FFmpegReader::UpdateVideoInfo() -{ +void FFmpegReader::UpdateVideoInfo() { if (check_fps) // Already initialized all the video metadata, no reason to do it again return; @@ -762,18 +753,13 @@ void FFmpegReader::UpdateVideoInfo() info.fps.num = pStream->avg_frame_rate.num; info.fps.den = pStream->avg_frame_rate.den; - if (pStream->sample_aspect_ratio.num != 0) - { + if (pStream->sample_aspect_ratio.num != 0) { info.pixel_ratio.num = pStream->sample_aspect_ratio.num; info.pixel_ratio.den = pStream->sample_aspect_ratio.den; - } - else if (AV_GET_CODEC_ATTRIBUTES(pStream, pCodecCtx)->sample_aspect_ratio.num != 0) - { + } else if (AV_GET_CODEC_ATTRIBUTES(pStream, pCodecCtx)->sample_aspect_ratio.num != 0) { info.pixel_ratio.num = AV_GET_CODEC_ATTRIBUTES(pStream, pCodecCtx)->sample_aspect_ratio.num; info.pixel_ratio.den = AV_GET_CODEC_ATTRIBUTES(pStream, pCodecCtx)->sample_aspect_ratio.den; - } - else - { + } else { info.pixel_ratio.num = 1; info.pixel_ratio.den = 1; } @@ -807,15 +793,12 @@ void FFmpegReader::UpdateVideoInfo() info.duration = (info.file_size / info.video_bit_rate); // No duration found in stream of file - if (info.duration <= 0.0f) - { + if (info.duration <= 0.0f) { // No duration is found in the video stream info.duration = -1; info.video_length = -1; is_duration_known = false; - } - else - { + } else { // Yes, a duration was found is_duration_known = true; @@ -840,8 +823,7 @@ void FFmpegReader::UpdateVideoInfo() } -std::shared_ptr FFmpegReader::GetFrame(int64_t requested_frame) -{ +std::shared_ptr FFmpegReader::GetFrame(int64_t requested_frame) { // Check for open reader (or throw exception) if (!is_open) throw ReaderClosed("The FFmpegReader is closed. Call Open() before calling this method.", path); @@ -866,10 +848,8 @@ std::shared_ptr FFmpegReader::GetFrame(int64_t requested_frame) // Return the cached frame return frame; - } - else - { - #pragma omp critical (ReadStream) + } else { +#pragma omp critical (ReadStream) { // Check the cache a 2nd time (due to a potential previous lock) frame = final_cache.GetFrame(requested_frame); @@ -878,8 +858,7 @@ std::shared_ptr FFmpegReader::GetFrame(int64_t requested_frame) ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetFrame", "returned cached frame on 2nd look", requested_frame, "", -1, "", -1, "", -1, "", -1, "", -1); // Return the cached frame - } - else { + } else { // Frame is not in cache // Reset seek count seek_count = 0; @@ -891,20 +870,16 @@ std::shared_ptr FFmpegReader::GetFrame(int64_t requested_frame) // Are we within X frames of the requested frame? int64_t diff = requested_frame - last_frame; - if (diff >= 1 && diff <= 20) - { + if (diff >= 1 && diff <= 20) { // Continue walking the stream frame = ReadStream(requested_frame); - } - else - { + } else { // Greater than 30 frames away, or backwards, we need to seek to the nearest key frame if (enable_seek) // Only seek if enabled Seek(requested_frame); - else if (!enable_seek && diff < 0) - { + else if (!enable_seek && diff < 0) { // Start over, since we can't seek, and the requested frame is smaller than our position Close(); Open(); @@ -920,8 +895,7 @@ std::shared_ptr FFmpegReader::GetFrame(int64_t requested_frame) } // Read the stream until we find the requested Frame -std::shared_ptr FFmpegReader::ReadStream(int64_t requested_frame) -{ +std::shared_ptr FFmpegReader::ReadStream(int64_t requested_frame) { // Allocate video frame bool end_of_stream = false; bool check_seek = false; @@ -931,7 +905,7 @@ std::shared_ptr FFmpegReader::ReadStream(int64_t requested_frame) // Minimum number of packets to process (for performance reasons) int packets_processed = 0; int minimum_packets = OPEN_MP_NUM_PROCESSORS; - int max_packets = 4096; + int max_packets = 4096; // Set the number of threads in OpenMP omp_set_num_threads(OPEN_MP_NUM_PROCESSORS); @@ -941,20 +915,19 @@ std::shared_ptr FFmpegReader::ReadStream(int64_t requested_frame) // Debug output ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream", "requested_frame", requested_frame, "OPEN_MP_NUM_PROCESSORS", OPEN_MP_NUM_PROCESSORS, "", -1, "", -1, "", -1, "", -1); - #pragma omp parallel +#pragma omp parallel { - #pragma omp single +#pragma omp single { // Loop through the stream until the correct frame is found - while (true) - { + while (true) { // Get the next packet into a local variable called packet packet_error = GetNextPacket(); int processing_video_frames_size = 0; int processing_audio_frames_size = 0; { - const GenericScopedLock lock(processingCriticalSection); + const GenericScopedLock lock(processingCriticalSection); processing_video_frames_size = processing_video_frames.size(); processing_audio_frames_size = processing_audio_frames.size(); } @@ -962,14 +935,13 @@ std::shared_ptr FFmpegReader::ReadStream(int64_t requested_frame) // Wait if too many frames are being processed while (processing_video_frames_size + processing_audio_frames_size >= minimum_packets) { usleep(2500); - const GenericScopedLock lock(processingCriticalSection); + const GenericScopedLock lock(processingCriticalSection); processing_video_frames_size = processing_video_frames.size(); processing_audio_frames_size = processing_audio_frames.size(); } // Get the next packet (if any) - if (packet_error < 0) - { + if (packet_error < 0) { // Break loop when no more packets found end_of_stream = true; break; @@ -979,29 +951,27 @@ std::shared_ptr FFmpegReader::ReadStream(int64_t requested_frame) ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (GetNextPacket)", "requested_frame", requested_frame, "processing_video_frames_size", processing_video_frames_size, "processing_audio_frames_size", processing_audio_frames_size, "minimum_packets", minimum_packets, "packets_processed", packets_processed, "is_seeking", is_seeking); // Video packet - if (info.has_video && packet->stream_index == videoStream) - { + if (info.has_video && packet->stream_index == videoStream) { // Reset this counter, since we have a video packet num_packets_since_video_frame = 0; // Check the status of a seek (if any) - if (is_seeking) - #pragma omp critical (openshot_seek) - check_seek = CheckSeek(true); - else - check_seek = false; + if (is_seeking) +#pragma omp critical (openshot_seek) + check_seek = CheckSeek(true); + else + check_seek = false; - if (check_seek) { - // Jump to the next iteration of this loop - continue; - } + if (check_seek) { + // Jump to the next iteration of this loop + continue; + } // Get the AVFrame from the current packet frame_finished = GetAVFrame(); // Check if the AVFrame is finished and set it - if (frame_finished) - { + if (frame_finished) { // Update PTS / Frame Offset (if any) UpdatePTSOffset(true); @@ -1011,20 +981,19 @@ std::shared_ptr FFmpegReader::ReadStream(int64_t requested_frame) if (openshot::Settings::Instance()->WAIT_FOR_VIDEO_PROCESSING_TASK) { // Wait on each OMP task to complete before moving on to the next one. This slows // down processing considerably, but might be more stable on some systems. - #pragma omp taskwait +#pragma omp taskwait } } } // Audio packet - else if (info.has_audio && packet->stream_index == audioStream) - { + else if (info.has_audio && packet->stream_index == audioStream) { // Increment this (to track # of packets since the last video packet) num_packets_since_video_frame++; // Check the status of a seek (if any) if (is_seeking) - #pragma omp critical (openshot_seek) +#pragma omp critical (openshot_seek) check_seek = CheckSeek(false); else check_seek = false; @@ -1086,8 +1055,7 @@ std::shared_ptr FFmpegReader::ReadStream(int64_t requested_frame) if (frame) { // return the largest processed frame (assuming it was the last in the video file) return frame; - } - else { + } else { // The largest processed frame is no longer in cache, return a blank frame std::shared_ptr f = CreateFrame(largest_frame_processed); f->AddColor(info.width, info.height, "#000"); @@ -1098,43 +1066,40 @@ std::shared_ptr FFmpegReader::ReadStream(int64_t requested_frame) } // Get the next packet (if any) -int FFmpegReader::GetNextPacket() -{ +int FFmpegReader::GetNextPacket() { int found_packet = 0; AVPacket *next_packet; - #pragma omp critical(getnextpacket) +#pragma omp critical(getnextpacket) { - next_packet = new AVPacket(); - found_packet = av_read_frame(pFormatCtx, next_packet); + next_packet = new AVPacket(); + found_packet = av_read_frame(pFormatCtx, next_packet); - if (packet) { - // Remove previous packet before getting next one - RemoveAVPacket(packet); - packet = NULL; + if (packet) { + // Remove previous packet before getting next one + RemoveAVPacket(packet); + packet = NULL; + } + + if (found_packet >= 0) { + // Update current packet pointer + packet = next_packet; + } } - - if (found_packet >= 0) - { - // Update current packet pointer - packet = next_packet; - } -} // Return if packet was found (or error number) return found_packet; } // Get an AVFrame (if any) -bool FFmpegReader::GetAVFrame() -{ +bool FFmpegReader::GetAVFrame() { int frameFinished = -1; int ret = 0; // Decode video frame AVFrame *next_frame = AV_ALLOCATE_FRAME(); - #pragma omp critical (packet_cache) +#pragma omp critical (packet_cache) { - #if IS_FFMPEG_3_2 +#if IS_FFMPEG_3_2 frameFinished = 0; ret = avcodec_send_packet(pCodecCtx, packet); @@ -1157,29 +1122,30 @@ bool FFmpegReader::GetAVFrame() } pFrame = new AVFrame(); while (ret >= 0) { - ret = avcodec_receive_frame(pCodecCtx, next_frame2); - if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { - break; - } - if (ret != 0) { - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (invalid return frame received)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); - } - if (hw_de_on && hw_de_supported) { - int err; - if (next_frame2->format == hw_de_av_pix_fmt) { - next_frame->format = AV_PIX_FMT_YUV420P; - if ((err = av_hwframe_transfer_data(next_frame,next_frame2,0)) < 0) { - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (Failed to transfer data to output frame)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); - } - if ((err = av_frame_copy_props(next_frame,next_frame2)) < 0) { - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (Failed to copy props to output frame)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); - } + ret = avcodec_receive_frame(pCodecCtx, next_frame2); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + break; + } + if (ret != 0) { + ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (invalid return frame received)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); + } + if (hw_de_on && hw_de_supported) { + int err; + if (next_frame2->format == hw_de_av_pix_fmt) { + next_frame->format = AV_PIX_FMT_YUV420P; + if ((err = av_hwframe_transfer_data(next_frame,next_frame2,0)) < 0) { + ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (Failed to transfer data to output frame)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); + } + if ((err = av_frame_copy_props(next_frame,next_frame2)) < 0) { + ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (Failed to copy props to output frame)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); } } - else - { // No hardware acceleration used -> no copy from GPU memory needed - next_frame = next_frame2; - } + } + else + { // No hardware acceleration used -> no copy from GPU memory needed + next_frame = next_frame2; + } + // TODO also handle possible further frames // Use only the first frame like avcodec_decode_video2 if (frameFinished == 0 ) { @@ -1198,7 +1164,7 @@ bool FFmpegReader::GetAVFrame() AV_FREE_FRAME(&next_frame2); } } - #else +#else avcodec_decode_video2(pCodecCtx, next_frame, &frameFinished, packet); // is frame finished @@ -1217,7 +1183,7 @@ bool FFmpegReader::GetAVFrame() info.top_field_first = next_frame->top_field_first; } } - #endif +#endif } // deallocate the frame @@ -1228,11 +1194,9 @@ bool FFmpegReader::GetAVFrame() } // Check the current seek position and determine if we need to seek again -bool FFmpegReader::CheckSeek(bool is_video) -{ +bool FFmpegReader::CheckSeek(bool is_video) { // Are we seeking for a specific frame? - if (is_seeking) - { + if (is_seeking) { // Determine if both an audio and video packet have been decoded since the seek happened. // If not, allow the ReadStream method to keep looping if ((is_video_seek && !seek_video_frame_found) || (!is_video_seek && !seek_audio_frame_found)) @@ -1248,16 +1212,13 @@ bool FFmpegReader::CheckSeek(bool is_video) max_seeked_frame = seek_video_frame_found; // determine if we are "before" the requested frame - if (max_seeked_frame >= seeking_frame) - { + if (max_seeked_frame >= seeking_frame) { // SEEKED TOO FAR ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::CheckSeek (Too far, seek again)", "is_video_seek", is_video_seek, "max_seeked_frame", max_seeked_frame, "seeking_frame", seeking_frame, "seeking_pts", seeking_pts, "seek_video_frame_found", seek_video_frame_found, "seek_audio_frame_found", seek_audio_frame_found); // Seek again... to the nearest Keyframe Seek(seeking_frame - (10 * seek_count * seek_count)); - } - else - { + } else { // SEEK WORKED ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::CheckSeek (Successful)", "is_video_seek", is_video_seek, "current_pts", packet->pts, "seeking_pts", seeking_pts, "seeking_frame", seeking_frame, "seek_video_frame_found", seek_video_frame_found, "seek_audio_frame_found", seek_audio_frame_found); @@ -1273,8 +1234,7 @@ bool FFmpegReader::CheckSeek(bool is_video) } // Process a video packet -void FFmpegReader::ProcessVideoPacket(int64_t requested_frame) -{ +void FFmpegReader::ProcessVideoPacket(int64_t requested_frame) { // Calculate current frame # int64_t current_frame = ConvertVideoPTStoFrame(GetVideoPTS()); @@ -1283,8 +1243,7 @@ void FFmpegReader::ProcessVideoPacket(int64_t requested_frame) seek_video_frame_found = current_frame; // Are we close enough to decode the frame? and is this frame # valid? - if ((current_frame < (requested_frame - 20)) or (current_frame == -1)) - { + if ((current_frame < (requested_frame - 20)) or (current_frame == -1)) { // Remove frame and packet RemoveAVFrame(pFrame); @@ -1306,10 +1265,10 @@ void FFmpegReader::ProcessVideoPacket(int64_t requested_frame) AVFrame *my_frame = pFrame; // Add video frame to list of processing video frames - const GenericScopedLock lock(processingCriticalSection); + const GenericScopedLock lock(processingCriticalSection); processing_video_frames[current_frame] = current_frame; - #pragma omp task firstprivate(current_frame, my_frame, height, width, video_length, pix_fmt) +#pragma omp task firstprivate(current_frame, my_frame, height, width, video_length, pix_fmt) { // Create variables for a RGB Frame (since most videos are not in RGB, we must convert it) AVFrame *pFrameRGB = NULL; @@ -1333,7 +1292,7 @@ void FFmpegReader::ProcessVideoPacket(int64_t requested_frame) if (max_height <= 0) max_height = info.height; - Clip* parent = (Clip*) GetClip(); + Clip *parent = (Clip *) GetClip(); if (parent) { if (parent->scale == SCALE_FIT || parent->scale == SCALE_STRETCH) { // Best fit or Stretch scaling (based on max timeline size * scaling keyframes) @@ -1354,8 +1313,7 @@ void FFmpegReader::ProcessVideoPacket(int64_t requested_frame) if (width_size.width() >= max_width && width_size.height() >= max_height) { max_width = max(max_width, width_size.width()); max_height = max(max_height, width_size.height()); - } - else { + } else { max_width = max(max_width, height_size.width()); max_height = max(max_height, height_size.height()); } @@ -1389,7 +1347,7 @@ void FFmpegReader::ProcessVideoPacket(int64_t requested_frame) // Determine required buffer size and allocate buffer numBytes = AV_GET_IMAGE_SIZE(PIX_FMT_RGBA, width, height); - #pragma omp critical (video_buffer) +#pragma omp critical (video_buffer) buffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t)); // Copy picture data from one AVFrame (or AVPicture) to another one. @@ -1404,7 +1362,7 @@ void FFmpegReader::ProcessVideoPacket(int64_t requested_frame) // Resize / Convert to RGB sws_scale(img_convert_ctx, my_frame->data, my_frame->linesize, 0, - original_height, pFrameRGB->data, pFrameRGB->linesize); + original_height, pFrameRGB->data, pFrameRGB->linesize); // Create or get the existing frame object std::shared_ptr f = CreateFrame(current_frame); @@ -1416,7 +1374,7 @@ void FFmpegReader::ProcessVideoPacket(int64_t requested_frame) working_cache.Add(f); // Keep track of last last_video_frame - #pragma omp critical (video_buffer) +#pragma omp critical (video_buffer) last_video_frame = f; // Free the RGB image @@ -1429,7 +1387,7 @@ void FFmpegReader::ProcessVideoPacket(int64_t requested_frame) // Remove video frame from list of processing video frames { - const GenericScopedLock lock(processingCriticalSection); + const GenericScopedLock lock(processingCriticalSection); processing_video_frames.erase(current_frame); processed_video_frames[current_frame] = current_frame; } @@ -1442,15 +1400,13 @@ void FFmpegReader::ProcessVideoPacket(int64_t requested_frame) } // Process an audio packet -void FFmpegReader::ProcessAudioPacket(int64_t requested_frame, int64_t target_frame, int starting_sample) -{ +void FFmpegReader::ProcessAudioPacket(int64_t requested_frame, int64_t target_frame, int starting_sample) { // Track 1st audio packet after a successful seek if (!seek_audio_frame_found && is_seeking) seek_audio_frame_found = target_frame; // Are we close enough to decode the frame's audio? - if (target_frame < (requested_frame - 20)) - { + if (target_frame < (requested_frame - 20)) { // Debug output ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ProcessAudioPacket (Skipped)", "requested_frame", requested_frame, "target_frame", target_frame, "starting_sample", starting_sample, "", -1, "", -1, "", -1); @@ -1471,9 +1427,9 @@ void FFmpegReader::ProcessAudioPacket(int64_t requested_frame, int64_t target_fr // re-initialize buffer size (it gets changed in the avcodec_decode_audio2 method call) int buf_size = AVCODEC_MAX_AUDIO_FRAME_SIZE + MY_INPUT_BUFFER_PADDING_SIZE; - #pragma omp critical (ProcessAudioPacket) +#pragma omp critical (ProcessAudioPacket) { - #if IS_FFMPEG_3_2 +#if IS_FFMPEG_3_2 int ret = 0; frame_finished = 1; while((packet->size > 0 || (!packet->data && frame_finished)) && ret >= 0) { @@ -1500,7 +1456,7 @@ void FFmpegReader::ProcessAudioPacket(int64_t requested_frame, int64_t target_fr { ret = -1; } - #else +#else int used = avcodec_decode_audio4(aCodecCtx, audio_frame, &frame_finished, packet); #endif } @@ -1508,12 +1464,12 @@ void FFmpegReader::ProcessAudioPacket(int64_t requested_frame, int64_t target_fr if (frame_finished) { // determine how many samples were decoded - int planar = av_sample_fmt_is_planar((AVSampleFormat)AV_GET_CODEC_PIXEL_FORMAT(aStream, aCodecCtx)); + int planar = av_sample_fmt_is_planar((AVSampleFormat) AV_GET_CODEC_PIXEL_FORMAT(aStream, aCodecCtx)); int plane_size = -1; data_size = av_samples_get_buffer_size(&plane_size, - AV_GET_CODEC_ATTRIBUTES(aStream, aCodecCtx)->channels, - audio_frame->nb_samples, - (AVSampleFormat)(AV_GET_SAMPLE_FORMAT(aStream, aCodecCtx)), 1); + AV_GET_CODEC_ATTRIBUTES(aStream, aCodecCtx)->channels, + audio_frame->nb_samples, + (AVSampleFormat) (AV_GET_SAMPLE_FORMAT(aStream, aCodecCtx)), 1); // Calculate total number of samples packet_samples = audio_frame->nb_samples * AV_GET_CODEC_ATTRIBUTES(aStream, aCodecCtx)->channels; @@ -1539,12 +1495,11 @@ void FFmpegReader::ProcessAudioPacket(int64_t requested_frame, int64_t target_fr // Add audio frame to list of processing audio frames { - const GenericScopedLock lock(processingCriticalSection); + const GenericScopedLock lock(processingCriticalSection); processing_audio_frames.insert(pair(previous_packet_location.frame, previous_packet_location.frame)); } - while (pts_remaining_samples) - { + while (pts_remaining_samples) { // Get Samples per frame (for this frame number) int samples_per_frame = Frame::GetSamplesPerFrame(previous_packet_location.frame, info.fps, info.sample_rate, info.channels); @@ -1563,7 +1518,7 @@ void FFmpegReader::ProcessAudioPacket(int64_t requested_frame, int64_t target_fr // Add audio frame to list of processing audio frames { - const GenericScopedLock lock(processingCriticalSection); + const GenericScopedLock lock(processingCriticalSection); processing_audio_frames.insert(pair(previous_packet_location.frame, previous_packet_location.frame)); } @@ -1590,24 +1545,24 @@ void FFmpegReader::ProcessAudioPacket(int64_t requested_frame, int64_t target_fr // setup resample context avr = SWR_ALLOC(); - av_opt_set_int(avr, "in_channel_layout", AV_GET_CODEC_ATTRIBUTES(aStream, aCodecCtx)->channel_layout, 0); + av_opt_set_int(avr, "in_channel_layout", AV_GET_CODEC_ATTRIBUTES(aStream, aCodecCtx)->channel_layout, 0); av_opt_set_int(avr, "out_channel_layout", AV_GET_CODEC_ATTRIBUTES(aStream, aCodecCtx)->channel_layout, 0); - av_opt_set_int(avr, "in_sample_fmt", AV_GET_SAMPLE_FORMAT(aStream, aCodecCtx), 0); - av_opt_set_int(avr, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0); - av_opt_set_int(avr, "in_sample_rate", info.sample_rate, 0); - av_opt_set_int(avr, "out_sample_rate", info.sample_rate, 0); - av_opt_set_int(avr, "in_channels", info.channels, 0); - av_opt_set_int(avr, "out_channels", info.channels, 0); + av_opt_set_int(avr, "in_sample_fmt", AV_GET_SAMPLE_FORMAT(aStream, aCodecCtx), 0); + av_opt_set_int(avr, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0); + av_opt_set_int(avr, "in_sample_rate", info.sample_rate, 0); + av_opt_set_int(avr, "out_sample_rate", info.sample_rate, 0); + av_opt_set_int(avr, "in_channels", info.channels, 0); + av_opt_set_int(avr, "out_channels", info.channels, 0); int r = SWR_INIT(avr); // Convert audio samples - nb_samples = SWR_CONVERT(avr, // audio resample context - audio_converted->data, // output data pointers - audio_converted->linesize[0], // output plane size, in bytes. (0 if unknown) - audio_converted->nb_samples, // maximum number of samples that the output buffer can hold - audio_frame->data, // input data pointers - audio_frame->linesize[0], // input plane size, in bytes (0 if unknown) - audio_frame->nb_samples); // number of input samples to convert + nb_samples = SWR_CONVERT(avr, // audio resample context + audio_converted->data, // output data pointers + audio_converted->linesize[0], // output plane size, in bytes. (0 if unknown) + audio_converted->nb_samples, // maximum number of samples that the output buffer can hold + audio_frame->data, // input data pointers + audio_frame->linesize[0], // input plane size, in bytes (0 if unknown) + audio_frame->nb_samples); // number of input samples to convert // Copy audio samples over original samples memcpy(audio_buf, audio_converted->data[0], audio_converted->nb_samples * av_get_bytes_per_sample(AV_SAMPLE_FMT_S16) * info.channels); @@ -1623,8 +1578,7 @@ void FFmpegReader::ProcessAudioPacket(int64_t requested_frame, int64_t target_fr int64_t starting_frame_number = -1; bool partial_frame = true; - for (int channel_filter = 0; channel_filter < info.channels; channel_filter++) - { + for (int channel_filter = 0; channel_filter < info.channels; channel_filter++) { // Array of floats (to hold samples for each channel) starting_frame_number = target_frame; int channel_buffer_size = packet_samples / info.channels; @@ -1638,11 +1592,9 @@ void FFmpegReader::ProcessAudioPacket(int64_t requested_frame, int64_t target_fr // Toggle through each channel number, since channel data is stored like (left right left right) int channel = 0; int position = 0; - for (int sample = 0; sample < packet_samples; sample++) - { + for (int sample = 0; sample < packet_samples; sample++) { // Only add samples for current channel - if (channel_filter == channel) - { + if (channel_filter == channel) { // Add sample (convert from (-32768 to 32768) to (-1.0 to 1.0)) channel_buffer[position] = audio_buf[sample] * (1.0f / (1 << 15)); @@ -1653,7 +1605,7 @@ void FFmpegReader::ProcessAudioPacket(int64_t requested_frame, int64_t target_fr // increment channel (if needed) if ((channel + 1) < info.channels) // move to next channel - channel ++; + channel++; else // reset channel channel = 0; @@ -1662,9 +1614,8 @@ void FFmpegReader::ProcessAudioPacket(int64_t requested_frame, int64_t target_fr // Loop through samples, and add them to the correct frames int start = starting_sample; int remaining_samples = channel_buffer_size; - float *iterate_channel_buffer = channel_buffer; // pointer to channel buffer - while (remaining_samples > 0) - { + float *iterate_channel_buffer = channel_buffer; // pointer to channel buffer + while (remaining_samples > 0) { // Get Samples per frame (for this frame number) int samples_per_frame = Frame::GetSamplesPerFrame(starting_frame_number, info.fps, info.sample_rate, info.channels); @@ -1718,7 +1669,7 @@ void FFmpegReader::ProcessAudioPacket(int64_t requested_frame, int64_t target_fr // Remove audio frame from list of processing audio frames { - const GenericScopedLock lock(processingCriticalSection); + const GenericScopedLock lock(processingCriticalSection); // Update all frames as completed for (int64_t f = target_frame; f < starting_frame_number; f++) { // Remove the frame # from the processing list. NOTE: If more than one thread is @@ -1747,10 +1698,8 @@ void FFmpegReader::ProcessAudioPacket(int64_t requested_frame, int64_t target_fr } - // Seek to a specific frame. This is not always frame accurate, it's more of an estimation on many codecs. -void FFmpegReader::Seek(int64_t requested_frame) -{ +void FFmpegReader::Seek(int64_t requested_frame) { // Adjust for a requested frame that is too small or too large if (requested_frame < 1) requested_frame = 1; @@ -1760,7 +1709,7 @@ void FFmpegReader::Seek(int64_t requested_frame) int processing_video_frames_size = 0; int processing_audio_frames_size = 0; { - const GenericScopedLock lock(processingCriticalSection); + const GenericScopedLock lock(processingCriticalSection); processing_video_frames_size = processing_video_frames.size(); processing_audio_frames_size = processing_audio_frames.size(); } @@ -1771,7 +1720,7 @@ void FFmpegReader::Seek(int64_t requested_frame) // Wait for any processing frames to complete while (processing_video_frames_size + processing_audio_frames_size > 0) { usleep(2500); - const GenericScopedLock lock(processingCriticalSection); + const GenericScopedLock lock(processingCriticalSection); processing_video_frames_size = processing_video_frames.size(); processing_audio_frames_size = processing_audio_frames.size(); } @@ -1782,7 +1731,7 @@ void FFmpegReader::Seek(int64_t requested_frame) // Clear processed lists { - const GenericScopedLock lock(processingCriticalSection); + const GenericScopedLock lock(processingCriticalSection); processing_audio_frames.clear(); processing_video_frames.clear(); processed_video_frames.clear(); @@ -1809,8 +1758,7 @@ void FFmpegReader::Seek(int64_t requested_frame) // If seeking near frame 1, we need to close and re-open the file (this is more reliable than seeking) int buffer_amount = max(OPEN_MP_NUM_PROCESSORS, 8); - if (requested_frame - buffer_amount < 20) - { + if (requested_frame - buffer_amount < 20) { // Close and re-open file (basically seeking to frame 1) Close(); Open(); @@ -1828,21 +1776,18 @@ void FFmpegReader::Seek(int64_t requested_frame) } seek_audio_frame_found = 0; // used to detect which frames to throw away after a seek seek_video_frame_found = 0; // used to detect which frames to throw away after a seek - } - else - { + + } else { // Seek to nearest key-frame (aka, i-frame) bool seek_worked = false; int64_t seek_target = 0; // Seek video stream (if any) - if (!seek_worked && info.has_video) - { + if (!seek_worked && info.has_video) { seek_target = ConvertFrameToVideoPTS(requested_frame - buffer_amount); if (av_seek_frame(pFormatCtx, info.video_stream_index, seek_target, AVSEEK_FLAG_BACKWARD) < 0) { fprintf(stderr, "%s: error while seeking video stream\n", pFormatCtx->AV_FILENAME); - } else - { + } else { // VIDEO SEEK is_video_seek = true; seek_worked = true; @@ -1850,13 +1795,11 @@ void FFmpegReader::Seek(int64_t requested_frame) } // Seek audio stream (if not already seeked... and if an audio stream is found) - if (!seek_worked && info.has_audio) - { + if (!seek_worked && info.has_audio) { seek_target = ConvertFrameToAudioPTS(requested_frame - buffer_amount); if (av_seek_frame(pFormatCtx, info.audio_stream_index, seek_target, AVSEEK_FLAG_BACKWARD) < 0) { fprintf(stderr, "%s: error while seeking audio stream\n", pFormatCtx->AV_FILENAME); - } else - { + } else { // AUDIO SEEK is_video_seek = false; seek_worked = true; @@ -1864,8 +1807,7 @@ void FFmpegReader::Seek(int64_t requested_frame) } // Was the seek successful? - if (seek_worked) - { + if (seek_worked) { // Flush audio buffer if (info.has_audio) avcodec_flush_buffers(aCodecCtx); @@ -1888,9 +1830,7 @@ void FFmpegReader::Seek(int64_t requested_frame) seek_audio_frame_found = 0; // used to detect which frames to throw away after a seek seek_video_frame_found = 0; // used to detect which frames to throw away after a seek - } - else - { + } else { // seek failed is_seeking = false; seeking_pts = 0; @@ -1912,10 +1852,9 @@ void FFmpegReader::Seek(int64_t requested_frame) } // Get the PTS for the current video packet -int64_t FFmpegReader::GetVideoPTS() -{ +int64_t FFmpegReader::GetVideoPTS() { int64_t current_pts = 0; - if(packet->dts != AV_NOPTS_VALUE) + if (packet->dts != AV_NOPTS_VALUE) current_pts = packet->dts; // Return adjusted PTS @@ -1923,11 +1862,9 @@ int64_t FFmpegReader::GetVideoPTS() } // Update PTS Offset (if any) -void FFmpegReader::UpdatePTSOffset(bool is_video) -{ +void FFmpegReader::UpdatePTSOffset(bool is_video) { // Determine the offset between the PTS and Frame number (only for 1st frame) - if (is_video) - { + if (is_video) { // VIDEO PACKET if (video_pts_offset == 99999) // Has the offset been set yet? { @@ -1937,9 +1874,7 @@ void FFmpegReader::UpdatePTSOffset(bool is_video) // debug output ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::UpdatePTSOffset (Video)", "video_pts_offset", video_pts_offset, "is_video", is_video, "", -1, "", -1, "", -1, "", -1); } - } - else - { + } else { // AUDIO PACKET if (audio_pts_offset == 99999) // Has the offset been set yet? { @@ -1953,8 +1888,7 @@ void FFmpegReader::UpdatePTSOffset(bool is_video) } // Convert PTS into Frame Number -int64_t FFmpegReader::ConvertVideoPTStoFrame(int64_t pts) -{ +int64_t FFmpegReader::ConvertVideoPTStoFrame(int64_t pts) { // Apply PTS offset pts = pts + video_pts_offset; int64_t previous_video_frame = current_video_frame; @@ -1974,10 +1908,10 @@ int64_t FFmpegReader::ConvertVideoPTStoFrame(int64_t pts) if (frame == previous_video_frame) { // return -1 frame number frame = -1; - } - else + } else { // Increment expected frame current_video_frame++; + } if (current_video_frame < frame) // has missing frames @@ -1985,7 +1919,7 @@ int64_t FFmpegReader::ConvertVideoPTStoFrame(int64_t pts) // Sometimes frames are missing due to varying timestamps, or they were dropped. Determine // if we are missing a video frame. - const GenericScopedLock lock(processingCriticalSection); + const GenericScopedLock lock(processingCriticalSection); while (current_video_frame < frame) { if (!missing_video_frames.count(current_video_frame)) { ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ConvertVideoPTStoFrame (tracking missing frame)", "current_video_frame", current_video_frame, "previous_video_frame", previous_video_frame, "", -1, "", -1, "", -1, "", -1); @@ -2006,8 +1940,7 @@ int64_t FFmpegReader::ConvertVideoPTStoFrame(int64_t pts) } // Convert Frame Number into Video PTS -int64_t FFmpegReader::ConvertFrameToVideoPTS(int64_t frame_number) -{ +int64_t FFmpegReader::ConvertFrameToVideoPTS(int64_t frame_number) { // Get timestamp of this frame (in seconds) double seconds = double(frame_number) / info.fps.ToDouble(); @@ -2019,8 +1952,7 @@ int64_t FFmpegReader::ConvertFrameToVideoPTS(int64_t frame_number) } // Convert Frame Number into Video PTS -int64_t FFmpegReader::ConvertFrameToAudioPTS(int64_t frame_number) -{ +int64_t FFmpegReader::ConvertFrameToAudioPTS(int64_t frame_number) { // Get timestamp of this frame (in seconds) double seconds = double(frame_number) / info.fps.ToDouble(); @@ -2032,8 +1964,7 @@ int64_t FFmpegReader::ConvertFrameToAudioPTS(int64_t frame_number) } // Calculate Starting video frame and sample # for an audio PTS -AudioLocation FFmpegReader::GetAudioPTSLocation(int64_t pts) -{ +AudioLocation FFmpegReader::GetAudioPTSLocation(int64_t pts) { // Apply PTS offset pts = pts + audio_pts_offset; @@ -2066,8 +1997,7 @@ AudioLocation FFmpegReader::GetAudioPTSLocation(int64_t pts) // Compare to previous audio packet (and fix small gaps due to varying PTS timestamps) if (previous_packet_location.frame != -1) { - if (location.is_near(previous_packet_location, samples_per_frame, samples_per_frame)) - { + if (location.is_near(previous_packet_location, samples_per_frame, samples_per_frame)) { int64_t orig_frame = location.frame; int orig_start = location.sample_start; @@ -2082,7 +2012,7 @@ AudioLocation FFmpegReader::GetAudioPTSLocation(int64_t pts) // Debug output ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAudioPTSLocation (Audio Gap Ignored - too big)", "Previous location frame", previous_packet_location.frame, "Target Frame", location.frame, "Target Audio Sample", location.sample_start, "pts", pts, "", -1, "", -1); - const GenericScopedLock lock(processingCriticalSection); + const GenericScopedLock lock(processingCriticalSection); for (int64_t audio_frame = previous_packet_location.frame; audio_frame < location.frame; audio_frame++) { if (!missing_audio_frames.count(audio_frame)) { ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAudioPTSLocation (tracking missing frame)", "missing_audio_frame", audio_frame, "previous_audio_frame", previous_packet_location.frame, "new location frame", location.frame, "", -1, "", -1, "", -1); @@ -2100,12 +2030,10 @@ AudioLocation FFmpegReader::GetAudioPTSLocation(int64_t pts) } // Create a new Frame (or return an existing one) and add it to the working queue. -std::shared_ptr FFmpegReader::CreateFrame(int64_t requested_frame) -{ +std::shared_ptr FFmpegReader::CreateFrame(int64_t requested_frame) { // Check working cache std::shared_ptr output = working_cache.GetFrame(requested_frame); - if (!output) - { + if (!output) { // Create a new frame on the working cache output = std::make_shared(requested_frame, info.width, info.height, "#000000", Frame::GetSamplesPerFrame(requested_frame, info.fps, info.sample_rate, info.channels), info.channels); output->SetPixelRatio(info.pixel_ratio.num, info.pixel_ratio.den); // update pixel ratio @@ -2129,20 +2057,21 @@ bool FFmpegReader::IsPartialFrame(int64_t requested_frame) { // Sometimes a seek gets partial frames, and we need to remove them bool seek_trash = false; int64_t max_seeked_frame = seek_audio_frame_found; // determine max seeked frame - if (seek_video_frame_found > max_seeked_frame) + if (seek_video_frame_found > max_seeked_frame) { max_seeked_frame = seek_video_frame_found; + } if ((info.has_audio && seek_audio_frame_found && max_seeked_frame >= requested_frame) || - (info.has_video && seek_video_frame_found && max_seeked_frame >= requested_frame)) - seek_trash = true; + (info.has_video && seek_video_frame_found && max_seeked_frame >= requested_frame)) { + seek_trash = true; + } return seek_trash; } // Check if a frame is missing and attempt to replace it's frame image (and -bool FFmpegReader::CheckMissingFrame(int64_t requested_frame) -{ +bool FFmpegReader::CheckMissingFrame(int64_t requested_frame) { // Lock - const GenericScopedLock lock(processingCriticalSection); + const GenericScopedLock lock(processingCriticalSection); // Init # of times this frame has been checked so far int checked_count = 0; @@ -2171,9 +2100,9 @@ bool FFmpegReader::CheckMissingFrame(int64_t requested_frame) if (checked_count > 8 && !missing_video_frames.count(requested_frame) && !processing_audio_frames.count(requested_frame) && processed_audio_frames.count(requested_frame) && last_frame && last_video_frame->has_image_data && aCodecId == AV_CODEC_ID_MP3 && (vCodecId == AV_CODEC_ID_MJPEGB || vCodecId == AV_CODEC_ID_MJPEG)) { - missing_video_frames.insert(pair(requested_frame, last_video_frame->number)); - missing_video_frames_source.insert(pair(last_video_frame->number, requested_frame)); - missing_frames.Add(last_video_frame); + missing_video_frames.insert(pair(requested_frame, last_video_frame->number)); + missing_video_frames_source.insert(pair(last_video_frame->number, requested_frame)); + missing_frames.Add(last_video_frame); } } @@ -2238,8 +2167,7 @@ bool FFmpegReader::CheckMissingFrame(int64_t requested_frame) } // Check the working queue, and move finished frames to the finished queue -void FFmpegReader::CheckWorkingFrames(bool end_of_stream, int64_t requested_frame) -{ +void FFmpegReader::CheckWorkingFrames(bool end_of_stream, int64_t requested_frame) { // Loop through all working queue frames bool checked_count_tripped = false; int max_checked_count = 80; @@ -2247,8 +2175,7 @@ void FFmpegReader::CheckWorkingFrames(bool end_of_stream, int64_t requested_fram // Check if requested frame is 'missing' CheckMissingFrame(requested_frame); - while (true) - { + while (true) { // Get the front frame of working cache std::shared_ptr f(working_cache.GetSmallestFrame()); @@ -2272,7 +2199,7 @@ void FFmpegReader::CheckWorkingFrames(bool end_of_stream, int64_t requested_fram bool is_video_ready = false; bool is_audio_ready = false; { // limit scope of next few lines - const GenericScopedLock lock(processingCriticalSection); + const GenericScopedLock lock(processingCriticalSection); is_video_ready = processed_video_frames.count(f->number); is_audio_ready = processed_audio_frames.count(f->number); @@ -2317,13 +2244,11 @@ void FFmpegReader::CheckWorkingFrames(bool end_of_stream, int64_t requested_fram ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::CheckWorkingFrames", "requested_frame", requested_frame, "frame_number", f->number, "is_video_ready", is_video_ready, "is_audio_ready", is_audio_ready, "checked_count", checked_count, "checked_frames_size", checked_frames_size); // Check if working frame is final - if ((!end_of_stream && is_video_ready && is_audio_ready) || end_of_stream || is_seek_trash) - { + if ((!end_of_stream && is_video_ready && is_audio_ready) || end_of_stream || is_seek_trash) { // Debug output ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::CheckWorkingFrames (mark frame as final)", "requested_frame", requested_frame, "f->number", f->number, "is_seek_trash", is_seek_trash, "Working Cache Count", working_cache.Count(), "Final Cache Count", final_cache.Count(), "end_of_stream", end_of_stream); - if (!is_seek_trash) - { + if (!is_seek_trash) { // Add missing image (if needed - sometimes end_of_stream causes frames with only audio) if (info.has_video && !is_video_ready && last_video_frame) // Copy image from last frame @@ -2337,7 +2262,7 @@ void FFmpegReader::CheckWorkingFrames(bool end_of_stream, int64_t requested_fram // Add to missing cache (if another frame depends on it) { - const GenericScopedLock lock(processingCriticalSection); + const GenericScopedLock lock(processingCriticalSection); if (missing_video_frames_source.count(f->number)) { // Debug output ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::CheckWorkingFrames (add frame to missing cache)", "f->number", f->number, "is_seek_trash", is_seek_trash, "Missing Cache Count", missing_frames.Count(), "Working Cache Count", working_cache.Count(), "Final Cache Count", final_cache.Count(), "", -1); @@ -2358,16 +2283,16 @@ void FFmpegReader::CheckWorkingFrames(bool end_of_stream, int64_t requested_fram // Seek trash, so delete the frame from the working cache, and never add it to the final cache. working_cache.Remove(f->number); } - } - else + + } else { // Stop looping break; + } } } // Check for the correct frames per second (FPS) value by scanning the 1st few seconds of video packets. -void FFmpegReader::CheckFPS() -{ +void FFmpegReader::CheckFPS() { check_fps = true; @@ -2380,19 +2305,16 @@ void FFmpegReader::CheckFPS() int64_t pts = 0; // Loop through the stream - while (true) - { + while (true) { // Get the next packet (if any) if (GetNextPacket() < 0) // Break loop when no more packets found break; // Video packet - if (packet->stream_index == videoStream) - { + if (packet->stream_index == videoStream) { // Check if the AVFrame is finished and set it - if (GetAVFrame()) - { + if (GetAVFrame()) { // Update PTS / Frame Offset (if any) UpdatePTSOffset(true); @@ -2467,13 +2389,11 @@ void FFmpegReader::CheckFPS() } // Remove AVFrame from cache (and deallocate it's memory) -void FFmpegReader::RemoveAVFrame(AVFrame* remove_frame) -{ +void FFmpegReader::RemoveAVFrame(AVFrame *remove_frame) { // Remove pFrame (if exists) - if (remove_frame) - { + if (remove_frame) { // Free memory - #pragma omp critical (packet_cache) +#pragma omp critical (packet_cache) { av_freep(&remove_frame->data[0]); #ifndef WIN32 @@ -2484,8 +2404,7 @@ void FFmpegReader::RemoveAVFrame(AVFrame* remove_frame) } // Remove AVPacket from cache (and deallocate it's memory) -void FFmpegReader::RemoveAVPacket(AVPacket* remove_packet) -{ +void FFmpegReader::RemoveAVPacket(AVPacket *remove_packet) { // deallocate memory for packet AV_FREE_PACKET(remove_packet); @@ -2494,14 +2413,12 @@ void FFmpegReader::RemoveAVPacket(AVPacket* remove_packet) } /// Get the smallest video frame that is still being processed -int64_t FFmpegReader::GetSmallestVideoFrame() -{ +int64_t FFmpegReader::GetSmallestVideoFrame() { // Loop through frame numbers map::iterator itr; int64_t smallest_frame = -1; - const GenericScopedLock lock(processingCriticalSection); - for(itr = processing_video_frames.begin(); itr != processing_video_frames.end(); ++itr) - { + const GenericScopedLock lock(processingCriticalSection); + for (itr = processing_video_frames.begin(); itr != processing_video_frames.end(); ++itr) { if (itr->first < smallest_frame || smallest_frame == -1) smallest_frame = itr->first; } @@ -2511,14 +2428,12 @@ int64_t FFmpegReader::GetSmallestVideoFrame() } /// Get the smallest audio frame that is still being processed -int64_t FFmpegReader::GetSmallestAudioFrame() -{ +int64_t FFmpegReader::GetSmallestAudioFrame() { // Loop through frame numbers map::iterator itr; int64_t smallest_frame = -1; - const GenericScopedLock lock(processingCriticalSection); - for(itr = processing_audio_frames.begin(); itr != processing_audio_frames.end(); ++itr) - { + const GenericScopedLock lock(processingCriticalSection); + for (itr = processing_audio_frames.begin(); itr != processing_audio_frames.end(); ++itr) { if (itr->first < smallest_frame || smallest_frame == -1) smallest_frame = itr->first; } @@ -2552,18 +2467,16 @@ void FFmpegReader::SetJson(string value) { // Parse JSON string into JSON objects Json::Value root; Json::Reader reader; - bool success = reader.parse( value, root ); + bool success = reader.parse(value, root); if (!success) // Raise exception throw InvalidJSON("JSON could not be parsed (or is invalid)", ""); - try - { + try { // Set all values that match SetJsonValue(root); } - catch (exception e) - { + catch (exception e) { // Error parsing JSON (or missing keys) throw InvalidJSON("JSON is invalid (missing keys or invalid data types)", ""); } @@ -2580,8 +2493,7 @@ void FFmpegReader::SetJsonValue(Json::Value root) { path = root["path"].asString(); // Re-Open path, and re-init everything (if needed) - if (is_open) - { + if (is_open) { Close(); Open(); } From 7930b289dcda88fd078ba01fa1ea9cf14d02ab1a Mon Sep 17 00:00:00 2001 From: "FeRD (Frank Dana)" Date: Thu, 18 Apr 2019 07:51:03 -0400 Subject: [PATCH 142/223] Update Python install path detection This changes the `PYTHON_MODULE_PATH` handling in two ways: * Makes it a cache variable, so detection is only run once and only if it's not already set. This allows it to be overridden on the command line, e.g. `cmake -DPYTHON_MODULE_PATH:PATH=lib/python3.6/dist-packages` * Uses the presence of the `pybuild` executable to sense for a Debian-derived system (only on UNIX AND NOT APPLE), and if found it falls back to the old `getsitepackages()[0]` path extraction method. --- src/bindings/python/CMakeLists.txt | 37 ++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/src/bindings/python/CMakeLists.txt b/src/bindings/python/CMakeLists.txt index 3418d2de..08182d95 100644 --- a/src/bindings/python/CMakeLists.txt +++ b/src/bindings/python/CMakeLists.txt @@ -66,17 +66,36 @@ if (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) ${PYTHON_LIBRARIES} openshot) ### FIND THE PYTHON INTERPRETER (AND THE SITE PACKAGES FOLDER) - execute_process ( COMMAND ${PYTHON_EXECUTABLE} -c "\ + if (UNIX AND NOT APPLE) + ### Special-case for Debian's crazy, by checking to see if pybuild + ### is available. We don't use it, except as a canary in a coal mine + find_program(PYBUILD_EXECUTABLE pybuild + DOC "Path to Debian's pybuild utility") + if (PYBUILD_EXECUTABLE) + # We're on a Debian derivative, fall back to old path detection + set(py_detection "import site; print(site.getsitepackages()[0])") + else() + # Use distutils to detect install path + set (py_detection "\ from distutils.sysconfig import get_python_lib; \ -print( get_python_lib( plat_specific=True, prefix='${CMAKE_INSTALL_PREFIX}' ) )" - OUTPUT_VARIABLE _ABS_PYTHON_MODULE_PATH - OUTPUT_STRIP_TRAILING_WHITESPACE ) +print( get_python_lib( plat_specific=True, prefix='${CMAKE_INSTALL_PREFIX}' ) )") + endif() + endif() - GET_FILENAME_COMPONENT(_ABS_PYTHON_MODULE_PATH - "${_ABS_PYTHON_MODULE_PATH}" ABSOLUTE) - FILE(RELATIVE_PATH _REL_PYTHON_MODULE_PATH - ${CMAKE_INSTALL_PREFIX} ${_ABS_PYTHON_MODULE_PATH}) - SET(PYTHON_MODULE_PATH ${_REL_PYTHON_MODULE_PATH}) + if (NOT PYTHON_MODULE_PATH) + execute_process ( COMMAND ${PYTHON_EXECUTABLE} -c "${py_detection}" + OUTPUT_VARIABLE _ABS_PYTHON_MODULE_PATH + OUTPUT_STRIP_TRAILING_WHITESPACE ) + + GET_FILENAME_COMPONENT(_ABS_PYTHON_MODULE_PATH + "${_ABS_PYTHON_MODULE_PATH}" ABSOLUTE) + FILE(RELATIVE_PATH _REL_PYTHON_MODULE_PATH + ${CMAKE_INSTALL_PREFIX} ${_ABS_PYTHON_MODULE_PATH}) + SET(PYTHON_MODULE_PATH ${_REL_PYTHON_MODULE_PATH} + CACHE PATH "Install path for Python modules (relative to prefix)") + endif() + + message(STATUS "Will install Python module to: ${PYTHON_MODULE_PATH}") ############### INSTALL HEADERS & LIBRARY ################ ### Install Python bindings From dc4d687e712e1ca96035624c4768792cff1ed60f Mon Sep 17 00:00:00 2001 From: Frank Dana Date: Thu, 18 Apr 2019 09:26:20 -0400 Subject: [PATCH 143/223] Travis CI: Also run `make install` Taking my own suggestion, this adds a `make install` step to all three (overkill, maybe?) Travis builds, using `DESTDIR=dist/` to keep the files local to the build dir. Meant to bring the CMake-managed install logic under CI verification. --- .travis.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4afd8467..2e7db59d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,8 @@ matrix: - cmake -D"CMAKE_BUILD_TYPE:STRING=Debug" ../ - make VERBOSE=1 - make os_test - + - make install DESTDIR=dist/ + - language: cpp name: "FFmpeg 3" before_script: @@ -31,7 +32,8 @@ matrix: - cmake -D"CMAKE_BUILD_TYPE:STRING=Debug" ../ - make VERBOSE=1 - make os_test - + - make install DESTDIR=dist/ + - language: cpp name: "FFmpeg 4" before_script: @@ -48,3 +50,4 @@ matrix: - cmake -D"CMAKE_BUILD_TYPE:STRING=Debug" ../ - make VERBOSE=1 - make os_test + - make install DESTDIR=dist/ From b3f5406db38c25f3bfff4c9bb408df9880810c6e Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Thu, 18 Apr 2019 14:04:37 -0500 Subject: [PATCH 144/223] More code reformatting on FFmpegWriter.h/.cpp --- include/FFmpegWriter.h | 2 +- include/OpenMPUtilities.h | 1 - src/FFmpegWriter.cpp | 753 +++++++++++++++++--------------------- 3 files changed, 344 insertions(+), 412 deletions(-) diff --git a/include/FFmpegWriter.h b/include/FFmpegWriter.h index b93ef7b3..35dd1ed9 100644 --- a/include/FFmpegWriter.h +++ b/include/FFmpegWriter.h @@ -233,7 +233,7 @@ namespace openshot { void process_video_packet(std::shared_ptr frame); /// write all queued frames' audio to the video file - void write_audio_packets(bool final); + void write_audio_packets(bool is_final); /// write video frame bool write_video_packet(std::shared_ptr frame, AVFrame *frame_final); diff --git a/include/OpenMPUtilities.h b/include/OpenMPUtilities.h index 0411b6ba..7c198a76 100644 --- a/include/OpenMPUtilities.h +++ b/include/OpenMPUtilities.h @@ -42,5 +42,4 @@ using namespace openshot; #define FF_NUM_PROCESSORS (min(omp_get_num_procs(), max(2, openshot::Settings::Instance()->FF_THREADS) )) - #endif diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index 1c9094a6..e12ff094 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -83,8 +83,7 @@ FFmpegWriter::FFmpegWriter(string path) : initial_audio_input_frame_size(0), img_convert_ctx(NULL), cache_size(8), num_of_rescalers(32), rescaler_position(0), video_codec(NULL), audio_codec(NULL), is_writing(false), write_video_count(0), write_audio_count(0), original_sample_rate(0), original_channels(0), avr(NULL), avr_planar(NULL), is_open(false), prepare_streams(false), - write_header(false), write_trailer(false), audio_encoder_buffer_size(0), audio_encoder_buffer(NULL) -{ + write_header(false), write_trailer(false), audio_encoder_buffer_size(0), audio_encoder_buffer(NULL) { // Disable audio & video (so they can be independently enabled) info.has_audio = false; @@ -98,9 +97,8 @@ FFmpegWriter::FFmpegWriter(string path) : } // Open the writer -void FFmpegWriter::Open() -{ - if (!is_open) { +void FFmpegWriter::Open() { + if (!is_open) { // Open the writer is_open = true; @@ -121,8 +119,7 @@ void FFmpegWriter::Open() } // auto detect format (from path) -void FFmpegWriter::auto_detect_format() -{ +void FFmpegWriter::auto_detect_format() { // Auto detect the output format from the name. default is mpeg. fmt = av_guess_format(NULL, path.c_str(), NULL); if (!fmt) @@ -147,8 +144,7 @@ void FFmpegWriter::auto_detect_format() } // initialize streams -void FFmpegWriter::initialize_streams() -{ +void FFmpegWriter::initialize_streams() { ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::initialize_streams", "fmt->video_codec", fmt->video_codec, "fmt->audio_codec", fmt->audio_codec, "AV_CODEC_ID_NONE", AV_CODEC_ID_NONE, "", -1, "", -1, "", -1); // Add the audio and video streams using the default format codecs and initialize the codecs @@ -164,14 +160,12 @@ void FFmpegWriter::initialize_streams() } // Set video export options -void FFmpegWriter::SetVideoOptions(bool has_video, string codec, Fraction fps, int width, int height, Fraction pixel_ratio, bool interlaced, bool top_field_first, int bit_rate) -{ +void FFmpegWriter::SetVideoOptions(bool has_video, string codec, Fraction fps, int width, int height, Fraction pixel_ratio, bool interlaced, bool top_field_first, int bit_rate) { // Set the video options - if (codec.length() > 0) - { + if (codec.length() > 0) { AVCodec *new_codec; // Check if the codec selected is a hardware accelerated codec - #if IS_FFMPEG_3_2 +#if IS_FFMPEG_3_2 #if defined(__linux__) if ( (strcmp(codec.c_str(),"h264_vaapi") == 0)) { new_codec = avcodec_find_encoder_by_name(codec.c_str()); @@ -194,7 +188,7 @@ void FFmpegWriter::SetVideoOptions(bool has_video, string codec, Fraction fps, i hw_en_supported = 0; } } - #elif defined(_WIN32) +#elif defined(_WIN32) if ( (strcmp(codec.c_str(),"h264_dxva2") == 0)) { new_codec = avcodec_find_encoder_by_name(codec.c_str()); hw_en_on = 1; @@ -216,7 +210,7 @@ void FFmpegWriter::SetVideoOptions(bool has_video, string codec, Fraction fps, i hw_en_supported = 0; } } - #elif defined(__APPLE__) +#elif defined(__APPLE__) if ( (strcmp(codec.c_str(),"h264_qsv") == 0)) { new_codec = avcodec_find_encoder_by_name(codec.c_str()); hw_en_on = 1; @@ -229,12 +223,12 @@ void FFmpegWriter::SetVideoOptions(bool has_video, string codec, Fraction fps, i hw_en_on = 0; hw_en_supported = 0; } - #else // is FFmpeg 3 but not linux +#else // is FFmpeg 3 but not linux new_codec = avcodec_find_encoder_by_name(codec.c_str()); - #endif //__linux__ - #else // not ffmpeg 3 +#endif //__linux__ +#else // not ffmpeg 3 new_codec = avcodec_find_encoder_by_name(codec.c_str()); - #endif //IS_FFMPEG_3_2 +#endif //IS_FFMPEG_3_2 if (new_codec == NULL) throw InvalidCodec("A valid video codec could not be found for this file.", path); else { @@ -245,8 +239,7 @@ void FFmpegWriter::SetVideoOptions(bool has_video, string codec, Fraction fps, i fmt->video_codec = new_codec->id; } } - if (fps.num > 0) - { + if (fps.num > 0) { // Set frames per second (if provided) info.fps.num = fps.num; info.fps.den = fps.den; @@ -259,14 +252,13 @@ void FFmpegWriter::SetVideoOptions(bool has_video, string codec, Fraction fps, i info.width = width; if (height >= 1) info.height = height; - if (pixel_ratio.num > 0) - { + if (pixel_ratio.num > 0) { info.pixel_ratio.num = pixel_ratio.num; info.pixel_ratio.den = pixel_ratio.den; } - if (bit_rate >= 1000) // bit_rate is the bitrate in b/s + if (bit_rate >= 1000) // bit_rate is the bitrate in b/s info.video_bit_rate = bit_rate; - if ((bit_rate >= 0) && (bit_rate < 64) ) // bit_rate is the bitrate in crf + if ((bit_rate >= 0) && (bit_rate < 64)) // bit_rate is the bitrate in crf info.video_bit_rate = bit_rate; info.interlaced_frame = interlaced; @@ -289,16 +281,13 @@ void FFmpegWriter::SetVideoOptions(bool has_video, string codec, Fraction fps, i } // Set audio export options -void FFmpegWriter::SetAudioOptions(bool has_audio, string codec, int sample_rate, int channels, ChannelLayout channel_layout, int bit_rate) -{ +void FFmpegWriter::SetAudioOptions(bool has_audio, string codec, int sample_rate, int channels, ChannelLayout channel_layout, int bit_rate) { // Set audio options - if (codec.length() > 0) - { + if (codec.length() > 0) { AVCodec *new_codec = avcodec_find_encoder_by_name(codec.c_str()); if (new_codec == NULL) throw InvalidCodec("A valid audio codec could not be found for this file.", path); - else - { + else { // Set audio codec info.acodec = new_codec->name; @@ -327,8 +316,7 @@ void FFmpegWriter::SetAudioOptions(bool has_audio, string codec, int sample_rate } // Set custom options (some codecs accept additional params) -void FFmpegWriter::SetOption(StreamType stream, string name, string value) -{ +void FFmpegWriter::SetOption(StreamType stream, string name, string value) { // Declare codec context AVCodecContext *c = NULL; AVStream *st = NULL; @@ -338,13 +326,11 @@ void FFmpegWriter::SetOption(StreamType stream, string name, string value) st = video_st; // Get codec context c = AV_GET_CODEC_PAR_CONTEXT(st, video_codec); - } - else if (info.has_audio && stream == AUDIO_STREAM && audio_st) { + } else if (info.has_audio && stream == AUDIO_STREAM && audio_st) { st = audio_st; // Get codec context c = AV_GET_CODEC_PAR_CONTEXT(st, audio_codec); - } - else + } else throw NoStreamsFound("The stream was not found. Be sure to call PrepareStreams() first.", path); // Init AVOption @@ -357,9 +343,8 @@ void FFmpegWriter::SetOption(StreamType stream, string name, string value) // Was option found? if (option || (name == "g" || name == "qmin" || name == "qmax" || name == "max_b_frames" || name == "mb_decision" || - name == "level" || name == "profile" || name == "slices" || name == "rc_min_rate" || name == "rc_max_rate" || - name == "crf")) - { + name == "level" || name == "profile" || name == "slices" || name == "rc_min_rate" || name == "rc_max_rate" || + name == "crf")) { // Check for specific named options if (name == "g") // Set gop_size @@ -409,81 +394,79 @@ void FFmpegWriter::SetOption(StreamType stream, string name, string value) // encode quality and special settings like lossless // This might be better in an extra methods as more options // and way to set quality are possible - #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 39, 101) - #if IS_FFMPEG_3_2 - if (hw_en_on) { - double mbs = 15000000.0; - if (info.video_bit_rate > 0) { - if (info.video_bit_rate > 42) { - mbs = 380000.0; - } - else { - mbs *= pow(0.912,info.video_bit_rate); - } +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 39, 101) +#if IS_FFMPEG_3_2 + if (hw_en_on) { + double mbs = 15000000.0; + if (info.video_bit_rate > 0) { + if (info.video_bit_rate > 42) { + mbs = 380000.0; } - c->bit_rate = (int)(mbs); - } else - #endif - { - switch (c->codec_id) { - #if (LIBAVCODEC_VERSION_MAJOR >= 58) - case AV_CODEC_ID_AV1 : - c->bit_rate = 0; - av_opt_set_int(c->priv_data, "crf", min(stoi(value),63), 0); - break; - #endif - case AV_CODEC_ID_VP8 : - c->bit_rate = 10000000; - av_opt_set_int(c->priv_data, "crf", max(min(stoi(value),63),4), 0); // 4-63 - break; - case AV_CODEC_ID_VP9 : - c->bit_rate = 0; // Must be zero! - av_opt_set_int(c->priv_data, "crf", min(stoi(value),63), 0); // 0-63 - if (stoi(value) == 0) { - av_opt_set(c->priv_data, "preset", "veryslow", 0); - av_opt_set_int(c->priv_data, "lossless", 1, 0); - } - break; - case AV_CODEC_ID_H264 : - av_opt_set_int(c->priv_data, "crf", min(stoi(value),51), 0); // 0-51 - if (stoi(value) == 0) { - av_opt_set(c->priv_data, "preset", "veryslow", 0); - } - break; - case AV_CODEC_ID_H265 : - av_opt_set_int(c->priv_data, "crf", min(stoi(value),51), 0); // 0-51 - if (stoi(value) == 0) { - av_opt_set(c->priv_data, "preset", "veryslow", 0); - av_opt_set_int(c->priv_data, "lossless", 1, 0); - } - break; - default: - // If this codec doesn't support crf calculate a bitrate - // TODO: find better formula - double mbs = 15000000.0; - if (info.video_bit_rate > 0) { - if (info.video_bit_rate > 42) { - mbs = 380000.0; - } - else { - mbs *= pow(0.912,info.video_bit_rate); - } - } - c->bit_rate = (int)(mbs); + else { + mbs *= pow(0.912,info.video_bit_rate); } } - #endif - } - - else + c->bit_rate = (int)(mbs); + } else +#endif + { + switch (c->codec_id) { +#if (LIBAVCODEC_VERSION_MAJOR >= 58) + case AV_CODEC_ID_AV1 : + c->bit_rate = 0; + av_opt_set_int(c->priv_data, "crf", min(stoi(value),63), 0); + break; +#endif + case AV_CODEC_ID_VP8 : + c->bit_rate = 10000000; + av_opt_set_int(c->priv_data, "crf", max(min(stoi(value), 63), 4), 0); // 4-63 + break; + case AV_CODEC_ID_VP9 : + c->bit_rate = 0; // Must be zero! + av_opt_set_int(c->priv_data, "crf", min(stoi(value), 63), 0); // 0-63 + if (stoi(value) == 0) { + av_opt_set(c->priv_data, "preset", "veryslow", 0); + av_opt_set_int(c->priv_data, "lossless", 1, 0); + } + break; + case AV_CODEC_ID_H264 : + av_opt_set_int(c->priv_data, "crf", min(stoi(value), 51), 0); // 0-51 + if (stoi(value) == 0) { + av_opt_set(c->priv_data, "preset", "veryslow", 0); + } + break; + case AV_CODEC_ID_H265 : + av_opt_set_int(c->priv_data, "crf", min(stoi(value), 51), 0); // 0-51 + if (stoi(value) == 0) { + av_opt_set(c->priv_data, "preset", "veryslow", 0); + av_opt_set_int(c->priv_data, "lossless", 1, 0); + } + break; + default: + // If this codec doesn't support crf calculate a bitrate + // TODO: find better formula + double mbs = 15000000.0; + if (info.video_bit_rate > 0) { + if (info.video_bit_rate > 42) { + mbs = 380000.0; + } else { + mbs *= pow(0.912, info.video_bit_rate); + } + } + c->bit_rate = (int) (mbs); + } + } +#endif + } else { // Set AVOption AV_OPTION_SET(st, c->priv_data, name.c_str(), value.c_str(), c); + } ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::SetOption (" + (string)name + ")", "stream == VIDEO_STREAM", stream == VIDEO_STREAM, "", -1, "", -1, "", -1, "", -1, "", -1); - } - else + } else { throw InvalidOptions("The option is not valid for this codec.", path); + } } @@ -500,8 +483,7 @@ bool FFmpegWriter::IsValidCodec(string codec_name) { } // Prepare & initialize streams and open codecs -void FFmpegWriter::PrepareStreams() -{ +void FFmpegWriter::PrepareStreams() { if (!info.has_audio && !info.has_video) throw InvalidOptions("No video or audio options have been set. You must set has_video or has_audio (or both).", path); @@ -515,8 +497,7 @@ void FFmpegWriter::PrepareStreams() } // Write the file header (after the options are set) -void FFmpegWriter::WriteHeader() -{ +void FFmpegWriter::WriteHeader() { if (!info.has_audio && !info.has_video) throw InvalidOptions("No video or audio options have been set. You must set has_video or has_audio (or both).", path); @@ -526,20 +507,19 @@ void FFmpegWriter::WriteHeader() throw InvalidFile("Could not open or write file.", path); } - // Force the output filename (which doesn't always happen for some reason) - snprintf(oc->AV_FILENAME, sizeof(oc->AV_FILENAME), "%s", path.c_str()); + // Force the output filename (which doesn't always happen for some reason) + snprintf(oc->AV_FILENAME, sizeof(oc->AV_FILENAME), "%s", path.c_str()); // Write the stream header, if any // TODO: add avoptions / parameters instead of NULL // Add general metadata (if any) - for(std::map::iterator iter = info.metadata.begin(); iter != info.metadata.end(); ++iter) - { + for (std::map::iterator iter = info.metadata.begin(); iter != info.metadata.end(); ++iter) { av_dict_set(&oc->metadata, iter->first.c_str(), iter->second.c_str(), 0); } if (avformat_write_header(oc, NULL) != 0) { - throw InvalidFile("Could not write header to file.", path); + throw InvalidFile("Could not write header to file.", path); }; // Mark as 'written' @@ -549,8 +529,7 @@ void FFmpegWriter::WriteHeader() } // Add a frame to the queue waiting to be encoded. -void FFmpegWriter::WriteFrame(std::shared_ptr frame) -{ +void FFmpegWriter::WriteFrame(std::shared_ptr frame) { // Check for open reader (or throw exception) if (!is_open) throw WriterClosed("The FFmpegWriter is closed. Call Open() before calling this method.", path); @@ -566,15 +545,13 @@ void FFmpegWriter::WriteFrame(std::shared_ptr frame) ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::WriteFrame", "frame->number", frame->number, "spooled_video_frames.size()", spooled_video_frames.size(), "spooled_audio_frames.size()", spooled_audio_frames.size(), "cache_size", cache_size, "is_writing", is_writing, "", -1); // Write the frames once it reaches the correct cache size - if (spooled_video_frames.size() == cache_size || spooled_audio_frames.size() == cache_size) - { + if (spooled_video_frames.size() == cache_size || spooled_audio_frames.size() == cache_size) { // Is writer currently writing? if (!is_writing) // Write frames to video file write_queued_frames(); - else - { + else { // Write frames to video file write_queued_frames(); } @@ -585,8 +562,7 @@ void FFmpegWriter::WriteFrame(std::shared_ptr frame) } // Write all frames in the queue to the video file. -void FFmpegWriter::write_queued_frames() -{ +void FFmpegWriter::write_queued_frames() { ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::write_queued_frames", "spooled_video_frames.size()", spooled_video_frames.size(), "spooled_audio_frames.size()", spooled_audio_frames.size(), "", -1, "", -1, "", -1, "", -1); // Flip writing flag @@ -608,17 +584,16 @@ void FFmpegWriter::write_queued_frames() // Create blank exception bool has_error_encoding_video = false; - #pragma omp parallel +#pragma omp parallel { - #pragma omp single +#pragma omp single { // Process all audio frames (in a separate thread) if (info.has_audio && audio_st && !queued_audio_frames.empty()) write_audio_packets(false); // Loop through each queued image frame - while (!queued_video_frames.empty()) - { + while (!queued_video_frames.empty()) { // Get front frame (from the queue) std::shared_ptr frame = queued_video_frames.front(); @@ -635,22 +610,19 @@ void FFmpegWriter::write_queued_frames() } // end while } // end omp single - #pragma omp single +#pragma omp single { // Loop back through the frames (in order), and write them to the video file - while (!processed_frames.empty()) - { + while (!processed_frames.empty()) { // Get front frame (from the queue) std::shared_ptr frame = processed_frames.front(); - if (info.has_video && video_st) - { + if (info.has_video && video_st) { // Add to deallocate queue (so we can remove the AVFrames when we are done) deallocate_frames.push_back(frame); // Does this frame's AVFrame still exist - if (av_frames.count(frame)) - { + if (av_frames.count(frame)) { // Get AVFrame AVFrame *frame_final = av_frames[frame]; @@ -666,14 +638,12 @@ void FFmpegWriter::write_queued_frames() } // Loop through, and deallocate AVFrames - while (!deallocate_frames.empty()) - { + while (!deallocate_frames.empty()) { // Get front frame (from the queue) std::shared_ptr frame = deallocate_frames.front(); // Does this frame's AVFrame still exist - if (av_frames.count(frame)) - { + if (av_frames.count(frame)) { // Get AVFrame AVFrame *av_frame = av_frames[frame]; @@ -700,13 +670,11 @@ void FFmpegWriter::write_queued_frames() } // Write a block of frames from a reader -void FFmpegWriter::WriteFrame(ReaderBase* reader, int64_t start, int64_t length) -{ +void FFmpegWriter::WriteFrame(ReaderBase *reader, int64_t start, int64_t length) { ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::WriteFrame (from Reader)", "start", start, "length", length, "", -1, "", -1, "", -1, "", -1); // Loop through each frame (and encoded it) - for (int64_t number = start; number <= length; number++) - { + for (int64_t number = start; number <= length; number++) { // Get the frame std::shared_ptr f = reader->GetFrame(number); @@ -716,8 +684,7 @@ void FFmpegWriter::WriteFrame(ReaderBase* reader, int64_t start, int64_t length) } // Write the file trailer (after all frames are written) -void FFmpegWriter::WriteTrailer() -{ +void FFmpegWriter::WriteTrailer() { // Write any remaining queued frames to video file write_queued_frames(); @@ -741,8 +708,7 @@ void FFmpegWriter::WriteTrailer() } // Flush encoders -void FFmpegWriter::flush_encoders() -{ +void FFmpegWriter::flush_encoders() { if (info.has_audio && audio_codec && AV_GET_CODEC_TYPE(audio_st) == AVMEDIA_TYPE_AUDIO && AV_GET_CODEC_ATTRIBUTES(audio_st, audio_codec)->frame_size <= 1) return; #if (LIBAVFORMAT_VERSION_MAJOR < 58) @@ -750,15 +716,15 @@ void FFmpegWriter::flush_encoders() return; #endif - int error_code = 0; - int stop_encoding = 1; + int error_code = 0; + int stop_encoding = 1; - // FLUSH VIDEO ENCODER - if (info.has_video) + // FLUSH VIDEO ENCODER + if (info.has_video) for (;;) { // Increment PTS (in frames and scaled to the codec's timebase) - write_video_count += av_rescale_q(1, (AVRational){info.fps.den, info.fps.num}, video_codec->time_base); + write_video_count += av_rescale_q(1, (AVRational) {info.fps.den, info.fps.num}, video_codec->time_base); AVPacket pkt; av_init_packet(&pkt); @@ -772,7 +738,7 @@ void FFmpegWriter::flush_encoders() int got_packet = 0; int error_code = 0; - #if IS_FFMPEG_3_2 +#if IS_FFMPEG_3_2 #pragma omp critical (write_video_packet) { // Encode video packet (latest version of FFmpeg) @@ -796,34 +762,35 @@ void FFmpegWriter::flush_encoders() error_code = av_interleaved_write_frame(oc, &pkt); } } - #else +#else // IS_FFMPEG_3_2 - #if LIBAVFORMAT_VERSION_MAJOR >= 54 - // Encode video packet (older than FFmpeg 3.2) - error_code = avcodec_encode_video2(video_codec, &pkt, NULL, &got_packet); +#if LIBAVFORMAT_VERSION_MAJOR >= 54 + // Encode video packet (older than FFmpeg 3.2) + error_code = avcodec_encode_video2(video_codec, &pkt, NULL, &got_packet); - #else - // Encode video packet (even older version of FFmpeg) - int video_outbuf_size = 0; +#else + // Encode video packet (even older version of FFmpeg) + int video_outbuf_size = 0; - /* encode the image */ - int out_size = avcodec_encode_video(video_codec, NULL, video_outbuf_size, NULL); + /* encode the image */ + int out_size = avcodec_encode_video(video_codec, NULL, video_outbuf_size, NULL); - /* if zero size, it means the image was buffered */ - if (out_size > 0) { - if(video_codec->coded_frame->key_frame) - pkt.flags |= AV_PKT_FLAG_KEY; - pkt.data= video_outbuf; - pkt.size= out_size; + /* if zero size, it means the image was buffered */ + if (out_size > 0) { + if(video_codec->coded_frame->key_frame) + pkt.flags |= AV_PKT_FLAG_KEY; + pkt.data= video_outbuf; + pkt.size= out_size; - // got data back (so encode this frame) - got_packet = 1; - } - #endif - #endif + // got data back (so encode this frame) + got_packet = 1; + } +#endif // LIBAVFORMAT_VERSION_MAJOR >= 54 +#endif // IS_FFMPEG_3_2 if (error_code < 0) { - ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::flush_encoders ERROR [" + (string)av_err2str(error_code) + "]", "error_code", error_code, "", -1, "", -1, "", -1, "", -1, "", -1); + ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::flush_encoders ERROR [" + (string) av_err2str(error_code) + "]", "error_code", error_code, "", -1, "", -1, + "", -1, "", -1, "", -1); } if (!got_packet) { stop_encoding = 1; @@ -853,8 +820,8 @@ void FFmpegWriter::flush_encoders() av_freep(&video_outbuf); } - // FLUSH AUDIO ENCODER - if (info.has_audio) + // FLUSH AUDIO ENCODER + if (info.has_audio) for (;;) { // Increment PTS (in samples and scaled to the codec's timebase) @@ -873,12 +840,12 @@ void FFmpegWriter::flush_encoders() /* encode the image */ int got_packet = 0; - #if IS_FFMPEG_3_2 +#if IS_FFMPEG_3_2 avcodec_send_frame(audio_codec, NULL); got_packet = 0; - #else +#else error_code = avcodec_encode_audio2(audio_codec, &pkt, NULL, &got_packet); - #endif +#endif if (error_code < 0) { ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::flush_encoders ERROR [" + (string)av_err2str(error_code) + "]", "error_code", error_code, "", -1, "", -1, "", -1, "", -1, "", -1); } @@ -917,25 +884,23 @@ void FFmpegWriter::flush_encoders() } // Close the video codec -void FFmpegWriter::close_video(AVFormatContext *oc, AVStream *st) -{ +void FFmpegWriter::close_video(AVFormatContext *oc, AVStream *st) { AV_FREE_CONTEXT(video_codec); video_codec = NULL; - #if IS_FFMPEG_3_2 -// #if defined(__linux__) - if (hw_en_on && hw_en_supported) { - if (hw_device_ctx) { - av_buffer_unref(&hw_device_ctx); - hw_device_ctx = NULL; +#if IS_FFMPEG_3_2 + // #if defined(__linux__) + if (hw_en_on && hw_en_supported) { + if (hw_device_ctx) { + av_buffer_unref(&hw_device_ctx); + hw_device_ctx = NULL; + } } - } -// #endif - #endif + // #endif +#endif } // Close the audio codec -void FFmpegWriter::close_audio(AVFormatContext *oc, AVStream *st) -{ +void FFmpegWriter::close_audio(AVFormatContext *oc, AVStream *st) { AV_FREE_CONTEXT(audio_codec); audio_codec = NULL; @@ -962,8 +927,7 @@ void FFmpegWriter::close_audio(AVFormatContext *oc, AVStream *st) } // Close the writer -void FFmpegWriter::Close() -{ +void FFmpegWriter::Close() { // Write trailer (if needed) if (!write_trailer) WriteTrailer(); @@ -1006,24 +970,19 @@ void FFmpegWriter::Close() } // Add an AVFrame to the cache -void FFmpegWriter::add_avframe(std::shared_ptr frame, AVFrame* av_frame) -{ +void FFmpegWriter::add_avframe(std::shared_ptr frame, AVFrame *av_frame) { // Add AVFrame to map (if it does not already exist) - if (!av_frames.count(frame)) - { + if (!av_frames.count(frame)) { // Add av_frame av_frames[frame] = av_frame; - } - else - { + } else { // Do not add, and deallocate this AVFrame AV_FREE_FRAME(&av_frame); } } // Add an audio output stream -AVStream* FFmpegWriter::add_audio_stream() -{ +AVStream *FFmpegWriter::add_audio_stream() { AVCodecContext *c; AVStream *st; @@ -1050,14 +1009,13 @@ AVStream* FFmpegWriter::add_audio_stream() if (codec->supported_samplerates) { int i; for (i = 0; codec->supported_samplerates[i] != 0; i++) - if (info.sample_rate == codec->supported_samplerates[i]) - { + if (info.sample_rate == codec->supported_samplerates[i]) { // Set the valid sample rate c->sample_rate = info.sample_rate; break; } - if (codec->supported_samplerates[i] == 0) - throw InvalidSampleRate("An invalid sample rate was detected for this codec.", path); + if (codec->supported_samplerates[i] == 0) + throw InvalidSampleRate("An invalid sample rate was detected for this codec.", path); } else // Set sample rate c->sample_rate = info.sample_rate; @@ -1068,8 +1026,7 @@ AVStream* FFmpegWriter::add_audio_stream() if (codec->channel_layouts) { int i; for (i = 0; codec->channel_layouts[i] != 0; i++) - if (channel_layout == codec->channel_layouts[i]) - { + if (channel_layout == codec->channel_layouts[i]) { // Set valid channel layout c->channel_layout = channel_layout; break; @@ -1082,8 +1039,7 @@ AVStream* FFmpegWriter::add_audio_stream() // Choose a valid sample_fmt if (codec->sample_fmts) { - for (int i = 0; codec->sample_fmts[i] != AV_SAMPLE_FMT_NONE; i++) - { + for (int i = 0; codec->sample_fmts[i] != AV_SAMPLE_FMT_NONE; i++) { // Set sample format to 1st valid format (and then exit loop) c->sample_fmt = codec->sample_fmts[i]; break; @@ -1109,8 +1065,7 @@ AVStream* FFmpegWriter::add_audio_stream() } // Add a video output stream -AVStream* FFmpegWriter::add_video_stream() -{ +AVStream *FFmpegWriter::add_video_stream() { AVCodecContext *c; AVStream *st; @@ -1138,24 +1093,22 @@ AVStream* FFmpegWriter::add_video_stream() } // Here should be the setting for low fixed bitrate // Defaults are used because mpeg2 otherwise had problems - } - else { + } else { // Check if codec supports crf switch (c->codec_id) { - #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 39, 101) - #if (LIBAVCODEC_VERSION_MAJOR >= 58) +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 39, 101) +#if (LIBAVCODEC_VERSION_MAJOR >= 58) case AV_CODEC_ID_AV1 : - #endif +#endif case AV_CODEC_ID_VP9 : case AV_CODEC_ID_H265 : - #endif +#endif case AV_CODEC_ID_VP8 : case AV_CODEC_ID_H264 : if (info.video_bit_rate < 40) { c->qmin = 0; c->qmax = 63; - } - else { + } else { c->qmin = info.video_bit_rate - 5; c->qmax = 63; } @@ -1186,9 +1139,9 @@ AVStream* FFmpegWriter::add_video_stream() identically 1. */ c->time_base.num = info.video_timebase.num; c->time_base.den = info.video_timebase.den; - #if LIBAVFORMAT_VERSION_MAJOR >= 56 +#if LIBAVFORMAT_VERSION_MAJOR >= 56 c->framerate = av_inv_q(c->time_base); - #endif +#endif st->avg_frame_rate = av_inv_q(c->time_base); st->time_base.num = info.video_timebase.num; st->time_base.den = info.video_timebase.den; @@ -1212,7 +1165,7 @@ AVStream* FFmpegWriter::add_video_stream() #endif // Find all supported pixel formats for this codec - const PixelFormat* supported_pixel_formats = codec->pix_fmts; + const PixelFormat *supported_pixel_formats = codec->pix_fmts; while (supported_pixel_formats != NULL && *supported_pixel_formats != PIX_FMT_NONE) { // Assign the 1st valid pixel format (if one is missing) if (c->pix_fmt == PIX_FMT_NONE) @@ -1222,7 +1175,7 @@ AVStream* FFmpegWriter::add_video_stream() // Codec doesn't have any pix formats? if (c->pix_fmt == PIX_FMT_NONE) { - if(fmt->video_codec == AV_CODEC_ID_RAWVIDEO) { + if (fmt->video_codec == AV_CODEC_ID_RAWVIDEO) { // Raw video should use RGB24 c->pix_fmt = PIX_FMT_RGB24; @@ -1249,8 +1202,7 @@ AVStream* FFmpegWriter::add_video_stream() } // open audio codec -void FFmpegWriter::open_audio(AVFormatContext *oc, AVStream *st) -{ +void FFmpegWriter::open_audio(AVFormatContext *oc, AVStream *st) { AVCodec *codec; AV_GET_CODEC_FROM_STREAM(st, audio_codec) @@ -1284,14 +1236,14 @@ void FFmpegWriter::open_audio(AVFormatContext *oc, AVStream *st) int s = AV_FIND_DECODER_CODEC_ID(st); switch (s) { - case AV_CODEC_ID_PCM_S16LE: - case AV_CODEC_ID_PCM_S16BE: - case AV_CODEC_ID_PCM_U16LE: - case AV_CODEC_ID_PCM_U16BE: - audio_input_frame_size >>= 1; - break; - default: - break; + case AV_CODEC_ID_PCM_S16LE: + case AV_CODEC_ID_PCM_S16BE: + case AV_CODEC_ID_PCM_U16LE: + case AV_CODEC_ID_PCM_U16BE: + audio_input_frame_size >>= 1; + break; + default: + break; } } else { // Set frame size based on the codec @@ -1313,25 +1265,22 @@ void FFmpegWriter::open_audio(AVFormatContext *oc, AVStream *st) audio_encoder_buffer = new uint8_t[audio_encoder_buffer_size]; // Add audio metadata (if any) - for(std::map::iterator iter = info.metadata.begin(); iter != info.metadata.end(); ++iter) - { + for (std::map::iterator iter = info.metadata.begin(); iter != info.metadata.end(); ++iter) { av_dict_set(&st->metadata, iter->first.c_str(), iter->second.c_str(), 0); } ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::open_audio", "audio_codec->thread_count", audio_codec->thread_count, "audio_input_frame_size", audio_input_frame_size, "buffer_size", AVCODEC_MAX_AUDIO_FRAME_SIZE + MY_INPUT_BUFFER_PADDING_SIZE, "", -1, "", -1, "", -1); - } // open video codec -void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) -{ +void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) { AVCodec *codec; AV_GET_CODEC_FROM_STREAM(st, video_codec) // Set number of threads equal to number of processors (not to exceed 16) video_codec->thread_count = min(FF_NUM_PROCESSORS, 16); - #if IS_FFMPEG_3_2 +#if IS_FFMPEG_3_2 if (hw_en_on && hw_en_supported) { //char *dev_hw = NULL; char adapter[256]; @@ -1341,32 +1290,31 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) adapter_num = openshot::Settings::Instance()->HW_EN_DEVICE_SET; fprintf(stderr, "\n\nEncodiing Device Nr: %d\n", adapter_num); if (adapter_num < 3 && adapter_num >=0) { - #if defined(__linux__) +#if defined(__linux__) snprintf(adapter,sizeof(adapter),"/dev/dri/renderD%d", adapter_num+128); // Maybe 127 is better because the first card would be 1?! adapter_ptr = adapter; - #elif defined(_WIN32) +#elif defined(_WIN32) adapter_ptr = NULL; - #elif defined(__APPLE__) +#elif defined(__APPLE__) adapter_ptr = NULL; - #endif +#endif } else { adapter_ptr = NULL; // Just to be sure } // Check if it is there and writable - #if defined(__linux__) +#if defined(__linux__) if( adapter_ptr != NULL && access( adapter_ptr, W_OK ) == 0 ) { - #elif defined(_WIN32) +#elif defined(_WIN32) if( adapter_ptr != NULL ) { - #elif defined(__APPLE__) +#elif defined(__APPLE__) if( adapter_ptr != NULL ) { - #endif +#endif ZmqLogger::Instance()->AppendDebugMethod("Encode Device present using device", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); } else { adapter_ptr = NULL; // use default - //cerr << "\n\n\nEncode Device not present using default\n\n\n"; ZmqLogger::Instance()->AppendDebugMethod("Encode Device not present using default", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); } if (av_hwdevice_ctx_create(&hw_device_ctx, hw_en_av_device_type, @@ -1375,7 +1323,8 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) throw InvalidCodec("Could not create hwdevice", path); } } - #endif +#endif + /* find the video encoder */ codec = avcodec_find_encoder_by_name(info.vcodec.c_str()); if (!codec) @@ -1383,15 +1332,15 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) if (!codec) throw InvalidCodec("Could not find codec", path); - /* Force max_b_frames to 0 in some cases (i.e. for mjpeg image sequences */ - if(video_codec->max_b_frames && video_codec->codec_id != AV_CODEC_ID_MPEG4 && video_codec->codec_id != AV_CODEC_ID_MPEG1VIDEO && video_codec->codec_id != AV_CODEC_ID_MPEG2VIDEO) - video_codec->max_b_frames = 0; + /* Force max_b_frames to 0 in some cases (i.e. for mjpeg image sequences */ + if (video_codec->max_b_frames && video_codec->codec_id != AV_CODEC_ID_MPEG4 && video_codec->codec_id != AV_CODEC_ID_MPEG1VIDEO && video_codec->codec_id != AV_CODEC_ID_MPEG2VIDEO) + video_codec->max_b_frames = 0; // Init options AVDictionary *opts = NULL; av_dict_set(&opts, "strict", "experimental", 0); - #if IS_FFMPEG_3_2 +#if IS_FFMPEG_3_2 if (hw_en_on && hw_en_supported) { video_codec->max_b_frames = 0; // At least this GPU doesn't support b-frames video_codec->pix_fmt = hw_en_av_pix_fmt; @@ -1405,7 +1354,7 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) fprintf(stderr, "Failed to set hwframe context.\n"); } } - #endif +#endif /* open the codec */ if (avcodec_open2(video_codec, codec, &opts) < 0) @@ -1416,8 +1365,7 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) av_dict_free(&opts); // Add video metadata (if any) - for(std::map::iterator iter = info.metadata.begin(); iter != info.metadata.end(); ++iter) - { + for (std::map::iterator iter = info.metadata.begin(); iter != info.metadata.end(); ++iter) { av_dict_set(&st->metadata, iter->first.c_str(), iter->second.c_str(), 0); } @@ -1426,9 +1374,8 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) } // write all queued frames' audio to the video file -void FFmpegWriter::write_audio_packets(bool final) -{ - #pragma omp task firstprivate(final) +void FFmpegWriter::write_audio_packets(bool is_final) { +#pragma omp task firstprivate(is_final) { // Init audio buffers / variables int total_frame_samples = 0; @@ -1439,14 +1386,13 @@ void FFmpegWriter::write_audio_packets(bool final) ChannelLayout channel_layout_in_frame = LAYOUT_MONO; // default channel layout // Create a new array (to hold all S16 audio samples, for the current queued frames - int16_t* all_queued_samples = (int16_t*)av_malloc((sizeof(int16_t)*(queued_audio_frames.size() * AVCODEC_MAX_AUDIO_FRAME_SIZE))); - int16_t* all_resampled_samples = NULL; - int16_t* final_samples_planar = NULL; - int16_t* final_samples = NULL; + int16_t *all_queued_samples = (int16_t *) av_malloc((sizeof(int16_t) * (queued_audio_frames.size() * AVCODEC_MAX_AUDIO_FRAME_SIZE))); + int16_t *all_resampled_samples = NULL; + int16_t *final_samples_planar = NULL; + int16_t *final_samples = NULL; // Loop through each queued audio frame - while (!queued_audio_frames.empty()) - { + while (!queued_audio_frames.empty()) { // Get front frame (from the queue) std::shared_ptr frame = queued_audio_frames.front(); @@ -1458,7 +1404,7 @@ void FFmpegWriter::write_audio_packets(bool final) // Get audio sample array - float* frame_samples_float = NULL; + float *frame_samples_float = NULL; // Get samples interleaved together (c1 c2 c1 c2 c1 c2) frame_samples_float = frame->GetInterleavedAudioSamples(sample_rate_in_frame, NULL, &samples_in_frame); @@ -1487,13 +1433,13 @@ void FFmpegWriter::write_audio_packets(bool final) int samples_position = 0; - ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::write_audio_packets", "final", final, "total_frame_samples", total_frame_samples, "channel_layout_in_frame", channel_layout_in_frame, "channels_in_frame", channels_in_frame, "samples_in_frame", samples_in_frame, "LAYOUT_MONO", LAYOUT_MONO); + ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::write_audio_packets", "is_final", is_final, "total_frame_samples", total_frame_samples, "channel_layout_in_frame", channel_layout_in_frame, "channels_in_frame", channels_in_frame, "samples_in_frame", samples_in_frame, "LAYOUT_MONO", LAYOUT_MONO); // Keep track of the original sample format AVSampleFormat output_sample_fmt = audio_codec->sample_fmt; AVFrame *audio_frame = NULL; - if (!final) { + if (!is_final) { // Create input frame (and allocate arrays) audio_frame = AV_ALLOCATE_FRAME(); AV_RESET_FRAME(audio_frame); @@ -1501,28 +1447,23 @@ void FFmpegWriter::write_audio_packets(bool final) // Fill input frame with sample data avcodec_fill_audio_frame(audio_frame, channels_in_frame, AV_SAMPLE_FMT_S16, (uint8_t *) all_queued_samples, - audio_encoder_buffer_size, 0); + audio_encoder_buffer_size, 0); // Do not convert audio to planar format (yet). We need to keep everything interleaved at this point. - switch (audio_codec->sample_fmt) - { - case AV_SAMPLE_FMT_FLTP: - { + switch (audio_codec->sample_fmt) { + case AV_SAMPLE_FMT_FLTP: { output_sample_fmt = AV_SAMPLE_FMT_FLT; break; } - case AV_SAMPLE_FMT_S32P: - { + case AV_SAMPLE_FMT_S32P: { output_sample_fmt = AV_SAMPLE_FMT_S32; break; } - case AV_SAMPLE_FMT_S16P: - { + case AV_SAMPLE_FMT_S16P: { output_sample_fmt = AV_SAMPLE_FMT_S16; break; } - case AV_SAMPLE_FMT_U8P: - { + case AV_SAMPLE_FMT_U8P: { output_sample_fmt = AV_SAMPLE_FMT_U8; break; } @@ -1546,29 +1487,30 @@ void FFmpegWriter::write_audio_packets(bool final) // setup resample context if (!avr) { avr = SWR_ALLOC(); - av_opt_set_int(avr, "in_channel_layout", channel_layout_in_frame, 0); + av_opt_set_int(avr, "in_channel_layout", channel_layout_in_frame, 0); av_opt_set_int(avr, "out_channel_layout", info.channel_layout, 0); - av_opt_set_int(avr, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0); - av_opt_set_int(avr, "out_sample_fmt", output_sample_fmt, 0); // planar not allowed here - av_opt_set_int(avr, "in_sample_rate", sample_rate_in_frame, 0); - av_opt_set_int(avr, "out_sample_rate", info.sample_rate, 0); - av_opt_set_int(avr, "in_channels", channels_in_frame, 0); - av_opt_set_int(avr, "out_channels", info.channels, 0); + av_opt_set_int(avr, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0); + av_opt_set_int(avr, "out_sample_fmt", output_sample_fmt, 0); // planar not allowed here + av_opt_set_int(avr, "in_sample_rate", sample_rate_in_frame, 0); + av_opt_set_int(avr, "out_sample_rate", info.sample_rate, 0); + av_opt_set_int(avr, "in_channels", channels_in_frame, 0); + av_opt_set_int(avr, "out_channels", info.channels, 0); SWR_INIT(avr); } int nb_samples = 0; // Convert audio samples - nb_samples = SWR_CONVERT(avr, // audio resample context - audio_converted->data, // output data pointers - audio_converted->linesize[0], // output plane size, in bytes. (0 if unknown) - audio_converted->nb_samples, // maximum number of samples that the output buffer can hold - audio_frame->data, // input data pointers - audio_frame->linesize[0], // input plane size, in bytes (0 if unknown) - audio_frame->nb_samples); // number of input samples to convert + nb_samples = SWR_CONVERT(avr, // audio resample context + audio_converted->data, // output data pointers + audio_converted->linesize[0], // output plane size, in bytes. (0 if unknown) + audio_converted->nb_samples, // maximum number of samples that the output buffer can hold + audio_frame->data, // input data pointers + audio_frame->linesize[0], // input plane size, in bytes (0 if unknown) + audio_frame->nb_samples); // number of input samples to convert // Create a new array (to hold all resampled S16 audio samples) - all_resampled_samples = (int16_t*)av_malloc(sizeof(int16_t) * nb_samples * info.channels * (av_get_bytes_per_sample(output_sample_fmt) / av_get_bytes_per_sample(AV_SAMPLE_FMT_S16))); + all_resampled_samples = (int16_t *) av_malloc( + sizeof(int16_t) * nb_samples * info.channels * (av_get_bytes_per_sample(output_sample_fmt) / av_get_bytes_per_sample(AV_SAMPLE_FMT_S16))); // Copy audio samples over original samples memcpy(all_resampled_samples, audio_converted->data[0], nb_samples * info.channels * av_get_bytes_per_sample(output_sample_fmt)); @@ -1584,7 +1526,7 @@ void FFmpegWriter::write_audio_packets(bool final) } // Loop until no more samples - while (remaining_frame_samples > 0 || final) { + while (remaining_frame_samples > 0 || is_final) { // Get remaining samples needed for this packet int remaining_packet_samples = (audio_input_frame_size * info.channels) - audio_input_position; @@ -1596,9 +1538,10 @@ void FFmpegWriter::write_audio_packets(bool final) diff = remaining_frame_samples; // Copy frame samples into the packet samples array - if (!final) + if (!is_final) //TODO: Make this more sane - memcpy(samples + (audio_input_position * (av_get_bytes_per_sample(output_sample_fmt) / av_get_bytes_per_sample(AV_SAMPLE_FMT_S16))), all_resampled_samples + samples_position, diff * av_get_bytes_per_sample(output_sample_fmt)); + memcpy(samples + (audio_input_position * (av_get_bytes_per_sample(output_sample_fmt) / av_get_bytes_per_sample(AV_SAMPLE_FMT_S16))), + all_resampled_samples + samples_position, diff * av_get_bytes_per_sample(output_sample_fmt)); // Increment counters audio_input_position += diff; @@ -1607,28 +1550,27 @@ void FFmpegWriter::write_audio_packets(bool final) remaining_packet_samples -= diff; // Do we have enough samples to proceed? - if (audio_input_position < (audio_input_frame_size * info.channels) && !final) + if (audio_input_position < (audio_input_frame_size * info.channels) && !is_final) // Not enough samples to encode... so wait until the next frame break; // Convert to planar (if needed by audio codec) AVFrame *frame_final = AV_ALLOCATE_FRAME(); AV_RESET_FRAME(frame_final); - if (av_sample_fmt_is_planar(audio_codec->sample_fmt)) - { + if (av_sample_fmt_is_planar(audio_codec->sample_fmt)) { ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::write_audio_packets (2nd resampling for Planar formats)", "in_sample_fmt", output_sample_fmt, "out_sample_fmt", audio_codec->sample_fmt, "in_sample_rate", info.sample_rate, "out_sample_rate", info.sample_rate, "in_channels", info.channels, "out_channels", info.channels); // setup resample context if (!avr_planar) { avr_planar = SWR_ALLOC(); - av_opt_set_int(avr_planar, "in_channel_layout", info.channel_layout, 0); + av_opt_set_int(avr_planar, "in_channel_layout", info.channel_layout, 0); av_opt_set_int(avr_planar, "out_channel_layout", info.channel_layout, 0); - av_opt_set_int(avr_planar, "in_sample_fmt", output_sample_fmt, 0); - av_opt_set_int(avr_planar, "out_sample_fmt", audio_codec->sample_fmt, 0); // planar not allowed here - av_opt_set_int(avr_planar, "in_sample_rate", info.sample_rate, 0); - av_opt_set_int(avr_planar, "out_sample_rate", info.sample_rate, 0); - av_opt_set_int(avr_planar, "in_channels", info.channels, 0); - av_opt_set_int(avr_planar, "out_channels", info.channels, 0); + av_opt_set_int(avr_planar, "in_sample_fmt", output_sample_fmt, 0); + av_opt_set_int(avr_planar, "out_sample_fmt", audio_codec->sample_fmt, 0); // planar not allowed here + av_opt_set_int(avr_planar, "in_sample_rate", info.sample_rate, 0); + av_opt_set_int(avr_planar, "out_sample_rate", info.sample_rate, 0); + av_opt_set_int(avr_planar, "in_channels", info.channels, 0); + av_opt_set_int(avr_planar, "out_channels", info.channels, 0); SWR_INIT(avr_planar); } @@ -1638,27 +1580,28 @@ void FFmpegWriter::write_audio_packets(bool final) audio_frame->nb_samples = audio_input_position / info.channels; // Create a new array - final_samples_planar = (int16_t*)av_malloc(sizeof(int16_t) * audio_frame->nb_samples * info.channels * (av_get_bytes_per_sample(output_sample_fmt) / av_get_bytes_per_sample(AV_SAMPLE_FMT_S16))); + final_samples_planar = (int16_t *) av_malloc( + sizeof(int16_t) * audio_frame->nb_samples * info.channels * (av_get_bytes_per_sample(output_sample_fmt) / av_get_bytes_per_sample(AV_SAMPLE_FMT_S16))); // Copy audio into buffer for frame memcpy(final_samples_planar, samples, audio_frame->nb_samples * info.channels * av_get_bytes_per_sample(output_sample_fmt)); // Fill input frame with sample data avcodec_fill_audio_frame(audio_frame, info.channels, output_sample_fmt, (uint8_t *) final_samples_planar, - audio_encoder_buffer_size, 0); + audio_encoder_buffer_size, 0); // Create output frame (and allocate arrays) frame_final->nb_samples = audio_input_frame_size; av_samples_alloc(frame_final->data, frame_final->linesize, info.channels, frame_final->nb_samples, audio_codec->sample_fmt, 0); // Convert audio samples - int nb_samples = SWR_CONVERT(avr_planar, // audio resample context - frame_final->data, // output data pointers - frame_final->linesize[0], // output plane size, in bytes. (0 if unknown) - frame_final->nb_samples, // maximum number of samples that the output buffer can hold - audio_frame->data, // input data pointers - audio_frame->linesize[0], // input plane size, in bytes (0 if unknown) - audio_frame->nb_samples); // number of input samples to convert + int nb_samples = SWR_CONVERT(avr_planar, // audio resample context + frame_final->data, // output data pointers + frame_final->linesize[0], // output plane size, in bytes. (0 if unknown) + frame_final->nb_samples, // maximum number of samples that the output buffer can hold + audio_frame->data, // input data pointers + audio_frame->linesize[0], // input plane size, in bytes (0 if unknown) + audio_frame->nb_samples); // number of input samples to convert // Copy audio samples over original samples if (nb_samples > 0) @@ -1673,7 +1616,8 @@ void FFmpegWriter::write_audio_packets(bool final) } else { // Create a new array - final_samples = (int16_t*)av_malloc(sizeof(int16_t) * audio_input_position * (av_get_bytes_per_sample(audio_codec->sample_fmt) / av_get_bytes_per_sample(AV_SAMPLE_FMT_S16))); + final_samples = (int16_t *) av_malloc( + sizeof(int16_t) * audio_input_position * (av_get_bytes_per_sample(audio_codec->sample_fmt) / av_get_bytes_per_sample(AV_SAMPLE_FMT_S16))); // Copy audio into buffer for frame memcpy(final_samples, samples, audio_input_position * av_get_bytes_per_sample(audio_codec->sample_fmt)); @@ -1683,7 +1627,7 @@ void FFmpegWriter::write_audio_packets(bool final) // Fill the final_frame AVFrame with audio (non planar) avcodec_fill_audio_frame(frame_final, audio_codec->channels, audio_codec->sample_fmt, (uint8_t *) final_samples, - audio_encoder_buffer_size, 0); + audio_encoder_buffer_size, 0); } // Increment PTS (in samples) @@ -1702,7 +1646,7 @@ void FFmpegWriter::write_audio_packets(bool final) /* encode the audio samples */ int got_packet_ptr = 0; - #if IS_FFMPEG_3_2 +#if IS_FFMPEG_3_2 // Encode audio (latest version of FFmpeg) int error_code; int ret = 0; @@ -1730,10 +1674,10 @@ void FFmpegWriter::write_audio_packets(bool final) ret = -1; } got_packet_ptr = ret; - #else +#else // Encode audio (older versions of FFmpeg) int error_code = avcodec_encode_audio2(audio_codec, &pkt, frame_final, &got_packet_ptr); - #endif +#endif /* if zero size, it means the image was buffered */ if (error_code == 0 && got_packet_ptr) { @@ -1755,15 +1699,13 @@ void FFmpegWriter::write_audio_packets(bool final) /* write the compressed frame in the media file */ int error_code = av_interleaved_write_frame(oc, &pkt); - if (error_code < 0) - { - ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::write_audio_packets ERROR [" + (string)av_err2str(error_code) + "]", "error_code", error_code, "", -1, "", -1, "", -1, "", -1, "", -1); + if (error_code < 0) { + ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::write_audio_packets ERROR [" + (string) av_err2str(error_code) + "]", "error_code", error_code, "", -1, "", -1, "", -1, "", -1, "", -1); } } - if (error_code < 0) - { - ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::write_audio_packets ERROR [" + (string)av_err2str(error_code) + "]", "error_code", error_code, "", -1, "", -1, "", -1, "", -1, "", -1); + if (error_code < 0) { + ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::write_audio_packets ERROR [" + (string) av_err2str(error_code) + "]", "error_code", error_code, "", -1, "", -1, "", -1, "", -1, "", -1); } // deallocate AVFrame @@ -1775,7 +1717,7 @@ void FFmpegWriter::write_audio_packets(bool final) // Reset position audio_input_position = 0; - final = false; + is_final = false; } // Delete arrays (if needed) @@ -1792,8 +1734,7 @@ void FFmpegWriter::write_audio_packets(bool final) } // Allocate an AVFrame object -AVFrame* FFmpegWriter::allocate_avframe(PixelFormat pix_fmt, int width, int height, int *buffer_size, uint8_t *new_buffer) -{ +AVFrame *FFmpegWriter::allocate_avframe(PixelFormat pix_fmt, int width, int height, int *buffer_size, uint8_t *new_buffer) { // Create an RGB AVFrame AVFrame *new_av_frame = NULL; @@ -1806,10 +1747,9 @@ AVFrame* FFmpegWriter::allocate_avframe(PixelFormat pix_fmt, int width, int heig *buffer_size = AV_GET_IMAGE_SIZE(pix_fmt, width, height); // Create buffer (if not provided) - if (!new_buffer) - { + if (!new_buffer) { // New Buffer - new_buffer = (uint8_t*)av_malloc(*buffer_size * sizeof(uint8_t)); + new_buffer = (uint8_t *) av_malloc(*buffer_size * sizeof(uint8_t)); // Attach buffer to AVFrame AV_COPY_PICTURE_DATA(new_av_frame, new_buffer, pix_fmt, width, height); new_av_frame->width = width; @@ -1822,8 +1762,7 @@ AVFrame* FFmpegWriter::allocate_avframe(PixelFormat pix_fmt, int width, int heig } // process video frame -void FFmpegWriter::process_video_packet(std::shared_ptr frame) -{ +void FFmpegWriter::process_video_packet(std::shared_ptr frame) { // Determine the height & width of the source image int source_image_width = frame->GetWidth(); int source_image_height = frame->GetHeight(); @@ -1842,7 +1781,7 @@ void FFmpegWriter::process_video_packet(std::shared_ptr frame) if (rescaler_position == num_of_rescalers) rescaler_position = 0; - #pragma omp task firstprivate(frame, scaler, source_image_width, source_image_height) +#pragma omp task firstprivate(frame, scaler, source_image_width, source_image_height) { // Allocate an RGB frame & final output frame int bytes_source = 0; @@ -1854,29 +1793,28 @@ void FFmpegWriter::process_video_packet(std::shared_ptr frame) pixels = frame->GetPixels(); // Init AVFrame for source image & final (converted image) - frame_source = allocate_avframe(PIX_FMT_RGBA, source_image_width, source_image_height, &bytes_source, (uint8_t*) pixels); - #if IS_FFMPEG_3_2 - AVFrame *frame_final; + frame_source = allocate_avframe(PIX_FMT_RGBA, source_image_width, source_image_height, &bytes_source, (uint8_t *) pixels); +#if IS_FFMPEG_3_2 + AVFrame *frame_final; if (hw_en_on && hw_en_supported) { frame_final = allocate_avframe(AV_PIX_FMT_NV12, info.width, info.height, &bytes_final, NULL); - } else - { + } else { frame_final = allocate_avframe((AVPixelFormat)(video_st->codecpar->format), info.width, info.height, &bytes_final, NULL); } - #else +#else AVFrame *frame_final = allocate_avframe(video_codec->pix_fmt, info.width, info.height, &bytes_final, NULL); - #endif +#endif // Fill with data - AV_COPY_PICTURE_DATA(frame_source, (uint8_t*)pixels, PIX_FMT_RGBA, source_image_width, source_image_height); + AV_COPY_PICTURE_DATA(frame_source, (uint8_t *) pixels, PIX_FMT_RGBA, source_image_width, source_image_height); ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::process_video_packet", "frame->number", frame->number, "bytes_source", bytes_source, "bytes_final", bytes_final, "", -1, "", -1, "", -1); // Resize & convert pixel format sws_scale(scaler, frame_source->data, frame_source->linesize, 0, - source_image_height, frame_final->data, frame_final->linesize); + source_image_height, frame_final->data, frame_final->linesize); // Add resized AVFrame to av_frames map - #pragma omp critical (av_frames_section) +#pragma omp critical (av_frames_section) add_avframe(frame, frame_final); // Deallocate memory @@ -1887,8 +1825,7 @@ void FFmpegWriter::process_video_packet(std::shared_ptr frame) } // write video frame -bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame* frame_final) -{ +bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame *frame_final) { #if (LIBAVFORMAT_VERSION_MAJOR >= 58) ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::write_video_packet", "frame->number", frame->number, "oc->oformat->flags", oc->oformat->flags, "", -1, "", -1, "", -1, "", -1); #else @@ -1900,19 +1837,18 @@ bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame* fra av_init_packet(&pkt); pkt.flags |= AV_PKT_FLAG_KEY; - pkt.stream_index= video_st->index; - pkt.data= (uint8_t*)frame_final->data; - pkt.size= sizeof(AVPicture); + pkt.stream_index = video_st->index; + pkt.data = (uint8_t *) frame_final->data; + pkt.size = sizeof(AVPicture); // Increment PTS (in frames and scaled to the codec's timebase) - write_video_count += av_rescale_q(1, (AVRational){info.fps.den, info.fps.num}, video_codec->time_base); + write_video_count += av_rescale_q(1, (AVRational) {info.fps.den, info.fps.num}, video_codec->time_base); pkt.pts = write_video_count; /* write the compressed frame in the media file */ int error_code = av_interleaved_write_frame(oc, &pkt); - if (error_code < 0) - { - ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::write_video_packet ERROR [" + (string)av_err2str(error_code) + "]", "error_code", error_code, "", -1, "", -1, "", -1, "", -1, "", -1); + if (error_code < 0) { + ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::write_video_packet ERROR [" + (string) av_err2str(error_code) + "]", "error_code", error_code, "", -1, "", -1, "", -1, "", -1, "", -1); return false; } @@ -1933,11 +1869,11 @@ bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame* fra uint8_t *video_outbuf = NULL; // Increment PTS (in frames and scaled to the codec's timebase) - write_video_count += av_rescale_q(1, (AVRational){info.fps.den, info.fps.num}, video_codec->time_base); + write_video_count += av_rescale_q(1, (AVRational) {info.fps.den, info.fps.num}, video_codec->time_base); // Assign the initial AVFrame PTS from the frame counter frame_final->pts = write_video_count; - #if IS_FFMPEG_3_2 +#if IS_FFMPEG_3_2 if (hw_en_on && hw_en_supported) { if (!(hw_frame = av_frame_alloc())) { fprintf(stderr, "Error code: av_hwframe_alloc\n"); @@ -1954,20 +1890,20 @@ bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame* fra } av_frame_copy_props(hw_frame, frame_final); } - #endif +#endif /* encode the image */ int got_packet_ptr = 0; int error_code = 0; - #if IS_FFMPEG_3_2 +#if IS_FFMPEG_3_2 // Write video packet (latest version of FFmpeg) int frameFinished = 0; int ret; - #if IS_FFMPEG_3_2 + if (hw_en_on && hw_en_supported) { ret = avcodec_send_frame(video_codec, hw_frame); //hw_frame!!! - } else - #endif - ret = avcodec_send_frame(video_codec, frame_final); + } else { + ret = avcodec_send_frame(video_codec, frame_final); + } error_code = ret; if (ret < 0 ) { ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::write_video_packet (Frame not sent)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); @@ -1982,10 +1918,11 @@ bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame* fra else { while (ret >= 0) { ret = avcodec_receive_packet(video_codec, &pkt); - if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { avcodec_flush_buffers(video_codec); got_packet_ptr = 0; - break; + break; } if (ret == 0) { got_packet_ptr = 1; @@ -1993,36 +1930,36 @@ bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame* fra } } } - #else - #if LIBAVFORMAT_VERSION_MAJOR >= 54 - // Write video packet (older than FFmpeg 3.2) - error_code = avcodec_encode_video2(video_codec, &pkt, frame_final, &got_packet_ptr); - if (error_code != 0 ) { - cerr << "Frame AVERROR_EOF" << "\n"; - } - if (got_packet_ptr == 0 ) { - cerr << "Frame gotpacket error" << "\n"; - } - #else - // Write video packet (even older versions of FFmpeg) - int video_outbuf_size = 200000; - video_outbuf = (uint8_t*) av_malloc(200000); +#else +#if LIBAVFORMAT_VERSION_MAJOR >= 54 + // Write video packet (older than FFmpeg 3.2) + error_code = avcodec_encode_video2(video_codec, &pkt, frame_final, &got_packet_ptr); + if (error_code != 0) { + cerr << "Frame AVERROR_EOF" << "\n"; + } + if (got_packet_ptr == 0) { + cerr << "Frame gotpacket error" << "\n"; + } +#else + // Write video packet (even older versions of FFmpeg) + int video_outbuf_size = 200000; + video_outbuf = (uint8_t*) av_malloc(200000); - /* encode the image */ - int out_size = avcodec_encode_video(video_codec, video_outbuf, video_outbuf_size, frame_final); + /* encode the image */ + int out_size = avcodec_encode_video(video_codec, video_outbuf, video_outbuf_size, frame_final); - /* if zero size, it means the image was buffered */ - if (out_size > 0) { - if(video_codec->coded_frame->key_frame) - pkt.flags |= AV_PKT_FLAG_KEY; - pkt.data= video_outbuf; - pkt.size= out_size; + /* if zero size, it means the image was buffered */ + if (out_size > 0) { + if(video_codec->coded_frame->key_frame) + pkt.flags |= AV_PKT_FLAG_KEY; + pkt.data= video_outbuf; + pkt.size= out_size; - // got data back (so encode this frame) - got_packet_ptr = 1; - } - #endif - #endif + // got data back (so encode this frame) + got_packet_ptr = 1; + } +#endif +#endif /* if zero size, it means the image was buffered */ if (error_code == 0 && got_packet_ptr) { @@ -2042,9 +1979,8 @@ bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame* fra /* write the compressed frame in the media file */ int error_code = av_interleaved_write_frame(oc, &pkt); - if (error_code < 0) - { - ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::write_video_packet ERROR [" + (string)av_err2str(error_code) + "]", "error_code", error_code, "", -1, "", -1, "", -1, "", -1, "", -1); + if (error_code < 0) { + ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::write_video_packet ERROR [" + (string) av_err2str(error_code) + "]", "error_code", error_code, "", -1, "", -1, "", -1, "", -1, "", -1); return false; } } @@ -2055,14 +1991,14 @@ bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame* fra // Deallocate packet AV_FREE_PACKET(&pkt); - #if IS_FFMPEG_3_2 +#if IS_FFMPEG_3_2 if (hw_en_on && hw_en_supported) { if (hw_frame) { av_frame_free(&hw_frame); hw_frame = NULL; } } - #endif +#endif } // Success @@ -2070,31 +2006,29 @@ bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame* fra } // Output the ffmpeg info about this format, streams, and codecs (i.e. dump format) -void FFmpegWriter::OutputStreamInfo() -{ +void FFmpegWriter::OutputStreamInfo() { // output debug info av_dump_format(oc, 0, path.c_str(), 1); } // Init a collection of software rescalers (thread safe) -void FFmpegWriter::InitScalers(int source_width, int source_height) -{ +void FFmpegWriter::InitScalers(int source_width, int source_height) { int scale_mode = SWS_FAST_BILINEAR; if (openshot::Settings::Instance()->HIGH_QUALITY_SCALING) { scale_mode = SWS_LANCZOS; } // Init software rescalers vector (many of them, one for each thread) - for (int x = 0; x < num_of_rescalers; x++) - { + for (int x = 0; x < num_of_rescalers; x++) { // Init the software scaler from FFMpeg - #if IS_FFMPEG_3_2 +#if IS_FFMPEG_3_2 if (hw_en_on && hw_en_supported) { img_convert_ctx = sws_getContext(source_width, source_height, PIX_FMT_RGBA, info.width, info.height, AV_PIX_FMT_NV12, SWS_BILINEAR, NULL, NULL, NULL); } else - #endif +#endif { - img_convert_ctx = sws_getContext(source_width, source_height, PIX_FMT_RGBA, info.width, info.height, AV_GET_CODEC_PIXEL_FORMAT(video_st, video_st->codec), SWS_BILINEAR, NULL, NULL, NULL); + img_convert_ctx = sws_getContext(source_width, source_height, PIX_FMT_RGBA, info.width, info.height, AV_GET_CODEC_PIXEL_FORMAT(video_st, video_st->codec), SWS_BILINEAR, + NULL, NULL, NULL); } // Add rescaler to vector @@ -2109,8 +2043,7 @@ void FFmpegWriter::ResampleAudio(int sample_rate, int channels) { } // Remove & deallocate all software scalers -void FFmpegWriter::RemoveScalers() -{ +void FFmpegWriter::RemoveScalers() { // Close all rescalers for (int x = 0; x < num_of_rescalers; x++) sws_freeContext(image_rescalers[x]); From 19f5fa37f2c915be6ad4bb96323802d3c508f04b Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Thu, 18 Apr 2019 16:41:11 -0700 Subject: [PATCH 145/223] Replace qsv with videotoolbox for MacOS codec library. Windows and MacOS is not tested! We need users who test it. --- src/FFmpegReader.cpp | 12 ++++++------ src/FFmpegWriter.cpp | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 3af851e1..2ea6fcbb 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -248,9 +248,9 @@ static enum AVPixelFormat get_hw_dec_format_qs(AVCodecContext *ctx, const enum A for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { switch (*p) { - case AV_PIX_FMT_QSV: - hw_de_av_pix_fmt_global = AV_PIX_FMT_QSV; - hw_de_av_device_type_global = AV_HWDEVICE_TYPE_QSV; + case AV_PIX_FMT_VIDEOTOOLBOX: + hw_de_av_pix_fmt_global = AV_PIX_FMT_VIDEOTOOLBOX; + hw_de_av_device_type_global = AV_HWDEVICE_TYPE_VIDEOTOOLBOX; return *p; break; } @@ -413,15 +413,15 @@ void FFmpegReader::Open() { i_decoder_hw = openshot::Settings::Instance()->HARDWARE_DECODER; switch (i_decoder_hw) { case 0: - hw_de_av_device_type = AV_HWDEVICE_TYPE_QSV; + hw_de_av_device_type = AV_HWDEVICE_TYPE_VIDEOTOOLBOX; pCodecCtx->get_format = get_hw_dec_format_qs; break; case 5: - hw_de_av_device_type = AV_HWDEVICE_TYPE_QSV; + hw_de_av_device_type = AV_HWDEVICE_TYPE_VIDEOTOOLBOX; pCodecCtx->get_format = get_hw_dec_format_qs; break; default: - hw_de_av_device_type = AV_HWDEVICE_TYPE_QSV; + hw_de_av_device_type = AV_HWDEVICE_TYPE_VIDEOTOOLBOX; pCodecCtx->get_format = get_hw_dec_format_qs; break; } diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index e12ff094..072ac6d7 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -211,12 +211,12 @@ void FFmpegWriter::SetVideoOptions(bool has_video, string codec, Fraction fps, i } } #elif defined(__APPLE__) - if ( (strcmp(codec.c_str(),"h264_qsv") == 0)) { + if ( (strcmp(codec.c_str(),"h264_videotoolbox") == 0)) { new_codec = avcodec_find_encoder_by_name(codec.c_str()); hw_en_on = 1; hw_en_supported = 1; - hw_en_av_pix_fmt = AV_PIX_FMT_QSV; - hw_en_av_device_type = AV_HWDEVICE_TYPE_QSV; + hw_en_av_pix_fmt = AV_PIX_FMT_VIDEOTOOLBOX; + hw_en_av_device_type = AV_HWDEVICE_TYPE_VIDEOTOOLBOX; } else { new_codec = avcodec_find_encoder_by_name(codec.c_str()); From 8d3263f2faacad1235624a9e0acb2327342b034c Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Thu, 18 Apr 2019 17:09:20 -0700 Subject: [PATCH 146/223] Some information --- doc/HW-ACCEL.md | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/doc/HW-ACCEL.md b/doc/HW-ACCEL.md index edbe85b4..c11d2adc 100644 --- a/doc/HW-ACCEL.md +++ b/doc/HW-ACCEL.md @@ -8,8 +8,8 @@ Observations for developers wanting to make hardware acceleration work. * HW accel is supported from ffmpeg version 3.2 (3.3 for nVidia drivers) * HW accel was removed for nVidia drivers in Ubuntu for ffmpeg 4+ -* I could not manage to build a version of ffmpeg 4.1 with the nVidia SDK -that worked with nVidia cards. There might be a problem in ffmpeg 4+ +* I could not manage to build a version of ffmpeg 4.1 with the nVidia SDK +that worked with nVidia cards. There might be a problem in ffmpeg 4+ that prohibits this. **Notice:** The ffmpeg versions of Ubuntu and PPAs for Ubuntu show the @@ -55,7 +55,7 @@ int HW_EN_DEVICE_SET = 0; The correct version of libva is needed (libva in Ubuntu 16.04 or libva2 in Ubuntu 18.04) for the AppImage to work with hardware acceleration. -An AppImage that works on both systems (supporting libva and libva2), +An AppImage that works on both systems (supporting libva and libva2), might be possible when no libva is included in the AppImage. * vaapi is working for intel and AMD @@ -64,14 +64,14 @@ might be possible when no libva is included in the AppImage. ## AMD Graphics Cards (RadeonOpenCompute/ROCm) -Decoding and encoding on the (AMD) GPU can be done on systems where ROCm -is installed and run. Possible future use for GPU acceleration of effects (contributions -welcome). +Decoding and encoding on the (AMD) GPU is possible with the default drivers. +On systems where ROCm is installed and run a future use for GPU acceleration +of effects could be implemented (contributions welcome). ## Multiple Graphics Cards If the computer has multiple graphics cards installed, you can choose which -should be used by libopenshot. Also, you can optionally use one card for +should be used by libopenshot. Also, you can optionally use one card for decoding and the other for encoding (if both cards support acceleration). ## Help Us Improve Hardware Support @@ -82,3 +82,17 @@ this document if you find an error or discover some new information. **Desperately Needed:** a way to compile ffmpeg 4.0 and up with working nVidia hardware acceleration support on Ubuntu Linux! + +**Needed:** a way to get the options and limits of the GPU, like +supported codecs and the supported dimensions (width and height). + +**Would be nice:** a way in python to only have some source for the desired +plattform. Example: VAAPI is not supported in Windows or Mac and should not +be displayed as an option for encoder libraries. + +**Further improvement:** Right now the frame can be decoded on the GPU but the +frame is then copied to CPU memory. Before encoding the frame the frame is then +copied to GPU memory for encoding. That is necessary because the modifications +are done by the CPU. Using the GPU for that too will make it possible to do +away with these two copies. A possible solution would be to use Vulkan compute +which would be available on Linux and Windows natively and on MacOS via MoltenVK. From f6465e3a786f6796210b2d7db66b890d810847a6 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Thu, 18 Apr 2019 21:05:31 -0500 Subject: [PATCH 147/223] Experimental Python install path logic --- src/bindings/python/CMakeLists.txt | 39 ++++++++++++++++++------------ 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/src/bindings/python/CMakeLists.txt b/src/bindings/python/CMakeLists.txt index c8686097..2a481aa7 100644 --- a/src/bindings/python/CMakeLists.txt +++ b/src/bindings/python/CMakeLists.txt @@ -30,10 +30,10 @@ FIND_PACKAGE(SWIG 2.0 REQUIRED) INCLUDE(${SWIG_USE_FILE}) ### Enable some legacy SWIG behaviors, in newer CMAKEs -if (CMAKE_VERSION VERSION_GREATER 3.13) +if (POLICY CMP0078) cmake_policy(SET CMP0078 OLD) endif() -if (CMAKE_VERSION VERSION_GREATER 3.14) +if (POLICY CMP0086) cmake_policy(SET CMP0086 OLD) endif() @@ -65,20 +65,29 @@ if (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) target_link_libraries(${SWIG_MODULE_pyopenshot_REAL_NAME} ${PYTHON_LIBRARIES} openshot) - ### FIND THE PYTHON INTERPRETER (AND THE SITE PACKAGES FOLDER) - execute_process ( COMMAND ${PYTHON_EXECUTABLE} -c "\ -import site; from distutils.sysconfig import get_python_lib; \ -print( get_python_lib( plat_specific=True, standard_lib=True, prefix='${CMAKE_INSTALL_PREFIX}' ) \ - + '/' + get_python_lib( plat_specific=False, standard_lib=False, prefix='${CMAKE_INSTALL_PREFIX}' ).split('/')[-1] \ - + '/' )" - OUTPUT_VARIABLE _ABS_PYTHON_MODULE_PATH - OUTPUT_STRIP_TRAILING_WHITESPACE ) + ### Check if the following Debian-friendly python module path exists + SET(PYTHON_MODULE_PATH "${CMAKE_INSTALL_PREFIX}/lib/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/site-packages") + if (NOT EXISTS ${PYTHON_MODULE_PATH}) - GET_FILENAME_COMPONENT(_ABS_PYTHON_MODULE_PATH - "${_ABS_PYTHON_MODULE_PATH}" ABSOLUTE) - FILE(RELATIVE_PATH _REL_PYTHON_MODULE_PATH - ${CMAKE_INSTALL_PREFIX} ${_ABS_PYTHON_MODULE_PATH}) - SET(PYTHON_MODULE_PATH ${_REL_PYTHON_MODULE_PATH}) + ### Check if another Debian-friendly python module path exists + SET(PYTHON_MODULE_PATH "${CMAKE_INSTALL_PREFIX}/lib/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/dist-packages") + if (NOT EXISTS ${PYTHON_MODULE_PATH}) + + ### Calculate the python module path (using distutils) + execute_process ( COMMAND ${PYTHON_EXECUTABLE} -c "\ +from distutils.sysconfig import get_python_lib; \ +print( get_python_lib( plat_specific=True, prefix='${CMAKE_INSTALL_PREFIX}' ) )" + OUTPUT_VARIABLE _ABS_PYTHON_MODULE_PATH + OUTPUT_STRIP_TRAILING_WHITESPACE ) + + GET_FILENAME_COMPONENT(_ABS_PYTHON_MODULE_PATH + "${_ABS_PYTHON_MODULE_PATH}" ABSOLUTE) + FILE(RELATIVE_PATH _REL_PYTHON_MODULE_PATH + ${CMAKE_INSTALL_PREFIX} ${_ABS_PYTHON_MODULE_PATH}) + SET(PYTHON_MODULE_PATH ${_ABS_PYTHON_MODULE_PATH}) + endif() + endif() + message("PYTHON_MODULE_PATH: ${PYTHON_MODULE_PATH}") ############### INSTALL HEADERS & LIBRARY ################ ### Install Python bindings From 4c65804d4547cb61343116ea5c349a3653bec4bb Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Fri, 19 Apr 2019 15:47:29 -0700 Subject: [PATCH 148/223] User interface is now usable --- doc/HW-ACCEL.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/doc/HW-ACCEL.md b/doc/HW-ACCEL.md index c11d2adc..3f750e6e 100644 --- a/doc/HW-ACCEL.md +++ b/doc/HW-ACCEL.md @@ -86,10 +86,6 @@ hardware acceleration support on Ubuntu Linux! **Needed:** a way to get the options and limits of the GPU, like supported codecs and the supported dimensions (width and height). -**Would be nice:** a way in python to only have some source for the desired -plattform. Example: VAAPI is not supported in Windows or Mac and should not -be displayed as an option for encoder libraries. - **Further improvement:** Right now the frame can be decoded on the GPU but the frame is then copied to CPU memory. Before encoding the frame the frame is then copied to GPU memory for encoding. That is necessary because the modifications From 825e38ac9d8f80eaeb3fd90cd6a2d9cfa1c55400 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Sun, 21 Apr 2019 10:04:24 -0700 Subject: [PATCH 149/223] Removing old way to select hardware support Removing the decode setting makes hardware supported decode break. There must be some hidden dependency on that variable somewhere which might also be responsible for the problems with nVidia on Linux. TODO Remove the dependency --- include/Settings.h | 6 +++++- src/Settings.cpp | 6 +++++- tests/Settings_Tests.cpp | 22 +++++++++++++--------- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/include/Settings.h b/include/Settings.h index 3e18bc9a..fc98ab4f 100644 --- a/include/Settings.h +++ b/include/Settings.h @@ -77,13 +77,17 @@ namespace openshot { public: /// Use video card for faster video decoding (if supported) + // REMOVE_HW_OLD + // Removing this breaks decode completely + // Find bug in libopenshot bool HARDWARE_DECODE = false; /// Use video codec for faster video decoding (if supported) int HARDWARE_DECODER = 0; /// Use video card for faster video encoding (if supported) - bool HARDWARE_ENCODE = false; + // REMOVE_HW_OLD + //bool HARDWARE_ENCODE = false; /// Scale mode used in FFmpeg decoding and encoding (used as an optimization for faster previews) bool HIGH_QUALITY_SCALING = false; diff --git a/src/Settings.cpp b/src/Settings.cpp index 461f9183..e0f8e693 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -40,9 +40,13 @@ Settings *Settings::Instance() if (!m_pInstance) { // Create the actual instance of logger only once m_pInstance = new Settings; + // REMOVE_HW_OLD + // Removing this breaks decode completely + // Find bug in libopenshot m_pInstance->HARDWARE_DECODE = false; m_pInstance->HARDWARE_DECODER = 0; - m_pInstance->HARDWARE_ENCODE = false; + // REMOVE_HW_OLD + //m_pInstance->HARDWARE_ENCODE = false; m_pInstance->HIGH_QUALITY_SCALING = false; m_pInstance->MAX_WIDTH = 0; m_pInstance->MAX_HEIGHT = 0; diff --git a/tests/Settings_Tests.cpp b/tests/Settings_Tests.cpp index 86790653..f76d60ba 100644 --- a/tests/Settings_Tests.cpp +++ b/tests/Settings_Tests.cpp @@ -36,8 +36,9 @@ TEST(Settings_Default_Constructor) // Create an empty color Settings *s = Settings::Instance(); - CHECK_EQUAL(false, s->HARDWARE_DECODE); - CHECK_EQUAL(false, s->HARDWARE_ENCODE); + CHECK_EQUAL(1, s->HARDWARE_DECODER); + // REMOVE_HW_OLD + //CHECK_EQUAL(false, s->HARDWARE_ENCODE); CHECK_EQUAL(false, s->HIGH_QUALITY_SCALING); CHECK_EQUAL(false, s->WAIT_FOR_VIDEO_PROCESSING_TASK); } @@ -46,18 +47,21 @@ TEST(Settings_Change_Settings) { // Create an empty color Settings *s = Settings::Instance(); - s->HARDWARE_DECODE = true; - s->HARDWARE_ENCODE = true; + s->HARDWARE_DECODER = 1; + // REMOVE_HW_OLD + //s->HARDWARE_ENCODE = true; s->HIGH_QUALITY_SCALING = true; s->WAIT_FOR_VIDEO_PROCESSING_TASK = true; - CHECK_EQUAL(true, s->HARDWARE_DECODE); - CHECK_EQUAL(true, s->HARDWARE_ENCODE); + CHECK_EQUAL(1, s->HARDWARE_DECODER); + // REMOVE_HW_OLD + //CHECK_EQUAL(true, s->HARDWARE_ENCODE); CHECK_EQUAL(true, s->HIGH_QUALITY_SCALING); CHECK_EQUAL(true, s->WAIT_FOR_VIDEO_PROCESSING_TASK); - CHECK_EQUAL(true, s->HARDWARE_DECODE); - CHECK_EQUAL(true, s->HARDWARE_ENCODE); + CHECK_EQUAL(1, s->HARDWARE_DECODER); + // REMOVE_HW_OLD + //CHECK_EQUAL(true, s->HARDWARE_ENCODE); CHECK_EQUAL(true, Settings::Instance()->HIGH_QUALITY_SCALING); CHECK_EQUAL(true, Settings::Instance()->WAIT_FOR_VIDEO_PROCESSING_TASK); -} \ No newline at end of file +} From bb561ae4e2102505890760f23f80eee19d54590f Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Sun, 21 Apr 2019 10:17:31 -0700 Subject: [PATCH 150/223] Temporarily disable test for DECODER --- tests/Settings_Tests.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Settings_Tests.cpp b/tests/Settings_Tests.cpp index f76d60ba..bc127805 100644 --- a/tests/Settings_Tests.cpp +++ b/tests/Settings_Tests.cpp @@ -36,7 +36,7 @@ TEST(Settings_Default_Constructor) // Create an empty color Settings *s = Settings::Instance(); - CHECK_EQUAL(1, s->HARDWARE_DECODER); + //CHECK_EQUAL(1, s->HARDWARE_DECODER); // REMOVE_HW_OLD //CHECK_EQUAL(false, s->HARDWARE_ENCODE); CHECK_EQUAL(false, s->HIGH_QUALITY_SCALING); @@ -47,19 +47,19 @@ TEST(Settings_Change_Settings) { // Create an empty color Settings *s = Settings::Instance(); - s->HARDWARE_DECODER = 1; + //s->HARDWARE_DECODER = 1; // REMOVE_HW_OLD //s->HARDWARE_ENCODE = true; s->HIGH_QUALITY_SCALING = true; s->WAIT_FOR_VIDEO_PROCESSING_TASK = true; - CHECK_EQUAL(1, s->HARDWARE_DECODER); + //CHECK_EQUAL(1, s->HARDWARE_DECODER); // REMOVE_HW_OLD //CHECK_EQUAL(true, s->HARDWARE_ENCODE); CHECK_EQUAL(true, s->HIGH_QUALITY_SCALING); CHECK_EQUAL(true, s->WAIT_FOR_VIDEO_PROCESSING_TASK); - CHECK_EQUAL(1, s->HARDWARE_DECODER); + //CHECK_EQUAL(1, s->HARDWARE_DECODER); // REMOVE_HW_OLD //CHECK_EQUAL(true, s->HARDWARE_ENCODE); CHECK_EQUAL(true, Settings::Instance()->HIGH_QUALITY_SCALING); From 0e77fbdc3b967b97bed8454cda4fa0e97185e232 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Sun, 21 Apr 2019 10:53:53 -0700 Subject: [PATCH 151/223] Re-anable the DECODER test --- tests/Settings_Tests.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Settings_Tests.cpp b/tests/Settings_Tests.cpp index bc127805..82690bbe 100644 --- a/tests/Settings_Tests.cpp +++ b/tests/Settings_Tests.cpp @@ -36,7 +36,7 @@ TEST(Settings_Default_Constructor) // Create an empty color Settings *s = Settings::Instance(); - //CHECK_EQUAL(1, s->HARDWARE_DECODER); + CHECK_EQUAL(0, s->HARDWARE_DECODER); // REMOVE_HW_OLD //CHECK_EQUAL(false, s->HARDWARE_ENCODE); CHECK_EQUAL(false, s->HIGH_QUALITY_SCALING); @@ -47,19 +47,19 @@ TEST(Settings_Change_Settings) { // Create an empty color Settings *s = Settings::Instance(); - //s->HARDWARE_DECODER = 1; + s->HARDWARE_DECODER = 1; // REMOVE_HW_OLD //s->HARDWARE_ENCODE = true; s->HIGH_QUALITY_SCALING = true; s->WAIT_FOR_VIDEO_PROCESSING_TASK = true; - //CHECK_EQUAL(1, s->HARDWARE_DECODER); + CHECK_EQUAL(1, s->HARDWARE_DECODER); // REMOVE_HW_OLD //CHECK_EQUAL(true, s->HARDWARE_ENCODE); CHECK_EQUAL(true, s->HIGH_QUALITY_SCALING); CHECK_EQUAL(true, s->WAIT_FOR_VIDEO_PROCESSING_TASK); - //CHECK_EQUAL(1, s->HARDWARE_DECODER); + CHECK_EQUAL(1, s->HARDWARE_DECODER); // REMOVE_HW_OLD //CHECK_EQUAL(true, s->HARDWARE_ENCODE); CHECK_EQUAL(true, Settings::Instance()->HIGH_QUALITY_SCALING); From e6efea7a929e21c13e016cef0ba569df4fb653fa Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Sun, 21 Apr 2019 11:31:58 -0700 Subject: [PATCH 152/223] Update documentation --- doc/HW-ACCEL.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/doc/HW-ACCEL.md b/doc/HW-ACCEL.md index 3f750e6e..ed886a5f 100644 --- a/doc/HW-ACCEL.md +++ b/doc/HW-ACCEL.md @@ -23,15 +23,12 @@ The following settings are use by libopenshot to enable, disable, and control the various hardware acceleration features. ``` -/// Use video card for faster video decoding (if supported) +/// DEPRECATED Use video card for faster video decoding (if supported) bool HARDWARE_DECODE = false; /// Use video codec for faster video decoding (if supported) int HARDWARE_DECODER = 0; -/// Use video card for faster video encoding (if supported) -bool HARDWARE_ENCODE = false; - /// Number of threads of OpenMP int OMP_THREADS = 12; @@ -83,6 +80,11 @@ this document if you find an error or discover some new information. **Desperately Needed:** a way to compile ffmpeg 4.0 and up with working nVidia hardware acceleration support on Ubuntu Linux! +**BUG:** the use of HARDWARE_DECODE is somehow still needed, otherwise hardware +supported decoding breaks. TODO remove the hidden dependency on this variable. +The variable HARDWARE_DECODER should and does select if and which hardware +decoder is used but somehow HARDWARE_DECODE has to be there too. + **Needed:** a way to get the options and limits of the GPU, like supported codecs and the supported dimensions (width and height). From 65d9134722440d1812e095fb25910046a0dd9677 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Sun, 21 Apr 2019 12:18:44 -0700 Subject: [PATCH 153/223] Remove DECODE varaible. Turn out that a buggy graphic driver was the problem. --- doc/HW-ACCEL.md | 12 +++++------- include/Settings.h | 4 +--- src/Settings.cpp | 4 +--- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/doc/HW-ACCEL.md b/doc/HW-ACCEL.md index ed886a5f..9a8b78b9 100644 --- a/doc/HW-ACCEL.md +++ b/doc/HW-ACCEL.md @@ -23,9 +23,6 @@ The following settings are use by libopenshot to enable, disable, and control the various hardware acceleration features. ``` -/// DEPRECATED Use video card for faster video decoding (if supported) -bool HARDWARE_DECODE = false; - /// Use video codec for faster video decoding (if supported) int HARDWARE_DECODER = 0; @@ -80,10 +77,11 @@ this document if you find an error or discover some new information. **Desperately Needed:** a way to compile ffmpeg 4.0 and up with working nVidia hardware acceleration support on Ubuntu Linux! -**BUG:** the use of HARDWARE_DECODE is somehow still needed, otherwise hardware -supported decoding breaks. TODO remove the hidden dependency on this variable. -The variable HARDWARE_DECODER should and does select if and which hardware -decoder is used but somehow HARDWARE_DECODE has to be there too. +**BUG:** hardware supported decoding still has a bug. The speed gains with +decoding are by far not as great as with encoding. In case hardware accelerated +decoding does not work disable it. +Hardware acceleration might also break because of graphics drivers that have +bugs. **Needed:** a way to get the options and limits of the GPU, like supported codecs and the supported dimensions (width and height). diff --git a/include/Settings.h b/include/Settings.h index fc98ab4f..1ea61335 100644 --- a/include/Settings.h +++ b/include/Settings.h @@ -78,9 +78,7 @@ namespace openshot { public: /// Use video card for faster video decoding (if supported) // REMOVE_HW_OLD - // Removing this breaks decode completely - // Find bug in libopenshot - bool HARDWARE_DECODE = false; + //bool HARDWARE_DECODE = false; /// Use video codec for faster video decoding (if supported) int HARDWARE_DECODER = 0; diff --git a/src/Settings.cpp b/src/Settings.cpp index e0f8e693..e838cc51 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -41,9 +41,7 @@ Settings *Settings::Instance() // Create the actual instance of logger only once m_pInstance = new Settings; // REMOVE_HW_OLD - // Removing this breaks decode completely - // Find bug in libopenshot - m_pInstance->HARDWARE_DECODE = false; + //m_pInstance->HARDWARE_DECODE = false; m_pInstance->HARDWARE_DECODER = 0; // REMOVE_HW_OLD //m_pInstance->HARDWARE_ENCODE = false; From 79335277cba0d001faf1904b1e9ca907d52c4f4e Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sun, 21 Apr 2019 14:37:06 -0500 Subject: [PATCH 154/223] Removing old commented out code --- doc/HW-ACCEL.md | 13 ++++++------- include/Settings.h | 8 -------- src/Settings.cpp | 4 ---- tests/Settings_Tests.cpp | 8 -------- 4 files changed, 6 insertions(+), 27 deletions(-) diff --git a/doc/HW-ACCEL.md b/doc/HW-ACCEL.md index 9a8b78b9..7ed4c637 100644 --- a/doc/HW-ACCEL.md +++ b/doc/HW-ACCEL.md @@ -74,20 +74,19 @@ This information might be wrong, and we would love to continue improving our support for hardware acceleration in OpenShot. Please help us update this document if you find an error or discover some new information. -**Desperately Needed:** a way to compile ffmpeg 4.0 and up with working nVidia +**Desperately Needed:** A way to compile ffmpeg 4.0 and up with working nVidia hardware acceleration support on Ubuntu Linux! -**BUG:** hardware supported decoding still has a bug. The speed gains with +**BUG:** Hardware supported decoding still has a bug. The speed gains with decoding are by far not as great as with encoding. In case hardware accelerated -decoding does not work disable it. -Hardware acceleration might also break because of graphics drivers that have -bugs. +decoding does not work disable it. Hardware acceleration might also break +because of graphics drivers that have bugs. -**Needed:** a way to get the options and limits of the GPU, like +**Needed:** A way to get the options and limits of the GPU, like supported codecs and the supported dimensions (width and height). **Further improvement:** Right now the frame can be decoded on the GPU but the -frame is then copied to CPU memory. Before encoding the frame the frame is then +frame is then copied to CPU memory. Before encoding the frame is then copied to GPU memory for encoding. That is necessary because the modifications are done by the CPU. Using the GPU for that too will make it possible to do away with these two copies. A possible solution would be to use Vulkan compute diff --git a/include/Settings.h b/include/Settings.h index 1ea61335..26edf464 100644 --- a/include/Settings.h +++ b/include/Settings.h @@ -76,17 +76,9 @@ namespace openshot { static Settings * m_pInstance; public: - /// Use video card for faster video decoding (if supported) - // REMOVE_HW_OLD - //bool HARDWARE_DECODE = false; - /// Use video codec for faster video decoding (if supported) int HARDWARE_DECODER = 0; - /// Use video card for faster video encoding (if supported) - // REMOVE_HW_OLD - //bool HARDWARE_ENCODE = false; - /// Scale mode used in FFmpeg decoding and encoding (used as an optimization for faster previews) bool HIGH_QUALITY_SCALING = false; diff --git a/src/Settings.cpp b/src/Settings.cpp index e838cc51..99b059e8 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -40,11 +40,7 @@ Settings *Settings::Instance() if (!m_pInstance) { // Create the actual instance of logger only once m_pInstance = new Settings; - // REMOVE_HW_OLD - //m_pInstance->HARDWARE_DECODE = false; m_pInstance->HARDWARE_DECODER = 0; - // REMOVE_HW_OLD - //m_pInstance->HARDWARE_ENCODE = false; m_pInstance->HIGH_QUALITY_SCALING = false; m_pInstance->MAX_WIDTH = 0; m_pInstance->MAX_HEIGHT = 0; diff --git a/tests/Settings_Tests.cpp b/tests/Settings_Tests.cpp index 82690bbe..1b8180c9 100644 --- a/tests/Settings_Tests.cpp +++ b/tests/Settings_Tests.cpp @@ -37,8 +37,6 @@ TEST(Settings_Default_Constructor) Settings *s = Settings::Instance(); CHECK_EQUAL(0, s->HARDWARE_DECODER); - // REMOVE_HW_OLD - //CHECK_EQUAL(false, s->HARDWARE_ENCODE); CHECK_EQUAL(false, s->HIGH_QUALITY_SCALING); CHECK_EQUAL(false, s->WAIT_FOR_VIDEO_PROCESSING_TASK); } @@ -48,20 +46,14 @@ TEST(Settings_Change_Settings) // Create an empty color Settings *s = Settings::Instance(); s->HARDWARE_DECODER = 1; - // REMOVE_HW_OLD - //s->HARDWARE_ENCODE = true; s->HIGH_QUALITY_SCALING = true; s->WAIT_FOR_VIDEO_PROCESSING_TASK = true; CHECK_EQUAL(1, s->HARDWARE_DECODER); - // REMOVE_HW_OLD - //CHECK_EQUAL(true, s->HARDWARE_ENCODE); CHECK_EQUAL(true, s->HIGH_QUALITY_SCALING); CHECK_EQUAL(true, s->WAIT_FOR_VIDEO_PROCESSING_TASK); CHECK_EQUAL(1, s->HARDWARE_DECODER); - // REMOVE_HW_OLD - //CHECK_EQUAL(true, s->HARDWARE_ENCODE); CHECK_EQUAL(true, Settings::Instance()->HIGH_QUALITY_SCALING); CHECK_EQUAL(true, Settings::Instance()->WAIT_FOR_VIDEO_PROCESSING_TASK); } From 140fbaddff7e26e472474631765a5956b0b4075c Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Tue, 23 Apr 2019 16:45:02 -0500 Subject: [PATCH 155/223] Added new AudioDeviceInfo struct, and populate a vector of them on QtPlayer initialization. This allows a user to overwrite the preferred audio device by using the setting PLAYBACK_AUDIO_DEVICE_NAME. --- include/AudioDeviceInfo.h | 43 ++++++++++++++++++++++++++++++++ include/Qt/AudioPlaybackThread.h | 14 ++++++++--- include/QtPlayer.h | 3 +++ include/Settings.h | 3 +++ src/Qt/AudioPlaybackThread.cpp | 25 +++++++++++++++---- src/QtPlayer.cpp | 11 +++++++- src/Settings.cpp | 2 +- src/bindings/python/openshot.i | 3 +++ src/bindings/ruby/openshot.i | 3 +++ 9 files changed, 97 insertions(+), 10 deletions(-) create mode 100644 include/AudioDeviceInfo.h diff --git a/include/AudioDeviceInfo.h b/include/AudioDeviceInfo.h new file mode 100644 index 00000000..29a89139 --- /dev/null +++ b/include/AudioDeviceInfo.h @@ -0,0 +1,43 @@ +/** + * @file + * @brief Header file for Audio Device Info struct + * @author Jonathan Thomas + * + * @section LICENSE + * + * Copyright (c) 2008-2014 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 . + */ + +#ifndef OPENSHOT_AUDIODEVICEINFO_H +#define OPENSHOT_AUDIODEVICEINFO_H + + +/** + * @brief This struct hold information about Audio Devices + * + * The type and name of the audio device. + */ +struct AudioDeviceInfo +{ + string name; + string type; +}; + +#endif \ No newline at end of file diff --git a/include/Qt/AudioPlaybackThread.h b/include/Qt/AudioPlaybackThread.h index 1d654756..be26c4e8 100644 --- a/include/Qt/AudioPlaybackThread.h +++ b/include/Qt/AudioPlaybackThread.h @@ -32,6 +32,8 @@ #include "../ReaderBase.h" #include "../RendererBase.h" #include "../AudioReaderSource.h" +#include "../AudioDeviceInfo.h" +#include "../Settings.h" namespace openshot { @@ -66,8 +68,11 @@ namespace openshot /// Error found during JUCE initialise method string initialise_error; - /// Create or get an instance of this singleton (invoke the class with this method) - static AudioDeviceManagerSingleton * Instance(int numChannels); + /// List of valid audio device names + vector audio_device_names; + + /// Override with no channels and no preferred audio device + static AudioDeviceManagerSingleton * Instance(); /// Public device manager property AudioDeviceManager audioDeviceManager; @@ -126,7 +131,10 @@ namespace openshot int getSpeed() const { if (source) return source->getSpeed(); else return 1; } /// Get Audio Error (if any) - string getError() { return AudioDeviceManagerSingleton::Instance(numChannels)->initialise_error; } + string getError() { return AudioDeviceManagerSingleton::Instance()->initialise_error; } + + /// Get Audio Device Names (if any) + vector getAudioDeviceNames() { return AudioDeviceManagerSingleton::Instance()->audio_device_names; }; friend class PlayerPrivate; friend class QtPlayer; diff --git a/include/QtPlayer.h b/include/QtPlayer.h index a1a7ee0c..c9137f5e 100644 --- a/include/QtPlayer.h +++ b/include/QtPlayer.h @@ -62,6 +62,9 @@ namespace openshot /// Get Error (if any) string GetError(); + /// Get Audio Devices from JUCE + vector GetAudioDeviceNames(); + /// Play the video void Play(); diff --git a/include/Settings.h b/include/Settings.h index 26edf464..859b2fab 100644 --- a/include/Settings.h +++ b/include/Settings.h @@ -109,6 +109,9 @@ namespace openshot { /// Which GPU to use to encode (0 is the first) int HW_EN_DEVICE_SET = 0; + /// The audio device name to use during playback + string PLAYBACK_AUDIO_DEVICE_NAME = ""; + /// Create or get an instance of this logger singleton (invoke the class with this method) static Settings * Instance(); }; diff --git a/src/Qt/AudioPlaybackThread.cpp b/src/Qt/AudioPlaybackThread.cpp index c64bd688..28db8df6 100644 --- a/src/Qt/AudioPlaybackThread.cpp +++ b/src/Qt/AudioPlaybackThread.cpp @@ -35,7 +35,7 @@ namespace openshot AudioDeviceManagerSingleton *AudioDeviceManagerSingleton::m_pInstance = NULL; // Create or Get an instance of the device manager singleton - AudioDeviceManagerSingleton *AudioDeviceManagerSingleton::Instance(int numChannels) + AudioDeviceManagerSingleton *AudioDeviceManagerSingleton::Instance() { if (!m_pInstance) { // Create the actual instance of device manager only once @@ -44,9 +44,10 @@ namespace openshot // Initialize audio device only 1 time String error = m_pInstance->audioDeviceManager.initialise ( 0, /* number of input channels */ - numChannels, /* number of output channels */ + 2, /* number of output channels */ 0, /* no XML settings.. */ - true /* select default device on failure */); + true, /* select default device on failure */ + Settings::Instance()->PLAYBACK_AUDIO_DEVICE_NAME /* preferredDefaultDeviceName */); // Persist any errors detected if (error.isNotEmpty()) { @@ -54,6 +55,20 @@ namespace openshot } else { m_pInstance->initialise_error = ""; } + + // Get all audio device names + for (int i = 0; i < m_pInstance->audioDeviceManager.getAvailableDeviceTypes().size(); ++i) + { + const AudioIODeviceType* t = m_pInstance->audioDeviceManager.getAvailableDeviceTypes()[i]; + const StringArray deviceNames = t->getDeviceNames (); + + for (int j = 0; j < deviceNames.size (); ++j ) + { + const String deviceName = deviceNames[j]; + AudioDeviceInfo deviceInfo = {deviceName.toStdString(), t->getTypeName().toStdString()}; + m_pInstance->audio_device_names.push_back(deviceInfo); + } + } } return m_pInstance; @@ -149,7 +164,7 @@ namespace openshot // Start new audio device (or get existing one) // Add callback - AudioDeviceManagerSingleton::Instance(numChannels)->audioDeviceManager.addAudioCallback(&player); + AudioDeviceManagerSingleton::Instance()->audioDeviceManager.addAudioCallback(&player); // Create TimeSliceThread for audio buffering time_thread.startThread(); @@ -182,7 +197,7 @@ namespace openshot transport.setSource(NULL); player.setSource(NULL); - AudioDeviceManagerSingleton::Instance(0)->audioDeviceManager.removeAudioCallback(&player); + AudioDeviceManagerSingleton::Instance()->audioDeviceManager.removeAudioCallback(&player); // Remove source delete source; diff --git a/src/QtPlayer.cpp b/src/QtPlayer.cpp index 4f53c7ca..3287c19d 100644 --- a/src/QtPlayer.cpp +++ b/src/QtPlayer.cpp @@ -56,7 +56,7 @@ QtPlayer::~QtPlayer() void QtPlayer::CloseAudioDevice() { // Close audio device (only do this once, when all audio playback is finished) - AudioDeviceManagerSingleton::Instance(0)->CloseAudioDevice(); + AudioDeviceManagerSingleton::Instance()->CloseAudioDevice(); } // Return any error string during initialization @@ -69,6 +69,15 @@ string QtPlayer::GetError() { } } +/// Get Audio Devices from JUCE +vector QtPlayer::GetAudioDeviceNames() { + if (reader && threads_started) { + return p->audioPlayback->getAudioDeviceNames(); + } else { + return vector(); + } +} + void QtPlayer::SetSource(const std::string &source) { FFmpegReader *ffreader = new FFmpegReader(source); diff --git a/src/Settings.cpp b/src/Settings.cpp index 99b059e8..8193ec6b 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -51,7 +51,7 @@ Settings *Settings::Instance() m_pInstance->DE_LIMIT_WIDTH_MAX = 1950; m_pInstance->HW_DE_DEVICE_SET = 0; m_pInstance->HW_EN_DEVICE_SET = 0; - + m_pInstance->PLAYBACK_AUDIO_DEVICE_NAME = ""; } return m_pInstance; diff --git a/src/bindings/python/openshot.i b/src/bindings/python/openshot.i index de1f020c..ed34b658 100644 --- a/src/bindings/python/openshot.i +++ b/src/bindings/python/openshot.i @@ -87,6 +87,7 @@ #include "../../../include/Settings.h" #include "../../../include/Timeline.h" #include "../../../include/ZmqLogger.h" +#include "../../../include/AudioDeviceInfo.h" %} @@ -154,6 +155,7 @@ %include "../../../include/Settings.h" %include "../../../include/Timeline.h" %include "../../../include/ZmqLogger.h" +%include "../../../include/AudioDeviceInfo.h" #ifdef USE_IMAGEMAGICK %include "../../../include/ImageReader.h" @@ -187,4 +189,5 @@ namespace std { %template(FieldVector) vector; %template(MappedFrameVector) vector; %template(MappedMetadata) map; + %template(AudioDeviceInfoVector) vector; } diff --git a/src/bindings/ruby/openshot.i b/src/bindings/ruby/openshot.i index b9a35d41..1cd9bb75 100644 --- a/src/bindings/ruby/openshot.i +++ b/src/bindings/ruby/openshot.i @@ -91,6 +91,7 @@ namespace std { #include "../../../include/Settings.h" #include "../../../include/Timeline.h" #include "../../../include/ZmqLogger.h" +#include "../../../include/AudioDeviceInfo.h" %} @@ -147,6 +148,7 @@ namespace std { %include "../../../include/Settings.h" %include "../../../include/Timeline.h" %include "../../../include/ZmqLogger.h" +%include "../../../include/AudioDeviceInfo.h" #ifdef USE_IMAGEMAGICK %include "../../../include/ImageReader.h" @@ -181,4 +183,5 @@ namespace std { %template(FieldVector) vector; %template(MappedFrameVector) vector; %template(MappedMetadata) map; + %template(AudioDeviceInfoVector) vector; } From a69c34ffbb49c5f2a79a5f124e8bf9b2b41df8ff Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Wed, 24 Apr 2019 09:41:52 -0500 Subject: [PATCH 156/223] Small refactor to audio device manager initialise (to prevent compile breakage on Mac) --- src/Qt/AudioPlaybackThread.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Qt/AudioPlaybackThread.cpp b/src/Qt/AudioPlaybackThread.cpp index 28db8df6..d31f719e 100644 --- a/src/Qt/AudioPlaybackThread.cpp +++ b/src/Qt/AudioPlaybackThread.cpp @@ -41,13 +41,16 @@ namespace openshot // Create the actual instance of device manager only once m_pInstance = new AudioDeviceManagerSingleton; + // Get preferred audio device name (if any) + string preferred_audio_device = Settings::Instance()->PLAYBACK_AUDIO_DEVICE_NAME; + // Initialize audio device only 1 time String error = m_pInstance->audioDeviceManager.initialise ( 0, /* number of input channels */ 2, /* number of output channels */ 0, /* no XML settings.. */ true, /* select default device on failure */ - Settings::Instance()->PLAYBACK_AUDIO_DEVICE_NAME /* preferredDefaultDeviceName */); + preferred_audio_device /* preferredDefaultDeviceName */); // Persist any errors detected if (error.isNotEmpty()) { From ef2ed569065beaf28049b2b9767fcbc6dfa84aa6 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Wed, 24 Apr 2019 10:08:22 -0500 Subject: [PATCH 157/223] More refactoring for Mac compile breakage --- src/Qt/AudioPlaybackThread.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Qt/AudioPlaybackThread.cpp b/src/Qt/AudioPlaybackThread.cpp index d31f719e..678a9c09 100644 --- a/src/Qt/AudioPlaybackThread.cpp +++ b/src/Qt/AudioPlaybackThread.cpp @@ -42,10 +42,10 @@ namespace openshot m_pInstance = new AudioDeviceManagerSingleton; // Get preferred audio device name (if any) - string preferred_audio_device = Settings::Instance()->PLAYBACK_AUDIO_DEVICE_NAME; + juce::String preferred_audio_device = juce::String(Settings::Instance()->PLAYBACK_AUDIO_DEVICE_NAME.c_str()); // Initialize audio device only 1 time - String error = m_pInstance->audioDeviceManager.initialise ( + juce::String error = m_pInstance->audioDeviceManager.initialise ( 0, /* number of input channels */ 2, /* number of output channels */ 0, /* no XML settings.. */ @@ -67,7 +67,7 @@ namespace openshot for (int j = 0; j < deviceNames.size (); ++j ) { - const String deviceName = deviceNames[j]; + juce::String deviceName = deviceNames[j]; AudioDeviceInfo deviceInfo = {deviceName.toStdString(), t->getTypeName().toStdString()}; m_pInstance->audio_device_names.push_back(deviceInfo); } From 49831a24b00ea46b749fbd6fe9101fb74fbc6972 Mon Sep 17 00:00:00 2001 From: Sergey Parfenyuk Date: Sat, 27 Apr 2019 12:37:24 +0200 Subject: [PATCH 158/223] Add virtual destructor for abstract classes --- include/CacheBase.h | 1 + include/ClipBase.h | 1 + include/EffectBase.h | 1 + include/PlayerBase.h | 1 + include/ReaderBase.h | 2 ++ include/WriterBase.h | 2 ++ 6 files changed, 8 insertions(+) diff --git a/include/CacheBase.h b/include/CacheBase.h index aaef5320..a7f8c88f 100644 --- a/include/CacheBase.h +++ b/include/CacheBase.h @@ -109,6 +109,7 @@ namespace openshot { virtual void SetJson(string value) = 0; ///< Load JSON string into this object virtual Json::Value JsonValue() = 0; ///< Generate Json::JsonValue for this object virtual void SetJsonValue(Json::Value root) = 0; ///< Load Json::JsonValue into this object + virtual ~CacheBase() = default; }; diff --git a/include/ClipBase.h b/include/ClipBase.h index 3dae8a53..4678f25a 100644 --- a/include/ClipBase.h +++ b/include/ClipBase.h @@ -101,6 +101,7 @@ namespace openshot { /// of all properties at any time) virtual string PropertiesJSON(int64_t requested_frame) = 0; + virtual ~ClipBase() = default; }; diff --git a/include/EffectBase.h b/include/EffectBase.h index d38e3f45..26add05d 100644 --- a/include/EffectBase.h +++ b/include/EffectBase.h @@ -105,6 +105,7 @@ namespace openshot /// Set the order that this effect should be executed. void Order(int new_order) { order = new_order; } + virtual ~EffectBase() = default; }; } diff --git a/include/PlayerBase.h b/include/PlayerBase.h index ecc222a8..f1ce2d80 100644 --- a/include/PlayerBase.h +++ b/include/PlayerBase.h @@ -104,6 +104,7 @@ namespace openshot /// Set the Volume (1.0 = normal volume, <1.0 = quieter, >1.0 louder) virtual void Volume(float new_volume) = 0; + virtual ~PlayerBase() = default; }; } diff --git a/include/ReaderBase.h b/include/ReaderBase.h index b0a1b3db..ce665f1d 100644 --- a/include/ReaderBase.h +++ b/include/ReaderBase.h @@ -147,6 +147,8 @@ namespace openshot /// Open the reader (and start consuming resources, such as images or video files) virtual void Open() = 0; + + virtual ~ReaderBase() = default; }; } diff --git a/include/WriterBase.h b/include/WriterBase.h index 8f424054..3fd80c7f 100644 --- a/include/WriterBase.h +++ b/include/WriterBase.h @@ -116,6 +116,8 @@ namespace openshot /// Open the writer (and start initializing streams) virtual void Open() = 0; + + virtual ~WriterBase() = default; }; } From 665a03f9e23973a626abcc9520071fe1f6644142 Mon Sep 17 00:00:00 2001 From: Sergey Parfenyuk Date: Sat, 27 Apr 2019 12:50:31 +0200 Subject: [PATCH 159/223] Fix logical statements --- src/AudioResampler.cpp | 4 ++-- src/KeyFrame.cpp | 2 +- src/QtImageReader.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/AudioResampler.cpp b/src/AudioResampler.cpp index 442a91d9..d7829ec5 100644 --- a/src/AudioResampler.cpp +++ b/src/AudioResampler.cpp @@ -74,9 +74,9 @@ AudioResampler::~AudioResampler() void AudioResampler::SetBuffer(AudioSampleBuffer *new_buffer, double sample_rate, double new_sample_rate) { if (sample_rate <= 0) - sample_rate == 44100; + sample_rate = 44100; if (new_sample_rate <= 0) - new_sample_rate == 44100; + new_sample_rate = 44100; // Set the sample ratio (the ratio of sample rate change) source_ratio = sample_rate / new_sample_rate; diff --git a/src/KeyFrame.cpp b/src/KeyFrame.cpp index 025484a3..a2c2362d 100644 --- a/src/KeyFrame.cpp +++ b/src/KeyFrame.cpp @@ -784,7 +784,7 @@ void Keyframe::ProcessSegment(int Segment, Point p1, Point p2) { // Add new value to the vector Coordinate new_coord(current_frame, current_value); - if (Segment == 0 || Segment > 0 && current_frame > p1.co.X) + if (Segment == 0 || (Segment > 0 && current_frame > p1.co.X)) // Add to "values" vector Values.push_back(new_coord); diff --git a/src/QtImageReader.cpp b/src/QtImageReader.cpp index c500d221..8cc2debc 100644 --- a/src/QtImageReader.cpp +++ b/src/QtImageReader.cpp @@ -209,7 +209,7 @@ std::shared_ptr QtImageReader::GetFrame(int64_t requested_frame) } // Scale image smaller (or use a previous scaled image) - if (!cached_image || (cached_image && cached_image->width() != max_width || cached_image->height() != max_height)) { + if (!cached_image || (cached_image->width() != max_width || cached_image->height() != max_height)) { #if USE_RESVG == 1 // If defined and found in CMake, utilize the libresvg for parsing From eea67ad97296e6c5ca475086cebd99ccc61c0496 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Sat, 27 Apr 2019 13:36:21 -0700 Subject: [PATCH 160/223] Link to instruction to produce ffmpeg 4 plus the libraries on Ubuntu that support nVidia GPU acceleration. Tested on Mint 19.1. --- doc/HW-ACCEL.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/doc/HW-ACCEL.md b/doc/HW-ACCEL.md index 7ed4c637..34e05e4f 100644 --- a/doc/HW-ACCEL.md +++ b/doc/HW-ACCEL.md @@ -74,8 +74,13 @@ This information might be wrong, and we would love to continue improving our support for hardware acceleration in OpenShot. Please help us update this document if you find an error or discover some new information. -**Desperately Needed:** A way to compile ffmpeg 4.0 and up with working nVidia -hardware acceleration support on Ubuntu Linux! +**Desperately Needed:** The manual at: +https://www.tal.org/tutorials/ffmpeg_nvidia_encode +works pretty well. I could compile and install a version of ffmpeg 4.1.3 +on Mint 19.1 that supports the GPU on nVidia cards. A version of openshot +with hardware support using these libraries could use the nVidia GPU. +(A way to compile ffmpeg 4.0 and up with working nVidia +hardware acceleration support on Ubuntu Linux!) **BUG:** Hardware supported decoding still has a bug. The speed gains with decoding are by far not as great as with encoding. In case hardware accelerated From 3bd2ae5f2394dd725363d66bf85979fa7c02c341 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sun, 28 Apr 2019 14:03:45 -0500 Subject: [PATCH 161/223] Integrating VDPAU decoding into libopenshot --- doc/HW-ACCEL.md | 6 ++++++ src/FFmpegReader.cpp | 21 +++++++++++++++++++++ src/examples/Example.cpp | 4 ++++ 3 files changed, 31 insertions(+) diff --git a/doc/HW-ACCEL.md b/doc/HW-ACCEL.md index 34e05e4f..b785a9ce 100644 --- a/doc/HW-ACCEL.md +++ b/doc/HW-ACCEL.md @@ -96,3 +96,9 @@ copied to GPU memory for encoding. That is necessary because the modifications are done by the CPU. Using the GPU for that too will make it possible to do away with these two copies. A possible solution would be to use Vulkan compute which would be available on Linux and Windows natively and on MacOS via MoltenVK. + +## Credit + +A big thanks to Peter M (https://github.com/eisneinechse) for all his work +on integrating hardware accelleration into libopenshot! The community thanks +you for this major contribution! diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 2ea6fcbb..929ddf72 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -186,6 +186,23 @@ static enum AVPixelFormat get_hw_dec_format_cu(AVCodecContext *ctx, const enum A ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::get_hw_dec_format_cu (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); return AV_PIX_FMT_NONE; } + +static enum AVPixelFormat get_hw_dec_format_vd(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) +{ + const enum AVPixelFormat *p; + + for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { + switch (*p) { + case AV_PIX_FMT_VDPAU: + hw_de_av_pix_fmt_global = AV_PIX_FMT_VDPAU; + hw_de_av_device_type_global = AV_HWDEVICE_TYPE_VDPAU; + return *p; + break; + } + } + ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::get_hw_dec_format_vd (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); + return AV_PIX_FMT_NONE; +} #endif #if defined(_WIN32) @@ -377,6 +394,10 @@ void FFmpegReader::Open() { hw_de_av_device_type = AV_HWDEVICE_TYPE_CUDA; pCodecCtx->get_format = get_hw_dec_format_cu; break; + case 6: + hw_de_av_device_type = AV_HWDEVICE_TYPE_VDPAU; + pCodecCtx->get_format = get_hw_dec_format_vd; + break; default: hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; pCodecCtx->get_format = get_hw_dec_format_va; diff --git a/src/examples/Example.cpp b/src/examples/Example.cpp index 411abdda..80339684 100644 --- a/src/examples/Example.cpp +++ b/src/examples/Example.cpp @@ -36,6 +36,10 @@ using namespace openshot; int main(int argc, char* argv[]) { + Settings *s = Settings::Instance(); + s->HARDWARE_DECODER = 2; // 1 VA-API, 2 NVDEC + s->HW_DE_DEVICE_SET = 1; + FFmpegReader r9("/home/jonathan/Videos/sintel_trailer-720p.mp4"); r9.Open(); r9.DisplayInfo(); From 2bafe60448eb72a82665b96a164558d61f4033fa Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sun, 28 Apr 2019 17:18:43 -0500 Subject: [PATCH 162/223] Removing 0 cases, and adding new QSV decoder support (experimental) --- src/FFmpegReader.cpp | 47 ++++++++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 929ddf72..480e835b 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -259,7 +259,7 @@ static enum AVPixelFormat get_hw_dec_format_cu(AVCodecContext *ctx, const enum A #endif #if defined(__APPLE__) -static enum AVPixelFormat get_hw_dec_format_qs(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) +static enum AVPixelFormat get_hw_dec_format_vt(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) { const enum AVPixelFormat *p; @@ -272,11 +272,28 @@ static enum AVPixelFormat get_hw_dec_format_qs(AVCodecContext *ctx, const enum A break; } } - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::get_hw_dec_format_qs (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); + ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::get_hw_dec_format_vt (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); return AV_PIX_FMT_NONE; } #endif +static enum AVPixelFormat get_hw_dec_format_qs(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) +{ + const enum AVPixelFormat *p; + + for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { + switch (*p) { + case AV_PIX_FMT_QSV: + hw_de_av_pix_fmt_global = AV_PIX_FMT_QSV; + hw_de_av_device_type_global = AV_HWDEVICE_TYPE_QSV; + return *p; + break; + } + } + ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::get_hw_dec_format_qs (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); + return AV_PIX_FMT_NONE; +} + int FFmpegReader::IsHardwareDecodeSupported(int codecid) { int ret; @@ -382,10 +399,6 @@ void FFmpegReader::Open() { adapter_ptr = adapter; i_decoder_hw = openshot::Settings::Instance()->HARDWARE_DECODER; switch (i_decoder_hw) { - case 0: - hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; - pCodecCtx->get_format = get_hw_dec_format_va; - break; case 1: hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; pCodecCtx->get_format = get_hw_dec_format_va; @@ -398,6 +411,10 @@ void FFmpegReader::Open() { hw_de_av_device_type = AV_HWDEVICE_TYPE_VDPAU; pCodecCtx->get_format = get_hw_dec_format_vd; break; + case 7: + hw_de_av_device_type = AV_HWDEVICE_TYPE_QSV; + pCodecCtx->get_format = get_hw_dec_format_qs; + break; default: hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; pCodecCtx->get_format = get_hw_dec_format_va; @@ -408,10 +425,6 @@ void FFmpegReader::Open() { adapter_ptr = NULL; i_decoder_hw = openshot::Settings::Instance()->HARDWARE_DECODER; switch (i_decoder_hw) { - case 0: - hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2; - pCodecCtx->get_format = get_hw_dec_format_dx; - break; case 2: hw_de_av_device_type = AV_HWDEVICE_TYPE_CUDA; pCodecCtx->get_format = get_hw_dec_format_cu; @@ -424,6 +437,10 @@ void FFmpegReader::Open() { hw_de_av_device_type = AV_HWDEVICE_TYPE_D3D11VA; pCodecCtx->get_format = get_hw_dec_format_d3; break; + case 7: + hw_de_av_device_type = AV_HWDEVICE_TYPE_QSV; + pCodecCtx->get_format = get_hw_dec_format_qs; + break; default: hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2; pCodecCtx->get_format = get_hw_dec_format_dx; @@ -433,17 +450,17 @@ void FFmpegReader::Open() { adapter_ptr = NULL; i_decoder_hw = openshot::Settings::Instance()->HARDWARE_DECODER; switch (i_decoder_hw) { - case 0: - hw_de_av_device_type = AV_HWDEVICE_TYPE_VIDEOTOOLBOX; - pCodecCtx->get_format = get_hw_dec_format_qs; - break; case 5: hw_de_av_device_type = AV_HWDEVICE_TYPE_VIDEOTOOLBOX; + pCodecCtx->get_format = get_hw_dec_format_vt; + break; + case 7: + hw_de_av_device_type = AV_HWDEVICE_TYPE_QSV; pCodecCtx->get_format = get_hw_dec_format_qs; break; default: hw_de_av_device_type = AV_HWDEVICE_TYPE_VIDEOTOOLBOX; - pCodecCtx->get_format = get_hw_dec_format_qs; + pCodecCtx->get_format = get_hw_dec_format_vt; break; } #endif From cdb4ae5483d43748d5af8ecbc6a7e5b112b5c850 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Mon, 29 Apr 2019 17:05:13 -0500 Subject: [PATCH 163/223] Fixing crash on Mac due to juce::String again --- src/FFmpegReader.cpp | 2 +- src/Qt/AudioPlaybackThread.cpp | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 480e835b..690af140 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -76,7 +76,7 @@ using namespace openshot; int hw_de_on = 0; #if IS_FFMPEG_3_2 -AVPixelFormat hw_de_av_pix_fmt_global = AV_PIX_FMT_NONE; + AVPixelFormat hw_de_av_pix_fmt_global = AV_PIX_FMT_NONE; AVHWDeviceType hw_de_av_device_type_global = AV_HWDEVICE_TYPE_NONE; #endif diff --git a/src/Qt/AudioPlaybackThread.cpp b/src/Qt/AudioPlaybackThread.cpp index 678a9c09..7bad4649 100644 --- a/src/Qt/AudioPlaybackThread.cpp +++ b/src/Qt/AudioPlaybackThread.cpp @@ -45,7 +45,7 @@ namespace openshot juce::String preferred_audio_device = juce::String(Settings::Instance()->PLAYBACK_AUDIO_DEVICE_NAME.c_str()); // Initialize audio device only 1 time - juce::String error = m_pInstance->audioDeviceManager.initialise ( + juce::String audio_error = m_pInstance->audioDeviceManager.initialise ( 0, /* number of input channels */ 2, /* number of output channels */ 0, /* no XML settings.. */ @@ -53,8 +53,8 @@ namespace openshot preferred_audio_device /* preferredDefaultDeviceName */); // Persist any errors detected - if (error.isNotEmpty()) { - m_pInstance->initialise_error = error.toStdString(); + if (audio_error.isNotEmpty()) { + m_pInstance->initialise_error = audio_error.toRawUTF8(); } else { m_pInstance->initialise_error = ""; } @@ -68,7 +68,8 @@ namespace openshot for (int j = 0; j < deviceNames.size (); ++j ) { juce::String deviceName = deviceNames[j]; - AudioDeviceInfo deviceInfo = {deviceName.toStdString(), t->getTypeName().toStdString()}; + juce::String typeName = t->getTypeName(); + AudioDeviceInfo deviceInfo = {deviceName.toRawUTF8(), typeName.toRawUTF8()}; m_pInstance->audio_device_names.push_back(deviceInfo); } } From 70f07ca4f81196d6e919858a4395fc72184f2a70 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Tue, 30 Apr 2019 13:17:43 -0500 Subject: [PATCH 164/223] Improving HW-ACCEL documentation --- doc/HW-ACCEL.md | 88 ++++++++++++++++++++++++++++++---------------- include/Settings.h | 13 ++++++- 2 files changed, 70 insertions(+), 31 deletions(-) diff --git a/doc/HW-ACCEL.md b/doc/HW-ACCEL.md index b785a9ce..af5bb091 100644 --- a/doc/HW-ACCEL.md +++ b/doc/HW-ACCEL.md @@ -1,20 +1,33 @@ ## Hardware Acceleration -Observations for developers wanting to make hardware acceleration work. +OpenShot now has experimental support for hardware acceleration, which uses 1 (or more) +graphics cards to offload some of the work for both decoding and encoding. This is +very new and experimental (as of May 2019), but we look forward to "accelerating" +our support for this in the future! -*All observations are for Linux (but contributions welcome).* +The following table summarizes our current level of support: + +| | Linux Decode | Linux Encode | Mac Decode | Mac Encode | Windows Decode | Windows Encode | Notes | +|--------------------|--------------|--------------|------------|------------|----------------------|----------------|----------------| +| VA-API | Verified | Verified | - | - | - | - | Linux Only | +| VDPAU | Verified | Verified | - | - | - | - | Linux Only | +| CUDA (NVDEC/NVENC) | Verified | Verified | - | - | - | Verified | Cross Platform | +| VideoToolBox | - | - | Verified | Crashes | - | - | Mac Only | +| DXVA2 | - | - | - | - | Fails (green frames) | Verified | Windows Only | +| D3D11VA | - | - | - | - | Fails (green frames) | - | Windows Only | +| QSV | Fails | Fails | Fails | Fails | Fails | Fails | Cross Platform | ## Supported FFmpeg Versions -* HW accel is supported from ffmpeg version 3.2 (3.3 for nVidia drivers) -* HW accel was removed for nVidia drivers in Ubuntu for ffmpeg 4+ -* I could not manage to build a version of ffmpeg 4.1 with the nVidia SDK -that worked with nVidia cards. There might be a problem in ffmpeg 4+ +* HW accel is supported from FFmpeg version 3.2 (3.3 for nVidia drivers) +* HW accel was removed for nVidia drivers in Ubuntu for FFmpeg 4+ +* We could not manage to build a version of FFmpeg 4.1 with the nVidia SDK +that worked with nVidia cards. There might be a problem in FFmpeg 4+ that prohibits this. -**Notice:** The ffmpeg versions of Ubuntu and PPAs for Ubuntu show the -same behaviour. ffmpeg 3 has working nVidia hardware acceleration while -ffmpeg 4+ has no support for nVidia hardware acceleration +**Notice:** The FFmpeg versions of Ubuntu and PPAs for Ubuntu show the +same behaviour. FFmpeg 3 has working nVidia hardware acceleration while +FFmpeg 4+ has no support for nVidia hardware acceleration included. ## OpenShot Settings @@ -26,10 +39,19 @@ the various hardware acceleration features. /// Use video codec for faster video decoding (if supported) int HARDWARE_DECODER = 0; +/* 0 - No acceleration + 1 - Linux VA-API + 2 - nVidia NVDEC + 3 - Windows D3D9 + 4 - Windows D3D11 + 5 - MacOS / VideoToolBox + 6 - Linux VDPAU + 7 - Intel QSV */ + /// Number of threads of OpenMP int OMP_THREADS = 12; -/// Number of threads that ffmpeg uses +/// Number of threads that FFmpeg uses int FF_THREADS = 8; /// Maximum rows that hardware decode can handle @@ -38,10 +60,10 @@ int DE_LIMIT_HEIGHT_MAX = 1100; /// Maximum columns that hardware decode can handle int DE_LIMIT_WIDTH_MAX = 1950; -/// Which GPU to use to decode (0 is the first) +/// Which GPU to use to decode (0 is the first, LINUX ONLY) int HW_DE_DEVICE_SET = 0; -/// Which GPU to use to encode (0 is the first) +/// Which GPU to use to encode (0 is the first, LINUX ONLY) int HW_EN_DEVICE_SET = 0; ``` @@ -67,38 +89,44 @@ of effects could be implemented (contributions welcome). If the computer has multiple graphics cards installed, you can choose which should be used by libopenshot. Also, you can optionally use one card for decoding and the other for encoding (if both cards support acceleration). +This is currently only supported on Linux, due to the device name FFmpeg +expects (i.e. **/dev/dri/render128**). Contributions welcome if anyone can +determine what string format to pass for Windows and Mac. ## Help Us Improve Hardware Support This information might be wrong, and we would love to continue improving our support for hardware acceleration in OpenShot. Please help us update -this document if you find an error or discover some new information. +this document if you find an error or discover new and/or useful information. -**Desperately Needed:** The manual at: +**FFmpeg 4 + nVidia** The manual at: https://www.tal.org/tutorials/ffmpeg_nvidia_encode -works pretty well. I could compile and install a version of ffmpeg 4.1.3 +works pretty well. We could compile and install a version of FFmpeg 4.1.3 on Mint 19.1 that supports the GPU on nVidia cards. A version of openshot with hardware support using these libraries could use the nVidia GPU. -(A way to compile ffmpeg 4.0 and up with working nVidia -hardware acceleration support on Ubuntu Linux!) -**BUG:** Hardware supported decoding still has a bug. The speed gains with -decoding are by far not as great as with encoding. In case hardware accelerated -decoding does not work disable it. Hardware acceleration might also break -because of graphics drivers that have bugs. +**BUG:** Hardware supported decoding still has some bugs (as you can see from +the chart above). Also, the speed gains with decoding are not as great +as with encoding. Currently, if hardware decoding fails, there is no +fallback (you either get green frames or an "invalid file" error in OpenShot). +This needs to be improved to successfully fall-back to software decoding. -**Needed:** A way to get the options and limits of the GPU, like -supported codecs and the supported dimensions (width and height). +**Needed:** + * A way to get options and limits of the GPU, such as + supported dimensions (width and height). + * A way to list the actual Graphic Cards available to FFmpeg (for the + user to choose which card for decoding and encoding, as opposed + to "Graphics Card X") -**Further improvement:** Right now the frame can be decoded on the GPU but the -frame is then copied to CPU memory. Before encoding the frame is then -copied to GPU memory for encoding. That is necessary because the modifications -are done by the CPU. Using the GPU for that too will make it possible to do -away with these two copies. A possible solution would be to use Vulkan compute -which would be available on Linux and Windows natively and on MacOS via MoltenVK. +**Further improvement:** Right now the frame can be decoded on the GPU, but the +frame is then copied to CPU memory for modifications. It is then copied back to +GPU memory for encoding. Using the GPU for both decoding and modifications +will make it possible to do away with these two copies. A possible solution would +be to use Vulkan compute which would be available on Linux and Windows natively +and on MacOS via MoltenVK. ## Credit A big thanks to Peter M (https://github.com/eisneinechse) for all his work -on integrating hardware accelleration into libopenshot! The community thanks +on integrating hardware acceleration into libopenshot! The community thanks you for this major contribution! diff --git a/include/Settings.h b/include/Settings.h index 859b2fab..89561034 100644 --- a/include/Settings.h +++ b/include/Settings.h @@ -76,7 +76,18 @@ namespace openshot { static Settings * m_pInstance; public: - /// Use video codec for faster video decoding (if supported) + /** + * @brief Use video codec for faster video decoding (if supported) + * + * 0 - No acceleration, + * 1 - Linux VA-API, + * 2 - nVidia NVDEC, + * 3 - Windows D3D9, + * 4 - Windows D3D11, + * 5 - MacOS / VideoToolBox, + * 6 - Linux VDPAU, + * 7 - Intel QSV + */ int HARDWARE_DECODER = 0; /// Scale mode used in FFmpeg decoding and encoding (used as an optimization for faster previews) From 27450a8a869ccd400db115b16b8c6c3122810b23 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Tue, 30 Apr 2019 14:05:52 -0700 Subject: [PATCH 165/223] Clarify table --- doc/HW-ACCEL.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/doc/HW-ACCEL.md b/doc/HW-ACCEL.md index af5bb091..1dd8c92c 100644 --- a/doc/HW-ACCEL.md +++ b/doc/HW-ACCEL.md @@ -1,8 +1,8 @@ ## Hardware Acceleration -OpenShot now has experimental support for hardware acceleration, which uses 1 (or more) +OpenShot now has experimental support for hardware acceleration, which uses 1 (or more) graphics cards to offload some of the work for both decoding and encoding. This is -very new and experimental (as of May 2019), but we look forward to "accelerating" +very new and experimental (as of May 2019), but we look forward to "accelerating" our support for this in the future! The following table summarizes our current level of support: @@ -10,8 +10,8 @@ The following table summarizes our current level of support: | | Linux Decode | Linux Encode | Mac Decode | Mac Encode | Windows Decode | Windows Encode | Notes | |--------------------|--------------|--------------|------------|------------|----------------------|----------------|----------------| | VA-API | Verified | Verified | - | - | - | - | Linux Only | -| VDPAU | Verified | Verified | - | - | - | - | Linux Only | -| CUDA (NVDEC/NVENC) | Verified | Verified | - | - | - | Verified | Cross Platform | +| VDPAU | Verified(+) | N/A(++) | - | - | - | - | Linux Only | +| CUDA (NVDEC/NVENC) | Fails(+++) | Verified | - | - | - | Verified | Cross Platform | | VideoToolBox | - | - | Verified | Crashes | - | - | Mac Only | | DXVA2 | - | - | - | - | Fails (green frames) | Verified | Windows Only | | D3D11VA | - | - | - | - | Fails (green frames) | - | Windows Only | @@ -21,9 +21,9 @@ The following table summarizes our current level of support: * HW accel is supported from FFmpeg version 3.2 (3.3 for nVidia drivers) * HW accel was removed for nVidia drivers in Ubuntu for FFmpeg 4+ -* We could not manage to build a version of FFmpeg 4.1 with the nVidia SDK -that worked with nVidia cards. There might be a problem in FFmpeg 4+ -that prohibits this. +* (+) VDPAU for some reason needs a card number one higher than it really is +* (++) VDPAU is a decoder only. +* (+++) Green frames **Notice:** The FFmpeg versions of Ubuntu and PPAs for Ubuntu show the same behaviour. FFmpeg 3 has working nVidia hardware acceleration while @@ -106,23 +106,23 @@ on Mint 19.1 that supports the GPU on nVidia cards. A version of openshot with hardware support using these libraries could use the nVidia GPU. **BUG:** Hardware supported decoding still has some bugs (as you can see from -the chart above). Also, the speed gains with decoding are not as great +the chart above). Also, the speed gains with decoding are not as great as with encoding. Currently, if hardware decoding fails, there is no fallback (you either get green frames or an "invalid file" error in OpenShot). This needs to be improved to successfully fall-back to software decoding. -**Needed:** - * A way to get options and limits of the GPU, such as +**Needed:** + * A way to get options and limits of the GPU, such as supported dimensions (width and height). - * A way to list the actual Graphic Cards available to FFmpeg (for the - user to choose which card for decoding and encoding, as opposed + * A way to list the actual Graphic Cards available to FFmpeg (for the + user to choose which card for decoding and encoding, as opposed to "Graphics Card X") **Further improvement:** Right now the frame can be decoded on the GPU, but the -frame is then copied to CPU memory for modifications. It is then copied back to +frame is then copied to CPU memory for modifications. It is then copied back to GPU memory for encoding. Using the GPU for both decoding and modifications -will make it possible to do away with these two copies. A possible solution would -be to use Vulkan compute which would be available on Linux and Windows natively +will make it possible to do away with these two copies. A possible solution would +be to use Vulkan compute which would be available on Linux and Windows natively and on MacOS via MoltenVK. ## Credit From 9324b691e9a2152ad1dfb6fdfed1ccd44b4c750e Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Tue, 30 Apr 2019 17:11:04 -0500 Subject: [PATCH 166/223] Improving HW-ACCEL documentation --- doc/HW-ACCEL.md | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/doc/HW-ACCEL.md b/doc/HW-ACCEL.md index 1dd8c92c..c04c3b88 100644 --- a/doc/HW-ACCEL.md +++ b/doc/HW-ACCEL.md @@ -7,23 +7,24 @@ our support for this in the future! The following table summarizes our current level of support: -| | Linux Decode | Linux Encode | Mac Decode | Mac Encode | Windows Decode | Windows Encode | Notes | -|--------------------|--------------|--------------|------------|------------|----------------------|----------------|----------------| -| VA-API | Verified | Verified | - | - | - | - | Linux Only | -| VDPAU | Verified(+) | N/A(++) | - | - | - | - | Linux Only | -| CUDA (NVDEC/NVENC) | Fails(+++) | Verified | - | - | - | Verified | Cross Platform | -| VideoToolBox | - | - | Verified | Crashes | - | - | Mac Only | -| DXVA2 | - | - | - | - | Fails (green frames) | Verified | Windows Only | -| D3D11VA | - | - | - | - | Fails (green frames) | - | Windows Only | -| QSV | Fails | Fails | Fails | Fails | Fails | Fails | Cross Platform | +| | Linux Decode | Linux Encode | Mac Decode | Mac Encode | Windows Decode | Windows Encode | Notes | +|--------------------|--------------|--------------|------------|------------|----------------|----------------|----------------| +| VA-API | Verified | Verified | - | - | - | - | Linux Only | +| VDPAU | Verified(+) | N/A(++) | - | - | - | - | Linux Only | +| CUDA (NVDEC/NVENC) | Fails(+++) | Verified | - | - | - | Verified | Cross Platform | +| VideoToolBox | - | - | Verified | Crashes | - | - | Mac Only | +| DXVA2 | - | - | - | - | Fails(+++) | - | Windows Only | +| D3D11VA | - | - | - | - | Fails(+++) | - | Windows Only | +| QSV | Fails | Fails | Fails | Fails | Fails | Fails | Cross Platform | + +* *(+) VDPAU for some reason needs a card number one higher than it really is* +* *(++) VDPAU is a decoder only.* +* *(+++) Green frames (pixel data not correctly tranferred back to system memory)* ## Supported FFmpeg Versions * HW accel is supported from FFmpeg version 3.2 (3.3 for nVidia drivers) * HW accel was removed for nVidia drivers in Ubuntu for FFmpeg 4+ -* (+) VDPAU for some reason needs a card number one higher than it really is -* (++) VDPAU is a decoder only. -* (+++) Green frames **Notice:** The FFmpeg versions of Ubuntu and PPAs for Ubuntu show the same behaviour. FFmpeg 3 has working nVidia hardware acceleration while From fad8f40cf577e314b9890e8db7a76480c6b98959 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Tue, 30 Apr 2019 17:43:15 -0500 Subject: [PATCH 167/223] Simplifying hardware decoder logic (when looking for pixmap) --- src/FFmpegReader.cpp | 285 +++++++++++++++---------------------------- 1 file changed, 95 insertions(+), 190 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 690af140..acd3b55f 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -152,137 +152,53 @@ bool AudioLocation::is_near(AudioLocation location, int samples_per_frame, int64 #if IS_FFMPEG_3_2 -#if defined(__linux__) -static enum AVPixelFormat get_hw_dec_format_va(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) -{ - const enum AVPixelFormat *p; - - for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { - switch (*p) { - case AV_PIX_FMT_VAAPI: - hw_de_av_pix_fmt_global = AV_PIX_FMT_VAAPI; - hw_de_av_device_type_global = AV_HWDEVICE_TYPE_VAAPI; - return *p; - break; - } - } - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::get_hw_dec_format_va (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); - return AV_PIX_FMT_NONE; -} - -static enum AVPixelFormat get_hw_dec_format_cu(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) -{ - const enum AVPixelFormat *p; - - for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { - switch (*p) { - case AV_PIX_FMT_CUDA: - hw_de_av_pix_fmt_global = AV_PIX_FMT_CUDA; - hw_de_av_device_type_global = AV_HWDEVICE_TYPE_CUDA; - return *p; - break; - } - } - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::get_hw_dec_format_cu (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); - return AV_PIX_FMT_NONE; -} - -static enum AVPixelFormat get_hw_dec_format_vd(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) -{ - const enum AVPixelFormat *p; - - for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { - switch (*p) { - case AV_PIX_FMT_VDPAU: - hw_de_av_pix_fmt_global = AV_PIX_FMT_VDPAU; - hw_de_av_device_type_global = AV_HWDEVICE_TYPE_VDPAU; - return *p; - break; - } - } - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::get_hw_dec_format_vd (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); - return AV_PIX_FMT_NONE; -} -#endif - -#if defined(_WIN32) -static enum AVPixelFormat get_hw_dec_format_dx(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) -{ - const enum AVPixelFormat *p; - - for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { - switch (*p) { - case AV_PIX_FMT_DXVA2_VLD: - hw_de_av_pix_fmt_global = AV_PIX_FMT_DXVA2_VLD; - hw_de_av_device_type_global = AV_HWDEVICE_TYPE_DXVA2; - return *p; - break; - } - } - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::get_hw_dec_format_dx (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); - return AV_PIX_FMT_NONE; -} - -static enum AVPixelFormat get_hw_dec_format_d3(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) -{ - const enum AVPixelFormat *p; - - for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { - switch (*p) { - case AV_PIX_FMT_D3D11: - hw_de_av_pix_fmt_global = AV_PIX_FMT_D3D11; - hw_de_av_device_type_global = AV_HWDEVICE_TYPE_D3D11VA; - return *p; - break; - } - } - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::get_hw_dec_format_d3 (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); - return AV_PIX_FMT_NONE; -} - -static enum AVPixelFormat get_hw_dec_format_cu(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) -{ - const enum AVPixelFormat *p; - - for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { - switch (*p) { - case AV_PIX_FMT_CUDA: - hw_de_av_pix_fmt_global = AV_PIX_FMT_CUDA; - hw_de_av_device_type_global = AV_HWDEVICE_TYPE_CUDA; - return *p; - break; - } - } - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::get_hw_dec_format_cu (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); - return AV_PIX_FMT_NONE; -} -#endif - -#if defined(__APPLE__) -static enum AVPixelFormat get_hw_dec_format_vt(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) -{ - const enum AVPixelFormat *p; - - for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { - switch (*p) { - case AV_PIX_FMT_VIDEOTOOLBOX: - hw_de_av_pix_fmt_global = AV_PIX_FMT_VIDEOTOOLBOX; - hw_de_av_device_type_global = AV_HWDEVICE_TYPE_VIDEOTOOLBOX; - return *p; - break; - } - } - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::get_hw_dec_format_vt (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); - return AV_PIX_FMT_NONE; -} -#endif - -static enum AVPixelFormat get_hw_dec_format_qs(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) +// Get hardware pix format +static enum AVPixelFormat get_hw_dec_format(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) { const enum AVPixelFormat *p; for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { switch (*p) { +#if defined(__linux__) + // Linux pix formats + case AV_PIX_FMT_VAAPI: + hw_de_av_pix_fmt_global = AV_PIX_FMT_VAAPI; + hw_de_av_device_type_global = AV_HWDEVICE_TYPE_VAAPI; + return *p; + break; + case AV_PIX_FMT_VDPAU: + hw_de_av_pix_fmt_global = AV_PIX_FMT_VDPAU; + hw_de_av_device_type_global = AV_HWDEVICE_TYPE_VDPAU; + return *p; + break; +#endif +#if defined(_WIN32) + // Windows pix formats + case AV_PIX_FMT_DXVA2_VLD: + hw_de_av_pix_fmt_global = AV_PIX_FMT_DXVA2_VLD; + hw_de_av_device_type_global = AV_HWDEVICE_TYPE_DXVA2; + return *p; + break; + case AV_PIX_FMT_D3D11: + hw_de_av_pix_fmt_global = AV_PIX_FMT_D3D11; + hw_de_av_device_type_global = AV_HWDEVICE_TYPE_D3D11VA; + return *p; + break; +#endif +#if defined(__APPLE__) + // Apple pix formats + case AV_PIX_FMT_VIDEOTOOLBOX: + hw_de_av_pix_fmt_global = AV_PIX_FMT_VIDEOTOOLBOX; + hw_de_av_device_type_global = AV_HWDEVICE_TYPE_VIDEOTOOLBOX; + return *p; + break; +#endif + // Cross-platform pix formats + case AV_PIX_FMT_CUDA: + hw_de_av_pix_fmt_global = AV_PIX_FMT_CUDA; + hw_de_av_device_type_global = AV_HWDEVICE_TYPE_CUDA; + return *p; + break; case AV_PIX_FMT_QSV: hw_de_av_pix_fmt_global = AV_PIX_FMT_QSV; hw_de_av_device_type_global = AV_HWDEVICE_TYPE_QSV; @@ -290,7 +206,7 @@ static enum AVPixelFormat get_hw_dec_format_qs(AVCodecContext *ctx, const enum A break; } } - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::get_hw_dec_format_qs (Unable to decode this file using hardware decode.)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); + ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::get_hw_dec_format (Unable to decode this file using hardware decode)", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); return AV_PIX_FMT_NONE; } @@ -304,15 +220,14 @@ int FFmpegReader::IsHardwareDecodeSupported(int codecid) case AV_CODEC_ID_WMV1: case AV_CODEC_ID_WMV2: case AV_CODEC_ID_WMV3: - ret = 1; - break; - default : - ret = 0; - break; + ret = 1; + break; + default : + ret = 0; + break; } return ret; } - #endif void FFmpegReader::Open() { @@ -393,76 +308,66 @@ void FFmpegReader::Open() { adapter_num = openshot::Settings::Instance()->HW_DE_DEVICE_SET; fprintf(stderr, "\n\nDecodiing Device Nr: %d\n", adapter_num); + // Set hardware pix format (callback) + pCodecCtx->get_format = get_hw_dec_format; + if (adapter_num < 3 && adapter_num >=0) { #if defined(__linux__) - snprintf(adapter,sizeof(adapter),"/dev/dri/renderD%d", adapter_num+128); - adapter_ptr = adapter; - i_decoder_hw = openshot::Settings::Instance()->HARDWARE_DECODER; - switch (i_decoder_hw) { - case 1: - hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; - pCodecCtx->get_format = get_hw_dec_format_va; - break; - case 2: - hw_de_av_device_type = AV_HWDEVICE_TYPE_CUDA; - pCodecCtx->get_format = get_hw_dec_format_cu; - break; - case 6: - hw_de_av_device_type = AV_HWDEVICE_TYPE_VDPAU; - pCodecCtx->get_format = get_hw_dec_format_vd; - break; - case 7: - hw_de_av_device_type = AV_HWDEVICE_TYPE_QSV; - pCodecCtx->get_format = get_hw_dec_format_qs; - break; - default: - hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; - pCodecCtx->get_format = get_hw_dec_format_va; - break; - } - -#elif defined(_WIN32) - adapter_ptr = NULL; - i_decoder_hw = openshot::Settings::Instance()->HARDWARE_DECODER; - switch (i_decoder_hw) { + snprintf(adapter,sizeof(adapter),"/dev/dri/renderD%d", adapter_num+128); + adapter_ptr = adapter; + i_decoder_hw = openshot::Settings::Instance()->HARDWARE_DECODER; + switch (i_decoder_hw) { + case 1: + hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; + break; case 2: hw_de_av_device_type = AV_HWDEVICE_TYPE_CUDA; - pCodecCtx->get_format = get_hw_dec_format_cu; break; - case 3: - hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2; - pCodecCtx->get_format = get_hw_dec_format_dx; - break; - case 4: - hw_de_av_device_type = AV_HWDEVICE_TYPE_D3D11VA; - pCodecCtx->get_format = get_hw_dec_format_d3; + case 6: + hw_de_av_device_type = AV_HWDEVICE_TYPE_VDPAU; break; case 7: hw_de_av_device_type = AV_HWDEVICE_TYPE_QSV; - pCodecCtx->get_format = get_hw_dec_format_qs; break; default: - hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2; - pCodecCtx->get_format = get_hw_dec_format_dx; + hw_de_av_device_type = AV_HWDEVICE_TYPE_VAAPI; break; } + +#elif defined(_WIN32) + adapter_ptr = NULL; + i_decoder_hw = openshot::Settings::Instance()->HARDWARE_DECODER; + switch (i_decoder_hw) { + case 2: + hw_de_av_device_type = AV_HWDEVICE_TYPE_CUDA; + break; + case 3: + hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2; + break; + case 4: + hw_de_av_device_type = AV_HWDEVICE_TYPE_D3D11VA; + break; + case 7: + hw_de_av_device_type = AV_HWDEVICE_TYPE_QSV; + break; + default: + hw_de_av_device_type = AV_HWDEVICE_TYPE_DXVA2; + break; + } #elif defined(__APPLE__) - adapter_ptr = NULL; - i_decoder_hw = openshot::Settings::Instance()->HARDWARE_DECODER; - switch (i_decoder_hw) { - case 5: - hw_de_av_device_type = AV_HWDEVICE_TYPE_VIDEOTOOLBOX; - pCodecCtx->get_format = get_hw_dec_format_vt; - break; - case 7: - hw_de_av_device_type = AV_HWDEVICE_TYPE_QSV; - pCodecCtx->get_format = get_hw_dec_format_qs; - break; - default: - hw_de_av_device_type = AV_HWDEVICE_TYPE_VIDEOTOOLBOX; - pCodecCtx->get_format = get_hw_dec_format_vt; - break; - } + adapter_ptr = NULL; + i_decoder_hw = openshot::Settings::Instance()->HARDWARE_DECODER; + switch (i_decoder_hw) { + case 5: + hw_de_av_device_type = AV_HWDEVICE_TYPE_VIDEOTOOLBOX; + break; + case 7: + hw_de_av_device_type = AV_HWDEVICE_TYPE_QSV; + break; + default: + hw_de_av_device_type = AV_HWDEVICE_TYPE_VIDEOTOOLBOX; + break; + } #endif } else { From 2b42574ffdbbb1207601f306e03b842da37eb854 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Wed, 1 May 2019 18:02:25 -0500 Subject: [PATCH 168/223] Adding SetJson support for display_ratio and pixel_ratio updates, and improving SetMaxSize to maintain aspect ratio correctly, regardless of what is passed in. This helps support things like square aspect ratios. --- src/Timeline.cpp | 42 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/src/Timeline.cpp b/src/Timeline.cpp index 5cb9ff5e..b3a24461 100644 --- a/src/Timeline.cpp +++ b/src/Timeline.cpp @@ -1371,6 +1371,33 @@ void Timeline::apply_json_to_timeline(Json::Value change) { else if (root_key == "fps" && sub_key == "den") // Set fps.den info.fps.den = change["value"].asInt(); + else if (root_key == "display_ratio" && sub_key == "" && change["value"].isObject()) { + // Set display_ratio fraction + if (!change["value"]["num"].isNull()) + info.display_ratio.num = change["value"]["num"].asInt(); + if (!change["value"]["den"].isNull()) + info.display_ratio.den = change["value"]["den"].asInt(); + } + else if (root_key == "display_ratio" && sub_key == "num") + // Set display_ratio.num + info.display_ratio.num = change["value"].asInt(); + else if (root_key == "display_ratio" && sub_key == "den") + // Set display_ratio.den + info.display_ratio.den = change["value"].asInt(); + else if (root_key == "pixel_ratio" && sub_key == "" && change["value"].isObject()) { + // Set pixel_ratio fraction + if (!change["value"]["num"].isNull()) + info.pixel_ratio.num = change["value"]["num"].asInt(); + if (!change["value"]["den"].isNull()) + info.pixel_ratio.den = change["value"]["den"].asInt(); + } + else if (root_key == "pixel_ratio" && sub_key == "num") + // Set pixel_ratio.num + info.pixel_ratio.num = change["value"].asInt(); + else if (root_key == "pixel_ratio" && sub_key == "den") + // Set pixel_ratio.den + info.pixel_ratio.den = change["value"].asInt(); + else if (root_key == "sample_rate") // Set sample rate info.sample_rate = change["value"].asInt(); @@ -1380,9 +1407,7 @@ void Timeline::apply_json_to_timeline(Json::Value change) { else if (root_key == "channel_layout") // Set channel layout info.channel_layout = (ChannelLayout) change["value"].asInt(); - else - // Error parsing JSON (or missing keys) throw InvalidJSONKey("JSON change key is invalid", change.toStyledString()); @@ -1443,7 +1468,14 @@ void Timeline::ClearAllCache() { // Set Max Image Size (used for performance optimization). Convenience function for setting // Settings::Instance()->MAX_WIDTH and Settings::Instance()->MAX_HEIGHT. void Timeline::SetMaxSize(int width, int height) { - // Init max image size (choose the smallest one) - Settings::Instance()->MAX_WIDTH = min(width, info.width); - Settings::Instance()->MAX_HEIGHT = min(height, info.height); + // Maintain aspect ratio regardless of what size is passed in + QSize display_ratio_size = QSize(info.display_ratio.num * info.pixel_ratio.ToFloat(), info.display_ratio.den * info.pixel_ratio.ToFloat()); + QSize proposed_size = QSize(min(width, info.width), min(height, info.height)); + + // Scale QSize up to proposed size + display_ratio_size.scale(proposed_size, Qt::KeepAspectRatio); + + // Set max size + Settings::Instance()->MAX_WIDTH = display_ratio_size.width(); + Settings::Instance()->MAX_HEIGHT = display_ratio_size.height(); } \ No newline at end of file From 6f00062b7b2526b23e51adb3b5f55f8be1a00d63 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Thu, 2 May 2019 11:43:34 -0500 Subject: [PATCH 169/223] Fixing small regression with SetMaxSize and missing display_ratio and pixel_ratio --- src/Timeline.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Timeline.cpp b/src/Timeline.cpp index b3a24461..b229a3de 100644 --- a/src/Timeline.cpp +++ b/src/Timeline.cpp @@ -58,6 +58,9 @@ Timeline::Timeline(int width, int height, Fraction fps, int sample_rate, int cha info.has_audio = true; info.has_video = true; info.video_length = info.fps.ToFloat() * info.duration; + info.display_ratio = openshot::Fraction(width, height); + info.display_ratio.Reduce(); + info.pixel_ratio = openshot::Fraction(1, 1); // Init max image size SetMaxSize(info.width, info.height); From da07ab250be91ce07267568f51330e3a36dc1ba4 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Thu, 2 May 2019 12:02:56 -0500 Subject: [PATCH 170/223] Updating hwaccel table to use emojis (instead of words) --- doc/HW-ACCEL.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/doc/HW-ACCEL.md b/doc/HW-ACCEL.md index c04c3b88..8d29a11f 100644 --- a/doc/HW-ACCEL.md +++ b/doc/HW-ACCEL.md @@ -7,19 +7,20 @@ our support for this in the future! The following table summarizes our current level of support: -| | Linux Decode | Linux Encode | Mac Decode | Mac Encode | Windows Decode | Windows Encode | Notes | -|--------------------|--------------|--------------|------------|------------|----------------|----------------|----------------| -| VA-API | Verified | Verified | - | - | - | - | Linux Only | -| VDPAU | Verified(+) | N/A(++) | - | - | - | - | Linux Only | -| CUDA (NVDEC/NVENC) | Fails(+++) | Verified | - | - | - | Verified | Cross Platform | -| VideoToolBox | - | - | Verified | Crashes | - | - | Mac Only | -| DXVA2 | - | - | - | - | Fails(+++) | - | Windows Only | -| D3D11VA | - | - | - | - | Fails(+++) | - | Windows Only | -| QSV | Fails | Fails | Fails | Fails | Fails | Fails | Cross Platform | +| | Linux Decode | Linux Encode | Mac Decode | Mac Encode |Windows Decode| Windows Encode | Notes | +|--------------------|------------------------|----------------------|------------------|---------------|--------------|------------------|------------------| +| VA-API | :heavy_check_mark: | :heavy_check_mark: | - | - | - | - | *Linux Only* | +| VDPAU | :heavy_check_mark:(+) |:white_check_mark:(++)| - | - | - | - | *Linux Only* | +| CUDA (NVDEC/NVENC) | :x:(+++) | :heavy_check_mark: | - | - | - |:heavy_check_mark:| *Cross Platform* | +| VideoToolBox | - | - |:heavy_check_mark:| :x:(++++) | - | - | *Mac Only* | +| DXVA2 | - | - | - | - | :x:(+++) | - | *Windows Only* | +| D3D11VA | - | - | - | - | :x:(+++) | - | *Windows Only* | +| QSV | :x:(+++) | :x: | :x: | :x: | :x: | :x: | *Cross Platform* | * *(+) VDPAU for some reason needs a card number one higher than it really is* * *(++) VDPAU is a decoder only.* * *(+++) Green frames (pixel data not correctly tranferred back to system memory)* +* *(++++) Crashes and burns ## Supported FFmpeg Versions From 10ef8838b18326be51c68e2e90640e6882d1ef16 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Thu, 2 May 2019 12:03:58 -0500 Subject: [PATCH 171/223] Updating hwaccel table to use emojis (instead of words) --- doc/HW-ACCEL.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/doc/HW-ACCEL.md b/doc/HW-ACCEL.md index 8d29a11f..f78fcd3a 100644 --- a/doc/HW-ACCEL.md +++ b/doc/HW-ACCEL.md @@ -7,15 +7,15 @@ our support for this in the future! The following table summarizes our current level of support: -| | Linux Decode | Linux Encode | Mac Decode | Mac Encode |Windows Decode| Windows Encode | Notes | -|--------------------|------------------------|----------------------|------------------|---------------|--------------|------------------|------------------| -| VA-API | :heavy_check_mark: | :heavy_check_mark: | - | - | - | - | *Linux Only* | -| VDPAU | :heavy_check_mark:(+) |:white_check_mark:(++)| - | - | - | - | *Linux Only* | -| CUDA (NVDEC/NVENC) | :x:(+++) | :heavy_check_mark: | - | - | - |:heavy_check_mark:| *Cross Platform* | -| VideoToolBox | - | - |:heavy_check_mark:| :x:(++++) | - | - | *Mac Only* | -| DXVA2 | - | - | - | - | :x:(+++) | - | *Windows Only* | -| D3D11VA | - | - | - | - | :x:(+++) | - | *Windows Only* | -| QSV | :x:(+++) | :x: | :x: | :x: | :x: | :x: | *Cross Platform* | +| | Linux Decode | Linux Encode | Mac Decode | Mac Encode |Windows Decode| Windows Encode | Notes | +|--------------------|------------------------|----------------------|------------------|----------------|--------------|------------------|------------------| +| VA-API | :heavy_check_mark: | :heavy_check_mark: | - | - | - | - | *Linux Only* | +| VDPAU | :heavy_check_mark:(+) |:white_check_mark:(++)| - | - | - | - | *Linux Only* | +| CUDA (NVDEC/NVENC) | :x:(+++) | :heavy_check_mark: | - | - | - |:heavy_check_mark:| *Cross Platform* | +| VideoToolBox | - | - |:heavy_check_mark:| :x:(++++) | - | - | *Mac Only* | +| DXVA2 | - | - | - | - | :x:(+++) | - | *Windows Only* | +| D3D11VA | - | - | - | - | :x:(+++) | - | *Windows Only* | +| QSV | :x:(+++) | :x: | :x: | :x: | :x: | :x: | *Cross Platform* | * *(+) VDPAU for some reason needs a card number one higher than it really is* * *(++) VDPAU is a decoder only.* From 4a0f0fa1c6297e4fba2c76968f1586c521a820a8 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Thu, 2 May 2019 12:04:51 -0500 Subject: [PATCH 172/223] Updating hwaccel table to use emojis (instead of words) take 3 --- doc/HW-ACCEL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/HW-ACCEL.md b/doc/HW-ACCEL.md index f78fcd3a..b8ee7b4e 100644 --- a/doc/HW-ACCEL.md +++ b/doc/HW-ACCEL.md @@ -20,7 +20,7 @@ The following table summarizes our current level of support: * *(+) VDPAU for some reason needs a card number one higher than it really is* * *(++) VDPAU is a decoder only.* * *(+++) Green frames (pixel data not correctly tranferred back to system memory)* -* *(++++) Crashes and burns +* *(++++) Crashes and burns* ## Supported FFmpeg Versions From eab0bbbe18eb77f84c7cea17eb4fce79f14eaa03 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Thu, 2 May 2019 14:19:14 -0500 Subject: [PATCH 173/223] Revert "Update Python install path detection" --- src/bindings/python/CMakeLists.txt | 48 +++++++++++++----------------- 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/src/bindings/python/CMakeLists.txt b/src/bindings/python/CMakeLists.txt index 08182d95..2a481aa7 100644 --- a/src/bindings/python/CMakeLists.txt +++ b/src/bindings/python/CMakeLists.txt @@ -65,37 +65,29 @@ if (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) target_link_libraries(${SWIG_MODULE_pyopenshot_REAL_NAME} ${PYTHON_LIBRARIES} openshot) - ### FIND THE PYTHON INTERPRETER (AND THE SITE PACKAGES FOLDER) - if (UNIX AND NOT APPLE) - ### Special-case for Debian's crazy, by checking to see if pybuild - ### is available. We don't use it, except as a canary in a coal mine - find_program(PYBUILD_EXECUTABLE pybuild - DOC "Path to Debian's pybuild utility") - if (PYBUILD_EXECUTABLE) - # We're on a Debian derivative, fall back to old path detection - set(py_detection "import site; print(site.getsitepackages()[0])") - else() - # Use distutils to detect install path - set (py_detection "\ + ### Check if the following Debian-friendly python module path exists + SET(PYTHON_MODULE_PATH "${CMAKE_INSTALL_PREFIX}/lib/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/site-packages") + if (NOT EXISTS ${PYTHON_MODULE_PATH}) + + ### Check if another Debian-friendly python module path exists + SET(PYTHON_MODULE_PATH "${CMAKE_INSTALL_PREFIX}/lib/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/dist-packages") + if (NOT EXISTS ${PYTHON_MODULE_PATH}) + + ### Calculate the python module path (using distutils) + execute_process ( COMMAND ${PYTHON_EXECUTABLE} -c "\ from distutils.sysconfig import get_python_lib; \ -print( get_python_lib( plat_specific=True, prefix='${CMAKE_INSTALL_PREFIX}' ) )") +print( get_python_lib( plat_specific=True, prefix='${CMAKE_INSTALL_PREFIX}' ) )" + OUTPUT_VARIABLE _ABS_PYTHON_MODULE_PATH + OUTPUT_STRIP_TRAILING_WHITESPACE ) + + GET_FILENAME_COMPONENT(_ABS_PYTHON_MODULE_PATH + "${_ABS_PYTHON_MODULE_PATH}" ABSOLUTE) + FILE(RELATIVE_PATH _REL_PYTHON_MODULE_PATH + ${CMAKE_INSTALL_PREFIX} ${_ABS_PYTHON_MODULE_PATH}) + SET(PYTHON_MODULE_PATH ${_ABS_PYTHON_MODULE_PATH}) endif() endif() - - if (NOT PYTHON_MODULE_PATH) - execute_process ( COMMAND ${PYTHON_EXECUTABLE} -c "${py_detection}" - OUTPUT_VARIABLE _ABS_PYTHON_MODULE_PATH - OUTPUT_STRIP_TRAILING_WHITESPACE ) - - GET_FILENAME_COMPONENT(_ABS_PYTHON_MODULE_PATH - "${_ABS_PYTHON_MODULE_PATH}" ABSOLUTE) - FILE(RELATIVE_PATH _REL_PYTHON_MODULE_PATH - ${CMAKE_INSTALL_PREFIX} ${_ABS_PYTHON_MODULE_PATH}) - SET(PYTHON_MODULE_PATH ${_REL_PYTHON_MODULE_PATH} - CACHE PATH "Install path for Python modules (relative to prefix)") - endif() - - message(STATUS "Will install Python module to: ${PYTHON_MODULE_PATH}") + message("PYTHON_MODULE_PATH: ${PYTHON_MODULE_PATH}") ############### INSTALL HEADERS & LIBRARY ################ ### Install Python bindings From 626a2f73f5b4f200cef8965963efc21e80c0030b Mon Sep 17 00:00:00 2001 From: "FeRD (Frank Dana)" Date: Thu, 2 May 2019 20:14:45 -0400 Subject: [PATCH 174/223] Python: Assume /usr/local prefix on Debian If we're using the fallback path detection method on Debian, we shouldn't compute the install path relative to `${CMAKE_INSTALL_PREFIX}` since it _isn't_. So, we assume it's relative to `/usr/local` instead. --- src/bindings/python/CMakeLists.txt | 50 ++++++++++++++++++------------ 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/src/bindings/python/CMakeLists.txt b/src/bindings/python/CMakeLists.txt index 2a481aa7..3f8ff2c1 100644 --- a/src/bindings/python/CMakeLists.txt +++ b/src/bindings/python/CMakeLists.txt @@ -65,29 +65,39 @@ if (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) target_link_libraries(${SWIG_MODULE_pyopenshot_REAL_NAME} ${PYTHON_LIBRARIES} openshot) - ### Check if the following Debian-friendly python module path exists - SET(PYTHON_MODULE_PATH "${CMAKE_INSTALL_PREFIX}/lib/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/site-packages") - if (NOT EXISTS ${PYTHON_MODULE_PATH}) - - ### Check if another Debian-friendly python module path exists - SET(PYTHON_MODULE_PATH "${CMAKE_INSTALL_PREFIX}/lib/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/dist-packages") - if (NOT EXISTS ${PYTHON_MODULE_PATH}) - - ### Calculate the python module path (using distutils) - execute_process ( COMMAND ${PYTHON_EXECUTABLE} -c "\ + ### FIND THE PYTHON INTERPRETER (AND THE SITE PACKAGES FOLDER) + if (UNIX AND NOT APPLE) + ### Special-case for Debian's crazy, by checking to see if pybuild + ### is available. We don't use it, except as a canary in a coal mine + find_program(PYBUILD_EXECUTABLE pybuild + DOC "Path to Debian's pybuild utility") + if (PYBUILD_EXECUTABLE) + # We're on a Debian derivative, fall back to old path detection + set(py_detection "import site; print(site.getsitepackages()[0])") + set(PY_INSTALL_PREFIX "/usr/local") # An assumption (bad one?) + else() + # Use distutils to detect install path + set (py_detection "\ from distutils.sysconfig import get_python_lib; \ -print( get_python_lib( plat_specific=True, prefix='${CMAKE_INSTALL_PREFIX}' ) )" - OUTPUT_VARIABLE _ABS_PYTHON_MODULE_PATH - OUTPUT_STRIP_TRAILING_WHITESPACE ) - - GET_FILENAME_COMPONENT(_ABS_PYTHON_MODULE_PATH - "${_ABS_PYTHON_MODULE_PATH}" ABSOLUTE) - FILE(RELATIVE_PATH _REL_PYTHON_MODULE_PATH - ${CMAKE_INSTALL_PREFIX} ${_ABS_PYTHON_MODULE_PATH}) - SET(PYTHON_MODULE_PATH ${_ABS_PYTHON_MODULE_PATH}) +print( get_python_lib( plat_specific=True, prefix='${CMAKE_INSTALL_PREFIX}' ) )") + set(PY_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}) endif() endif() - message("PYTHON_MODULE_PATH: ${PYTHON_MODULE_PATH}") + + if (NOT PYTHON_MODULE_PATH) + execute_process ( COMMAND ${PYTHON_EXECUTABLE} -c "${py_detection}" + OUTPUT_VARIABLE _ABS_PYTHON_MODULE_PATH + OUTPUT_STRIP_TRAILING_WHITESPACE ) + + GET_FILENAME_COMPONENT(_ABS_PYTHON_MODULE_PATH + "${_ABS_PYTHON_MODULE_PATH}" ABSOLUTE) + FILE(RELATIVE_PATH _REL_PYTHON_MODULE_PATH + ${PY_INSTALL_PREFIX} ${_ABS_PYTHON_MODULE_PATH}) + SET(PYTHON_MODULE_PATH ${_REL_PYTHON_MODULE_PATH} + CACHE PATH "Install path for Python modules (relative to prefix)") + endif() + + message(STATUS "Will install Python module to: ${PYTHON_MODULE_PATH}") ############### INSTALL HEADERS & LIBRARY ################ ### Install Python bindings From bfa8a838643989316ede715b731e2998d893fbff Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Fri, 3 May 2019 13:13:45 -0700 Subject: [PATCH 175/223] The default return value is present Remove else so that the default return value is used if no other return was used."else if" in line 334 had no else and therefore in some cases no return value was present. --- src/KeyFrame.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/KeyFrame.cpp b/src/KeyFrame.cpp index 025484a3..d83adc7f 100644 --- a/src/KeyFrame.cpp +++ b/src/KeyFrame.cpp @@ -336,9 +336,8 @@ bool Keyframe::IsIncreasing(int index) return false; } } - else - // return default true (since most curves increase) - return true; + // return default true (since most curves increase) + return true; } // Generate JSON string of this object From c55d8551c15b2f2c913627765476015d6343def1 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Sun, 5 May 2019 18:18:14 -0700 Subject: [PATCH 176/223] Simplification Further simplify the else branches. Thanks to SuslikV for pointing this out. --- src/KeyFrame.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/KeyFrame.cpp b/src/KeyFrame.cpp index d83adc7f..2b0389de 100644 --- a/src/KeyFrame.cpp +++ b/src/KeyFrame.cpp @@ -327,11 +327,7 @@ bool Keyframe::IsIncreasing(int index) } } - if (current_value < next_value) { - // Increasing - return true; - } - else if (current_value >= next_value) { + if (current_value >= next_value) { // Decreasing return false; } From d23197c9b64b183826ee0aa1d7b304ff78f8bc95 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Wed, 8 May 2019 14:00:55 -0500 Subject: [PATCH 177/223] Updating hwaccel table to use emojis (instead of words) take 3 --- include/QtImageReader.h | 7 ++++--- src/QtImageReader.cpp | 11 +++++++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/include/QtImageReader.h b/include/QtImageReader.h index 6b260f15..e4d14f9b 100644 --- a/include/QtImageReader.h +++ b/include/QtImageReader.h @@ -65,9 +65,10 @@ namespace openshot { private: string path; - std::shared_ptr image; ///> Original image (full quality) - std::shared_ptr cached_image; ///> Scaled for performance - bool is_open; + std::shared_ptr image; ///> Original image (full quality) + std::shared_ptr cached_image; ///> Scaled for performance + bool is_open; ///> Is Reader opened + QSize max_size; ///> Current max_size as calculated with Clip properties public: diff --git a/src/QtImageReader.cpp b/src/QtImageReader.cpp index c500d221..a9682bd9 100644 --- a/src/QtImageReader.cpp +++ b/src/QtImageReader.cpp @@ -130,6 +130,10 @@ void QtImageReader::Open() info.display_ratio.num = size.num; info.display_ratio.den = size.den; + // Set current max size + max_size.setWidth(info.width); + max_size.setHeight(info.height); + // Mark as "open" is_open = true; } @@ -209,8 +213,7 @@ std::shared_ptr QtImageReader::GetFrame(int64_t requested_frame) } // Scale image smaller (or use a previous scaled image) - if (!cached_image || (cached_image && cached_image->width() != max_width || cached_image->height() != max_height)) { - + if (!cached_image || (cached_image && max_size.width() != max_width || max_size.height() != max_height)) { #if USE_RESVG == 1 // If defined and found in CMake, utilize the libresvg for parsing // SVG files and rasterizing them to QImages. @@ -239,6 +242,10 @@ std::shared_ptr QtImageReader::GetFrame(int64_t requested_frame) cached_image = std::shared_ptr(new QImage(image->scaled(max_width, max_height, Qt::KeepAspectRatio, Qt::SmoothTransformation))); cached_image = std::shared_ptr(new QImage(cached_image->convertToFormat(QImage::Format_RGBA8888))); #endif + + // Set max size (to later determine if max_size is changed) + max_size.setWidth(max_width); + max_size.setHeight(max_height); } // Create or get frame object From 833fcb8e8e69dc0cffa15b19424942165d190c19 Mon Sep 17 00:00:00 2001 From: Chris Kirmse Date: Wed, 8 May 2019 14:53:23 -0700 Subject: [PATCH 178/223] fix a number of memory leaks - some were with libav functions - same were due to non-virtual destructors --- include/CacheBase.h | 2 ++ include/CacheMemory.h | 2 +- include/Clip.h | 2 +- include/ClipBase.h | 1 + include/FFmpegReader.h | 2 +- include/FFmpegUtilities.h | 6 +++--- include/Frame.h | 2 +- include/FrameMapper.h | 2 +- include/QtImageReader.h | 2 ++ include/ReaderBase.h | 2 ++ include/Timeline.h | 2 ++ src/CacheBase.cpp | 5 ++++- src/ClipBase.cpp | 5 ++++- src/FFmpegReader.cpp | 15 +++++++++++++-- src/FFmpegWriter.cpp | 22 +++++++--------------- src/Frame.cpp | 2 +- src/FrameMapper.cpp | 3 +++ src/QtImageReader.cpp | 6 +++++- src/ReaderBase.cpp | 3 +++ src/Timeline.cpp | 7 ++++++- 20 files changed, 63 insertions(+), 30 deletions(-) diff --git a/include/CacheBase.h b/include/CacheBase.h index aaef5320..c764b2d9 100644 --- a/include/CacheBase.h +++ b/include/CacheBase.h @@ -60,6 +60,8 @@ namespace openshot { /// @param max_bytes The maximum bytes to allow in the cache. Once exceeded, the cache will purge the oldest frames. CacheBase(int64_t max_bytes); + virtual ~CacheBase(); + /// @brief Add a Frame to the cache /// @param frame The openshot::Frame object needing to be cached. virtual void Add(std::shared_ptr frame) = 0; diff --git a/include/CacheMemory.h b/include/CacheMemory.h index 2f3f018b..29187799 100644 --- a/include/CacheMemory.h +++ b/include/CacheMemory.h @@ -71,7 +71,7 @@ namespace openshot { CacheMemory(int64_t max_bytes); // Default destructor - ~CacheMemory(); + virtual ~CacheMemory(); /// @brief Add a Frame to the cache /// @param frame The openshot::Frame object needing to be cached. diff --git a/include/Clip.h b/include/Clip.h index 346629e4..b1869a90 100644 --- a/include/Clip.h +++ b/include/Clip.h @@ -160,7 +160,7 @@ namespace openshot { Clip(ReaderBase* new_reader); /// Destructor - ~Clip(); + virtual ~Clip(); /// @brief Add an effect to the clip /// @param effect Add an effect to the clip. An effect can modify the audio or video of an openshot::Frame. diff --git a/include/ClipBase.h b/include/ClipBase.h index 3dae8a53..1c5534fc 100644 --- a/include/ClipBase.h +++ b/include/ClipBase.h @@ -69,6 +69,7 @@ namespace openshot { /// Constructor for the base clip ClipBase() { }; + virtual ~ClipBase(); // Compare a clip using the Position() property bool operator< ( ClipBase& a) { return (Position() < a.Position()); } diff --git a/include/FFmpegReader.h b/include/FFmpegReader.h index abf1af57..7ef44c50 100644 --- a/include/FFmpegReader.h +++ b/include/FFmpegReader.h @@ -243,7 +243,7 @@ namespace openshot { FFmpegReader(string path, bool inspect_reader); /// Destructor - ~FFmpegReader(); + virtual ~FFmpegReader(); /// Close File void Close(); diff --git a/include/FFmpegUtilities.h b/include/FFmpegUtilities.h index 0d12ba72..7e3e0070 100644 --- a/include/FFmpegUtilities.h +++ b/include/FFmpegUtilities.h @@ -209,9 +209,9 @@ #define AV_FORMAT_NEW_STREAM(oc, st_codec, av_codec, av_st) av_st = avformat_new_stream(oc, NULL);\ if (!av_st) \ throw OutOfMemory("Could not allocate memory for the video stream.", path); \ - c = avcodec_alloc_context3(av_codec); \ - st_codec = c; \ - av_st->codecpar->codec_id = av_codec->id; + avcodec_get_context_defaults3(av_st->codec, av_codec); \ + c = av_st->codec; \ + st_codec = c; #define AV_COPY_PARAMS_FROM_CONTEXT(av_stream, av_codec) avcodec_parameters_from_context(av_stream->codecpar, av_codec); #elif LIBAVFORMAT_VERSION_MAJOR >= 55 #define AV_REGISTER_ALL av_register_all(); diff --git a/include/Frame.h b/include/Frame.h index 66d8ccfa..56a2a3ec 100644 --- a/include/Frame.h +++ b/include/Frame.h @@ -159,7 +159,7 @@ namespace openshot Frame& operator= (const Frame& other); /// Destructor - ~Frame(); + virtual ~Frame(); /// Add (or replace) pixel data to the frame (based on a solid color) void AddColor(int new_width, int new_height, string new_color); diff --git a/include/FrameMapper.h b/include/FrameMapper.h index 216fe73f..1901cb91 100644 --- a/include/FrameMapper.h +++ b/include/FrameMapper.h @@ -170,7 +170,7 @@ namespace openshot FrameMapper(ReaderBase *reader, Fraction target_fps, PulldownType target_pulldown, int target_sample_rate, int target_channels, ChannelLayout target_channel_layout); /// Destructor - ~FrameMapper(); + virtual ~FrameMapper(); /// Change frame rate or audio mapping details void ChangeMapping(Fraction target_fps, PulldownType pulldown, int target_sample_rate, int target_channels, ChannelLayout target_channel_layout); diff --git a/include/QtImageReader.h b/include/QtImageReader.h index e4d14f9b..d26c9b79 100644 --- a/include/QtImageReader.h +++ b/include/QtImageReader.h @@ -81,6 +81,8 @@ namespace openshot /// when you are inflating the object using JSON after instantiating it. QtImageReader(string path, bool inspect_reader); + virtual ~QtImageReader(); + /// Close File void Close(); diff --git a/include/ReaderBase.h b/include/ReaderBase.h index b0a1b3db..efbd5bc7 100644 --- a/include/ReaderBase.h +++ b/include/ReaderBase.h @@ -107,6 +107,8 @@ namespace openshot /// Constructor for the base reader, where many things are initialized. ReaderBase(); + virtual ~ReaderBase(); + /// Information about the current media file ReaderInfo info; diff --git a/include/Timeline.h b/include/Timeline.h index 312add2e..97923153 100644 --- a/include/Timeline.h +++ b/include/Timeline.h @@ -205,6 +205,8 @@ namespace openshot { /// @param channel_layout The channel layout (i.e. mono, stereo, 3 point surround, etc...) Timeline(int width, int height, Fraction fps, int sample_rate, int channels, ChannelLayout channel_layout); + virtual ~Timeline(); + /// @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. void AddClip(Clip* clip); diff --git a/src/CacheBase.cpp b/src/CacheBase.cpp index cffd995d..874674c0 100644 --- a/src/CacheBase.cpp +++ b/src/CacheBase.cpp @@ -42,6 +42,9 @@ CacheBase::CacheBase(int64_t max_bytes) : max_bytes(max_bytes) { cacheCriticalSection = new CriticalSection(); }; +CacheBase::~CacheBase() { +}; + // Set maximum bytes to a different amount based on a ReaderInfo struct void CacheBase::SetMaxBytesFromInfo(int64_t number_of_frames, int width, int height, int sample_rate, int channels) { @@ -69,4 +72,4 @@ void CacheBase::SetJsonValue(Json::Value root) { // Set data from Json (if key is found) if (!root["max_bytes"].isNull()) max_bytes = atoll(root["max_bytes"].asString().c_str()); -} \ No newline at end of file +} diff --git a/src/ClipBase.cpp b/src/ClipBase.cpp index 80cad87d..b2926244 100644 --- a/src/ClipBase.cpp +++ b/src/ClipBase.cpp @@ -29,6 +29,9 @@ using namespace openshot; +ClipBase::~ClipBase() { +} + // Generate Json::JsonValue for this object Json::Value ClipBase::JsonValue() { @@ -108,4 +111,4 @@ Json::Value ClipBase::add_property_choice_json(string name, int value, int selec // return JsonValue return new_choice; -} \ No newline at end of file +} diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index acd3b55f..0b67da4e 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -576,6 +576,12 @@ void FFmpegReader::Close() { ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::Close", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); + if (packet) { + // Remove previous packet before getting next one + RemoveAVPacket(packet); + packet = NULL; + } + // Close the codec if (info.has_video) { avcodec_flush_buffers(pCodecCtx); @@ -624,6 +630,8 @@ void FFmpegReader::Close() { seek_video_frame_found = 0; current_video_frame = 0; has_missing_frames = false; + + last_video_frame.reset(); } } @@ -926,7 +934,9 @@ std::shared_ptr FFmpegReader::ReadStream(int64_t requested_frame) { // down processing considerably, but might be more stable on some systems. #pragma omp taskwait } - } + } else { + RemoveAVFrame(pFrame); + } } // Audio packet @@ -1063,7 +1073,7 @@ bool FFmpegReader::GetAVFrame() { { next_frame2 = next_frame; } - pFrame = new AVFrame(); + pFrame = AV_ALLOCATE_FRAME(); while (ret >= 0) { ret = avcodec_receive_frame(pCodecCtx, next_frame2); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { @@ -1206,6 +1216,7 @@ void FFmpegReader::ProcessVideoPacket(int64_t requested_frame) { int width = info.width; int64_t video_length = info.video_length; AVFrame *my_frame = pFrame; + pFrame = NULL; // Add video frame to list of processing video frames const GenericScopedLock lock(processingCriticalSection); diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index 072ac6d7..ddcd4e16 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -884,9 +884,8 @@ void FFmpegWriter::flush_encoders() { } // Close the video codec -void FFmpegWriter::close_video(AVFormatContext *oc, AVStream *st) { - AV_FREE_CONTEXT(video_codec); - video_codec = NULL; +void FFmpegWriter::close_video(AVFormatContext *oc, AVStream *st) +{ #if IS_FFMPEG_3_2 // #if defined(__linux__) if (hw_en_on && hw_en_supported) { @@ -900,10 +899,8 @@ void FFmpegWriter::close_video(AVFormatContext *oc, AVStream *st) { } // Close the audio codec -void FFmpegWriter::close_audio(AVFormatContext *oc, AVStream *st) { - AV_FREE_CONTEXT(audio_codec); - audio_codec = NULL; - +void FFmpegWriter::close_audio(AVFormatContext *oc, AVStream *st) +{ // Clear buffers delete[] samples; delete[] audio_outbuf; @@ -942,12 +939,6 @@ void FFmpegWriter::Close() { if (image_rescalers.size() > 0) RemoveScalers(); - // Free the streams - for (int i = 0; i < oc->nb_streams; i++) { - av_freep(AV_GET_CODEC_ATTRIBUTES(&oc->streams[i], &oc->streams[i])); - av_freep(&oc->streams[i]); - } - if (!(fmt->flags & AVFMT_NOFILE)) { /* close the output file */ avio_close(oc->pb); @@ -957,8 +948,9 @@ void FFmpegWriter::Close() { write_video_count = 0; write_audio_count = 0; - // Free the context - av_freep(&oc); + // Free the context which frees the streams too + avformat_free_context(oc); + oc = NULL; // Close writer is_open = false; diff --git a/src/Frame.cpp b/src/Frame.cpp index 24b653a9..6ef44d67 100644 --- a/src/Frame.cpp +++ b/src/Frame.cpp @@ -120,7 +120,7 @@ void Frame::DeepCopy(const Frame& other) wave_image = std::shared_ptr(new QImage(*(other.wave_image))); } -// Descructor +// Destructor Frame::~Frame() { // Clear all pointers image.reset(); diff --git a/src/FrameMapper.cpp b/src/FrameMapper.cpp index 73b7bb22..cf6955f2 100644 --- a/src/FrameMapper.cpp +++ b/src/FrameMapper.cpp @@ -61,6 +61,9 @@ FrameMapper::~FrameMapper() { if (is_open) // Auto Close if not already Close(); + + delete reader; + reader = NULL; } /// Get the current reader diff --git a/src/QtImageReader.cpp b/src/QtImageReader.cpp index a9682bd9..502ddb9a 100644 --- a/src/QtImageReader.cpp +++ b/src/QtImageReader.cpp @@ -57,6 +57,10 @@ QtImageReader::QtImageReader(string path, bool inspect_reader) : path(path), is_ } } +QtImageReader::~QtImageReader() +{ +} + // Open image file void QtImageReader::Open() { @@ -147,7 +151,7 @@ void QtImageReader::Close() { // Mark as "closed" is_open = false; - + // Delete the image image.reset(); diff --git a/src/ReaderBase.cpp b/src/ReaderBase.cpp index f2607cfd..3b1bb76f 100644 --- a/src/ReaderBase.cpp +++ b/src/ReaderBase.cpp @@ -63,6 +63,9 @@ ReaderBase::ReaderBase() parent = NULL; } +ReaderBase::~ReaderBase() { +} + // Display file information void ReaderBase::DisplayInfo() { cout << fixed << setprecision(2) << boolalpha; diff --git a/src/Timeline.cpp b/src/Timeline.cpp index b229a3de..e40de562 100644 --- a/src/Timeline.cpp +++ b/src/Timeline.cpp @@ -70,6 +70,11 @@ Timeline::Timeline(int width, int height, Fraction fps, int sample_rate, int cha final_cache->SetMaxBytesFromInfo(OPEN_MP_NUM_PROCESSORS * 2, info.width, info.height, info.sample_rate, info.channels); } +Timeline::~Timeline() { + delete final_cache; + final_cache = NULL; +} + // Add an openshot::Clip to the timeline void Timeline::AddClip(Clip* clip) { @@ -1481,4 +1486,4 @@ void Timeline::SetMaxSize(int width, int height) { // Set max size Settings::Instance()->MAX_WIDTH = display_ratio_size.width(); Settings::Instance()->MAX_HEIGHT = display_ratio_size.height(); -} \ No newline at end of file +} From d5a29500a52ccef98bf9cc00e0c20f1d4750fb53 Mon Sep 17 00:00:00 2001 From: Chris Kirmse Date: Thu, 9 May 2019 10:51:40 -0700 Subject: [PATCH 179/223] change freeing of frame_mappers allocated in Timeline - each class is now responsible to free whatever it allocates - all tests passed on my machine with ffmpeg 3.2 - Clip is now more careful about freeing a reader if it allocated it as well --- include/Clip.h | 5 ++++- include/DummyReader.h | 2 ++ include/Timeline.h | 2 ++ src/Clip.cpp | 19 ++++++++----------- src/DummyReader.cpp | 3 +++ src/FrameMapper.cpp | 1 - src/Timeline.cpp | 22 +++++++++++++++++++--- 7 files changed, 38 insertions(+), 16 deletions(-) diff --git a/include/Clip.h b/include/Clip.h index b1869a90..58eacab7 100644 --- a/include/Clip.h +++ b/include/Clip.h @@ -112,7 +112,10 @@ namespace openshot { // File Reader object ReaderBase* reader; - bool manage_reader; + + /// If we allocated a reader, we store it here to free it later + /// (reader member variable itself may have been replaced) + ReaderBase* allocated_reader; /// Adjust frame number minimum value int64_t adjust_frame_number_minimum(int64_t frame_number); diff --git a/include/DummyReader.h b/include/DummyReader.h index 559215de..e9bce1b5 100644 --- a/include/DummyReader.h +++ b/include/DummyReader.h @@ -64,6 +64,8 @@ namespace openshot /// Constructor for DummyReader. DummyReader(Fraction fps, int width, int height, int sample_rate, int channels, float duration); + virtual ~DummyReader(); + /// Close File void Close(); diff --git a/include/Timeline.h b/include/Timeline.h index 97923153..eecafec1 100644 --- a/include/Timeline.h +++ b/include/Timeline.h @@ -30,6 +30,7 @@ #include #include +#include #include #include #include "CacheBase.h" @@ -152,6 +153,7 @@ namespace openshot { map open_clips; /// effects; /// allocated_frame_mappers; /// all the frame mappers we allocated and must free /// Process a new layer of video or audio void add_layer(std::shared_ptr new_frame, Clip* source_clip, int64_t clip_frame_number, int64_t timeline_frame_number, bool is_top_clip, float max_volume); diff --git a/src/Clip.cpp b/src/Clip.cpp index 207494e3..2099705d 100644 --- a/src/Clip.cpp +++ b/src/Clip.cpp @@ -101,9 +101,6 @@ void Clip::init_settings() // Init audio and video overrides has_audio = Keyframe(-1.0); has_video = Keyframe(-1.0); - - // Default pointers - manage_reader = false; } // Init reader's rotation (if any) @@ -131,14 +128,14 @@ void Clip::init_reader_rotation() { } // Default Constructor for a clip -Clip::Clip() : reader(NULL), resampler(NULL), audio_cache(NULL) +Clip::Clip() : resampler(NULL), audio_cache(NULL), reader(NULL), allocated_reader(NULL) { // Init all default settings init_settings(); } // Constructor with reader -Clip::Clip(ReaderBase* new_reader) : reader(new_reader), resampler(NULL), audio_cache(NULL) +Clip::Clip(ReaderBase* new_reader) : resampler(NULL), audio_cache(NULL), reader(new_reader), allocated_reader(NULL) { // Init all default settings init_settings(); @@ -152,7 +149,7 @@ Clip::Clip(ReaderBase* new_reader) : reader(new_reader), resampler(NULL), audio_ } // Constructor with filepath -Clip::Clip(string path) : reader(NULL), resampler(NULL), audio_cache(NULL) +Clip::Clip(string path) : resampler(NULL), audio_cache(NULL), reader(NULL), allocated_reader(NULL) { // Init all default settings init_settings(); @@ -194,7 +191,7 @@ Clip::Clip(string path) : reader(NULL), resampler(NULL), audio_cache(NULL) // Update duration if (reader) { End(reader->info.duration); - manage_reader = true; + allocated_reader = reader; init_reader_rotation(); } } @@ -203,9 +200,9 @@ Clip::Clip(string path) : reader(NULL), resampler(NULL), audio_cache(NULL) Clip::~Clip() { // Delete the reader if clip created it - if (manage_reader && reader) { - delete reader; - reader = NULL; + if (allocated_reader) { + delete allocated_reader; + allocated_reader = NULL; } // Close the resampler @@ -968,7 +965,7 @@ void Clip::SetJsonValue(Json::Value root) { // mark as managed reader and set parent if (reader) { reader->SetClip(this); - manage_reader = true; + allocated_reader = reader; } // Re-Open reader (if needed) diff --git a/src/DummyReader.cpp b/src/DummyReader.cpp index 8fe039ab..dc77db5b 100644 --- a/src/DummyReader.cpp +++ b/src/DummyReader.cpp @@ -71,6 +71,9 @@ DummyReader::DummyReader(Fraction fps, int width, int height, int sample_rate, i Close(); } +DummyReader::~DummyReader() { +} + // Open image file void DummyReader::Open() { diff --git a/src/FrameMapper.cpp b/src/FrameMapper.cpp index cf6955f2..ca2038a9 100644 --- a/src/FrameMapper.cpp +++ b/src/FrameMapper.cpp @@ -62,7 +62,6 @@ FrameMapper::~FrameMapper() { // Auto Close if not already Close(); - delete reader; reader = NULL; } diff --git a/src/Timeline.cpp b/src/Timeline.cpp index e40de562..8692d17f 100644 --- a/src/Timeline.cpp +++ b/src/Timeline.cpp @@ -71,8 +71,12 @@ Timeline::Timeline(int width, int height, Fraction fps, int sample_rate, int cha } Timeline::~Timeline() { - delete final_cache; - final_cache = NULL; + if (is_open) + // Auto Close if not already + Close(); + + delete final_cache; + final_cache = NULL; } // Add an openshot::Clip to the timeline @@ -128,7 +132,9 @@ void Timeline::apply_mapper_to_clip(Clip* clip) } else { // Create a new FrameMapper to wrap the current reader - clip_reader = (ReaderBase*) new FrameMapper(clip->Reader(), info.fps, PULLDOWN_NONE, info.sample_rate, info.channels, info.channel_layout); + FrameMapper* mapper = new FrameMapper(clip->Reader(), info.fps, PULLDOWN_NONE, info.sample_rate, info.channels, info.channel_layout); + allocated_frame_mappers.insert(mapper); + clip_reader = (ReaderBase*) mapper; } // Update the mapping @@ -642,6 +648,16 @@ void Timeline::Close() update_open_clips(clip, false); } + // Free all allocated frame mappers + set::iterator frame_mapper_itr; + for (frame_mapper_itr=allocated_frame_mappers.begin(); frame_mapper_itr != allocated_frame_mappers.end(); ++frame_mapper_itr) + { + // Get frame mapper object from the iterator + FrameMapper *frame_mapper = (*frame_mapper_itr); + delete frame_mapper; + } + allocated_frame_mappers.clear(); + // Mark timeline as closed is_open = false; From 8ea0af59c60e77ce0ff260a134876ce511fa4c0c Mon Sep 17 00:00:00 2001 From: Chris Kirmse Date: Fri, 10 May 2019 11:39:26 -0700 Subject: [PATCH 180/223] fix allocations to be done the same for ffmpeg < 3.2 - fixes freeing an invalid pointer on old ffmpeg - now all tests pass --- src/FFmpegReader.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 0b67da4e..38d59f83 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -1120,11 +1120,13 @@ bool FFmpegReader::GetAVFrame() { #else avcodec_decode_video2(pCodecCtx, next_frame, &frameFinished, packet); + // always allocate pFrame (because we do that in the ffmpeg >= 3.2 as well); it will always be freed later + pFrame = AV_ALLOCATE_FRAME(); + // is frame finished if (frameFinished) { // AVFrames are clobbered on the each call to avcodec_decode_video, so we // must make a copy of the image data before this method is called again. - pFrame = AV_ALLOCATE_FRAME(); avpicture_alloc((AVPicture *) pFrame, pCodecCtx->pix_fmt, info.width, info.height); av_picture_copy((AVPicture *) pFrame, (AVPicture *) next_frame, pCodecCtx->pix_fmt, info.width, info.height); From bd21d1a751b3d1375cae37747e6d193e2e38d17c Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Mon, 13 May 2019 16:18:15 -0500 Subject: [PATCH 181/223] Fixing crash on Timeline::Close due to deleted FrameMappers --- src/Timeline.cpp | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/Timeline.cpp b/src/Timeline.cpp index 8692d17f..fd79364a 100644 --- a/src/Timeline.cpp +++ b/src/Timeline.cpp @@ -75,8 +75,20 @@ Timeline::~Timeline() { // Auto Close if not already Close(); - delete final_cache; - final_cache = NULL; + // Free all allocated frame mappers + set::iterator frame_mapper_itr; + for (frame_mapper_itr = allocated_frame_mappers.begin(); frame_mapper_itr != allocated_frame_mappers.end(); ++frame_mapper_itr) { + // Get frame mapper object from the iterator + FrameMapper *frame_mapper = (*frame_mapper_itr); + delete frame_mapper; + } + allocated_frame_mappers.clear(); + + // Remove cache + if (final_cache) { + delete final_cache; + final_cache = NULL; + } } // Add an openshot::Clip to the timeline @@ -648,16 +660,6 @@ void Timeline::Close() update_open_clips(clip, false); } - // Free all allocated frame mappers - set::iterator frame_mapper_itr; - for (frame_mapper_itr=allocated_frame_mappers.begin(); frame_mapper_itr != allocated_frame_mappers.end(); ++frame_mapper_itr) - { - // Get frame mapper object from the iterator - FrameMapper *frame_mapper = (*frame_mapper_itr); - delete frame_mapper; - } - allocated_frame_mappers.clear(); - // Mark timeline as closed is_open = false; From 968e472c73bccac9179a139fa7584a05acf8a95b Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Mon, 13 May 2019 17:11:40 -0500 Subject: [PATCH 182/223] Tweak how Timeline manages the cache object (sometimes itself, and sometimes by the user if they call SetCache) --- include/Timeline.h | 6 ++++-- src/Timeline.cpp | 13 ++++++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/include/Timeline.h b/include/Timeline.h index eecafec1..11911d40 100644 --- a/include/Timeline.h +++ b/include/Timeline.h @@ -153,7 +153,8 @@ namespace openshot { map open_clips; /// effects; /// allocated_frame_mappers; /// all the frame mappers we allocated and must free + set allocated_frame_mappers; ///< all the frame mappers we allocated and must free + bool managed_cache; ///< Does this timeline instance manage the cache object /// Process a new layer of video or audio void add_layer(std::shared_ptr new_frame, Clip* source_clip, int64_t clip_frame_number, int64_t timeline_frame_number, bool is_top_clip, float max_volume); @@ -241,7 +242,8 @@ namespace openshot { /// Get the cache object used by this reader CacheBase* GetCache() { return final_cache; }; - /// Get the cache object used by this reader + /// Set the cache object used by this reader. You must now manage the lifecycle + /// of this cache object though (Timeline will not delete it for you). void SetCache(CacheBase* new_cache); /// Get an openshot::Frame object for a specific frame number of this timeline. diff --git a/src/Timeline.cpp b/src/Timeline.cpp index fd79364a..91194399 100644 --- a/src/Timeline.cpp +++ b/src/Timeline.cpp @@ -31,7 +31,7 @@ using namespace openshot; // Default Constructor for the timeline (which sets the canvas width and height) Timeline::Timeline(int width, int height, Fraction fps, int sample_rate, int channels, ChannelLayout channel_layout) : - is_open(false), auto_map_clips(true) + is_open(false), auto_map_clips(true), managed_cache(true) { // Create CrashHandler and Attach (incase of errors) CrashHandler::Instance(); @@ -84,8 +84,8 @@ Timeline::~Timeline() { } allocated_frame_mappers.clear(); - // Remove cache - if (final_cache) { + // Destroy previous cache (if managed by timeline) + if (managed_cache && final_cache) { delete final_cache; final_cache = NULL; } @@ -923,6 +923,13 @@ vector Timeline::find_intersecting_clips(int64_t requested_frame, int num // Get the cache object used by this reader void Timeline::SetCache(CacheBase* new_cache) { + // Destroy previous cache (if managed by timeline) + if (managed_cache && final_cache) { + delete final_cache; + final_cache = NULL; + managed_cache = false; + } + // Set new cache final_cache = new_cache; } From 6335d6f06cfbfa796454eeabd19ac00547432579 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Mon, 13 May 2019 22:48:21 -0500 Subject: [PATCH 183/223] Adding debugging messaging to unit test which is failing on Travis CI --- tests/Timeline_Tests.cpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/Timeline_Tests.cpp b/tests/Timeline_Tests.cpp index 8c81579c..a59907b3 100644 --- a/tests/Timeline_Tests.cpp +++ b/tests/Timeline_Tests.cpp @@ -185,7 +185,9 @@ TEST(Timeline_Check_Two_Track_Video) TEST(Timeline_Clip_Order) { // Create a timeline + cout << "A" << endl; Timeline t(640, 480, Fraction(30, 1), 44100, 2, LAYOUT_STEREO); + cout << "B" << endl; // Add some clips out of order stringstream path_top; @@ -193,40 +195,51 @@ TEST(Timeline_Clip_Order) Clip clip_top(path_top.str()); clip_top.Layer(2); t.AddClip(&clip_top); + cout << "C" << endl; stringstream path_middle; path_middle << TEST_MEDIA_PATH << "front.png"; Clip clip_middle(path_middle.str()); clip_middle.Layer(0); t.AddClip(&clip_middle); + cout << "D" << endl; stringstream path_bottom; path_bottom << TEST_MEDIA_PATH << "back.png"; Clip clip_bottom(path_bottom.str()); clip_bottom.Layer(1); t.AddClip(&clip_bottom); + cout << "E" << endl; // Open Timeline t.Open(); + cout << "F" << endl; // 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(); + cout << "G" << endl; int counter = 0; for (clip_itr=clips.begin(); clip_itr != clips.end(); ++clip_itr) { + cout << "H" << endl; // Get clip object from the iterator Clip *clip = (*clip_itr); + cout << "I" << endl; + switch (counter) { case 0: + cout << "J" << endl; CHECK_EQUAL(0, clip->Layer()); break; case 1: + cout << "K" << endl; CHECK_EQUAL(1, clip->Layer()); break; case 2: + cout << "L" << endl; CHECK_EQUAL(2, clip->Layer()); break; } @@ -235,6 +248,8 @@ TEST(Timeline_Clip_Order) counter++; } + cout << "M" << endl; + // Add another clip stringstream path_middle1; path_middle1 << TEST_MEDIA_PATH << "interlaced.png"; @@ -243,28 +258,35 @@ TEST(Timeline_Clip_Order) clip_middle1.Position(0.5); t.AddClip(&clip_middle1); + cout << "N" << endl; // Loop through clips again, and re-check order counter = 0; clips = t.Clips(); + cout << "O" << endl; for (clip_itr=clips.begin(); clip_itr != clips.end(); ++clip_itr) { // Get clip object from the iterator Clip *clip = (*clip_itr); + cout << "P" << endl; switch (counter) { case 0: + cout << "Q" << endl; CHECK_EQUAL(0, clip->Layer()); break; case 1: + cout << "R" << endl; CHECK_EQUAL(1, clip->Layer()); CHECK_CLOSE(0.0, clip->Position(), 0.0001); break; case 2: + cout << "S" << endl; CHECK_EQUAL(1, clip->Layer()); CHECK_CLOSE(0.5, clip->Position(), 0.0001); break; case 3: + cout << "T" << endl; CHECK_EQUAL(2, clip->Layer()); break; } @@ -273,8 +295,12 @@ TEST(Timeline_Clip_Order) counter++; } + cout << "U" << endl; + // Close reader t.Close(); + + cout << "V" << endl; } From 9ffd6a6f754ac03200aec40b3a20820116f7f72d Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Mon, 13 May 2019 23:55:03 -0500 Subject: [PATCH 184/223] Fixing crash when destructing Timeline/Clips/FrameMapper --- include/FrameMapper.h | 3 +++ src/Timeline.cpp | 2 ++ tests/Timeline_Tests.cpp | 27 --------------------------- 3 files changed, 5 insertions(+), 27 deletions(-) diff --git a/include/FrameMapper.h b/include/FrameMapper.h index 1901cb91..d046af25 100644 --- a/include/FrameMapper.h +++ b/include/FrameMapper.h @@ -213,6 +213,9 @@ namespace openshot /// Get the current reader ReaderBase* Reader(); + /// Set the current reader + void Reader(ReaderBase *new_reader) { reader = new_reader; } + /// Resample audio and map channels (if needed) void ResampleMappedAudio(std::shared_ptr frame, int64_t original_frame_number); diff --git a/src/Timeline.cpp b/src/Timeline.cpp index 91194399..2ca24e84 100644 --- a/src/Timeline.cpp +++ b/src/Timeline.cpp @@ -80,6 +80,8 @@ Timeline::~Timeline() { for (frame_mapper_itr = allocated_frame_mappers.begin(); frame_mapper_itr != allocated_frame_mappers.end(); ++frame_mapper_itr) { // Get frame mapper object from the iterator FrameMapper *frame_mapper = (*frame_mapper_itr); + frame_mapper->Reader(NULL); + frame_mapper->Close(); delete frame_mapper; } allocated_frame_mappers.clear(); diff --git a/tests/Timeline_Tests.cpp b/tests/Timeline_Tests.cpp index a59907b3..22f33894 100644 --- a/tests/Timeline_Tests.cpp +++ b/tests/Timeline_Tests.cpp @@ -185,9 +185,7 @@ TEST(Timeline_Check_Two_Track_Video) TEST(Timeline_Clip_Order) { // Create a timeline - cout << "A" << endl; Timeline t(640, 480, Fraction(30, 1), 44100, 2, LAYOUT_STEREO); - cout << "B" << endl; // Add some clips out of order stringstream path_top; @@ -195,51 +193,40 @@ TEST(Timeline_Clip_Order) Clip clip_top(path_top.str()); clip_top.Layer(2); t.AddClip(&clip_top); - cout << "C" << endl; stringstream path_middle; path_middle << TEST_MEDIA_PATH << "front.png"; Clip clip_middle(path_middle.str()); clip_middle.Layer(0); t.AddClip(&clip_middle); - cout << "D" << endl; stringstream path_bottom; path_bottom << TEST_MEDIA_PATH << "back.png"; Clip clip_bottom(path_bottom.str()); clip_bottom.Layer(1); t.AddClip(&clip_bottom); - cout << "E" << endl; // Open Timeline t.Open(); - cout << "F" << endl; // 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(); - cout << "G" << endl; int counter = 0; for (clip_itr=clips.begin(); clip_itr != clips.end(); ++clip_itr) { - cout << "H" << endl; // Get clip object from the iterator Clip *clip = (*clip_itr); - cout << "I" << endl; - switch (counter) { case 0: - cout << "J" << endl; CHECK_EQUAL(0, clip->Layer()); break; case 1: - cout << "K" << endl; CHECK_EQUAL(1, clip->Layer()); break; case 2: - cout << "L" << endl; CHECK_EQUAL(2, clip->Layer()); break; } @@ -248,8 +235,6 @@ TEST(Timeline_Clip_Order) counter++; } - cout << "M" << endl; - // Add another clip stringstream path_middle1; path_middle1 << TEST_MEDIA_PATH << "interlaced.png"; @@ -258,35 +243,27 @@ TEST(Timeline_Clip_Order) clip_middle1.Position(0.5); t.AddClip(&clip_middle1); - cout << "N" << endl; - // Loop through clips again, and re-check order counter = 0; clips = t.Clips(); - cout << "O" << endl; for (clip_itr=clips.begin(); clip_itr != clips.end(); ++clip_itr) { // Get clip object from the iterator Clip *clip = (*clip_itr); - cout << "P" << endl; switch (counter) { case 0: - cout << "Q" << endl; CHECK_EQUAL(0, clip->Layer()); break; case 1: - cout << "R" << endl; CHECK_EQUAL(1, clip->Layer()); CHECK_CLOSE(0.0, clip->Position(), 0.0001); break; case 2: - cout << "S" << endl; CHECK_EQUAL(1, clip->Layer()); CHECK_CLOSE(0.5, clip->Position(), 0.0001); break; case 3: - cout << "T" << endl; CHECK_EQUAL(2, clip->Layer()); break; } @@ -295,12 +272,8 @@ TEST(Timeline_Clip_Order) counter++; } - cout << "U" << endl; - // Close reader t.Close(); - - cout << "V" << endl; } From 4a3985e209902f51798758766d1860956343b028 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Tue, 14 May 2019 00:20:32 -0500 Subject: [PATCH 185/223] Updating comment --- src/Timeline.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Timeline.cpp b/src/Timeline.cpp index 2ca24e84..37d3f71c 100644 --- a/src/Timeline.cpp +++ b/src/Timeline.cpp @@ -923,7 +923,7 @@ vector Timeline::find_intersecting_clips(int64_t requested_frame, int num return matching_clips; } -// Get the cache object used by this reader +// Set the cache object used by this reader void Timeline::SetCache(CacheBase* new_cache) { // Destroy previous cache (if managed by timeline) if (managed_cache && final_cache) { From fab70dde1ed88c5643672146e45808b43d9fef2a Mon Sep 17 00:00:00 2001 From: Chad Walker Date: Wed, 15 May 2019 10:27:48 -0500 Subject: [PATCH 186/223] plug another small leak --- src/FFmpegReader.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 38d59f83..e8f135ce 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -1038,6 +1038,8 @@ int FFmpegReader::GetNextPacket() { // Update current packet pointer packet = next_packet; } + else + delete next_packet; } // Return if packet was found (or error number) return found_packet; From 25e51d815efa3f30b337fd79bdd831ade6a88033 Mon Sep 17 00:00:00 2001 From: Chris Kirmse Date: Thu, 30 May 2019 09:40:18 -0700 Subject: [PATCH 187/223] free cache in FrameMapper::Close() - this hugely reduces the memory used by rendering a timeline with a lot of clips - could be related to issue #239 --- src/FrameMapper.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/FrameMapper.cpp b/src/FrameMapper.cpp index ca2038a9..113171a2 100644 --- a/src/FrameMapper.cpp +++ b/src/FrameMapper.cpp @@ -651,6 +651,16 @@ void FrameMapper::Close() // Close internal reader reader->Close(); + // Clear the fields & frames lists + fields.clear(); + frames.clear(); + + // Mark as dirty + is_dirty = true; + + // Clear cache + final_cache.Clear(); + // Deallocate resample buffer if (avr) { SWR_CLOSE(avr); From 13e74b147abe530e0373c836bcadaa4cf58fad84 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Fri, 31 May 2019 19:02:28 -0500 Subject: [PATCH 188/223] Adding new CheckPixel method to validate a specific pixel color --- include/Frame.h | 3 +++ src/Frame.cpp | 22 ++++++++++++++++++++++ tests/FFmpegReader_Tests.cpp | 8 ++++++++ 3 files changed, 33 insertions(+) diff --git a/include/Frame.h b/include/Frame.h index 56a2a3ec..72822188 100644 --- a/include/Frame.h +++ b/include/Frame.h @@ -249,6 +249,9 @@ namespace openshot /// Get pixel data (for only a single scan-line) const unsigned char* GetPixels(int row); + /// Check a specific pixel color value (returns True/False) + bool CheckPixel(int row, int col, int red, int green, int blue, int alpha); + /// Get height of image int GetHeight(); diff --git a/src/Frame.cpp b/src/Frame.cpp index 6ef44d67..27bf5f71 100644 --- a/src/Frame.cpp +++ b/src/Frame.cpp @@ -480,6 +480,28 @@ const unsigned char* Frame::GetPixels(int row) return image->scanLine(row); } +// Check a specific pixel color value (returns True/False) +bool Frame::CheckPixel(int row, int col, int red, int green, int blue, int alpha) { + int col_pos = col * 4; // Find column array position + if (!image || row < 0 || row >= (height - 1) || + col_pos < 0 || col_pos >= (width - 1) ) { + // invalid row / col + return false; + } + // Check pixel color + const unsigned char* pixels = GetPixels(row); + if (pixels[col_pos + 0] == red && + pixels[col_pos + 1] == green && + pixels[col_pos + 2] == blue && + pixels[col_pos + 3] == alpha) { + // Pixel color matches successfully + return true; + } else { + // Pixel color does not match + return false; + } +} + // Set Pixel Aspect Ratio void Frame::SetPixelRatio(int num, int den) { diff --git a/tests/FFmpegReader_Tests.cpp b/tests/FFmpegReader_Tests.cpp index 53563cac..454420a4 100644 --- a/tests/FFmpegReader_Tests.cpp +++ b/tests/FFmpegReader_Tests.cpp @@ -100,6 +100,10 @@ TEST(FFmpegReader_Check_Video_File) CHECK_EQUAL(0, (int)pixels[pixel_index + 2]); CHECK_EQUAL(255, (int)pixels[pixel_index + 3]); + // Check pixel function + CHECK_EQUAL(true, f->CheckPixel(10, 112, 21, 191, 0, 255)); + CHECK_EQUAL(false, f->CheckPixel(10, 112, 0, 0, 0, 0)); + // Get frame 1 f = r.GetFrame(2); @@ -113,6 +117,10 @@ TEST(FFmpegReader_Check_Video_File) CHECK_EQUAL(188, (int)pixels[pixel_index + 2]); CHECK_EQUAL(255, (int)pixels[pixel_index + 3]); + // Check pixel function + CHECK_EQUAL(true, f->CheckPixel(10, 112, 0, 96, 188, 255)); + CHECK_EQUAL(false, f->CheckPixel(10, 112, 0, 0, 0, 0)); + // Close reader r.Close(); } From 855fd85de2b3d24f04fcb62ebdc4cf3f2e1794bc Mon Sep 17 00:00:00 2001 From: Jeff Shillitto Date: Fri, 10 May 2019 13:23:25 +1000 Subject: [PATCH 189/223] Fix path to Settings.h --- include/OpenMPUtilities.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/OpenMPUtilities.h b/include/OpenMPUtilities.h index 7c198a76..4dd02b59 100644 --- a/include/OpenMPUtilities.h +++ b/include/OpenMPUtilities.h @@ -32,7 +32,7 @@ #include #include -#include "../include/Settings.h" +#include "Settings.h" using namespace std; using namespace openshot; From e1b474e812c0be721f3f6784205a722225538620 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Mon, 3 Jun 2019 11:44:59 -0700 Subject: [PATCH 190/223] Silence deprecated warnings in ffmpeg 3.x --- src/FFmpegWriter.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index 072ac6d7..64618ce5 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -992,7 +992,14 @@ AVStream *FFmpegWriter::add_audio_stream() { throw InvalidCodec("A valid audio codec could not be found for this file.", path); // Create a new audio stream +#if (IS_FFMPEG_3_2 && (LIBAVFORMAT_VERSION_MAJOR < 58)) +_Pragma ("GCC diagnostic push") +_Pragma ("GCC diagnostic ignored \"-Wdeprecated-declarations\"") +#endif AV_FORMAT_NEW_STREAM(oc, audio_codec, codec, st) +#if (IS_FFMPEG_3_2 && (LIBAVFORMAT_VERSION_MAJOR < 58)) +_Pragma ("GCC diagnostic pop") +#endif c->codec_id = codec->id; #if LIBAVFORMAT_VERSION_MAJOR >= 53 @@ -1075,7 +1082,14 @@ AVStream *FFmpegWriter::add_video_stream() { throw InvalidCodec("A valid video codec could not be found for this file.", path); // Create a new video stream +#if (IS_FFMPEG_3_2 && (LIBAVFORMAT_VERSION_MAJOR < 58)) +_Pragma ("GCC diagnostic push") +_Pragma ("GCC diagnostic ignored \"-Wdeprecated-declarations\"") +#endif AV_FORMAT_NEW_STREAM(oc, video_codec, codec, st) +#if (IS_FFMPEG_3_2 && (LIBAVFORMAT_VERSION_MAJOR < 58)) +_Pragma ("GCC diagnostic pop") +#endif c->codec_id = codec->id; #if LIBAVFORMAT_VERSION_MAJOR >= 53 From 2be5e5e16845cdcf2f80383b1329808558e98c87 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Tue, 4 Jun 2019 13:42:46 -0500 Subject: [PATCH 191/223] Fixing crash on certain hardware accelerator modes (specifically decoder 2, device 0) --- src/FFmpegReader.cpp | 4 +--- src/examples/Example.cpp | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index e8f135ce..6aa0938e 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -934,9 +934,7 @@ std::shared_ptr FFmpegReader::ReadStream(int64_t requested_frame) { // down processing considerably, but might be more stable on some systems. #pragma omp taskwait } - } else { - RemoveAVFrame(pFrame); - } + } } // Audio packet diff --git a/src/examples/Example.cpp b/src/examples/Example.cpp index 80339684..1e19f4d9 100644 --- a/src/examples/Example.cpp +++ b/src/examples/Example.cpp @@ -38,7 +38,7 @@ int main(int argc, char* argv[]) { Settings *s = Settings::Instance(); s->HARDWARE_DECODER = 2; // 1 VA-API, 2 NVDEC - s->HW_DE_DEVICE_SET = 1; + s->HW_DE_DEVICE_SET = 0; FFmpegReader r9("/home/jonathan/Videos/sintel_trailer-720p.mp4"); r9.Open(); From 438b2c333e1366e206957b18baef6e17d73f4a38 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sat, 8 Jun 2019 12:21:56 -0500 Subject: [PATCH 192/223] Update Frame.cpp --- src/Frame.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Frame.cpp b/src/Frame.cpp index 27bf5f71..cacd91d9 100644 --- a/src/Frame.cpp +++ b/src/Frame.cpp @@ -481,7 +481,7 @@ const unsigned char* Frame::GetPixels(int row) } // Check a specific pixel color value (returns True/False) -bool Frame::CheckPixel(int row, int col, int red, int green, int blue, int alpha) { +bool Frame::CheckPixel(int row, int col, int red, int green, int blue, int alpha, int threshold) { int col_pos = col * 4; // Find column array position if (!image || row < 0 || row >= (height - 1) || col_pos < 0 || col_pos >= (width - 1) ) { @@ -490,10 +490,10 @@ bool Frame::CheckPixel(int row, int col, int red, int green, int blue, int alpha } // Check pixel color const unsigned char* pixels = GetPixels(row); - if (pixels[col_pos + 0] == red && - pixels[col_pos + 1] == green && - pixels[col_pos + 2] == blue && - pixels[col_pos + 3] == alpha) { + if (pixels[col_pos + 0] >= (red - threshold) && pixels[col_pos + 0] <= (red + threshold) && + pixels[col_pos + 0] >= (green - threshold) && pixels[col_pos + 0] <= (green + threshold) && + pixels[col_pos + 0] >= (blue - threshold) && pixels[col_pos + 0] <= (blue + threshold) && + pixels[col_pos + 0] >= (alpha - threshold) && pixels[col_pos + 0] <= (alpha + threshold)) { // Pixel color matches successfully return true; } else { From 2b308c6d5974a6cd643f2414b1f5b41285d831d2 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sat, 8 Jun 2019 12:23:26 -0500 Subject: [PATCH 193/223] Update Frame.h --- include/Frame.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/Frame.h b/include/Frame.h index 72822188..6b682edb 100644 --- a/include/Frame.h +++ b/include/Frame.h @@ -250,7 +250,7 @@ namespace openshot const unsigned char* GetPixels(int row); /// Check a specific pixel color value (returns True/False) - bool CheckPixel(int row, int col, int red, int green, int blue, int alpha); + bool CheckPixel(int row, int col, int red, int green, int blue, int alpha, int threshold); /// Get height of image int GetHeight(); From 238e2d16d8a5a45716297415892dac50f2824761 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sat, 8 Jun 2019 12:24:49 -0500 Subject: [PATCH 194/223] Update FFmpegReader_Tests.cpp --- tests/FFmpegReader_Tests.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/FFmpegReader_Tests.cpp b/tests/FFmpegReader_Tests.cpp index 454420a4..72f5462d 100644 --- a/tests/FFmpegReader_Tests.cpp +++ b/tests/FFmpegReader_Tests.cpp @@ -101,8 +101,8 @@ TEST(FFmpegReader_Check_Video_File) CHECK_EQUAL(255, (int)pixels[pixel_index + 3]); // Check pixel function - CHECK_EQUAL(true, f->CheckPixel(10, 112, 21, 191, 0, 255)); - CHECK_EQUAL(false, f->CheckPixel(10, 112, 0, 0, 0, 0)); + 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); From 3f926f45df8f7c2c30c0872346749beb47bf37f9 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sat, 8 Jun 2019 12:45:13 -0500 Subject: [PATCH 195/223] Update FFmpegReader_Tests.cpp --- tests/FFmpegReader_Tests.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/FFmpegReader_Tests.cpp b/tests/FFmpegReader_Tests.cpp index 72f5462d..462a77c3 100644 --- a/tests/FFmpegReader_Tests.cpp +++ b/tests/FFmpegReader_Tests.cpp @@ -118,8 +118,8 @@ TEST(FFmpegReader_Check_Video_File) CHECK_EQUAL(255, (int)pixels[pixel_index + 3]); // Check pixel function - CHECK_EQUAL(true, f->CheckPixel(10, 112, 0, 96, 188, 255)); - CHECK_EQUAL(false, f->CheckPixel(10, 112, 0, 0, 0, 0)); + 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(); From 722d672f5838fc6799aa5faec0cc9da821750691 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sat, 8 Jun 2019 12:52:35 -0500 Subject: [PATCH 196/223] Update Frame.cpp --- src/Frame.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Frame.cpp b/src/Frame.cpp index cacd91d9..aa7c0d87 100644 --- a/src/Frame.cpp +++ b/src/Frame.cpp @@ -491,9 +491,9 @@ bool Frame::CheckPixel(int row, int col, int red, int green, int blue, int alpha // Check pixel color const unsigned char* pixels = GetPixels(row); if (pixels[col_pos + 0] >= (red - threshold) && pixels[col_pos + 0] <= (red + threshold) && - pixels[col_pos + 0] >= (green - threshold) && pixels[col_pos + 0] <= (green + threshold) && - pixels[col_pos + 0] >= (blue - threshold) && pixels[col_pos + 0] <= (blue + threshold) && - pixels[col_pos + 0] >= (alpha - threshold) && pixels[col_pos + 0] <= (alpha + threshold)) { + pixels[col_pos + 1] >= (green - threshold) && pixels[col_pos + 1] <= (green + threshold) && + pixels[col_pos + 2] >= (blue - threshold) && pixels[col_pos + 2] <= (blue + threshold) && + pixels[col_pos + 3] >= (alpha - threshold) && pixels[col_pos + 3] <= (alpha + threshold)) { // Pixel color matches successfully return true; } else { From 0327c2ab5c126d07b07f7a321689d7be1d9aeb7c Mon Sep 17 00:00:00 2001 From: "FeRD (Frank Dana)" Date: Sun, 9 Jun 2019 08:31:04 -0400 Subject: [PATCH 197/223] Remove license block from documentation comment --- include/AudioBufferSource.h | 5 ++++- include/AudioDeviceInfo.h | 5 ++++- include/AudioReaderSource.h | 5 ++++- include/AudioResampler.h | 5 ++++- include/CacheBase.h | 5 ++++- include/CacheDisk.h | 5 ++++- include/CacheMemory.h | 5 ++++- include/ChannelLayouts.h | 5 ++++- include/ChunkReader.h | 5 ++++- include/ChunkWriter.h | 5 ++++- include/Clip.h | 5 ++++- include/ClipBase.h | 5 ++++- include/Color.h | 5 ++++- include/Coordinate.h | 5 ++++- include/CrashHandler.h | 5 ++++- include/DecklinkInput.h | 5 ++++- include/DecklinkOutput.h | 5 ++++- include/DecklinkReader.h | 5 ++++- include/DecklinkWriter.h | 5 ++++- include/DummyReader.h | 5 ++++- include/EffectBase.h | 5 ++++- include/EffectInfo.h | 5 ++++- include/Effects.h | 5 ++++- include/Enums.h | 5 ++++- include/Exceptions.h | 5 ++++- include/FFmpegReader.h | 5 ++++- include/FFmpegUtilities.h | 5 ++++- include/FFmpegWriter.h | 5 ++++- include/Fraction.h | 5 ++++- include/Frame.h | 5 ++++- include/FrameMapper.h | 5 ++++- include/ImageReader.h | 5 ++++- include/ImageWriter.h | 5 ++++- include/Json.h | 5 ++++- include/KeyFrame.h | 5 ++++- include/OpenMPUtilities.h | 5 ++++- include/OpenShot.h | 1 + include/PlayerBase.h | 5 ++++- include/Point.h | 5 ++++- include/Profiles.h | 5 ++++- include/Qt/AudioPlaybackThread.h | 5 ++++- include/Qt/PlayerDemo.h | 5 ++++- include/Qt/PlayerPrivate.h | 5 ++++- include/Qt/VideoCacheThread.h | 5 ++++- include/Qt/VideoPlaybackThread.h | 5 ++++- include/Qt/VideoRenderWidget.h | 5 ++++- include/Qt/VideoRenderer.h | 5 ++++- include/QtImageReader.h | 5 ++++- include/QtPlayer.h | 5 ++++- include/ReaderBase.h | 5 ++++- include/RendererBase.h | 5 ++++- include/Settings.h | 5 ++++- include/Tests.h | 5 ++++- include/TextReader.h | 5 ++++- include/Timeline.h | 5 ++++- include/Version.h | 5 ++++- include/WriterBase.h | 5 ++++- include/ZmqLogger.h | 5 ++++- include/effects/Bars.h | 5 ++++- include/effects/Blur.h | 5 ++++- include/effects/Brightness.h | 5 ++++- include/effects/ChromaKey.h | 5 ++++- include/effects/ColorShift.h | 5 ++++- include/effects/Crop.h | 5 ++++- include/effects/Deinterlace.h | 5 ++++- include/effects/Hue.h | 5 ++++- include/effects/Mask.h | 5 ++++- include/effects/Negate.h | 5 ++++- include/effects/Pixelate.h | 5 ++++- include/effects/Saturation.h | 5 ++++- include/effects/Shift.h | 5 ++++- include/effects/Wave.h | 5 ++++- src/AudioBufferSource.cpp | 5 ++++- src/AudioReaderSource.cpp | 5 ++++- src/AudioResampler.cpp | 5 ++++- src/CacheBase.cpp | 5 ++++- src/CacheDisk.cpp | 5 ++++- src/CacheMemory.cpp | 5 ++++- src/ChunkReader.cpp | 5 ++++- src/ChunkWriter.cpp | 5 ++++- src/Clip.cpp | 5 ++++- src/ClipBase.cpp | 5 ++++- src/Color.cpp | 5 ++++- src/Coordinate.cpp | 5 ++++- src/CrashHandler.cpp | 5 ++++- src/DecklinkInput.cpp | 5 ++++- src/DecklinkOutput.cpp | 5 ++++- src/DecklinkReader.cpp | 5 ++++- src/DecklinkWriter.cpp | 5 ++++- src/DummyReader.cpp | 5 ++++- src/EffectBase.cpp | 5 ++++- src/EffectInfo.cpp | 5 ++++- src/FFmpegReader.cpp | 5 ++++- src/FFmpegWriter.cpp | 5 ++++- src/Fraction.cpp | 5 ++++- src/Frame.cpp | 5 ++++- src/FrameMapper.cpp | 5 ++++- src/ImageReader.cpp | 5 ++++- src/ImageWriter.cpp | 5 ++++- src/KeyFrame.cpp | 5 ++++- src/PlayerBase.cpp | 5 ++++- src/Point.cpp | 5 ++++- src/Profiles.cpp | 5 ++++- src/Qt/AudioPlaybackThread.cpp | 5 ++++- src/Qt/PlayerDemo.cpp | 5 ++++- src/Qt/PlayerPrivate.cpp | 5 ++++- src/Qt/VideoCacheThread.cpp | 5 ++++- src/Qt/VideoPlaybackThread.cpp | 5 ++++- src/Qt/VideoRenderWidget.cpp | 5 ++++- src/Qt/VideoRenderer.cpp | 5 ++++- src/Qt/demo/main.cpp | 5 ++++- src/QtImageReader.cpp | 5 ++++- src/QtPlayer.cpp | 5 ++++- src/ReaderBase.cpp | 5 ++++- src/RendererBase.cpp | 5 ++++- src/Settings.cpp | 5 ++++- src/TextReader.cpp | 5 ++++- src/Timeline.cpp | 5 ++++- src/WriterBase.cpp | 5 ++++- src/ZmqLogger.cpp | 5 ++++- src/effects/Bars.cpp | 5 ++++- src/effects/Blur.cpp | 5 ++++- src/effects/Brightness.cpp | 5 ++++- src/effects/ChromaKey.cpp | 5 ++++- src/effects/ColorShift.cpp | 5 ++++- src/effects/Crop.cpp | 5 ++++- src/effects/Deinterlace.cpp | 5 ++++- src/effects/Hue.cpp | 5 ++++- src/effects/Mask.cpp | 5 ++++- src/effects/Negate.cpp | 5 ++++- src/effects/Pixelate.cpp | 5 ++++- src/effects/Saturation.cpp | 5 ++++- src/effects/Shift.cpp | 5 ++++- src/effects/Wave.cpp | 5 ++++- src/examples/Example.cpp | 5 ++++- src/examples/ExampleBlackmagic.cpp | 5 ++++- tests/Cache_Tests.cpp | 5 ++++- tests/Clip_Tests.cpp | 5 ++++- tests/Color_Tests.cpp | 5 ++++- tests/Coordinate_Tests.cpp | 5 ++++- tests/FFmpegReader_Tests.cpp | 5 ++++- tests/FFmpegWriter_Tests.cpp | 5 ++++- tests/Fraction_Tests.cpp | 5 ++++- tests/FrameMapper_Tests.cpp | 5 ++++- tests/ImageWriter_Tests.cpp | 5 ++++- tests/KeyFrame_Tests.cpp | 5 ++++- tests/Point_Tests.cpp | 5 ++++- tests/ReaderBase_Tests.cpp | 5 ++++- tests/Settings_Tests.cpp | 5 ++++- tests/Timeline_Tests.cpp | 5 ++++- tests/tests.cpp | 5 ++++- 151 files changed, 601 insertions(+), 150 deletions(-) diff --git a/include/AudioBufferSource.h b/include/AudioBufferSource.h index b1571c8d..0b72aa5d 100644 --- a/include/AudioBufferSource.h +++ b/include/AudioBufferSource.h @@ -3,7 +3,10 @@ * @brief Header file for AudioBufferSource class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/AudioDeviceInfo.h b/include/AudioDeviceInfo.h index 29a89139..f58c1562 100644 --- a/include/AudioDeviceInfo.h +++ b/include/AudioDeviceInfo.h @@ -3,7 +3,10 @@ * @brief Header file for Audio Device Info struct * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/AudioReaderSource.h b/include/AudioReaderSource.h index 76c3dc7d..a60f940e 100644 --- a/include/AudioReaderSource.h +++ b/include/AudioReaderSource.h @@ -3,7 +3,10 @@ * @brief Header file for AudioReaderSource class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/AudioResampler.h b/include/AudioResampler.h index b81bfc3e..16d35906 100644 --- a/include/AudioResampler.h +++ b/include/AudioResampler.h @@ -3,7 +3,10 @@ * @brief Header file for AudioResampler class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/CacheBase.h b/include/CacheBase.h index c764b2d9..d8bcf805 100644 --- a/include/CacheBase.h +++ b/include/CacheBase.h @@ -3,7 +3,10 @@ * @brief Header file for CacheBase class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/CacheDisk.h b/include/CacheDisk.h index adecda9a..03935dcb 100644 --- a/include/CacheDisk.h +++ b/include/CacheDisk.h @@ -3,7 +3,10 @@ * @brief Header file for CacheDisk class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/CacheMemory.h b/include/CacheMemory.h index 29187799..03db05d1 100644 --- a/include/CacheMemory.h +++ b/include/CacheMemory.h @@ -3,7 +3,10 @@ * @brief Header file for CacheMemory class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/ChannelLayouts.h b/include/ChannelLayouts.h index 657d2757..4c12822f 100644 --- a/include/ChannelLayouts.h +++ b/include/ChannelLayouts.h @@ -3,7 +3,10 @@ * @brief Header file for ChannelLayout class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/ChunkReader.h b/include/ChunkReader.h index b780602b..022dfb77 100644 --- a/include/ChunkReader.h +++ b/include/ChunkReader.h @@ -3,7 +3,10 @@ * @brief Header file for ChunkReader class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/ChunkWriter.h b/include/ChunkWriter.h index 2cdef4fe..2ecdf06e 100644 --- a/include/ChunkWriter.h +++ b/include/ChunkWriter.h @@ -3,7 +3,10 @@ * @brief Header file for ChunkWriter class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/Clip.h b/include/Clip.h index 58eacab7..9f10148f 100644 --- a/include/Clip.h +++ b/include/Clip.h @@ -3,7 +3,10 @@ * @brief Header file for Clip class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/ClipBase.h b/include/ClipBase.h index 1c5534fc..97d60ef6 100644 --- a/include/ClipBase.h +++ b/include/ClipBase.h @@ -3,7 +3,10 @@ * @brief Header file for ClipBase class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/Color.h b/include/Color.h index a0edd602..8e9d152f 100644 --- a/include/Color.h +++ b/include/Color.h @@ -3,7 +3,10 @@ * @brief Header file for Color class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/Coordinate.h b/include/Coordinate.h index bf561084..40e4bb63 100644 --- a/include/Coordinate.h +++ b/include/Coordinate.h @@ -3,7 +3,10 @@ * @brief Header file for Coordinate class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/CrashHandler.h b/include/CrashHandler.h index 12c79a86..1c6ed187 100644 --- a/include/CrashHandler.h +++ b/include/CrashHandler.h @@ -3,7 +3,10 @@ * @brief Header file for CrashHandler class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/DecklinkInput.h b/include/DecklinkInput.h index cfd5b6b1..ebac2baf 100644 --- a/include/DecklinkInput.h +++ b/include/DecklinkInput.h @@ -3,7 +3,10 @@ * @brief Header file for DecklinkInput class * @author Jonathan Thomas , Blackmagic Design * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2009 Blackmagic Design * diff --git a/include/DecklinkOutput.h b/include/DecklinkOutput.h index ddb6e9bc..f653b8dd 100644 --- a/include/DecklinkOutput.h +++ b/include/DecklinkOutput.h @@ -3,7 +3,10 @@ * @brief Header file for DecklinkOutput class * @author Jonathan Thomas , Blackmagic Design * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2009 Blackmagic Design * diff --git a/include/DecklinkReader.h b/include/DecklinkReader.h index 3ce9273f..29a8fdcb 100644 --- a/include/DecklinkReader.h +++ b/include/DecklinkReader.h @@ -3,7 +3,10 @@ * @brief Header file for DecklinkReader class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/DecklinkWriter.h b/include/DecklinkWriter.h index 00ac8b3e..3a65f0c1 100644 --- a/include/DecklinkWriter.h +++ b/include/DecklinkWriter.h @@ -3,7 +3,10 @@ * @brief Header file for DecklinkWriter class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/DummyReader.h b/include/DummyReader.h index e9bce1b5..692c7589 100644 --- a/include/DummyReader.h +++ b/include/DummyReader.h @@ -3,7 +3,10 @@ * @brief Header file for DummyReader class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/EffectBase.h b/include/EffectBase.h index d38e3f45..20cd8edc 100644 --- a/include/EffectBase.h +++ b/include/EffectBase.h @@ -3,7 +3,10 @@ * @brief Header file for EffectBase class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/EffectInfo.h b/include/EffectInfo.h index 999c9b6a..59b7d234 100644 --- a/include/EffectInfo.h +++ b/include/EffectInfo.h @@ -3,7 +3,10 @@ * @brief Header file for the EffectInfo class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/Effects.h b/include/Effects.h index 224c48be..7297ba07 100644 --- a/include/Effects.h +++ b/include/Effects.h @@ -6,7 +6,10 @@ * @brief This header includes all commonly used effects for libopenshot, for ease-of-use. * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/Enums.h b/include/Enums.h index fb91f1fa..505c28f6 100644 --- a/include/Enums.h +++ b/include/Enums.h @@ -3,7 +3,10 @@ * @brief Header file for TextReader class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/Exceptions.h b/include/Exceptions.h index fe76d6dc..ae9755ae 100644 --- a/include/Exceptions.h +++ b/include/Exceptions.h @@ -3,7 +3,10 @@ * @brief Header file for all Exception classes * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/FFmpegReader.h b/include/FFmpegReader.h index 7ef44c50..331ca32a 100644 --- a/include/FFmpegReader.h +++ b/include/FFmpegReader.h @@ -3,7 +3,10 @@ * @brief Header file for FFmpegReader class * @author Jonathan Thomas , Fabrice Bellard * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2013 OpenShot Studios, LLC, Fabrice Bellard * (http://www.openshotstudios.com). This file is part of diff --git a/include/FFmpegUtilities.h b/include/FFmpegUtilities.h index 7e3e0070..1069bb2c 100644 --- a/include/FFmpegUtilities.h +++ b/include/FFmpegUtilities.h @@ -3,7 +3,10 @@ * @brief Header file for FFmpegUtilities * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/FFmpegWriter.h b/include/FFmpegWriter.h index 35dd1ed9..e4ebe176 100644 --- a/include/FFmpegWriter.h +++ b/include/FFmpegWriter.h @@ -3,7 +3,10 @@ * @brief Header file for FFmpegWriter class * @author Jonathan Thomas , Fabrice Bellard * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2013 OpenShot Studios, LLC, Fabrice Bellard * (http://www.openshotstudios.com). This file is part of diff --git a/include/Fraction.h b/include/Fraction.h index 7f5e5881..178cca41 100644 --- a/include/Fraction.h +++ b/include/Fraction.h @@ -3,7 +3,10 @@ * @brief Header file for Fraction class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/Frame.h b/include/Frame.h index 6b682edb..d34800ff 100644 --- a/include/Frame.h +++ b/include/Frame.h @@ -3,7 +3,10 @@ * @brief Header file for Frame class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/FrameMapper.h b/include/FrameMapper.h index d046af25..66020951 100644 --- a/include/FrameMapper.h +++ b/include/FrameMapper.h @@ -3,7 +3,10 @@ * @brief Header file for the FrameMapper class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/ImageReader.h b/include/ImageReader.h index e698e0c1..2ba2be8d 100644 --- a/include/ImageReader.h +++ b/include/ImageReader.h @@ -3,7 +3,10 @@ * @brief Header file for ImageReader class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/ImageWriter.h b/include/ImageWriter.h index 25177134..41c460e1 100644 --- a/include/ImageWriter.h +++ b/include/ImageWriter.h @@ -3,7 +3,10 @@ * @brief Header file for ImageWriter class * @author Jonathan Thomas , Fabrice Bellard * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2013 OpenShot Studios, LLC, Fabrice Bellard * (http://www.openshotstudios.com). This file is part of diff --git a/include/Json.h b/include/Json.h index 970034a5..bacd59a1 100644 --- a/include/Json.h +++ b/include/Json.h @@ -3,7 +3,10 @@ * @brief Header file for JSON class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/KeyFrame.h b/include/KeyFrame.h index bab87f7c..1aca9cc0 100644 --- a/include/KeyFrame.h +++ b/include/KeyFrame.h @@ -3,7 +3,10 @@ * @brief Header file for the Keyframe class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/OpenMPUtilities.h b/include/OpenMPUtilities.h index 7c198a76..dcde3c32 100644 --- a/include/OpenMPUtilities.h +++ b/include/OpenMPUtilities.h @@ -3,7 +3,10 @@ * @brief Header file for OpenMPUtilities (set some common macros) * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/OpenShot.h b/include/OpenShot.h index 207f4b42..d7c62af6 100644 --- a/include/OpenShot.h +++ b/include/OpenShot.h @@ -71,6 +71,7 @@ * ### Want to Learn More? ### * To continue learning about libopenshot, take a look at the full list of classes available. * + * \anchor License * ### License & Copyright ### * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/PlayerBase.h b/include/PlayerBase.h index ecc222a8..d6ff64f5 100644 --- a/include/PlayerBase.h +++ b/include/PlayerBase.h @@ -3,7 +3,10 @@ * @brief Header file for PlayerBase class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/Point.h b/include/Point.h index 139aa64d..8eabf7c8 100644 --- a/include/Point.h +++ b/include/Point.h @@ -3,7 +3,10 @@ * @brief Header file for Point class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/Profiles.h b/include/Profiles.h index fa279d57..f7621551 100644 --- a/include/Profiles.h +++ b/include/Profiles.h @@ -3,7 +3,10 @@ * @brief Header file for Profile class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/Qt/AudioPlaybackThread.h b/include/Qt/AudioPlaybackThread.h index be26c4e8..eaf3b73c 100644 --- a/include/Qt/AudioPlaybackThread.h +++ b/include/Qt/AudioPlaybackThread.h @@ -4,7 +4,10 @@ * @author Duzy Chan * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/Qt/PlayerDemo.h b/include/Qt/PlayerDemo.h index c02f863c..3749ce4c 100644 --- a/include/Qt/PlayerDemo.h +++ b/include/Qt/PlayerDemo.h @@ -3,7 +3,10 @@ * @brief Header file for demo application for QtPlayer class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/Qt/PlayerPrivate.h b/include/Qt/PlayerPrivate.h index f626fb99..4745018e 100644 --- a/include/Qt/PlayerPrivate.h +++ b/include/Qt/PlayerPrivate.h @@ -4,7 +4,10 @@ * @author Duzy Chan * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/Qt/VideoCacheThread.h b/include/Qt/VideoCacheThread.h index 4afb7ee5..db932263 100644 --- a/include/Qt/VideoCacheThread.h +++ b/include/Qt/VideoCacheThread.h @@ -3,7 +3,10 @@ * @brief Source file for VideoCacheThread class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/Qt/VideoPlaybackThread.h b/include/Qt/VideoPlaybackThread.h index 90dc3681..363c5d1d 100644 --- a/include/Qt/VideoPlaybackThread.h +++ b/include/Qt/VideoPlaybackThread.h @@ -4,7 +4,10 @@ * @author Duzy Chan * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/Qt/VideoRenderWidget.h b/include/Qt/VideoRenderWidget.h index 19675cc8..82bb8320 100644 --- a/include/Qt/VideoRenderWidget.h +++ b/include/Qt/VideoRenderWidget.h @@ -3,7 +3,10 @@ * @brief Header file for Video RendererWidget class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/Qt/VideoRenderer.h b/include/Qt/VideoRenderer.h index aaf973ca..4b1fcc89 100644 --- a/include/Qt/VideoRenderer.h +++ b/include/Qt/VideoRenderer.h @@ -3,7 +3,10 @@ * @brief Header file for Video Renderer class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/QtImageReader.h b/include/QtImageReader.h index d26c9b79..c7196e6b 100644 --- a/include/QtImageReader.h +++ b/include/QtImageReader.h @@ -3,7 +3,10 @@ * @brief Header file for QtImageReader class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/QtPlayer.h b/include/QtPlayer.h index c9137f5e..aaa12fa0 100644 --- a/include/QtPlayer.h +++ b/include/QtPlayer.h @@ -4,7 +4,10 @@ * @author Duzy Chan * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/ReaderBase.h b/include/ReaderBase.h index efbd5bc7..4f9f724d 100644 --- a/include/ReaderBase.h +++ b/include/ReaderBase.h @@ -3,7 +3,10 @@ * @brief Header file for ReaderBase class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/RendererBase.h b/include/RendererBase.h index c71d664a..787a254f 100644 --- a/include/RendererBase.h +++ b/include/RendererBase.h @@ -3,7 +3,10 @@ * @brief Header file for RendererBase class * @author Duzy Chan * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/Settings.h b/include/Settings.h index 89561034..984e3685 100644 --- a/include/Settings.h +++ b/include/Settings.h @@ -3,7 +3,10 @@ * @brief Header file for global Settings class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/Tests.h b/include/Tests.h index b7d9dbf5..208cfaf1 100644 --- a/include/Tests.h +++ b/include/Tests.h @@ -3,7 +3,10 @@ * @brief Header file for UnitTests * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/TextReader.h b/include/TextReader.h index d7d653d2..447ae08c 100644 --- a/include/TextReader.h +++ b/include/TextReader.h @@ -3,7 +3,10 @@ * @brief Header file for TextReader class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/Timeline.h b/include/Timeline.h index 11911d40..dea81bf2 100644 --- a/include/Timeline.h +++ b/include/Timeline.h @@ -3,7 +3,10 @@ * @brief Header file for Timeline class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/Version.h b/include/Version.h index 6077cde5..be17ca9a 100644 --- a/include/Version.h +++ b/include/Version.h @@ -3,7 +3,10 @@ * @brief Header file that includes the version number of libopenshot * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/WriterBase.h b/include/WriterBase.h index 8f424054..e8caf72f 100644 --- a/include/WriterBase.h +++ b/include/WriterBase.h @@ -3,7 +3,10 @@ * @brief Header file for WriterBase class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/ZmqLogger.h b/include/ZmqLogger.h index 8ababac0..ba736382 100644 --- a/include/ZmqLogger.h +++ b/include/ZmqLogger.h @@ -3,7 +3,10 @@ * @brief Header file for ZeroMQ-based Logger class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/effects/Bars.h b/include/effects/Bars.h index 27d21725..c9d90325 100644 --- a/include/effects/Bars.h +++ b/include/effects/Bars.h @@ -3,7 +3,10 @@ * @brief Header file for Bars effect class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/effects/Blur.h b/include/effects/Blur.h index 314dabbe..c4a92772 100644 --- a/include/effects/Blur.h +++ b/include/effects/Blur.h @@ -3,7 +3,10 @@ * @brief Header file for Blur effect class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/effects/Brightness.h b/include/effects/Brightness.h index 67ab4c9c..572f2b11 100644 --- a/include/effects/Brightness.h +++ b/include/effects/Brightness.h @@ -3,7 +3,10 @@ * @brief Header file for Brightness class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/effects/ChromaKey.h b/include/effects/ChromaKey.h index 000dbba4..c08a09f5 100644 --- a/include/effects/ChromaKey.h +++ b/include/effects/ChromaKey.h @@ -3,7 +3,10 @@ * @brief Header file for ChromaKey class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/effects/ColorShift.h b/include/effects/ColorShift.h index 4b3de2bb..b2f2f115 100644 --- a/include/effects/ColorShift.h +++ b/include/effects/ColorShift.h @@ -3,7 +3,10 @@ * @brief Header file for Color Shift effect class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/effects/Crop.h b/include/effects/Crop.h index 7921a78d..da78dbe3 100644 --- a/include/effects/Crop.h +++ b/include/effects/Crop.h @@ -3,7 +3,10 @@ * @brief Header file for Crop effect class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/effects/Deinterlace.h b/include/effects/Deinterlace.h index c1fb7227..7614fb37 100644 --- a/include/effects/Deinterlace.h +++ b/include/effects/Deinterlace.h @@ -3,7 +3,10 @@ * @brief Header file for De-interlace class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/effects/Hue.h b/include/effects/Hue.h index 4f680047..c023fdef 100644 --- a/include/effects/Hue.h +++ b/include/effects/Hue.h @@ -3,7 +3,10 @@ * @brief Header file for Hue effect class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/effects/Mask.h b/include/effects/Mask.h index ef707f5f..badbaf40 100644 --- a/include/effects/Mask.h +++ b/include/effects/Mask.h @@ -3,7 +3,10 @@ * @brief Header file for Mask class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/effects/Negate.h b/include/effects/Negate.h index 84621132..1618a427 100644 --- a/include/effects/Negate.h +++ b/include/effects/Negate.h @@ -3,7 +3,10 @@ * @brief Header file for Negate class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/effects/Pixelate.h b/include/effects/Pixelate.h index b8ca2998..50f555d6 100644 --- a/include/effects/Pixelate.h +++ b/include/effects/Pixelate.h @@ -3,7 +3,10 @@ * @brief Header file for Pixelate effect class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/effects/Saturation.h b/include/effects/Saturation.h index d49069a6..a7b3806f 100644 --- a/include/effects/Saturation.h +++ b/include/effects/Saturation.h @@ -3,7 +3,10 @@ * @brief Header file for Saturation class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/effects/Shift.h b/include/effects/Shift.h index 86ccf7a4..d6f2066b 100644 --- a/include/effects/Shift.h +++ b/include/effects/Shift.h @@ -3,7 +3,10 @@ * @brief Header file for Shift effect class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/include/effects/Wave.h b/include/effects/Wave.h index 04c1620f..ed28b8a0 100644 --- a/include/effects/Wave.h +++ b/include/effects/Wave.h @@ -3,7 +3,10 @@ * @brief Header file for Wave effect class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/AudioBufferSource.cpp b/src/AudioBufferSource.cpp index 3b00f742..c7eed591 100644 --- a/src/AudioBufferSource.cpp +++ b/src/AudioBufferSource.cpp @@ -3,7 +3,10 @@ * @brief Source file for AudioBufferSource class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/AudioReaderSource.cpp b/src/AudioReaderSource.cpp index b1bb2cd3..84d0bd0b 100644 --- a/src/AudioReaderSource.cpp +++ b/src/AudioReaderSource.cpp @@ -3,7 +3,10 @@ * @brief Source file for AudioReaderSource class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/AudioResampler.cpp b/src/AudioResampler.cpp index 442a91d9..0b5d2d3c 100644 --- a/src/AudioResampler.cpp +++ b/src/AudioResampler.cpp @@ -3,7 +3,10 @@ * @brief Source file for AudioResampler class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/CacheBase.cpp b/src/CacheBase.cpp index 874674c0..44abbdcf 100644 --- a/src/CacheBase.cpp +++ b/src/CacheBase.cpp @@ -3,7 +3,10 @@ * @brief Source file for CacheBase class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/CacheDisk.cpp b/src/CacheDisk.cpp index 23854f3a..ce14d109 100644 --- a/src/CacheDisk.cpp +++ b/src/CacheDisk.cpp @@ -3,7 +3,10 @@ * @brief Source file for CacheDisk class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/CacheMemory.cpp b/src/CacheMemory.cpp index f830d74e..9536878a 100644 --- a/src/CacheMemory.cpp +++ b/src/CacheMemory.cpp @@ -3,7 +3,10 @@ * @brief Source file for Cache class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/ChunkReader.cpp b/src/ChunkReader.cpp index fe552243..bfd25575 100644 --- a/src/ChunkReader.cpp +++ b/src/ChunkReader.cpp @@ -3,7 +3,10 @@ * @brief Source file for ChunkReader class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/ChunkWriter.cpp b/src/ChunkWriter.cpp index 958fecb7..2734bb1b 100644 --- a/src/ChunkWriter.cpp +++ b/src/ChunkWriter.cpp @@ -3,7 +3,10 @@ * @brief Source file for ChunkWriter class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/Clip.cpp b/src/Clip.cpp index 2099705d..2e99106c 100644 --- a/src/Clip.cpp +++ b/src/Clip.cpp @@ -3,7 +3,10 @@ * @brief Source file for Clip class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/ClipBase.cpp b/src/ClipBase.cpp index b2926244..26e7710b 100644 --- a/src/ClipBase.cpp +++ b/src/ClipBase.cpp @@ -3,7 +3,10 @@ * @brief Source file for EffectBase class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/Color.cpp b/src/Color.cpp index df2cf097..dec1571a 100644 --- a/src/Color.cpp +++ b/src/Color.cpp @@ -3,7 +3,10 @@ * @brief Source file for EffectBase class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/Coordinate.cpp b/src/Coordinate.cpp index 60ea90b2..3fd293d6 100644 --- a/src/Coordinate.cpp +++ b/src/Coordinate.cpp @@ -3,7 +3,10 @@ * @brief Source file for Coordinate class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/CrashHandler.cpp b/src/CrashHandler.cpp index e7827d09..24d8975b 100644 --- a/src/CrashHandler.cpp +++ b/src/CrashHandler.cpp @@ -3,7 +3,10 @@ * @brief Source file for CrashHandler class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/DecklinkInput.cpp b/src/DecklinkInput.cpp index 99d13341..5a83931c 100644 --- a/src/DecklinkInput.cpp +++ b/src/DecklinkInput.cpp @@ -3,7 +3,10 @@ * @brief Source file for DecklinkInput class * @author Jonathan Thomas , Blackmagic Design * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2009 Blackmagic Design * diff --git a/src/DecklinkOutput.cpp b/src/DecklinkOutput.cpp index 8606420a..d86fe777 100644 --- a/src/DecklinkOutput.cpp +++ b/src/DecklinkOutput.cpp @@ -3,7 +3,10 @@ * @brief Source file for DecklinkOutput class * @author Jonathan Thomas , Blackmagic Design * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2009 Blackmagic Design * diff --git a/src/DecklinkReader.cpp b/src/DecklinkReader.cpp index 1d9b70b9..512389f6 100644 --- a/src/DecklinkReader.cpp +++ b/src/DecklinkReader.cpp @@ -3,7 +3,10 @@ * @brief Source file for DecklinkReader class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/DecklinkWriter.cpp b/src/DecklinkWriter.cpp index dd0de9c5..25e2db27 100644 --- a/src/DecklinkWriter.cpp +++ b/src/DecklinkWriter.cpp @@ -3,7 +3,10 @@ * @brief Source file for DecklinkWriter class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/DummyReader.cpp b/src/DummyReader.cpp index dc77db5b..8e7c82c5 100644 --- a/src/DummyReader.cpp +++ b/src/DummyReader.cpp @@ -3,7 +3,10 @@ * @brief Source file for DummyReader class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/EffectBase.cpp b/src/EffectBase.cpp index c0afded8..6c095402 100644 --- a/src/EffectBase.cpp +++ b/src/EffectBase.cpp @@ -3,7 +3,10 @@ * @brief Source file for EffectBase class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/EffectInfo.cpp b/src/EffectInfo.cpp index f9e4c409..9d094564 100644 --- a/src/EffectInfo.cpp +++ b/src/EffectInfo.cpp @@ -3,7 +3,10 @@ * @brief Source file for EffectInfo class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 6aa0938e..b6c9dd1e 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -3,7 +3,10 @@ * @brief Source file for FFmpegReader class * @author Jonathan Thomas , Fabrice Bellard * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2013 OpenShot Studios, LLC, Fabrice Bellard * (http://www.openshotstudios.com). This file is part of diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index ddcd4e16..e052bc63 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -3,7 +3,10 @@ * @brief Source file for FFmpegWriter class * @author Jonathan Thomas , Fabrice Bellard * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2013 OpenShot Studios, LLC, Fabrice Bellard * (http://www.openshotstudios.com). This file is part of diff --git a/src/Fraction.cpp b/src/Fraction.cpp index 2d9c449c..ee5a961b 100644 --- a/src/Fraction.cpp +++ b/src/Fraction.cpp @@ -3,7 +3,10 @@ * @brief Source file for Fraction class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/Frame.cpp b/src/Frame.cpp index aa7c0d87..d7edc889 100644 --- a/src/Frame.cpp +++ b/src/Frame.cpp @@ -3,7 +3,10 @@ * @brief Source file for Frame class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/FrameMapper.cpp b/src/FrameMapper.cpp index 113171a2..2b725615 100644 --- a/src/FrameMapper.cpp +++ b/src/FrameMapper.cpp @@ -3,7 +3,10 @@ * @brief Source file for the FrameMapper class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/ImageReader.cpp b/src/ImageReader.cpp index f535666a..0f09631e 100644 --- a/src/ImageReader.cpp +++ b/src/ImageReader.cpp @@ -3,7 +3,10 @@ * @brief Source file for ImageReader class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/ImageWriter.cpp b/src/ImageWriter.cpp index 41626b09..1205a8ee 100644 --- a/src/ImageWriter.cpp +++ b/src/ImageWriter.cpp @@ -3,7 +3,10 @@ * @brief Source file for ImageWriter class * @author Jonathan Thomas , Fabrice Bellard * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2013 OpenShot Studios, LLC, Fabrice Bellard * (http://www.openshotstudios.com). This file is part of diff --git a/src/KeyFrame.cpp b/src/KeyFrame.cpp index 2b0389de..b82fa2ea 100644 --- a/src/KeyFrame.cpp +++ b/src/KeyFrame.cpp @@ -3,7 +3,10 @@ * @brief Source file for the Keyframe class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/PlayerBase.cpp b/src/PlayerBase.cpp index 2cfec475..f4686d9f 100644 --- a/src/PlayerBase.cpp +++ b/src/PlayerBase.cpp @@ -3,7 +3,10 @@ * @brief Source file for PlayerBase class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/Point.cpp b/src/Point.cpp index e55e6453..4b73bf6f 100644 --- a/src/Point.cpp +++ b/src/Point.cpp @@ -3,7 +3,10 @@ * @brief Source file for Point class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/Profiles.cpp b/src/Profiles.cpp index 390e5765..a39100bc 100644 --- a/src/Profiles.cpp +++ b/src/Profiles.cpp @@ -3,7 +3,10 @@ * @brief Source file for Profile class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/Qt/AudioPlaybackThread.cpp b/src/Qt/AudioPlaybackThread.cpp index 7bad4649..3a286729 100644 --- a/src/Qt/AudioPlaybackThread.cpp +++ b/src/Qt/AudioPlaybackThread.cpp @@ -4,7 +4,10 @@ * @author Duzy Chan * @author Jonathan Thomas * * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/Qt/PlayerDemo.cpp b/src/Qt/PlayerDemo.cpp index 8c5d267e..55862a96 100644 --- a/src/Qt/PlayerDemo.cpp +++ b/src/Qt/PlayerDemo.cpp @@ -3,7 +3,10 @@ * @brief Source file for Demo QtPlayer application * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/Qt/PlayerPrivate.cpp b/src/Qt/PlayerPrivate.cpp index 1839f1eb..2040375e 100644 --- a/src/Qt/PlayerPrivate.cpp +++ b/src/Qt/PlayerPrivate.cpp @@ -4,7 +4,10 @@ * @author Duzy Chan * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/Qt/VideoCacheThread.cpp b/src/Qt/VideoCacheThread.cpp index 208fcaab..1a90f1c6 100644 --- a/src/Qt/VideoCacheThread.cpp +++ b/src/Qt/VideoCacheThread.cpp @@ -3,7 +3,10 @@ * @brief Source file for VideoCacheThread class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/Qt/VideoPlaybackThread.cpp b/src/Qt/VideoPlaybackThread.cpp index cd116162..505a11d8 100644 --- a/src/Qt/VideoPlaybackThread.cpp +++ b/src/Qt/VideoPlaybackThread.cpp @@ -4,7 +4,10 @@ * @author Duzy Chan * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/Qt/VideoRenderWidget.cpp b/src/Qt/VideoRenderWidget.cpp index 8dd6a73c..64b539ff 100644 --- a/src/Qt/VideoRenderWidget.cpp +++ b/src/Qt/VideoRenderWidget.cpp @@ -3,7 +3,10 @@ * @brief Source file for Video RendererWidget class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/Qt/VideoRenderer.cpp b/src/Qt/VideoRenderer.cpp index 21e2e07b..70986b03 100644 --- a/src/Qt/VideoRenderer.cpp +++ b/src/Qt/VideoRenderer.cpp @@ -3,7 +3,10 @@ * @brief Source file for VideoRenderer class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/Qt/demo/main.cpp b/src/Qt/demo/main.cpp index 795b3c25..9c7e18eb 100644 --- a/src/Qt/demo/main.cpp +++ b/src/Qt/demo/main.cpp @@ -3,7 +3,10 @@ * @brief Demo Qt application to test the QtPlayer class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/QtImageReader.cpp b/src/QtImageReader.cpp index 502ddb9a..c9fe6b4e 100644 --- a/src/QtImageReader.cpp +++ b/src/QtImageReader.cpp @@ -3,7 +3,10 @@ * @brief Source file for QtImageReader class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/QtPlayer.cpp b/src/QtPlayer.cpp index 3287c19d..08d7df5e 100644 --- a/src/QtPlayer.cpp +++ b/src/QtPlayer.cpp @@ -4,7 +4,10 @@ * @author Duzy Chan * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/ReaderBase.cpp b/src/ReaderBase.cpp index 3b1bb76f..1aa39e5a 100644 --- a/src/ReaderBase.cpp +++ b/src/ReaderBase.cpp @@ -3,7 +3,10 @@ * @brief Source file for ReaderBase class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/RendererBase.cpp b/src/RendererBase.cpp index e21d984b..edd1f795 100644 --- a/src/RendererBase.cpp +++ b/src/RendererBase.cpp @@ -3,7 +3,10 @@ * @brief Source file for RendererBase class * @author Duzy Chan * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/Settings.cpp b/src/Settings.cpp index 8193ec6b..7c284624 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -3,7 +3,10 @@ * @brief Source file for global Settings class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/TextReader.cpp b/src/TextReader.cpp index 8234aa5d..fd9dcfef 100644 --- a/src/TextReader.cpp +++ b/src/TextReader.cpp @@ -3,7 +3,10 @@ * @brief Source file for TextReader class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/Timeline.cpp b/src/Timeline.cpp index 37d3f71c..d28b4ff8 100644 --- a/src/Timeline.cpp +++ b/src/Timeline.cpp @@ -3,7 +3,10 @@ * @brief Source file for Timeline class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/WriterBase.cpp b/src/WriterBase.cpp index 3697c52a..3a8b5f72 100644 --- a/src/WriterBase.cpp +++ b/src/WriterBase.cpp @@ -3,7 +3,10 @@ * @brief Source file for WriterBase class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/ZmqLogger.cpp b/src/ZmqLogger.cpp index 27da2977..cc38f9a4 100644 --- a/src/ZmqLogger.cpp +++ b/src/ZmqLogger.cpp @@ -3,7 +3,10 @@ * @brief Source file for ZeroMQ-based Logger class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/effects/Bars.cpp b/src/effects/Bars.cpp index a4a72c19..cc541cc2 100644 --- a/src/effects/Bars.cpp +++ b/src/effects/Bars.cpp @@ -3,7 +3,10 @@ * @brief Source file for Bars effect class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/effects/Blur.cpp b/src/effects/Blur.cpp index 6eafc2a1..39c70921 100644 --- a/src/effects/Blur.cpp +++ b/src/effects/Blur.cpp @@ -3,7 +3,10 @@ * @brief Source file for Blur effect class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/effects/Brightness.cpp b/src/effects/Brightness.cpp index 591bce6a..cac05b14 100644 --- a/src/effects/Brightness.cpp +++ b/src/effects/Brightness.cpp @@ -3,7 +3,10 @@ * @brief Source file for Brightness class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/effects/ChromaKey.cpp b/src/effects/ChromaKey.cpp index 2c3008f2..3329b4df 100644 --- a/src/effects/ChromaKey.cpp +++ b/src/effects/ChromaKey.cpp @@ -3,7 +3,10 @@ * @brief Source file for ChromaKey class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/effects/ColorShift.cpp b/src/effects/ColorShift.cpp index 00300561..2adb88ff 100644 --- a/src/effects/ColorShift.cpp +++ b/src/effects/ColorShift.cpp @@ -3,7 +3,10 @@ * @brief Source file for Color Shift effect class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/effects/Crop.cpp b/src/effects/Crop.cpp index 53953ec2..390d537e 100644 --- a/src/effects/Crop.cpp +++ b/src/effects/Crop.cpp @@ -3,7 +3,10 @@ * @brief Source file for Crop effect class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/effects/Deinterlace.cpp b/src/effects/Deinterlace.cpp index bf34d13f..fce8e1b1 100644 --- a/src/effects/Deinterlace.cpp +++ b/src/effects/Deinterlace.cpp @@ -3,7 +3,10 @@ * @brief Source file for De-interlace class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/effects/Hue.cpp b/src/effects/Hue.cpp index ae401a8b..e6c7e761 100644 --- a/src/effects/Hue.cpp +++ b/src/effects/Hue.cpp @@ -3,7 +3,10 @@ * @brief Source file for Hue effect class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/effects/Mask.cpp b/src/effects/Mask.cpp index f8f34ac6..d0adf8e7 100644 --- a/src/effects/Mask.cpp +++ b/src/effects/Mask.cpp @@ -3,7 +3,10 @@ * @brief Source file for Mask class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/effects/Negate.cpp b/src/effects/Negate.cpp index 8ff6e0d6..383ded34 100644 --- a/src/effects/Negate.cpp +++ b/src/effects/Negate.cpp @@ -3,7 +3,10 @@ * @brief Source file for Negate class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/effects/Pixelate.cpp b/src/effects/Pixelate.cpp index 78030283..3f27f7f8 100644 --- a/src/effects/Pixelate.cpp +++ b/src/effects/Pixelate.cpp @@ -3,7 +3,10 @@ * @brief Source file for Pixelate effect class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/effects/Saturation.cpp b/src/effects/Saturation.cpp index ecb3165f..4500ff9b 100644 --- a/src/effects/Saturation.cpp +++ b/src/effects/Saturation.cpp @@ -3,7 +3,10 @@ * @brief Source file for Saturation class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/effects/Shift.cpp b/src/effects/Shift.cpp index 80a7b2d7..30aaa5ae 100644 --- a/src/effects/Shift.cpp +++ b/src/effects/Shift.cpp @@ -3,7 +3,10 @@ * @brief Source file for Shift effect class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/effects/Wave.cpp b/src/effects/Wave.cpp index 0a1c5273..71b75dbd 100644 --- a/src/effects/Wave.cpp +++ b/src/effects/Wave.cpp @@ -3,7 +3,10 @@ * @brief Source file for Wave effect class * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/examples/Example.cpp b/src/examples/Example.cpp index 1e19f4d9..314a5ea2 100644 --- a/src/examples/Example.cpp +++ b/src/examples/Example.cpp @@ -3,7 +3,10 @@ * @brief Source file for Example Executable (example app for libopenshot) * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/src/examples/ExampleBlackmagic.cpp b/src/examples/ExampleBlackmagic.cpp index 0af6c9b4..84bbe809 100644 --- a/src/examples/ExampleBlackmagic.cpp +++ b/src/examples/ExampleBlackmagic.cpp @@ -3,7 +3,10 @@ * @brief Source file for Main_Blackmagic class (live greenscreen example app) * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/tests/Cache_Tests.cpp b/tests/Cache_Tests.cpp index 8cf64c40..4f97061f 100644 --- a/tests/Cache_Tests.cpp +++ b/tests/Cache_Tests.cpp @@ -3,7 +3,10 @@ * @brief Unit tests for openshot::Cache * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/tests/Clip_Tests.cpp b/tests/Clip_Tests.cpp index 1134e8ab..c8b23862 100644 --- a/tests/Clip_Tests.cpp +++ b/tests/Clip_Tests.cpp @@ -3,7 +3,10 @@ * @brief Unit tests for openshot::Clip * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/tests/Color_Tests.cpp b/tests/Color_Tests.cpp index b1976ea8..e01fe853 100644 --- a/tests/Color_Tests.cpp +++ b/tests/Color_Tests.cpp @@ -3,7 +3,10 @@ * @brief Unit tests for openshot::Color * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/tests/Coordinate_Tests.cpp b/tests/Coordinate_Tests.cpp index 0d513027..a871a911 100644 --- a/tests/Coordinate_Tests.cpp +++ b/tests/Coordinate_Tests.cpp @@ -3,7 +3,10 @@ * @brief Unit tests for openshot::Coordinate * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/tests/FFmpegReader_Tests.cpp b/tests/FFmpegReader_Tests.cpp index 462a77c3..aff6cc64 100644 --- a/tests/FFmpegReader_Tests.cpp +++ b/tests/FFmpegReader_Tests.cpp @@ -3,7 +3,10 @@ * @brief Unit tests for openshot::FFmpegReader * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/tests/FFmpegWriter_Tests.cpp b/tests/FFmpegWriter_Tests.cpp index 73357f21..67882b05 100644 --- a/tests/FFmpegWriter_Tests.cpp +++ b/tests/FFmpegWriter_Tests.cpp @@ -3,7 +3,10 @@ * @brief Unit tests for openshot::FFmpegWriter * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/tests/Fraction_Tests.cpp b/tests/Fraction_Tests.cpp index 89becdef..4fabcf6d 100644 --- a/tests/Fraction_Tests.cpp +++ b/tests/Fraction_Tests.cpp @@ -3,7 +3,10 @@ * @brief Unit tests for openshot::Fraction * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/tests/FrameMapper_Tests.cpp b/tests/FrameMapper_Tests.cpp index 053df31f..6b0042dd 100644 --- a/tests/FrameMapper_Tests.cpp +++ b/tests/FrameMapper_Tests.cpp @@ -3,7 +3,10 @@ * @brief Unit tests for openshot::FrameMapper * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/tests/ImageWriter_Tests.cpp b/tests/ImageWriter_Tests.cpp index 107ee399..6400f84e 100644 --- a/tests/ImageWriter_Tests.cpp +++ b/tests/ImageWriter_Tests.cpp @@ -3,7 +3,10 @@ * @brief Unit tests for openshot::ImageWriter * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/tests/KeyFrame_Tests.cpp b/tests/KeyFrame_Tests.cpp index cbd1a0e0..1a16de0d 100644 --- a/tests/KeyFrame_Tests.cpp +++ b/tests/KeyFrame_Tests.cpp @@ -3,7 +3,10 @@ * @brief Unit tests for openshot::Keyframe * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/tests/Point_Tests.cpp b/tests/Point_Tests.cpp index 376a69d1..107f661c 100644 --- a/tests/Point_Tests.cpp +++ b/tests/Point_Tests.cpp @@ -3,7 +3,10 @@ * @brief Unit tests for openshot::Point * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/tests/ReaderBase_Tests.cpp b/tests/ReaderBase_Tests.cpp index 70ca90d5..e4a3935b 100644 --- a/tests/ReaderBase_Tests.cpp +++ b/tests/ReaderBase_Tests.cpp @@ -3,7 +3,10 @@ * @brief Unit tests for openshot::ReaderBase * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/tests/Settings_Tests.cpp b/tests/Settings_Tests.cpp index 1b8180c9..5bb020f1 100644 --- a/tests/Settings_Tests.cpp +++ b/tests/Settings_Tests.cpp @@ -3,7 +3,10 @@ * @brief Unit tests for openshot::Color * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/tests/Timeline_Tests.cpp b/tests/Timeline_Tests.cpp index 22f33894..9ad42f2c 100644 --- a/tests/Timeline_Tests.cpp +++ b/tests/Timeline_Tests.cpp @@ -3,7 +3,10 @@ * @brief Unit tests for openshot::Timeline * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of diff --git a/tests/tests.cpp b/tests/tests.cpp index 2321513d..ca01331c 100644 --- a/tests/tests.cpp +++ b/tests/tests.cpp @@ -3,7 +3,10 @@ * @brief Source code for Unit test executable (runs all tests and reports successes and failures) * @author Jonathan Thomas * - * @section LICENSE + * @ref License + */ + +/* LICENSE * * Copyright (c) 2008-2014 OpenShot Studios, LLC * . This file is part of From ae96690e074fa8ff7c52fe1eeda727d9d0617b03 Mon Sep 17 00:00:00 2001 From: "FeRD (Frank Dana)" Date: Sun, 9 Jun 2019 08:31:40 -0400 Subject: [PATCH 198/223] Doxyfile.in: Exclude python source --- Doxyfile.in | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Doxyfile.in b/Doxyfile.in index 7cf50631..ba047c6b 100644 --- a/Doxyfile.in +++ b/Doxyfile.in @@ -798,7 +798,9 @@ EXCLUDE_SYMLINKS = NO EXCLUDE_PATTERNS = "*/.*" \ "*/.*/*" \ "*/src/Main.cpp*" \ - "*/src/Main_Blackmagic.cpp*" + "*/src/Main_Blackmagic.cpp*" \ + "*/src/bindings/*" \ + "*.py" # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the From be7db1148074aa3a3f7524300310192ab150fe5d Mon Sep 17 00:00:00 2001 From: SuslikV Date: Tue, 11 Jun 2019 10:49:45 +0300 Subject: [PATCH 199/223] Add streamable file format options for mp4, mov Add 2 new multiplexing presets for mp4, mov files: mp4_faststart mp4_fragmented The Preset usage from openshot-qt export.py as follows (example): w.SetOption(openshot.VIDEO_STREAM, "muxing_preset", "mp4_faststart") YouTube suggest to use streamable file formats to process the uploading videos faster (by starting its processing when upload not yet complete) MP4, MOV files export requires additional dictionary keys to be set in this case. --- src/FFmpegWriter.cpp | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index ddcd4e16..45ff31b8 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -38,6 +38,9 @@ using namespace openshot; #pragma message "You are compiling only with software encode" #endif +// Multiplexer parameters temporary storage +AVDictionary *mux_dict = NULL; + #if IS_FFMPEG_3_2 int hw_en_on = 1; // Is set in UI int hw_en_supported = 0; // Is set by FFmpegWriter @@ -464,6 +467,17 @@ void FFmpegWriter::SetOption(StreamType stream, string name, string value) { ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::SetOption (" + (string)name + ")", "stream == VIDEO_STREAM", stream == VIDEO_STREAM, "", -1, "", -1, "", -1, "", -1, "", -1); + // Muxing dictionary is not part of the codec context. + // Just reusing SetOption function to set popular multiplexing presets. + } else if (name == "muxing_preset") { + if (value == "mp4_faststart") { + // 'moov' box to the beginning; only for MOV, MP4 + av_dict_set(&mux_dict, "movflags", "faststart", 0); + } else if (value == "mp4_fragmented") { + // write selfcontained fragmented file, minimum length of the fragment 8 sec; only for MOV, MP4 + av_dict_set(&mux_dict, "movflags", "frag_keyframe", 0); + av_dict_set(&mux_dict, "min_frag_duration", "8000000", 0); + } } else { throw InvalidOptions("The option is not valid for this codec.", path); } @@ -511,17 +525,29 @@ void FFmpegWriter::WriteHeader() { snprintf(oc->AV_FILENAME, sizeof(oc->AV_FILENAME), "%s", path.c_str()); // Write the stream header, if any - // TODO: add avoptions / parameters instead of NULL // Add general metadata (if any) for (std::map::iterator iter = info.metadata.begin(); iter != info.metadata.end(); ++iter) { av_dict_set(&oc->metadata, iter->first.c_str(), iter->second.c_str(), 0); } - if (avformat_write_header(oc, NULL) != 0) { + // Set multiplexing parameters + AVDictionary *dict = NULL; + + bool is_mp4 = strcmp(oc->oformat->name, "mp4"); + bool is_mov = strcmp(oc->oformat->name, "mov"); + // Set dictionary preset only for MP4 and MOV files + if (is_mp4 || is_mov) + av_dict_copy(&dict, mux_dict, 0); + + if (avformat_write_header(oc, &dict) != 0) { throw InvalidFile("Could not write header to file.", path); }; + // Free multiplexing dictionaries sets + if (dict) av_dict_free(&dict); + if (mux_dict) av_dict_free(&mux_dict); + // Mark as 'written' write_header = true; From f170fdd009465ffea765e58e8d5cebd1d541fdfe Mon Sep 17 00:00:00 2001 From: "FeRD (Frank Dana)" Date: Tue, 11 Jun 2019 06:48:32 -0400 Subject: [PATCH 200/223] Update copyright range to current year --- include/AudioBufferSource.h | 2 +- include/AudioDeviceInfo.h | 2 +- include/AudioReaderSource.h | 2 +- include/AudioResampler.h | 2 +- include/CacheBase.h | 2 +- include/CacheDisk.h | 2 +- include/CacheMemory.h | 2 +- include/ChannelLayouts.h | 2 +- include/ChunkReader.h | 2 +- include/ChunkWriter.h | 2 +- include/Clip.h | 2 +- include/ClipBase.h | 2 +- include/Color.h | 2 +- include/Coordinate.h | 2 +- include/CrashHandler.h | 2 +- include/DecklinkInput.h | 2 +- include/DecklinkOutput.h | 2 +- include/DecklinkReader.h | 2 +- include/DecklinkWriter.h | 2 +- include/DummyReader.h | 2 +- include/EffectBase.h | 2 +- include/EffectInfo.h | 2 +- include/Effects.h | 2 +- include/Enums.h | 2 +- include/Exceptions.h | 2 +- include/FFmpegUtilities.h | 2 +- include/Fraction.h | 2 +- include/Frame.h | 2 +- include/FrameMapper.h | 2 +- include/ImageReader.h | 2 +- include/Json.h | 2 +- include/KeyFrame.h | 2 +- include/OpenMPUtilities.h | 2 +- include/OpenShot.h | 2 +- include/PlayerBase.h | 2 +- include/Point.h | 2 +- include/Profiles.h | 2 +- include/Qt/AudioPlaybackThread.h | 2 +- include/Qt/PlayerDemo.h | 2 +- include/Qt/PlayerPrivate.h | 2 +- include/Qt/VideoCacheThread.h | 2 +- include/Qt/VideoPlaybackThread.h | 2 +- include/Qt/VideoRenderWidget.h | 2 +- include/Qt/VideoRenderer.h | 2 +- include/QtImageReader.h | 2 +- include/QtPlayer.h | 2 +- include/ReaderBase.h | 2 +- include/RendererBase.h | 2 +- include/Settings.h | 2 +- include/Tests.h | 2 +- include/TextReader.h | 2 +- include/Timeline.h | 2 +- include/Version.h | 2 +- include/WriterBase.h | 2 +- include/ZmqLogger.h | 2 +- include/effects/Bars.h | 2 +- include/effects/Blur.h | 2 +- include/effects/Brightness.h | 2 +- include/effects/ChromaKey.h | 2 +- include/effects/ColorShift.h | 2 +- include/effects/Crop.h | 2 +- include/effects/Deinterlace.h | 2 +- include/effects/Hue.h | 2 +- include/effects/Mask.h | 2 +- include/effects/Negate.h | 2 +- include/effects/Pixelate.h | 2 +- include/effects/Saturation.h | 2 +- include/effects/Shift.h | 2 +- include/effects/Wave.h | 2 +- src/AudioBufferSource.cpp | 2 +- src/AudioReaderSource.cpp | 2 +- src/AudioResampler.cpp | 2 +- src/CMakeLists.txt | 2 +- src/CacheBase.cpp | 2 +- src/CacheDisk.cpp | 2 +- src/CacheMemory.cpp | 2 +- src/ChunkReader.cpp | 2 +- src/ChunkWriter.cpp | 2 +- src/Clip.cpp | 2 +- src/ClipBase.cpp | 2 +- src/Color.cpp | 2 +- src/Coordinate.cpp | 2 +- src/CrashHandler.cpp | 2 +- src/DecklinkInput.cpp | 2 +- src/DecklinkOutput.cpp | 2 +- src/DecklinkReader.cpp | 2 +- src/DecklinkWriter.cpp | 2 +- src/DummyReader.cpp | 2 +- src/EffectBase.cpp | 2 +- src/EffectInfo.cpp | 2 +- src/Fraction.cpp | 2 +- src/Frame.cpp | 2 +- src/FrameMapper.cpp | 2 +- src/ImageReader.cpp | 2 +- src/KeyFrame.cpp | 2 +- src/PlayerBase.cpp | 2 +- src/Point.cpp | 2 +- src/Profiles.cpp | 2 +- src/Qt/AudioPlaybackThread.cpp | 2 +- src/Qt/PlayerDemo.cpp | 2 +- src/Qt/PlayerPrivate.cpp | 2 +- src/Qt/VideoCacheThread.cpp | 2 +- src/Qt/VideoPlaybackThread.cpp | 2 +- src/Qt/VideoRenderWidget.cpp | 2 +- src/Qt/VideoRenderer.cpp | 2 +- src/Qt/demo/main.cpp | 2 +- src/QtImageReader.cpp | 2 +- src/QtPlayer.cpp | 2 +- src/ReaderBase.cpp | 2 +- src/RendererBase.cpp | 2 +- src/Settings.cpp | 2 +- src/TextReader.cpp | 2 +- src/Timeline.cpp | 2 +- src/WriterBase.cpp | 2 +- src/ZmqLogger.cpp | 2 +- src/bindings/CMakeLists.txt | 2 +- src/bindings/python/CMakeLists.txt | 2 +- src/bindings/python/openshot.i | 2 +- src/bindings/ruby/CMakeLists.txt | 2 +- src/bindings/ruby/openshot.i | 2 +- src/effects/Bars.cpp | 2 +- src/effects/Blur.cpp | 2 +- src/effects/Brightness.cpp | 2 +- src/effects/ChromaKey.cpp | 2 +- src/effects/ColorShift.cpp | 2 +- src/effects/Crop.cpp | 2 +- src/effects/Deinterlace.cpp | 2 +- src/effects/Hue.cpp | 2 +- src/effects/Mask.cpp | 2 +- src/effects/Negate.cpp | 2 +- src/effects/Pixelate.cpp | 2 +- src/effects/Saturation.cpp | 2 +- src/effects/Shift.cpp | 2 +- src/effects/Wave.cpp | 2 +- src/examples/Example.cpp | 2 +- src/examples/ExampleBlackmagic.cpp | 2 +- tests/CMakeLists.txt | 2 +- tests/Cache_Tests.cpp | 2 +- tests/Clip_Tests.cpp | 2 +- tests/Color_Tests.cpp | 2 +- tests/Coordinate_Tests.cpp | 2 +- tests/FFmpegReader_Tests.cpp | 2 +- tests/FFmpegWriter_Tests.cpp | 2 +- tests/Fraction_Tests.cpp | 2 +- tests/FrameMapper_Tests.cpp | 2 +- tests/ImageWriter_Tests.cpp | 2 +- tests/KeyFrame_Tests.cpp | 2 +- tests/Point_Tests.cpp | 2 +- tests/ReaderBase_Tests.cpp | 2 +- tests/Settings_Tests.cpp | 2 +- tests/Timeline_Tests.cpp | 2 +- tests/tests.cpp | 2 +- 152 files changed, 152 insertions(+), 152 deletions(-) diff --git a/include/AudioBufferSource.h b/include/AudioBufferSource.h index 0b72aa5d..4addb37d 100644 --- a/include/AudioBufferSource.h +++ b/include/AudioBufferSource.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/AudioDeviceInfo.h b/include/AudioDeviceInfo.h index f58c1562..4099430e 100644 --- a/include/AudioDeviceInfo.h +++ b/include/AudioDeviceInfo.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/AudioReaderSource.h b/include/AudioReaderSource.h index a60f940e..679aed61 100644 --- a/include/AudioReaderSource.h +++ b/include/AudioReaderSource.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/AudioResampler.h b/include/AudioResampler.h index 16d35906..85a44b1f 100644 --- a/include/AudioResampler.h +++ b/include/AudioResampler.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/CacheBase.h b/include/CacheBase.h index d8bcf805..af24b153 100644 --- a/include/CacheBase.h +++ b/include/CacheBase.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/CacheDisk.h b/include/CacheDisk.h index 03935dcb..11a8808c 100644 --- a/include/CacheDisk.h +++ b/include/CacheDisk.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/CacheMemory.h b/include/CacheMemory.h index 03db05d1..eb11f273 100644 --- a/include/CacheMemory.h +++ b/include/CacheMemory.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/ChannelLayouts.h b/include/ChannelLayouts.h index 4c12822f..673eeccd 100644 --- a/include/ChannelLayouts.h +++ b/include/ChannelLayouts.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/ChunkReader.h b/include/ChunkReader.h index 022dfb77..5b330b70 100644 --- a/include/ChunkReader.h +++ b/include/ChunkReader.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/ChunkWriter.h b/include/ChunkWriter.h index 2ecdf06e..5ac9be4a 100644 --- a/include/ChunkWriter.h +++ b/include/ChunkWriter.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/Clip.h b/include/Clip.h index 9f10148f..52be853e 100644 --- a/include/Clip.h +++ b/include/Clip.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/ClipBase.h b/include/ClipBase.h index 97d60ef6..adbf11ef 100644 --- a/include/ClipBase.h +++ b/include/ClipBase.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/Color.h b/include/Color.h index 8e9d152f..d06bee91 100644 --- a/include/Color.h +++ b/include/Color.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/Coordinate.h b/include/Coordinate.h index 40e4bb63..6ab296dc 100644 --- a/include/Coordinate.h +++ b/include/Coordinate.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/CrashHandler.h b/include/CrashHandler.h index 1c6ed187..76d788f2 100644 --- a/include/CrashHandler.h +++ b/include/CrashHandler.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/DecklinkInput.h b/include/DecklinkInput.h index ebac2baf..87207505 100644 --- a/include/DecklinkInput.h +++ b/include/DecklinkInput.h @@ -33,7 +33,7 @@ * DEALINGS IN THE SOFTWARE. * * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/DecklinkOutput.h b/include/DecklinkOutput.h index f653b8dd..ebaa9ab4 100644 --- a/include/DecklinkOutput.h +++ b/include/DecklinkOutput.h @@ -33,7 +33,7 @@ * DEALINGS IN THE SOFTWARE. * * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/DecklinkReader.h b/include/DecklinkReader.h index 29a8fdcb..3dc7e23d 100644 --- a/include/DecklinkReader.h +++ b/include/DecklinkReader.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/DecklinkWriter.h b/include/DecklinkWriter.h index 3a65f0c1..809890f3 100644 --- a/include/DecklinkWriter.h +++ b/include/DecklinkWriter.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/DummyReader.h b/include/DummyReader.h index 692c7589..adae512f 100644 --- a/include/DummyReader.h +++ b/include/DummyReader.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/EffectBase.h b/include/EffectBase.h index 20cd8edc..cd39c272 100644 --- a/include/EffectBase.h +++ b/include/EffectBase.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/EffectInfo.h b/include/EffectInfo.h index 59b7d234..7806f096 100644 --- a/include/EffectInfo.h +++ b/include/EffectInfo.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/Effects.h b/include/Effects.h index 7297ba07..746da4c0 100644 --- a/include/Effects.h +++ b/include/Effects.h @@ -11,7 +11,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/Enums.h b/include/Enums.h index 505c28f6..387191ea 100644 --- a/include/Enums.h +++ b/include/Enums.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/Exceptions.h b/include/Exceptions.h index ae9755ae..0fb2bb1e 100644 --- a/include/Exceptions.h +++ b/include/Exceptions.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/FFmpegUtilities.h b/include/FFmpegUtilities.h index 1069bb2c..a6b83015 100644 --- a/include/FFmpegUtilities.h +++ b/include/FFmpegUtilities.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/Fraction.h b/include/Fraction.h index 178cca41..9ffcda1f 100644 --- a/include/Fraction.h +++ b/include/Fraction.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/Frame.h b/include/Frame.h index d34800ff..911e2713 100644 --- a/include/Frame.h +++ b/include/Frame.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/FrameMapper.h b/include/FrameMapper.h index 66020951..78e8944e 100644 --- a/include/FrameMapper.h +++ b/include/FrameMapper.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/ImageReader.h b/include/ImageReader.h index 2ba2be8d..25bdc68c 100644 --- a/include/ImageReader.h +++ b/include/ImageReader.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/Json.h b/include/Json.h index bacd59a1..3a10ab74 100644 --- a/include/Json.h +++ b/include/Json.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/KeyFrame.h b/include/KeyFrame.h index 1aca9cc0..14f519a1 100644 --- a/include/KeyFrame.h +++ b/include/KeyFrame.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/OpenMPUtilities.h b/include/OpenMPUtilities.h index dcde3c32..b78c3742 100644 --- a/include/OpenMPUtilities.h +++ b/include/OpenMPUtilities.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/OpenShot.h b/include/OpenShot.h index d7c62af6..8987f5e5 100644 --- a/include/OpenShot.h +++ b/include/OpenShot.h @@ -73,7 +73,7 @@ * * \anchor License * ### License & Copyright ### - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/PlayerBase.h b/include/PlayerBase.h index d6ff64f5..ecc8f5f2 100644 --- a/include/PlayerBase.h +++ b/include/PlayerBase.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/Point.h b/include/Point.h index 8eabf7c8..d9a5e33d 100644 --- a/include/Point.h +++ b/include/Point.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/Profiles.h b/include/Profiles.h index f7621551..a6e3d22d 100644 --- a/include/Profiles.h +++ b/include/Profiles.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/Qt/AudioPlaybackThread.h b/include/Qt/AudioPlaybackThread.h index eaf3b73c..94abf806 100644 --- a/include/Qt/AudioPlaybackThread.h +++ b/include/Qt/AudioPlaybackThread.h @@ -9,7 +9,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/Qt/PlayerDemo.h b/include/Qt/PlayerDemo.h index 3749ce4c..e5d304a4 100644 --- a/include/Qt/PlayerDemo.h +++ b/include/Qt/PlayerDemo.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/Qt/PlayerPrivate.h b/include/Qt/PlayerPrivate.h index 4745018e..f846fb2a 100644 --- a/include/Qt/PlayerPrivate.h +++ b/include/Qt/PlayerPrivate.h @@ -9,7 +9,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/Qt/VideoCacheThread.h b/include/Qt/VideoCacheThread.h index db932263..6fbf6d58 100644 --- a/include/Qt/VideoCacheThread.h +++ b/include/Qt/VideoCacheThread.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/Qt/VideoPlaybackThread.h b/include/Qt/VideoPlaybackThread.h index 363c5d1d..753c7778 100644 --- a/include/Qt/VideoPlaybackThread.h +++ b/include/Qt/VideoPlaybackThread.h @@ -9,7 +9,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/Qt/VideoRenderWidget.h b/include/Qt/VideoRenderWidget.h index 82bb8320..429940e7 100644 --- a/include/Qt/VideoRenderWidget.h +++ b/include/Qt/VideoRenderWidget.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/Qt/VideoRenderer.h b/include/Qt/VideoRenderer.h index 4b1fcc89..1bdbfac3 100644 --- a/include/Qt/VideoRenderer.h +++ b/include/Qt/VideoRenderer.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/QtImageReader.h b/include/QtImageReader.h index c7196e6b..a6f70165 100644 --- a/include/QtImageReader.h +++ b/include/QtImageReader.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/QtPlayer.h b/include/QtPlayer.h index aaa12fa0..5d0beba6 100644 --- a/include/QtPlayer.h +++ b/include/QtPlayer.h @@ -9,7 +9,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/ReaderBase.h b/include/ReaderBase.h index 4f9f724d..4579ebb6 100644 --- a/include/ReaderBase.h +++ b/include/ReaderBase.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/RendererBase.h b/include/RendererBase.h index 787a254f..2638d336 100644 --- a/include/RendererBase.h +++ b/include/RendererBase.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/Settings.h b/include/Settings.h index 984e3685..11d7f76c 100644 --- a/include/Settings.h +++ b/include/Settings.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/Tests.h b/include/Tests.h index 208cfaf1..b647cb0e 100644 --- a/include/Tests.h +++ b/include/Tests.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/TextReader.h b/include/TextReader.h index 447ae08c..0444c2d5 100644 --- a/include/TextReader.h +++ b/include/TextReader.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/Timeline.h b/include/Timeline.h index dea81bf2..a9ada689 100644 --- a/include/Timeline.h +++ b/include/Timeline.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/Version.h b/include/Version.h index be17ca9a..ec9e94a6 100644 --- a/include/Version.h +++ b/include/Version.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/WriterBase.h b/include/WriterBase.h index e8caf72f..e03263ed 100644 --- a/include/WriterBase.h +++ b/include/WriterBase.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/ZmqLogger.h b/include/ZmqLogger.h index ba736382..96e16213 100644 --- a/include/ZmqLogger.h +++ b/include/ZmqLogger.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/effects/Bars.h b/include/effects/Bars.h index c9d90325..eb49a07f 100644 --- a/include/effects/Bars.h +++ b/include/effects/Bars.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/effects/Blur.h b/include/effects/Blur.h index c4a92772..cdb1402a 100644 --- a/include/effects/Blur.h +++ b/include/effects/Blur.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/effects/Brightness.h b/include/effects/Brightness.h index 572f2b11..cb828442 100644 --- a/include/effects/Brightness.h +++ b/include/effects/Brightness.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/effects/ChromaKey.h b/include/effects/ChromaKey.h index c08a09f5..eca5cf74 100644 --- a/include/effects/ChromaKey.h +++ b/include/effects/ChromaKey.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/effects/ColorShift.h b/include/effects/ColorShift.h index b2f2f115..ba2326f7 100644 --- a/include/effects/ColorShift.h +++ b/include/effects/ColorShift.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/effects/Crop.h b/include/effects/Crop.h index da78dbe3..c79ec83d 100644 --- a/include/effects/Crop.h +++ b/include/effects/Crop.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/effects/Deinterlace.h b/include/effects/Deinterlace.h index 7614fb37..14b13e21 100644 --- a/include/effects/Deinterlace.h +++ b/include/effects/Deinterlace.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/effects/Hue.h b/include/effects/Hue.h index c023fdef..02d8cf9b 100644 --- a/include/effects/Hue.h +++ b/include/effects/Hue.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/effects/Mask.h b/include/effects/Mask.h index badbaf40..253dbfe3 100644 --- a/include/effects/Mask.h +++ b/include/effects/Mask.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/effects/Negate.h b/include/effects/Negate.h index 1618a427..89969a9e 100644 --- a/include/effects/Negate.h +++ b/include/effects/Negate.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/effects/Pixelate.h b/include/effects/Pixelate.h index 50f555d6..5cb0cffb 100644 --- a/include/effects/Pixelate.h +++ b/include/effects/Pixelate.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/effects/Saturation.h b/include/effects/Saturation.h index a7b3806f..cd714d6e 100644 --- a/include/effects/Saturation.h +++ b/include/effects/Saturation.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/effects/Shift.h b/include/effects/Shift.h index d6f2066b..c208ded6 100644 --- a/include/effects/Shift.h +++ b/include/effects/Shift.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/include/effects/Wave.h b/include/effects/Wave.h index ed28b8a0..ce2e8618 100644 --- a/include/effects/Wave.h +++ b/include/effects/Wave.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/AudioBufferSource.cpp b/src/AudioBufferSource.cpp index c7eed591..912d2552 100644 --- a/src/AudioBufferSource.cpp +++ b/src/AudioBufferSource.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/AudioReaderSource.cpp b/src/AudioReaderSource.cpp index 84d0bd0b..4c42d2ed 100644 --- a/src/AudioReaderSource.cpp +++ b/src/AudioReaderSource.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/AudioResampler.cpp b/src/AudioResampler.cpp index 0b5d2d3c..3aafb75d 100644 --- a/src/AudioResampler.cpp +++ b/src/AudioResampler.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d793ffa2..538f2284 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,7 +4,7 @@ # # @section LICENSE # -# Copyright (c) 2008-2014 OpenShot Studios, LLC +# 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 diff --git a/src/CacheBase.cpp b/src/CacheBase.cpp index 44abbdcf..8270b393 100644 --- a/src/CacheBase.cpp +++ b/src/CacheBase.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/CacheDisk.cpp b/src/CacheDisk.cpp index ce14d109..2027cee6 100644 --- a/src/CacheDisk.cpp +++ b/src/CacheDisk.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/CacheMemory.cpp b/src/CacheMemory.cpp index 9536878a..5b4c9a6b 100644 --- a/src/CacheMemory.cpp +++ b/src/CacheMemory.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/ChunkReader.cpp b/src/ChunkReader.cpp index bfd25575..1c78b5dd 100644 --- a/src/ChunkReader.cpp +++ b/src/ChunkReader.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/ChunkWriter.cpp b/src/ChunkWriter.cpp index 2734bb1b..5dad4df3 100644 --- a/src/ChunkWriter.cpp +++ b/src/ChunkWriter.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/Clip.cpp b/src/Clip.cpp index 2e99106c..50a46f6e 100644 --- a/src/Clip.cpp +++ b/src/Clip.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/ClipBase.cpp b/src/ClipBase.cpp index 26e7710b..1517a7e3 100644 --- a/src/ClipBase.cpp +++ b/src/ClipBase.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/Color.cpp b/src/Color.cpp index dec1571a..fafe8a8f 100644 --- a/src/Color.cpp +++ b/src/Color.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/Coordinate.cpp b/src/Coordinate.cpp index 3fd293d6..2a5bdfbe 100644 --- a/src/Coordinate.cpp +++ b/src/Coordinate.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/CrashHandler.cpp b/src/CrashHandler.cpp index 24d8975b..1782f5ba 100644 --- a/src/CrashHandler.cpp +++ b/src/CrashHandler.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/DecklinkInput.cpp b/src/DecklinkInput.cpp index 5a83931c..da5a8d00 100644 --- a/src/DecklinkInput.cpp +++ b/src/DecklinkInput.cpp @@ -33,7 +33,7 @@ * DEALINGS IN THE SOFTWARE. * * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/DecklinkOutput.cpp b/src/DecklinkOutput.cpp index d86fe777..2dee7e9e 100644 --- a/src/DecklinkOutput.cpp +++ b/src/DecklinkOutput.cpp @@ -33,7 +33,7 @@ * DEALINGS IN THE SOFTWARE. * * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/DecklinkReader.cpp b/src/DecklinkReader.cpp index 512389f6..3f45830a 100644 --- a/src/DecklinkReader.cpp +++ b/src/DecklinkReader.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/DecklinkWriter.cpp b/src/DecklinkWriter.cpp index 25e2db27..7dcede04 100644 --- a/src/DecklinkWriter.cpp +++ b/src/DecklinkWriter.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/DummyReader.cpp b/src/DummyReader.cpp index 8e7c82c5..6b643d88 100644 --- a/src/DummyReader.cpp +++ b/src/DummyReader.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/EffectBase.cpp b/src/EffectBase.cpp index 6c095402..33d0cc72 100644 --- a/src/EffectBase.cpp +++ b/src/EffectBase.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/EffectInfo.cpp b/src/EffectInfo.cpp index 9d094564..0b9c360d 100644 --- a/src/EffectInfo.cpp +++ b/src/EffectInfo.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/Fraction.cpp b/src/Fraction.cpp index ee5a961b..c9cdad55 100644 --- a/src/Fraction.cpp +++ b/src/Fraction.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/Frame.cpp b/src/Frame.cpp index d7edc889..2fff3435 100644 --- a/src/Frame.cpp +++ b/src/Frame.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/FrameMapper.cpp b/src/FrameMapper.cpp index 2b725615..d68270e0 100644 --- a/src/FrameMapper.cpp +++ b/src/FrameMapper.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/ImageReader.cpp b/src/ImageReader.cpp index 0f09631e..1de252bd 100644 --- a/src/ImageReader.cpp +++ b/src/ImageReader.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/KeyFrame.cpp b/src/KeyFrame.cpp index b82fa2ea..776bd03a 100644 --- a/src/KeyFrame.cpp +++ b/src/KeyFrame.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/PlayerBase.cpp b/src/PlayerBase.cpp index f4686d9f..3c904afd 100644 --- a/src/PlayerBase.cpp +++ b/src/PlayerBase.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/Point.cpp b/src/Point.cpp index 4b73bf6f..0606dca6 100644 --- a/src/Point.cpp +++ b/src/Point.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/Profiles.cpp b/src/Profiles.cpp index a39100bc..0da0efe9 100644 --- a/src/Profiles.cpp +++ b/src/Profiles.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/Qt/AudioPlaybackThread.cpp b/src/Qt/AudioPlaybackThread.cpp index 3a286729..7a6e4569 100644 --- a/src/Qt/AudioPlaybackThread.cpp +++ b/src/Qt/AudioPlaybackThread.cpp @@ -9,7 +9,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/Qt/PlayerDemo.cpp b/src/Qt/PlayerDemo.cpp index 55862a96..110b69dc 100644 --- a/src/Qt/PlayerDemo.cpp +++ b/src/Qt/PlayerDemo.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/Qt/PlayerPrivate.cpp b/src/Qt/PlayerPrivate.cpp index 2040375e..d0f17842 100644 --- a/src/Qt/PlayerPrivate.cpp +++ b/src/Qt/PlayerPrivate.cpp @@ -9,7 +9,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/Qt/VideoCacheThread.cpp b/src/Qt/VideoCacheThread.cpp index 1a90f1c6..fbe784f7 100644 --- a/src/Qt/VideoCacheThread.cpp +++ b/src/Qt/VideoCacheThread.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/Qt/VideoPlaybackThread.cpp b/src/Qt/VideoPlaybackThread.cpp index 505a11d8..730a1d37 100644 --- a/src/Qt/VideoPlaybackThread.cpp +++ b/src/Qt/VideoPlaybackThread.cpp @@ -9,7 +9,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/Qt/VideoRenderWidget.cpp b/src/Qt/VideoRenderWidget.cpp index 64b539ff..2bfe8fa2 100644 --- a/src/Qt/VideoRenderWidget.cpp +++ b/src/Qt/VideoRenderWidget.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/Qt/VideoRenderer.cpp b/src/Qt/VideoRenderer.cpp index 70986b03..8d0e3a1d 100644 --- a/src/Qt/VideoRenderer.cpp +++ b/src/Qt/VideoRenderer.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/Qt/demo/main.cpp b/src/Qt/demo/main.cpp index 9c7e18eb..3e5f00ba 100644 --- a/src/Qt/demo/main.cpp +++ b/src/Qt/demo/main.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/QtImageReader.cpp b/src/QtImageReader.cpp index c9fe6b4e..111d83d7 100644 --- a/src/QtImageReader.cpp +++ b/src/QtImageReader.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/QtPlayer.cpp b/src/QtPlayer.cpp index 08d7df5e..c53de79d 100644 --- a/src/QtPlayer.cpp +++ b/src/QtPlayer.cpp @@ -9,7 +9,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/ReaderBase.cpp b/src/ReaderBase.cpp index 1aa39e5a..ece0684f 100644 --- a/src/ReaderBase.cpp +++ b/src/ReaderBase.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/RendererBase.cpp b/src/RendererBase.cpp index edd1f795..b2bea40e 100644 --- a/src/RendererBase.cpp +++ b/src/RendererBase.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/Settings.cpp b/src/Settings.cpp index 7c284624..e48fd981 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/TextReader.cpp b/src/TextReader.cpp index fd9dcfef..d435b77e 100644 --- a/src/TextReader.cpp +++ b/src/TextReader.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/Timeline.cpp b/src/Timeline.cpp index d28b4ff8..da960230 100644 --- a/src/Timeline.cpp +++ b/src/Timeline.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/WriterBase.cpp b/src/WriterBase.cpp index 3a8b5f72..29fefe48 100644 --- a/src/WriterBase.cpp +++ b/src/WriterBase.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/ZmqLogger.cpp b/src/ZmqLogger.cpp index cc38f9a4..f9ecc875 100644 --- a/src/ZmqLogger.cpp +++ b/src/ZmqLogger.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/bindings/CMakeLists.txt b/src/bindings/CMakeLists.txt index 0858eb4e..831b7bde 100644 --- a/src/bindings/CMakeLists.txt +++ b/src/bindings/CMakeLists.txt @@ -4,7 +4,7 @@ # # @section LICENSE # -# Copyright (c) 2008-2014 OpenShot Studios, LLC +# 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 diff --git a/src/bindings/python/CMakeLists.txt b/src/bindings/python/CMakeLists.txt index 2a481aa7..eb7c989a 100644 --- a/src/bindings/python/CMakeLists.txt +++ b/src/bindings/python/CMakeLists.txt @@ -4,7 +4,7 @@ # # @section LICENSE # -# Copyright (c) 2008-2014 OpenShot Studios, LLC +# 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 diff --git a/src/bindings/python/openshot.i b/src/bindings/python/openshot.i index ed34b658..74d2705b 100644 --- a/src/bindings/python/openshot.i +++ b/src/bindings/python/openshot.i @@ -4,7 +4,7 @@ # # @section LICENSE # -# Copyright (c) 2008-2014 OpenShot Studios, LLC +# 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 diff --git a/src/bindings/ruby/CMakeLists.txt b/src/bindings/ruby/CMakeLists.txt index 7e3bce99..e0987ca4 100644 --- a/src/bindings/ruby/CMakeLists.txt +++ b/src/bindings/ruby/CMakeLists.txt @@ -4,7 +4,7 @@ # # @section LICENSE # -# Copyright (c) 2008-2014 OpenShot Studios, LLC +# 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 diff --git a/src/bindings/ruby/openshot.i b/src/bindings/ruby/openshot.i index 2868708e..775421b6 100644 --- a/src/bindings/ruby/openshot.i +++ b/src/bindings/ruby/openshot.i @@ -4,7 +4,7 @@ # # @section LICENSE # -# Copyright (c) 2008-2014 OpenShot Studios, LLC +# 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 diff --git a/src/effects/Bars.cpp b/src/effects/Bars.cpp index cc541cc2..2868165f 100644 --- a/src/effects/Bars.cpp +++ b/src/effects/Bars.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/effects/Blur.cpp b/src/effects/Blur.cpp index 39c70921..69c1bbb2 100644 --- a/src/effects/Blur.cpp +++ b/src/effects/Blur.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/effects/Brightness.cpp b/src/effects/Brightness.cpp index cac05b14..ea438fc6 100644 --- a/src/effects/Brightness.cpp +++ b/src/effects/Brightness.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/effects/ChromaKey.cpp b/src/effects/ChromaKey.cpp index 3329b4df..c0889e5e 100644 --- a/src/effects/ChromaKey.cpp +++ b/src/effects/ChromaKey.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/effects/ColorShift.cpp b/src/effects/ColorShift.cpp index 2adb88ff..e80a1d0f 100644 --- a/src/effects/ColorShift.cpp +++ b/src/effects/ColorShift.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/effects/Crop.cpp b/src/effects/Crop.cpp index 390d537e..fa6d20ea 100644 --- a/src/effects/Crop.cpp +++ b/src/effects/Crop.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/effects/Deinterlace.cpp b/src/effects/Deinterlace.cpp index fce8e1b1..6da40d08 100644 --- a/src/effects/Deinterlace.cpp +++ b/src/effects/Deinterlace.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/effects/Hue.cpp b/src/effects/Hue.cpp index e6c7e761..a24595fc 100644 --- a/src/effects/Hue.cpp +++ b/src/effects/Hue.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/effects/Mask.cpp b/src/effects/Mask.cpp index d0adf8e7..4da1bb0a 100644 --- a/src/effects/Mask.cpp +++ b/src/effects/Mask.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/effects/Negate.cpp b/src/effects/Negate.cpp index 383ded34..650b1258 100644 --- a/src/effects/Negate.cpp +++ b/src/effects/Negate.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/effects/Pixelate.cpp b/src/effects/Pixelate.cpp index 3f27f7f8..92d13ea8 100644 --- a/src/effects/Pixelate.cpp +++ b/src/effects/Pixelate.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/effects/Saturation.cpp b/src/effects/Saturation.cpp index 4500ff9b..8f2874da 100644 --- a/src/effects/Saturation.cpp +++ b/src/effects/Saturation.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/effects/Shift.cpp b/src/effects/Shift.cpp index 30aaa5ae..474f4466 100644 --- a/src/effects/Shift.cpp +++ b/src/effects/Shift.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/effects/Wave.cpp b/src/effects/Wave.cpp index 71b75dbd..32075780 100644 --- a/src/effects/Wave.cpp +++ b/src/effects/Wave.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/examples/Example.cpp b/src/examples/Example.cpp index 314a5ea2..eec8d00e 100644 --- a/src/examples/Example.cpp +++ b/src/examples/Example.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/src/examples/ExampleBlackmagic.cpp b/src/examples/ExampleBlackmagic.cpp index 84bbe809..91a6655d 100644 --- a/src/examples/ExampleBlackmagic.cpp +++ b/src/examples/ExampleBlackmagic.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e358fb44..6388f4e8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -4,7 +4,7 @@ # # @section LICENSE # -# Copyright (c) 2008-2014 OpenShot Studios, LLC +# 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 diff --git a/tests/Cache_Tests.cpp b/tests/Cache_Tests.cpp index 4f97061f..7e42c741 100644 --- a/tests/Cache_Tests.cpp +++ b/tests/Cache_Tests.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/tests/Clip_Tests.cpp b/tests/Clip_Tests.cpp index c8b23862..b66724b8 100644 --- a/tests/Clip_Tests.cpp +++ b/tests/Clip_Tests.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/tests/Color_Tests.cpp b/tests/Color_Tests.cpp index e01fe853..86b3a458 100644 --- a/tests/Color_Tests.cpp +++ b/tests/Color_Tests.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/tests/Coordinate_Tests.cpp b/tests/Coordinate_Tests.cpp index a871a911..ec8c29a1 100644 --- a/tests/Coordinate_Tests.cpp +++ b/tests/Coordinate_Tests.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/tests/FFmpegReader_Tests.cpp b/tests/FFmpegReader_Tests.cpp index aff6cc64..4ef570ae 100644 --- a/tests/FFmpegReader_Tests.cpp +++ b/tests/FFmpegReader_Tests.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/tests/FFmpegWriter_Tests.cpp b/tests/FFmpegWriter_Tests.cpp index 67882b05..53a9cbf2 100644 --- a/tests/FFmpegWriter_Tests.cpp +++ b/tests/FFmpegWriter_Tests.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/tests/Fraction_Tests.cpp b/tests/Fraction_Tests.cpp index 4fabcf6d..72bd6407 100644 --- a/tests/Fraction_Tests.cpp +++ b/tests/Fraction_Tests.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/tests/FrameMapper_Tests.cpp b/tests/FrameMapper_Tests.cpp index 6b0042dd..ce43fc20 100644 --- a/tests/FrameMapper_Tests.cpp +++ b/tests/FrameMapper_Tests.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/tests/ImageWriter_Tests.cpp b/tests/ImageWriter_Tests.cpp index 6400f84e..d44bbd84 100644 --- a/tests/ImageWriter_Tests.cpp +++ b/tests/ImageWriter_Tests.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/tests/KeyFrame_Tests.cpp b/tests/KeyFrame_Tests.cpp index 1a16de0d..3d790739 100644 --- a/tests/KeyFrame_Tests.cpp +++ b/tests/KeyFrame_Tests.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/tests/Point_Tests.cpp b/tests/Point_Tests.cpp index 107f661c..ed560a9d 100644 --- a/tests/Point_Tests.cpp +++ b/tests/Point_Tests.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/tests/ReaderBase_Tests.cpp b/tests/ReaderBase_Tests.cpp index e4a3935b..b3be12fa 100644 --- a/tests/ReaderBase_Tests.cpp +++ b/tests/ReaderBase_Tests.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/tests/Settings_Tests.cpp b/tests/Settings_Tests.cpp index 5bb020f1..cf7b8a00 100644 --- a/tests/Settings_Tests.cpp +++ b/tests/Settings_Tests.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/tests/Timeline_Tests.cpp b/tests/Timeline_Tests.cpp index 9ad42f2c..1290bee7 100644 --- a/tests/Timeline_Tests.cpp +++ b/tests/Timeline_Tests.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 diff --git a/tests/tests.cpp b/tests/tests.cpp index ca01331c..20d5fd33 100644 --- a/tests/tests.cpp +++ b/tests/tests.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2014 OpenShot Studios, LLC + * 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 From 9261f46772a9943a22dc2183738ff0c2dda3544c Mon Sep 17 00:00:00 2001 From: "FeRD (Frank Dana)" Date: Tue, 11 Jun 2019 06:51:37 -0400 Subject: [PATCH 201/223] More copyright, missed a few older ones --- include/FFmpegReader.h | 2 +- include/FFmpegWriter.h | 2 +- include/ImageWriter.h | 2 +- src/FFmpegReader.cpp | 2 +- src/FFmpegWriter.cpp | 2 +- src/ImageWriter.cpp | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/FFmpegReader.h b/include/FFmpegReader.h index 331ca32a..923c8c18 100644 --- a/include/FFmpegReader.h +++ b/include/FFmpegReader.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2013 OpenShot Studios, LLC, Fabrice Bellard + * Copyright (c) 2008-2019 OpenShot Studios, LLC, Fabrice Bellard * (http://www.openshotstudios.com). This file is part of * OpenShot Library (http://www.openshot.org), an open-source project * dedicated to delivering high quality video editing and animation solutions diff --git a/include/FFmpegWriter.h b/include/FFmpegWriter.h index e4ebe176..d27d8238 100644 --- a/include/FFmpegWriter.h +++ b/include/FFmpegWriter.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2013 OpenShot Studios, LLC, Fabrice Bellard + * Copyright (c) 2008-2019 OpenShot Studios, LLC, Fabrice Bellard * (http://www.openshotstudios.com). This file is part of * OpenShot Library (http://www.openshot.org), an open-source project * dedicated to delivering high quality video editing and animation solutions diff --git a/include/ImageWriter.h b/include/ImageWriter.h index 41c460e1..03aeadb6 100644 --- a/include/ImageWriter.h +++ b/include/ImageWriter.h @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2013 OpenShot Studios, LLC, Fabrice Bellard + * Copyright (c) 2008-2019 OpenShot Studios, LLC, Fabrice Bellard * (http://www.openshotstudios.com). This file is part of * OpenShot Library (http://www.openshot.org), an open-source project * dedicated to delivering high quality video editing and animation solutions diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index b6c9dd1e..ef1702bf 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2013 OpenShot Studios, LLC, Fabrice Bellard + * Copyright (c) 2008-2019 OpenShot Studios, LLC, Fabrice Bellard * (http://www.openshotstudios.com). This file is part of * OpenShot Library (http://www.openshot.org), an open-source project * dedicated to delivering high quality video editing and animation solutions diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index e052bc63..b20f6b23 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2013 OpenShot Studios, LLC, Fabrice Bellard + * Copyright (c) 2008-2019 OpenShot Studios, LLC, Fabrice Bellard * (http://www.openshotstudios.com). This file is part of * OpenShot Library (http://www.openshot.org), an open-source project * dedicated to delivering high quality video editing and animation solutions diff --git a/src/ImageWriter.cpp b/src/ImageWriter.cpp index 1205a8ee..2cbd26d3 100644 --- a/src/ImageWriter.cpp +++ b/src/ImageWriter.cpp @@ -8,7 +8,7 @@ /* LICENSE * - * Copyright (c) 2008-2013 OpenShot Studios, LLC, Fabrice Bellard + * Copyright (c) 2008-2019 OpenShot Studios, LLC, Fabrice Bellard * (http://www.openshotstudios.com). This file is part of * OpenShot Library (http://www.openshot.org), an open-source project * dedicated to delivering high quality video editing and animation solutions From 7d8c1da2c283cd6c9a2fae840864ba1ff4830f3b Mon Sep 17 00:00:00 2001 From: Frank Dana Date: Wed, 12 Jun 2019 21:16:56 -0400 Subject: [PATCH 202/223] Doxyfile.in: Exclude all examples The source has been reorganized since the doxygen configs were last updated; there are more examples that should be excluded, and they're all now under `src/examples/`, so exclude the entire dir. --- Doxyfile.in | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Doxyfile.in b/Doxyfile.in index ba047c6b..a4be716d 100644 --- a/Doxyfile.in +++ b/Doxyfile.in @@ -797,8 +797,7 @@ EXCLUDE_SYMLINKS = NO EXCLUDE_PATTERNS = "*/.*" \ "*/.*/*" \ - "*/src/Main.cpp*" \ - "*/src/Main_Blackmagic.cpp*" \ + "*/src/examples/*" \ "*/src/bindings/*" \ "*.py" From df4fc4b05473599d44247346a0e4d071fb145a01 Mon Sep 17 00:00:00 2001 From: "FeRD (Frank Dana)" Date: Wed, 12 Jun 2019 21:20:37 -0400 Subject: [PATCH 203/223] Doxyfile.in: Remove doc/InstallationGuide.pdf No longer exists. --- Doxyfile.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doxyfile.in b/Doxyfile.in index a4be716d..48c74100 100644 --- a/Doxyfile.in +++ b/Doxyfile.in @@ -1083,7 +1083,7 @@ HTML_EXTRA_STYLESHEET = # files will be copied as-is; there are no commands or markers available. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_EXTRA_FILES = "doc/InstallationGuide.pdf" +HTML_EXTRA_FILES = # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the stylesheet and background images according to From 3ba6ba2a2908e47024330b9a7e0ce7891d411fe6 Mon Sep 17 00:00:00 2001 From: "FeRD (Frank Dana)" Date: Wed, 12 Jun 2019 21:21:58 -0400 Subject: [PATCH 204/223] Upgrade Doyfile.in Some arguments are obsolete with recent doxygen (and we never used them anyway), upgraded file with `doxygen -u` to remove. --- Doxyfile.in | 555 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 367 insertions(+), 188 deletions(-) diff --git a/Doxyfile.in b/Doxyfile.in index 48c74100..05af5ea1 100644 --- a/Doxyfile.in +++ b/Doxyfile.in @@ -1,4 +1,4 @@ -# Doxyfile 1.8.6 +# Doxyfile 1.8.15 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. @@ -17,11 +17,11 @@ # Project related configuration options #--------------------------------------------------------------------------- -# This tag specifies the encoding used for all characters in the config file -# that follow. The default is UTF-8 which is also the encoding used for all text -# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv -# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv -# for the list of possible encodings. +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. # The default value is: UTF-8. DOXYFILE_ENCODING = UTF-8 @@ -46,10 +46,10 @@ PROJECT_NUMBER = "@PROJECT_VERSION@" PROJECT_BRIEF = -# With the PROJECT_LOGO tag one can specify an logo or icon that is included in -# the documentation. The maximum height of the logo should not exceed 55 pixels -# and the maximum width should not exceed 200 pixels. Doxygen will copy the logo -# to the output directory. +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. PROJECT_LOGO = @@ -60,7 +60,7 @@ PROJECT_LOGO = OUTPUT_DIRECTORY = "@DOXYFILE_OUTPUT_DIR@" -# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub- +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- # directories (in 2 levels) under the output directory of each output format and # will distribute the generated files over these directories. Enabling this # option can be useful when feeding doxygen a huge amount of source files, where @@ -70,6 +70,14 @@ OUTPUT_DIRECTORY = "@DOXYFILE_OUTPUT_DIR@" CREATE_SUBDIRS = NO +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. @@ -85,14 +93,22 @@ CREATE_SUBDIRS = NO OUTPUT_LANGUAGE = English -# If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member +# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all generated output in the proper direction. +# Possible values are: None, LTR, RTL and Context. +# The default value is: None. + +OUTPUT_TEXT_DIRECTION = None + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member # descriptions after the members that are listed in the file and class # documentation (similar to Javadoc). Set to NO to disable this. # The default value is: YES. BRIEF_MEMBER_DESC = YES -# If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief # description of a member or function before the detailed description # # Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the @@ -127,7 +143,7 @@ ALWAYS_DETAILED_SEC = NO INLINE_INHERITED_MEMB = NO -# If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path # before files name in the file list and in the header files. If set to NO the # shortest path that makes the file name unique will be used # The default value is: YES. @@ -197,9 +213,9 @@ MULTILINE_CPP_IS_BRIEF = NO INHERIT_DOCS = YES -# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a -# new page for each member. If set to NO, the documentation of a member will be -# part of the file/class/namespace that contains it. +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. # The default value is: NO. SEPARATE_MEMBER_PAGES = NO @@ -218,7 +234,12 @@ TAB_SIZE = 8 # will allow you to put the command \sideeffect (or @sideeffect) in the # documentation, which will result in a user-defined paragraph with heading # "Side Effects:". You can put \n's in the value part of an alias to insert -# newlines. +# newlines (in the resulting output). You can put ^^ in the value part of an +# alias to insert a newline as if a physical newline was in the original file. +# When you need a literal { or } or , in the value part of an alias you have to +# escape them by means of a backslash (\), this can lead to conflicts with the +# commands \{ and \} for these it is advised to use the version @{ and @} or use +# a double escape (\\{ and \\}) ALIASES = @@ -256,16 +277,28 @@ OPTIMIZE_FOR_FORTRAN = NO OPTIMIZE_OUTPUT_VHDL = NO +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + # Doxygen selects the parser to use depending on the extension of the files it # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and # language is one of the parsers supported by doxygen: IDL, Java, Javascript, -# C#, C, C++, D, PHP, Objective-C, Python, Fortran, VHDL. For instance to make -# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C -# (default is Fortran), use: inc=Fortran f=C. +# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, +# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files), VHDL, tcl. For instance to make doxygen treat +# .inc files as Fortran files (default is PHP), and .f files as C (default is +# Fortran), use: inc=Fortran f=C. # -# Note For files without extension you can use no_extension as a placeholder. +# Note: For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise # the files are not read by doxygen. @@ -274,7 +307,7 @@ EXTENSION_MAPPING = # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments # according to the Markdown format, which allows for more readable -# documentation. See http://daringfireball.net/projects/markdown/ for details. +# documentation. See https://daringfireball.net/projects/markdown/ for details. # The output of markdown processing is further processed by doxygen, so you can # mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in # case of backward compatibilities issues. @@ -282,10 +315,19 @@ EXTENSION_MAPPING = MARKDOWN_SUPPORT = YES +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 0. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 0 + # When enabled doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can -# be prevented in individual cases by by putting a % sign in front of the word -# or globally by setting AUTOLINK_SUPPORT to NO. +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. # The default value is: YES. AUTOLINK_SUPPORT = YES @@ -307,7 +349,7 @@ BUILTIN_STL_SUPPORT = NO CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip (see: -# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen # will parse them like normal C++ but will assume all classes use public instead # of private inheritance when no explicit protection keyword is present. # The default value is: NO. @@ -325,13 +367,20 @@ SIP_SUPPORT = NO IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC -# tag is set to YES, then doxygen will reuse the documentation of the first +# tag is set to YES then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. # The default value is: NO. DISTRIBUTE_GROUP_DOC = NO +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + # Set the SUBGROUPING tag to YES to allow class member groups of the same type # (for instance a group of public functions) to be put as a subgroup of that # type (e.g. under the Public Functions section). Set it to NO to prevent @@ -390,7 +439,7 @@ LOOKUP_CACHE_SIZE = 0 # Build related configuration options #--------------------------------------------------------------------------- -# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in # documentation are documented, even if no documentation was available. Private # class members and static file members will be hidden unless the # EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. @@ -400,35 +449,35 @@ LOOKUP_CACHE_SIZE = 0 EXTRACT_ALL = YES -# If the EXTRACT_PRIVATE tag is set to YES all private members of a class will +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will # be included in the documentation. # The default value is: NO. EXTRACT_PRIVATE = NO -# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal # scope will be included in the documentation. # The default value is: NO. EXTRACT_PACKAGE = NO -# If the EXTRACT_STATIC tag is set to YES all static members of a file will be +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be # included in the documentation. # The default value is: NO. EXTRACT_STATIC = NO -# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined -# locally in source files will be included in the documentation. If set to NO +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, # only classes defined in header files are included. Does not have any effect # for Java sources. # The default value is: YES. EXTRACT_LOCAL_CLASSES = YES -# This flag is only useful for Objective-C code. When set to YES local methods, +# This flag is only useful for Objective-C code. If set to YES, local methods, # which are defined in the implementation section but not in the interface are -# included in the documentation. If set to NO only methods in the interface are +# included in the documentation. If set to NO, only methods in the interface are # included. # The default value is: NO. @@ -453,21 +502,21 @@ HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. If set -# to NO these classes will be included in the various overviews. This option has -# no effect if EXTRACT_ALL is enabled. +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend -# (class|struct|union) declarations. If set to NO these declarations will be +# (class|struct|union) declarations. If set to NO, these declarations will be # included in the documentation. # The default value is: NO. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any -# documentation blocks found inside the body of a function. If set to NO these +# documentation blocks found inside the body of a function. If set to NO, these # blocks will be appended to the function's detailed documentation block. # The default value is: NO. @@ -481,7 +530,7 @@ HIDE_IN_BODY_DOCS = NO INTERNAL_DOCS = NO # If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file -# names in lower-case letters. If set to YES upper-case letters are also +# names in lower-case letters. If set to YES, upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. @@ -490,12 +539,19 @@ INTERNAL_DOCS = NO CASE_SENSE_NAMES = YES # If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with -# their full class and namespace scopes in the documentation. If set to YES the +# their full class and namespace scopes in the documentation. If set to YES, the # scope will be hidden. # The default value is: NO. HIDE_SCOPE_NAMES = NO +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + # If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of # the files that are included by a file in the documentation of that file. # The default value is: YES. @@ -523,14 +579,14 @@ INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the # (detailed) documentation of file and class members alphabetically by member -# name. If set to NO the members will appear in declaration order. +# name. If set to NO, the members will appear in declaration order. # The default value is: YES. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief # descriptions of file, namespace and class members alphabetically by member -# name. If set to NO the members will appear in declaration order. Note that +# name. If set to NO, the members will appear in declaration order. Note that # this will also influence the order of the classes in the class list. # The default value is: NO. @@ -575,27 +631,25 @@ SORT_BY_SCOPE_NAME = NO STRICT_PROTO_MATCHING = NO -# The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the -# todo list. This list is created by putting \todo commands in the -# documentation. +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. # The default value is: YES. GENERATE_TODOLIST = YES -# The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the -# test list. This list is created by putting \test commands in the -# documentation. +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. # The default value is: YES. GENERATE_TESTLIST = YES -# The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug # list. This list is created by putting \bug commands in the documentation. # The default value is: YES. GENERATE_BUGLIST = YES -# The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO) +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) # the deprecated list. This list is created by putting \deprecated commands in # the documentation. # The default value is: YES. @@ -620,8 +674,8 @@ ENABLED_SECTIONS = MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated at -# the bottom of the documentation of classes and structs. If set to YES the list -# will mention the files that were used to generate the documentation. +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. # The default value is: YES. SHOW_USED_FILES = YES @@ -666,11 +720,10 @@ LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib # extension is automatically appended if omitted. This requires the bibtex tool -# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. # For LaTeX the style of the bibliography can be controlled using # LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the -# search path. Do not use file names with spaces, bibtex cannot handle them. See -# also \cite for info how to create references. +# search path. See also \cite for info how to create references. CITE_BIB_FILES = @@ -686,7 +739,7 @@ CITE_BIB_FILES = QUIET = YES # The WARNINGS tag can be used to turn on/off the warning messages that are -# generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES # this implies that the warnings are on. # # Tip: Turn warnings on while writing the documentation. @@ -694,7 +747,7 @@ QUIET = YES WARNINGS = YES -# If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate # warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag # will automatically be disabled. # The default value is: YES. @@ -711,12 +764,19 @@ WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that # are documented, but have no documentation for their parameters or return -# value. If set to NO doxygen will only warn about wrong or incomplete parameter -# documentation, but not about the absence of documentation. +# value. If set to NO, doxygen will only warn about wrong or incomplete +# parameter documentation, but not about the absence of documentation. If +# EXTRACT_ALL is set to YES then this flag will automatically be disabled. # The default value is: NO. WARN_NO_PARAMDOC = NO +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. +# The default value is: NO. + +WARN_AS_ERROR = NO + # The WARN_FORMAT tag determines the format of the warning messages that doxygen # can produce. The string should contain the $file, $line, and $text tags, which # will be replaced by the file and line number from which the warning originated @@ -740,7 +800,7 @@ WARN_LOGFILE = # The INPUT tag is used to specify the files and/or directories that contain # documented source files. You may enter file names like myfile.cpp or # directories like /usr/src/myproject. Separate the files or directories with -# spaces. +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. INPUT = "@CMAKE_CURRENT_SOURCE_DIR@/include" \ @@ -749,7 +809,7 @@ INPUT = "@CMAKE_CURRENT_SOURCE_DIR@/include" \ # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv -# documentation (see: http://www.gnu.org/software/libiconv) for the list of +# documentation (see: https://www.gnu.org/software/libiconv/) for the list of # possible encodings. # The default value is: UTF-8. @@ -757,12 +817,17 @@ INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and -# *.h) to filter out the source-files in the directories. If left blank the -# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii, -# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, -# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, -# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, -# *.qsf, *.as and *.js. +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, +# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. FILE_PATTERNS = @@ -852,6 +917,10 @@ IMAGE_PATH = "@CMAKE_CURRENT_SOURCE_DIR@" # Note that the filter must not add or remove lines; it is applied before the # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. INPUT_FILTER = @@ -861,11 +930,15 @@ INPUT_FILTER = # (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how # filters are used. If the FILTER_PATTERNS tag is empty or if none of the # patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using -# INPUT_FILTER ) will also be used to filter the input files that are used for +# INPUT_FILTER) will also be used to filter the input files that are used for # producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). # The default value is: NO. @@ -913,7 +986,7 @@ INLINE_SOURCES = NO STRIP_CODE_COMMENTS = NO # If the REFERENCED_BY_RELATION tag is set to YES then for each documented -# function all documented functions referencing it will be listed. +# entity all documented functions referencing it will be listed. # The default value is: NO. REFERENCED_BY_RELATION = NO @@ -925,7 +998,7 @@ REFERENCED_BY_RELATION = NO REFERENCES_RELATION = NO # If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set -# to YES, then the hyperlinks from functions in REFERENCES_RELATION and +# to YES then the hyperlinks from functions in REFERENCES_RELATION and # REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will # link to the documentation. # The default value is: YES. @@ -945,12 +1018,12 @@ SOURCE_TOOLTIPS = YES # If the USE_HTAGS tag is set to YES then the references to source code will # point to the HTML generated by the htags(1) tool instead of doxygen built-in # source browser. The htags tool is part of GNU's global source tagging system -# (see http://www.gnu.org/software/global/global.html). You will need version +# (see https://www.gnu.org/software/global/global.html). You will need version # 4.8.6 or higher. # # To use it do the following: # - Install the latest version of global -# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file # - Make sure the INPUT points to the root of the source tree # - Run doxygen as normal # @@ -1002,7 +1075,7 @@ IGNORE_PREFIX = # Configuration options related to the HTML output #--------------------------------------------------------------------------- -# If the GENERATE_HTML tag is set to YES doxygen will generate HTML output +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output # The default value is: YES. GENERATE_HTML = YES @@ -1064,13 +1137,15 @@ HTML_FOOTER = HTML_STYLESHEET = -# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional user- -# defined cascading style sheet that is included after the standard style sheets +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets # created by doxygen. Using this option one can overrule certain style aspects. # This is preferred over using HTML_STYLESHEET since it does not replace the -# standard style sheet and is therefor more robust against future updates. -# Doxygen will copy the style sheet file to the output directory. For an example -# see the documentation. +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_STYLESHEET = @@ -1083,12 +1158,12 @@ HTML_EXTRA_STYLESHEET = # files will be copied as-is; there are no commands or markers available. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_EXTRA_FILES = +HTML_EXTRA_FILES = # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen -# will adjust the colors in the stylesheet and background images according to +# will adjust the colors in the style sheet and background images according to # this color. Hue is specified as an angle on a colorwheel, see -# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 # purple, and 360 is red again. # Minimum value: 0, maximum value: 359, default value: 220. @@ -1117,12 +1192,24 @@ HTML_COLORSTYLE_GAMMA = 80 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML # page will contain the date and time when the page was generated. Setting this -# to NO can help when comparing the output of multiple runs. -# The default value is: YES. +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_TIMESTAMP = YES +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via Javascript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have Javascript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. @@ -1146,13 +1233,13 @@ HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files will be # generated that can be used as input for Apple's Xcode 3 integrated development -# environment (see: http://developer.apple.com/tools/xcode/), introduced with -# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a +# environment (see: https://developer.apple.com/xcode/), introduced with OSX +# 10.5 (Leopard). To create a documentation set, doxygen will generate a # Makefile in the HTML output directory. Running make will produce the docset in # that directory and running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at -# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html -# for more information. +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. @@ -1191,7 +1278,7 @@ DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop -# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on # Windows. # # The HTML Help Workshop contains a compiler that can convert all HTML output @@ -1214,28 +1301,29 @@ GENERATE_HTMLHELP = NO CHM_FILE = # The HHC_LOCATION tag can be used to specify the location (absolute path -# including file name) of the HTML help compiler ( hhc.exe). If non-empty +# including file name) of the HTML help compiler (hhc.exe). If non-empty, # doxygen will try to run the HTML help compiler on the generated index.hhp. # The file has to be specified with full path. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. HHC_LOCATION = -# The GENERATE_CHI flag controls if a separate .chi index file is generated ( -# YES) or that it should be included in the master .chm file ( NO). +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the master .chm file (NO). # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. GENERATE_CHI = NO -# The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc) +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) # and project file content. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_INDEX_ENCODING = -# The BINARY_TOC flag controls whether a binary table of contents is generated ( -# YES) or a normal table of contents ( NO) in the .chm file. +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. @@ -1266,7 +1354,7 @@ QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace -# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). +# (see: http://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1274,7 +1362,7 @@ QHP_NAMESPACE = # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt # Help Project output. For more information please see Qt Help Project / Virtual -# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- +# Folders (see: http://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual- # folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1283,7 +1371,7 @@ QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom -# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# Filters (see: http://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- # filters). # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1291,7 +1379,7 @@ QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom -# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# Filters (see: http://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- # filters). # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1299,7 +1387,7 @@ QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's filter section matches. Qt Help Project / Filter Attributes (see: -# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# http://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_SECT_FILTER_ATTRS = @@ -1348,7 +1436,7 @@ DISABLE_INDEX = NO # index structure (just like the one that is generated for HTML Help). For this # to work a browser that supports JavaScript, DHTML, CSS and frames is required # (i.e. any modern browser). Windows users are probably better off using the -# HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can # further fine-tune the look of the index. As an example, the default style # sheet generated by doxygen has an example that shows how to put an image at # the root of the tree instead of the PROJECT_NAME. Since the tree basically has @@ -1376,7 +1464,7 @@ ENUM_VALUES_PER_LINE = 4 TREEVIEW_WIDTH = 250 -# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to # external symbols imported via tag files in a separate window. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. @@ -1392,7 +1480,7 @@ EXT_LINKS_IN_WINDOW = NO FORMULA_FONTSIZE = 10 -# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# Use the FORMULA_TRANSPARENT tag to determine whether or not the images # generated for formulas are transparent PNGs. Transparent PNGs are not # supported properly for IE 6.0, but are supported on all modern browsers. # @@ -1404,8 +1492,8 @@ FORMULA_FONTSIZE = 10 FORMULA_TRANSPARENT = YES # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see -# http://www.mathjax.org) which uses client side Javascript for the rendering -# instead of using prerendered bitmaps. Use this if you do not have LaTeX +# https://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path # to it using the MATHJAX_RELPATH option. @@ -1431,8 +1519,8 @@ MATHJAX_FORMAT = HTML-CSS # MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of -# MathJax from http://www.mathjax.org before deployment. -# The default value is: http://cdn.mathjax.org/mathjax/latest. +# MathJax from https://www.mathjax.org before deployment. +# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest @@ -1475,11 +1563,11 @@ SEARCHENGINE = NO # When the SERVER_BASED_SEARCH tag is enabled the search engine will be # implemented using a web server instead of a web client using Javascript. There -# are two flavours of web server based searching depending on the -# EXTERNAL_SEARCH setting. When disabled, doxygen will generate a PHP script for -# searching and an index file used by the script. When EXTERNAL_SEARCH is -# enabled the indexing and searching needs to be provided by external tools. See -# the section "External Indexing and Searching" for details. +# are two flavors of web server based searching depending on the EXTERNAL_SEARCH +# setting. When disabled, doxygen will generate a PHP script for searching and +# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing +# and searching needs to be provided by external tools. See the section +# "External Indexing and Searching" for details. # The default value is: NO. # This tag requires that the tag SEARCHENGINE is set to YES. @@ -1491,9 +1579,9 @@ SERVER_BASED_SEARCH = NO # external search engine pointed to by the SEARCHENGINE_URL option to obtain the # search results. # -# Doxygen ships with an example indexer ( doxyindexer) and search engine +# Doxygen ships with an example indexer (doxyindexer) and search engine # (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: http://xapian.org/). +# Xapian (see: https://xapian.org/). # # See the section "External Indexing and Searching" for details. # The default value is: NO. @@ -1504,9 +1592,9 @@ EXTERNAL_SEARCH = NO # The SEARCHENGINE_URL should point to a search engine hosted by a web server # which will return the search results when EXTERNAL_SEARCH is enabled. # -# Doxygen ships with an example indexer ( doxyindexer) and search engine +# Doxygen ships with an example indexer (doxyindexer) and search engine # (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: http://xapian.org/). See the section "External Indexing and +# Xapian (see: https://xapian.org/). See the section "External Indexing and # Searching" for details. # This tag requires that the tag SEARCHENGINE is set to YES. @@ -1542,7 +1630,7 @@ EXTRA_SEARCH_MAPPINGS = # Configuration options related to the LaTeX output #--------------------------------------------------------------------------- -# If the GENERATE_LATEX tag is set to YES doxygen will generate LaTeX output. +# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output. # The default value is: YES. GENERATE_LATEX = @DOXYFILE_GENERATE_LATEX@ @@ -1558,22 +1646,35 @@ LATEX_OUTPUT = "@DOXYFILE_LATEX_DIR@" # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be # invoked. # -# Note that when enabling USE_PDFLATEX this option is only used for generating -# bitmaps for formulas in the HTML output, but not in the Makefile that is -# written to the output directory. -# The default file is: latex. +# Note that when not enabling USE_PDFLATEX the default is latex when enabling +# USE_PDFLATEX the default is pdflatex and when in the later case latex is +# chosen this is overwritten by pdflatex. For specific output languages the +# default can have been set differently, this depends on the implementation of +# the output language. # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_CMD_NAME = "@LATEX_COMPILER@" # The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate # index for LaTeX. +# Note: This tag is used in the Makefile / make.bat. +# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file +# (.tex). # The default file is: makeindex. # This tag requires that the tag GENERATE_LATEX is set to YES. MAKEINDEX_CMD_NAME = "@MAKEINDEX_COMPILER@" -# If the COMPACT_LATEX tag is set to YES doxygen generates more compact LaTeX +# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to +# generate index for LaTeX. +# Note: This tag is used in the generated output file (.tex). +# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat. +# The default value is: \makeindex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_MAKEINDEX_CMD = \makeindex + +# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX # documents. This may be useful for small projects and may help to save some # trees in general. # The default value is: NO. @@ -1591,9 +1692,12 @@ COMPACT_LATEX = NO PAPER_TYPE = a4wide # The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names -# that should be included in the LaTeX output. To get the times font for -# instance you can specify -# EXTRA_PACKAGES=times +# that should be included in the LaTeX output. The package can be specified just +# by its name or with the correct syntax as to be used with the LaTeX +# \usepackage command. To get the times font for instance you can specify : +# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times} +# To use the option intlimits with the amsmath package you can specify: +# EXTRA_PACKAGES=[intlimits]{amsmath} # If left blank no extra packages will be included. # This tag requires that the tag GENERATE_LATEX is set to YES. @@ -1607,23 +1711,36 @@ EXTRA_PACKAGES = # # Note: Only use a user-defined header if you know what you are doing! The # following commands have a special meaning inside the header: $title, -# $datetime, $date, $doxygenversion, $projectname, $projectnumber. Doxygen will -# replace them by respectively the title of the page, the current date and time, -# only the current date, the version number of doxygen, the project name (see -# PROJECT_NAME), or the project number (see PROJECT_NUMBER). +# $datetime, $date, $doxygenversion, $projectname, $projectnumber, +# $projectbrief, $projectlogo. Doxygen will replace $title with the empty +# string, for the replacement values of the other commands the user is referred +# to HTML_HEADER. # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_HEADER = # The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the # generated LaTeX document. The footer should contain everything after the last -# chapter. If it is left blank doxygen will generate a standard footer. +# chapter. If it is left blank doxygen will generate a standard footer. See +# LATEX_HEADER for more information on how to generate a default footer and what +# special commands can be used inside the footer. # # Note: Only use a user-defined footer if you know what you are doing! # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_FOOTER = +# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# LaTeX style sheets that are included after the standard style sheets created +# by doxygen. Using this option one can overrule certain style aspects. Doxygen +# will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EXTRA_STYLESHEET = + # The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the LATEX_OUTPUT output # directory. Note that the files will be copied as-is; there are no commands or @@ -1641,8 +1758,8 @@ LATEX_EXTRA_FILES = PDF_HYPERLINKS = YES -# If the LATEX_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate -# the PDF file directly from the LaTeX files. Set this option to YES to get a +# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate +# the PDF file directly from the LaTeX files. Set this option to YES, to get a # higher quality PDF documentation. # The default value is: YES. # This tag requires that the tag GENERATE_LATEX is set to YES. @@ -1677,17 +1794,33 @@ LATEX_SOURCE_CODE = NO # The LATEX_BIB_STYLE tag can be used to specify the style to use for the # bibliography, e.g. plainnat, or ieeetr. See -# http://en.wikipedia.org/wiki/BibTeX and \cite for more info. +# https://en.wikipedia.org/wiki/BibTeX and \cite for more info. # The default value is: plain. # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_BIB_STYLE = plain +# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated +# page will contain the date and time when the page was generated. Setting this +# to NO can help when comparing the output of multiple runs. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_TIMESTAMP = NO + +# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute) +# path from which the emoji images will be read. If a relative path is entered, +# it will be relative to the LATEX_OUTPUT directory. If left blank the +# LATEX_OUTPUT directory will be used. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EMOJI_DIRECTORY = + #--------------------------------------------------------------------------- # Configuration options related to the RTF output #--------------------------------------------------------------------------- -# If the GENERATE_RTF tag is set to YES doxygen will generate RTF output. The +# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The # RTF output is optimized for Word 97 and may not look too pretty with other RTF # readers/editors. # The default value is: NO. @@ -1702,7 +1835,7 @@ GENERATE_RTF = NO RTF_OUTPUT = rtf -# If the COMPACT_RTF tag is set to YES doxygen generates more compact RTF +# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF # documents. This may be useful for small projects and may help to save some # trees in general. # The default value is: NO. @@ -1722,9 +1855,9 @@ COMPACT_RTF = NO RTF_HYPERLINKS = NO -# Load stylesheet definitions from file. Syntax is similar to doxygen's config -# file, i.e. a series of assignments. You only have to provide replacements, -# missing definitions are set to their default value. +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# configuration file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. # # See also section "Doxygen usage" for information on how to generate the # default style sheet that doxygen normally uses. @@ -1733,17 +1866,27 @@ RTF_HYPERLINKS = NO RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an RTF document. Syntax is -# similar to doxygen's config file. A template extensions file can be generated -# using doxygen -e rtf extensionFile. +# similar to doxygen's configuration file. A template extensions file can be +# generated using doxygen -e rtf extensionFile. # This tag requires that the tag GENERATE_RTF is set to YES. RTF_EXTENSIONS_FILE = +# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code +# with syntax highlighting in the RTF output. +# +# Note that which sources are shown also depends on other settings such as +# SOURCE_BROWSER. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_SOURCE_CODE = NO + #--------------------------------------------------------------------------- # Configuration options related to the man page output #--------------------------------------------------------------------------- -# If the GENERATE_MAN tag is set to YES doxygen will generate man pages for +# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for # classes and files. # The default value is: NO. @@ -1767,6 +1910,13 @@ MAN_OUTPUT = man MAN_EXTENSION = .3 +# The MAN_SUBDIR tag determines the name of the directory created within +# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by +# MAN_EXTENSION with the initial . removed. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_SUBDIR = + # If the MAN_LINKS tag is set to YES and doxygen generates man output, then it # will generate one additional man file for each entity documented in the real # man page(s). These additional files only source the real man page, but without @@ -1780,7 +1930,7 @@ MAN_LINKS = NO # Configuration options related to the XML output #--------------------------------------------------------------------------- -# If the GENERATE_XML tag is set to YES doxygen will generate an XML file that +# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that # captures the structure of the code including all documentation. # The default value is: NO. @@ -1794,19 +1944,7 @@ GENERATE_XML = NO XML_OUTPUT = xml -# The XML_SCHEMA tag can be used to specify a XML schema, which can be used by a -# validating XML parser to check the syntax of the XML files. -# This tag requires that the tag GENERATE_XML is set to YES. - -XML_SCHEMA = - -# The XML_DTD tag can be used to specify a XML DTD, which can be used by a -# validating XML parser to check the syntax of the XML files. -# This tag requires that the tag GENERATE_XML is set to YES. - -XML_DTD = - -# If the XML_PROGRAMLISTING tag is set to YES doxygen will dump the program +# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program # listings (including syntax highlighting and cross-referencing information) to # the XML output. Note that enabling this will significantly increase the size # of the XML output. @@ -1815,11 +1953,18 @@ XML_DTD = XML_PROGRAMLISTING = YES +# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include +# namespace members in file scope as well, matching the HTML output. +# The default value is: NO. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_NS_MEMB_FILE_SCOPE = NO + #--------------------------------------------------------------------------- # Configuration options related to the DOCBOOK output #--------------------------------------------------------------------------- -# If the GENERATE_DOCBOOK tag is set to YES doxygen will generate Docbook files +# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files # that can be used to generate PDF. # The default value is: NO. @@ -1833,14 +1978,23 @@ GENERATE_DOCBOOK = NO DOCBOOK_OUTPUT = docbook +# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the +# program listings (including syntax highlighting and cross-referencing +# information) to the DOCBOOK output. Note that enabling this will significantly +# increase the size of the DOCBOOK output. +# The default value is: NO. +# This tag requires that the tag GENERATE_DOCBOOK is set to YES. + +DOCBOOK_PROGRAMLISTING = NO + #--------------------------------------------------------------------------- # Configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- -# If the GENERATE_AUTOGEN_DEF tag is set to YES doxygen will generate an AutoGen -# Definitions (see http://autogen.sf.net) file that captures the structure of -# the code including all documentation. Note that this feature is still -# experimental and incomplete at the moment. +# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an +# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures +# the structure of the code including all documentation. Note that this feature +# is still experimental and incomplete at the moment. # The default value is: NO. GENERATE_AUTOGEN_DEF = NO @@ -1849,7 +2003,7 @@ GENERATE_AUTOGEN_DEF = NO # Configuration options related to the Perl module output #--------------------------------------------------------------------------- -# If the GENERATE_PERLMOD tag is set to YES doxygen will generate a Perl module +# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module # file that captures the structure of the code including all documentation. # # Note that this feature is still experimental and incomplete at the moment. @@ -1857,7 +2011,7 @@ GENERATE_AUTOGEN_DEF = NO GENERATE_PERLMOD = NO -# If the PERLMOD_LATEX tag is set to YES doxygen will generate the necessary +# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary # Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI # output from the Perl module output. # The default value is: NO. @@ -1865,9 +2019,9 @@ GENERATE_PERLMOD = NO PERLMOD_LATEX = NO -# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be nicely +# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely # formatted so it can be parsed by a human reader. This is useful if you want to -# understand what is going on. On the other hand, if this tag is set to NO the +# understand what is going on. On the other hand, if this tag is set to NO, the # size of the Perl module output will be much smaller and Perl will parse it # just the same. # The default value is: YES. @@ -1887,14 +2041,14 @@ PERLMOD_MAKEVAR_PREFIX = # Configuration options related to the preprocessor #--------------------------------------------------------------------------- -# If the ENABLE_PREPROCESSING tag is set to YES doxygen will evaluate all +# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all # C-preprocessor directives found in the sources and include files. # The default value is: YES. ENABLE_PREPROCESSING = YES -# If the MACRO_EXPANSION tag is set to YES doxygen will expand all macro names -# in the source code. If set to NO only conditional compilation will be +# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names +# in the source code. If set to NO, only conditional compilation will be # performed. Macro expansion can be done in a controlled way by setting # EXPAND_ONLY_PREDEF to YES. # The default value is: NO. @@ -1910,7 +2064,7 @@ MACRO_EXPANSION = NO EXPAND_ONLY_PREDEF = NO -# If the SEARCH_INCLUDES tag is set to YES the includes files in the +# If the SEARCH_INCLUDES tag is set to YES, the include files in the # INCLUDE_PATH will be searched if a #include is found. # The default value is: YES. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. @@ -1952,9 +2106,9 @@ PREDEFINED = EXPAND_AS_DEFINED = # If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will -# remove all refrences to function-like macros that are alone on a line, have an -# all uppercase name, and do not end with a semicolon. Such function macros are -# typically used for boiler-plate code, and will confuse the parser if not +# remove all references to function-like macros that are alone on a line, have +# an all uppercase name, and do not end with a semicolon. Such function macros +# are typically used for boiler-plate code, and will confuse the parser if not # removed. # The default value is: YES. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. @@ -1974,7 +2128,7 @@ SKIP_FUNCTION_MACROS = YES # where loc1 and loc2 can be relative or absolute paths or URLs. See the # section "Linking to external documentation" for more information about the use # of tag files. -# Note: Each tag file must have an unique name (where the name does NOT include +# Note: Each tag file must have a unique name (where the name does NOT include # the path). If a tag file is not located in the directory in which doxygen is # run, you must also specify the path to the tagfile here. @@ -1986,20 +2140,21 @@ TAGFILES = GENERATE_TAGFILE = -# If the ALLEXTERNALS tag is set to YES all external class will be listed in the -# class index. If set to NO only the inherited external classes will be listed. +# If the ALLEXTERNALS tag is set to YES, all external class will be listed in +# the class index. If set to NO, only the inherited external classes will be +# listed. # The default value is: NO. ALLEXTERNALS = NO -# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed in -# the modules index. If set to NO, only the current project's groups will be +# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will be # listed. # The default value is: YES. EXTERNAL_GROUPS = YES -# If the EXTERNAL_PAGES tag is set to YES all external pages will be listed in +# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in # the related pages index. If set to NO, only the current project's pages will # be listed. # The default value is: YES. @@ -2016,7 +2171,7 @@ PERL_PATH = /usr/bin/perl # Configuration options related to the dot tool #--------------------------------------------------------------------------- -# If the CLASS_DIAGRAMS tag is set to YES doxygen will generate a class diagram +# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram # (in HTML and LaTeX) for classes with base or super classes. Setting the tag to # NO turns the diagrams off. Note that this option also works with HAVE_DOT # disabled, but it is recommended to install and use dot, since it yields more @@ -2041,7 +2196,7 @@ MSCGEN_PATH = DIA_PATH = -# If set to YES, the inheritance and collaboration graphs will hide inheritance +# If set to YES the inheritance and collaboration graphs will hide inheritance # and usage relations if the target is undocumented or is not a class. # The default value is: YES. @@ -2066,7 +2221,7 @@ HAVE_DOT = @DOXYFILE_DOT@ DOT_NUM_THREADS = 0 -# When you want a differently looking font n the dot files that doxygen +# When you want a differently looking font in the dot files that doxygen # generates you can specify the font name using DOT_FONTNAME. You need to make # sure dot is able to find the font, which can be done by putting it in a # standard location or by setting the DOTFONTPATH environment variable or by @@ -2074,7 +2229,7 @@ DOT_NUM_THREADS = 0 # The default value is: Helvetica. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_FONTNAME = +DOT_FONTNAME = # The DOT_FONTSIZE tag can be used to set the size (in points) of the font of # dot graphs. @@ -2114,7 +2269,7 @@ COLLABORATION_GRAPH = YES GROUP_GRAPHS = YES -# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and # collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. # The default value is: NO. @@ -2166,7 +2321,8 @@ INCLUDED_BY_GRAPH = YES # # Note that enabling this option will significantly increase the time of a run. # So in most cases it will be better to enable call graphs for selected -# functions only using the \callgraph command. +# functions only using the \callgraph command. Disabling a call graph can be +# accomplished by means of the command \hidecallgraph. # The default value is: NO. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2177,7 +2333,8 @@ CALL_GRAPH = NO # # Note that enabling this option will significantly increase the time of a run. # So in most cases it will be better to enable caller graphs for selected -# functions only using the \callergraph command. +# functions only using the \callergraph command. Disabling a caller graph can be +# accomplished by means of the command \hidecallergraph. # The default value is: NO. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2200,11 +2357,15 @@ GRAPHICAL_HIERARCHY = YES DIRECTORY_GRAPH = YES # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images -# generated by dot. +# generated by dot. For an explanation of the image formats see the section +# output formats in the documentation of the dot tool (Graphviz (see: +# http://www.graphviz.org/)). # Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order # to make the SVG files visible in IE 9+ (other browsers do not have this # requirement). -# Possible values are: png, jpg, gif and svg. +# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo, +# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and +# png:gdiplus:gdiplus. # The default value is: png. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2247,6 +2408,24 @@ MSCFILE_DIRS = DIAFILE_DIRS = +# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the +# path where java can find the plantuml.jar file. If left blank, it is assumed +# PlantUML is not used or called during a preprocessing step. Doxygen will +# generate a warning when it encounters a \startuml command in this case and +# will not generate output for the diagram. + +PLANTUML_JAR_PATH = + +# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a +# configuration file for plantuml. + +PLANTUML_CFG_FILE = + +# When using plantuml, the specified paths are searched for files specified by +# the !include statement in a plantuml block. + +PLANTUML_INCLUDE_PATH = + # The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes # that will be shown in the graph. If the number of nodes in a graph becomes # larger than this value, doxygen will truncate the graph, which is visualized @@ -2283,7 +2462,7 @@ MAX_DOT_GRAPH_DEPTH = 0 DOT_TRANSPARENT = YES -# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This # makes dot run faster, but since only newer versions of dot (>1.8.10) support # this, this feature is disabled by default. @@ -2300,7 +2479,7 @@ DOT_MULTI_TARGETS = NO GENERATE_LEGEND = YES -# If the DOT_CLEANUP tag is set to YES doxygen will remove the intermediate dot +# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot # files that are used to generate the various graphs. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. From 5292661dec36df8f0f2d69ba6915059f6dc72b7f Mon Sep 17 00:00:00 2001 From: "FeRD (Frank Dana)" Date: Wed, 12 Jun 2019 21:26:18 -0400 Subject: [PATCH 205/223] Also remove install guide ref from OpenShot.h --- include/OpenShot.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/include/OpenShot.h b/include/OpenShot.h index 8987f5e5..c3fde052 100644 --- a/include/OpenShot.h +++ b/include/OpenShot.h @@ -64,10 +64,6 @@ * canvas (i.e. pan & scan). * \image html /doc/images/Timeline_Layers.png * - * ### Build Instructions (Linux, Mac, and Windows) ### - * For a step-by-step guide to building / compiling libopenshot, check out the - * Official Installation Guide. - * * ### Want to Learn More? ### * To continue learning about libopenshot, take a look at the full list of classes available. * From 4455f77bd93d4cd5dd62f05cb380ae575cc3ffcd Mon Sep 17 00:00:00 2001 From: "FeRD (Frank Dana)" Date: Wed, 12 Jun 2019 21:27:53 -0400 Subject: [PATCH 206/223] Crop.h: Remove nonexistent color argument Doxygen caught this one: the default constructor for Crop() doesn't take an argument 'color', though it was documented to. --- include/effects/Crop.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/effects/Crop.h b/include/effects/Crop.h index 7921a78d..39779f07 100644 --- a/include/effects/Crop.h +++ b/include/effects/Crop.h @@ -69,7 +69,6 @@ namespace openshot /// Default constructor, which takes 4 curves. These curves animate the crop over time. /// - /// @param color The curve to adjust the color of bars /// @param left The curve to adjust the left bar size (between 0 and 1) /// @param top The curve to adjust the top bar size (between 0 and 1) /// @param right The curve to adjust the right bar size (between 0 and 1) From 7319201e37b1b82da2832d8280328532dfc7724e Mon Sep 17 00:00:00 2001 From: "FeRD (Frank Dana)" Date: Wed, 12 Jun 2019 22:44:05 -0400 Subject: [PATCH 207/223] Doygen: Include doc/*.md in docs Also include the documentation MarkDown pages in the Doxygen docs. These will show up as Related Pages in the interface. * The formatting of `INSTALL-*.md` had to be changed some: - Doxygen doesn't support headings in bulleted lists (lines starting with `* ###`) -- I'm not even sure that's legal markdown. They were changed to just level 3 headings (lines starting with `###`). - ALL Windows paths in `INSTALL-WINDOWS.md` were wrapped in backticks, to prevent Doxygen parsing them as markup commands. - Level 1 headings were added to the top of the three install docs, giving them the title "Building libopenshot for ___(OS)___". Otherwise all three pages were titled "Getting Started". * Separately, the table at the top of `HW-ACCEL.md` does not translate well to Doxygen. It will need further polishing. But the docs are all quite readable now. --- Doxyfile.in | 4 ++- doc/INSTALL-LINUX.md | 38 ++++++++++++++------------ doc/INSTALL-MAC.md | 38 ++++++++++++++------------ doc/INSTALL-WINDOWS.md | 62 ++++++++++++++++++++++-------------------- 4 files changed, 75 insertions(+), 67 deletions(-) diff --git a/Doxyfile.in b/Doxyfile.in index 05af5ea1..4d4649c6 100644 --- a/Doxyfile.in +++ b/Doxyfile.in @@ -804,7 +804,9 @@ WARN_LOGFILE = # Note: If this tag is empty the current directory is searched. INPUT = "@CMAKE_CURRENT_SOURCE_DIR@/include" \ - "@CMAKE_CURRENT_SOURCE_DIR@/src" + "@CMAKE_CURRENT_SOURCE_DIR@/src" \ + "@CMAKE_CURRENT_SOURCE_DIR@/doc" + # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses diff --git a/doc/INSTALL-LINUX.md b/doc/INSTALL-LINUX.md index 6ed4f3f6..2a0bbe1b 100644 --- a/doc/INSTALL-LINUX.md +++ b/doc/INSTALL-LINUX.md @@ -1,3 +1,5 @@ +# Building libopenshot for Linux + ## Getting Started The best way to get started with libopenshot, is to learn about our build system, obtain all the source code, @@ -22,47 +24,47 @@ The following libraries are required to build libopenshot. Instructions on how dependencies vary for each operating system. Libraries and Executables have been labeled in the list below to help distinguish between them. -* ### FFmpeg (libavformat, libavcodec, libavutil, libavdevice, libavresample, libswscale) +### FFmpeg (libavformat, libavcodec, libavutil, libavdevice, libavresample, libswscale) * http://www.ffmpeg.org/ `(Library)` * This library is used to decode and encode video, audio, and image files. It is also used to obtain information about media files, such as frame rate, sample rate, aspect ratio, and other common attributes. -* ### ImageMagick++ (libMagick++, libMagickWand, libMagickCore) +### ImageMagick++ (libMagick++, libMagickWand, libMagickCore) * http://www.imagemagick.org/script/magick++.php `(Library)` * This library is **optional**, and used to decode and encode images. -* ### OpenShot Audio Library (libopenshot-audio) +### OpenShot Audio Library (libopenshot-audio) * https://github.com/OpenShot/libopenshot-audio/ `(Library)` * This library is used to mix, resample, host plug-ins, and play audio. It is based on the JUCE project, which is an outstanding audio library used by many different applications -* ### Qt 5 (libqt5) +### Qt 5 (libqt5) * http://www.qt.io/qt5/ `(Library)` * Qt5 is used to display video, store image data, composite images, apply image effects, and many other utility functions, such as file system manipulation, high resolution timers, etc... -* ### CMake (cmake) +### CMake (cmake) * http://www.cmake.org/ `(Executable)` * This executable is used to automate the generation of Makefiles, check for dependencies, and is the backbone of libopenshot’s cross-platform build process. -* ### SWIG (swig) +### SWIG (swig) * http://www.swig.org/ `(Executable)` * This executable is used to generate the Python and Ruby bindings for libopenshot. It is a simple and powerful wrapper for C++ libraries, and supports many languages. -* ### Python 3 (libpython) +### Python 3 (libpython) * http://www.python.org/ `(Executable and Library)` * This library is used by swig to create the Python (version 3+) bindings for libopenshot. This is also the official language used by OpenShot Video Editor (a graphical interface to libopenshot). -* ### Doxygen (doxygen) +### Doxygen (doxygen) * http://www.stack.nl/~dimitri/doxygen/ `(Executable)` * This executable is used to auto-generate the documentation used by libopenshot. -* ### UnitTest++ (libunittest++) +### UnitTest++ (libunittest++) * https://github.com/unittest-cpp/ `(Library)` * This library is used to execute unit tests for libopenshot. It contains many macros used to keep our unit testing code very clean and simple. -* ### ZeroMQ (libzmq) +### ZeroMQ (libzmq) * http://zeromq.org/ `(Library)` * This library is used to communicate between libopenshot and other applications (publisher / subscriber). Primarily used to send debug data from libopenshot. -* ### OpenMP (-fopenmp) +### OpenMP (-fopenmp) * http://openmp.org/wp/ `(Compiler Flag)` * If your compiler supports this flag (GCC, Clang, and most other compilers), it provides libopenshot with easy methods of using parallel programming techniques to improve performance and take advantage of multi-core processors. @@ -99,25 +101,25 @@ git clone https://github.com/OpenShot/libopenshot-audio.git The source code is divided up into the following folders. -* ### build/ +### build/ * This folder needs to be manually created, and is used by cmake to store the temporary build files, such as makefiles, as well as the final binaries (library and test executables). -* ### cmake/ +### cmake/ * This folder contains custom modules not included by default in cmake, used to find dependency libraries and headers and determine if these libraries are installed. -* ### doc/ +### doc/ * This folder contains documentation and related files, such as logos and images required by the doxygen auto-generated documentation. -* ### include/ +### include/ * This folder contains all headers (*.h) used by libopenshot. -* ### src/ +### src/ * This folder contains all source code (*.cpp) used by libopenshot. -* ### tests/ +### tests/ * This folder contains all unit test code. Each class has it’s own test file (*.cpp), and uses UnitTest++ macros to keep the test code simple and manageable. -* ### thirdparty/ +### thirdparty/ * This folder contains code not written by the OpenShot team. For example, jsoncpp, an open-source JSON parser. ## Install Dependencies diff --git a/doc/INSTALL-MAC.md b/doc/INSTALL-MAC.md index ab7f79c3..ac0c7f7c 100644 --- a/doc/INSTALL-MAC.md +++ b/doc/INSTALL-MAC.md @@ -1,3 +1,5 @@ +# Building libopenshot for MacOS + ## Getting Started The best way to get started with libopenshot, is to learn about our build system, obtain all the source code, @@ -22,47 +24,47 @@ The following libraries are required to build libopenshot. Instructions on how dependencies vary for each operating system. Libraries and Executables have been labeled in the list below to help distinguish between them. -* ### FFmpeg (libavformat, libavcodec, libavutil, libavdevice, libavresample, libswscale) +### FFmpeg (libavformat, libavcodec, libavutil, libavdevice, libavresample, libswscale) * http://www.ffmpeg.org/ `(Library)` * This library is used to decode and encode video, audio, and image files. It is also used to obtain information about media files, such as frame rate, sample rate, aspect ratio, and other common attributes. -* ### ImageMagick++ (libMagick++, libMagickWand, libMagickCore) +### ImageMagick++ (libMagick++, libMagickWand, libMagickCore) * http://www.imagemagick.org/script/magick++.php `(Library)` * This library is **optional**, and used to decode and encode images. -* ### OpenShot Audio Library (libopenshot-audio) +### OpenShot Audio Library (libopenshot-audio) * https://github.com/OpenShot/libopenshot-audio/ `(Library)` * This library is used to mix, resample, host plug-ins, and play audio. It is based on the JUCE project, which is an outstanding audio library used by many different applications -* ### Qt 5 (libqt5) +### Qt 5 (libqt5) * http://www.qt.io/qt5/ `(Library)` * Qt5 is used to display video, store image data, composite images, apply image effects, and many other utility functions, such as file system manipulation, high resolution timers, etc... -* ### CMake (cmake) +### CMake (cmake) * http://www.cmake.org/ `(Executable)` * This executable is used to automate the generation of Makefiles, check for dependencies, and is the backbone of libopenshot’s cross-platform build process. -* ### SWIG (swig) +### SWIG (swig) * http://www.swig.org/ `(Executable)` * This executable is used to generate the Python and Ruby bindings for libopenshot. It is a simple and powerful wrapper for C++ libraries, and supports many languages. -* ### Python 3 (libpython) +### Python 3 (libpython) * http://www.python.org/ `(Executable and Library)` * This library is used by swig to create the Python (version 3+) bindings for libopenshot. This is also the official language used by OpenShot Video Editor (a graphical interface to libopenshot). -* ### Doxygen (doxygen) +### Doxygen (doxygen) * http://www.stack.nl/~dimitri/doxygen/ `(Executable)` * This executable is used to auto-generate the documentation used by libopenshot. -* ### UnitTest++ (libunittest++) +### UnitTest++ (libunittest++) * https://github.com/unittest-cpp/ `(Library)` * This library is used to execute unit tests for libopenshot. It contains many macros used to keep our unit testing code very clean and simple. -* ### ZeroMQ (libzmq) +### ZeroMQ (libzmq) * http://zeromq.org/ `(Library)` * This library is used to communicate between libopenshot and other applications (publisher / subscriber). Primarily used to send debug data from libopenshot. -* ### OpenMP (-fopenmp) +### OpenMP (-fopenmp) * http://openmp.org/wp/ `(Compiler Flag)` * If your compiler supports this flag (GCC, Clang, and most other compilers), it provides libopenshot with easy methods of using parallel programming techniques to improve performance and take advantage of multi-core processors. @@ -98,25 +100,25 @@ git clone https://github.com/OpenShot/libopenshot-audio.git The source code is divided up into the following folders. -* ### build/ +### build/ * This folder needs to be manually created, and is used by cmake to store the temporary build files, such as makefiles, as well as the final binaries (library and test executables). -* ### cmake/ +### cmake/ * This folder contains custom modules not included by default in cmake, used to find dependency libraries and headers and determine if these libraries are installed. -* ### doc/ +### doc/ * This folder contains documentation and related files, such as logos and images required by the doxygen auto-generated documentation. -* ### include/ +### include/ * This folder contains all headers (*.h) used by libopenshot. -* ### src/ +### src/ * This folder contains all source code (*.cpp) used by libopenshot. -* ### tests/ +### tests/ * This folder contains all unit test code. Each class has it’s own test file (*.cpp), and uses UnitTest++ macros to keep the test code simple and manageable. -* ### thirdparty/ +### thirdparty/ * This folder contains code not written by the OpenShot team. For example, jsoncpp, an open-source JSON parser. ## Install Dependencies diff --git a/doc/INSTALL-WINDOWS.md b/doc/INSTALL-WINDOWS.md index 7f5b8f78..2b17569b 100644 --- a/doc/INSTALL-WINDOWS.md +++ b/doc/INSTALL-WINDOWS.md @@ -1,3 +1,5 @@ +# Building libopenshot for Windows + ## Getting Started The best way to get started with libopenshot, is to learn about our build system, obtain all the @@ -24,47 +26,47 @@ The following libraries are required to build libopenshot. Instructions on how install these dependencies vary for each operating system. Libraries and Executables have been labeled in the list below to help distinguish between them. -* ### FFmpeg (libavformat, libavcodec, libavutil, libavdevice, libavresample, libswscale) +### FFmpeg (libavformat, libavcodec, libavutil, libavdevice, libavresample, libswscale) * http://www.ffmpeg.org/ `(Library)` * This library is used to decode and encode video, audio, and image files. It is also used to obtain information about media files, such as frame rate, sample rate, aspect ratio, and other common attributes. -* ### ImageMagick++ (libMagick++, libMagickWand, libMagickCore) +### ImageMagick++ (libMagick++, libMagickWand, libMagickCore) * http://www.imagemagick.org/script/magick++.php `(Library)` * This library is **optional**, and used to decode and encode images. -* ### OpenShot Audio Library (libopenshot-audio) +### OpenShot Audio Library (libopenshot-audio) * https://github.com/OpenShot/libopenshot-audio/ `(Library)` * This library is used to mix, resample, host plug-ins, and play audio. It is based on the JUCE project, which is an outstanding audio library used by many different applications -* ### Qt 5 (libqt5) +### Qt 5 (libqt5) * http://www.qt.io/qt5/ `(Library)` * Qt5 is used to display video, store image data, composite images, apply image effects, and many other utility functions, such as file system manipulation, high resolution timers, etc... -* ### CMake (cmake) +### CMake (cmake) * http://www.cmake.org/ `(Executable)` * This executable is used to automate the generation of Makefiles, check for dependencies, and is the backbone of libopenshot’s cross-platform build process. -* ### SWIG (swig) +### SWIG (swig) * http://www.swig.org/ `(Executable)` * This executable is used to generate the Python and Ruby bindings for libopenshot. It is a simple and powerful wrapper for C++ libraries, and supports many languages. -* ### Python 3 (libpython) +### Python 3 (libpython) * http://www.python.org/ `(Executable and Library)` * This library is used by swig to create the Python (version 3+) bindings for libopenshot. This is also the official language used by OpenShot Video Editor (a graphical interface to libopenshot). -* ### Doxygen (doxygen) +### Doxygen (doxygen) * http://www.stack.nl/~dimitri/doxygen/ `(Executable)` * This executable is used to auto-generate the documentation used by libopenshot. -* ### UnitTest++ (libunittest++) +### UnitTest++ (libunittest++) * https://github.com/unittest-cpp/ `(Library)` * This library is used to execute unit tests for libopenshot. It contains many macros used to keep our unit testing code very clean and simple. -* ### ZeroMQ (libzmq) +### ZeroMQ (libzmq) * http://zeromq.org/ `(Library)` * This library is used to communicate between libopenshot and other applications (publisher / subscriber). Primarily used to send debug data from libopenshot. -* ### OpenMP (-fopenmp) +### OpenMP (-fopenmp) * http://openmp.org/wp/ `(Compiler Flag)` * If your compiler supports this flag (GCC, Clang, and most other compilers), it provides libopenshot with easy methods of using parallel programming techniques to improve performance and take advantage of multi-core processors. @@ -109,7 +111,7 @@ check each folder path for accuracy, as your paths will likely be different than * UNITTEST_DIR (`C:\UnitTest++`) * ZMQDIR (`C:\msys2\usr\local\`) * PATH (`The following paths are an example`) - * C:\Qt5\bin; C:\Qt5\MinGW\bin\; C:\msys\1.0\local\lib; C:\Program Files\CMake 2.8\bin; C:\UnitTest++\build; C:\libopenshot\build\src; C:\Program Files\doxygen\bin; C:\ffmpeg-git-95f163b-win32-dev\lib; C:\swigwin-2.0.4; C:\Python33; C:\Program Files\Project\lib; C:\msys2\usr\local\ + * `C:\Qt5\bin; C:\Qt5\MinGW\bin\; C:\msys\1.0\local\lib; C:\Program Files\CMake 2.8\bin; C:\UnitTest++\build; C:\libopenshot\build\src; C:\Program Files\doxygen\bin; C:\ffmpeg-git-95f163b-win32-dev\lib; C:\swigwin-2.0.4; C:\Python33; C:\Program Files\Project\lib; C:\msys2\usr\local\` @@ -130,29 +132,29 @@ git clone https://github.com/OpenShot/libopenshot-audio.git The source code is divided up into the following folders. -* ### build/ +### build/ * This folder needs to be manually created, and is used by cmake to store the temporary build files, such as makefiles, as well as the final binaries (library and test executables). -* ### cmake/ +### cmake/ * This folder contains custom modules not included by default in cmake, used to find dependency libraries and headers and determine if these libraries are installed. -* ### doc/ +### doc/ * This folder contains documentation and related files, such as logos and images required by the doxygen auto-generated documentation. -* ### include/ +### include/ * This folder contains all headers (*.h) used by libopenshot. -* ### src/ +### src/ * This folder contains all source code (*.cpp) used by libopenshot. -* ### tests/ +### tests/ * This folder contains all unit test code. Each class has it’s own test file (*.cpp), and uses UnitTest++ macros to keep the test code simple and manageable. -* ### thirdparty/ +### thirdparty/ * This folder contains code not written by the OpenShot team. For example, jsoncpp, an open-source JSON parser. @@ -240,28 +242,28 @@ mingw32-make install ## Manual Dependencies -* ### DLfcn +### DLfcn * https://github.com/dlfcn-win32/dlfcn-win32 - * Download and Extract the Win32 Static (.tar.bz2) archive to a local folder: C:\libdl\ - * Create an environment variable called DL_DIR and set the value to C:\libdl\. This environment variable will be used by CMake to find the binary and header file. + * Download and Extract the Win32 Static (.tar.bz2) archive to a local folder: `C:\libdl\` + * Create an environment variable called DL_DIR and set the value to `C:\libdl\`. This environment variable will be used by CMake to find the binary and header file. -* ### DirectX SDK / Windows SDK +### DirectX SDK / Windows SDK * Windows 7: (DirectX SDK) http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=6812 * Windows 8: (Windows SDK) * https://msdn.microsoft.com/en-us/windows/desktop/aa904949 * Download and Install the SDK Setup program. This is needed for the JUCE library to play audio on Windows. -Create an environment variable called DXSDK_DIR and set the value to C:\Program Files\Microsoft DirectX SDK (June 2010)\ (your path might be different). This environment variable will be used by CMake to find the binaries and header files. +Create an environment variable called DXSDK_DIR and set the value to `C:\Program Files\Microsoft DirectX SDK (June 2010)\` (your path might be different). This environment variable will be used by CMake to find the binaries and header files. -* ### libSndFile +### libSndFile * http://www.mega-nerd.com/libsndfile/#Download * Download and Install the Win32 Setup program. - * Create an environment variable called SNDFILE_DIR and set the value to C:\Program Files\libsndfile. This environment variable will be used by CMake to find the binary and header files. + * Create an environment variable called SNDFILE_DIR and set the value to `C:\Program Files\libsndfile`. This environment variable will be used by CMake to find the binary and header files. -* ### libzmq +### libzmq * http://zeromq.org/intro:get-the-software * Download source code (zip) * Follow their instructions, and build with mingw - * Create an environment variable called ZMQDIR and set the value to C:\libzmq\build\ (the location of the compiled version). This environment variable will be used by CMake to find the binary and header files. + * Create an environment variable called ZMQDIR and set the value to `C:\libzmq\build\` (the location of the compiled version). This environment variable will be used by CMake to find the binary and header files. ## Windows Build Instructions (libopenshot-audio) In order to compile libopenshot-audio, launch a command prompt and enter the following commands. This does not require the MSYS2 prompt, but it should work in both the Windows command prompt and the MSYS2 prompt. @@ -314,8 +316,8 @@ built, we need to install it (i.e. copy it to the correct folder, so other libra mingw32-make install ``` -This should copy the binary files to C:\Program Files\openshot\lib\, and the header -files to C:\Program Files\openshot\include\... This is where other projects will +This should copy the binary files to `C:\Program Files\openshot\lib\`, and the header +files to `C:\Program Files\openshot\include\...` This is where other projects will look for the libopenshot files when building.. Python 3 bindings are also installed at this point. let's verify the python bindings work: From 0dcbc20921bbb180679f7fc94cce3413826b1071 Mon Sep 17 00:00:00 2001 From: "FeRD (Frank Dana)" Date: Wed, 12 Jun 2019 22:55:54 -0400 Subject: [PATCH 208/223] Doxygen docs: Link to install docs This adds references to all three `INSTALL-*.md` documents to the main page text in `include/OpenShot.h`, replacing the previous link to the PDF. --- include/OpenShot.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/OpenShot.h b/include/OpenShot.h index c3fde052..fb53164c 100644 --- a/include/OpenShot.h +++ b/include/OpenShot.h @@ -64,6 +64,12 @@ * canvas (i.e. pan & scan). * \image html /doc/images/Timeline_Layers.png * + * ### Build Instructions ### + * Build instructions are available for all three major Operating Systems: + * * [Building libopenshot for Windows](doc/INSTALL-WINDOWS.md) + * * [Building libopenshot for MacOS](doc/INSTALL-MAC.md) + * * [Building libopenshot for Linux](doc/INSTALL-LINUX.md) + * * ### Want to Learn More? ### * To continue learning about libopenshot, take a look at the full list of classes available. * From 55f26a226dbfcd1e0a82270fe4b4f8402d542286 Mon Sep 17 00:00:00 2001 From: "FeRD (Frank Dana)" Date: Thu, 13 Jun 2019 00:06:01 -0400 Subject: [PATCH 209/223] Doxyfile.in: Switch on referenced-by linking --- Doxyfile.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doxyfile.in b/Doxyfile.in index 4d4649c6..3499d643 100644 --- a/Doxyfile.in +++ b/Doxyfile.in @@ -991,7 +991,7 @@ STRIP_CODE_COMMENTS = NO # entity all documented functions referencing it will be listed. # The default value is: NO. -REFERENCED_BY_RELATION = NO +REFERENCED_BY_RELATION = YES # If the REFERENCES_RELATION tag is set to YES then for each documented function # all documented entities called/used by that function will be listed. From 95aca48831cdc1672054530c7de194c4fda4a48d Mon Sep 17 00:00:00 2001 From: "FeRD (Frank Dana)" Date: Thu, 13 Jun 2019 06:54:16 -0400 Subject: [PATCH 210/223] Fix some bugs in UseDoxygen.cmake There was a bug in the definition of `DOXYFILE_LATEX`, and _another_ in the processing of it (which were the only reason it wasn't defaulting on, the way it appeared to be configured), fixed the bug and changed it to default OFF. But now it _can_ be enabled. Also moved the handling of `DOXYFILE_DOT` out of the latex-only section, so that it can be turned on with the new config variable `DOXYFILE_USE_DOT` (default on). If `DOXYFILE_USE_DOT` is enabled and the `dot` executable is found, `DOXYFILE_DOT` will be set "YES" and `dot` will be used for the HTML as well, giving better graphs. --- cmake/Modules/UseDoxygen.cmake | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/cmake/Modules/UseDoxygen.cmake b/cmake/Modules/UseDoxygen.cmake index b261d431..807dbac6 100644 --- a/cmake/Modules/UseDoxygen.cmake +++ b/cmake/Modules/UseDoxygen.cmake @@ -105,12 +105,18 @@ if(DOXYGEN_FOUND AND DOXYFILE_IN_FOUND) STRING "Additional source files/directories separated by space") set(DOXYFILE_SOURE_DIRS "\"${DOXYFILE_SOURCE_DIR}\" ${DOXYFILE_EXTRA_SOURCES}") - usedoxygen_set_default(DOXYFILE_LATEX YES BOOL "Generate LaTeX API documentation" OFF) + usedoxygen_set_default(DOXYFILE_LATEX OFF BOOL "Generate LaTeX API documentation") usedoxygen_set_default(DOXYFILE_LATEX_DIR "latex" STRING "LaTex output directory") mark_as_advanced(DOXYFILE_OUTPUT_DIR DOXYFILE_HTML_DIR DOXYFILE_LATEX_DIR DOXYFILE_SOURCE_DIR DOXYFILE_EXTRA_SOURCE_DIRS DOXYFILE_IN) + ## Dot + usedoxygen_set_default(DOXYFILE_USE_DOT ON BOOL "Use dot (part of graphviz) to generate graphs") + set(DOXYFILE_DOT "NO") + if(DOXYFILE_USE_DOT AND DOXYGEN_DOT_EXECUTABLE) + set(DOXYFILE_DOT "YES") + endif() set_property(DIRECTORY APPEND PROPERTY @@ -125,13 +131,12 @@ if(DOXYGEN_FOUND AND DOXYFILE_IN_FOUND) ## LaTeX set(DOXYFILE_PDFLATEX "NO") - set(DOXYFILE_DOT "NO") set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES "${DOXYFILE_OUTPUT_DIR}/${DOXYFILE_LATEX_DIR}") - if(DOXYFILE_LATEX STREQUAL "ON") + if(DOXYFILE_LATEX) set(DOXYFILE_GENERATE_LATEX "YES") find_package(LATEX) find_program(DOXYFILE_MAKE make) @@ -140,9 +145,6 @@ if(DOXYGEN_FOUND AND DOXYFILE_IN_FOUND) if(PDFLATEX_COMPILER) set(DOXYFILE_PDFLATEX "YES") endif() - if(DOXYGEN_DOT_EXECUTABLE) - set(DOXYFILE_DOT "YES") - endif() add_custom_command(TARGET doxygen POST_BUILD From 26090c2f0c9c98753ef4ac6811fc2096742256f4 Mon Sep 17 00:00:00 2001 From: "FeRD (Frank Dana)" Date: Thu, 13 Jun 2019 06:57:54 -0400 Subject: [PATCH 211/223] Set the dot graphs to generate as interactive SVG --- Doxyfile.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doxyfile.in b/Doxyfile.in index 3499d643..7b54a9c0 100644 --- a/Doxyfile.in +++ b/Doxyfile.in @@ -2371,7 +2371,7 @@ DIRECTORY_GRAPH = YES # The default value is: png. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_IMAGE_FORMAT = png +DOT_IMAGE_FORMAT = svg # If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to # enable generation of interactive SVG images that allow zooming and panning. @@ -2383,7 +2383,7 @@ DOT_IMAGE_FORMAT = png # The default value is: NO. # This tag requires that the tag HAVE_DOT is set to YES. -INTERACTIVE_SVG = NO +INTERACTIVE_SVG = YES # The DOT_PATH tag can be used to specify the path where the dot tool can be # found. If left blank, it is assumed the dot tool can be found in the path. From eab81b0b7d0873869dd0bdd40ea4113172007620 Mon Sep 17 00:00:00 2001 From: "FeRD (Frank Dana)" Date: Wed, 19 Jun 2019 20:58:43 -0400 Subject: [PATCH 212/223] Upgrade jsoncpp to 1.8.4 This current version supports the new `Json::CharReader`, which replaces the now-deprecated `Json::Reader`. It's necessary to eliminate `Json::Reader` from libopenshot because using it causes a flood of deprecation warnings when building with system jsoncpp. Also, the `thirdparty/jsoncpp` directory was generated using the jsoncpp project's `amalgamate.py` tool, which condenses the library down to a single C++ source file and two headers, making it easier to incorporate into libopenshot. --- src/CMakeLists.txt | 6 +- thirdparty/jsoncpp/LICENSE | 56 +- thirdparty/jsoncpp/include/json/autolink.h | 19 - thirdparty/jsoncpp/include/json/config.h | 43 - thirdparty/jsoncpp/include/json/features.h | 42 - thirdparty/jsoncpp/include/json/forwards.h | 39 - thirdparty/jsoncpp/include/json/json.h | 10 - thirdparty/jsoncpp/include/json/reader.h | 196 - thirdparty/jsoncpp/include/json/value.h | 1069 ---- thirdparty/jsoncpp/include/json/writer.h | 174 - thirdparty/jsoncpp/json/json-forwards.h | 320 + thirdparty/jsoncpp/json/json.h | 2346 +++++++ thirdparty/jsoncpp/jsoncpp.cpp | 5406 +++++++++++++++++ .../src/lib_json/json_batchallocator.h | 125 - .../src/lib_json/json_internalarray.inl | 448 -- .../jsoncpp/src/lib_json/json_internalmap.inl | 607 -- .../jsoncpp/src/lib_json/json_reader.cpp | 883 --- .../jsoncpp/src/lib_json/json_value.cpp | 1717 ------ .../src/lib_json/json_valueiterator.inl | 292 - .../jsoncpp/src/lib_json/json_writer.cpp | 828 --- thirdparty/jsoncpp/src/lib_json/sconscript | 8 - 21 files changed, 8129 insertions(+), 6505 deletions(-) delete mode 100644 thirdparty/jsoncpp/include/json/autolink.h delete mode 100644 thirdparty/jsoncpp/include/json/config.h delete mode 100644 thirdparty/jsoncpp/include/json/features.h delete mode 100644 thirdparty/jsoncpp/include/json/forwards.h delete mode 100644 thirdparty/jsoncpp/include/json/json.h delete mode 100644 thirdparty/jsoncpp/include/json/reader.h delete mode 100644 thirdparty/jsoncpp/include/json/value.h delete mode 100644 thirdparty/jsoncpp/include/json/writer.h create mode 100644 thirdparty/jsoncpp/json/json-forwards.h create mode 100644 thirdparty/jsoncpp/json/json.h create mode 100644 thirdparty/jsoncpp/jsoncpp.cpp delete mode 100644 thirdparty/jsoncpp/src/lib_json/json_batchallocator.h delete mode 100644 thirdparty/jsoncpp/src/lib_json/json_internalarray.inl delete mode 100644 thirdparty/jsoncpp/src/lib_json/json_internalmap.inl delete mode 100644 thirdparty/jsoncpp/src/lib_json/json_reader.cpp delete mode 100644 thirdparty/jsoncpp/src/lib_json/json_value.cpp delete mode 100644 thirdparty/jsoncpp/src/lib_json/json_valueiterator.inl delete mode 100644 thirdparty/jsoncpp/src/lib_json/json_writer.cpp delete mode 100644 thirdparty/jsoncpp/src/lib_json/sconscript diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d793ffa2..7aa16720 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -205,7 +205,7 @@ if (USE_SYSTEM_JSONCPP) message(STATUS "Discovering system JsonCPP - done") else() message(STATUS "Using embedded JsonCpp (USE_SYSTEM_JSONCPP not enabled)") - include_directories("../thirdparty/jsoncpp/include") + include_directories("../thirdparty/jsoncpp") endif(USE_SYSTEM_JSONCPP) ############### PROFILING ################# @@ -262,9 +262,7 @@ SET ( OPENSHOT_SOURCE_FILES IF (NOT USE_SYSTEM_JSONCPP) # Third Party JSON Parser SET ( OPENSHOT_SOURCE_FILES ${OPENSHOT_SOURCE_FILES} - ../thirdparty/jsoncpp/src/lib_json/json_reader.cpp - ../thirdparty/jsoncpp/src/lib_json/json_value.cpp - ../thirdparty/jsoncpp/src/lib_json/json_writer.cpp) + ../thirdparty/jsoncpp/jsoncpp.cpp ) ENDIF (NOT USE_SYSTEM_JSONCPP) # ImageMagic related files diff --git a/thirdparty/jsoncpp/LICENSE b/thirdparty/jsoncpp/LICENSE index d20fb29a..89280a6c 100644 --- a/thirdparty/jsoncpp/LICENSE +++ b/thirdparty/jsoncpp/LICENSE @@ -1 +1,55 @@ -The json-cpp library and this documentation are in Public Domain. +The JsonCpp library's source code, including accompanying documentation, +tests and demonstration applications, are licensed under the following +conditions... + +Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all +jurisdictions which recognize such a disclaimer. In such jurisdictions, +this software is released into the Public Domain. + +In jurisdictions which do not recognize Public Domain property (e.g. Germany as of +2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur and +The JsonCpp Authors, and is released under the terms of the MIT License (see below). + +In jurisdictions which recognize Public Domain property, the user of this +software may choose to accept it either as 1) Public Domain, 2) under the +conditions of the MIT License (see below), or 3) under the terms of dual +Public Domain/MIT License conditions described here, as they choose. + +The MIT License is about as close to Public Domain as a license can get, and is +described in clear, concise terms at: + + http://en.wikipedia.org/wiki/MIT_License + +The full text of the MIT License follows: + +======================================================================== +Copyright (c) 2007-2010 Baptiste Lepilleur and The JsonCpp Authors + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, +modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +======================================================================== +(END LICENSE TEXT) + +The MIT license is compatible with both the GPL and commercial +software, affording one all of the rights of Public Domain with the +minor nuisance of being required to keep the above copyright notice +and license text in the source code. Note also that by accepting the +Public Domain "license" you can re-license your copy using whatever +license you like. diff --git a/thirdparty/jsoncpp/include/json/autolink.h b/thirdparty/jsoncpp/include/json/autolink.h deleted file mode 100644 index 37c9258e..00000000 --- a/thirdparty/jsoncpp/include/json/autolink.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef JSON_AUTOLINK_H_INCLUDED -# define JSON_AUTOLINK_H_INCLUDED - -# include "config.h" - -# ifdef JSON_IN_CPPTL -# include -# endif - -# if !defined(JSON_NO_AUTOLINK) && !defined(JSON_DLL_BUILD) && !defined(JSON_IN_CPPTL) -# define CPPTL_AUTOLINK_NAME "json" -# undef CPPTL_AUTOLINK_DLL -# ifdef JSON_DLL -# define CPPTL_AUTOLINK_DLL -# endif -# include "autolink.h" -# endif - -#endif // JSON_AUTOLINK_H_INCLUDED diff --git a/thirdparty/jsoncpp/include/json/config.h b/thirdparty/jsoncpp/include/json/config.h deleted file mode 100644 index 5d334cbc..00000000 --- a/thirdparty/jsoncpp/include/json/config.h +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef JSON_CONFIG_H_INCLUDED -# define JSON_CONFIG_H_INCLUDED - -/// If defined, indicates that json library is embedded in CppTL library. -//# define JSON_IN_CPPTL 1 - -/// If defined, indicates that json may leverage CppTL library -//# define JSON_USE_CPPTL 1 -/// If defined, indicates that cpptl vector based map should be used instead of std::map -/// as Value container. -//# define JSON_USE_CPPTL_SMALLMAP 1 -/// If defined, indicates that Json specific container should be used -/// (hash table & simple deque container with customizable allocator). -/// THIS FEATURE IS STILL EXPERIMENTAL! -//# define JSON_VALUE_USE_INTERNAL_MAP 1 -/// Force usage of standard new/malloc based allocator instead of memory pool based allocator. -/// The memory pools allocator used optimization (initializing Value and ValueInternalLink -/// as if it was a POD) that may cause some validation tool to report errors. -/// Only has effects if JSON_VALUE_USE_INTERNAL_MAP is defined. -//# define JSON_USE_SIMPLE_INTERNAL_ALLOCATOR 1 - -/// If defined, indicates that Json use exception to report invalid type manipulation -/// instead of C assert macro. -# define JSON_USE_EXCEPTION 1 - -# ifdef JSON_IN_CPPTL -# include -# ifndef JSON_USE_CPPTL -# define JSON_USE_CPPTL 1 -# endif -# endif - -# ifdef JSON_IN_CPPTL -# define JSON_API CPPTL_API -# elif defined(JSON_DLL_BUILD) -# define JSON_API __declspec(dllexport) -# elif defined(JSON_DLL) -# define JSON_API __declspec(dllimport) -# else -# define JSON_API -# endif - -#endif // JSON_CONFIG_H_INCLUDED diff --git a/thirdparty/jsoncpp/include/json/features.h b/thirdparty/jsoncpp/include/json/features.h deleted file mode 100644 index ac25b887..00000000 --- a/thirdparty/jsoncpp/include/json/features.h +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef CPPTL_JSON_FEATURES_H_INCLUDED -# define CPPTL_JSON_FEATURES_H_INCLUDED - -# include "forwards.h" - -namespace Json { - - /** @brief Configuration passed to reader and writer. - * This configuration object can be used to force the Reader or Writer - * to behave in a standard conforming way. - */ - class JSON_API Features - { - public: - /** @brief A configuration that allows all features and assumes all strings are UTF-8. - * - C & C++ comments are allowed - * - Root object can be any JSON value - * - Assumes Value strings are encoded in UTF-8 - */ - static Features all(); - - /** @brief A configuration that is strictly compatible with the JSON specification. - * - Comments are forbidden. - * - Root object must be either an array or an object value. - * - Assumes Value strings are encoded in UTF-8 - */ - static Features strictMode(); - - /** @brief Initialize the configuration like JsonConfig::allFeatures; - */ - Features(); - - /// \c true if comments are allowed. Default: \c true. - bool allowComments_; - - /// \c true if root must be either an array or an object value. Default: \c false. - bool strictRoot_; - }; - -} // namespace Json - -#endif // CPPTL_JSON_FEATURES_H_INCLUDED diff --git a/thirdparty/jsoncpp/include/json/forwards.h b/thirdparty/jsoncpp/include/json/forwards.h deleted file mode 100644 index d0ce8300..00000000 --- a/thirdparty/jsoncpp/include/json/forwards.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef JSON_FORWARDS_H_INCLUDED -# define JSON_FORWARDS_H_INCLUDED - -# include "config.h" - -namespace Json { - - // writer.h - class FastWriter; - class StyledWriter; - - // reader.h - class Reader; - - // features.h - class Features; - - // value.h - typedef int Int; - typedef unsigned int UInt; - class StaticString; - class Path; - class PathArgument; - class Value; - class ValueIteratorBase; - class ValueIterator; - class ValueConstIterator; -#ifdef JSON_VALUE_USE_INTERNAL_MAP - class ValueAllocator; - class ValueMapAllocator; - class ValueInternalLink; - class ValueInternalArray; - class ValueInternalMap; -#endif // #ifdef JSON_VALUE_USE_INTERNAL_MAP - -} // namespace Json - - -#endif // JSON_FORWARDS_H_INCLUDED diff --git a/thirdparty/jsoncpp/include/json/json.h b/thirdparty/jsoncpp/include/json/json.h deleted file mode 100644 index c71ed65a..00000000 --- a/thirdparty/jsoncpp/include/json/json.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef JSON_JSON_H_INCLUDED -# define JSON_JSON_H_INCLUDED - -# include "autolink.h" -# include "value.h" -# include "reader.h" -# include "writer.h" -# include "features.h" - -#endif // JSON_JSON_H_INCLUDED diff --git a/thirdparty/jsoncpp/include/json/reader.h b/thirdparty/jsoncpp/include/json/reader.h deleted file mode 100644 index 95da3767..00000000 --- a/thirdparty/jsoncpp/include/json/reader.h +++ /dev/null @@ -1,196 +0,0 @@ -#ifndef CPPTL_JSON_READER_H_INCLUDED -# define CPPTL_JSON_READER_H_INCLUDED - -# include "features.h" -# include "value.h" -# include -# include -# include -# include - -namespace Json { - - /** @brief Unserialize a JSON document into a Value. - * - */ - class JSON_API Reader - { - public: - typedef char Char; - typedef const Char *Location; - - /** @brief Constructs a Reader allowing all features - * for parsing. - */ - Reader(); - - /** @brief Constructs a Reader allowing the specified feature set - * for parsing. - */ - Reader( const Features &features ); - - /** @brief Read a Value from a JSON document. - * \param document UTF-8 encoded string containing the document to read. - * \param root [out] Contains the root value of the document if it was - * successfully parsed. - * \param collectComments \c true to collect comment and allow writing them back during - * serialization, \c false to discard comments. - * This parameter is ignored if Features::allowComments_ - * is \c false. - * \return \c true if the document was successfully parsed, \c false if an error occurred. - */ - bool parse( const std::string &document, - Value &root, - bool collectComments = true ); - - /** @brief Read a Value from a JSON document. - * \param document UTF-8 encoded string containing the document to read. - * \param root [out] Contains the root value of the document if it was - * successfully parsed. - * \param collectComments \c true to collect comment and allow writing them back during - * serialization, \c false to discard comments. - * This parameter is ignored if Features::allowComments_ - * is \c false. - * \return \c true if the document was successfully parsed, \c false if an error occurred. - */ - bool parse( const char *beginDoc, const char *endDoc, - Value &root, - bool collectComments = true ); - - /// @brief Parse from input stream. - /// \see Json::operator>>(std::istream&, Json::Value&). - bool parse( std::istream &is, - Value &root, - bool collectComments = true ); - - /** @brief Returns a user friendly string that list errors in the parsed document. - * \return Formatted error message with the list of errors with their location in - * the parsed document. An empty string is returned if no error occurred - * during parsing. - */ - std::string getFormatedErrorMessages() const; - - private: - enum TokenType - { - tokenEndOfStream = 0, - tokenObjectBegin, - tokenObjectEnd, - tokenArrayBegin, - tokenArrayEnd, - tokenString, - tokenNumber, - tokenTrue, - tokenFalse, - tokenNull, - tokenArraySeparator, - tokenMemberSeparator, - tokenComment, - tokenError - }; - - class Token - { - public: - TokenType type_; - Location start_; - Location end_; - }; - - class ErrorInfo - { - public: - Token token_; - std::string message_; - Location extra_; - }; - - typedef std::deque Errors; - - bool expectToken( TokenType type, Token &token, const char *message ); - bool readToken( Token &token ); - void skipSpaces(); - bool match( Location pattern, - int patternLength ); - bool readComment(); - bool readCStyleComment(); - bool readCppStyleComment(); - bool readString(); - void readNumber(); - bool readValue(); - bool readObject( Token &token ); - bool readArray( Token &token ); - bool decodeNumber( Token &token ); - bool decodeString( Token &token ); - bool decodeString( Token &token, std::string &decoded ); - bool decodeDouble( Token &token ); - bool decodeUnicodeCodePoint( Token &token, - Location ¤t, - Location end, - unsigned int &unicode ); - bool decodeUnicodeEscapeSequence( Token &token, - Location ¤t, - Location end, - unsigned int &unicode ); - bool addError( const std::string &message, - Token &token, - Location extra = 0 ); - bool recoverFromError( TokenType skipUntilToken ); - bool addErrorAndRecover( const std::string &message, - Token &token, - TokenType skipUntilToken ); - void skipUntilSpace(); - Value ¤tValue(); - Char getNextChar(); - void getLocationLineAndColumn( Location location, - int &line, - int &column ) const; - std::string getLocationLineAndColumn( Location location ) const; - void addComment( Location begin, - Location end, - CommentPlacement placement ); - void skipCommentTokens( Token &token ); - - typedef std::stack Nodes; - Nodes nodes_; - Errors errors_; - std::string document_; - Location begin_; - Location end_; - Location current_; - Location lastValueEnd_; - Value *lastValue_; - std::string commentsBefore_; - Features features_; - bool collectComments_; - }; - - /** @brief Read from 'sin' into 'root'. - - Always keep comments from the input JSON. - - This can be used to read a file into a particular sub-object. - For example: - \code - Json::Value root; - cin >> root["dir"]["file"]; - cout << root; - \endcode - Result: - \verbatim - { - "dir": { - "file": { - // The input stream JSON would be nested here. - } - } - } - \endverbatim - \throw std::exception on parse error. - \see Json::operator<<() - */ - std::istream& operator>>( std::istream&, Value& ); - -} // namespace Json - -#endif // CPPTL_JSON_READER_H_INCLUDED diff --git a/thirdparty/jsoncpp/include/json/value.h b/thirdparty/jsoncpp/include/json/value.h deleted file mode 100644 index 7e56ded6..00000000 --- a/thirdparty/jsoncpp/include/json/value.h +++ /dev/null @@ -1,1069 +0,0 @@ -#ifndef CPPTL_JSON_H_INCLUDED -# define CPPTL_JSON_H_INCLUDED - -# include "forwards.h" -# include -# include - -# ifndef JSON_USE_CPPTL_SMALLMAP -# include -# else -# include -# endif -# ifdef JSON_USE_CPPTL -# include -# endif - -/** @brief JSON (JavaScript Object Notation). - */ -namespace Json { - - /** @brief Type of the value held by a Value object. - */ - enum ValueType - { - nullValue = 0, ///< 'null' value - intValue, ///< signed integer value - uintValue, ///< unsigned integer value - realValue, ///< double value - stringValue, ///< UTF-8 string value - booleanValue, ///< bool value - arrayValue, ///< array value (ordered list) - objectValue ///< object value (collection of name/value pairs). - }; - - enum CommentPlacement - { - commentBefore = 0, ///< a comment placed on the line before a value - commentAfterOnSameLine, ///< a comment just after a value on the same line - commentAfter, ///< a comment on the line after a value (only make sense for root value) - numberOfCommentPlacement - }; - -//# ifdef JSON_USE_CPPTL -// typedef CppTL::AnyEnumerator EnumMemberNames; -// typedef CppTL::AnyEnumerator EnumValues; -//# endif - - /** @brief Lightweight wrapper to tag static string. - * - * Value constructor and objectValue member assignement takes advantage of the - * StaticString and avoid the cost of string duplication when storing the - * string or the member name. - * - * Example of usage: - * \code - * Json::Value aValue( StaticString("some text") ); - * Json::Value object; - * static const StaticString code("code"); - * object[code] = 1234; - * \endcode - */ - class JSON_API StaticString - { - public: - explicit StaticString( const char *czstring ) - : str_( czstring ) - { - } - - operator const char *() const - { - return str_; - } - - const char *c_str() const - { - return str_; - } - - private: - const char *str_; - }; - - /** @brief Represents a JSON value. - * - * This class is a discriminated union wrapper that can represents a: - * - signed integer [range: Value::minInt - Value::maxInt] - * - unsigned integer (range: 0 - Value::maxUInt) - * - double - * - UTF-8 string - * - boolean - * - 'null' - * - an ordered list of Value - * - collection of name/value pairs (javascript object) - * - * The type of the held value is represented by a #ValueType and - * can be obtained using type(). - * - * values of an #objectValue or #arrayValue can be accessed using operator[]() methods. - * Non const methods will automatically create the a #nullValue element - * if it does not exist. - * The sequence of an #arrayValue will be automatically resize and initialized - * with #nullValue. resize() can be used to enlarge or truncate an #arrayValue. - * - * The get() methods can be used to obtanis default value in the case the required element - * does not exist. - * - * It is possible to iterate over the list of a #objectValue values using - * the getMemberNames() method. - */ - class JSON_API Value - { - friend class ValueIteratorBase; -# ifdef JSON_VALUE_USE_INTERNAL_MAP - friend class ValueInternalLink; - friend class ValueInternalMap; -# endif - public: - typedef std::vector Members; - typedef ValueIterator iterator; - typedef ValueConstIterator const_iterator; - typedef Json::UInt UInt; - typedef Json::Int Int; - typedef UInt ArrayIndex; - - static const Value null; - static const Int minInt; - static const Int maxInt; - static const UInt maxUInt; - - private: -#ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION -# ifndef JSON_VALUE_USE_INTERNAL_MAP - class CZString - { - public: - enum DuplicationPolicy - { - noDuplication = 0, - duplicate, - duplicateOnCopy - }; - CZString( int index ); - CZString( const char *cstr, DuplicationPolicy allocate ); - CZString( const CZString &other ); - ~CZString(); - CZString &operator =( const CZString &other ); - bool operator<( const CZString &other ) const; - bool operator==( const CZString &other ) const; - int index() const; - const char *c_str() const; - bool isStaticString() const; - private: - void swap( CZString &other ); - const char *cstr_; - int index_; - }; - - public: -# ifndef JSON_USE_CPPTL_SMALLMAP - typedef std::map ObjectValues; -# else - typedef CppTL::SmallMap ObjectValues; -# endif // ifndef JSON_USE_CPPTL_SMALLMAP -# endif // ifndef JSON_VALUE_USE_INTERNAL_MAP -#endif // ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION - - public: - /** @brief Create a default Value of the given type. - - This is a very useful constructor. - To create an empty array, pass arrayValue. - To create an empty object, pass objectValue. - Another Value can then be set to this one by assignment. - This is useful since clear() and resize() will not alter types. - - Examples: - \code - Json::Value null_value; // null - Json::Value arr_value(Json::arrayValue); // [] - Json::Value obj_value(Json::objectValue); // {} - \endcode - */ - Value( ValueType type = nullValue ); - Value( Int value ); - Value( UInt value ); - Value( double value ); - Value( const char *value ); - Value( const char *beginValue, const char *endValue ); - /** @brief Constructs a value from a static string. - - * Like other value string constructor but do not duplicate the string for - * internal storage. The given string must remain alive after the call to this - * constructor. - * Example of usage: - * \code - * Json::Value aValue( StaticString("some text") ); - * \endcode - */ - Value( const StaticString &value ); - Value( const std::string &value ); -# ifdef JSON_USE_CPPTL - Value( const CppTL::ConstString &value ); -# endif - Value( bool value ); - Value( const Value &other ); - ~Value(); - - Value &operator=( const Value &other ); - /// Swap values. - /// \note Currently, comments are intentionally not swapped, for - /// both logic and efficiency. - void swap( Value &other ); - - ValueType type() const; - - bool operator <( const Value &other ) const; - bool operator <=( const Value &other ) const; - bool operator >=( const Value &other ) const; - bool operator >( const Value &other ) const; - - bool operator ==( const Value &other ) const; - bool operator !=( const Value &other ) const; - - int compare( const Value &other ); - - const char *asCString() const; - std::string asString() const; -# ifdef JSON_USE_CPPTL - CppTL::ConstString asConstString() const; -# endif - Int asInt() const; - UInt asUInt() const; - double asDouble() const; - bool asBool() const; - - bool isNull() const; - bool isBool() const; - bool isInt() const; - bool isUInt() const; - bool isIntegral() const; - bool isDouble() const; - bool isNumeric() const; - bool isString() const; - bool isArray() const; - bool isObject() const; - - bool isConvertibleTo( ValueType other ) const; - - /// Number of values in array or object - UInt size() const; - - /// @brief Return true if empty array, empty object, or null; - /// otherwise, false. - bool empty() const; - - /// Return isNull() - bool operator!() const; - - /// Remove all object members and array elements. - /// \pre type() is arrayValue, objectValue, or nullValue - /// \post type() is unchanged - void clear(); - - /// Resize the array to size elements. - /// New elements are initialized to null. - /// May only be called on nullValue or arrayValue. - /// \pre type() is arrayValue or nullValue - /// \post type() is arrayValue - void resize( UInt size ); - - /// Access an array element (zero based index ). - /// If the array contains less than index element, then null value are inserted - /// in the array so that its size is index+1. - /// (You may need to say 'value[0u]' to get your compiler to distinguish - /// this from the operator[] which takes a string.) - Value &operator[]( UInt index ); - /// Access an array element (zero based index ) - /// (You may need to say 'value[0u]' to get your compiler to distinguish - /// this from the operator[] which takes a string.) - const Value &operator[]( UInt index ) const; - /// If the array contains at least index+1 elements, returns the element value, - /// otherwise returns defaultValue. - Value get( UInt index, - const Value &defaultValue ) const; - /// Return true if index < size(). - bool isValidIndex( UInt index ) const; - /// @brief Append value to array at the end. - /// - /// Equivalent to jsonvalue[jsonvalue.size()] = value; - Value &append( const Value &value ); - - /// Access an object value by name, create a null member if it does not exist. - Value &operator[]( const char *key ); - /// Access an object value by name, returns null if there is no member with that name. - const Value &operator[]( const char *key ) const; - /// Access an object value by name, create a null member if it does not exist. - Value &operator[]( const std::string &key ); - /// Access an object value by name, returns null if there is no member with that name. - const Value &operator[]( const std::string &key ) const; - /** @brief Access an object value by name, create a null member if it does not exist. - - * If the object as no entry for that name, then the member name used to store - * the new entry is not duplicated. - * Example of use: - * \code - * Json::Value object; - * static const StaticString code("code"); - * object[code] = 1234; - * \endcode - */ - Value &operator[]( const StaticString &key ); -# ifdef JSON_USE_CPPTL - /// Access an object value by name, create a null member if it does not exist. - Value &operator[]( const CppTL::ConstString &key ); - /// Access an object value by name, returns null if there is no member with that name. - const Value &operator[]( const CppTL::ConstString &key ) const; -# endif - /// Return the member named key if it exist, defaultValue otherwise. - Value get( const char *key, - const Value &defaultValue ) const; - /// Return the member named key if it exist, defaultValue otherwise. - Value get( const std::string &key, - const Value &defaultValue ) const; -# ifdef JSON_USE_CPPTL - /// Return the member named key if it exist, defaultValue otherwise. - Value get( const CppTL::ConstString &key, - const Value &defaultValue ) const; -# endif - /// @brief Remove and return the named member. - /// - /// Do nothing if it did not exist. - /// \return the removed Value, or null. - /// \pre type() is objectValue or nullValue - /// \post type() is unchanged - Value removeMember( const char* key ); - /// Same as removeMember(const char*) - Value removeMember( const std::string &key ); - - /// Return true if the object has a member named key. - bool isMember( const char *key ) const; - /// Return true if the object has a member named key. - bool isMember( const std::string &key ) const; -# ifdef JSON_USE_CPPTL - /// Return true if the object has a member named key. - bool isMember( const CppTL::ConstString &key ) const; -# endif - - /// @brief Return a list of the member names. - /// - /// If null, return an empty list. - /// \pre type() is objectValue or nullValue - /// \post if type() was nullValue, it remains nullValue - Members getMemberNames() const; - -//# ifdef JSON_USE_CPPTL -// EnumMemberNames enumMemberNames() const; -// EnumValues enumValues() const; -//# endif - - /// Comments must be //... or /* ... */ - void setComment( const char *comment, - CommentPlacement placement ); - /// Comments must be //... or /* ... */ - void setComment( const std::string &comment, - CommentPlacement placement ); - bool hasComment( CommentPlacement placement ) const; - /// Include delimiters and embedded newlines. - std::string getComment( CommentPlacement placement ) const; - - std::string toStyledString() const; - - const_iterator begin() const; - const_iterator end() const; - - iterator begin(); - iterator end(); - - private: - Value &resolveReference( const char *key, - bool isStatic ); - -# ifdef JSON_VALUE_USE_INTERNAL_MAP - inline bool isItemAvailable() const - { - return itemIsUsed_ == 0; - } - - inline void setItemUsed( bool isUsed = true ) - { - itemIsUsed_ = isUsed ? 1 : 0; - } - - inline bool isMemberNameStatic() const - { - return memberNameIsStatic_ == 0; - } - - inline void setMemberNameIsStatic( bool isStatic ) - { - memberNameIsStatic_ = isStatic ? 1 : 0; - } -# endif // # ifdef JSON_VALUE_USE_INTERNAL_MAP - - private: - struct CommentInfo - { - CommentInfo(); - ~CommentInfo(); - - void setComment( const char *text ); - - char *comment_; - }; - - //struct MemberNamesTransform - //{ - // typedef const char *result_type; - // const char *operator()( const CZString &name ) const - // { - // return name.c_str(); - // } - //}; - - union ValueHolder - { - Int int_; - UInt uint_; - double real_; - bool bool_; - char *string_; -# ifdef JSON_VALUE_USE_INTERNAL_MAP - ValueInternalArray *array_; - ValueInternalMap *map_; -#else - ObjectValues *map_; -# endif - } value_; - ValueType type_ : 8; - int allocated_ : 1; // Notes: if declared as bool, bitfield is useless. -# ifdef JSON_VALUE_USE_INTERNAL_MAP - unsigned int itemIsUsed_ : 1; // used by the ValueInternalMap container. - int memberNameIsStatic_ : 1; // used by the ValueInternalMap container. -# endif - CommentInfo *comments_; - }; - - - /** @brief Experimental and untested: represents an element of the "path" to access a node. - */ - class PathArgument - { - public: - friend class Path; - - PathArgument(); - PathArgument( UInt index ); - PathArgument( const char *key ); - PathArgument( const std::string &key ); - - private: - enum Kind - { - kindNone = 0, - kindIndex, - kindKey - }; - std::string key_; - UInt index_; - Kind kind_; - }; - - /** @brief Experimental and untested: represents a "path" to access a node. - * - * Syntax: - * - "." => root node - * - ".[n]" => elements at index 'n' of root node (an array value) - * - ".name" => member named 'name' of root node (an object value) - * - ".name1.name2.name3" - * - ".[0][1][2].name1[3]" - * - ".%" => member name is provided as parameter - * - ".[%]" => index is provied as parameter - */ - class Path - { - public: - Path( const std::string &path, - const PathArgument &a1 = PathArgument(), - const PathArgument &a2 = PathArgument(), - const PathArgument &a3 = PathArgument(), - const PathArgument &a4 = PathArgument(), - const PathArgument &a5 = PathArgument() ); - - const Value &resolve( const Value &root ) const; - Value resolve( const Value &root, - const Value &defaultValue ) const; - /// Creates the "path" to access the specified node and returns a reference on the node. - Value &make( Value &root ) const; - - private: - typedef std::vector InArgs; - typedef std::vector Args; - - void makePath( const std::string &path, - const InArgs &in ); - void addPathInArg( const std::string &path, - const InArgs &in, - InArgs::const_iterator &itInArg, - PathArgument::Kind kind ); - void invalidPath( const std::string &path, - int location ); - - Args args_; - }; - - /** @brief Experimental do not use: Allocator to customize member name and string value memory management done by Value. - * - * - makeMemberName() and releaseMemberName() are called to respectively duplicate and - * free an Json::objectValue member name. - * - duplicateStringValue() and releaseStringValue() are called similarly to - * duplicate and free a Json::stringValue value. - */ - class ValueAllocator - { - public: - enum { unknown = (unsigned)-1 }; - - virtual ~ValueAllocator(); - - virtual char *makeMemberName( const char *memberName ) = 0; - virtual void releaseMemberName( char *memberName ) = 0; - virtual char *duplicateStringValue( const char *value, - unsigned int length = unknown ) = 0; - virtual void releaseStringValue( char *value ) = 0; - }; - -#ifdef JSON_VALUE_USE_INTERNAL_MAP - /** @brief Allocator to customize Value internal map. - * Below is an example of a simple implementation (default implementation actually - * use memory pool for speed). - * \code - class DefaultValueMapAllocator : public ValueMapAllocator - { - public: // overridden from ValueMapAllocator - virtual ValueInternalMap *newMap() - { - return new ValueInternalMap(); - } - - virtual ValueInternalMap *newMapCopy( const ValueInternalMap &other ) - { - return new ValueInternalMap( other ); - } - - virtual void destructMap( ValueInternalMap *map ) - { - delete map; - } - - virtual ValueInternalLink *allocateMapBuckets( unsigned int size ) - { - return new ValueInternalLink[size]; - } - - virtual void releaseMapBuckets( ValueInternalLink *links ) - { - delete [] links; - } - - virtual ValueInternalLink *allocateMapLink() - { - return new ValueInternalLink(); - } - - virtual void releaseMapLink( ValueInternalLink *link ) - { - delete link; - } - }; - * \endcode - */ - class JSON_API ValueMapAllocator - { - public: - virtual ~ValueMapAllocator(); - virtual ValueInternalMap *newMap() = 0; - virtual ValueInternalMap *newMapCopy( const ValueInternalMap &other ) = 0; - virtual void destructMap( ValueInternalMap *map ) = 0; - virtual ValueInternalLink *allocateMapBuckets( unsigned int size ) = 0; - virtual void releaseMapBuckets( ValueInternalLink *links ) = 0; - virtual ValueInternalLink *allocateMapLink() = 0; - virtual void releaseMapLink( ValueInternalLink *link ) = 0; - }; - - /** @brief ValueInternalMap hash-map bucket chain link (for internal use only). - * \internal previous_ & next_ allows for bidirectional traversal. - */ - class JSON_API ValueInternalLink - { - public: - enum { itemPerLink = 6 }; // sizeof(ValueInternalLink) = 128 on 32 bits architecture. - enum InternalFlags { - flagAvailable = 0, - flagUsed = 1 - }; - - ValueInternalLink(); - - ~ValueInternalLink(); - - Value items_[itemPerLink]; - char *keys_[itemPerLink]; - ValueInternalLink *previous_; - ValueInternalLink *next_; - }; - - - /** @brief A linked page based hash-table implementation used internally by Value. - * \internal ValueInternalMap is a tradional bucket based hash-table, with a linked - * list in each bucket to handle collision. There is an addional twist in that - * each node of the collision linked list is a page containing a fixed amount of - * value. This provides a better compromise between memory usage and speed. - * - * Each bucket is made up of a chained list of ValueInternalLink. The last - * link of a given bucket can be found in the 'previous_' field of the following bucket. - * The last link of the last bucket is stored in tailLink_ as it has no following bucket. - * Only the last link of a bucket may contains 'available' item. The last link always - * contains at least one element unless is it the bucket one very first link. - */ - class JSON_API ValueInternalMap - { - friend class ValueIteratorBase; - friend class Value; - public: - typedef unsigned int HashKey; - typedef unsigned int BucketIndex; - -# ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION - struct IteratorState - { - IteratorState() - : map_(0) - , link_(0) - , itemIndex_(0) - , bucketIndex_(0) - { - } - ValueInternalMap *map_; - ValueInternalLink *link_; - BucketIndex itemIndex_; - BucketIndex bucketIndex_; - }; -# endif // ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION - - ValueInternalMap(); - ValueInternalMap( const ValueInternalMap &other ); - ValueInternalMap &operator =( const ValueInternalMap &other ); - ~ValueInternalMap(); - - void swap( ValueInternalMap &other ); - - BucketIndex size() const; - - void clear(); - - bool reserveDelta( BucketIndex growth ); - - bool reserve( BucketIndex newItemCount ); - - const Value *find( const char *key ) const; - - Value *find( const char *key ); - - Value &resolveReference( const char *key, - bool isStatic ); - - void remove( const char *key ); - - void doActualRemove( ValueInternalLink *link, - BucketIndex index, - BucketIndex bucketIndex ); - - ValueInternalLink *&getLastLinkInBucket( BucketIndex bucketIndex ); - - Value &setNewItem( const char *key, - bool isStatic, - ValueInternalLink *link, - BucketIndex index ); - - Value &unsafeAdd( const char *key, - bool isStatic, - HashKey hashedKey ); - - HashKey hash( const char *key ) const; - - int compare( const ValueInternalMap &other ) const; - - private: - void makeBeginIterator( IteratorState &it ) const; - void makeEndIterator( IteratorState &it ) const; - static bool equals( const IteratorState &x, const IteratorState &other ); - static void increment( IteratorState &iterator ); - static void incrementBucket( IteratorState &iterator ); - static void decrement( IteratorState &iterator ); - static const char *key( const IteratorState &iterator ); - static const char *key( const IteratorState &iterator, bool &isStatic ); - static Value &value( const IteratorState &iterator ); - static int distance( const IteratorState &x, const IteratorState &y ); - - private: - ValueInternalLink *buckets_; - ValueInternalLink *tailLink_; - BucketIndex bucketsSize_; - BucketIndex itemCount_; - }; - - /** @brief A simplified deque implementation used internally by Value. - * \internal - * It is based on a list of fixed "page", each page contains a fixed number of items. - * Instead of using a linked-list, a array of pointer is used for fast item look-up. - * Look-up for an element is as follow: - * - compute page index: pageIndex = itemIndex / itemsPerPage - * - look-up item in page: pages_[pageIndex][itemIndex % itemsPerPage] - * - * Insertion is amortized constant time (only the array containing the index of pointers - * need to be reallocated when items are appended). - */ - class JSON_API ValueInternalArray - { - friend class Value; - friend class ValueIteratorBase; - public: - enum { itemsPerPage = 8 }; // should be a power of 2 for fast divide and modulo. - typedef Value::ArrayIndex ArrayIndex; - typedef unsigned int PageIndex; - -# ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION - struct IteratorState // Must be a POD - { - IteratorState() - : array_(0) - , currentPageIndex_(0) - , currentItemIndex_(0) - { - } - ValueInternalArray *array_; - Value **currentPageIndex_; - unsigned int currentItemIndex_; - }; -# endif // ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION - - ValueInternalArray(); - ValueInternalArray( const ValueInternalArray &other ); - ValueInternalArray &operator =( const ValueInternalArray &other ); - ~ValueInternalArray(); - void swap( ValueInternalArray &other ); - - void clear(); - void resize( ArrayIndex newSize ); - - Value &resolveReference( ArrayIndex index ); - - Value *find( ArrayIndex index ) const; - - ArrayIndex size() const; - - int compare( const ValueInternalArray &other ) const; - - private: - static bool equals( const IteratorState &x, const IteratorState &other ); - static void increment( IteratorState &iterator ); - static void decrement( IteratorState &iterator ); - static Value &dereference( const IteratorState &iterator ); - static Value &unsafeDereference( const IteratorState &iterator ); - static int distance( const IteratorState &x, const IteratorState &y ); - static ArrayIndex indexOf( const IteratorState &iterator ); - void makeBeginIterator( IteratorState &it ) const; - void makeEndIterator( IteratorState &it ) const; - void makeIterator( IteratorState &it, ArrayIndex index ) const; - - void makeIndexValid( ArrayIndex index ); - - Value **pages_; - ArrayIndex size_; - PageIndex pageCount_; - }; - - /** @brief Experimental: do not use. Allocator to customize Value internal array. - * Below is an example of a simple implementation (actual implementation use - * memory pool). - \code -class DefaultValueArrayAllocator : public ValueArrayAllocator -{ -public: // overridden from ValueArrayAllocator - virtual ~DefaultValueArrayAllocator() - { - } - - virtual ValueInternalArray *newArray() - { - return new ValueInternalArray(); - } - - virtual ValueInternalArray *newArrayCopy( const ValueInternalArray &other ) - { - return new ValueInternalArray( other ); - } - - virtual void destruct( ValueInternalArray *array ) - { - delete array; - } - - virtual void reallocateArrayPageIndex( Value **&indexes, - ValueInternalArray::PageIndex &indexCount, - ValueInternalArray::PageIndex minNewIndexCount ) - { - ValueInternalArray::PageIndex newIndexCount = (indexCount*3)/2 + 1; - if ( minNewIndexCount > newIndexCount ) - newIndexCount = minNewIndexCount; - void *newIndexes = realloc( indexes, sizeof(Value*) * newIndexCount ); - if ( !newIndexes ) - throw std::bad_alloc(); - indexCount = newIndexCount; - indexes = static_cast( newIndexes ); - } - virtual void releaseArrayPageIndex( Value **indexes, - ValueInternalArray::PageIndex indexCount ) - { - if ( indexes ) - free( indexes ); - } - - virtual Value *allocateArrayPage() - { - return static_cast( malloc( sizeof(Value) * ValueInternalArray::itemsPerPage ) ); - } - - virtual void releaseArrayPage( Value *value ) - { - if ( value ) - free( value ); - } -}; - \endcode - */ - class JSON_API ValueArrayAllocator - { - public: - virtual ~ValueArrayAllocator(); - virtual ValueInternalArray *newArray() = 0; - virtual ValueInternalArray *newArrayCopy( const ValueInternalArray &other ) = 0; - virtual void destructArray( ValueInternalArray *array ) = 0; - /** @brief Reallocate array page index. - * Reallocates an array of pointer on each page. - * \param indexes [input] pointer on the current index. May be \c NULL. - * [output] pointer on the new index of at least - * \a minNewIndexCount pages. - * \param indexCount [input] current number of pages in the index. - * [output] number of page the reallocated index can handle. - * \b MUST be >= \a minNewIndexCount. - * \param minNewIndexCount Minimum number of page the new index must be able to - * handle. - */ - virtual void reallocateArrayPageIndex( Value **&indexes, - ValueInternalArray::PageIndex &indexCount, - ValueInternalArray::PageIndex minNewIndexCount ) = 0; - virtual void releaseArrayPageIndex( Value **indexes, - ValueInternalArray::PageIndex indexCount ) = 0; - virtual Value *allocateArrayPage() = 0; - virtual void releaseArrayPage( Value *value ) = 0; - }; -#endif // #ifdef JSON_VALUE_USE_INTERNAL_MAP - - - /** @brief base class for Value iterators. - * - */ - class ValueIteratorBase - { - public: - typedef unsigned int size_t; - typedef int difference_type; - typedef ValueIteratorBase SelfType; - - ValueIteratorBase(); -#ifndef JSON_VALUE_USE_INTERNAL_MAP - explicit ValueIteratorBase( const Value::ObjectValues::iterator ¤t ); -#else - ValueIteratorBase( const ValueInternalArray::IteratorState &state ); - ValueIteratorBase( const ValueInternalMap::IteratorState &state ); -#endif - - bool operator ==( const SelfType &other ) const - { - return isEqual( other ); - } - - bool operator !=( const SelfType &other ) const - { - return !isEqual( other ); - } - - difference_type operator -( const SelfType &other ) const - { - return computeDistance( other ); - } - - /// Return either the index or the member name of the referenced value as a Value. - Value key() const; - - /// Return the index of the referenced Value. -1 if it is not an arrayValue. - UInt index() const; - - /// Return the member name of the referenced Value. "" if it is not an objectValue. - const char *memberName() const; - - protected: - Value &deref() const; - - void increment(); - - void decrement(); - - difference_type computeDistance( const SelfType &other ) const; - - bool isEqual( const SelfType &other ) const; - - void copy( const SelfType &other ); - - private: -#ifndef JSON_VALUE_USE_INTERNAL_MAP - Value::ObjectValues::iterator current_; - // Indicates that iterator is for a null value. - bool isNull_; -#else - union - { - ValueInternalArray::IteratorState array_; - ValueInternalMap::IteratorState map_; - } iterator_; - bool isArray_; -#endif - }; - - /** @brief const iterator for object and array value. - * - */ - class ValueConstIterator : public ValueIteratorBase - { - friend class Value; - public: - typedef unsigned int size_t; - typedef int difference_type; - typedef const Value &reference; - typedef const Value *pointer; - typedef ValueConstIterator SelfType; - - ValueConstIterator(); - private: - /*! \internal Use by Value to create an iterator. - */ -#ifndef JSON_VALUE_USE_INTERNAL_MAP - explicit ValueConstIterator( const Value::ObjectValues::iterator ¤t ); -#else - ValueConstIterator( const ValueInternalArray::IteratorState &state ); - ValueConstIterator( const ValueInternalMap::IteratorState &state ); -#endif - public: - SelfType &operator =( const ValueIteratorBase &other ); - - SelfType operator++( int ) - { - SelfType temp( *this ); - ++*this; - return temp; - } - - SelfType operator--( int ) - { - SelfType temp( *this ); - --*this; - return temp; - } - - SelfType &operator--() - { - decrement(); - return *this; - } - - SelfType &operator++() - { - increment(); - return *this; - } - - reference operator *() const - { - return deref(); - } - }; - - - /** @brief Iterator for object and array value. - */ - class ValueIterator : public ValueIteratorBase - { - friend class Value; - public: - typedef unsigned int size_t; - typedef int difference_type; - typedef Value &reference; - typedef Value *pointer; - typedef ValueIterator SelfType; - - ValueIterator(); - ValueIterator( const ValueConstIterator &other ); - ValueIterator( const ValueIterator &other ); - private: - /*! \internal Use by Value to create an iterator. - */ -#ifndef JSON_VALUE_USE_INTERNAL_MAP - explicit ValueIterator( const Value::ObjectValues::iterator ¤t ); -#else - ValueIterator( const ValueInternalArray::IteratorState &state ); - ValueIterator( const ValueInternalMap::IteratorState &state ); -#endif - public: - - SelfType &operator =( const SelfType &other ); - - SelfType operator++( int ) - { - SelfType temp( *this ); - ++*this; - return temp; - } - - SelfType operator--( int ) - { - SelfType temp( *this ); - --*this; - return temp; - } - - SelfType &operator--() - { - decrement(); - return *this; - } - - SelfType &operator++() - { - increment(); - return *this; - } - - reference operator *() const - { - return deref(); - } - }; - - -} // namespace Json - - -#endif // CPPTL_JSON_H_INCLUDED diff --git a/thirdparty/jsoncpp/include/json/writer.h b/thirdparty/jsoncpp/include/json/writer.h deleted file mode 100644 index 706c8e6a..00000000 --- a/thirdparty/jsoncpp/include/json/writer.h +++ /dev/null @@ -1,174 +0,0 @@ -#ifndef JSON_WRITER_H_INCLUDED -# define JSON_WRITER_H_INCLUDED - -# include "value.h" -# include -# include -# include - -namespace Json { - - class Value; - - /** @brief Abstract class for writers. - */ - class JSON_API Writer - { - public: - virtual ~Writer(); - - virtual std::string write( const Value &root ) = 0; - }; - - /** @brief Outputs a Value in JSON format without formatting (not human friendly). - * - * The JSON document is written in a single line. It is not intended for 'human' consumption, - * but may be usefull to support feature such as RPC where bandwith is limited. - * \sa Reader, Value - */ - class JSON_API FastWriter : public Writer - { - public: - FastWriter(); - virtual ~FastWriter(){} - - void enableYAMLCompatibility(); - - public: // overridden from Writer - virtual std::string write( const Value &root ); - - private: - void writeValue( const Value &value ); - - std::string document_; - bool yamlCompatiblityEnabled_; - }; - - /** @brief Writes a Value in JSON format in a human friendly way. - * - * The rules for line break and indent are as follow: - * - Object value: - * - if empty then print {} without indent and line break - * - if not empty the print '{', line break & indent, print one value per line - * and then unindent and line break and print '}'. - * - Array value: - * - if empty then print [] without indent and line break - * - if the array contains no object value, empty array or some other value types, - * and all the values fit on one lines, then print the array on a single line. - * - otherwise, it the values do not fit on one line, or the array contains - * object or non empty array, then print one value per line. - * - * If the Value have comments then they are outputed according to their #CommentPlacement. - * - * \sa Reader, Value, Value::setComment() - */ - class JSON_API StyledWriter: public Writer - { - public: - StyledWriter(); - virtual ~StyledWriter(){} - - public: // overridden from Writer - /** @brief Serialize a Value in JSON format. - * \param root Value to serialize. - * \return String containing the JSON document that represents the root value. - */ - virtual std::string write( const Value &root ); - - private: - void writeValue( const Value &value ); - void writeArrayValue( const Value &value ); - bool isMultineArray( const Value &value ); - void pushValue( const std::string &value ); - void writeIndent(); - void writeWithIndent( const std::string &value ); - void indent(); - void unindent(); - void writeCommentBeforeValue( const Value &root ); - void writeCommentAfterValueOnSameLine( const Value &root ); - bool hasCommentForValue( const Value &value ); - static std::string normalizeEOL( const std::string &text ); - - typedef std::vector ChildValues; - - ChildValues childValues_; - std::string document_; - std::string indentString_; - int rightMargin_; - int indentSize_; - bool addChildValues_; - }; - - /** @brief Writes a Value in JSON format in a human friendly way, - to a stream rather than to a string. - * - * The rules for line break and indent are as follow: - * - Object value: - * - if empty then print {} without indent and line break - * - if not empty the print '{', line break & indent, print one value per line - * and then unindent and line break and print '}'. - * - Array value: - * - if empty then print [] without indent and line break - * - if the array contains no object value, empty array or some other value types, - * and all the values fit on one lines, then print the array on a single line. - * - otherwise, it the values do not fit on one line, or the array contains - * object or non empty array, then print one value per line. - * - * If the Value have comments then they are outputed according to their #CommentPlacement. - * - * \param indentation Each level will be indented by this amount extra. - * \sa Reader, Value, Value::setComment() - */ - class JSON_API StyledStreamWriter - { - public: - StyledStreamWriter( std::string indentation="\t" ); - ~StyledStreamWriter(){} - - public: - /** @brief Serialize a Value in JSON format. - * \param out Stream to write to. (Can be ostringstream, e.g.) - * \param root Value to serialize. - * \note There is no point in deriving from Writer, since write() should not return a value. - */ - void write( std::ostream &out, const Value &root ); - - private: - void writeValue( const Value &value ); - void writeArrayValue( const Value &value ); - bool isMultineArray( const Value &value ); - void pushValue( const std::string &value ); - void writeIndent(); - void writeWithIndent( const std::string &value ); - void indent(); - void unindent(); - void writeCommentBeforeValue( const Value &root ); - void writeCommentAfterValueOnSameLine( const Value &root ); - bool hasCommentForValue( const Value &value ); - static std::string normalizeEOL( const std::string &text ); - - typedef std::vector ChildValues; - - ChildValues childValues_; - std::ostream* document_; - std::string indentString_; - int rightMargin_; - std::string indentation_; - bool addChildValues_; - }; - - std::string JSON_API valueToString( Int value ); - std::string JSON_API valueToString( UInt value ); - std::string JSON_API valueToString( double value ); - std::string JSON_API valueToString( bool value ); - std::string JSON_API valueToQuotedString( const char *value ); - - /// @brief Output using the StyledStreamWriter. - /// \see Json::operator>>() - std::ostream& operator<<( std::ostream&, const Value &root ); - -} // namespace Json - - - -#endif // JSON_WRITER_H_INCLUDED diff --git a/thirdparty/jsoncpp/json/json-forwards.h b/thirdparty/jsoncpp/json/json-forwards.h new file mode 100644 index 00000000..dd9a9bf3 --- /dev/null +++ b/thirdparty/jsoncpp/json/json-forwards.h @@ -0,0 +1,320 @@ +/// Json-cpp amalgamated forward header (http://jsoncpp.sourceforge.net/). +/// It is intended to be used with #include "json/json-forwards.h" +/// This header provides forward declaration for all JsonCpp types. + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: LICENSE +// ////////////////////////////////////////////////////////////////////// + +/* +The JsonCpp library's source code, including accompanying documentation, +tests and demonstration applications, are licensed under the following +conditions... + +Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all +jurisdictions which recognize such a disclaimer. In such jurisdictions, +this software is released into the Public Domain. + +In jurisdictions which do not recognize Public Domain property (e.g. Germany as of +2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur and +The JsonCpp Authors, and is released under the terms of the MIT License (see below). + +In jurisdictions which recognize Public Domain property, the user of this +software may choose to accept it either as 1) Public Domain, 2) under the +conditions of the MIT License (see below), or 3) under the terms of dual +Public Domain/MIT License conditions described here, as they choose. + +The MIT License is about as close to Public Domain as a license can get, and is +described in clear, concise terms at: + + http://en.wikipedia.org/wiki/MIT_License + +The full text of the MIT License follows: + +======================================================================== +Copyright (c) 2007-2010 Baptiste Lepilleur and The JsonCpp Authors + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, +modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +======================================================================== +(END LICENSE TEXT) + +The MIT license is compatible with both the GPL and commercial +software, affording one all of the rights of Public Domain with the +minor nuisance of being required to keep the above copyright notice +and license text in the source code. Note also that by accepting the +Public Domain "license" you can re-license your copy using whatever +license you like. + +*/ + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: LICENSE +// ////////////////////////////////////////////////////////////////////// + + + + + +#ifndef JSON_FORWARD_AMALGAMATED_H_INCLUDED +# define JSON_FORWARD_AMALGAMATED_H_INCLUDED +/// If defined, indicates that the source file is amalgamated +/// to prevent private header inclusion. +#define JSON_IS_AMALGAMATION + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/config.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_CONFIG_H_INCLUDED +#define JSON_CONFIG_H_INCLUDED +#include +#include +#include +#include +#include +#include +#include +#include + +/// If defined, indicates that json library is embedded in CppTL library. +//# define JSON_IN_CPPTL 1 + +/// If defined, indicates that json may leverage CppTL library +//# define JSON_USE_CPPTL 1 +/// If defined, indicates that cpptl vector based map should be used instead of +/// std::map +/// as Value container. +//# define JSON_USE_CPPTL_SMALLMAP 1 + +// If non-zero, the library uses exceptions to report bad input instead of C +// assertion macros. The default is to use exceptions. +#ifndef JSON_USE_EXCEPTION +#define JSON_USE_EXCEPTION 1 +#endif + +/// If defined, indicates that the source file is amalgamated +/// to prevent private header inclusion. +/// Remarks: it is automatically defined in the generated amalgamated header. +// #define JSON_IS_AMALGAMATION + +#ifdef JSON_IN_CPPTL +#include +#ifndef JSON_USE_CPPTL +#define JSON_USE_CPPTL 1 +#endif +#endif + +#ifdef JSON_IN_CPPTL +#define JSON_API CPPTL_API +#elif defined(JSON_DLL_BUILD) +#if defined(_MSC_VER) || defined(__MINGW32__) +#define JSON_API __declspec(dllexport) +#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING +#elif defined(__GNUC__) || defined(__clang__) +#define JSON_API __attribute__((visibility("default"))) +#endif // if defined(_MSC_VER) +#elif defined(JSON_DLL) +#if defined(_MSC_VER) || defined(__MINGW32__) +#define JSON_API __declspec(dllimport) +#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING +#endif // if defined(_MSC_VER) +#endif // ifdef JSON_IN_CPPTL +#if !defined(JSON_API) +#define JSON_API +#endif + +#if defined(_MSC_VER) && _MSC_VER < 1800 +#error \ + "ERROR: Visual Studio 12 (2013) with _MSC_VER=1800 is the oldest supported compiler with sufficient C++11 capabilities" +#endif + +#if defined(_MSC_VER) && _MSC_VER < 1900 +// As recommended at +// https://stackoverflow.com/questions/2915672/snprintf-and-visual-studio-2010 +extern JSON_API int +msvc_pre1900_c99_snprintf(char* outBuf, size_t size, const char* format, ...); +#define jsoncpp_snprintf msvc_pre1900_c99_snprintf +#else +#define jsoncpp_snprintf std::snprintf +#endif + +// If JSON_NO_INT64 is defined, then Json only support C++ "int" type for +// integer +// Storages, and 64 bits integer support is disabled. +// #define JSON_NO_INT64 1 + +// JSONCPP_OVERRIDE is maintained for backwards compatibility of external tools. +// C++11 should be used directly in JSONCPP. +#define JSONCPP_OVERRIDE override + +#if __cplusplus >= 201103L +#define JSONCPP_NOEXCEPT noexcept +#define JSONCPP_OP_EXPLICIT explicit +#elif defined(_MSC_VER) && _MSC_VER < 1900 +#define JSONCPP_NOEXCEPT throw() +#define JSONCPP_OP_EXPLICIT explicit +#elif defined(_MSC_VER) && _MSC_VER >= 1900 +#define JSONCPP_NOEXCEPT noexcept +#define JSONCPP_OP_EXPLICIT explicit +#else +#define JSONCPP_NOEXCEPT throw() +#define JSONCPP_OP_EXPLICIT +#endif + +#ifdef __clang__ +#if __has_extension(attribute_deprecated_with_message) +#define JSONCPP_DEPRECATED(message) __attribute__((deprecated(message))) +#endif +#elif defined __GNUC__ // not clang (gcc comes later since clang emulates gcc) +#if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)) +#define JSONCPP_DEPRECATED(message) __attribute__((deprecated(message))) +#elif (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)) +#define JSONCPP_DEPRECATED(message) __attribute__((__deprecated__)) +#endif // GNUC version +#elif defined(_MSC_VER) // MSVC (after clang because clang on Windows emulates MSVC) +#define JSONCPP_DEPRECATED(message) __declspec(deprecated(message)) +#endif // __clang__ || __GNUC__ || _MSC_VER + +#if !defined(JSONCPP_DEPRECATED) +#define JSONCPP_DEPRECATED(message) +#endif // if !defined(JSONCPP_DEPRECATED) + +#if __GNUC__ >= 6 +#define JSON_USE_INT64_DOUBLE_CONVERSION 1 +#endif + +#if !defined(JSON_IS_AMALGAMATION) + +#include "allocator.h" +#include "version.h" + +#endif // if !defined(JSON_IS_AMALGAMATION) + +namespace Json { +typedef int Int; +typedef unsigned int UInt; +#if defined(JSON_NO_INT64) +typedef int LargestInt; +typedef unsigned int LargestUInt; +#undef JSON_HAS_INT64 +#else // if defined(JSON_NO_INT64) +// For Microsoft Visual use specific types as long long is not supported +#if defined(_MSC_VER) // Microsoft Visual Studio +typedef __int64 Int64; +typedef unsigned __int64 UInt64; +#else // if defined(_MSC_VER) // Other platforms, use long long +typedef int64_t Int64; +typedef uint64_t UInt64; +#endif // if defined(_MSC_VER) +typedef Int64 LargestInt; +typedef UInt64 LargestUInt; +#define JSON_HAS_INT64 +#endif // if defined(JSON_NO_INT64) + +template +using Allocator = typename std::conditional, + std::allocator>::type; +using String = std::basic_string, Allocator>; +using IStringStream = std::basic_istringstream; +using OStringStream = std::basic_ostringstream; +using IStream = std::istream; +using OStream = std::ostream; +} // namespace Json + +// Legacy names (formerly macros). +using JSONCPP_STRING = Json::String; +using JSONCPP_ISTRINGSTREAM = Json::IStringStream; +using JSONCPP_OSTRINGSTREAM = Json::OStringStream; +using JSONCPP_ISTREAM = Json::IStream; +using JSONCPP_OSTREAM = Json::OStream; + +#endif // JSON_CONFIG_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/config.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/forwards.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_FORWARDS_H_INCLUDED +#define JSON_FORWARDS_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +#include "config.h" +#endif // if !defined(JSON_IS_AMALGAMATION) + +namespace Json { + +// writer.h +class FastWriter; +class StyledWriter; + +// reader.h +class Reader; + +// features.h +class Features; + +// value.h +typedef unsigned int ArrayIndex; +class StaticString; +class Path; +class PathArgument; +class Value; +class ValueIteratorBase; +class ValueIterator; +class ValueConstIterator; + +} // namespace Json + +#endif // JSON_FORWARDS_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/forwards.h +// ////////////////////////////////////////////////////////////////////// + + + + + +#endif //ifndef JSON_FORWARD_AMALGAMATED_H_INCLUDED diff --git a/thirdparty/jsoncpp/json/json.h b/thirdparty/jsoncpp/json/json.h new file mode 100644 index 00000000..c5963843 --- /dev/null +++ b/thirdparty/jsoncpp/json/json.h @@ -0,0 +1,2346 @@ +/// Json-cpp amalgamated header (http://jsoncpp.sourceforge.net/). +/// It is intended to be used with #include "json/json.h" + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: LICENSE +// ////////////////////////////////////////////////////////////////////// + +/* +The JsonCpp library's source code, including accompanying documentation, +tests and demonstration applications, are licensed under the following +conditions... + +Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all +jurisdictions which recognize such a disclaimer. In such jurisdictions, +this software is released into the Public Domain. + +In jurisdictions which do not recognize Public Domain property (e.g. Germany as of +2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur and +The JsonCpp Authors, and is released under the terms of the MIT License (see below). + +In jurisdictions which recognize Public Domain property, the user of this +software may choose to accept it either as 1) Public Domain, 2) under the +conditions of the MIT License (see below), or 3) under the terms of dual +Public Domain/MIT License conditions described here, as they choose. + +The MIT License is about as close to Public Domain as a license can get, and is +described in clear, concise terms at: + + http://en.wikipedia.org/wiki/MIT_License + +The full text of the MIT License follows: + +======================================================================== +Copyright (c) 2007-2010 Baptiste Lepilleur and The JsonCpp Authors + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, +modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +======================================================================== +(END LICENSE TEXT) + +The MIT license is compatible with both the GPL and commercial +software, affording one all of the rights of Public Domain with the +minor nuisance of being required to keep the above copyright notice +and license text in the source code. Note also that by accepting the +Public Domain "license" you can re-license your copy using whatever +license you like. + +*/ + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: LICENSE +// ////////////////////////////////////////////////////////////////////// + + + + + +#ifndef JSON_AMALGAMATED_H_INCLUDED +# define JSON_AMALGAMATED_H_INCLUDED +/// If defined, indicates that the source file is amalgamated +/// to prevent private header inclusion. +#define JSON_IS_AMALGAMATION + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/version.h +// ////////////////////////////////////////////////////////////////////// + +// DO NOT EDIT. This file (and "version") is generated by CMake. +// Run CMake configure step to update it. +#ifndef JSON_VERSION_H_INCLUDED +#define JSON_VERSION_H_INCLUDED + +#define JSONCPP_VERSION_STRING "1.8.4" +#define JSONCPP_VERSION_MAJOR 1 +#define JSONCPP_VERSION_MINOR 8 +#define JSONCPP_VERSION_PATCH 4 +#define JSONCPP_VERSION_QUALIFIER +#define JSONCPP_VERSION_HEXA \ + ((JSONCPP_VERSION_MAJOR << 24) | (JSONCPP_VERSION_MINOR << 16) | \ + (JSONCPP_VERSION_PATCH << 8)) + +#ifdef JSONCPP_USING_SECURE_MEMORY +#undef JSONCPP_USING_SECURE_MEMORY +#endif +#define JSONCPP_USING_SECURE_MEMORY 0 +// If non-zero, the library zeroes any memory that it has allocated before +// it frees its memory. + +#endif // JSON_VERSION_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/version.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/allocator.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef CPPTL_JSON_ALLOCATOR_H_INCLUDED +#define CPPTL_JSON_ALLOCATOR_H_INCLUDED + +#include +#include + +#pragma pack(push, 8) + +namespace Json { +template class SecureAllocator { +public: + // Type definitions + using value_type = T; + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = const T&; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + + /** + * Allocate memory for N items using the standard allocator. + */ + pointer allocate(size_type n) { + // allocate using "global operator new" + return static_cast(::operator new(n * sizeof(T))); + } + + /** + * Release memory which was allocated for N items at pointer P. + * + * The memory block is filled with zeroes before being released. + * The pointer argument is tagged as "volatile" to prevent the + * compiler optimizing out this critical step. + */ + void deallocate(volatile pointer p, size_type n) { + std::memset(p, 0, n * sizeof(T)); + // free using "global operator delete" + ::operator delete(p); + } + + /** + * Construct an item in-place at pointer P. + */ + template void construct(pointer p, Args&&... args) { + // construct using "placement new" and "perfect forwarding" + ::new (static_cast(p)) T(std::forward(args)...); + } + + size_type max_size() const { return size_t(-1) / sizeof(T); } + + pointer address(reference x) const { return std::addressof(x); } + + const_pointer address(const_reference x) const { return std::addressof(x); } + + /** + * Destroy an item in-place at pointer P. + */ + void destroy(pointer p) { + // destroy using "explicit destructor" + p->~T(); + } + + // Boilerplate + SecureAllocator() {} + template SecureAllocator(const SecureAllocator&) {} + template struct rebind { using other = SecureAllocator; }; +}; + +template +bool operator==(const SecureAllocator&, const SecureAllocator&) { + return true; +} + +template +bool operator!=(const SecureAllocator&, const SecureAllocator&) { + return false; +} + +} // namespace Json + +#pragma pack(pop) + +#endif // CPPTL_JSON_ALLOCATOR_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/allocator.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/config.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_CONFIG_H_INCLUDED +#define JSON_CONFIG_H_INCLUDED +#include +#include +#include +#include +#include +#include +#include +#include + +/// If defined, indicates that json library is embedded in CppTL library. +//# define JSON_IN_CPPTL 1 + +/// If defined, indicates that json may leverage CppTL library +//# define JSON_USE_CPPTL 1 +/// If defined, indicates that cpptl vector based map should be used instead of +/// std::map +/// as Value container. +//# define JSON_USE_CPPTL_SMALLMAP 1 + +// If non-zero, the library uses exceptions to report bad input instead of C +// assertion macros. The default is to use exceptions. +#ifndef JSON_USE_EXCEPTION +#define JSON_USE_EXCEPTION 1 +#endif + +/// If defined, indicates that the source file is amalgamated +/// to prevent private header inclusion. +/// Remarks: it is automatically defined in the generated amalgamated header. +// #define JSON_IS_AMALGAMATION + +#ifdef JSON_IN_CPPTL +#include +#ifndef JSON_USE_CPPTL +#define JSON_USE_CPPTL 1 +#endif +#endif + +#ifdef JSON_IN_CPPTL +#define JSON_API CPPTL_API +#elif defined(JSON_DLL_BUILD) +#if defined(_MSC_VER) || defined(__MINGW32__) +#define JSON_API __declspec(dllexport) +#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING +#elif defined(__GNUC__) || defined(__clang__) +#define JSON_API __attribute__((visibility("default"))) +#endif // if defined(_MSC_VER) +#elif defined(JSON_DLL) +#if defined(_MSC_VER) || defined(__MINGW32__) +#define JSON_API __declspec(dllimport) +#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING +#endif // if defined(_MSC_VER) +#endif // ifdef JSON_IN_CPPTL +#if !defined(JSON_API) +#define JSON_API +#endif + +#if defined(_MSC_VER) && _MSC_VER < 1800 +#error \ + "ERROR: Visual Studio 12 (2013) with _MSC_VER=1800 is the oldest supported compiler with sufficient C++11 capabilities" +#endif + +#if defined(_MSC_VER) && _MSC_VER < 1900 +// As recommended at +// https://stackoverflow.com/questions/2915672/snprintf-and-visual-studio-2010 +extern JSON_API int +msvc_pre1900_c99_snprintf(char* outBuf, size_t size, const char* format, ...); +#define jsoncpp_snprintf msvc_pre1900_c99_snprintf +#else +#define jsoncpp_snprintf std::snprintf +#endif + +// If JSON_NO_INT64 is defined, then Json only support C++ "int" type for +// integer +// Storages, and 64 bits integer support is disabled. +// #define JSON_NO_INT64 1 + +// JSONCPP_OVERRIDE is maintained for backwards compatibility of external tools. +// C++11 should be used directly in JSONCPP. +#define JSONCPP_OVERRIDE override + +#if __cplusplus >= 201103L +#define JSONCPP_NOEXCEPT noexcept +#define JSONCPP_OP_EXPLICIT explicit +#elif defined(_MSC_VER) && _MSC_VER < 1900 +#define JSONCPP_NOEXCEPT throw() +#define JSONCPP_OP_EXPLICIT explicit +#elif defined(_MSC_VER) && _MSC_VER >= 1900 +#define JSONCPP_NOEXCEPT noexcept +#define JSONCPP_OP_EXPLICIT explicit +#else +#define JSONCPP_NOEXCEPT throw() +#define JSONCPP_OP_EXPLICIT +#endif + +#ifdef __clang__ +#if __has_extension(attribute_deprecated_with_message) +#define JSONCPP_DEPRECATED(message) __attribute__((deprecated(message))) +#endif +#elif defined __GNUC__ // not clang (gcc comes later since clang emulates gcc) +#if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)) +#define JSONCPP_DEPRECATED(message) __attribute__((deprecated(message))) +#elif (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)) +#define JSONCPP_DEPRECATED(message) __attribute__((__deprecated__)) +#endif // GNUC version +#elif defined(_MSC_VER) // MSVC (after clang because clang on Windows emulates MSVC) +#define JSONCPP_DEPRECATED(message) __declspec(deprecated(message)) +#endif // __clang__ || __GNUC__ || _MSC_VER + +#if !defined(JSONCPP_DEPRECATED) +#define JSONCPP_DEPRECATED(message) +#endif // if !defined(JSONCPP_DEPRECATED) + +#if __GNUC__ >= 6 +#define JSON_USE_INT64_DOUBLE_CONVERSION 1 +#endif + +#if !defined(JSON_IS_AMALGAMATION) + +#include "allocator.h" +#include "version.h" + +#endif // if !defined(JSON_IS_AMALGAMATION) + +namespace Json { +typedef int Int; +typedef unsigned int UInt; +#if defined(JSON_NO_INT64) +typedef int LargestInt; +typedef unsigned int LargestUInt; +#undef JSON_HAS_INT64 +#else // if defined(JSON_NO_INT64) +// For Microsoft Visual use specific types as long long is not supported +#if defined(_MSC_VER) // Microsoft Visual Studio +typedef __int64 Int64; +typedef unsigned __int64 UInt64; +#else // if defined(_MSC_VER) // Other platforms, use long long +typedef int64_t Int64; +typedef uint64_t UInt64; +#endif // if defined(_MSC_VER) +typedef Int64 LargestInt; +typedef UInt64 LargestUInt; +#define JSON_HAS_INT64 +#endif // if defined(JSON_NO_INT64) + +template +using Allocator = typename std::conditional, + std::allocator>::type; +using String = std::basic_string, Allocator>; +using IStringStream = std::basic_istringstream; +using OStringStream = std::basic_ostringstream; +using IStream = std::istream; +using OStream = std::ostream; +} // namespace Json + +// Legacy names (formerly macros). +using JSONCPP_STRING = Json::String; +using JSONCPP_ISTRINGSTREAM = Json::IStringStream; +using JSONCPP_OSTRINGSTREAM = Json::OStringStream; +using JSONCPP_ISTREAM = Json::IStream; +using JSONCPP_OSTREAM = Json::OStream; + +#endif // JSON_CONFIG_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/config.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/forwards.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_FORWARDS_H_INCLUDED +#define JSON_FORWARDS_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +#include "config.h" +#endif // if !defined(JSON_IS_AMALGAMATION) + +namespace Json { + +// writer.h +class FastWriter; +class StyledWriter; + +// reader.h +class Reader; + +// features.h +class Features; + +// value.h +typedef unsigned int ArrayIndex; +class StaticString; +class Path; +class PathArgument; +class Value; +class ValueIteratorBase; +class ValueIterator; +class ValueConstIterator; + +} // namespace Json + +#endif // JSON_FORWARDS_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/forwards.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/features.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef CPPTL_JSON_FEATURES_H_INCLUDED +#define CPPTL_JSON_FEATURES_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +#include "forwards.h" +#endif // if !defined(JSON_IS_AMALGAMATION) + +#pragma pack(push, 8) + +namespace Json { + +/** \brief Configuration passed to reader and writer. + * This configuration object can be used to force the Reader or Writer + * to behave in a standard conforming way. + */ +class JSON_API Features { +public: + /** \brief A configuration that allows all features and assumes all strings + * are UTF-8. + * - C & C++ comments are allowed + * - Root object can be any JSON value + * - Assumes Value strings are encoded in UTF-8 + */ + static Features all(); + + /** \brief A configuration that is strictly compatible with the JSON + * specification. + * - Comments are forbidden. + * - Root object must be either an array or an object value. + * - Assumes Value strings are encoded in UTF-8 + */ + static Features strictMode(); + + /** \brief Initialize the configuration like JsonConfig::allFeatures; + */ + Features(); + + /// \c true if comments are allowed. Default: \c true. + bool allowComments_{true}; + + /// \c true if root must be either an array or an object value. Default: \c + /// false. + bool strictRoot_{false}; + + /// \c true if dropped null placeholders are allowed. Default: \c false. + bool allowDroppedNullPlaceholders_{false}; + + /// \c true if numeric object key are allowed. Default: \c false. + bool allowNumericKeys_{false}; +}; + +} // namespace Json + +#pragma pack(pop) + +#endif // CPPTL_JSON_FEATURES_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/features.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/value.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef CPPTL_JSON_H_INCLUDED +#define CPPTL_JSON_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +#include "forwards.h" +#endif // if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include +#include +#include + +#ifndef JSON_USE_CPPTL_SMALLMAP +#include +#else +#include +#endif +#ifdef JSON_USE_CPPTL +#include +#endif + +// Conditional NORETURN attribute on the throw functions would: +// a) suppress false positives from static code analysis +// b) possibly improve optimization opportunities. +#if !defined(JSONCPP_NORETURN) +#if defined(_MSC_VER) +#define JSONCPP_NORETURN __declspec(noreturn) +#elif defined(__GNUC__) +#define JSONCPP_NORETURN __attribute__((__noreturn__)) +#else +#define JSONCPP_NORETURN +#endif +#endif + +// Disable warning C4251: : needs to have dll-interface to +// be used by... +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) +#pragma warning(push) +#pragma warning(disable : 4251) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +#pragma pack(push, 8) + +/** \brief JSON (JavaScript Object Notation). + */ +namespace Json { + +/** Base class for all exceptions we throw. + * + * We use nothing but these internally. Of course, STL can throw others. + */ +class JSON_API Exception : public std::exception { +public: + Exception(String msg); + ~Exception() JSONCPP_NOEXCEPT override; + char const* what() const JSONCPP_NOEXCEPT override; + +protected: + String msg_; +}; + +/** Exceptions which the user cannot easily avoid. + * + * E.g. out-of-memory (when we use malloc), stack-overflow, malicious input + * + * \remark derived from Json::Exception + */ +class JSON_API RuntimeError : public Exception { +public: + RuntimeError(String const& msg); +}; + +/** Exceptions thrown by JSON_ASSERT/JSON_FAIL macros. + * + * These are precondition-violations (user bugs) and internal errors (our bugs). + * + * \remark derived from Json::Exception + */ +class JSON_API LogicError : public Exception { +public: + LogicError(String const& msg); +}; + +/// used internally +JSONCPP_NORETURN void throwRuntimeError(String const& msg); +/// used internally +JSONCPP_NORETURN void throwLogicError(String const& msg); + +/** \brief Type of the value held by a Value object. + */ +enum ValueType { + nullValue = 0, ///< 'null' value + intValue, ///< signed integer value + uintValue, ///< unsigned integer value + realValue, ///< double value + stringValue, ///< UTF-8 string value + booleanValue, ///< bool value + arrayValue, ///< array value (ordered list) + objectValue ///< object value (collection of name/value pairs). +}; + +enum CommentPlacement { + commentBefore = 0, ///< a comment placed on the line before a value + commentAfterOnSameLine, ///< a comment just after a value on the same line + commentAfter, ///< a comment on the line after a value (only make sense for + /// root value) + numberOfCommentPlacement +}; + +/** \brief Type of precision for formatting of real values. + */ +enum PrecisionType { + significantDigits = 0, ///< we set max number of significant digits in string + decimalPlaces ///< we set max number of digits after "." in string +}; + +//# ifdef JSON_USE_CPPTL +// typedef CppTL::AnyEnumerator EnumMemberNames; +// typedef CppTL::AnyEnumerator EnumValues; +//# endif + +/** \brief Lightweight wrapper to tag static string. + * + * Value constructor and objectValue member assignment takes advantage of the + * StaticString and avoid the cost of string duplication when storing the + * string or the member name. + * + * Example of usage: + * \code + * Json::Value aValue( StaticString("some text") ); + * Json::Value object; + * static const StaticString code("code"); + * object[code] = 1234; + * \endcode + */ +class JSON_API StaticString { +public: + explicit StaticString(const char* czstring) : c_str_(czstring) {} + + operator const char*() const { return c_str_; } + + const char* c_str() const { return c_str_; } + +private: + const char* c_str_; +}; + +/** \brief Represents a JSON value. + * + * This class is a discriminated union wrapper that can represents a: + * - signed integer [range: Value::minInt - Value::maxInt] + * - unsigned integer (range: 0 - Value::maxUInt) + * - double + * - UTF-8 string + * - boolean + * - 'null' + * - an ordered list of Value + * - collection of name/value pairs (javascript object) + * + * The type of the held value is represented by a #ValueType and + * can be obtained using type(). + * + * Values of an #objectValue or #arrayValue can be accessed using operator[]() + * methods. + * Non-const methods will automatically create the a #nullValue element + * if it does not exist. + * The sequence of an #arrayValue will be automatically resized and initialized + * with #nullValue. resize() can be used to enlarge or truncate an #arrayValue. + * + * The get() methods can be used to obtain default value in the case the + * required element does not exist. + * + * It is possible to iterate over the list of member keys of an object using + * the getMemberNames() method. + * + * \note #Value string-length fit in size_t, but keys must be < 2^30. + * (The reason is an implementation detail.) A #CharReader will raise an + * exception if a bound is exceeded to avoid security holes in your app, + * but the Value API does *not* check bounds. That is the responsibility + * of the caller. + */ +class JSON_API Value { + friend class ValueIteratorBase; + +public: + typedef std::vector Members; + typedef ValueIterator iterator; + typedef ValueConstIterator const_iterator; + typedef Json::UInt UInt; + typedef Json::Int Int; +#if defined(JSON_HAS_INT64) + typedef Json::UInt64 UInt64; + typedef Json::Int64 Int64; +#endif // defined(JSON_HAS_INT64) + typedef Json::LargestInt LargestInt; + typedef Json::LargestUInt LargestUInt; + typedef Json::ArrayIndex ArrayIndex; + + // Required for boost integration, e. g. BOOST_TEST + typedef std::string value_type; + + static const Value& null; ///< We regret this reference to a global instance; + ///< prefer the simpler Value(). + static const Value& nullRef; ///< just a kludge for binary-compatibility; same + ///< as null + static Value const& nullSingleton(); ///< Prefer this to null or nullRef. + + /// Minimum signed integer value that can be stored in a Json::Value. + static const LargestInt minLargestInt; + /// Maximum signed integer value that can be stored in a Json::Value. + static const LargestInt maxLargestInt; + /// Maximum unsigned integer value that can be stored in a Json::Value. + static const LargestUInt maxLargestUInt; + + /// Minimum signed int value that can be stored in a Json::Value. + static const Int minInt; + /// Maximum signed int value that can be stored in a Json::Value. + static const Int maxInt; + /// Maximum unsigned int value that can be stored in a Json::Value. + static const UInt maxUInt; + +#if defined(JSON_HAS_INT64) + /// Minimum signed 64 bits int value that can be stored in a Json::Value. + static const Int64 minInt64; + /// Maximum signed 64 bits int value that can be stored in a Json::Value. + static const Int64 maxInt64; + /// Maximum unsigned 64 bits int value that can be stored in a Json::Value. + static const UInt64 maxUInt64; +#endif // defined(JSON_HAS_INT64) + + /// Default precision for real value for string representation. + static const UInt defaultRealPrecision; + +// Workaround for bug in the NVIDIAs CUDA 9.1 nvcc compiler +// when using gcc and clang backend compilers. CZString +// cannot be defined as private. See issue #486 +#ifdef __NVCC__ +public: +#else +private: +#endif +#ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION + class CZString { + public: + enum DuplicationPolicy { noDuplication = 0, duplicate, duplicateOnCopy }; + CZString(ArrayIndex index); + CZString(char const* str, unsigned length, DuplicationPolicy allocate); + CZString(CZString const& other); + CZString(CZString&& other); + ~CZString(); + CZString& operator=(const CZString& other); + CZString& operator=(CZString&& other); + + bool operator<(CZString const& other) const; + bool operator==(CZString const& other) const; + ArrayIndex index() const; + // const char* c_str() const; ///< \deprecated + char const* data() const; + unsigned length() const; + bool isStaticString() const; + + private: + void swap(CZString& other); + + struct StringStorage { + unsigned policy_ : 2; + unsigned length_ : 30; // 1GB max + }; + + char const* cstr_; // actually, a prefixed string, unless policy is noDup + union { + ArrayIndex index_; + StringStorage storage_; + }; + }; + +public: +#ifndef JSON_USE_CPPTL_SMALLMAP + typedef std::map ObjectValues; +#else + typedef CppTL::SmallMap ObjectValues; +#endif // ifndef JSON_USE_CPPTL_SMALLMAP +#endif // ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION + +public: + /** \brief Create a default Value of the given type. + + This is a very useful constructor. + To create an empty array, pass arrayValue. + To create an empty object, pass objectValue. + Another Value can then be set to this one by assignment. +This is useful since clear() and resize() will not alter types. + + Examples: +\code +Json::Value null_value; // null +Json::Value arr_value(Json::arrayValue); // [] +Json::Value obj_value(Json::objectValue); // {} +\endcode + */ + Value(ValueType type = nullValue); + Value(Int value); + Value(UInt value); +#if defined(JSON_HAS_INT64) + Value(Int64 value); + Value(UInt64 value); +#endif // if defined(JSON_HAS_INT64) + Value(double value); + Value(const char* value); ///< Copy til first 0. (NULL causes to seg-fault.) + Value(const char* begin, const char* end); ///< Copy all, incl zeroes. + /** \brief Constructs a value from a static string. + + * Like other value string constructor but do not duplicate the string for + * internal storage. The given string must remain alive after the call to this + * constructor. + * \note This works only for null-terminated strings. (We cannot change the + * size of this class, so we have nowhere to store the length, + * which might be computed later for various operations.) + * + * Example of usage: + * \code + * static StaticString foo("some text"); + * Json::Value aValue(foo); + * \endcode + */ + Value(const StaticString& value); + Value(const String& value); ///< Copy data() til size(). Embedded + ///< zeroes too. +#ifdef JSON_USE_CPPTL + Value(const CppTL::ConstString& value); +#endif + Value(bool value); + Value(const Value& other); + Value(Value&& other); + ~Value(); + + /// \note Overwrite existing comments. To preserve comments, use + /// #swapPayload(). + Value& operator=(const Value& other); + Value& operator=(Value&& other); + + /// Swap everything. + void swap(Value& other); + /// Swap values but leave comments and source offsets in place. + void swapPayload(Value& other); + + /// copy everything. + void copy(const Value& other); + /// copy values but leave comments and source offsets in place. + void copyPayload(const Value& other); + + ValueType type() const; + + /// Compare payload only, not comments etc. + bool operator<(const Value& other) const; + bool operator<=(const Value& other) const; + bool operator>=(const Value& other) const; + bool operator>(const Value& other) const; + bool operator==(const Value& other) const; + bool operator!=(const Value& other) const; + int compare(const Value& other) const; + + const char* asCString() const; ///< Embedded zeroes could cause you trouble! +#if JSONCPP_USING_SECURE_MEMORY + unsigned getCStringLength() const; // Allows you to understand the length of + // the CString +#endif + String asString() const; ///< Embedded zeroes are possible. + /** Get raw char* of string-value. + * \return false if !string. (Seg-fault if str or end are NULL.) + */ + bool getString(char const** begin, char const** end) const; +#ifdef JSON_USE_CPPTL + CppTL::ConstString asConstString() const; +#endif + Int asInt() const; + UInt asUInt() const; +#if defined(JSON_HAS_INT64) + Int64 asInt64() const; + UInt64 asUInt64() const; +#endif // if defined(JSON_HAS_INT64) + LargestInt asLargestInt() const; + LargestUInt asLargestUInt() const; + float asFloat() const; + double asDouble() const; + bool asBool() const; + + bool isNull() const; + bool isBool() const; + bool isInt() const; + bool isInt64() const; + bool isUInt() const; + bool isUInt64() const; + bool isIntegral() const; + bool isDouble() const; + bool isNumeric() const; + bool isString() const; + bool isArray() const; + bool isObject() const; + + bool isConvertibleTo(ValueType other) const; + + /// Number of values in array or object + ArrayIndex size() const; + + /// \brief Return true if empty array, empty object, or null; + /// otherwise, false. + bool empty() const; + + /// Return !isNull() + JSONCPP_OP_EXPLICIT operator bool() const; + + /// Remove all object members and array elements. + /// \pre type() is arrayValue, objectValue, or nullValue + /// \post type() is unchanged + void clear(); + + /// Resize the array to newSize elements. + /// New elements are initialized to null. + /// May only be called on nullValue or arrayValue. + /// \pre type() is arrayValue or nullValue + /// \post type() is arrayValue + void resize(ArrayIndex newSize); + + /// Access an array element (zero based index ). + /// If the array contains less than index element, then null value are + /// inserted + /// in the array so that its size is index+1. + /// (You may need to say 'value[0u]' to get your compiler to distinguish + /// this from the operator[] which takes a string.) + Value& operator[](ArrayIndex index); + + /// Access an array element (zero based index ). + /// If the array contains less than index element, then null value are + /// inserted + /// in the array so that its size is index+1. + /// (You may need to say 'value[0u]' to get your compiler to distinguish + /// this from the operator[] which takes a string.) + Value& operator[](int index); + + /// Access an array element (zero based index ) + /// (You may need to say 'value[0u]' to get your compiler to distinguish + /// this from the operator[] which takes a string.) + const Value& operator[](ArrayIndex index) const; + + /// Access an array element (zero based index ) + /// (You may need to say 'value[0u]' to get your compiler to distinguish + /// this from the operator[] which takes a string.) + const Value& operator[](int index) const; + + /// If the array contains at least index+1 elements, returns the element + /// value, + /// otherwise returns defaultValue. + Value get(ArrayIndex index, const Value& defaultValue) const; + /// Return true if index < size(). + bool isValidIndex(ArrayIndex index) const; + /// \brief Append value to array at the end. + /// + /// Equivalent to jsonvalue[jsonvalue.size()] = value; + Value& append(const Value& value); + Value& append(Value&& value); + + /// Access an object value by name, create a null member if it does not exist. + /// \note Because of our implementation, keys are limited to 2^30 -1 chars. + /// Exceeding that will cause an exception. + Value& operator[](const char* key); + /// Access an object value by name, returns null if there is no member with + /// that name. + const Value& operator[](const char* key) const; + /// Access an object value by name, create a null member if it does not exist. + /// \param key may contain embedded nulls. + Value& operator[](const String& key); + /// Access an object value by name, returns null if there is no member with + /// that name. + /// \param key may contain embedded nulls. + const Value& operator[](const String& key) const; + /** \brief Access an object value by name, create a null member if it does not + exist. + + * If the object has no entry for that name, then the member name used to + store + * the new entry is not duplicated. + * Example of use: + * \code + * Json::Value object; + * static const StaticString code("code"); + * object[code] = 1234; + * \endcode + */ + Value& operator[](const StaticString& key); +#ifdef JSON_USE_CPPTL + /// Access an object value by name, create a null member if it does not exist. + Value& operator[](const CppTL::ConstString& key); + /// Access an object value by name, returns null if there is no member with + /// that name. + const Value& operator[](const CppTL::ConstString& key) const; +#endif + /// Return the member named key if it exist, defaultValue otherwise. + /// \note deep copy + Value get(const char* key, const Value& defaultValue) const; + /// Return the member named key if it exist, defaultValue otherwise. + /// \note deep copy + /// \note key may contain embedded nulls. + Value + get(const char* begin, const char* end, const Value& defaultValue) const; + /// Return the member named key if it exist, defaultValue otherwise. + /// \note deep copy + /// \param key may contain embedded nulls. + Value get(const String& key, const Value& defaultValue) const; +#ifdef JSON_USE_CPPTL + /// Return the member named key if it exist, defaultValue otherwise. + /// \note deep copy + Value get(const CppTL::ConstString& key, const Value& defaultValue) const; +#endif + /// Most general and efficient version of isMember()const, get()const, + /// and operator[]const + /// \note As stated elsewhere, behavior is undefined if (end-begin) >= 2^30 + Value const* find(char const* begin, char const* end) const; + /// Most general and efficient version of object-mutators. + /// \note As stated elsewhere, behavior is undefined if (end-begin) >= 2^30 + /// \return non-zero, but JSON_ASSERT if this is neither object nor nullValue. + Value* demand(char const* begin, char const* end); + /// \brief Remove and return the named member. + /// + /// Do nothing if it did not exist. + /// \pre type() is objectValue or nullValue + /// \post type() is unchanged + void removeMember(const char* key); + /// Same as removeMember(const char*) + /// \param key may contain embedded nulls. + void removeMember(const String& key); + /// Same as removeMember(const char* begin, const char* end, Value* removed), + /// but 'key' is null-terminated. + bool removeMember(const char* key, Value* removed); + /** \brief Remove the named map member. + + Update 'removed' iff removed. + \param key may contain embedded nulls. + \return true iff removed (no exceptions) + */ + bool removeMember(String const& key, Value* removed); + /// Same as removeMember(String const& key, Value* removed) + bool removeMember(const char* begin, const char* end, Value* removed); + /** \brief Remove the indexed array element. + + O(n) expensive operations. + Update 'removed' iff removed. + \return true if removed (no exceptions) + */ + bool removeIndex(ArrayIndex index, Value* removed); + + /// Return true if the object has a member named key. + /// \note 'key' must be null-terminated. + bool isMember(const char* key) const; + /// Return true if the object has a member named key. + /// \param key may contain embedded nulls. + bool isMember(const String& key) const; + /// Same as isMember(String const& key)const + bool isMember(const char* begin, const char* end) const; +#ifdef JSON_USE_CPPTL + /// Return true if the object has a member named key. + bool isMember(const CppTL::ConstString& key) const; +#endif + + /// \brief Return a list of the member names. + /// + /// If null, return an empty list. + /// \pre type() is objectValue or nullValue + /// \post if type() was nullValue, it remains nullValue + Members getMemberNames() const; + + //# ifdef JSON_USE_CPPTL + // EnumMemberNames enumMemberNames() const; + // EnumValues enumValues() const; + //# endif + + /// \deprecated Always pass len. + JSONCPP_DEPRECATED("Use setComment(String const&) instead.") + void setComment(const char* comment, CommentPlacement placement) { + setComment(String(comment, strlen(comment)), placement); + } + /// Comments must be //... or /* ... */ + void setComment(const char* comment, size_t len, CommentPlacement placement) { + setComment(String(comment, len), placement); + } + /// Comments must be //... or /* ... */ + void setComment(String comment, CommentPlacement placement); + bool hasComment(CommentPlacement placement) const; + /// Include delimiters and embedded newlines. + String getComment(CommentPlacement placement) const; + + String toStyledString() const; + + const_iterator begin() const; + const_iterator end() const; + + iterator begin(); + iterator end(); + + // Accessors for the [start, limit) range of bytes within the JSON text from + // which this value was parsed, if any. + void setOffsetStart(ptrdiff_t start); + void setOffsetLimit(ptrdiff_t limit); + ptrdiff_t getOffsetStart() const; + ptrdiff_t getOffsetLimit() const; + +private: + void setType(ValueType v) { bits_.value_type_ = static_cast (v); } + bool isAllocated() const { return bits_.allocated_; } + void setIsAllocated(bool v) { bits_.allocated_ = v; } + + void initBasic(ValueType type, bool allocated = false); + void dupPayload(const Value& other); + void releasePayload(); + void dupMeta(const Value& other); + + Value& resolveReference(const char* key); + Value& resolveReference(const char* key, const char* end); + + // struct MemberNamesTransform + //{ + // typedef const char *result_type; + // const char *operator()( const CZString &name ) const + // { + // return name.c_str(); + // } + //}; + + union ValueHolder { + LargestInt int_; + LargestUInt uint_; + double real_; + bool bool_; + char* string_; // if allocated_, ptr to { unsigned, char[] }. + ObjectValues* map_; + } value_; + + struct { + // Really a ValueType, but types should agree for bitfield packing. + unsigned int value_type_ : 8; + // Unless allocated_, string_ must be null-terminated. + unsigned int allocated_ : 1; + } bits_; + + class Comments { + public: + Comments() = default; + Comments(const Comments& that); + Comments(Comments&& that); + Comments& operator=(const Comments& that); + Comments& operator=(Comments&& that); + bool has(CommentPlacement slot) const; + String get(CommentPlacement slot) const; + void set(CommentPlacement slot, String s); + + private: + using Array = std::array; + std::unique_ptr ptr_; + }; + Comments comments_; + + // [start, limit) byte offsets in the source JSON text from which this Value + // was extracted. + ptrdiff_t start_; + ptrdiff_t limit_; +}; + +/** \brief Experimental and untested: represents an element of the "path" to + * access a node. + */ +class JSON_API PathArgument { +public: + friend class Path; + + PathArgument(); + PathArgument(ArrayIndex index); + PathArgument(const char* key); + PathArgument(const String& key); + +private: + enum Kind { kindNone = 0, kindIndex, kindKey }; + String key_; + ArrayIndex index_{}; + Kind kind_{kindNone}; +}; + +/** \brief Experimental and untested: represents a "path" to access a node. + * + * Syntax: + * - "." => root node + * - ".[n]" => elements at index 'n' of root node (an array value) + * - ".name" => member named 'name' of root node (an object value) + * - ".name1.name2.name3" + * - ".[0][1][2].name1[3]" + * - ".%" => member name is provided as parameter + * - ".[%]" => index is provied as parameter + */ +class JSON_API Path { +public: + Path(const String& path, + const PathArgument& a1 = PathArgument(), + const PathArgument& a2 = PathArgument(), + const PathArgument& a3 = PathArgument(), + const PathArgument& a4 = PathArgument(), + const PathArgument& a5 = PathArgument()); + + const Value& resolve(const Value& root) const; + Value resolve(const Value& root, const Value& defaultValue) const; + /// Creates the "path" to access the specified node and returns a reference on + /// the node. + Value& make(Value& root) const; + +private: + typedef std::vector InArgs; + typedef std::vector Args; + + void makePath(const String& path, const InArgs& in); + void addPathInArg(const String& path, + const InArgs& in, + InArgs::const_iterator& itInArg, + PathArgument::Kind kind); + static void invalidPath(const String& path, int location); + + Args args_; +}; + +/** \brief base class for Value iterators. + * + */ +class JSON_API ValueIteratorBase { +public: + typedef std::bidirectional_iterator_tag iterator_category; + typedef unsigned int size_t; + typedef int difference_type; + typedef ValueIteratorBase SelfType; + + bool operator==(const SelfType& other) const { return isEqual(other); } + + bool operator!=(const SelfType& other) const { return !isEqual(other); } + + difference_type operator-(const SelfType& other) const { + return other.computeDistance(*this); + } + + /// Return either the index or the member name of the referenced value as a + /// Value. + Value key() const; + + /// Return the index of the referenced Value, or -1 if it is not an + /// arrayValue. + UInt index() const; + + /// Return the member name of the referenced Value, or "" if it is not an + /// objectValue. + /// \note Avoid `c_str()` on result, as embedded zeroes are possible. + String name() const; + + /// Return the member name of the referenced Value. "" if it is not an + /// objectValue. + /// \deprecated This cannot be used for UTF-8 strings, since there can be + /// embedded nulls. + JSONCPP_DEPRECATED("Use `key = name();` instead.") + char const* memberName() const; + /// Return the member name of the referenced Value, or NULL if it is not an + /// objectValue. + /// \note Better version than memberName(). Allows embedded nulls. + char const* memberName(char const** end) const; + +protected: + Value& deref() const; + + void increment(); + + void decrement(); + + difference_type computeDistance(const SelfType& other) const; + + bool isEqual(const SelfType& other) const; + + void copy(const SelfType& other); + +private: + Value::ObjectValues::iterator current_; + // Indicates that iterator is for a null value. + bool isNull_{true}; + +public: + // For some reason, BORLAND needs these at the end, rather + // than earlier. No idea why. + ValueIteratorBase(); + explicit ValueIteratorBase(const Value::ObjectValues::iterator& current); +}; + +/** \brief const iterator for object and array value. + * + */ +class JSON_API ValueConstIterator : public ValueIteratorBase { + friend class Value; + +public: + typedef const Value value_type; + // typedef unsigned int size_t; + // typedef int difference_type; + typedef const Value& reference; + typedef const Value* pointer; + typedef ValueConstIterator SelfType; + + ValueConstIterator(); + ValueConstIterator(ValueIterator const& other); + +private: + /*! \internal Use by Value to create an iterator. + */ + explicit ValueConstIterator(const Value::ObjectValues::iterator& current); + +public: + SelfType& operator=(const ValueIteratorBase& other); + + SelfType operator++(int) { + SelfType temp(*this); + ++*this; + return temp; + } + + SelfType operator--(int) { + SelfType temp(*this); + --*this; + return temp; + } + + SelfType& operator--() { + decrement(); + return *this; + } + + SelfType& operator++() { + increment(); + return *this; + } + + reference operator*() const { return deref(); } + + pointer operator->() const { return &deref(); } +}; + +/** \brief Iterator for object and array value. + */ +class JSON_API ValueIterator : public ValueIteratorBase { + friend class Value; + +public: + typedef Value value_type; + typedef unsigned int size_t; + typedef int difference_type; + typedef Value& reference; + typedef Value* pointer; + typedef ValueIterator SelfType; + + ValueIterator(); + explicit ValueIterator(const ValueConstIterator& other); + ValueIterator(const ValueIterator& other); + +private: + /*! \internal Use by Value to create an iterator. + */ + explicit ValueIterator(const Value::ObjectValues::iterator& current); + +public: + SelfType& operator=(const SelfType& other); + + SelfType operator++(int) { + SelfType temp(*this); + ++*this; + return temp; + } + + SelfType operator--(int) { + SelfType temp(*this); + --*this; + return temp; + } + + SelfType& operator--() { + decrement(); + return *this; + } + + SelfType& operator++() { + increment(); + return *this; + } + + reference operator*() const { return deref(); } + + pointer operator->() const { return &deref(); } +}; + +inline void swap(Value& a, Value& b) { a.swap(b); } + +} // namespace Json + +#pragma pack(pop) + +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) +#pragma warning(pop) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +#endif // CPPTL_JSON_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/value.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/reader.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef CPPTL_JSON_READER_H_INCLUDED +#define CPPTL_JSON_READER_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +#include "features.h" +#include "value.h" +#endif // if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include +#include +#include + +// Disable warning C4251: : needs to have dll-interface to +// be used by... +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) +#pragma warning(push) +#pragma warning(disable : 4251) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +#pragma pack(push, 8) + +namespace Json { + +/** \brief Unserialize a JSON document into a + *Value. + * + * \deprecated Use CharReader and CharReaderBuilder. + */ +class JSON_API Reader { +public: + typedef char Char; + typedef const Char* Location; + + /** \brief An error tagged with where in the JSON text it was encountered. + * + * The offsets give the [start, limit) range of bytes within the text. Note + * that this is bytes, not codepoints. + * + */ + struct StructuredError { + ptrdiff_t offset_start; + ptrdiff_t offset_limit; + String message; + }; + + /** \brief Constructs a Reader allowing all features + * for parsing. + */ + JSONCPP_DEPRECATED("Use CharReader and CharReaderBuilder instead") + Reader(); + + /** \brief Constructs a Reader allowing the specified feature set + * for parsing. + */ + JSONCPP_DEPRECATED("Use CharReader and CharReaderBuilder instead") + Reader(const Features& features); + + /** \brief Read a Value from a JSON + * document. + * \param document UTF-8 encoded string containing the document to read. + * \param root [out] Contains the root value of the document if it was + * successfully parsed. + * \param collectComments \c true to collect comment and allow writing them + * back during + * serialization, \c false to discard comments. + * This parameter is ignored if + * Features::allowComments_ + * is \c false. + * \return \c true if the document was successfully parsed, \c false if an + * error occurred. + */ + bool + parse(const std::string& document, Value& root, bool collectComments = true); + + /** \brief Read a Value from a JSON + document. + * \param beginDoc Pointer on the beginning of the UTF-8 encoded string of the + document to read. + * \param endDoc Pointer on the end of the UTF-8 encoded string of the + document to read. + * Must be >= beginDoc. + * \param root [out] Contains the root value of the document if it was + * successfully parsed. + * \param collectComments \c true to collect comment and allow writing them + back during + * serialization, \c false to discard comments. + * This parameter is ignored if + Features::allowComments_ + * is \c false. + * \return \c true if the document was successfully parsed, \c false if an + error occurred. + */ + bool parse(const char* beginDoc, + const char* endDoc, + Value& root, + bool collectComments = true); + + /// \brief Parse from input stream. + /// \see Json::operator>>(std::istream&, Json::Value&). + bool parse(IStream& is, Value& root, bool collectComments = true); + + /** \brief Returns a user friendly string that list errors in the parsed + * document. + * \return Formatted error message with the list of errors with their location + * in + * the parsed document. An empty string is returned if no error + * occurred + * during parsing. + * \deprecated Use getFormattedErrorMessages() instead (typo fix). + */ + JSONCPP_DEPRECATED("Use getFormattedErrorMessages() instead.") + String getFormatedErrorMessages() const; + + /** \brief Returns a user friendly string that list errors in the parsed + * document. + * \return Formatted error message with the list of errors with their location + * in + * the parsed document. An empty string is returned if no error + * occurred + * during parsing. + */ + String getFormattedErrorMessages() const; + + /** \brief Returns a vector of structured erros encounted while parsing. + * \return A (possibly empty) vector of StructuredError objects. Currently + * only one error can be returned, but the caller should tolerate + * multiple + * errors. This can occur if the parser recovers from a non-fatal + * parse error and then encounters additional errors. + */ + std::vector getStructuredErrors() const; + + /** \brief Add a semantic error message. + * \param value JSON Value location associated with the error + * \param message The error message. + * \return \c true if the error was successfully added, \c false if the + * Value offset exceeds the document size. + */ + bool pushError(const Value& value, const String& message); + + /** \brief Add a semantic error message with extra context. + * \param value JSON Value location associated with the error + * \param message The error message. + * \param extra Additional JSON Value location to contextualize the error + * \return \c true if the error was successfully added, \c false if either + * Value offset exceeds the document size. + */ + bool pushError(const Value& value, const String& message, const Value& extra); + + /** \brief Return whether there are any errors. + * \return \c true if there are no errors to report \c false if + * errors have occurred. + */ + bool good() const; + +private: + enum TokenType { + tokenEndOfStream = 0, + tokenObjectBegin, + tokenObjectEnd, + tokenArrayBegin, + tokenArrayEnd, + tokenString, + tokenNumber, + tokenTrue, + tokenFalse, + tokenNull, + tokenArraySeparator, + tokenMemberSeparator, + tokenComment, + tokenError + }; + + class Token { + public: + TokenType type_; + Location start_; + Location end_; + }; + + class ErrorInfo { + public: + Token token_; + String message_; + Location extra_; + }; + + typedef std::deque Errors; + + bool readToken(Token& token); + void skipSpaces(); + bool match(Location pattern, int patternLength); + bool readComment(); + bool readCStyleComment(); + bool readCppStyleComment(); + bool readString(); + void readNumber(); + bool readValue(); + bool readObject(Token& token); + bool readArray(Token& token); + bool decodeNumber(Token& token); + bool decodeNumber(Token& token, Value& decoded); + bool decodeString(Token& token); + bool decodeString(Token& token, String& decoded); + bool decodeDouble(Token& token); + bool decodeDouble(Token& token, Value& decoded); + bool decodeUnicodeCodePoint(Token& token, + Location& current, + Location end, + unsigned int& unicode); + bool decodeUnicodeEscapeSequence(Token& token, + Location& current, + Location end, + unsigned int& unicode); + bool addError(const String& message, Token& token, Location extra = nullptr); + bool recoverFromError(TokenType skipUntilToken); + bool addErrorAndRecover(const String& message, + Token& token, + TokenType skipUntilToken); + void skipUntilSpace(); + Value& currentValue(); + Char getNextChar(); + void + getLocationLineAndColumn(Location location, int& line, int& column) const; + String getLocationLineAndColumn(Location location) const; + void addComment(Location begin, Location end, CommentPlacement placement); + void skipCommentTokens(Token& token); + + static bool containsNewLine(Location begin, Location end); + static String normalizeEOL(Location begin, Location end); + + typedef std::stack Nodes; + Nodes nodes_; + Errors errors_; + String document_; + Location begin_{}; + Location end_{}; + Location current_{}; + Location lastValueEnd_{}; + Value* lastValue_{}; + String commentsBefore_; + Features features_; + bool collectComments_{}; +}; // Reader + +/** Interface for reading JSON from a char array. + */ +class JSON_API CharReader { +public: + virtual ~CharReader() = default; + /** \brief Read a Value from a JSON + document. + * The document must be a UTF-8 encoded string containing the document to + read. + * + * \param beginDoc Pointer on the beginning of the UTF-8 encoded string of the + document to read. + * \param endDoc Pointer on the end of the UTF-8 encoded string of the + document to read. + * Must be >= beginDoc. + * \param root [out] Contains the root value of the document if it was + * successfully parsed. + * \param errs [out] Formatted error messages (if not NULL) + * a user friendly string that lists errors in the parsed + * document. + * \return \c true if the document was successfully parsed, \c false if an + error occurred. + */ + virtual bool parse(char const* beginDoc, + char const* endDoc, + Value* root, + String* errs) = 0; + + class JSON_API Factory { + public: + virtual ~Factory() = default; + /** \brief Allocate a CharReader via operator new(). + * \throw std::exception if something goes wrong (e.g. invalid settings) + */ + virtual CharReader* newCharReader() const = 0; + }; // Factory +}; // CharReader + +/** \brief Build a CharReader implementation. + +Usage: +\code + using namespace Json; + CharReaderBuilder builder; + builder["collectComments"] = false; + Value value; + String errs; + bool ok = parseFromStream(builder, std::cin, &value, &errs); +\endcode +*/ +class JSON_API CharReaderBuilder : public CharReader::Factory { +public: + // Note: We use a Json::Value so that we can add data-members to this class + // without a major version bump. + /** Configuration of this builder. + These are case-sensitive. + Available settings (case-sensitive): + - `"collectComments": false or true` + - true to collect comment and allow writing them + back during serialization, false to discard comments. + This parameter is ignored if allowComments is false. + - `"allowComments": false or true` + - true if comments are allowed. + - `"strictRoot": false or true` + - true if root must be either an array or an object value + - `"allowDroppedNullPlaceholders": false or true` + - true if dropped null placeholders are allowed. (See + StreamWriterBuilder.) + - `"allowNumericKeys": false or true` + - true if numeric object keys are allowed. + - `"allowSingleQuotes": false or true` + - true if '' are allowed for strings (both keys and values) + - `"stackLimit": integer` + - Exceeding stackLimit (recursive depth of `readValue()`) will + cause an exception. + - This is a security issue (seg-faults caused by deeply nested JSON), + so the default is low. + - `"failIfExtra": false or true` + - If true, `parse()` returns false when extra non-whitespace trails + the JSON value in the input string. + - `"rejectDupKeys": false or true` + - If true, `parse()` returns false when a key is duplicated within an + object. + - `"allowSpecialFloats": false or true` + - If true, special float values (NaNs and infinities) are allowed + and their values are lossfree restorable. + + You can examine 'settings_` yourself + to see the defaults. You can also write and read them just like any + JSON Value. + \sa setDefaults() + */ + Json::Value settings_; + + CharReaderBuilder(); + ~CharReaderBuilder() override; + + CharReader* newCharReader() const override; + + /** \return true if 'settings' are legal and consistent; + * otherwise, indicate bad settings via 'invalid'. + */ + bool validate(Json::Value* invalid) const; + + /** A simple way to update a specific setting. + */ + Value& operator[](const String& key); + + /** Called by ctor, but you can use this to reset settings_. + * \pre 'settings' != NULL (but Json::null is fine) + * \remark Defaults: + * \snippet src/lib_json/json_reader.cpp CharReaderBuilderDefaults + */ + static void setDefaults(Json::Value* settings); + /** Same as old Features::strictMode(). + * \pre 'settings' != NULL (but Json::null is fine) + * \remark Defaults: + * \snippet src/lib_json/json_reader.cpp CharReaderBuilderStrictMode + */ + static void strictMode(Json::Value* settings); +}; + +/** Consume entire stream and use its begin/end. + * Someday we might have a real StreamReader, but for now this + * is convenient. + */ +bool JSON_API parseFromStream(CharReader::Factory const&, + IStream&, + Value* root, + std::string* errs); + +/** \brief Read from 'sin' into 'root'. + + Always keep comments from the input JSON. + + This can be used to read a file into a particular sub-object. + For example: + \code + Json::Value root; + cin >> root["dir"]["file"]; + cout << root; + \endcode + Result: + \verbatim + { + "dir": { + "file": { + // The input stream JSON would be nested here. + } + } + } + \endverbatim + \throw std::exception on parse error. + \see Json::operator<<() +*/ +JSON_API IStream& operator>>(IStream&, Value&); + +} // namespace Json + +#pragma pack(pop) + +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) +#pragma warning(pop) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +#endif // CPPTL_JSON_READER_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/reader.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/writer.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_WRITER_H_INCLUDED +#define JSON_WRITER_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +#include "value.h" +#endif // if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include + +// Disable warning C4251: : needs to have dll-interface to +// be used by... +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) && defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable : 4251) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +#pragma pack(push, 8) + +namespace Json { + +class Value; + +/** + +Usage: +\code + using namespace Json; + void writeToStdout(StreamWriter::Factory const& factory, Value const& value) { + std::unique_ptr const writer( + factory.newStreamWriter()); + writer->write(value, &std::cout); + std::cout << std::endl; // add lf and flush + } +\endcode +*/ +class JSON_API StreamWriter { +protected: + OStream* sout_; // not owned; will not delete +public: + StreamWriter(); + virtual ~StreamWriter(); + /** Write Value into document as configured in sub-class. + Do not take ownership of sout, but maintain a reference during function. + \pre sout != NULL + \return zero on success (For now, we always return zero, so check the + stream instead.) \throw std::exception possibly, depending on configuration + */ + virtual int write(Value const& root, OStream* sout) = 0; + + /** \brief A simple abstract factory. + */ + class JSON_API Factory { + public: + virtual ~Factory(); + /** \brief Allocate a CharReader via operator new(). + * \throw std::exception if something goes wrong (e.g. invalid settings) + */ + virtual StreamWriter* newStreamWriter() const = 0; + }; // Factory +}; // StreamWriter + +/** \brief Write into stringstream, then return string, for convenience. + * A StreamWriter will be created from the factory, used, and then deleted. + */ +String JSON_API writeString(StreamWriter::Factory const& factory, + Value const& root); + +/** \brief Build a StreamWriter implementation. + +Usage: +\code + using namespace Json; + Value value = ...; + StreamWriterBuilder builder; + builder["commentStyle"] = "None"; + builder["indentation"] = " "; // or whatever you like + std::unique_ptr writer( + builder.newStreamWriter()); + writer->write(value, &std::cout); + std::cout << std::endl; // add lf and flush +\endcode +*/ +class JSON_API StreamWriterBuilder : public StreamWriter::Factory { +public: + // Note: We use a Json::Value so that we can add data-members to this class + // without a major version bump. + /** Configuration of this builder. + Available settings (case-sensitive): + - "commentStyle": "None" or "All" + - "indentation": "". + - Setting this to an empty string also omits newline characters. + - "enableYAMLCompatibility": false or true + - slightly change the whitespace around colons + - "dropNullPlaceholders": false or true + - Drop the "null" string from the writer's output for nullValues. + Strictly speaking, this is not valid JSON. But when the output is being + fed to a browser's JavaScript, it makes for smaller output and the + browser can handle the output just fine. + - "useSpecialFloats": false or true + - If true, outputs non-finite floating point values in the following way: + NaN values as "NaN", positive infinity as "Infinity", and negative + infinity as "-Infinity". + - "precision": int + - Number of precision digits for formatting of real values. + - "precisionType": "significant"(default) or "decimal" + - Type of precision for formatting of real values. + + You can examine 'settings_` yourself + to see the defaults. You can also write and read them just like any + JSON Value. + \sa setDefaults() + */ + Json::Value settings_; + + StreamWriterBuilder(); + ~StreamWriterBuilder() override; + + /** + * \throw std::exception if something goes wrong (e.g. invalid settings) + */ + StreamWriter* newStreamWriter() const override; + + /** \return true if 'settings' are legal and consistent; + * otherwise, indicate bad settings via 'invalid'. + */ + bool validate(Json::Value* invalid) const; + /** A simple way to update a specific setting. + */ + Value& operator[](const String& key); + + /** Called by ctor, but you can use this to reset settings_. + * \pre 'settings' != NULL (but Json::null is fine) + * \remark Defaults: + * \snippet src/lib_json/json_writer.cpp StreamWriterBuilderDefaults + */ + static void setDefaults(Json::Value* settings); +}; + +/** \brief Abstract class for writers. + * \deprecated Use StreamWriter. (And really, this is an implementation detail.) + */ +class JSONCPP_DEPRECATED("Use StreamWriter instead") JSON_API Writer { +public: + virtual ~Writer(); + + virtual String write(const Value& root) = 0; +}; + +/** \brief Outputs a Value in JSON format + *without formatting (not human friendly). + * + * The JSON document is written in a single line. It is not intended for 'human' + *consumption, + * but may be useful to support feature such as RPC where bandwidth is limited. + * \sa Reader, Value + * \deprecated Use StreamWriterBuilder. + */ +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable : 4996) // Deriving from deprecated class +#endif +class JSONCPP_DEPRECATED("Use StreamWriterBuilder instead") JSON_API FastWriter + : public Writer { +public: + FastWriter(); + ~FastWriter() override = default; + + void enableYAMLCompatibility(); + + /** \brief Drop the "null" string from the writer's output for nullValues. + * Strictly speaking, this is not valid JSON. But when the output is being + * fed to a browser's JavaScript, it makes for smaller output and the + * browser can handle the output just fine. + */ + void dropNullPlaceholders(); + + void omitEndingLineFeed(); + +public: // overridden from Writer + String write(const Value& root) override; + +private: + void writeValue(const Value& value); + + String document_; + bool yamlCompatibilityEnabled_{false}; + bool dropNullPlaceholders_{false}; + bool omitEndingLineFeed_{false}; +}; +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +/** \brief Writes a Value in JSON format in a + *human friendly way. + * + * The rules for line break and indent are as follow: + * - Object value: + * - if empty then print {} without indent and line break + * - if not empty the print '{', line break & indent, print one value per + *line + * and then unindent and line break and print '}'. + * - Array value: + * - if empty then print [] without indent and line break + * - if the array contains no object value, empty array or some other value + *types, + * and all the values fit on one lines, then print the array on a single + *line. + * - otherwise, it the values do not fit on one line, or the array contains + * object or non empty array, then print one value per line. + * + * If the Value have comments then they are outputed according to their + *#CommentPlacement. + * + * \sa Reader, Value, Value::setComment() + * \deprecated Use StreamWriterBuilder. + */ +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable : 4996) // Deriving from deprecated class +#endif +class JSONCPP_DEPRECATED("Use StreamWriterBuilder instead") JSON_API + StyledWriter : public Writer { +public: + StyledWriter(); + ~StyledWriter() override = default; + +public: // overridden from Writer + /** \brief Serialize a Value in JSON format. + * \param root Value to serialize. + * \return String containing the JSON document that represents the root value. + */ + String write(const Value& root) override; + +private: + void writeValue(const Value& value); + void writeArrayValue(const Value& value); + bool isMultilineArray(const Value& value); + void pushValue(const String& value); + void writeIndent(); + void writeWithIndent(const String& value); + void indent(); + void unindent(); + void writeCommentBeforeValue(const Value& root); + void writeCommentAfterValueOnSameLine(const Value& root); + static bool hasCommentForValue(const Value& value); + static String normalizeEOL(const String& text); + + typedef std::vector ChildValues; + + ChildValues childValues_; + String document_; + String indentString_; + unsigned int rightMargin_{74}; + unsigned int indentSize_{3}; + bool addChildValues_{false}; +}; +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +/** \brief Writes a Value in JSON format in a + human friendly way, + to a stream rather than to a string. + * + * The rules for line break and indent are as follow: + * - Object value: + * - if empty then print {} without indent and line break + * - if not empty the print '{', line break & indent, print one value per + line + * and then unindent and line break and print '}'. + * - Array value: + * - if empty then print [] without indent and line break + * - if the array contains no object value, empty array or some other value + types, + * and all the values fit on one lines, then print the array on a single + line. + * - otherwise, it the values do not fit on one line, or the array contains + * object or non empty array, then print one value per line. + * + * If the Value have comments then they are outputed according to their + #CommentPlacement. + * + * \sa Reader, Value, Value::setComment() + * \deprecated Use StreamWriterBuilder. + */ +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable : 4996) // Deriving from deprecated class +#endif +class JSONCPP_DEPRECATED("Use StreamWriterBuilder instead") JSON_API + StyledStreamWriter { +public: + /** + * \param indentation Each level will be indented by this amount extra. + */ + StyledStreamWriter(String indentation = "\t"); + ~StyledStreamWriter() = default; + +public: + /** \brief Serialize a Value in JSON format. + * \param out Stream to write to. (Can be ostringstream, e.g.) + * \param root Value to serialize. + * \note There is no point in deriving from Writer, since write() should not + * return a value. + */ + void write(OStream& out, const Value& root); + +private: + void writeValue(const Value& value); + void writeArrayValue(const Value& value); + bool isMultilineArray(const Value& value); + void pushValue(const String& value); + void writeIndent(); + void writeWithIndent(const String& value); + void indent(); + void unindent(); + void writeCommentBeforeValue(const Value& root); + void writeCommentAfterValueOnSameLine(const Value& root); + static bool hasCommentForValue(const Value& value); + static String normalizeEOL(const String& text); + + typedef std::vector ChildValues; + + ChildValues childValues_; + OStream* document_; + String indentString_; + unsigned int rightMargin_{74}; + String indentation_; + bool addChildValues_ : 1; + bool indented_ : 1; +}; +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +#if defined(JSON_HAS_INT64) +String JSON_API valueToString(Int value); +String JSON_API valueToString(UInt value); +#endif // if defined(JSON_HAS_INT64) +String JSON_API valueToString(LargestInt value); +String JSON_API valueToString(LargestUInt value); +String JSON_API +valueToString(double value, + unsigned int precision = Value::defaultRealPrecision, + PrecisionType precisionType = PrecisionType::significantDigits); +String JSON_API valueToString(bool value); +String JSON_API valueToQuotedString(const char* value); + +/// \brief Output using the StyledStreamWriter. +/// \see Json::operator>>() +JSON_API OStream& operator<<(OStream&, const Value& root); + +} // namespace Json + +#pragma pack(pop) + +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) +#pragma warning(pop) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +#endif // JSON_WRITER_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/writer.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/assertions.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef CPPTL_JSON_ASSERTIONS_H_INCLUDED +#define CPPTL_JSON_ASSERTIONS_H_INCLUDED + +#include +#include + +#if !defined(JSON_IS_AMALGAMATION) +#include "config.h" +#endif // if !defined(JSON_IS_AMALGAMATION) + +/** It should not be possible for a maliciously designed file to + * cause an abort() or seg-fault, so these macros are used only + * for pre-condition violations and internal logic errors. + */ +#if JSON_USE_EXCEPTION + +// @todo <= add detail about condition in exception +#define JSON_ASSERT(condition) \ + { \ + if (!(condition)) { \ + Json::throwLogicError("assert json failed"); \ + } \ + } + +#define JSON_FAIL_MESSAGE(message) \ + { \ + OStringStream oss; \ + oss << message; \ + Json::throwLogicError(oss.str()); \ + abort(); \ + } + +#else // JSON_USE_EXCEPTION + +#define JSON_ASSERT(condition) assert(condition) + +// The call to assert() will show the failure message in debug builds. In +// release builds we abort, for a core-dump or debugger. +#define JSON_FAIL_MESSAGE(message) \ + { \ + OStringStream oss; \ + oss << message; \ + assert(false && oss.str().c_str()); \ + abort(); \ + } + +#endif + +#define JSON_ASSERT_MESSAGE(condition, message) \ + if (!(condition)) { \ + JSON_FAIL_MESSAGE(message); \ + } + +#endif // CPPTL_JSON_ASSERTIONS_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/assertions.h +// ////////////////////////////////////////////////////////////////////// + + + + + +#endif //ifndef JSON_AMALGAMATED_H_INCLUDED diff --git a/thirdparty/jsoncpp/jsoncpp.cpp b/thirdparty/jsoncpp/jsoncpp.cpp new file mode 100644 index 00000000..86ca3435 --- /dev/null +++ b/thirdparty/jsoncpp/jsoncpp.cpp @@ -0,0 +1,5406 @@ +/// Json-cpp amalgamated source (http://jsoncpp.sourceforge.net/). +/// It is intended to be used with #include "json/json.h" + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: LICENSE +// ////////////////////////////////////////////////////////////////////// + +/* +The JsonCpp library's source code, including accompanying documentation, +tests and demonstration applications, are licensed under the following +conditions... + +Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all +jurisdictions which recognize such a disclaimer. In such jurisdictions, +this software is released into the Public Domain. + +In jurisdictions which do not recognize Public Domain property (e.g. Germany as of +2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur and +The JsonCpp Authors, and is released under the terms of the MIT License (see below). + +In jurisdictions which recognize Public Domain property, the user of this +software may choose to accept it either as 1) Public Domain, 2) under the +conditions of the MIT License (see below), or 3) under the terms of dual +Public Domain/MIT License conditions described here, as they choose. + +The MIT License is about as close to Public Domain as a license can get, and is +described in clear, concise terms at: + + http://en.wikipedia.org/wiki/MIT_License + +The full text of the MIT License follows: + +======================================================================== +Copyright (c) 2007-2010 Baptiste Lepilleur and The JsonCpp Authors + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, +modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +======================================================================== +(END LICENSE TEXT) + +The MIT license is compatible with both the GPL and commercial +software, affording one all of the rights of Public Domain with the +minor nuisance of being required to keep the above copyright notice +and license text in the source code. Note also that by accepting the +Public Domain "license" you can re-license your copy using whatever +license you like. + +*/ + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: LICENSE +// ////////////////////////////////////////////////////////////////////// + + + + + + +#include "json/json.h" + +#ifndef JSON_IS_AMALGAMATION +#error "Compile with -I PATH_TO_JSON_DIRECTORY" +#endif + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: src/lib_json/json_tool.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef LIB_JSONCPP_JSON_TOOL_H_INCLUDED +#define LIB_JSONCPP_JSON_TOOL_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +#include +#endif + +// Also support old flag NO_LOCALE_SUPPORT +#ifdef NO_LOCALE_SUPPORT +#define JSONCPP_NO_LOCALE_SUPPORT +#endif + +#ifndef JSONCPP_NO_LOCALE_SUPPORT +#include +#endif + +/* This header provides common string manipulation support, such as UTF-8, + * portable conversion from/to string... + * + * It is an internal header that must not be exposed. + */ + +namespace Json { +static inline char getDecimalPoint() { +#ifdef JSONCPP_NO_LOCALE_SUPPORT + return '\0'; +#else + struct lconv* lc = localeconv(); + return lc ? *(lc->decimal_point) : '\0'; +#endif +} + +/// Converts a unicode code-point to UTF-8. +static inline String codePointToUTF8(unsigned int cp) { + String result; + + // based on description from http://en.wikipedia.org/wiki/UTF-8 + + if (cp <= 0x7f) { + result.resize(1); + result[0] = static_cast(cp); + } else if (cp <= 0x7FF) { + result.resize(2); + result[1] = static_cast(0x80 | (0x3f & cp)); + result[0] = static_cast(0xC0 | (0x1f & (cp >> 6))); + } else if (cp <= 0xFFFF) { + result.resize(3); + result[2] = static_cast(0x80 | (0x3f & cp)); + result[1] = static_cast(0x80 | (0x3f & (cp >> 6))); + result[0] = static_cast(0xE0 | (0xf & (cp >> 12))); + } else if (cp <= 0x10FFFF) { + result.resize(4); + result[3] = static_cast(0x80 | (0x3f & cp)); + result[2] = static_cast(0x80 | (0x3f & (cp >> 6))); + result[1] = static_cast(0x80 | (0x3f & (cp >> 12))); + result[0] = static_cast(0xF0 | (0x7 & (cp >> 18))); + } + + return result; +} + +enum { + /// Constant that specify the size of the buffer that must be passed to + /// uintToString. + uintToStringBufferSize = 3 * sizeof(LargestUInt) + 1 +}; + +// Defines a char buffer for use with uintToString(). +typedef char UIntToStringBuffer[uintToStringBufferSize]; + +/** Converts an unsigned integer to string. + * @param value Unsigned integer to convert to string + * @param current Input/Output string buffer. + * Must have at least uintToStringBufferSize chars free. + */ +static inline void uintToString(LargestUInt value, char*& current) { + *--current = 0; + do { + *--current = static_cast(value % 10U + static_cast('0')); + value /= 10; + } while (value != 0); +} + +/** Change ',' to '.' everywhere in buffer. + * + * We had a sophisticated way, but it did not work in WinCE. + * @see https://github.com/open-source-parsers/jsoncpp/pull/9 + */ +template Iter fixNumericLocale(Iter begin, Iter end) { + for (; begin != end; ++begin) { + if (*begin == ',') { + *begin = '.'; + } + } + return begin; +} + +template void fixNumericLocaleInput(Iter begin, Iter end) { + char decimalPoint = getDecimalPoint(); + if (decimalPoint == '\0' || decimalPoint == '.') { + return; + } + for (; begin != end; ++begin) { + if (*begin == '.') { + *begin = decimalPoint; + } + } +} + +/** + * Return iterator that would be the new end of the range [begin,end), if we + * were to delete zeros in the end of string, but not the last zero before '.'. + */ +template Iter fixZerosInTheEnd(Iter begin, Iter end) { + for (; begin != end; --end) { + if (*(end - 1) != '0') { + return end; + } + // Don't delete the last zero before the decimal point. + if (begin != (end - 1) && *(end - 2) == '.') { + return end; + } + } + return end; +} + +} // namespace Json + +#endif // LIB_JSONCPP_JSON_TOOL_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: src/lib_json/json_tool.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: src/lib_json/json_reader.cpp +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2011 Baptiste Lepilleur and The JsonCpp Authors +// Copyright (C) 2016 InfoTeCS JSC. All rights reserved. +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#if !defined(JSON_IS_AMALGAMATION) +#include "json_tool.h" +#include +#include +#include +#endif // if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#if __cplusplus >= 201103L + +#if !defined(sscanf) +#define sscanf std::sscanf +#endif + +#endif //__cplusplus + +#if defined(_MSC_VER) +#if !defined(_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES) +#define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1 +#endif //_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES +#endif //_MSC_VER + +#if defined(_MSC_VER) +// Disable warning about strdup being deprecated. +#pragma warning(disable : 4996) +#endif + +// Define JSONCPP_DEPRECATED_STACK_LIMIT as an appropriate integer at compile +// time to change the stack limit +#if !defined(JSONCPP_DEPRECATED_STACK_LIMIT) +#define JSONCPP_DEPRECATED_STACK_LIMIT 1000 +#endif + +static size_t const stackLimit_g = + JSONCPP_DEPRECATED_STACK_LIMIT; // see readValue() + +namespace Json { + +#if __cplusplus >= 201103L || (defined(_CPPLIB_VER) && _CPPLIB_VER >= 520) +typedef std::unique_ptr CharReaderPtr; +#else +typedef std::auto_ptr CharReaderPtr; +#endif + +// Implementation of class Features +// //////////////////////////////// + +Features::Features() = default; + +Features Features::all() { return {}; } + +Features Features::strictMode() { + Features features; + features.allowComments_ = false; + features.strictRoot_ = true; + features.allowDroppedNullPlaceholders_ = false; + features.allowNumericKeys_ = false; + return features; +} + +// Implementation of class Reader +// //////////////////////////////// + +bool Reader::containsNewLine(Reader::Location begin, Reader::Location end) { + for (; begin < end; ++begin) + if (*begin == '\n' || *begin == '\r') + return true; + return false; +} + +// Class Reader +// ////////////////////////////////////////////////////////////////// + +Reader::Reader() + : errors_(), document_(), commentsBefore_(), features_(Features::all()) {} + +Reader::Reader(const Features& features) + : errors_(), document_(), begin_(), end_(), current_(), lastValueEnd_(), + lastValue_(), commentsBefore_(), features_(features), collectComments_() { +} + +bool Reader::parse(const std::string& document, + Value& root, + bool collectComments) { + document_.assign(document.begin(), document.end()); + const char* begin = document_.c_str(); + const char* end = begin + document_.length(); + return parse(begin, end, root, collectComments); +} + +bool Reader::parse(std::istream& is, Value& root, bool collectComments) { + // std::istream_iterator begin(is); + // std::istream_iterator end; + // Those would allow streamed input from a file, if parse() were a + // template function. + + // Since String is reference-counted, this at least does not + // create an extra copy. + String doc; + std::getline(is, doc, (char)EOF); + return parse(doc.data(), doc.data() + doc.size(), root, collectComments); +} + +bool Reader::parse(const char* beginDoc, + const char* endDoc, + Value& root, + bool collectComments) { + if (!features_.allowComments_) { + collectComments = false; + } + + begin_ = beginDoc; + end_ = endDoc; + collectComments_ = collectComments; + current_ = begin_; + lastValueEnd_ = nullptr; + lastValue_ = nullptr; + commentsBefore_.clear(); + errors_.clear(); + while (!nodes_.empty()) + nodes_.pop(); + nodes_.push(&root); + + bool successful = readValue(); + Token token; + skipCommentTokens(token); + if (collectComments_ && !commentsBefore_.empty()) + root.setComment(commentsBefore_, commentAfter); + if (features_.strictRoot_) { + if (!root.isArray() && !root.isObject()) { + // Set error location to start of doc, ideally should be first token found + // in doc + token.type_ = tokenError; + token.start_ = beginDoc; + token.end_ = endDoc; + addError( + "A valid JSON document must be either an array or an object value.", + token); + return false; + } + } + return successful; +} + +bool Reader::readValue() { + // readValue() may call itself only if it calls readObject() or ReadArray(). + // These methods execute nodes_.push() just before and nodes_.pop)() just + // after calling readValue(). parse() executes one nodes_.push(), so > instead + // of >=. + if (nodes_.size() > stackLimit_g) + throwRuntimeError("Exceeded stackLimit in readValue()."); + + Token token; + skipCommentTokens(token); + bool successful = true; + + if (collectComments_ && !commentsBefore_.empty()) { + currentValue().setComment(commentsBefore_, commentBefore); + commentsBefore_.clear(); + } + + switch (token.type_) { + case tokenObjectBegin: + successful = readObject(token); + currentValue().setOffsetLimit(current_ - begin_); + break; + case tokenArrayBegin: + successful = readArray(token); + currentValue().setOffsetLimit(current_ - begin_); + break; + case tokenNumber: + successful = decodeNumber(token); + break; + case tokenString: + successful = decodeString(token); + break; + case tokenTrue: { + Value v(true); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } break; + case tokenFalse: { + Value v(false); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } break; + case tokenNull: { + Value v; + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } break; + case tokenArraySeparator: + case tokenObjectEnd: + case tokenArrayEnd: + if (features_.allowDroppedNullPlaceholders_) { + // "Un-read" the current token and mark the current value as a null + // token. + current_--; + Value v; + currentValue().swapPayload(v); + currentValue().setOffsetStart(current_ - begin_ - 1); + currentValue().setOffsetLimit(current_ - begin_); + break; + } // Else, fall through... + default: + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return addError("Syntax error: value, object or array expected.", token); + } + + if (collectComments_) { + lastValueEnd_ = current_; + lastValue_ = ¤tValue(); + } + + return successful; +} + +void Reader::skipCommentTokens(Token& token) { + if (features_.allowComments_) { + do { + readToken(token); + } while (token.type_ == tokenComment); + } else { + readToken(token); + } +} + +bool Reader::readToken(Token& token) { + skipSpaces(); + token.start_ = current_; + Char c = getNextChar(); + bool ok = true; + switch (c) { + case '{': + token.type_ = tokenObjectBegin; + break; + case '}': + token.type_ = tokenObjectEnd; + break; + case '[': + token.type_ = tokenArrayBegin; + break; + case ']': + token.type_ = tokenArrayEnd; + break; + case '"': + token.type_ = tokenString; + ok = readString(); + break; + case '/': + token.type_ = tokenComment; + ok = readComment(); + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + token.type_ = tokenNumber; + readNumber(); + break; + case 't': + token.type_ = tokenTrue; + ok = match("rue", 3); + break; + case 'f': + token.type_ = tokenFalse; + ok = match("alse", 4); + break; + case 'n': + token.type_ = tokenNull; + ok = match("ull", 3); + break; + case ',': + token.type_ = tokenArraySeparator; + break; + case ':': + token.type_ = tokenMemberSeparator; + break; + case 0: + token.type_ = tokenEndOfStream; + break; + default: + ok = false; + break; + } + if (!ok) + token.type_ = tokenError; + token.end_ = current_; + return true; +} + +void Reader::skipSpaces() { + while (current_ != end_) { + Char c = *current_; + if (c == ' ' || c == '\t' || c == '\r' || c == '\n') + ++current_; + else + break; + } +} + +bool Reader::match(Location pattern, int patternLength) { + if (end_ - current_ < patternLength) + return false; + int index = patternLength; + while (index--) + if (current_[index] != pattern[index]) + return false; + current_ += patternLength; + return true; +} + +bool Reader::readComment() { + Location commentBegin = current_ - 1; + Char c = getNextChar(); + bool successful = false; + if (c == '*') + successful = readCStyleComment(); + else if (c == '/') + successful = readCppStyleComment(); + if (!successful) + return false; + + if (collectComments_) { + CommentPlacement placement = commentBefore; + if (lastValueEnd_ && !containsNewLine(lastValueEnd_, commentBegin)) { + if (c != '*' || !containsNewLine(commentBegin, current_)) + placement = commentAfterOnSameLine; + } + + addComment(commentBegin, current_, placement); + } + return true; +} + +String Reader::normalizeEOL(Reader::Location begin, Reader::Location end) { + String normalized; + normalized.reserve(static_cast(end - begin)); + Reader::Location current = begin; + while (current != end) { + char c = *current++; + if (c == '\r') { + if (current != end && *current == '\n') + // convert dos EOL + ++current; + // convert Mac EOL + normalized += '\n'; + } else { + normalized += c; + } + } + return normalized; +} + +void Reader::addComment(Location begin, + Location end, + CommentPlacement placement) { + assert(collectComments_); + const String& normalized = normalizeEOL(begin, end); + if (placement == commentAfterOnSameLine) { + assert(lastValue_ != nullptr); + lastValue_->setComment(normalized, placement); + } else { + commentsBefore_ += normalized; + } +} + +bool Reader::readCStyleComment() { + while ((current_ + 1) < end_) { + Char c = getNextChar(); + if (c == '*' && *current_ == '/') + break; + } + return getNextChar() == '/'; +} + +bool Reader::readCppStyleComment() { + while (current_ != end_) { + Char c = getNextChar(); + if (c == '\n') + break; + if (c == '\r') { + // Consume DOS EOL. It will be normalized in addComment. + if (current_ != end_ && *current_ == '\n') + getNextChar(); + // Break on Moc OS 9 EOL. + break; + } + } + return true; +} + +void Reader::readNumber() { + const char* p = current_; + char c = '0'; // stopgap for already consumed character + // integral part + while (c >= '0' && c <= '9') + c = (current_ = p) < end_ ? *p++ : '\0'; + // fractional part + if (c == '.') { + c = (current_ = p) < end_ ? *p++ : '\0'; + while (c >= '0' && c <= '9') + c = (current_ = p) < end_ ? *p++ : '\0'; + } + // exponential part + if (c == 'e' || c == 'E') { + c = (current_ = p) < end_ ? *p++ : '\0'; + if (c == '+' || c == '-') + c = (current_ = p) < end_ ? *p++ : '\0'; + while (c >= '0' && c <= '9') + c = (current_ = p) < end_ ? *p++ : '\0'; + } +} + +bool Reader::readString() { + Char c = '\0'; + while (current_ != end_) { + c = getNextChar(); + if (c == '\\') + getNextChar(); + else if (c == '"') + break; + } + return c == '"'; +} + +bool Reader::readObject(Token& token) { + Token tokenName; + String name; + Value init(objectValue); + currentValue().swapPayload(init); + currentValue().setOffsetStart(token.start_ - begin_); + while (readToken(tokenName)) { + bool initialTokenOk = true; + while (tokenName.type_ == tokenComment && initialTokenOk) + initialTokenOk = readToken(tokenName); + if (!initialTokenOk) + break; + if (tokenName.type_ == tokenObjectEnd && name.empty()) // empty object + return true; + name.clear(); + if (tokenName.type_ == tokenString) { + if (!decodeString(tokenName, name)) + return recoverFromError(tokenObjectEnd); + } else if (tokenName.type_ == tokenNumber && features_.allowNumericKeys_) { + Value numberName; + if (!decodeNumber(tokenName, numberName)) + return recoverFromError(tokenObjectEnd); + name = String(numberName.asCString()); + } else { + break; + } + + Token colon; + if (!readToken(colon) || colon.type_ != tokenMemberSeparator) { + return addErrorAndRecover("Missing ':' after object member name", colon, + tokenObjectEnd); + } + Value& value = currentValue()[name]; + nodes_.push(&value); + bool ok = readValue(); + nodes_.pop(); + if (!ok) // error already set + return recoverFromError(tokenObjectEnd); + + Token comma; + if (!readToken(comma) || + (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator && + comma.type_ != tokenComment)) { + return addErrorAndRecover("Missing ',' or '}' in object declaration", + comma, tokenObjectEnd); + } + bool finalizeTokenOk = true; + while (comma.type_ == tokenComment && finalizeTokenOk) + finalizeTokenOk = readToken(comma); + if (comma.type_ == tokenObjectEnd) + return true; + } + return addErrorAndRecover("Missing '}' or object member name", tokenName, + tokenObjectEnd); +} + +bool Reader::readArray(Token& token) { + Value init(arrayValue); + currentValue().swapPayload(init); + currentValue().setOffsetStart(token.start_ - begin_); + skipSpaces(); + if (current_ != end_ && *current_ == ']') // empty array + { + Token endArray; + readToken(endArray); + return true; + } + int index = 0; + for (;;) { + Value& value = currentValue()[index++]; + nodes_.push(&value); + bool ok = readValue(); + nodes_.pop(); + if (!ok) // error already set + return recoverFromError(tokenArrayEnd); + + Token currentToken; + // Accept Comment after last item in the array. + ok = readToken(currentToken); + while (currentToken.type_ == tokenComment && ok) { + ok = readToken(currentToken); + } + bool badTokenType = (currentToken.type_ != tokenArraySeparator && + currentToken.type_ != tokenArrayEnd); + if (!ok || badTokenType) { + return addErrorAndRecover("Missing ',' or ']' in array declaration", + currentToken, tokenArrayEnd); + } + if (currentToken.type_ == tokenArrayEnd) + break; + } + return true; +} + +bool Reader::decodeNumber(Token& token) { + Value decoded; + if (!decodeNumber(token, decoded)) + return false; + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; +} + +bool Reader::decodeNumber(Token& token, Value& decoded) { + // Attempts to parse the number as an integer. If the number is + // larger than the maximum supported value of an integer then + // we decode the number as a double. + Location current = token.start_; + bool isNegative = *current == '-'; + if (isNegative) + ++current; + // TODO: Help the compiler do the div and mod at compile time or get rid of + // them. + Value::LargestUInt maxIntegerValue = + isNegative ? Value::LargestUInt(Value::maxLargestInt) + 1 + : Value::maxLargestUInt; + Value::LargestUInt threshold = maxIntegerValue / 10; + Value::LargestUInt value = 0; + while (current < token.end_) { + Char c = *current++; + if (c < '0' || c > '9') + return decodeDouble(token, decoded); + auto digit(static_cast(c - '0')); + if (value >= threshold) { + // We've hit or exceeded the max value divided by 10 (rounded down). If + // a) we've only just touched the limit, b) this is the last digit, and + // c) it's small enough to fit in that rounding delta, we're okay. + // Otherwise treat this number as a double to avoid overflow. + if (value > threshold || current != token.end_ || + digit > maxIntegerValue % 10) { + return decodeDouble(token, decoded); + } + } + value = value * 10 + digit; + } + if (isNegative && value == maxIntegerValue) + decoded = Value::minLargestInt; + else if (isNegative) + decoded = -Value::LargestInt(value); + else if (value <= Value::LargestUInt(Value::maxInt)) + decoded = Value::LargestInt(value); + else + decoded = value; + return true; +} + +bool Reader::decodeDouble(Token& token) { + Value decoded; + if (!decodeDouble(token, decoded)) + return false; + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; +} + +bool Reader::decodeDouble(Token& token, Value& decoded) { + double value = 0; + String buffer(token.start_, token.end_); + IStringStream is(buffer); + if (!(is >> value)) + return addError( + "'" + String(token.start_, token.end_) + "' is not a number.", token); + decoded = value; + return true; +} + +bool Reader::decodeString(Token& token) { + String decoded_string; + if (!decodeString(token, decoded_string)) + return false; + Value decoded(decoded_string); + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; +} + +bool Reader::decodeString(Token& token, String& decoded) { + decoded.reserve(static_cast(token.end_ - token.start_ - 2)); + Location current = token.start_ + 1; // skip '"' + Location end = token.end_ - 1; // do not include '"' + while (current != end) { + Char c = *current++; + if (c == '"') + break; + else if (c == '\\') { + if (current == end) + return addError("Empty escape sequence in string", token, current); + Char escape = *current++; + switch (escape) { + case '"': + decoded += '"'; + break; + case '/': + decoded += '/'; + break; + case '\\': + decoded += '\\'; + break; + case 'b': + decoded += '\b'; + break; + case 'f': + decoded += '\f'; + break; + case 'n': + decoded += '\n'; + break; + case 'r': + decoded += '\r'; + break; + case 't': + decoded += '\t'; + break; + case 'u': { + unsigned int unicode; + if (!decodeUnicodeCodePoint(token, current, end, unicode)) + return false; + decoded += codePointToUTF8(unicode); + } break; + default: + return addError("Bad escape sequence in string", token, current); + } + } else { + decoded += c; + } + } + return true; +} + +bool Reader::decodeUnicodeCodePoint(Token& token, + Location& current, + Location end, + unsigned int& unicode) { + + if (!decodeUnicodeEscapeSequence(token, current, end, unicode)) + return false; + if (unicode >= 0xD800 && unicode <= 0xDBFF) { + // surrogate pairs + if (end - current < 6) + return addError( + "additional six characters expected to parse unicode surrogate pair.", + token, current); + if (*(current++) == '\\' && *(current++) == 'u') { + unsigned int surrogatePair; + if (decodeUnicodeEscapeSequence(token, current, end, surrogatePair)) { + unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF); + } else + return false; + } else + return addError("expecting another \\u token to begin the second half of " + "a unicode surrogate pair", + token, current); + } + return true; +} + +bool Reader::decodeUnicodeEscapeSequence(Token& token, + Location& current, + Location end, + unsigned int& ret_unicode) { + if (end - current < 4) + return addError( + "Bad unicode escape sequence in string: four digits expected.", token, + current); + int unicode = 0; + for (int index = 0; index < 4; ++index) { + Char c = *current++; + unicode *= 16; + if (c >= '0' && c <= '9') + unicode += c - '0'; + else if (c >= 'a' && c <= 'f') + unicode += c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + unicode += c - 'A' + 10; + else + return addError( + "Bad unicode escape sequence in string: hexadecimal digit expected.", + token, current); + } + ret_unicode = static_cast(unicode); + return true; +} + +bool Reader::addError(const String& message, Token& token, Location extra) { + ErrorInfo info; + info.token_ = token; + info.message_ = message; + info.extra_ = extra; + errors_.push_back(info); + return false; +} + +bool Reader::recoverFromError(TokenType skipUntilToken) { + size_t const errorCount = errors_.size(); + Token skip; + for (;;) { + if (!readToken(skip)) + errors_.resize(errorCount); // discard errors caused by recovery + if (skip.type_ == skipUntilToken || skip.type_ == tokenEndOfStream) + break; + } + errors_.resize(errorCount); + return false; +} + +bool Reader::addErrorAndRecover(const String& message, + Token& token, + TokenType skipUntilToken) { + addError(message, token); + return recoverFromError(skipUntilToken); +} + +Value& Reader::currentValue() { return *(nodes_.top()); } + +Reader::Char Reader::getNextChar() { + if (current_ == end_) + return 0; + return *current_++; +} + +void Reader::getLocationLineAndColumn(Location location, + int& line, + int& column) const { + Location current = begin_; + Location lastLineStart = current; + line = 0; + while (current < location && current != end_) { + Char c = *current++; + if (c == '\r') { + if (*current == '\n') + ++current; + lastLineStart = current; + ++line; + } else if (c == '\n') { + lastLineStart = current; + ++line; + } + } + // column & line start at 1 + column = int(location - lastLineStart) + 1; + ++line; +} + +String Reader::getLocationLineAndColumn(Location location) const { + int line, column; + getLocationLineAndColumn(location, line, column); + char buffer[18 + 16 + 16 + 1]; + jsoncpp_snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column); + return buffer; +} + +// Deprecated. Preserved for backward compatibility +String Reader::getFormatedErrorMessages() const { + return getFormattedErrorMessages(); +} + +String Reader::getFormattedErrorMessages() const { + String formattedMessage; + for (const auto& error : errors_) { + formattedMessage += + "* " + getLocationLineAndColumn(error.token_.start_) + "\n"; + formattedMessage += " " + error.message_ + "\n"; + if (error.extra_) + formattedMessage += + "See " + getLocationLineAndColumn(error.extra_) + " for detail.\n"; + } + return formattedMessage; +} + +std::vector Reader::getStructuredErrors() const { + std::vector allErrors; + for (const auto& error : errors_) { + Reader::StructuredError structured; + structured.offset_start = error.token_.start_ - begin_; + structured.offset_limit = error.token_.end_ - begin_; + structured.message = error.message_; + allErrors.push_back(structured); + } + return allErrors; +} + +bool Reader::pushError(const Value& value, const String& message) { + ptrdiff_t const length = end_ - begin_; + if (value.getOffsetStart() > length || value.getOffsetLimit() > length) + return false; + Token token; + token.type_ = tokenError; + token.start_ = begin_ + value.getOffsetStart(); + token.end_ = end_ + value.getOffsetLimit(); + ErrorInfo info; + info.token_ = token; + info.message_ = message; + info.extra_ = nullptr; + errors_.push_back(info); + return true; +} + +bool Reader::pushError(const Value& value, + const String& message, + const Value& extra) { + ptrdiff_t const length = end_ - begin_; + if (value.getOffsetStart() > length || value.getOffsetLimit() > length || + extra.getOffsetLimit() > length) + return false; + Token token; + token.type_ = tokenError; + token.start_ = begin_ + value.getOffsetStart(); + token.end_ = begin_ + value.getOffsetLimit(); + ErrorInfo info; + info.token_ = token; + info.message_ = message; + info.extra_ = begin_ + extra.getOffsetStart(); + errors_.push_back(info); + return true; +} + +bool Reader::good() const { return errors_.empty(); } + +// exact copy of Features +class OurFeatures { +public: + static OurFeatures all(); + bool allowComments_; + bool strictRoot_; + bool allowDroppedNullPlaceholders_; + bool allowNumericKeys_; + bool allowSingleQuotes_; + bool failIfExtra_; + bool rejectDupKeys_; + bool allowSpecialFloats_; + size_t stackLimit_; +}; // OurFeatures + +// exact copy of Implementation of class Features +// //////////////////////////////// + +OurFeatures OurFeatures::all() { return {}; } + +// Implementation of class Reader +// //////////////////////////////// + +// exact copy of Reader, renamed to OurReader +class OurReader { +public: + typedef char Char; + typedef const Char* Location; + struct StructuredError { + ptrdiff_t offset_start; + ptrdiff_t offset_limit; + String message; + }; + + OurReader(OurFeatures const& features); + bool parse(const char* beginDoc, + const char* endDoc, + Value& root, + bool collectComments = true); + String getFormattedErrorMessages() const; + std::vector getStructuredErrors() const; + bool pushError(const Value& value, const String& message); + bool pushError(const Value& value, const String& message, const Value& extra); + bool good() const; + +private: + OurReader(OurReader const&); // no impl + void operator=(OurReader const&); // no impl + + enum TokenType { + tokenEndOfStream = 0, + tokenObjectBegin, + tokenObjectEnd, + tokenArrayBegin, + tokenArrayEnd, + tokenString, + tokenNumber, + tokenTrue, + tokenFalse, + tokenNull, + tokenNaN, + tokenPosInf, + tokenNegInf, + tokenArraySeparator, + tokenMemberSeparator, + tokenComment, + tokenError + }; + + class Token { + public: + TokenType type_; + Location start_; + Location end_; + }; + + class ErrorInfo { + public: + Token token_; + String message_; + Location extra_; + }; + + typedef std::deque Errors; + + bool readToken(Token& token); + void skipSpaces(); + bool match(Location pattern, int patternLength); + bool readComment(); + bool readCStyleComment(); + bool readCppStyleComment(); + bool readString(); + bool readStringSingleQuote(); + bool readNumber(bool checkInf); + bool readValue(); + bool readObject(Token& token); + bool readArray(Token& token); + bool decodeNumber(Token& token); + bool decodeNumber(Token& token, Value& decoded); + bool decodeString(Token& token); + bool decodeString(Token& token, String& decoded); + bool decodeDouble(Token& token); + bool decodeDouble(Token& token, Value& decoded); + bool decodeUnicodeCodePoint(Token& token, + Location& current, + Location end, + unsigned int& unicode); + bool decodeUnicodeEscapeSequence(Token& token, + Location& current, + Location end, + unsigned int& unicode); + bool addError(const String& message, Token& token, Location extra = nullptr); + bool recoverFromError(TokenType skipUntilToken); + bool addErrorAndRecover(const String& message, + Token& token, + TokenType skipUntilToken); + void skipUntilSpace(); + Value& currentValue(); + Char getNextChar(); + void + getLocationLineAndColumn(Location location, int& line, int& column) const; + String getLocationLineAndColumn(Location location) const; + void addComment(Location begin, Location end, CommentPlacement placement); + void skipCommentTokens(Token& token); + + static String normalizeEOL(Location begin, Location end); + static bool containsNewLine(Location begin, Location end); + + typedef std::stack Nodes; + Nodes nodes_; + Errors errors_; + String document_; + Location begin_; + Location end_; + Location current_; + Location lastValueEnd_; + Value* lastValue_; + String commentsBefore_; + + OurFeatures const features_; + bool collectComments_; +}; // OurReader + +// complete copy of Read impl, for OurReader + +bool OurReader::containsNewLine(OurReader::Location begin, + OurReader::Location end) { + for (; begin < end; ++begin) + if (*begin == '\n' || *begin == '\r') + return true; + return false; +} + +OurReader::OurReader(OurFeatures const& features) + : errors_(), document_(), begin_(), end_(), current_(), lastValueEnd_(), + lastValue_(), commentsBefore_(), features_(features), collectComments_() { +} + +bool OurReader::parse(const char* beginDoc, + const char* endDoc, + Value& root, + bool collectComments) { + if (!features_.allowComments_) { + collectComments = false; + } + + begin_ = beginDoc; + end_ = endDoc; + collectComments_ = collectComments; + current_ = begin_; + lastValueEnd_ = nullptr; + lastValue_ = nullptr; + commentsBefore_.clear(); + errors_.clear(); + while (!nodes_.empty()) + nodes_.pop(); + nodes_.push(&root); + + bool successful = readValue(); + Token token; + skipCommentTokens(token); + if (features_.failIfExtra_) { + if ((features_.strictRoot_ || token.type_ != tokenError) && + token.type_ != tokenEndOfStream) { + addError("Extra non-whitespace after JSON value.", token); + return false; + } + } + if (collectComments_ && !commentsBefore_.empty()) + root.setComment(commentsBefore_, commentAfter); + if (features_.strictRoot_) { + if (!root.isArray() && !root.isObject()) { + // Set error location to start of doc, ideally should be first token found + // in doc + token.type_ = tokenError; + token.start_ = beginDoc; + token.end_ = endDoc; + addError( + "A valid JSON document must be either an array or an object value.", + token); + return false; + } + } + return successful; +} + +bool OurReader::readValue() { + // To preserve the old behaviour we cast size_t to int. + if (nodes_.size() > features_.stackLimit_) + throwRuntimeError("Exceeded stackLimit in readValue()."); + Token token; + skipCommentTokens(token); + bool successful = true; + + if (collectComments_ && !commentsBefore_.empty()) { + currentValue().setComment(commentsBefore_, commentBefore); + commentsBefore_.clear(); + } + + switch (token.type_) { + case tokenObjectBegin: + successful = readObject(token); + currentValue().setOffsetLimit(current_ - begin_); + break; + case tokenArrayBegin: + successful = readArray(token); + currentValue().setOffsetLimit(current_ - begin_); + break; + case tokenNumber: + successful = decodeNumber(token); + break; + case tokenString: + successful = decodeString(token); + break; + case tokenTrue: { + Value v(true); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } break; + case tokenFalse: { + Value v(false); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } break; + case tokenNull: { + Value v; + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } break; + case tokenNaN: { + Value v(std::numeric_limits::quiet_NaN()); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } break; + case tokenPosInf: { + Value v(std::numeric_limits::infinity()); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } break; + case tokenNegInf: { + Value v(-std::numeric_limits::infinity()); + currentValue().swapPayload(v); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + } break; + case tokenArraySeparator: + case tokenObjectEnd: + case tokenArrayEnd: + if (features_.allowDroppedNullPlaceholders_) { + // "Un-read" the current token and mark the current value as a null + // token. + current_--; + Value v; + currentValue().swapPayload(v); + currentValue().setOffsetStart(current_ - begin_ - 1); + currentValue().setOffsetLimit(current_ - begin_); + break; + } // else, fall through ... + default: + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return addError("Syntax error: value, object or array expected.", token); + } + + if (collectComments_) { + lastValueEnd_ = current_; + lastValue_ = ¤tValue(); + } + + return successful; +} + +void OurReader::skipCommentTokens(Token& token) { + if (features_.allowComments_) { + do { + readToken(token); + } while (token.type_ == tokenComment); + } else { + readToken(token); + } +} + +bool OurReader::readToken(Token& token) { + skipSpaces(); + token.start_ = current_; + Char c = getNextChar(); + bool ok = true; + switch (c) { + case '{': + token.type_ = tokenObjectBegin; + break; + case '}': + token.type_ = tokenObjectEnd; + break; + case '[': + token.type_ = tokenArrayBegin; + break; + case ']': + token.type_ = tokenArrayEnd; + break; + case '"': + token.type_ = tokenString; + ok = readString(); + break; + case '\'': + if (features_.allowSingleQuotes_) { + token.type_ = tokenString; + ok = readStringSingleQuote(); + break; + } // else fall through + case '/': + token.type_ = tokenComment; + ok = readComment(); + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + token.type_ = tokenNumber; + readNumber(false); + break; + case '-': + if (readNumber(true)) { + token.type_ = tokenNumber; + } else { + token.type_ = tokenNegInf; + ok = features_.allowSpecialFloats_ && match("nfinity", 7); + } + break; + case 't': + token.type_ = tokenTrue; + ok = match("rue", 3); + break; + case 'f': + token.type_ = tokenFalse; + ok = match("alse", 4); + break; + case 'n': + token.type_ = tokenNull; + ok = match("ull", 3); + break; + case 'N': + if (features_.allowSpecialFloats_) { + token.type_ = tokenNaN; + ok = match("aN", 2); + } else { + ok = false; + } + break; + case 'I': + if (features_.allowSpecialFloats_) { + token.type_ = tokenPosInf; + ok = match("nfinity", 7); + } else { + ok = false; + } + break; + case ',': + token.type_ = tokenArraySeparator; + break; + case ':': + token.type_ = tokenMemberSeparator; + break; + case 0: + token.type_ = tokenEndOfStream; + break; + default: + ok = false; + break; + } + if (!ok) + token.type_ = tokenError; + token.end_ = current_; + return true; +} + +void OurReader::skipSpaces() { + while (current_ != end_) { + Char c = *current_; + if (c == ' ' || c == '\t' || c == '\r' || c == '\n') + ++current_; + else + break; + } +} + +bool OurReader::match(Location pattern, int patternLength) { + if (end_ - current_ < patternLength) + return false; + int index = patternLength; + while (index--) + if (current_[index] != pattern[index]) + return false; + current_ += patternLength; + return true; +} + +bool OurReader::readComment() { + Location commentBegin = current_ - 1; + Char c = getNextChar(); + bool successful = false; + if (c == '*') + successful = readCStyleComment(); + else if (c == '/') + successful = readCppStyleComment(); + if (!successful) + return false; + + if (collectComments_) { + CommentPlacement placement = commentBefore; + if (lastValueEnd_ && !containsNewLine(lastValueEnd_, commentBegin)) { + if (c != '*' || !containsNewLine(commentBegin, current_)) + placement = commentAfterOnSameLine; + } + + addComment(commentBegin, current_, placement); + } + return true; +} + +String OurReader::normalizeEOL(OurReader::Location begin, + OurReader::Location end) { + String normalized; + normalized.reserve(static_cast(end - begin)); + OurReader::Location current = begin; + while (current != end) { + char c = *current++; + if (c == '\r') { + if (current != end && *current == '\n') + // convert dos EOL + ++current; + // convert Mac EOL + normalized += '\n'; + } else { + normalized += c; + } + } + return normalized; +} + +void OurReader::addComment(Location begin, + Location end, + CommentPlacement placement) { + assert(collectComments_); + const String& normalized = normalizeEOL(begin, end); + if (placement == commentAfterOnSameLine) { + assert(lastValue_ != nullptr); + lastValue_->setComment(normalized, placement); + } else { + commentsBefore_ += normalized; + } +} + +bool OurReader::readCStyleComment() { + while ((current_ + 1) < end_) { + Char c = getNextChar(); + if (c == '*' && *current_ == '/') + break; + } + return getNextChar() == '/'; +} + +bool OurReader::readCppStyleComment() { + while (current_ != end_) { + Char c = getNextChar(); + if (c == '\n') + break; + if (c == '\r') { + // Consume DOS EOL. It will be normalized in addComment. + if (current_ != end_ && *current_ == '\n') + getNextChar(); + // Break on Moc OS 9 EOL. + break; + } + } + return true; +} + +bool OurReader::readNumber(bool checkInf) { + const char* p = current_; + if (checkInf && p != end_ && *p == 'I') { + current_ = ++p; + return false; + } + char c = '0'; // stopgap for already consumed character + // integral part + while (c >= '0' && c <= '9') + c = (current_ = p) < end_ ? *p++ : '\0'; + // fractional part + if (c == '.') { + c = (current_ = p) < end_ ? *p++ : '\0'; + while (c >= '0' && c <= '9') + c = (current_ = p) < end_ ? *p++ : '\0'; + } + // exponential part + if (c == 'e' || c == 'E') { + c = (current_ = p) < end_ ? *p++ : '\0'; + if (c == '+' || c == '-') + c = (current_ = p) < end_ ? *p++ : '\0'; + while (c >= '0' && c <= '9') + c = (current_ = p) < end_ ? *p++ : '\0'; + } + return true; +} +bool OurReader::readString() { + Char c = 0; + while (current_ != end_) { + c = getNextChar(); + if (c == '\\') + getNextChar(); + else if (c == '"') + break; + } + return c == '"'; +} + +bool OurReader::readStringSingleQuote() { + Char c = 0; + while (current_ != end_) { + c = getNextChar(); + if (c == '\\') + getNextChar(); + else if (c == '\'') + break; + } + return c == '\''; +} + +bool OurReader::readObject(Token& token) { + Token tokenName; + String name; + Value init(objectValue); + currentValue().swapPayload(init); + currentValue().setOffsetStart(token.start_ - begin_); + while (readToken(tokenName)) { + bool initialTokenOk = true; + while (tokenName.type_ == tokenComment && initialTokenOk) + initialTokenOk = readToken(tokenName); + if (!initialTokenOk) + break; + if (tokenName.type_ == tokenObjectEnd && name.empty()) // empty object + return true; + name.clear(); + if (tokenName.type_ == tokenString) { + if (!decodeString(tokenName, name)) + return recoverFromError(tokenObjectEnd); + } else if (tokenName.type_ == tokenNumber && features_.allowNumericKeys_) { + Value numberName; + if (!decodeNumber(tokenName, numberName)) + return recoverFromError(tokenObjectEnd); + name = numberName.asString(); + } else { + break; + } + + Token colon; + if (!readToken(colon) || colon.type_ != tokenMemberSeparator) { + return addErrorAndRecover("Missing ':' after object member name", colon, + tokenObjectEnd); + } + if (name.length() >= (1U << 30)) + throwRuntimeError("keylength >= 2^30"); + if (features_.rejectDupKeys_ && currentValue().isMember(name)) { + String msg = "Duplicate key: '" + name + "'"; + return addErrorAndRecover(msg, tokenName, tokenObjectEnd); + } + Value& value = currentValue()[name]; + nodes_.push(&value); + bool ok = readValue(); + nodes_.pop(); + if (!ok) // error already set + return recoverFromError(tokenObjectEnd); + + Token comma; + if (!readToken(comma) || + (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator && + comma.type_ != tokenComment)) { + return addErrorAndRecover("Missing ',' or '}' in object declaration", + comma, tokenObjectEnd); + } + bool finalizeTokenOk = true; + while (comma.type_ == tokenComment && finalizeTokenOk) + finalizeTokenOk = readToken(comma); + if (comma.type_ == tokenObjectEnd) + return true; + } + return addErrorAndRecover("Missing '}' or object member name", tokenName, + tokenObjectEnd); +} + +bool OurReader::readArray(Token& token) { + Value init(arrayValue); + currentValue().swapPayload(init); + currentValue().setOffsetStart(token.start_ - begin_); + skipSpaces(); + if (current_ != end_ && *current_ == ']') // empty array + { + Token endArray; + readToken(endArray); + return true; + } + int index = 0; + for (;;) { + Value& value = currentValue()[index++]; + nodes_.push(&value); + bool ok = readValue(); + nodes_.pop(); + if (!ok) // error already set + return recoverFromError(tokenArrayEnd); + + Token currentToken; + // Accept Comment after last item in the array. + ok = readToken(currentToken); + while (currentToken.type_ == tokenComment && ok) { + ok = readToken(currentToken); + } + bool badTokenType = (currentToken.type_ != tokenArraySeparator && + currentToken.type_ != tokenArrayEnd); + if (!ok || badTokenType) { + return addErrorAndRecover("Missing ',' or ']' in array declaration", + currentToken, tokenArrayEnd); + } + if (currentToken.type_ == tokenArrayEnd) + break; + } + return true; +} + +bool OurReader::decodeNumber(Token& token) { + Value decoded; + if (!decodeNumber(token, decoded)) + return false; + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; +} + +bool OurReader::decodeNumber(Token& token, Value& decoded) { + // Attempts to parse the number as an integer. If the number is + // larger than the maximum supported value of an integer then + // we decode the number as a double. + Location current = token.start_; + bool isNegative = *current == '-'; + if (isNegative) + ++current; + // TODO: Help the compiler do the div and mod at compile time or get rid of + // them. + Value::LargestUInt maxIntegerValue = + isNegative ? Value::LargestUInt(Value::minLargestInt) + : Value::maxLargestUInt; + Value::LargestUInt threshold = maxIntegerValue / 10; + Value::LargestUInt value = 0; + while (current < token.end_) { + Char c = *current++; + if (c < '0' || c > '9') + return decodeDouble(token, decoded); + auto digit(static_cast(c - '0')); + if (value >= threshold) { + // We've hit or exceeded the max value divided by 10 (rounded down). If + // a) we've only just touched the limit, b) this is the last digit, and + // c) it's small enough to fit in that rounding delta, we're okay. + // Otherwise treat this number as a double to avoid overflow. + if (value > threshold || current != token.end_ || + digit > maxIntegerValue % 10) { + return decodeDouble(token, decoded); + } + } + value = value * 10 + digit; + } + if (isNegative) + decoded = -Value::LargestInt(value); + else if (value <= Value::LargestUInt(Value::maxInt)) + decoded = Value::LargestInt(value); + else + decoded = value; + return true; +} + +bool OurReader::decodeDouble(Token& token) { + Value decoded; + if (!decodeDouble(token, decoded)) + return false; + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; +} + +bool OurReader::decodeDouble(Token& token, Value& decoded) { + double value = 0; + const int bufferSize = 32; + int count; + ptrdiff_t const length = token.end_ - token.start_; + + // Sanity check to avoid buffer overflow exploits. + if (length < 0) { + return addError("Unable to parse token length", token); + } + auto const ulength = static_cast(length); + + // Avoid using a string constant for the format control string given to + // sscanf, as this can cause hard to debug crashes on OS X. See here for more + // info: + // + // http://developer.apple.com/library/mac/#DOCUMENTATION/DeveloperTools/gcc-4.0.1/gcc/Incompatibilities.html + char format[] = "%lf"; + + if (length <= bufferSize) { + Char buffer[bufferSize + 1]; + memcpy(buffer, token.start_, ulength); + buffer[length] = 0; + fixNumericLocaleInput(buffer, buffer + length); + count = sscanf(buffer, format, &value); + } else { + String buffer(token.start_, token.end_); + count = sscanf(buffer.c_str(), format, &value); + } + + if (count != 1) + return addError( + "'" + String(token.start_, token.end_) + "' is not a number.", token); + decoded = value; + return true; +} + +bool OurReader::decodeString(Token& token) { + String decoded_string; + if (!decodeString(token, decoded_string)) + return false; + Value decoded(decoded_string); + currentValue().swapPayload(decoded); + currentValue().setOffsetStart(token.start_ - begin_); + currentValue().setOffsetLimit(token.end_ - begin_); + return true; +} + +bool OurReader::decodeString(Token& token, String& decoded) { + decoded.reserve(static_cast(token.end_ - token.start_ - 2)); + Location current = token.start_ + 1; // skip '"' + Location end = token.end_ - 1; // do not include '"' + while (current != end) { + Char c = *current++; + if (c == '"') + break; + else if (c == '\\') { + if (current == end) + return addError("Empty escape sequence in string", token, current); + Char escape = *current++; + switch (escape) { + case '"': + decoded += '"'; + break; + case '/': + decoded += '/'; + break; + case '\\': + decoded += '\\'; + break; + case 'b': + decoded += '\b'; + break; + case 'f': + decoded += '\f'; + break; + case 'n': + decoded += '\n'; + break; + case 'r': + decoded += '\r'; + break; + case 't': + decoded += '\t'; + break; + case 'u': { + unsigned int unicode; + if (!decodeUnicodeCodePoint(token, current, end, unicode)) + return false; + decoded += codePointToUTF8(unicode); + } break; + default: + return addError("Bad escape sequence in string", token, current); + } + } else { + decoded += c; + } + } + return true; +} + +bool OurReader::decodeUnicodeCodePoint(Token& token, + Location& current, + Location end, + unsigned int& unicode) { + + if (!decodeUnicodeEscapeSequence(token, current, end, unicode)) + return false; + if (unicode >= 0xD800 && unicode <= 0xDBFF) { + // surrogate pairs + if (end - current < 6) + return addError( + "additional six characters expected to parse unicode surrogate pair.", + token, current); + if (*(current++) == '\\' && *(current++) == 'u') { + unsigned int surrogatePair; + if (decodeUnicodeEscapeSequence(token, current, end, surrogatePair)) { + unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF); + } else + return false; + } else + return addError("expecting another \\u token to begin the second half of " + "a unicode surrogate pair", + token, current); + } + return true; +} + +bool OurReader::decodeUnicodeEscapeSequence(Token& token, + Location& current, + Location end, + unsigned int& ret_unicode) { + if (end - current < 4) + return addError( + "Bad unicode escape sequence in string: four digits expected.", token, + current); + int unicode = 0; + for (int index = 0; index < 4; ++index) { + Char c = *current++; + unicode *= 16; + if (c >= '0' && c <= '9') + unicode += c - '0'; + else if (c >= 'a' && c <= 'f') + unicode += c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + unicode += c - 'A' + 10; + else + return addError( + "Bad unicode escape sequence in string: hexadecimal digit expected.", + token, current); + } + ret_unicode = static_cast(unicode); + return true; +} + +bool OurReader::addError(const String& message, Token& token, Location extra) { + ErrorInfo info; + info.token_ = token; + info.message_ = message; + info.extra_ = extra; + errors_.push_back(info); + return false; +} + +bool OurReader::recoverFromError(TokenType skipUntilToken) { + size_t errorCount = errors_.size(); + Token skip; + for (;;) { + if (!readToken(skip)) + errors_.resize(errorCount); // discard errors caused by recovery + if (skip.type_ == skipUntilToken || skip.type_ == tokenEndOfStream) + break; + } + errors_.resize(errorCount); + return false; +} + +bool OurReader::addErrorAndRecover(const String& message, + Token& token, + TokenType skipUntilToken) { + addError(message, token); + return recoverFromError(skipUntilToken); +} + +Value& OurReader::currentValue() { return *(nodes_.top()); } + +OurReader::Char OurReader::getNextChar() { + if (current_ == end_) + return 0; + return *current_++; +} + +void OurReader::getLocationLineAndColumn(Location location, + int& line, + int& column) const { + Location current = begin_; + Location lastLineStart = current; + line = 0; + while (current < location && current != end_) { + Char c = *current++; + if (c == '\r') { + if (*current == '\n') + ++current; + lastLineStart = current; + ++line; + } else if (c == '\n') { + lastLineStart = current; + ++line; + } + } + // column & line start at 1 + column = int(location - lastLineStart) + 1; + ++line; +} + +String OurReader::getLocationLineAndColumn(Location location) const { + int line, column; + getLocationLineAndColumn(location, line, column); + char buffer[18 + 16 + 16 + 1]; + jsoncpp_snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column); + return buffer; +} + +String OurReader::getFormattedErrorMessages() const { + String formattedMessage; + for (const auto& error : errors_) { + formattedMessage += + "* " + getLocationLineAndColumn(error.token_.start_) + "\n"; + formattedMessage += " " + error.message_ + "\n"; + if (error.extra_) + formattedMessage += + "See " + getLocationLineAndColumn(error.extra_) + " for detail.\n"; + } + return formattedMessage; +} + +std::vector OurReader::getStructuredErrors() const { + std::vector allErrors; + for (const auto& error : errors_) { + OurReader::StructuredError structured; + structured.offset_start = error.token_.start_ - begin_; + structured.offset_limit = error.token_.end_ - begin_; + structured.message = error.message_; + allErrors.push_back(structured); + } + return allErrors; +} + +bool OurReader::pushError(const Value& value, const String& message) { + ptrdiff_t length = end_ - begin_; + if (value.getOffsetStart() > length || value.getOffsetLimit() > length) + return false; + Token token; + token.type_ = tokenError; + token.start_ = begin_ + value.getOffsetStart(); + token.end_ = end_ + value.getOffsetLimit(); + ErrorInfo info; + info.token_ = token; + info.message_ = message; + info.extra_ = nullptr; + errors_.push_back(info); + return true; +} + +bool OurReader::pushError(const Value& value, + const String& message, + const Value& extra) { + ptrdiff_t length = end_ - begin_; + if (value.getOffsetStart() > length || value.getOffsetLimit() > length || + extra.getOffsetLimit() > length) + return false; + Token token; + token.type_ = tokenError; + token.start_ = begin_ + value.getOffsetStart(); + token.end_ = begin_ + value.getOffsetLimit(); + ErrorInfo info; + info.token_ = token; + info.message_ = message; + info.extra_ = begin_ + extra.getOffsetStart(); + errors_.push_back(info); + return true; +} + +bool OurReader::good() const { return errors_.empty(); } + +class OurCharReader : public CharReader { + bool const collectComments_; + OurReader reader_; + +public: + OurCharReader(bool collectComments, OurFeatures const& features) + : collectComments_(collectComments), reader_(features) {} + bool parse(char const* beginDoc, + char const* endDoc, + Value* root, + String* errs) override { + bool ok = reader_.parse(beginDoc, endDoc, *root, collectComments_); + if (errs) { + *errs = reader_.getFormattedErrorMessages(); + } + return ok; + } +}; + +CharReaderBuilder::CharReaderBuilder() { setDefaults(&settings_); } +CharReaderBuilder::~CharReaderBuilder() = default; +CharReader* CharReaderBuilder::newCharReader() const { + bool collectComments = settings_["collectComments"].asBool(); + OurFeatures features = OurFeatures::all(); + features.allowComments_ = settings_["allowComments"].asBool(); + features.strictRoot_ = settings_["strictRoot"].asBool(); + features.allowDroppedNullPlaceholders_ = + settings_["allowDroppedNullPlaceholders"].asBool(); + features.allowNumericKeys_ = settings_["allowNumericKeys"].asBool(); + features.allowSingleQuotes_ = settings_["allowSingleQuotes"].asBool(); +#if defined(JSON_HAS_INT64) + features.stackLimit_ = settings_["stackLimit"].asUInt64(); +#else + features.stackLimit_ = settings_["stackLimit"].asUInt(); +#endif + features.failIfExtra_ = settings_["failIfExtra"].asBool(); + features.rejectDupKeys_ = settings_["rejectDupKeys"].asBool(); + features.allowSpecialFloats_ = settings_["allowSpecialFloats"].asBool(); + return new OurCharReader(collectComments, features); +} +static void getValidReaderKeys(std::set* valid_keys) { + valid_keys->clear(); + valid_keys->insert("collectComments"); + valid_keys->insert("allowComments"); + valid_keys->insert("strictRoot"); + valid_keys->insert("allowDroppedNullPlaceholders"); + valid_keys->insert("allowNumericKeys"); + valid_keys->insert("allowSingleQuotes"); + valid_keys->insert("stackLimit"); + valid_keys->insert("failIfExtra"); + valid_keys->insert("rejectDupKeys"); + valid_keys->insert("allowSpecialFloats"); +} +bool CharReaderBuilder::validate(Json::Value* invalid) const { + Json::Value my_invalid; + if (!invalid) + invalid = &my_invalid; // so we do not need to test for NULL + Json::Value& inv = *invalid; + std::set valid_keys; + getValidReaderKeys(&valid_keys); + Value::Members keys = settings_.getMemberNames(); + size_t n = keys.size(); + for (size_t i = 0; i < n; ++i) { + String const& key = keys[i]; + if (valid_keys.find(key) == valid_keys.end()) { + inv[key] = settings_[key]; + } + } + return inv.empty(); +} +Value& CharReaderBuilder::operator[](const String& key) { + return settings_[key]; +} +// static +void CharReaderBuilder::strictMode(Json::Value* settings) { + //! [CharReaderBuilderStrictMode] + (*settings)["allowComments"] = false; + (*settings)["strictRoot"] = true; + (*settings)["allowDroppedNullPlaceholders"] = false; + (*settings)["allowNumericKeys"] = false; + (*settings)["allowSingleQuotes"] = false; + (*settings)["stackLimit"] = 1000; + (*settings)["failIfExtra"] = true; + (*settings)["rejectDupKeys"] = true; + (*settings)["allowSpecialFloats"] = false; + //! [CharReaderBuilderStrictMode] +} +// static +void CharReaderBuilder::setDefaults(Json::Value* settings) { + //! [CharReaderBuilderDefaults] + (*settings)["collectComments"] = true; + (*settings)["allowComments"] = true; + (*settings)["strictRoot"] = false; + (*settings)["allowDroppedNullPlaceholders"] = false; + (*settings)["allowNumericKeys"] = false; + (*settings)["allowSingleQuotes"] = false; + (*settings)["stackLimit"] = 1000; + (*settings)["failIfExtra"] = false; + (*settings)["rejectDupKeys"] = false; + (*settings)["allowSpecialFloats"] = false; + //! [CharReaderBuilderDefaults] +} + +////////////////////////////////// +// global functions + +bool parseFromStream(CharReader::Factory const& fact, + IStream& sin, + Value* root, + String* errs) { + OStringStream ssin; + ssin << sin.rdbuf(); + String doc = ssin.str(); + char const* begin = doc.data(); + char const* end = begin + doc.size(); + // Note that we do not actually need a null-terminator. + CharReaderPtr const reader(fact.newCharReader()); + return reader->parse(begin, end, root, errs); +} + +IStream& operator>>(IStream& sin, Value& root) { + CharReaderBuilder b; + String errs; + bool ok = parseFromStream(b, sin, &root, &errs); + if (!ok) { + throwRuntimeError(errs); + } + return sin; +} + +} // namespace Json + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: src/lib_json/json_reader.cpp +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: src/lib_json/json_valueiterator.inl +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +// included by json_value.cpp + +namespace Json { + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class ValueIteratorBase +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// + +ValueIteratorBase::ValueIteratorBase() : current_() {} + +ValueIteratorBase::ValueIteratorBase( + const Value::ObjectValues::iterator& current) + : current_(current), isNull_(false) {} + +Value& ValueIteratorBase::deref() const { return current_->second; } + +void ValueIteratorBase::increment() { ++current_; } + +void ValueIteratorBase::decrement() { --current_; } + +ValueIteratorBase::difference_type +ValueIteratorBase::computeDistance(const SelfType& other) const { +#ifdef JSON_USE_CPPTL_SMALLMAP + return other.current_ - current_; +#else + // Iterator for null value are initialized using the default + // constructor, which initialize current_ to the default + // std::map::iterator. As begin() and end() are two instance + // of the default std::map::iterator, they can not be compared. + // To allow this, we handle this comparison specifically. + if (isNull_ && other.isNull_) { + return 0; + } + + // Usage of std::distance is not portable (does not compile with Sun Studio 12 + // RogueWave STL, + // which is the one used by default). + // Using a portable hand-made version for non random iterator instead: + // return difference_type( std::distance( current_, other.current_ ) ); + difference_type myDistance = 0; + for (Value::ObjectValues::iterator it = current_; it != other.current_; + ++it) { + ++myDistance; + } + return myDistance; +#endif +} + +bool ValueIteratorBase::isEqual(const SelfType& other) const { + if (isNull_) { + return other.isNull_; + } + return current_ == other.current_; +} + +void ValueIteratorBase::copy(const SelfType& other) { + current_ = other.current_; + isNull_ = other.isNull_; +} + +Value ValueIteratorBase::key() const { + const Value::CZString czstring = (*current_).first; + if (czstring.data()) { + if (czstring.isStaticString()) + return Value(StaticString(czstring.data())); + return Value(czstring.data(), czstring.data() + czstring.length()); + } + return Value(czstring.index()); +} + +UInt ValueIteratorBase::index() const { + const Value::CZString czstring = (*current_).first; + if (!czstring.data()) + return czstring.index(); + return Value::UInt(-1); +} + +String ValueIteratorBase::name() const { + char const* keey; + char const* end; + keey = memberName(&end); + if (!keey) + return String(); + return String(keey, end); +} + +char const* ValueIteratorBase::memberName() const { + const char* cname = (*current_).first.data(); + return cname ? cname : ""; +} + +char const* ValueIteratorBase::memberName(char const** end) const { + const char* cname = (*current_).first.data(); + if (!cname) { + *end = nullptr; + return nullptr; + } + *end = cname + (*current_).first.length(); + return cname; +} + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class ValueConstIterator +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// + +ValueConstIterator::ValueConstIterator() = default; + +ValueConstIterator::ValueConstIterator( + const Value::ObjectValues::iterator& current) + : ValueIteratorBase(current) {} + +ValueConstIterator::ValueConstIterator(ValueIterator const& other) + : ValueIteratorBase(other) {} + +ValueConstIterator& ValueConstIterator:: +operator=(const ValueIteratorBase& other) { + copy(other); + return *this; +} + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class ValueIterator +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// + +ValueIterator::ValueIterator() = default; + +ValueIterator::ValueIterator(const Value::ObjectValues::iterator& current) + : ValueIteratorBase(current) {} + +ValueIterator::ValueIterator(const ValueConstIterator& other) + : ValueIteratorBase(other) { + throwRuntimeError("ConstIterator to Iterator should never be allowed."); +} + +ValueIterator::ValueIterator(const ValueIterator& other) = default; + +ValueIterator& ValueIterator::operator=(const SelfType& other) { + copy(other); + return *this; +} + +} // namespace Json + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: src/lib_json/json_valueiterator.inl +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: src/lib_json/json_value.cpp +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2011 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include +#endif // if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include +#include +#include +#ifdef JSON_USE_CPPTL +#include +#endif +#include // min() +#include // size_t + +// Provide implementation equivalent of std::snprintf for older _MSC compilers +#if defined(_MSC_VER) && _MSC_VER < 1900 +#include +static int msvc_pre1900_c99_vsnprintf(char* outBuf, + size_t size, + const char* format, + va_list ap) { + int count = -1; + if (size != 0) + count = _vsnprintf_s(outBuf, size, _TRUNCATE, format, ap); + if (count == -1) + count = _vscprintf(format, ap); + return count; +} + +int JSON_API msvc_pre1900_c99_snprintf(char* outBuf, + size_t size, + const char* format, + ...) { + va_list ap; + va_start(ap, format); + const int count = msvc_pre1900_c99_vsnprintf(outBuf, size, format, ap); + va_end(ap); + return count; +} +#endif + +// Disable warning C4702 : unreachable code +#if defined(_MSC_VER) +#pragma warning(disable : 4702) +#endif + +#define JSON_ASSERT_UNREACHABLE assert(false) + +namespace Json { + +template +static std::unique_ptr cloneUnique(const std::unique_ptr& p) { + std::unique_ptr r; + if (p) { + r = std::unique_ptr(new T(*p)); + } + return r; +} + +// This is a walkaround to avoid the static initialization of Value::null. +// kNull must be word-aligned to avoid crashing on ARM. We use an alignment of +// 8 (instead of 4) as a bit of future-proofing. +#if defined(__ARMEL__) +#define ALIGNAS(byte_alignment) __attribute__((aligned(byte_alignment))) +#else +#define ALIGNAS(byte_alignment) +#endif +// static const unsigned char ALIGNAS(8) kNull[sizeof(Value)] = { 0 }; +// const unsigned char& kNullRef = kNull[0]; +// const Value& Value::null = reinterpret_cast(kNullRef); +// const Value& Value::nullRef = null; + +// static +Value const& Value::nullSingleton() { + static Value const nullStatic; + return nullStatic; +} + +// for backwards compatibility, we'll leave these global references around, but +// DO NOT use them in JSONCPP library code any more! +Value const& Value::null = Value::nullSingleton(); +Value const& Value::nullRef = Value::nullSingleton(); + +const Int Value::minInt = Int(~(UInt(-1) / 2)); +const Int Value::maxInt = Int(UInt(-1) / 2); +const UInt Value::maxUInt = UInt(-1); +#if defined(JSON_HAS_INT64) +const Int64 Value::minInt64 = Int64(~(UInt64(-1) / 2)); +const Int64 Value::maxInt64 = Int64(UInt64(-1) / 2); +const UInt64 Value::maxUInt64 = UInt64(-1); +// The constant is hard-coded because some compiler have trouble +// converting Value::maxUInt64 to a double correctly (AIX/xlC). +// Assumes that UInt64 is a 64 bits integer. +static const double maxUInt64AsDouble = 18446744073709551615.0; +#endif // defined(JSON_HAS_INT64) +const LargestInt Value::minLargestInt = LargestInt(~(LargestUInt(-1) / 2)); +const LargestInt Value::maxLargestInt = LargestInt(LargestUInt(-1) / 2); +const LargestUInt Value::maxLargestUInt = LargestUInt(-1); + +const UInt Value::defaultRealPrecision = 17; + +#if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) +template +static inline bool InRange(double d, T min, U max) { + // The casts can lose precision, but we are looking only for + // an approximate range. Might fail on edge cases though. ~cdunn + // return d >= static_cast(min) && d <= static_cast(max); + return d >= min && d <= max; +} +#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) +static inline double integerToDouble(Json::UInt64 value) { + return static_cast(Int64(value / 2)) * 2.0 + + static_cast(Int64(value & 1)); +} + +template static inline double integerToDouble(T value) { + return static_cast(value); +} + +template +static inline bool InRange(double d, T min, U max) { + return d >= integerToDouble(min) && d <= integerToDouble(max); +} +#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + +/** Duplicates the specified string value. + * @param value Pointer to the string to duplicate. Must be zero-terminated if + * length is "unknown". + * @param length Length of the value. if equals to unknown, then it will be + * computed using strlen(value). + * @return Pointer on the duplicate instance of string. + */ +static inline char* duplicateStringValue(const char* value, size_t length) { + // Avoid an integer overflow in the call to malloc below by limiting length + // to a sane value. + if (length >= static_cast(Value::maxInt)) + length = Value::maxInt - 1; + + char* newString = static_cast(malloc(length + 1)); + if (newString == nullptr) { + throwRuntimeError("in Json::Value::duplicateStringValue(): " + "Failed to allocate string value buffer"); + } + memcpy(newString, value, length); + newString[length] = 0; + return newString; +} + +/* Record the length as a prefix. + */ +static inline char* duplicateAndPrefixStringValue(const char* value, + unsigned int length) { + // Avoid an integer overflow in the call to malloc below by limiting length + // to a sane value. + JSON_ASSERT_MESSAGE(length <= static_cast(Value::maxInt) - + sizeof(unsigned) - 1U, + "in Json::Value::duplicateAndPrefixStringValue(): " + "length too big for prefixing"); + unsigned actualLength = length + static_cast(sizeof(unsigned)) + 1U; + char* newString = static_cast(malloc(actualLength)); + if (newString == nullptr) { + throwRuntimeError("in Json::Value::duplicateAndPrefixStringValue(): " + "Failed to allocate string value buffer"); + } + *reinterpret_cast(newString) = length; + memcpy(newString + sizeof(unsigned), value, length); + newString[actualLength - 1U] = + 0; // to avoid buffer over-run accidents by users later + return newString; +} +inline static void decodePrefixedString(bool isPrefixed, + char const* prefixed, + unsigned* length, + char const** value) { + if (!isPrefixed) { + *length = static_cast(strlen(prefixed)); + *value = prefixed; + } else { + *length = *reinterpret_cast(prefixed); + *value = prefixed + sizeof(unsigned); + } +} +/** Free the string duplicated by + * duplicateStringValue()/duplicateAndPrefixStringValue(). + */ +#if JSONCPP_USING_SECURE_MEMORY +static inline void releasePrefixedStringValue(char* value) { + unsigned length = 0; + char const* valueDecoded; + decodePrefixedString(true, value, &length, &valueDecoded); + size_t const size = sizeof(unsigned) + length + 1U; + memset(value, 0, size); + free(value); +} +static inline void releaseStringValue(char* value, unsigned length) { + // length==0 => we allocated the strings memory + size_t size = (length == 0) ? strlen(value) : length; + memset(value, 0, size); + free(value); +} +#else // !JSONCPP_USING_SECURE_MEMORY +static inline void releasePrefixedStringValue(char* value) { free(value); } +static inline void releaseStringValue(char* value, unsigned) { free(value); } +#endif // JSONCPP_USING_SECURE_MEMORY + +} // namespace Json + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ValueInternals... +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +#if !defined(JSON_IS_AMALGAMATION) + +#include "json_valueiterator.inl" +#endif // if !defined(JSON_IS_AMALGAMATION) + +namespace Json { + +Exception::Exception(String msg) : msg_(std::move(msg)) {} +Exception::~Exception() JSONCPP_NOEXCEPT {} +char const* Exception::what() const JSONCPP_NOEXCEPT { return msg_.c_str(); } +RuntimeError::RuntimeError(String const& msg) : Exception(msg) {} +LogicError::LogicError(String const& msg) : Exception(msg) {} +JSONCPP_NORETURN void throwRuntimeError(String const& msg) { + throw RuntimeError(msg); +} +JSONCPP_NORETURN void throwLogicError(String const& msg) { + throw LogicError(msg); +} + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class Value::CZString +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// + +// Notes: policy_ indicates if the string was allocated when +// a string is stored. + +Value::CZString::CZString(ArrayIndex index) : cstr_(nullptr), index_(index) {} + +Value::CZString::CZString(char const* str, + unsigned length, + DuplicationPolicy allocate) + : cstr_(str) { + // allocate != duplicate + storage_.policy_ = allocate & 0x3; + storage_.length_ = length & 0x3FFFFFFF; +} + +Value::CZString::CZString(const CZString& other) { + cstr_ = (other.storage_.policy_ != noDuplication && other.cstr_ != nullptr + ? duplicateStringValue(other.cstr_, other.storage_.length_) + : other.cstr_); + storage_.policy_ = + static_cast( + other.cstr_ + ? (static_cast(other.storage_.policy_) == + noDuplication + ? noDuplication + : duplicate) + : static_cast(other.storage_.policy_)) & + 3U; + storage_.length_ = other.storage_.length_; +} + +Value::CZString::CZString(CZString&& other) + : cstr_(other.cstr_), index_(other.index_) { + other.cstr_ = nullptr; +} + +Value::CZString::~CZString() { + if (cstr_ && storage_.policy_ == duplicate) { + releaseStringValue(const_cast(cstr_), + storage_.length_ + 1u); // +1 for null terminating + // character for sake of + // completeness but not actually + // necessary + } +} + +void Value::CZString::swap(CZString& other) { + std::swap(cstr_, other.cstr_); + std::swap(index_, other.index_); +} + +Value::CZString& Value::CZString::operator=(const CZString& other) { + cstr_ = other.cstr_; + index_ = other.index_; + return *this; +} + +Value::CZString& Value::CZString::operator=(CZString&& other) { + cstr_ = other.cstr_; + index_ = other.index_; + other.cstr_ = nullptr; + return *this; +} + +bool Value::CZString::operator<(const CZString& other) const { + if (!cstr_) + return index_ < other.index_; + // return strcmp(cstr_, other.cstr_) < 0; + // Assume both are strings. + unsigned this_len = this->storage_.length_; + unsigned other_len = other.storage_.length_; + unsigned min_len = std::min(this_len, other_len); + JSON_ASSERT(this->cstr_ && other.cstr_); + int comp = memcmp(this->cstr_, other.cstr_, min_len); + if (comp < 0) + return true; + if (comp > 0) + return false; + return (this_len < other_len); +} + +bool Value::CZString::operator==(const CZString& other) const { + if (!cstr_) + return index_ == other.index_; + // return strcmp(cstr_, other.cstr_) == 0; + // Assume both are strings. + unsigned this_len = this->storage_.length_; + unsigned other_len = other.storage_.length_; + if (this_len != other_len) + return false; + JSON_ASSERT(this->cstr_ && other.cstr_); + int comp = memcmp(this->cstr_, other.cstr_, this_len); + return comp == 0; +} + +ArrayIndex Value::CZString::index() const { return index_; } + +// const char* Value::CZString::c_str() const { return cstr_; } +const char* Value::CZString::data() const { return cstr_; } +unsigned Value::CZString::length() const { return storage_.length_; } +bool Value::CZString::isStaticString() const { + return storage_.policy_ == noDuplication; +} + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class Value::Value +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// + +/*! \internal Default constructor initialization must be equivalent to: + * memset( this, 0, sizeof(Value) ) + * This optimization is used in ValueInternalMap fast allocator. + */ +Value::Value(ValueType type) { + static char const emptyString[] = ""; + initBasic(type); + switch (type) { + case nullValue: + break; + case intValue: + case uintValue: + value_.int_ = 0; + break; + case realValue: + value_.real_ = 0.0; + break; + case stringValue: + // allocated_ == false, so this is safe. + value_.string_ = const_cast(static_cast(emptyString)); + break; + case arrayValue: + case objectValue: + value_.map_ = new ObjectValues(); + break; + case booleanValue: + value_.bool_ = false; + break; + default: + JSON_ASSERT_UNREACHABLE; + } +} + +Value::Value(Int value) { + initBasic(intValue); + value_.int_ = value; +} + +Value::Value(UInt value) { + initBasic(uintValue); + value_.uint_ = value; +} +#if defined(JSON_HAS_INT64) +Value::Value(Int64 value) { + initBasic(intValue); + value_.int_ = value; +} +Value::Value(UInt64 value) { + initBasic(uintValue); + value_.uint_ = value; +} +#endif // defined(JSON_HAS_INT64) + +Value::Value(double value) { + initBasic(realValue); + value_.real_ = value; +} + +Value::Value(const char* value) { + initBasic(stringValue, true); + JSON_ASSERT_MESSAGE(value != nullptr, + "Null Value Passed to Value Constructor"); + value_.string_ = duplicateAndPrefixStringValue( + value, static_cast(strlen(value))); +} + +Value::Value(const char* begin, const char* end) { + initBasic(stringValue, true); + value_.string_ = + duplicateAndPrefixStringValue(begin, static_cast(end - begin)); +} + +Value::Value(const String& value) { + initBasic(stringValue, true); + value_.string_ = duplicateAndPrefixStringValue( + value.data(), static_cast(value.length())); +} + +Value::Value(const StaticString& value) { + initBasic(stringValue); + value_.string_ = const_cast(value.c_str()); +} + +#ifdef JSON_USE_CPPTL +Value::Value(const CppTL::ConstString& value) { + initBasic(stringValue, true); + value_.string_ = duplicateAndPrefixStringValue( + value, static_cast(value.length())); +} +#endif + +Value::Value(bool value) { + initBasic(booleanValue); + value_.bool_ = value; +} + +Value::Value(const Value& other) { + dupPayload(other); + dupMeta(other); +} + +Value::Value(Value&& other) { + initBasic(nullValue); + swap(other); +} + +Value::~Value() { + releasePayload(); + value_.uint_ = 0; +} + +Value& Value::operator=(const Value& other) { + Value(other).swap(*this); + return *this; +} + +Value& Value::operator=(Value&& other) { + other.swap(*this); + return *this; +} + +void Value::swapPayload(Value& other) { + std::swap(bits_, other.bits_); + std::swap(value_, other.value_); +} + +void Value::copyPayload(const Value& other) { + releasePayload(); + dupPayload(other); +} + +void Value::swap(Value& other) { + swapPayload(other); + std::swap(comments_, other.comments_); + std::swap(start_, other.start_); + std::swap(limit_, other.limit_); +} + +void Value::copy(const Value& other) { + copyPayload(other); + dupMeta(other); +} + +ValueType Value::type() const { + return static_cast(bits_.value_type_); +} + +int Value::compare(const Value& other) const { + if (*this < other) + return -1; + if (*this > other) + return 1; + return 0; +} + +bool Value::operator<(const Value& other) const { + int typeDelta = type() - other.type(); + if (typeDelta) + return typeDelta < 0 ? true : false; + switch (type()) { + case nullValue: + return false; + case intValue: + return value_.int_ < other.value_.int_; + case uintValue: + return value_.uint_ < other.value_.uint_; + case realValue: + return value_.real_ < other.value_.real_; + case booleanValue: + return value_.bool_ < other.value_.bool_; + case stringValue: { + if ((value_.string_ == nullptr) || (other.value_.string_ == nullptr)) { + if (other.value_.string_) + return true; + else + return false; + } + unsigned this_len; + unsigned other_len; + char const* this_str; + char const* other_str; + decodePrefixedString(this->isAllocated(), this->value_.string_, &this_len, + &this_str); + decodePrefixedString(other.isAllocated(), other.value_.string_, &other_len, + &other_str); + unsigned min_len = std::min(this_len, other_len); + JSON_ASSERT(this_str && other_str); + int comp = memcmp(this_str, other_str, min_len); + if (comp < 0) + return true; + if (comp > 0) + return false; + return (this_len < other_len); + } + case arrayValue: + case objectValue: { + int delta = int(value_.map_->size() - other.value_.map_->size()); + if (delta) + return delta < 0; + return (*value_.map_) < (*other.value_.map_); + } + default: + JSON_ASSERT_UNREACHABLE; + } + return false; // unreachable +} + +bool Value::operator<=(const Value& other) const { return !(other < *this); } + +bool Value::operator>=(const Value& other) const { return !(*this < other); } + +bool Value::operator>(const Value& other) const { return other < *this; } + +bool Value::operator==(const Value& other) const { + if (type() != other.type()) + return false; + switch (type()) { + case nullValue: + return true; + case intValue: + return value_.int_ == other.value_.int_; + case uintValue: + return value_.uint_ == other.value_.uint_; + case realValue: + return value_.real_ == other.value_.real_; + case booleanValue: + return value_.bool_ == other.value_.bool_; + case stringValue: { + if ((value_.string_ == nullptr) || (other.value_.string_ == nullptr)) { + return (value_.string_ == other.value_.string_); + } + unsigned this_len; + unsigned other_len; + char const* this_str; + char const* other_str; + decodePrefixedString(this->isAllocated(), this->value_.string_, &this_len, + &this_str); + decodePrefixedString(other.isAllocated(), other.value_.string_, &other_len, + &other_str); + if (this_len != other_len) + return false; + JSON_ASSERT(this_str && other_str); + int comp = memcmp(this_str, other_str, this_len); + return comp == 0; + } + case arrayValue: + case objectValue: + return value_.map_->size() == other.value_.map_->size() && + (*value_.map_) == (*other.value_.map_); + default: + JSON_ASSERT_UNREACHABLE; + } + return false; // unreachable +} + +bool Value::operator!=(const Value& other) const { return !(*this == other); } + +const char* Value::asCString() const { + JSON_ASSERT_MESSAGE(type() == stringValue, + "in Json::Value::asCString(): requires stringValue"); + if (value_.string_ == nullptr) + return nullptr; + unsigned this_len; + char const* this_str; + decodePrefixedString(this->isAllocated(), this->value_.string_, &this_len, + &this_str); + return this_str; +} + +#if JSONCPP_USING_SECURE_MEMORY +unsigned Value::getCStringLength() const { + JSON_ASSERT_MESSAGE(type() == stringValue, + "in Json::Value::asCString(): requires stringValue"); + if (value_.string_ == 0) + return 0; + unsigned this_len; + char const* this_str; + decodePrefixedString(this->isAllocated(), this->value_.string_, &this_len, + &this_str); + return this_len; +} +#endif + +bool Value::getString(char const** begin, char const** end) const { + if (type() != stringValue) + return false; + if (value_.string_ == nullptr) + return false; + unsigned length; + decodePrefixedString(this->isAllocated(), this->value_.string_, &length, + begin); + *end = *begin + length; + return true; +} + +String Value::asString() const { + switch (type()) { + case nullValue: + return ""; + case stringValue: { + if (value_.string_ == nullptr) + return ""; + unsigned this_len; + char const* this_str; + decodePrefixedString(this->isAllocated(), this->value_.string_, &this_len, + &this_str); + return String(this_str, this_len); + } + case booleanValue: + return value_.bool_ ? "true" : "false"; + case intValue: + return valueToString(value_.int_); + case uintValue: + return valueToString(value_.uint_); + case realValue: + return valueToString(value_.real_); + default: + JSON_FAIL_MESSAGE("Type is not convertible to string"); + } +} + +#ifdef JSON_USE_CPPTL +CppTL::ConstString Value::asConstString() const { + unsigned len; + char const* str; + decodePrefixedString(isAllocated(), value_.string_, &len, &str); + return CppTL::ConstString(str, len); +} +#endif + +Value::Int Value::asInt() const { + switch (type()) { + case intValue: + JSON_ASSERT_MESSAGE(isInt(), "LargestInt out of Int range"); + return Int(value_.int_); + case uintValue: + JSON_ASSERT_MESSAGE(isInt(), "LargestUInt out of Int range"); + return Int(value_.uint_); + case realValue: + JSON_ASSERT_MESSAGE(InRange(value_.real_, minInt, maxInt), + "double out of Int range"); + return Int(value_.real_); + case nullValue: + return 0; + case booleanValue: + return value_.bool_ ? 1 : 0; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to Int."); +} + +Value::UInt Value::asUInt() const { + switch (type()) { + case intValue: + JSON_ASSERT_MESSAGE(isUInt(), "LargestInt out of UInt range"); + return UInt(value_.int_); + case uintValue: + JSON_ASSERT_MESSAGE(isUInt(), "LargestUInt out of UInt range"); + return UInt(value_.uint_); + case realValue: + JSON_ASSERT_MESSAGE(InRange(value_.real_, 0, maxUInt), + "double out of UInt range"); + return UInt(value_.real_); + case nullValue: + return 0; + case booleanValue: + return value_.bool_ ? 1 : 0; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to UInt."); +} + +#if defined(JSON_HAS_INT64) + +Value::Int64 Value::asInt64() const { + switch (type()) { + case intValue: + return Int64(value_.int_); + case uintValue: + JSON_ASSERT_MESSAGE(isInt64(), "LargestUInt out of Int64 range"); + return Int64(value_.uint_); + case realValue: + JSON_ASSERT_MESSAGE(InRange(value_.real_, minInt64, maxInt64), + "double out of Int64 range"); + return Int64(value_.real_); + case nullValue: + return 0; + case booleanValue: + return value_.bool_ ? 1 : 0; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to Int64."); +} + +Value::UInt64 Value::asUInt64() const { + switch (type()) { + case intValue: + JSON_ASSERT_MESSAGE(isUInt64(), "LargestInt out of UInt64 range"); + return UInt64(value_.int_); + case uintValue: + return UInt64(value_.uint_); + case realValue: + JSON_ASSERT_MESSAGE(InRange(value_.real_, 0, maxUInt64), + "double out of UInt64 range"); + return UInt64(value_.real_); + case nullValue: + return 0; + case booleanValue: + return value_.bool_ ? 1 : 0; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to UInt64."); +} +#endif // if defined(JSON_HAS_INT64) + +LargestInt Value::asLargestInt() const { +#if defined(JSON_NO_INT64) + return asInt(); +#else + return asInt64(); +#endif +} + +LargestUInt Value::asLargestUInt() const { +#if defined(JSON_NO_INT64) + return asUInt(); +#else + return asUInt64(); +#endif +} + +double Value::asDouble() const { + switch (type()) { + case intValue: + return static_cast(value_.int_); + case uintValue: +#if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + return static_cast(value_.uint_); +#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + return integerToDouble(value_.uint_); +#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + case realValue: + return value_.real_; + case nullValue: + return 0.0; + case booleanValue: + return value_.bool_ ? 1.0 : 0.0; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to double."); +} + +float Value::asFloat() const { + switch (type()) { + case intValue: + return static_cast(value_.int_); + case uintValue: +#if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + return static_cast(value_.uint_); +#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + // This can fail (silently?) if the value is bigger than MAX_FLOAT. + return static_cast(integerToDouble(value_.uint_)); +#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + case realValue: + return static_cast(value_.real_); + case nullValue: + return 0.0; + case booleanValue: + return value_.bool_ ? 1.0f : 0.0f; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to float."); +} + +bool Value::asBool() const { + switch (type()) { + case booleanValue: + return value_.bool_; + case nullValue: + return false; + case intValue: + return value_.int_ ? true : false; + case uintValue: + return value_.uint_ ? true : false; + case realValue: + // This is kind of strange. Not recommended. + return (value_.real_ != 0.0) ? true : false; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to bool."); +} + +bool Value::isConvertibleTo(ValueType other) const { + switch (other) { + case nullValue: + return (isNumeric() && asDouble() == 0.0) || + (type() == booleanValue && value_.bool_ == false) || + (type() == stringValue && asString().empty()) || + (type() == arrayValue && value_.map_->empty()) || + (type() == objectValue && value_.map_->empty()) || + type() == nullValue; + case intValue: + return isInt() || + (type() == realValue && InRange(value_.real_, minInt, maxInt)) || + type() == booleanValue || type() == nullValue; + case uintValue: + return isUInt() || + (type() == realValue && InRange(value_.real_, 0, maxUInt)) || + type() == booleanValue || type() == nullValue; + case realValue: + return isNumeric() || type() == booleanValue || type() == nullValue; + case booleanValue: + return isNumeric() || type() == booleanValue || type() == nullValue; + case stringValue: + return isNumeric() || type() == booleanValue || type() == stringValue || + type() == nullValue; + case arrayValue: + return type() == arrayValue || type() == nullValue; + case objectValue: + return type() == objectValue || type() == nullValue; + } + JSON_ASSERT_UNREACHABLE; + return false; +} + +/// Number of values in array or object +ArrayIndex Value::size() const { + switch (type()) { + case nullValue: + case intValue: + case uintValue: + case realValue: + case booleanValue: + case stringValue: + return 0; + case arrayValue: // size of the array is highest index + 1 + if (!value_.map_->empty()) { + ObjectValues::const_iterator itLast = value_.map_->end(); + --itLast; + return (*itLast).first.index() + 1; + } + return 0; + case objectValue: + return ArrayIndex(value_.map_->size()); + } + JSON_ASSERT_UNREACHABLE; + return 0; // unreachable; +} + +bool Value::empty() const { + if (isNull() || isArray() || isObject()) + return size() == 0u; + else + return false; +} + +Value::operator bool() const { return !isNull(); } + +void Value::clear() { + JSON_ASSERT_MESSAGE(type() == nullValue || type() == arrayValue || + type() == objectValue, + "in Json::Value::clear(): requires complex value"); + start_ = 0; + limit_ = 0; + switch (type()) { + case arrayValue: + case objectValue: + value_.map_->clear(); + break; + default: + break; + } +} + +void Value::resize(ArrayIndex newSize) { + JSON_ASSERT_MESSAGE(type() == nullValue || type() == arrayValue, + "in Json::Value::resize(): requires arrayValue"); + if (type() == nullValue) + *this = Value(arrayValue); + ArrayIndex oldSize = size(); + if (newSize == 0) + clear(); + else if (newSize > oldSize) + this->operator[](newSize - 1); + else { + for (ArrayIndex index = newSize; index < oldSize; ++index) { + value_.map_->erase(index); + } + JSON_ASSERT(size() == newSize); + } +} + +Value& Value::operator[](ArrayIndex index) { + JSON_ASSERT_MESSAGE( + type() == nullValue || type() == arrayValue, + "in Json::Value::operator[](ArrayIndex): requires arrayValue"); + if (type() == nullValue) + *this = Value(arrayValue); + CZString key(index); + auto it = value_.map_->lower_bound(key); + if (it != value_.map_->end() && (*it).first == key) + return (*it).second; + + ObjectValues::value_type defaultValue(key, nullSingleton()); + it = value_.map_->insert(it, defaultValue); + return (*it).second; +} + +Value& Value::operator[](int index) { + JSON_ASSERT_MESSAGE( + index >= 0, + "in Json::Value::operator[](int index): index cannot be negative"); + return (*this)[ArrayIndex(index)]; +} + +const Value& Value::operator[](ArrayIndex index) const { + JSON_ASSERT_MESSAGE( + type() == nullValue || type() == arrayValue, + "in Json::Value::operator[](ArrayIndex)const: requires arrayValue"); + if (type() == nullValue) + return nullSingleton(); + CZString key(index); + ObjectValues::const_iterator it = value_.map_->find(key); + if (it == value_.map_->end()) + return nullSingleton(); + return (*it).second; +} + +const Value& Value::operator[](int index) const { + JSON_ASSERT_MESSAGE( + index >= 0, + "in Json::Value::operator[](int index) const: index cannot be negative"); + return (*this)[ArrayIndex(index)]; +} + +void Value::initBasic(ValueType type, bool allocated) { + setType(type); + setIsAllocated(allocated); + comments_ = Comments{}; + start_ = 0; + limit_ = 0; +} + +void Value::dupPayload(const Value& other) { + setType(other.type()); + setIsAllocated(false); + switch (type()) { + case nullValue: + case intValue: + case uintValue: + case realValue: + case booleanValue: + value_ = other.value_; + break; + case stringValue: + if (other.value_.string_ && other.isAllocated()) { + unsigned len; + char const* str; + decodePrefixedString(other.isAllocated(), other.value_.string_, &len, + &str); + value_.string_ = duplicateAndPrefixStringValue(str, len); + setIsAllocated(true); + } else { + value_.string_ = other.value_.string_; + } + break; + case arrayValue: + case objectValue: + value_.map_ = new ObjectValues(*other.value_.map_); + break; + default: + JSON_ASSERT_UNREACHABLE; + } +} + +void Value::releasePayload() { + switch (type()) { + case nullValue: + case intValue: + case uintValue: + case realValue: + case booleanValue: + break; + case stringValue: + if (isAllocated()) + releasePrefixedStringValue(value_.string_); + break; + case arrayValue: + case objectValue: + delete value_.map_; + break; + default: + JSON_ASSERT_UNREACHABLE; + } +} + +void Value::dupMeta(const Value& other) { + comments_ = other.comments_; + start_ = other.start_; + limit_ = other.limit_; +} + +// Access an object value by name, create a null member if it does not exist. +// @pre Type of '*this' is object or null. +// @param key is null-terminated. +Value& Value::resolveReference(const char* key) { + JSON_ASSERT_MESSAGE( + type() == nullValue || type() == objectValue, + "in Json::Value::resolveReference(): requires objectValue"); + if (type() == nullValue) + *this = Value(objectValue); + CZString actualKey(key, static_cast(strlen(key)), + CZString::noDuplication); // NOTE! + auto it = value_.map_->lower_bound(actualKey); + if (it != value_.map_->end() && (*it).first == actualKey) + return (*it).second; + + ObjectValues::value_type defaultValue(actualKey, nullSingleton()); + it = value_.map_->insert(it, defaultValue); + Value& value = (*it).second; + return value; +} + +// @param key is not null-terminated. +Value& Value::resolveReference(char const* key, char const* end) { + JSON_ASSERT_MESSAGE( + type() == nullValue || type() == objectValue, + "in Json::Value::resolveReference(key, end): requires objectValue"); + if (type() == nullValue) + *this = Value(objectValue); + CZString actualKey(key, static_cast(end - key), + CZString::duplicateOnCopy); + auto it = value_.map_->lower_bound(actualKey); + if (it != value_.map_->end() && (*it).first == actualKey) + return (*it).second; + + ObjectValues::value_type defaultValue(actualKey, nullSingleton()); + it = value_.map_->insert(it, defaultValue); + Value& value = (*it).second; + return value; +} + +Value Value::get(ArrayIndex index, const Value& defaultValue) const { + const Value* value = &((*this)[index]); + return value == &nullSingleton() ? defaultValue : *value; +} + +bool Value::isValidIndex(ArrayIndex index) const { return index < size(); } + +Value const* Value::find(char const* begin, char const* end) const { + JSON_ASSERT_MESSAGE(type() == nullValue || type() == objectValue, + "in Json::Value::find(begin, end): requires " + "objectValue or nullValue"); + if (type() == nullValue) + return nullptr; + CZString actualKey(begin, static_cast(end - begin), + CZString::noDuplication); + ObjectValues::const_iterator it = value_.map_->find(actualKey); + if (it == value_.map_->end()) + return nullptr; + return &(*it).second; +} +Value* Value::demand(char const* begin, char const* end) { + JSON_ASSERT_MESSAGE(type() == nullValue || type() == objectValue, + "in Json::Value::demand(begin, end): requires " + "objectValue or nullValue"); + return &resolveReference(begin, end); +} +const Value& Value::operator[](const char* key) const { + Value const* found = find(key, key + strlen(key)); + if (!found) + return nullSingleton(); + return *found; +} +Value const& Value::operator[](const String& key) const { + Value const* found = find(key.data(), key.data() + key.length()); + if (!found) + return nullSingleton(); + return *found; +} + +Value& Value::operator[](const char* key) { + return resolveReference(key, key + strlen(key)); +} + +Value& Value::operator[](const String& key) { + return resolveReference(key.data(), key.data() + key.length()); +} + +Value& Value::operator[](const StaticString& key) { + return resolveReference(key.c_str()); +} + +#ifdef JSON_USE_CPPTL +Value& Value::operator[](const CppTL::ConstString& key) { + return resolveReference(key.c_str(), key.end_c_str()); +} +Value const& Value::operator[](CppTL::ConstString const& key) const { + Value const* found = find(key.c_str(), key.end_c_str()); + if (!found) + return nullSingleton(); + return *found; +} +#endif + +Value& Value::append(const Value& value) { return (*this)[size()] = value; } + +Value& Value::append(Value&& value) { + return (*this)[size()] = std::move(value); +} + +Value Value::get(char const* begin, + char const* end, + Value const& defaultValue) const { + Value const* found = find(begin, end); + return !found ? defaultValue : *found; +} +Value Value::get(char const* key, Value const& defaultValue) const { + return get(key, key + strlen(key), defaultValue); +} +Value Value::get(String const& key, Value const& defaultValue) const { + return get(key.data(), key.data() + key.length(), defaultValue); +} + +bool Value::removeMember(const char* begin, const char* end, Value* removed) { + if (type() != objectValue) { + return false; + } + CZString actualKey(begin, static_cast(end - begin), + CZString::noDuplication); + auto it = value_.map_->find(actualKey); + if (it == value_.map_->end()) + return false; + if (removed) + *removed = std::move(it->second); + value_.map_->erase(it); + return true; +} +bool Value::removeMember(const char* key, Value* removed) { + return removeMember(key, key + strlen(key), removed); +} +bool Value::removeMember(String const& key, Value* removed) { + return removeMember(key.data(), key.data() + key.length(), removed); +} +void Value::removeMember(const char* key) { + JSON_ASSERT_MESSAGE(type() == nullValue || type() == objectValue, + "in Json::Value::removeMember(): requires objectValue"); + if (type() == nullValue) + return; + + CZString actualKey(key, unsigned(strlen(key)), CZString::noDuplication); + value_.map_->erase(actualKey); +} +void Value::removeMember(const String& key) { removeMember(key.c_str()); } + +bool Value::removeIndex(ArrayIndex index, Value* removed) { + if (type() != arrayValue) { + return false; + } + CZString key(index); + auto it = value_.map_->find(key); + if (it == value_.map_->end()) { + return false; + } + if (removed) + *removed = it->second; + ArrayIndex oldSize = size(); + // shift left all items left, into the place of the "removed" + for (ArrayIndex i = index; i < (oldSize - 1); ++i) { + CZString keey(i); + (*value_.map_)[keey] = (*this)[i + 1]; + } + // erase the last one ("leftover") + CZString keyLast(oldSize - 1); + auto itLast = value_.map_->find(keyLast); + value_.map_->erase(itLast); + return true; +} + +#ifdef JSON_USE_CPPTL +Value Value::get(const CppTL::ConstString& key, + const Value& defaultValue) const { + return get(key.c_str(), key.end_c_str(), defaultValue); +} +#endif + +bool Value::isMember(char const* begin, char const* end) const { + Value const* value = find(begin, end); + return nullptr != value; +} +bool Value::isMember(char const* key) const { + return isMember(key, key + strlen(key)); +} +bool Value::isMember(String const& key) const { + return isMember(key.data(), key.data() + key.length()); +} + +#ifdef JSON_USE_CPPTL +bool Value::isMember(const CppTL::ConstString& key) const { + return isMember(key.c_str(), key.end_c_str()); +} +#endif + +Value::Members Value::getMemberNames() const { + JSON_ASSERT_MESSAGE( + type() == nullValue || type() == objectValue, + "in Json::Value::getMemberNames(), value must be objectValue"); + if (type() == nullValue) + return Value::Members(); + Members members; + members.reserve(value_.map_->size()); + ObjectValues::const_iterator it = value_.map_->begin(); + ObjectValues::const_iterator itEnd = value_.map_->end(); + for (; it != itEnd; ++it) { + members.push_back(String((*it).first.data(), (*it).first.length())); + } + return members; +} +// +//# ifdef JSON_USE_CPPTL +// EnumMemberNames +// Value::enumMemberNames() const +//{ +// if ( type() == objectValue ) +// { +// return CppTL::Enum::any( CppTL::Enum::transform( +// CppTL::Enum::keys( *(value_.map_), CppTL::Type() ), +// MemberNamesTransform() ) ); +// } +// return EnumMemberNames(); +//} +// +// +// EnumValues +// Value::enumValues() const +//{ +// if ( type() == objectValue || type() == arrayValue ) +// return CppTL::Enum::anyValues( *(value_.map_), +// CppTL::Type() ); +// return EnumValues(); +//} +// +//# endif + +static bool IsIntegral(double d) { + double integral_part; + return modf(d, &integral_part) == 0.0; +} + +bool Value::isNull() const { return type() == nullValue; } + +bool Value::isBool() const { return type() == booleanValue; } + +bool Value::isInt() const { + switch (type()) { + case intValue: +#if defined(JSON_HAS_INT64) + return value_.int_ >= minInt && value_.int_ <= maxInt; +#else + return true; +#endif + case uintValue: + return value_.uint_ <= UInt(maxInt); + case realValue: + return value_.real_ >= minInt && value_.real_ <= maxInt && + IsIntegral(value_.real_); + default: + break; + } + return false; +} + +bool Value::isUInt() const { + switch (type()) { + case intValue: +#if defined(JSON_HAS_INT64) + return value_.int_ >= 0 && LargestUInt(value_.int_) <= LargestUInt(maxUInt); +#else + return value_.int_ >= 0; +#endif + case uintValue: +#if defined(JSON_HAS_INT64) + return value_.uint_ <= maxUInt; +#else + return true; +#endif + case realValue: + return value_.real_ >= 0 && value_.real_ <= maxUInt && + IsIntegral(value_.real_); + default: + break; + } + return false; +} + +bool Value::isInt64() const { +#if defined(JSON_HAS_INT64) + switch (type()) { + case intValue: + return true; + case uintValue: + return value_.uint_ <= UInt64(maxInt64); + case realValue: + // Note that maxInt64 (= 2^63 - 1) is not exactly representable as a + // double, so double(maxInt64) will be rounded up to 2^63. Therefore we + // require the value to be strictly less than the limit. + return value_.real_ >= double(minInt64) && + value_.real_ < double(maxInt64) && IsIntegral(value_.real_); + default: + break; + } +#endif // JSON_HAS_INT64 + return false; +} + +bool Value::isUInt64() const { +#if defined(JSON_HAS_INT64) + switch (type()) { + case intValue: + return value_.int_ >= 0; + case uintValue: + return true; + case realValue: + // Note that maxUInt64 (= 2^64 - 1) is not exactly representable as a + // double, so double(maxUInt64) will be rounded up to 2^64. Therefore we + // require the value to be strictly less than the limit. + return value_.real_ >= 0 && value_.real_ < maxUInt64AsDouble && + IsIntegral(value_.real_); + default: + break; + } +#endif // JSON_HAS_INT64 + return false; +} + +bool Value::isIntegral() const { + switch (type()) { + case intValue: + case uintValue: + return true; + case realValue: +#if defined(JSON_HAS_INT64) + // Note that maxUInt64 (= 2^64 - 1) is not exactly representable as a + // double, so double(maxUInt64) will be rounded up to 2^64. Therefore we + // require the value to be strictly less than the limit. + return value_.real_ >= double(minInt64) && + value_.real_ < maxUInt64AsDouble && IsIntegral(value_.real_); +#else + return value_.real_ >= minInt && value_.real_ <= maxUInt && + IsIntegral(value_.real_); +#endif // JSON_HAS_INT64 + default: + break; + } + return false; +} + +bool Value::isDouble() const { + return type() == intValue || type() == uintValue || type() == realValue; +} + +bool Value::isNumeric() const { return isDouble(); } + +bool Value::isString() const { return type() == stringValue; } + +bool Value::isArray() const { return type() == arrayValue; } + +bool Value::isObject() const { return type() == objectValue; } + +Value::Comments::Comments(const Comments& that) + : ptr_{cloneUnique(that.ptr_)} {} + +Value::Comments::Comments(Comments&& that) + : ptr_{std::move(that.ptr_)} {} + +Value::Comments& Value::Comments::operator=(const Comments& that) { + ptr_ = cloneUnique(that.ptr_); + return *this; +} + +Value::Comments& Value::Comments::operator=(Comments&& that) { + ptr_ = std::move(that.ptr_); + return *this; +} + +bool Value::Comments::has(CommentPlacement slot) const { + return ptr_ && !(*ptr_)[slot].empty(); +} + +String Value::Comments::get(CommentPlacement slot) const { + if (!ptr_) + return {}; + return (*ptr_)[slot]; +} + +void Value::Comments::set(CommentPlacement slot, String comment) { + if (!ptr_) { + ptr_ = std::unique_ptr(new Array()); + } + (*ptr_)[slot] = std::move(comment); +} + +void Value::setComment(String comment, CommentPlacement placement) { + if (!comment.empty() && (comment.back() == '\n')) { + // Always discard trailing newline, to aid indentation. + comment.pop_back(); + } + JSON_ASSERT(!comment.empty()); + JSON_ASSERT_MESSAGE( + comment[0] == '\0' || comment[0] == '/', + "in Json::Value::setComment(): Comments must start with /"); + comments_.set(placement, std::move(comment)); +} + +bool Value::hasComment(CommentPlacement placement) const { + return comments_.has(placement); +} + +String Value::getComment(CommentPlacement placement) const { + return comments_.get(placement); +} + +void Value::setOffsetStart(ptrdiff_t start) { start_ = start; } + +void Value::setOffsetLimit(ptrdiff_t limit) { limit_ = limit; } + +ptrdiff_t Value::getOffsetStart() const { return start_; } + +ptrdiff_t Value::getOffsetLimit() const { return limit_; } + +String Value::toStyledString() const { + StreamWriterBuilder builder; + + String out = this->hasComment(commentBefore) ? "\n" : ""; + out += Json::writeString(builder, *this); + out += '\n'; + + return out; +} + +Value::const_iterator Value::begin() const { + switch (type()) { + case arrayValue: + case objectValue: + if (value_.map_) + return const_iterator(value_.map_->begin()); + break; + default: + break; + } + return {}; +} + +Value::const_iterator Value::end() const { + switch (type()) { + case arrayValue: + case objectValue: + if (value_.map_) + return const_iterator(value_.map_->end()); + break; + default: + break; + } + return {}; +} + +Value::iterator Value::begin() { + switch (type()) { + case arrayValue: + case objectValue: + if (value_.map_) + return iterator(value_.map_->begin()); + break; + default: + break; + } + return iterator(); +} + +Value::iterator Value::end() { + switch (type()) { + case arrayValue: + case objectValue: + if (value_.map_) + return iterator(value_.map_->end()); + break; + default: + break; + } + return iterator(); +} + +// class PathArgument +// ////////////////////////////////////////////////////////////////// + +PathArgument::PathArgument() : key_() {} + +PathArgument::PathArgument(ArrayIndex index) + : key_(), index_(index), kind_(kindIndex) {} + +PathArgument::PathArgument(const char* key) + : key_(key), index_(), kind_(kindKey) {} + +PathArgument::PathArgument(const String& key) + : key_(key.c_str()), index_(), kind_(kindKey) {} + +// class Path +// ////////////////////////////////////////////////////////////////// + +Path::Path(const String& path, + const PathArgument& a1, + const PathArgument& a2, + const PathArgument& a3, + const PathArgument& a4, + const PathArgument& a5) { + InArgs in; + in.reserve(5); + in.push_back(&a1); + in.push_back(&a2); + in.push_back(&a3); + in.push_back(&a4); + in.push_back(&a5); + makePath(path, in); +} + +void Path::makePath(const String& path, const InArgs& in) { + const char* current = path.c_str(); + const char* end = current + path.length(); + auto itInArg = in.begin(); + while (current != end) { + if (*current == '[') { + ++current; + if (*current == '%') + addPathInArg(path, in, itInArg, PathArgument::kindIndex); + else { + ArrayIndex index = 0; + for (; current != end && *current >= '0' && *current <= '9'; ++current) + index = index * 10 + ArrayIndex(*current - '0'); + args_.push_back(index); + } + if (current == end || *++current != ']') + invalidPath(path, int(current - path.c_str())); + } else if (*current == '%') { + addPathInArg(path, in, itInArg, PathArgument::kindKey); + ++current; + } else if (*current == '.' || *current == ']') { + ++current; + } else { + const char* beginName = current; + while (current != end && !strchr("[.", *current)) + ++current; + args_.push_back(String(beginName, current)); + } + } +} + +void Path::addPathInArg(const String& /*path*/, + const InArgs& in, + InArgs::const_iterator& itInArg, + PathArgument::Kind kind) { + if (itInArg == in.end()) { + // Error: missing argument %d + } else if ((*itInArg)->kind_ != kind) { + // Error: bad argument type + } else { + args_.push_back(**itInArg++); + } +} + +void Path::invalidPath(const String& /*path*/, int /*location*/) { + // Error: invalid path. +} + +const Value& Path::resolve(const Value& root) const { + const Value* node = &root; + for (const auto& arg : args_) { + if (arg.kind_ == PathArgument::kindIndex) { + if (!node->isArray() || !node->isValidIndex(arg.index_)) { + // Error: unable to resolve path (array value expected at position... + return Value::null; + } + node = &((*node)[arg.index_]); + } else if (arg.kind_ == PathArgument::kindKey) { + if (!node->isObject()) { + // Error: unable to resolve path (object value expected at position...) + return Value::null; + } + node = &((*node)[arg.key_]); + if (node == &Value::nullSingleton()) { + // Error: unable to resolve path (object has no member named '' at + // position...) + return Value::null; + } + } + } + return *node; +} + +Value Path::resolve(const Value& root, const Value& defaultValue) const { + const Value* node = &root; + for (const auto& arg : args_) { + if (arg.kind_ == PathArgument::kindIndex) { + if (!node->isArray() || !node->isValidIndex(arg.index_)) + return defaultValue; + node = &((*node)[arg.index_]); + } else if (arg.kind_ == PathArgument::kindKey) { + if (!node->isObject()) + return defaultValue; + node = &((*node)[arg.key_]); + if (node == &Value::nullSingleton()) + return defaultValue; + } + } + return *node; +} + +Value& Path::make(Value& root) const { + Value* node = &root; + for (const auto& arg : args_) { + if (arg.kind_ == PathArgument::kindIndex) { + if (!node->isArray()) { + // Error: node is not an array at position ... + } + node = &((*node)[arg.index_]); + } else if (arg.kind_ == PathArgument::kindKey) { + if (!node->isObject()) { + // Error: node is not an object at position... + } + node = &((*node)[arg.key_]); + } + } + return *node; +} + +} // namespace Json + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: src/lib_json/json_value.cpp +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: src/lib_json/json_writer.cpp +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2011 Baptiste Lepilleur and The JsonCpp Authors +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#if !defined(JSON_IS_AMALGAMATION) +#include "json_tool.h" +#include +#endif // if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include +#include +#include +#include +#include + +#if __cplusplus >= 201103L +#include +#include + +#if !defined(isnan) +#define isnan std::isnan +#endif + +#if !defined(isfinite) +#define isfinite std::isfinite +#endif + +#else +#include +#include + +#if defined(_MSC_VER) +#if !defined(isnan) +#include +#define isnan _isnan +#endif + +#if !defined(isfinite) +#include +#define isfinite _finite +#endif + +#if !defined(_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES) +#define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1 +#endif //_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES + +#endif //_MSC_VER + +#if defined(__sun) && defined(__SVR4) // Solaris +#if !defined(isfinite) +#include +#define isfinite finite +#endif +#endif + +#if defined(__hpux) +#if !defined(isfinite) +#if defined(__ia64) && !defined(finite) +#define isfinite(x) \ + ((sizeof(x) == sizeof(float) ? _Isfinitef(x) : _IsFinite(x))) +#endif +#endif +#endif + +#if !defined(isnan) +// IEEE standard states that NaN values will not compare to themselves +#define isnan(x) (x != x) +#endif + +#if !defined(__APPLE__) +#if !defined(isfinite) +#define isfinite finite +#endif +#endif +#endif + +#if defined(_MSC_VER) +// Disable warning about strdup being deprecated. +#pragma warning(disable : 4996) +#endif + +namespace Json { + +#if __cplusplus >= 201103L || (defined(_CPPLIB_VER) && _CPPLIB_VER >= 520) +typedef std::unique_ptr StreamWriterPtr; +#else +typedef std::auto_ptr StreamWriterPtr; +#endif + +String valueToString(LargestInt value) { + UIntToStringBuffer buffer; + char* current = buffer + sizeof(buffer); + if (value == Value::minLargestInt) { + uintToString(LargestUInt(Value::maxLargestInt) + 1, current); + *--current = '-'; + } else if (value < 0) { + uintToString(LargestUInt(-value), current); + *--current = '-'; + } else { + uintToString(LargestUInt(value), current); + } + assert(current >= buffer); + return current; +} + +String valueToString(LargestUInt value) { + UIntToStringBuffer buffer; + char* current = buffer + sizeof(buffer); + uintToString(value, current); + assert(current >= buffer); + return current; +} + +#if defined(JSON_HAS_INT64) + +String valueToString(Int value) { return valueToString(LargestInt(value)); } + +String valueToString(UInt value) { return valueToString(LargestUInt(value)); } + +#endif // # if defined(JSON_HAS_INT64) + +namespace { +String valueToString(double value, + bool useSpecialFloats, + unsigned int precision, + PrecisionType precisionType) { + // Print into the buffer. We need not request the alternative representation + // that always has a decimal point because JSON doesn't distinguish the + // concepts of reals and integers. + if (!isfinite(value)) { + static const char* const reps[2][3] = {{"NaN", "-Infinity", "Infinity"}, + {"null", "-1e+9999", "1e+9999"}}; + return reps[useSpecialFloats ? 0 : 1] + [isnan(value) ? 0 : (value < 0) ? 1 : 2]; + } + + String buffer(size_t(36), '\0'); + while (true) { + int len = jsoncpp_snprintf( + &*buffer.begin(), buffer.size(), + (precisionType == PrecisionType::significantDigits) ? "%.*g" : "%.*f", + precision, value); + assert(len >= 0); + auto wouldPrint = static_cast(len); + if (wouldPrint >= buffer.size()) { + buffer.resize(wouldPrint + 1); + continue; + } + buffer.resize(wouldPrint); + break; + } + + buffer.erase(fixNumericLocale(buffer.begin(), buffer.end()), buffer.end()); + + // strip the zero padding from the right + if (precisionType == PrecisionType::decimalPlaces) { + buffer.erase(fixZerosInTheEnd(buffer.begin(), buffer.end()), buffer.end()); + } + + // try to ensure we preserve the fact that this was given to us as a double on + // input + if (buffer.find('.') == buffer.npos && buffer.find('e') == buffer.npos) { + buffer += ".0"; + } + return buffer; +} +} // namespace + +String valueToString(double value, + unsigned int precision, + PrecisionType precisionType) { + return valueToString(value, false, precision, precisionType); +} + +String valueToString(bool value) { return value ? "true" : "false"; } + +static bool isAnyCharRequiredQuoting(char const* s, size_t n) { + assert(s || !n); + + char const* const end = s + n; + for (char const* cur = s; cur < end; ++cur) { + if (*cur == '\\' || *cur == '\"' || *cur < ' ' || + static_cast(*cur) < 0x80) + return true; + } + return false; +} + +static unsigned int utf8ToCodepoint(const char*& s, const char* e) { + const unsigned int REPLACEMENT_CHARACTER = 0xFFFD; + + unsigned int firstByte = static_cast(*s); + + if (firstByte < 0x80) + return firstByte; + + if (firstByte < 0xE0) { + if (e - s < 2) + return REPLACEMENT_CHARACTER; + + unsigned int calculated = + ((firstByte & 0x1F) << 6) | (static_cast(s[1]) & 0x3F); + s += 1; + // oversized encoded characters are invalid + return calculated < 0x80 ? REPLACEMENT_CHARACTER : calculated; + } + + if (firstByte < 0xF0) { + if (e - s < 3) + return REPLACEMENT_CHARACTER; + + unsigned int calculated = ((firstByte & 0x0F) << 12) | + ((static_cast(s[1]) & 0x3F) << 6) | + (static_cast(s[2]) & 0x3F); + s += 2; + // surrogates aren't valid codepoints itself + // shouldn't be UTF-8 encoded + if (calculated >= 0xD800 && calculated <= 0xDFFF) + return REPLACEMENT_CHARACTER; + // oversized encoded characters are invalid + return calculated < 0x800 ? REPLACEMENT_CHARACTER : calculated; + } + + if (firstByte < 0xF8) { + if (e - s < 4) + return REPLACEMENT_CHARACTER; + + unsigned int calculated = ((firstByte & 0x07) << 18) | + ((static_cast(s[1]) & 0x3F) << 12) | + ((static_cast(s[2]) & 0x3F) << 6) | + (static_cast(s[3]) & 0x3F); + s += 3; + // oversized encoded characters are invalid + return calculated < 0x10000 ? REPLACEMENT_CHARACTER : calculated; + } + + return REPLACEMENT_CHARACTER; +} + +static const char hex2[] = "000102030405060708090a0b0c0d0e0f" + "101112131415161718191a1b1c1d1e1f" + "202122232425262728292a2b2c2d2e2f" + "303132333435363738393a3b3c3d3e3f" + "404142434445464748494a4b4c4d4e4f" + "505152535455565758595a5b5c5d5e5f" + "606162636465666768696a6b6c6d6e6f" + "707172737475767778797a7b7c7d7e7f" + "808182838485868788898a8b8c8d8e8f" + "909192939495969798999a9b9c9d9e9f" + "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf" + "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf" + "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf" + "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf" + "e0e1e2e3e4e5e6e7e8e9eaebecedeeef" + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"; + +static String toHex16Bit(unsigned int x) { + const unsigned int hi = (x >> 8) & 0xff; + const unsigned int lo = x & 0xff; + String result(4, ' '); + result[0] = hex2[2 * hi]; + result[1] = hex2[2 * hi + 1]; + result[2] = hex2[2 * lo]; + result[3] = hex2[2 * lo + 1]; + return result; +} + +static String valueToQuotedStringN(const char* value, unsigned length) { + if (value == nullptr) + return ""; + + if (!isAnyCharRequiredQuoting(value, length)) + return String("\"") + value + "\""; + // We have to walk value and escape any special characters. + // Appending to String is not efficient, but this should be rare. + // (Note: forward slashes are *not* rare, but I am not escaping them.) + String::size_type maxsize = length * 2 + 3; // allescaped+quotes+NULL + String result; + result.reserve(maxsize); // to avoid lots of mallocs + result += "\""; + char const* end = value + length; + for (const char* c = value; c != end; ++c) { + switch (*c) { + case '\"': + result += "\\\""; + break; + case '\\': + result += "\\\\"; + break; + case '\b': + result += "\\b"; + break; + case '\f': + result += "\\f"; + break; + case '\n': + result += "\\n"; + break; + case '\r': + result += "\\r"; + break; + case '\t': + result += "\\t"; + break; + // case '/': + // Even though \/ is considered a legal escape in JSON, a bare + // slash is also legal, so I see no reason to escape it. + // (I hope I am not misunderstanding something.) + // blep notes: actually escaping \/ may be useful in javascript to avoid = 0x20) + result += static_cast(cp); + else if (cp < 0x10000) { // codepoint is in Basic Multilingual Plane + result += "\\u"; + result += toHex16Bit(cp); + } else { // codepoint is not in Basic Multilingual Plane + // convert to surrogate pair first + cp -= 0x10000; + result += "\\u"; + result += toHex16Bit((cp >> 10) + 0xD800); + result += "\\u"; + result += toHex16Bit((cp & 0x3FF) + 0xDC00); + } + } break; + } + } + result += "\""; + return result; +} + +String valueToQuotedString(const char* value) { + return valueToQuotedStringN(value, static_cast(strlen(value))); +} + +// Class Writer +// ////////////////////////////////////////////////////////////////// +Writer::~Writer() = default; + +// Class FastWriter +// ////////////////////////////////////////////////////////////////// + +FastWriter::FastWriter() + + = default; + +void FastWriter::enableYAMLCompatibility() { yamlCompatibilityEnabled_ = true; } + +void FastWriter::dropNullPlaceholders() { dropNullPlaceholders_ = true; } + +void FastWriter::omitEndingLineFeed() { omitEndingLineFeed_ = true; } + +String FastWriter::write(const Value& root) { + document_.clear(); + writeValue(root); + if (!omitEndingLineFeed_) + document_ += '\n'; + return document_; +} + +void FastWriter::writeValue(const Value& value) { + switch (value.type()) { + case nullValue: + if (!dropNullPlaceholders_) + document_ += "null"; + break; + case intValue: + document_ += valueToString(value.asLargestInt()); + break; + case uintValue: + document_ += valueToString(value.asLargestUInt()); + break; + case realValue: + document_ += valueToString(value.asDouble()); + break; + case stringValue: { + // Is NULL possible for value.string_? No. + char const* str; + char const* end; + bool ok = value.getString(&str, &end); + if (ok) + document_ += valueToQuotedStringN(str, static_cast(end - str)); + break; + } + case booleanValue: + document_ += valueToString(value.asBool()); + break; + case arrayValue: { + document_ += '['; + ArrayIndex size = value.size(); + for (ArrayIndex index = 0; index < size; ++index) { + if (index > 0) + document_ += ','; + writeValue(value[index]); + } + document_ += ']'; + } break; + case objectValue: { + Value::Members members(value.getMemberNames()); + document_ += '{'; + for (auto it = members.begin(); it != members.end(); ++it) { + const String& name = *it; + if (it != members.begin()) + document_ += ','; + document_ += valueToQuotedStringN(name.data(), + static_cast(name.length())); + document_ += yamlCompatibilityEnabled_ ? ": " : ":"; + writeValue(value[name]); + } + document_ += '}'; + } break; + } +} + +// Class StyledWriter +// ////////////////////////////////////////////////////////////////// + +StyledWriter::StyledWriter() = default; + +String StyledWriter::write(const Value& root) { + document_.clear(); + addChildValues_ = false; + indentString_.clear(); + writeCommentBeforeValue(root); + writeValue(root); + writeCommentAfterValueOnSameLine(root); + document_ += '\n'; + return document_; +} + +void StyledWriter::writeValue(const Value& value) { + switch (value.type()) { + case nullValue: + pushValue("null"); + break; + case intValue: + pushValue(valueToString(value.asLargestInt())); + break; + case uintValue: + pushValue(valueToString(value.asLargestUInt())); + break; + case realValue: + pushValue(valueToString(value.asDouble())); + break; + case stringValue: { + // Is NULL possible for value.string_? No. + char const* str; + char const* end; + bool ok = value.getString(&str, &end); + if (ok) + pushValue(valueToQuotedStringN(str, static_cast(end - str))); + else + pushValue(""); + break; + } + case booleanValue: + pushValue(valueToString(value.asBool())); + break; + case arrayValue: + writeArrayValue(value); + break; + case objectValue: { + Value::Members members(value.getMemberNames()); + if (members.empty()) + pushValue("{}"); + else { + writeWithIndent("{"); + indent(); + auto it = members.begin(); + for (;;) { + const String& name = *it; + const Value& childValue = value[name]; + writeCommentBeforeValue(childValue); + writeWithIndent(valueToQuotedString(name.c_str())); + document_ += " : "; + writeValue(childValue); + if (++it == members.end()) { + writeCommentAfterValueOnSameLine(childValue); + break; + } + document_ += ','; + writeCommentAfterValueOnSameLine(childValue); + } + unindent(); + writeWithIndent("}"); + } + } break; + } +} + +void StyledWriter::writeArrayValue(const Value& value) { + unsigned size = value.size(); + if (size == 0) + pushValue("[]"); + else { + bool isArrayMultiLine = isMultilineArray(value); + if (isArrayMultiLine) { + writeWithIndent("["); + indent(); + bool hasChildValue = !childValues_.empty(); + unsigned index = 0; + for (;;) { + const Value& childValue = value[index]; + writeCommentBeforeValue(childValue); + if (hasChildValue) + writeWithIndent(childValues_[index]); + else { + writeIndent(); + writeValue(childValue); + } + if (++index == size) { + writeCommentAfterValueOnSameLine(childValue); + break; + } + document_ += ','; + writeCommentAfterValueOnSameLine(childValue); + } + unindent(); + writeWithIndent("]"); + } else // output on a single line + { + assert(childValues_.size() == size); + document_ += "[ "; + for (unsigned index = 0; index < size; ++index) { + if (index > 0) + document_ += ", "; + document_ += childValues_[index]; + } + document_ += " ]"; + } + } +} + +bool StyledWriter::isMultilineArray(const Value& value) { + ArrayIndex const size = value.size(); + bool isMultiLine = size * 3 >= rightMargin_; + childValues_.clear(); + for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) { + const Value& childValue = value[index]; + isMultiLine = ((childValue.isArray() || childValue.isObject()) && + !childValue.empty()); + } + if (!isMultiLine) // check if line length > max line length + { + childValues_.reserve(size); + addChildValues_ = true; + ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]' + for (ArrayIndex index = 0; index < size; ++index) { + if (hasCommentForValue(value[index])) { + isMultiLine = true; + } + writeValue(value[index]); + lineLength += static_cast(childValues_[index].length()); + } + addChildValues_ = false; + isMultiLine = isMultiLine || lineLength >= rightMargin_; + } + return isMultiLine; +} + +void StyledWriter::pushValue(const String& value) { + if (addChildValues_) + childValues_.push_back(value); + else + document_ += value; +} + +void StyledWriter::writeIndent() { + if (!document_.empty()) { + char last = document_[document_.length() - 1]; + if (last == ' ') // already indented + return; + if (last != '\n') // Comments may add new-line + document_ += '\n'; + } + document_ += indentString_; +} + +void StyledWriter::writeWithIndent(const String& value) { + writeIndent(); + document_ += value; +} + +void StyledWriter::indent() { indentString_ += String(indentSize_, ' '); } + +void StyledWriter::unindent() { + assert(indentString_.size() >= indentSize_); + indentString_.resize(indentString_.size() - indentSize_); +} + +void StyledWriter::writeCommentBeforeValue(const Value& root) { + if (!root.hasComment(commentBefore)) + return; + + document_ += '\n'; + writeIndent(); + const String& comment = root.getComment(commentBefore); + String::const_iterator iter = comment.begin(); + while (iter != comment.end()) { + document_ += *iter; + if (*iter == '\n' && ((iter + 1) != comment.end() && *(iter + 1) == '/')) + writeIndent(); + ++iter; + } + + // Comments are stripped of trailing newlines, so add one here + document_ += '\n'; +} + +void StyledWriter::writeCommentAfterValueOnSameLine(const Value& root) { + if (root.hasComment(commentAfterOnSameLine)) + document_ += " " + root.getComment(commentAfterOnSameLine); + + if (root.hasComment(commentAfter)) { + document_ += '\n'; + document_ += root.getComment(commentAfter); + document_ += '\n'; + } +} + +bool StyledWriter::hasCommentForValue(const Value& value) { + return value.hasComment(commentBefore) || + value.hasComment(commentAfterOnSameLine) || + value.hasComment(commentAfter); +} + +// Class StyledStreamWriter +// ////////////////////////////////////////////////////////////////// + +StyledStreamWriter::StyledStreamWriter(String indentation) + : document_(nullptr), indentation_(std::move(indentation)), + addChildValues_(), indented_(false) {} + +void StyledStreamWriter::write(OStream& out, const Value& root) { + document_ = &out; + addChildValues_ = false; + indentString_.clear(); + indented_ = true; + writeCommentBeforeValue(root); + if (!indented_) + writeIndent(); + indented_ = true; + writeValue(root); + writeCommentAfterValueOnSameLine(root); + *document_ << "\n"; + document_ = nullptr; // Forget the stream, for safety. +} + +void StyledStreamWriter::writeValue(const Value& value) { + switch (value.type()) { + case nullValue: + pushValue("null"); + break; + case intValue: + pushValue(valueToString(value.asLargestInt())); + break; + case uintValue: + pushValue(valueToString(value.asLargestUInt())); + break; + case realValue: + pushValue(valueToString(value.asDouble())); + break; + case stringValue: { + // Is NULL possible for value.string_? No. + char const* str; + char const* end; + bool ok = value.getString(&str, &end); + if (ok) + pushValue(valueToQuotedStringN(str, static_cast(end - str))); + else + pushValue(""); + break; + } + case booleanValue: + pushValue(valueToString(value.asBool())); + break; + case arrayValue: + writeArrayValue(value); + break; + case objectValue: { + Value::Members members(value.getMemberNames()); + if (members.empty()) + pushValue("{}"); + else { + writeWithIndent("{"); + indent(); + auto it = members.begin(); + for (;;) { + const String& name = *it; + const Value& childValue = value[name]; + writeCommentBeforeValue(childValue); + writeWithIndent(valueToQuotedString(name.c_str())); + *document_ << " : "; + writeValue(childValue); + if (++it == members.end()) { + writeCommentAfterValueOnSameLine(childValue); + break; + } + *document_ << ","; + writeCommentAfterValueOnSameLine(childValue); + } + unindent(); + writeWithIndent("}"); + } + } break; + } +} + +void StyledStreamWriter::writeArrayValue(const Value& value) { + unsigned size = value.size(); + if (size == 0) + pushValue("[]"); + else { + bool isArrayMultiLine = isMultilineArray(value); + if (isArrayMultiLine) { + writeWithIndent("["); + indent(); + bool hasChildValue = !childValues_.empty(); + unsigned index = 0; + for (;;) { + const Value& childValue = value[index]; + writeCommentBeforeValue(childValue); + if (hasChildValue) + writeWithIndent(childValues_[index]); + else { + if (!indented_) + writeIndent(); + indented_ = true; + writeValue(childValue); + indented_ = false; + } + if (++index == size) { + writeCommentAfterValueOnSameLine(childValue); + break; + } + *document_ << ","; + writeCommentAfterValueOnSameLine(childValue); + } + unindent(); + writeWithIndent("]"); + } else // output on a single line + { + assert(childValues_.size() == size); + *document_ << "[ "; + for (unsigned index = 0; index < size; ++index) { + if (index > 0) + *document_ << ", "; + *document_ << childValues_[index]; + } + *document_ << " ]"; + } + } +} + +bool StyledStreamWriter::isMultilineArray(const Value& value) { + ArrayIndex const size = value.size(); + bool isMultiLine = size * 3 >= rightMargin_; + childValues_.clear(); + for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) { + const Value& childValue = value[index]; + isMultiLine = ((childValue.isArray() || childValue.isObject()) && + !childValue.empty()); + } + if (!isMultiLine) // check if line length > max line length + { + childValues_.reserve(size); + addChildValues_ = true; + ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]' + for (ArrayIndex index = 0; index < size; ++index) { + if (hasCommentForValue(value[index])) { + isMultiLine = true; + } + writeValue(value[index]); + lineLength += static_cast(childValues_[index].length()); + } + addChildValues_ = false; + isMultiLine = isMultiLine || lineLength >= rightMargin_; + } + return isMultiLine; +} + +void StyledStreamWriter::pushValue(const String& value) { + if (addChildValues_) + childValues_.push_back(value); + else + *document_ << value; +} + +void StyledStreamWriter::writeIndent() { + // blep intended this to look at the so-far-written string + // to determine whether we are already indented, but + // with a stream we cannot do that. So we rely on some saved state. + // The caller checks indented_. + *document_ << '\n' << indentString_; +} + +void StyledStreamWriter::writeWithIndent(const String& value) { + if (!indented_) + writeIndent(); + *document_ << value; + indented_ = false; +} + +void StyledStreamWriter::indent() { indentString_ += indentation_; } + +void StyledStreamWriter::unindent() { + assert(indentString_.size() >= indentation_.size()); + indentString_.resize(indentString_.size() - indentation_.size()); +} + +void StyledStreamWriter::writeCommentBeforeValue(const Value& root) { + if (!root.hasComment(commentBefore)) + return; + + if (!indented_) + writeIndent(); + const String& comment = root.getComment(commentBefore); + String::const_iterator iter = comment.begin(); + while (iter != comment.end()) { + *document_ << *iter; + if (*iter == '\n' && ((iter + 1) != comment.end() && *(iter + 1) == '/')) + // writeIndent(); // would include newline + *document_ << indentString_; + ++iter; + } + indented_ = false; +} + +void StyledStreamWriter::writeCommentAfterValueOnSameLine(const Value& root) { + if (root.hasComment(commentAfterOnSameLine)) + *document_ << ' ' << root.getComment(commentAfterOnSameLine); + + if (root.hasComment(commentAfter)) { + writeIndent(); + *document_ << root.getComment(commentAfter); + } + indented_ = false; +} + +bool StyledStreamWriter::hasCommentForValue(const Value& value) { + return value.hasComment(commentBefore) || + value.hasComment(commentAfterOnSameLine) || + value.hasComment(commentAfter); +} + +////////////////////////// +// BuiltStyledStreamWriter + +/// Scoped enums are not available until C++11. +struct CommentStyle { + /// Decide whether to write comments. + enum Enum { + None, ///< Drop all comments. + Most, ///< Recover odd behavior of previous versions (not implemented yet). + All ///< Keep all comments. + }; +}; + +struct BuiltStyledStreamWriter : public StreamWriter { + BuiltStyledStreamWriter(String indentation, + CommentStyle::Enum cs, + String colonSymbol, + String nullSymbol, + String endingLineFeedSymbol, + bool useSpecialFloats, + unsigned int precision, + PrecisionType precisionType); + int write(Value const& root, OStream* sout) override; + +private: + void writeValue(Value const& value); + void writeArrayValue(Value const& value); + bool isMultilineArray(Value const& value); + void pushValue(String const& value); + void writeIndent(); + void writeWithIndent(String const& value); + void indent(); + void unindent(); + void writeCommentBeforeValue(Value const& root); + void writeCommentAfterValueOnSameLine(Value const& root); + static bool hasCommentForValue(const Value& value); + + typedef std::vector ChildValues; + + ChildValues childValues_; + String indentString_; + unsigned int rightMargin_; + String indentation_; + CommentStyle::Enum cs_; + String colonSymbol_; + String nullSymbol_; + String endingLineFeedSymbol_; + bool addChildValues_ : 1; + bool indented_ : 1; + bool useSpecialFloats_ : 1; + unsigned int precision_; + PrecisionType precisionType_; +}; +BuiltStyledStreamWriter::BuiltStyledStreamWriter(String indentation, + CommentStyle::Enum cs, + String colonSymbol, + String nullSymbol, + String endingLineFeedSymbol, + bool useSpecialFloats, + unsigned int precision, + PrecisionType precisionType) + : rightMargin_(74), indentation_(std::move(indentation)), cs_(cs), + colonSymbol_(std::move(colonSymbol)), nullSymbol_(std::move(nullSymbol)), + endingLineFeedSymbol_(std::move(endingLineFeedSymbol)), + addChildValues_(false), indented_(false), + useSpecialFloats_(useSpecialFloats), precision_(precision), + precisionType_(precisionType) {} +int BuiltStyledStreamWriter::write(Value const& root, OStream* sout) { + sout_ = sout; + addChildValues_ = false; + indented_ = true; + indentString_.clear(); + writeCommentBeforeValue(root); + if (!indented_) + writeIndent(); + indented_ = true; + writeValue(root); + writeCommentAfterValueOnSameLine(root); + *sout_ << endingLineFeedSymbol_; + sout_ = nullptr; + return 0; +} +void BuiltStyledStreamWriter::writeValue(Value const& value) { + switch (value.type()) { + case nullValue: + pushValue(nullSymbol_); + break; + case intValue: + pushValue(valueToString(value.asLargestInt())); + break; + case uintValue: + pushValue(valueToString(value.asLargestUInt())); + break; + case realValue: + pushValue(valueToString(value.asDouble(), useSpecialFloats_, precision_, + precisionType_)); + break; + case stringValue: { + // Is NULL is possible for value.string_? No. + char const* str; + char const* end; + bool ok = value.getString(&str, &end); + if (ok) + pushValue(valueToQuotedStringN(str, static_cast(end - str))); + else + pushValue(""); + break; + } + case booleanValue: + pushValue(valueToString(value.asBool())); + break; + case arrayValue: + writeArrayValue(value); + break; + case objectValue: { + Value::Members members(value.getMemberNames()); + if (members.empty()) + pushValue("{}"); + else { + writeWithIndent("{"); + indent(); + auto it = members.begin(); + for (;;) { + String const& name = *it; + Value const& childValue = value[name]; + writeCommentBeforeValue(childValue); + writeWithIndent(valueToQuotedStringN( + name.data(), static_cast(name.length()))); + *sout_ << colonSymbol_; + writeValue(childValue); + if (++it == members.end()) { + writeCommentAfterValueOnSameLine(childValue); + break; + } + *sout_ << ","; + writeCommentAfterValueOnSameLine(childValue); + } + unindent(); + writeWithIndent("}"); + } + } break; + } +} + +void BuiltStyledStreamWriter::writeArrayValue(Value const& value) { + unsigned size = value.size(); + if (size == 0) + pushValue("[]"); + else { + bool isMultiLine = (cs_ == CommentStyle::All) || isMultilineArray(value); + if (isMultiLine) { + writeWithIndent("["); + indent(); + bool hasChildValue = !childValues_.empty(); + unsigned index = 0; + for (;;) { + Value const& childValue = value[index]; + writeCommentBeforeValue(childValue); + if (hasChildValue) + writeWithIndent(childValues_[index]); + else { + if (!indented_) + writeIndent(); + indented_ = true; + writeValue(childValue); + indented_ = false; + } + if (++index == size) { + writeCommentAfterValueOnSameLine(childValue); + break; + } + *sout_ << ","; + writeCommentAfterValueOnSameLine(childValue); + } + unindent(); + writeWithIndent("]"); + } else // output on a single line + { + assert(childValues_.size() == size); + *sout_ << "["; + if (!indentation_.empty()) + *sout_ << " "; + for (unsigned index = 0; index < size; ++index) { + if (index > 0) + *sout_ << ((!indentation_.empty()) ? ", " : ","); + *sout_ << childValues_[index]; + } + if (!indentation_.empty()) + *sout_ << " "; + *sout_ << "]"; + } + } +} + +bool BuiltStyledStreamWriter::isMultilineArray(Value const& value) { + ArrayIndex const size = value.size(); + bool isMultiLine = size * 3 >= rightMargin_; + childValues_.clear(); + for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) { + Value const& childValue = value[index]; + isMultiLine = ((childValue.isArray() || childValue.isObject()) && + !childValue.empty()); + } + if (!isMultiLine) // check if line length > max line length + { + childValues_.reserve(size); + addChildValues_ = true; + ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]' + for (ArrayIndex index = 0; index < size; ++index) { + if (hasCommentForValue(value[index])) { + isMultiLine = true; + } + writeValue(value[index]); + lineLength += static_cast(childValues_[index].length()); + } + addChildValues_ = false; + isMultiLine = isMultiLine || lineLength >= rightMargin_; + } + return isMultiLine; +} + +void BuiltStyledStreamWriter::pushValue(String const& value) { + if (addChildValues_) + childValues_.push_back(value); + else + *sout_ << value; +} + +void BuiltStyledStreamWriter::writeIndent() { + // blep intended this to look at the so-far-written string + // to determine whether we are already indented, but + // with a stream we cannot do that. So we rely on some saved state. + // The caller checks indented_. + + if (!indentation_.empty()) { + // In this case, drop newlines too. + *sout_ << '\n' << indentString_; + } +} + +void BuiltStyledStreamWriter::writeWithIndent(String const& value) { + if (!indented_) + writeIndent(); + *sout_ << value; + indented_ = false; +} + +void BuiltStyledStreamWriter::indent() { indentString_ += indentation_; } + +void BuiltStyledStreamWriter::unindent() { + assert(indentString_.size() >= indentation_.size()); + indentString_.resize(indentString_.size() - indentation_.size()); +} + +void BuiltStyledStreamWriter::writeCommentBeforeValue(Value const& root) { + if (cs_ == CommentStyle::None) + return; + if (!root.hasComment(commentBefore)) + return; + + if (!indented_) + writeIndent(); + const String& comment = root.getComment(commentBefore); + String::const_iterator iter = comment.begin(); + while (iter != comment.end()) { + *sout_ << *iter; + if (*iter == '\n' && ((iter + 1) != comment.end() && *(iter + 1) == '/')) + // writeIndent(); // would write extra newline + *sout_ << indentString_; + ++iter; + } + indented_ = false; +} + +void BuiltStyledStreamWriter::writeCommentAfterValueOnSameLine( + Value const& root) { + if (cs_ == CommentStyle::None) + return; + if (root.hasComment(commentAfterOnSameLine)) + *sout_ << " " + root.getComment(commentAfterOnSameLine); + + if (root.hasComment(commentAfter)) { + writeIndent(); + *sout_ << root.getComment(commentAfter); + } +} + +// static +bool BuiltStyledStreamWriter::hasCommentForValue(const Value& value) { + return value.hasComment(commentBefore) || + value.hasComment(commentAfterOnSameLine) || + value.hasComment(commentAfter); +} + +/////////////// +// StreamWriter + +StreamWriter::StreamWriter() : sout_(nullptr) {} +StreamWriter::~StreamWriter() = default; +StreamWriter::Factory::~Factory() = default; +StreamWriterBuilder::StreamWriterBuilder() { setDefaults(&settings_); } +StreamWriterBuilder::~StreamWriterBuilder() = default; +StreamWriter* StreamWriterBuilder::newStreamWriter() const { + String indentation = settings_["indentation"].asString(); + String cs_str = settings_["commentStyle"].asString(); + String pt_str = settings_["precisionType"].asString(); + bool eyc = settings_["enableYAMLCompatibility"].asBool(); + bool dnp = settings_["dropNullPlaceholders"].asBool(); + bool usf = settings_["useSpecialFloats"].asBool(); + unsigned int pre = settings_["precision"].asUInt(); + CommentStyle::Enum cs = CommentStyle::All; + if (cs_str == "All") { + cs = CommentStyle::All; + } else if (cs_str == "None") { + cs = CommentStyle::None; + } else { + throwRuntimeError("commentStyle must be 'All' or 'None'"); + } + PrecisionType precisionType(significantDigits); + if (pt_str == "significant") { + precisionType = PrecisionType::significantDigits; + } else if (pt_str == "decimal") { + precisionType = PrecisionType::decimalPlaces; + } else { + throwRuntimeError("precisionType must be 'significant' or 'decimal'"); + } + String colonSymbol = " : "; + if (eyc) { + colonSymbol = ": "; + } else if (indentation.empty()) { + colonSymbol = ":"; + } + String nullSymbol = "null"; + if (dnp) { + nullSymbol.clear(); + } + if (pre > 17) + pre = 17; + String endingLineFeedSymbol; + return new BuiltStyledStreamWriter(indentation, cs, colonSymbol, nullSymbol, + endingLineFeedSymbol, usf, pre, + precisionType); +} +static void getValidWriterKeys(std::set* valid_keys) { + valid_keys->clear(); + valid_keys->insert("indentation"); + valid_keys->insert("commentStyle"); + valid_keys->insert("enableYAMLCompatibility"); + valid_keys->insert("dropNullPlaceholders"); + valid_keys->insert("useSpecialFloats"); + valid_keys->insert("precision"); + valid_keys->insert("precisionType"); +} +bool StreamWriterBuilder::validate(Json::Value* invalid) const { + Json::Value my_invalid; + if (!invalid) + invalid = &my_invalid; // so we do not need to test for NULL + Json::Value& inv = *invalid; + std::set valid_keys; + getValidWriterKeys(&valid_keys); + Value::Members keys = settings_.getMemberNames(); + size_t n = keys.size(); + for (size_t i = 0; i < n; ++i) { + String const& key = keys[i]; + if (valid_keys.find(key) == valid_keys.end()) { + inv[key] = settings_[key]; + } + } + return inv.empty(); +} +Value& StreamWriterBuilder::operator[](const String& key) { + return settings_[key]; +} +// static +void StreamWriterBuilder::setDefaults(Json::Value* settings) { + //! [StreamWriterBuilderDefaults] + (*settings)["commentStyle"] = "All"; + (*settings)["indentation"] = "\t"; + (*settings)["enableYAMLCompatibility"] = false; + (*settings)["dropNullPlaceholders"] = false; + (*settings)["useSpecialFloats"] = false; + (*settings)["precision"] = 17; + (*settings)["precisionType"] = "significant"; + //! [StreamWriterBuilderDefaults] +} + +String writeString(StreamWriter::Factory const& factory, Value const& root) { + OStringStream sout; + StreamWriterPtr const writer(factory.newStreamWriter()); + writer->write(root, &sout); + return sout.str(); +} + +OStream& operator<<(OStream& sout, Value const& root) { + StreamWriterBuilder builder; + StreamWriterPtr const writer(builder.newStreamWriter()); + writer->write(root, &sout); + return sout; +} + +} // namespace Json + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: src/lib_json/json_writer.cpp +// ////////////////////////////////////////////////////////////////////// + + + + + diff --git a/thirdparty/jsoncpp/src/lib_json/json_batchallocator.h b/thirdparty/jsoncpp/src/lib_json/json_batchallocator.h deleted file mode 100644 index 87ea5ed8..00000000 --- a/thirdparty/jsoncpp/src/lib_json/json_batchallocator.h +++ /dev/null @@ -1,125 +0,0 @@ -#ifndef JSONCPP_BATCHALLOCATOR_H_INCLUDED -# define JSONCPP_BATCHALLOCATOR_H_INCLUDED - -# include -# include - -# ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION - -namespace Json { - -/* Fast memory allocator. - * - * This memory allocator allocates memory for a batch of object (specified by - * the page size, the number of object in each page). - * - * It does not allow the destruction of a single object. All the allocated objects - * can be destroyed at once. The memory can be either released or reused for future - * allocation. - * - * The in-place new operator must be used to construct the object using the pointer - * returned by allocate. - */ -template -class BatchAllocator -{ -public: - typedef AllocatedType Type; - - BatchAllocator( unsigned int objectsPerPage = 255 ) - : freeHead_( 0 ) - , objectsPerPage_( objectsPerPage ) - { -// printf( "Size: %d => %s\n", sizeof(AllocatedType), typeid(AllocatedType).name() ); - assert( sizeof(AllocatedType) * objectPerAllocation >= sizeof(AllocatedType *) ); // We must be able to store a slist in the object free space. - assert( objectsPerPage >= 16 ); - batches_ = allocateBatch( 0 ); // allocated a dummy page - currentBatch_ = batches_; - } - - ~BatchAllocator() - { - for ( BatchInfo *batch = batches_; batch; ) - { - BatchInfo *nextBatch = batch->next_; - free( batch ); - batch = nextBatch; - } - } - - /// allocate space for an array of objectPerAllocation object. - /// @warning it is the responsability of the caller to call objects constructors. - AllocatedType *allocate() - { - if ( freeHead_ ) // returns node from free list. - { - AllocatedType *object = freeHead_; - freeHead_ = *(AllocatedType **)object; - return object; - } - if ( currentBatch_->used_ == currentBatch_->end_ ) - { - currentBatch_ = currentBatch_->next_; - while ( currentBatch_ && currentBatch_->used_ == currentBatch_->end_ ) - currentBatch_ = currentBatch_->next_; - - if ( !currentBatch_ ) // no free batch found, allocate a new one - { - currentBatch_ = allocateBatch( objectsPerPage_ ); - currentBatch_->next_ = batches_; // insert at the head of the list - batches_ = currentBatch_; - } - } - AllocatedType *allocated = currentBatch_->used_; - currentBatch_->used_ += objectPerAllocation; - return allocated; - } - - /// Release the object. - /// @warning it is the responsability of the caller to actually destruct the object. - void release( AllocatedType *object ) - { - assert( object != 0 ); - *(AllocatedType **)object = freeHead_; - freeHead_ = object; - } - -private: - struct BatchInfo - { - BatchInfo *next_; - AllocatedType *used_; - AllocatedType *end_; - AllocatedType buffer_[objectPerAllocation]; - }; - - // disabled copy constructor and assignement operator. - BatchAllocator( const BatchAllocator & ); - void operator =( const BatchAllocator &); - - static BatchInfo *allocateBatch( unsigned int objectsPerPage ) - { - const unsigned int mallocSize = sizeof(BatchInfo) - sizeof(AllocatedType)* objectPerAllocation - + sizeof(AllocatedType) * objectPerAllocation * objectsPerPage; - BatchInfo *batch = static_cast( malloc( mallocSize ) ); - batch->next_ = 0; - batch->used_ = batch->buffer_; - batch->end_ = batch->buffer_ + objectsPerPage; - return batch; - } - - BatchInfo *batches_; - BatchInfo *currentBatch_; - /// Head of a single linked list within the allocated space of freeed object - AllocatedType *freeHead_; - unsigned int objectsPerPage_; -}; - - -} // namespace Json - -# endif // ifndef JSONCPP_DOC_INCLUDE_IMPLEMENTATION - -#endif // JSONCPP_BATCHALLOCATOR_H_INCLUDED - diff --git a/thirdparty/jsoncpp/src/lib_json/json_internalarray.inl b/thirdparty/jsoncpp/src/lib_json/json_internalarray.inl deleted file mode 100644 index 9b985d25..00000000 --- a/thirdparty/jsoncpp/src/lib_json/json_internalarray.inl +++ /dev/null @@ -1,448 +0,0 @@ -// included by json_value.cpp -// everything is within Json namespace - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// class ValueInternalArray -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// - -ValueArrayAllocator::~ValueArrayAllocator() -{ -} - -// ////////////////////////////////////////////////////////////////// -// class DefaultValueArrayAllocator -// ////////////////////////////////////////////////////////////////// -#ifdef JSON_USE_SIMPLE_INTERNAL_ALLOCATOR -class DefaultValueArrayAllocator : public ValueArrayAllocator -{ -public: // overridden from ValueArrayAllocator - virtual ~DefaultValueArrayAllocator() - { - } - - virtual ValueInternalArray *newArray() - { - return new ValueInternalArray(); - } - - virtual ValueInternalArray *newArrayCopy( const ValueInternalArray &other ) - { - return new ValueInternalArray( other ); - } - - virtual void destructArray( ValueInternalArray *array ) - { - delete array; - } - - virtual void reallocateArrayPageIndex( Value **&indexes, - ValueInternalArray::PageIndex &indexCount, - ValueInternalArray::PageIndex minNewIndexCount ) - { - ValueInternalArray::PageIndex newIndexCount = (indexCount*3)/2 + 1; - if ( minNewIndexCount > newIndexCount ) - newIndexCount = minNewIndexCount; - void *newIndexes = realloc( indexes, sizeof(Value*) * newIndexCount ); - if ( !newIndexes ) - throw std::bad_alloc(); - indexCount = newIndexCount; - indexes = static_cast( newIndexes ); - } - virtual void releaseArrayPageIndex( Value **indexes, - ValueInternalArray::PageIndex indexCount ) - { - if ( indexes ) - free( indexes ); - } - - virtual Value *allocateArrayPage() - { - return static_cast( malloc( sizeof(Value) * ValueInternalArray::itemsPerPage ) ); - } - - virtual void releaseArrayPage( Value *value ) - { - if ( value ) - free( value ); - } -}; - -#else // #ifdef JSON_USE_SIMPLE_INTERNAL_ALLOCATOR -/// @todo make this thread-safe (lock when accessign batch allocator) -class DefaultValueArrayAllocator : public ValueArrayAllocator -{ -public: // overridden from ValueArrayAllocator - virtual ~DefaultValueArrayAllocator() - { - } - - virtual ValueInternalArray *newArray() - { - ValueInternalArray *array = arraysAllocator_.allocate(); - new (array) ValueInternalArray(); // placement new - return array; - } - - virtual ValueInternalArray *newArrayCopy( const ValueInternalArray &other ) - { - ValueInternalArray *array = arraysAllocator_.allocate(); - new (array) ValueInternalArray( other ); // placement new - return array; - } - - virtual void destructArray( ValueInternalArray *array ) - { - if ( array ) - { - array->~ValueInternalArray(); - arraysAllocator_.release( array ); - } - } - - virtual void reallocateArrayPageIndex( Value **&indexes, - ValueInternalArray::PageIndex &indexCount, - ValueInternalArray::PageIndex minNewIndexCount ) - { - ValueInternalArray::PageIndex newIndexCount = (indexCount*3)/2 + 1; - if ( minNewIndexCount > newIndexCount ) - newIndexCount = minNewIndexCount; - void *newIndexes = realloc( indexes, sizeof(Value*) * newIndexCount ); - if ( !newIndexes ) - throw std::bad_alloc(); - indexCount = newIndexCount; - indexes = static_cast( newIndexes ); - } - virtual void releaseArrayPageIndex( Value **indexes, - ValueInternalArray::PageIndex indexCount ) - { - if ( indexes ) - free( indexes ); - } - - virtual Value *allocateArrayPage() - { - return static_cast( pagesAllocator_.allocate() ); - } - - virtual void releaseArrayPage( Value *value ) - { - if ( value ) - pagesAllocator_.release( value ); - } -private: - BatchAllocator arraysAllocator_; - BatchAllocator pagesAllocator_; -}; -#endif // #ifdef JSON_USE_SIMPLE_INTERNAL_ALLOCATOR - -static ValueArrayAllocator *&arrayAllocator() -{ - static DefaultValueArrayAllocator defaultAllocator; - static ValueArrayAllocator *arrayAllocator = &defaultAllocator; - return arrayAllocator; -} - -static struct DummyArrayAllocatorInitializer { - DummyArrayAllocatorInitializer() - { - arrayAllocator(); // ensure arrayAllocator() statics are initialized before main(). - } -} dummyArrayAllocatorInitializer; - -// ////////////////////////////////////////////////////////////////// -// class ValueInternalArray -// ////////////////////////////////////////////////////////////////// -bool -ValueInternalArray::equals( const IteratorState &x, - const IteratorState &other ) -{ - return x.array_ == other.array_ - && x.currentItemIndex_ == other.currentItemIndex_ - && x.currentPageIndex_ == other.currentPageIndex_; -} - - -void -ValueInternalArray::increment( IteratorState &it ) -{ - JSON_ASSERT_MESSAGE( it.array_ && - (it.currentPageIndex_ - it.array_->pages_)*itemsPerPage + it.currentItemIndex_ - != it.array_->size_, - "ValueInternalArray::increment(): moving iterator beyond end" ); - ++(it.currentItemIndex_); - if ( it.currentItemIndex_ == itemsPerPage ) - { - it.currentItemIndex_ = 0; - ++(it.currentPageIndex_); - } -} - - -void -ValueInternalArray::decrement( IteratorState &it ) -{ - JSON_ASSERT_MESSAGE( it.array_ && it.currentPageIndex_ == it.array_->pages_ - && it.currentItemIndex_ == 0, - "ValueInternalArray::decrement(): moving iterator beyond end" ); - if ( it.currentItemIndex_ == 0 ) - { - it.currentItemIndex_ = itemsPerPage-1; - --(it.currentPageIndex_); - } - else - { - --(it.currentItemIndex_); - } -} - - -Value & -ValueInternalArray::unsafeDereference( const IteratorState &it ) -{ - return (*(it.currentPageIndex_))[it.currentItemIndex_]; -} - - -Value & -ValueInternalArray::dereference( const IteratorState &it ) -{ - JSON_ASSERT_MESSAGE( it.array_ && - (it.currentPageIndex_ - it.array_->pages_)*itemsPerPage + it.currentItemIndex_ - < it.array_->size_, - "ValueInternalArray::dereference(): dereferencing invalid iterator" ); - return unsafeDereference( it ); -} - -void -ValueInternalArray::makeBeginIterator( IteratorState &it ) const -{ - it.array_ = const_cast( this ); - it.currentItemIndex_ = 0; - it.currentPageIndex_ = pages_; -} - - -void -ValueInternalArray::makeIterator( IteratorState &it, ArrayIndex index ) const -{ - it.array_ = const_cast( this ); - it.currentItemIndex_ = index % itemsPerPage; - it.currentPageIndex_ = pages_ + index / itemsPerPage; -} - - -void -ValueInternalArray::makeEndIterator( IteratorState &it ) const -{ - makeIterator( it, size_ ); -} - - -ValueInternalArray::ValueInternalArray() - : pages_( 0 ) - , size_( 0 ) - , pageCount_( 0 ) -{ -} - - -ValueInternalArray::ValueInternalArray( const ValueInternalArray &other ) - : pages_( 0 ) - , pageCount_( 0 ) - , size_( other.size_ ) -{ - PageIndex minNewPages = other.size_ / itemsPerPage; - arrayAllocator()->reallocateArrayPageIndex( pages_, pageCount_, minNewPages ); - JSON_ASSERT_MESSAGE( pageCount_ >= minNewPages, - "ValueInternalArray::reserve(): bad reallocation" ); - IteratorState itOther; - other.makeBeginIterator( itOther ); - Value *value; - for ( ArrayIndex index = 0; index < size_; ++index, increment(itOther) ) - { - if ( index % itemsPerPage == 0 ) - { - PageIndex pageIndex = index / itemsPerPage; - value = arrayAllocator()->allocateArrayPage(); - pages_[pageIndex] = value; - } - new (value) Value( dereference( itOther ) ); - } -} - - -ValueInternalArray & -ValueInternalArray::operator =( const ValueInternalArray &other ) -{ - ValueInternalArray temp( other ); - swap( temp ); - return *this; -} - - -ValueInternalArray::~ValueInternalArray() -{ - // destroy all constructed items - IteratorState it; - IteratorState itEnd; - makeBeginIterator( it); - makeEndIterator( itEnd ); - for ( ; !equals(it,itEnd); increment(it) ) - { - Value *value = &dereference(it); - value->~Value(); - } - // release all pages - PageIndex lastPageIndex = size_ / itemsPerPage; - for ( PageIndex pageIndex = 0; pageIndex < lastPageIndex; ++pageIndex ) - arrayAllocator()->releaseArrayPage( pages_[pageIndex] ); - // release pages index - arrayAllocator()->releaseArrayPageIndex( pages_, pageCount_ ); -} - - -void -ValueInternalArray::swap( ValueInternalArray &other ) -{ - Value **tempPages = pages_; - pages_ = other.pages_; - other.pages_ = tempPages; - ArrayIndex tempSize = size_; - size_ = other.size_; - other.size_ = tempSize; - PageIndex tempPageCount = pageCount_; - pageCount_ = other.pageCount_; - other.pageCount_ = tempPageCount; -} - -void -ValueInternalArray::clear() -{ - ValueInternalArray dummy; - swap( dummy ); -} - - -void -ValueInternalArray::resize( ArrayIndex newSize ) -{ - if ( newSize == 0 ) - clear(); - else if ( newSize < size_ ) - { - IteratorState it; - IteratorState itEnd; - makeIterator( it, newSize ); - makeIterator( itEnd, size_ ); - for ( ; !equals(it,itEnd); increment(it) ) - { - Value *value = &dereference(it); - value->~Value(); - } - PageIndex pageIndex = (newSize + itemsPerPage - 1) / itemsPerPage; - PageIndex lastPageIndex = size_ / itemsPerPage; - for ( ; pageIndex < lastPageIndex; ++pageIndex ) - arrayAllocator()->releaseArrayPage( pages_[pageIndex] ); - size_ = newSize; - } - else if ( newSize > size_ ) - resolveReference( newSize ); -} - - -void -ValueInternalArray::makeIndexValid( ArrayIndex index ) -{ - // Need to enlarge page index ? - if ( index >= pageCount_ * itemsPerPage ) - { - PageIndex minNewPages = (index + 1) / itemsPerPage; - arrayAllocator()->reallocateArrayPageIndex( pages_, pageCount_, minNewPages ); - JSON_ASSERT_MESSAGE( pageCount_ >= minNewPages, "ValueInternalArray::reserve(): bad reallocation" ); - } - - // Need to allocate new pages ? - ArrayIndex nextPageIndex = - (size_ % itemsPerPage) != 0 ? size_ - (size_%itemsPerPage) + itemsPerPage - : size_; - if ( nextPageIndex <= index ) - { - PageIndex pageIndex = nextPageIndex / itemsPerPage; - PageIndex pageToAllocate = (index - nextPageIndex) / itemsPerPage + 1; - for ( ; pageToAllocate-- > 0; ++pageIndex ) - pages_[pageIndex] = arrayAllocator()->allocateArrayPage(); - } - - // Initialize all new entries - IteratorState it; - IteratorState itEnd; - makeIterator( it, size_ ); - size_ = index + 1; - makeIterator( itEnd, size_ ); - for ( ; !equals(it,itEnd); increment(it) ) - { - Value *value = &dereference(it); - new (value) Value(); // Construct a default value using placement new - } -} - -Value & -ValueInternalArray::resolveReference( ArrayIndex index ) -{ - if ( index >= size_ ) - makeIndexValid( index ); - return pages_[index/itemsPerPage][index%itemsPerPage]; -} - -Value * -ValueInternalArray::find( ArrayIndex index ) const -{ - if ( index >= size_ ) - return 0; - return &(pages_[index/itemsPerPage][index%itemsPerPage]); -} - -ValueInternalArray::ArrayIndex -ValueInternalArray::size() const -{ - return size_; -} - -int -ValueInternalArray::distance( const IteratorState &x, const IteratorState &y ) -{ - return indexOf(y) - indexOf(x); -} - - -ValueInternalArray::ArrayIndex -ValueInternalArray::indexOf( const IteratorState &iterator ) -{ - if ( !iterator.array_ ) - return ArrayIndex(-1); - return ArrayIndex( - (iterator.currentPageIndex_ - iterator.array_->pages_) * itemsPerPage - + iterator.currentItemIndex_ ); -} - - -int -ValueInternalArray::compare( const ValueInternalArray &other ) const -{ - int sizeDiff( size_ - other.size_ ); - if ( sizeDiff != 0 ) - return sizeDiff; - - for ( ArrayIndex index =0; index < size_; ++index ) - { - int diff = pages_[index/itemsPerPage][index%itemsPerPage].compare( - other.pages_[index/itemsPerPage][index%itemsPerPage] ); - if ( diff != 0 ) - return diff; - } - return 0; -} diff --git a/thirdparty/jsoncpp/src/lib_json/json_internalmap.inl b/thirdparty/jsoncpp/src/lib_json/json_internalmap.inl deleted file mode 100644 index 19771488..00000000 --- a/thirdparty/jsoncpp/src/lib_json/json_internalmap.inl +++ /dev/null @@ -1,607 +0,0 @@ -// included by json_value.cpp -// everything is within Json namespace - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// class ValueInternalMap -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// - -/** \internal MUST be safely initialized using memset( this, 0, sizeof(ValueInternalLink) ); - * This optimization is used by the fast allocator. - */ -ValueInternalLink::ValueInternalLink() - : previous_( 0 ) - , next_( 0 ) -{ -} - -ValueInternalLink::~ValueInternalLink() -{ - for ( int index =0; index < itemPerLink; ++index ) - { - if ( !items_[index].isItemAvailable() ) - { - if ( !items_[index].isMemberNameStatic() ) - free( keys_[index] ); - } - else - break; - } -} - - - -ValueMapAllocator::~ValueMapAllocator() -{ -} - -#ifdef JSON_USE_SIMPLE_INTERNAL_ALLOCATOR -class DefaultValueMapAllocator : public ValueMapAllocator -{ -public: // overridden from ValueMapAllocator - virtual ValueInternalMap *newMap() - { - return new ValueInternalMap(); - } - - virtual ValueInternalMap *newMapCopy( const ValueInternalMap &other ) - { - return new ValueInternalMap( other ); - } - - virtual void destructMap( ValueInternalMap *map ) - { - delete map; - } - - virtual ValueInternalLink *allocateMapBuckets( unsigned int size ) - { - return new ValueInternalLink[size]; - } - - virtual void releaseMapBuckets( ValueInternalLink *links ) - { - delete [] links; - } - - virtual ValueInternalLink *allocateMapLink() - { - return new ValueInternalLink(); - } - - virtual void releaseMapLink( ValueInternalLink *link ) - { - delete link; - } -}; -#else -/// @todo make this thread-safe (lock when accessign batch allocator) -class DefaultValueMapAllocator : public ValueMapAllocator -{ -public: // overridden from ValueMapAllocator - virtual ValueInternalMap *newMap() - { - ValueInternalMap *map = mapsAllocator_.allocate(); - new (map) ValueInternalMap(); // placement new - return map; - } - - virtual ValueInternalMap *newMapCopy( const ValueInternalMap &other ) - { - ValueInternalMap *map = mapsAllocator_.allocate(); - new (map) ValueInternalMap( other ); // placement new - return map; - } - - virtual void destructMap( ValueInternalMap *map ) - { - if ( map ) - { - map->~ValueInternalMap(); - mapsAllocator_.release( map ); - } - } - - virtual ValueInternalLink *allocateMapBuckets( unsigned int size ) - { - return new ValueInternalLink[size]; - } - - virtual void releaseMapBuckets( ValueInternalLink *links ) - { - delete [] links; - } - - virtual ValueInternalLink *allocateMapLink() - { - ValueInternalLink *link = linksAllocator_.allocate(); - memset( link, 0, sizeof(ValueInternalLink) ); - return link; - } - - virtual void releaseMapLink( ValueInternalLink *link ) - { - link->~ValueInternalLink(); - linksAllocator_.release( link ); - } -private: - BatchAllocator mapsAllocator_; - BatchAllocator linksAllocator_; -}; -#endif - -static ValueMapAllocator *&mapAllocator() -{ - static DefaultValueMapAllocator defaultAllocator; - static ValueMapAllocator *mapAllocator = &defaultAllocator; - return mapAllocator; -} - -static struct DummyMapAllocatorInitializer { - DummyMapAllocatorInitializer() - { - mapAllocator(); // ensure mapAllocator() statics are initialized before main(). - } -} dummyMapAllocatorInitializer; - - - -// h(K) = value * K >> w ; with w = 32 & K prime w.r.t. 2^32. - -/* -use linked list hash map. -buckets array is a container. -linked list element contains 6 key/values. (memory = (16+4) * 6 + 4 = 124) -value have extra state: valid, available, deleted -*/ - - -ValueInternalMap::ValueInternalMap() - : buckets_( 0 ) - , tailLink_( 0 ) - , bucketsSize_( 0 ) - , itemCount_( 0 ) -{ -} - - -ValueInternalMap::ValueInternalMap( const ValueInternalMap &other ) - : buckets_( 0 ) - , tailLink_( 0 ) - , bucketsSize_( 0 ) - , itemCount_( 0 ) -{ - reserve( other.itemCount_ ); - IteratorState it; - IteratorState itEnd; - other.makeBeginIterator( it ); - other.makeEndIterator( itEnd ); - for ( ; !equals(it,itEnd); increment(it) ) - { - bool isStatic; - const char *memberName = key( it, isStatic ); - const Value &aValue = value( it ); - resolveReference(memberName, isStatic) = aValue; - } -} - - -ValueInternalMap & -ValueInternalMap::operator =( const ValueInternalMap &other ) -{ - ValueInternalMap dummy( other ); - swap( dummy ); - return *this; -} - - -ValueInternalMap::~ValueInternalMap() -{ - if ( buckets_ ) - { - for ( BucketIndex bucketIndex =0; bucketIndex < bucketsSize_; ++bucketIndex ) - { - ValueInternalLink *link = buckets_[bucketIndex].next_; - while ( link ) - { - ValueInternalLink *linkToRelease = link; - link = link->next_; - mapAllocator()->releaseMapLink( linkToRelease ); - } - } - mapAllocator()->releaseMapBuckets( buckets_ ); - } -} - - -void -ValueInternalMap::swap( ValueInternalMap &other ) -{ - ValueInternalLink *tempBuckets = buckets_; - buckets_ = other.buckets_; - other.buckets_ = tempBuckets; - ValueInternalLink *tempTailLink = tailLink_; - tailLink_ = other.tailLink_; - other.tailLink_ = tempTailLink; - BucketIndex tempBucketsSize = bucketsSize_; - bucketsSize_ = other.bucketsSize_; - other.bucketsSize_ = tempBucketsSize; - BucketIndex tempItemCount = itemCount_; - itemCount_ = other.itemCount_; - other.itemCount_ = tempItemCount; -} - - -void -ValueInternalMap::clear() -{ - ValueInternalMap dummy; - swap( dummy ); -} - - -ValueInternalMap::BucketIndex -ValueInternalMap::size() const -{ - return itemCount_; -} - -bool -ValueInternalMap::reserveDelta( BucketIndex growth ) -{ - return reserve( itemCount_ + growth ); -} - -bool -ValueInternalMap::reserve( BucketIndex newItemCount ) -{ - if ( !buckets_ && newItemCount > 0 ) - { - buckets_ = mapAllocator()->allocateMapBuckets( 1 ); - bucketsSize_ = 1; - tailLink_ = &buckets_[0]; - } -// BucketIndex idealBucketCount = (newItemCount + ValueInternalLink::itemPerLink) / ValueInternalLink::itemPerLink; - return true; -} - - -const Value * -ValueInternalMap::find( const char *key ) const -{ - if ( !bucketsSize_ ) - return 0; - HashKey hashedKey = hash( key ); - BucketIndex bucketIndex = hashedKey % bucketsSize_; - for ( const ValueInternalLink *current = &buckets_[bucketIndex]; - current != 0; - current = current->next_ ) - { - for ( BucketIndex index=0; index < ValueInternalLink::itemPerLink; ++index ) - { - if ( current->items_[index].isItemAvailable() ) - return 0; - if ( strcmp( key, current->keys_[index] ) == 0 ) - return ¤t->items_[index]; - } - } - return 0; -} - - -Value * -ValueInternalMap::find( const char *key ) -{ - const ValueInternalMap *constThis = this; - return const_cast( constThis->find( key ) ); -} - - -Value & -ValueInternalMap::resolveReference( const char *key, - bool isStatic ) -{ - HashKey hashedKey = hash( key ); - if ( bucketsSize_ ) - { - BucketIndex bucketIndex = hashedKey % bucketsSize_; - ValueInternalLink **previous = 0; - BucketIndex index; - for ( ValueInternalLink *current = &buckets_[bucketIndex]; - current != 0; - previous = ¤t->next_, current = current->next_ ) - { - for ( index=0; index < ValueInternalLink::itemPerLink; ++index ) - { - if ( current->items_[index].isItemAvailable() ) - return setNewItem( key, isStatic, current, index ); - if ( strcmp( key, current->keys_[index] ) == 0 ) - return current->items_[index]; - } - } - } - - reserveDelta( 1 ); - return unsafeAdd( key, isStatic, hashedKey ); -} - - -void -ValueInternalMap::remove( const char *key ) -{ - HashKey hashedKey = hash( key ); - if ( !bucketsSize_ ) - return; - BucketIndex bucketIndex = hashedKey % bucketsSize_; - for ( ValueInternalLink *link = &buckets_[bucketIndex]; - link != 0; - link = link->next_ ) - { - BucketIndex index; - for ( index =0; index < ValueInternalLink::itemPerLink; ++index ) - { - if ( link->items_[index].isItemAvailable() ) - return; - if ( strcmp( key, link->keys_[index] ) == 0 ) - { - doActualRemove( link, index, bucketIndex ); - return; - } - } - } -} - -void -ValueInternalMap::doActualRemove( ValueInternalLink *link, - BucketIndex index, - BucketIndex bucketIndex ) -{ - // find last item of the bucket and swap it with the 'removed' one. - // set removed items flags to 'available'. - // if last page only contains 'available' items, then desallocate it (it's empty) - ValueInternalLink *&lastLink = getLastLinkInBucket( index ); - BucketIndex lastItemIndex = 1; // a link can never be empty, so start at 1 - for ( ; - lastItemIndex < ValueInternalLink::itemPerLink; - ++lastItemIndex ) // may be optimized with dicotomic search - { - if ( lastLink->items_[lastItemIndex].isItemAvailable() ) - break; - } - - BucketIndex lastUsedIndex = lastItemIndex - 1; - Value *valueToDelete = &link->items_[index]; - Value *valueToPreserve = &lastLink->items_[lastUsedIndex]; - if ( valueToDelete != valueToPreserve ) - valueToDelete->swap( *valueToPreserve ); - if ( lastUsedIndex == 0 ) // page is now empty - { // remove it from bucket linked list and delete it. - ValueInternalLink *linkPreviousToLast = lastLink->previous_; - if ( linkPreviousToLast != 0 ) // can not deleted bucket link. - { - mapAllocator()->releaseMapLink( lastLink ); - linkPreviousToLast->next_ = 0; - lastLink = linkPreviousToLast; - } - } - else - { - Value dummy; - valueToPreserve->swap( dummy ); // restore deleted to default Value. - valueToPreserve->setItemUsed( false ); - } - --itemCount_; -} - - -ValueInternalLink *& -ValueInternalMap::getLastLinkInBucket( BucketIndex bucketIndex ) -{ - if ( bucketIndex == bucketsSize_ - 1 ) - return tailLink_; - ValueInternalLink *&previous = buckets_[bucketIndex+1].previous_; - if ( !previous ) - previous = &buckets_[bucketIndex]; - return previous; -} - - -Value & -ValueInternalMap::setNewItem( const char *key, - bool isStatic, - ValueInternalLink *link, - BucketIndex index ) -{ - char *duplicatedKey = valueAllocator()->makeMemberName( key ); - ++itemCount_; - link->keys_[index] = duplicatedKey; - link->items_[index].setItemUsed(); - link->items_[index].setMemberNameIsStatic( isStatic ); - return link->items_[index]; // items already default constructed. -} - - -Value & -ValueInternalMap::unsafeAdd( const char *key, - bool isStatic, - HashKey hashedKey ) -{ - JSON_ASSERT_MESSAGE( bucketsSize_ > 0, "ValueInternalMap::unsafeAdd(): internal logic error." ); - BucketIndex bucketIndex = hashedKey % bucketsSize_; - ValueInternalLink *&previousLink = getLastLinkInBucket( bucketIndex ); - ValueInternalLink *link = previousLink; - BucketIndex index; - for ( index =0; index < ValueInternalLink::itemPerLink; ++index ) - { - if ( link->items_[index].isItemAvailable() ) - break; - } - if ( index == ValueInternalLink::itemPerLink ) // need to add a new page - { - ValueInternalLink *newLink = mapAllocator()->allocateMapLink(); - index = 0; - link->next_ = newLink; - previousLink = newLink; - link = newLink; - } - return setNewItem( key, isStatic, link, index ); -} - - -ValueInternalMap::HashKey -ValueInternalMap::hash( const char *key ) const -{ - HashKey hash = 0; - while ( *key ) - hash += *key++ * 37; - return hash; -} - - -int -ValueInternalMap::compare( const ValueInternalMap &other ) const -{ - int sizeDiff( itemCount_ - other.itemCount_ ); - if ( sizeDiff != 0 ) - return sizeDiff; - // Strict order guaranty is required. Compare all keys FIRST, then compare values. - IteratorState it; - IteratorState itEnd; - makeBeginIterator( it ); - makeEndIterator( itEnd ); - for ( ; !equals(it,itEnd); increment(it) ) - { - if ( !other.find( key( it ) ) ) - return 1; - } - - // All keys are equals, let's compare values - makeBeginIterator( it ); - for ( ; !equals(it,itEnd); increment(it) ) - { - const Value *otherValue = other.find( key( it ) ); - int valueDiff = value(it).compare( *otherValue ); - if ( valueDiff != 0 ) - return valueDiff; - } - return 0; -} - - -void -ValueInternalMap::makeBeginIterator( IteratorState &it ) const -{ - it.map_ = const_cast( this ); - it.bucketIndex_ = 0; - it.itemIndex_ = 0; - it.link_ = buckets_; -} - - -void -ValueInternalMap::makeEndIterator( IteratorState &it ) const -{ - it.map_ = const_cast( this ); - it.bucketIndex_ = bucketsSize_; - it.itemIndex_ = 0; - it.link_ = 0; -} - - -bool -ValueInternalMap::equals( const IteratorState &x, const IteratorState &other ) -{ - return x.map_ == other.map_ - && x.bucketIndex_ == other.bucketIndex_ - && x.link_ == other.link_ - && x.itemIndex_ == other.itemIndex_; -} - - -void -ValueInternalMap::incrementBucket( IteratorState &iterator ) -{ - ++iterator.bucketIndex_; - JSON_ASSERT_MESSAGE( iterator.bucketIndex_ <= iterator.map_->bucketsSize_, - "ValueInternalMap::increment(): attempting to iterate beyond end." ); - if ( iterator.bucketIndex_ == iterator.map_->bucketsSize_ ) - iterator.link_ = 0; - else - iterator.link_ = &(iterator.map_->buckets_[iterator.bucketIndex_]); - iterator.itemIndex_ = 0; -} - - -void -ValueInternalMap::increment( IteratorState &iterator ) -{ - JSON_ASSERT_MESSAGE( iterator.map_, "Attempting to iterator using invalid iterator." ); - ++iterator.itemIndex_; - if ( iterator.itemIndex_ == ValueInternalLink::itemPerLink ) - { - JSON_ASSERT_MESSAGE( iterator.link_ != 0, - "ValueInternalMap::increment(): attempting to iterate beyond end." ); - iterator.link_ = iterator.link_->next_; - if ( iterator.link_ == 0 ) - incrementBucket( iterator ); - } - else if ( iterator.link_->items_[iterator.itemIndex_].isItemAvailable() ) - { - incrementBucket( iterator ); - } -} - - -void -ValueInternalMap::decrement( IteratorState &iterator ) -{ - if ( iterator.itemIndex_ == 0 ) - { - JSON_ASSERT_MESSAGE( iterator.map_, "Attempting to iterate using invalid iterator." ); - if ( iterator.link_ == &iterator.map_->buckets_[iterator.bucketIndex_] ) - { - JSON_ASSERT_MESSAGE( iterator.bucketIndex_ > 0, "Attempting to iterate beyond beginning." ); - --(iterator.bucketIndex_); - } - iterator.link_ = iterator.link_->previous_; - iterator.itemIndex_ = ValueInternalLink::itemPerLink - 1; - } -} - - -const char * -ValueInternalMap::key( const IteratorState &iterator ) -{ - JSON_ASSERT_MESSAGE( iterator.link_, "Attempting to iterate using invalid iterator." ); - return iterator.link_->keys_[iterator.itemIndex_]; -} - -const char * -ValueInternalMap::key( const IteratorState &iterator, bool &isStatic ) -{ - JSON_ASSERT_MESSAGE( iterator.link_, "Attempting to iterate using invalid iterator." ); - isStatic = iterator.link_->items_[iterator.itemIndex_].isMemberNameStatic(); - return iterator.link_->keys_[iterator.itemIndex_]; -} - - -Value & -ValueInternalMap::value( const IteratorState &iterator ) -{ - JSON_ASSERT_MESSAGE( iterator.link_, "Attempting to iterate using invalid iterator." ); - return iterator.link_->items_[iterator.itemIndex_]; -} - - -int -ValueInternalMap::distance( const IteratorState &x, const IteratorState &y ) -{ - int offset = 0; - IteratorState it = x; - while ( !equals( it, y ) ) - increment( it ); - return offset; -} diff --git a/thirdparty/jsoncpp/src/lib_json/json_reader.cpp b/thirdparty/jsoncpp/src/lib_json/json_reader.cpp deleted file mode 100644 index 93fbe262..00000000 --- a/thirdparty/jsoncpp/src/lib_json/json_reader.cpp +++ /dev/null @@ -1,883 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#if _MSC_VER >= 1400 // VC++ 8.0 -#pragma warning( disable : 4996 ) // disable warning about strdup being deprecated. -#endif - -namespace Json { - -// Implementation of class Features -// //////////////////////////////// - -Features::Features() - : allowComments_( true ) - , strictRoot_( false ) -{ -} - - -Features -Features::all() -{ - return Features(); -} - - -Features -Features::strictMode() -{ - Features features; - features.allowComments_ = false; - features.strictRoot_ = true; - return features; -} - -// Implementation of class Reader -// //////////////////////////////// - - -static inline bool -in( Reader::Char c, Reader::Char c1, Reader::Char c2, Reader::Char c3, Reader::Char c4 ) -{ - return c == c1 || c == c2 || c == c3 || c == c4; -} - -static inline bool -in( Reader::Char c, Reader::Char c1, Reader::Char c2, Reader::Char c3, Reader::Char c4, Reader::Char c5 ) -{ - return c == c1 || c == c2 || c == c3 || c == c4 || c == c5; -} - - -static bool -containsNewLine( Reader::Location begin, - Reader::Location end ) -{ - for ( ;begin < end; ++begin ) - if ( *begin == '\n' || *begin == '\r' ) - return true; - return false; -} - -static std::string codePointToUTF8(unsigned int cp) -{ - std::string result; - - // based on description from http://en.wikipedia.org/wiki/UTF-8 - - if (cp <= 0x7f) - { - result.resize(1); - result[0] = static_cast(cp); - } - else if (cp <= 0x7FF) - { - result.resize(2); - result[1] = static_cast(0x80 | (0x3f & cp)); - result[0] = static_cast(0xC0 | (0x1f & (cp >> 6))); - } - else if (cp <= 0xFFFF) - { - result.resize(3); - result[2] = static_cast(0x80 | (0x3f & cp)); - result[1] = 0x80 | static_cast((0x3f & (cp >> 6))); - result[0] = 0xE0 | static_cast((0xf & (cp >> 12))); - } - else if (cp <= 0x10FFFF) - { - result.resize(4); - result[3] = static_cast(0x80 | (0x3f & cp)); - result[2] = static_cast(0x80 | (0x3f & (cp >> 6))); - result[1] = static_cast(0x80 | (0x3f & (cp >> 12))); - result[0] = static_cast(0xF0 | (0x7 & (cp >> 18))); - } - - return result; -} - - -// Class Reader -// ////////////////////////////////////////////////////////////////// - -Reader::Reader() - : features_( Features::all() ) -{ -} - - -Reader::Reader( const Features &features ) - : features_( features ) -{ -} - - -bool -Reader::parse( const std::string &document, - Value &root, - bool collectComments ) -{ - document_ = document; - const char *begin = document_.c_str(); - const char *end = begin + document_.length(); - return parse( begin, end, root, collectComments ); -} - - -bool -Reader::parse( std::istream& sin, - Value &root, - bool collectComments ) -{ - //std::istream_iterator begin(sin); - //std::istream_iterator end; - // Those would allow streamed input from a file, if parse() were a - // template function. - - // Since std::string is reference-counted, this at least does not - // create an extra copy. - std::string doc; - std::getline(sin, doc, (char)EOF); - return parse( doc, root, collectComments ); -} - -bool -Reader::parse( const char *beginDoc, const char *endDoc, - Value &root, - bool collectComments ) -{ - if ( !features_.allowComments_ ) - { - collectComments = false; - } - - begin_ = beginDoc; - end_ = endDoc; - collectComments_ = collectComments; - current_ = begin_; - lastValueEnd_ = 0; - lastValue_ = 0; - commentsBefore_ = ""; - errors_.clear(); - while ( !nodes_.empty() ) - nodes_.pop(); - nodes_.push( &root ); - - bool successful = readValue(); - Token token; - skipCommentTokens( token ); - if ( collectComments_ && !commentsBefore_.empty() ) - root.setComment( commentsBefore_, commentAfter ); - if ( features_.strictRoot_ ) - { - if ( !root.isArray() && !root.isObject() ) - { - // Set error location to start of doc, ideally should be first token found in doc - token.type_ = tokenError; - token.start_ = beginDoc; - token.end_ = endDoc; - addError( "A valid JSON document must be either an array or an object value.", - token ); - return false; - } - } - return successful; -} - - -bool -Reader::readValue() -{ - Token token; - skipCommentTokens( token ); - bool successful = true; - - if ( collectComments_ && !commentsBefore_.empty() ) - { - currentValue().setComment( commentsBefore_, commentBefore ); - commentsBefore_ = ""; - } - - - switch ( token.type_ ) - { - case tokenObjectBegin: - successful = readObject( token ); - break; - case tokenArrayBegin: - successful = readArray( token ); - break; - case tokenNumber: - successful = decodeNumber( token ); - break; - case tokenString: - successful = decodeString( token ); - break; - case tokenTrue: - currentValue() = true; - break; - case tokenFalse: - currentValue() = false; - break; - case tokenNull: - currentValue() = Value(); - break; - default: - return addError( "Syntax error: value, object or array expected.", token ); - } - - if ( collectComments_ ) - { - lastValueEnd_ = current_; - lastValue_ = ¤tValue(); - } - - return successful; -} - - -void -Reader::skipCommentTokens( Token &token ) -{ - if ( features_.allowComments_ ) - { - do - { - readToken( token ); - } - while ( token.type_ == tokenComment ); - } - else - { - readToken( token ); - } -} - - -bool -Reader::expectToken( TokenType type, Token &token, const char *message ) -{ - readToken( token ); - if ( token.type_ != type ) - return addError( message, token ); - return true; -} - - -bool -Reader::readToken( Token &token ) -{ - skipSpaces(); - token.start_ = current_; - Char c = getNextChar(); - bool ok = true; - switch ( c ) - { - case '{': - token.type_ = tokenObjectBegin; - break; - case '}': - token.type_ = tokenObjectEnd; - break; - case '[': - token.type_ = tokenArrayBegin; - break; - case ']': - token.type_ = tokenArrayEnd; - break; - case '"': - token.type_ = tokenString; - ok = readString(); - break; - case '/': - token.type_ = tokenComment; - ok = readComment(); - break; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case '-': - token.type_ = tokenNumber; - readNumber(); - break; - case 't': - token.type_ = tokenTrue; - ok = match( "rue", 3 ); - break; - case 'f': - token.type_ = tokenFalse; - ok = match( "alse", 4 ); - break; - case 'n': - token.type_ = tokenNull; - ok = match( "ull", 3 ); - break; - case ',': - token.type_ = tokenArraySeparator; - break; - case ':': - token.type_ = tokenMemberSeparator; - break; - case 0: - token.type_ = tokenEndOfStream; - break; - default: - ok = false; - break; - } - if ( !ok ) - token.type_ = tokenError; - token.end_ = current_; - return true; -} - - -void -Reader::skipSpaces() -{ - while ( current_ != end_ ) - { - Char c = *current_; - if ( c == ' ' || c == '\t' || c == '\r' || c == '\n' ) - ++current_; - else - break; - } -} - - -bool -Reader::match( Location pattern, - int patternLength ) -{ - if ( end_ - current_ < patternLength ) - return false; - int index = patternLength; - while ( index-- ) - if ( current_[index] != pattern[index] ) - return false; - current_ += patternLength; - return true; -} - - -bool -Reader::readComment() -{ - Location commentBegin = current_ - 1; - Char c = getNextChar(); - bool successful = false; - if ( c == '*' ) - successful = readCStyleComment(); - else if ( c == '/' ) - successful = readCppStyleComment(); - if ( !successful ) - return false; - - if ( collectComments_ ) - { - CommentPlacement placement = commentBefore; - if ( lastValueEnd_ && !containsNewLine( lastValueEnd_, commentBegin ) ) - { - if ( c != '*' || !containsNewLine( commentBegin, current_ ) ) - placement = commentAfterOnSameLine; - } - - addComment( commentBegin, current_, placement ); - } - return true; -} - - -void -Reader::addComment( Location begin, - Location end, - CommentPlacement placement ) -{ - assert( collectComments_ ); - if ( placement == commentAfterOnSameLine ) - { - assert( lastValue_ != 0 ); - lastValue_->setComment( std::string( begin, end ), placement ); - } - else - { - if ( !commentsBefore_.empty() ) - commentsBefore_ += "\n"; - commentsBefore_ += std::string( begin, end ); - } -} - - -bool -Reader::readCStyleComment() -{ - while ( current_ != end_ ) - { - Char c = getNextChar(); - if ( c == '*' && *current_ == '/' ) - break; - } - return getNextChar() == '/'; -} - - -bool -Reader::readCppStyleComment() -{ - while ( current_ != end_ ) - { - Char c = getNextChar(); - if ( c == '\r' || c == '\n' ) - break; - } - return true; -} - - -void -Reader::readNumber() -{ - while ( current_ != end_ ) - { - if ( !(*current_ >= '0' && *current_ <= '9') && - !in( *current_, '.', 'e', 'E', '+', '-' ) ) - break; - ++current_; - } -} - -bool -Reader::readString() -{ - Char c = 0; - while ( current_ != end_ ) - { - c = getNextChar(); - if ( c == '\\' ) - getNextChar(); - else if ( c == '"' ) - break; - } - return c == '"'; -} - - -bool -Reader::readObject( Token &tokenStart ) -{ - Token tokenName; - std::string name; - currentValue() = Value( objectValue ); - while ( readToken( tokenName ) ) - { - bool initialTokenOk = true; - while ( tokenName.type_ == tokenComment && initialTokenOk ) - initialTokenOk = readToken( tokenName ); - if ( !initialTokenOk ) - break; - if ( tokenName.type_ == tokenObjectEnd && name.empty() ) // empty object - return true; - if ( tokenName.type_ != tokenString ) - break; - - name = ""; - if ( !decodeString( tokenName, name ) ) - return recoverFromError( tokenObjectEnd ); - - Token colon; - if ( !readToken( colon ) || colon.type_ != tokenMemberSeparator ) - { - return addErrorAndRecover( "Missing ':' after object member name", - colon, - tokenObjectEnd ); - } - Value &value = currentValue()[ name ]; - nodes_.push( &value ); - bool ok = readValue(); - nodes_.pop(); - if ( !ok ) // error already set - return recoverFromError( tokenObjectEnd ); - - Token comma; - if ( !readToken( comma ) - || ( comma.type_ != tokenObjectEnd && - comma.type_ != tokenArraySeparator && - comma.type_ != tokenComment ) ) - { - return addErrorAndRecover( "Missing ',' or '}' in object declaration", - comma, - tokenObjectEnd ); - } - bool finalizeTokenOk = true; - while ( comma.type_ == tokenComment && - finalizeTokenOk ) - finalizeTokenOk = readToken( comma ); - if ( comma.type_ == tokenObjectEnd ) - return true; - } - return addErrorAndRecover( "Missing '}' or object member name", - tokenName, - tokenObjectEnd ); -} - - -bool -Reader::readArray( Token &tokenStart ) -{ - currentValue() = Value( arrayValue ); - skipSpaces(); - if ( *current_ == ']' ) // empty array - { - Token endArray; - readToken( endArray ); - return true; - } - int index = 0; - while ( true ) - { - Value &value = currentValue()[ index++ ]; - nodes_.push( &value ); - bool ok = readValue(); - nodes_.pop(); - if ( !ok ) // error already set - return recoverFromError( tokenArrayEnd ); - - Token token; - // Accept Comment after last item in the array. - ok = readToken( token ); - while ( token.type_ == tokenComment && ok ) - { - ok = readToken( token ); - } - bool badTokenType = ( token.type_ == tokenArraySeparator && - token.type_ == tokenArrayEnd ); - if ( !ok || badTokenType ) - { - return addErrorAndRecover( "Missing ',' or ']' in array declaration", - token, - tokenArrayEnd ); - } - if ( token.type_ == tokenArrayEnd ) - break; - } - return true; -} - - -bool -Reader::decodeNumber( Token &token ) -{ - bool isDouble = false; - for ( Location inspect = token.start_; inspect != token.end_; ++inspect ) - { - isDouble = isDouble - || in( *inspect, '.', 'e', 'E', '+' ) - || ( *inspect == '-' && inspect != token.start_ ); - } - if ( isDouble ) - return decodeDouble( token ); - Location current = token.start_; - bool isNegative = *current == '-'; - if ( isNegative ) - ++current; - Value::UInt threshold = (isNegative ? Value::UInt(-Value::minInt) - : Value::maxUInt) / 10; - Value::UInt value = 0; - while ( current < token.end_ ) - { - Char c = *current++; - if ( c < '0' || c > '9' ) - return addError( "'" + std::string( token.start_, token.end_ ) + "' is not a number.", token ); - if ( value >= threshold ) - return decodeDouble( token ); - value = value * 10 + Value::UInt(c - '0'); - } - if ( isNegative ) - currentValue() = -Value::Int( value ); - else if ( value <= Value::UInt(Value::maxInt) ) - currentValue() = Value::Int( value ); - else - currentValue() = value; - return true; -} - - -bool -Reader::decodeDouble( Token &token ) -{ - double value = 0; - const int bufferSize = 32; - int count; - int length = int(token.end_ - token.start_); - if ( length <= bufferSize ) - { - Char buffer[bufferSize]; - memcpy( buffer, token.start_, length ); - buffer[length] = 0; - count = sscanf( buffer, "%lf", &value ); - } - else - { - std::string buffer( token.start_, token.end_ ); - count = sscanf( buffer.c_str(), "%lf", &value ); - } - - if ( count != 1 ) - return addError( "'" + std::string( token.start_, token.end_ ) + "' is not a number.", token ); - currentValue() = value; - return true; -} - - -bool -Reader::decodeString( Token &token ) -{ - std::string decoded; - if ( !decodeString( token, decoded ) ) - return false; - currentValue() = decoded; - return true; -} - - -bool -Reader::decodeString( Token &token, std::string &decoded ) -{ - decoded.reserve( token.end_ - token.start_ - 2 ); - Location current = token.start_ + 1; // skip '"' - Location end = token.end_ - 1; // do not include '"' - while ( current != end ) - { - Char c = *current++; - if ( c == '"' ) - break; - else if ( c == '\\' ) - { - if ( current == end ) - return addError( "Empty escape sequence in string", token, current ); - Char escape = *current++; - switch ( escape ) - { - case '"': decoded += '"'; break; - case '/': decoded += '/'; break; - case '\\': decoded += '\\'; break; - case 'b': decoded += '\b'; break; - case 'f': decoded += '\f'; break; - case 'n': decoded += '\n'; break; - case 'r': decoded += '\r'; break; - case 't': decoded += '\t'; break; - case 'u': - { - unsigned int unicode; - if ( !decodeUnicodeCodePoint( token, current, end, unicode ) ) - return false; - decoded += codePointToUTF8(unicode); - } - break; - default: - return addError( "Bad escape sequence in string", token, current ); - } - } - else - { - decoded += c; - } - } - return true; -} - -bool -Reader::decodeUnicodeCodePoint( Token &token, - Location ¤t, - Location end, - unsigned int &unicode ) -{ - - if ( !decodeUnicodeEscapeSequence( token, current, end, unicode ) ) - return false; - if (unicode >= 0xD800 && unicode <= 0xDBFF) - { - // surrogate pairs - if (end - current < 6) - return addError( "additional six characters expected to parse unicode surrogate pair.", token, current ); - unsigned int surrogatePair; - if (*(current++) == '\\' && *(current++)== 'u') - { - if (decodeUnicodeEscapeSequence( token, current, end, surrogatePair )) - { - unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF); - } - else - return false; - } - else - return addError( "expecting another \\u token to begin the second half of a unicode surrogate pair", token, current ); - } - return true; -} - -bool -Reader::decodeUnicodeEscapeSequence( Token &token, - Location ¤t, - Location end, - unsigned int &unicode ) -{ - if ( end - current < 4 ) - return addError( "Bad unicode escape sequence in string: four digits expected.", token, current ); - unicode = 0; - for ( int index =0; index < 4; ++index ) - { - Char c = *current++; - unicode *= 16; - if ( c >= '0' && c <= '9' ) - unicode += c - '0'; - else if ( c >= 'a' && c <= 'f' ) - unicode += c - 'a' + 10; - else if ( c >= 'A' && c <= 'F' ) - unicode += c - 'A' + 10; - else - return addError( "Bad unicode escape sequence in string: hexadecimal digit expected.", token, current ); - } - return true; -} - - -bool -Reader::addError( const std::string &message, - Token &token, - Location extra ) -{ - ErrorInfo info; - info.token_ = token; - info.message_ = message; - info.extra_ = extra; - errors_.push_back( info ); - return false; -} - - -bool -Reader::recoverFromError( TokenType skipUntilToken ) -{ - int errorCount = int(errors_.size()); - Token skip; - while ( true ) - { - if ( !readToken(skip) ) - errors_.resize( errorCount ); // discard errors caused by recovery - if ( skip.type_ == skipUntilToken || skip.type_ == tokenEndOfStream ) - break; - } - errors_.resize( errorCount ); - return false; -} - - -bool -Reader::addErrorAndRecover( const std::string &message, - Token &token, - TokenType skipUntilToken ) -{ - addError( message, token ); - return recoverFromError( skipUntilToken ); -} - - -Value & -Reader::currentValue() -{ - return *(nodes_.top()); -} - - -Reader::Char -Reader::getNextChar() -{ - if ( current_ == end_ ) - return 0; - return *current_++; -} - - -void -Reader::getLocationLineAndColumn( Location location, - int &line, - int &column ) const -{ - Location current = begin_; - Location lastLineStart = current; - line = 0; - while ( current < location && current != end_ ) - { - Char c = *current++; - if ( c == '\r' ) - { - if ( *current == '\n' ) - ++current; - lastLineStart = current; - ++line; - } - else if ( c == '\n' ) - { - lastLineStart = current; - ++line; - } - } - // column & line start at 1 - column = int(location - lastLineStart) + 1; - ++line; -} - - -std::string -Reader::getLocationLineAndColumn( Location location ) const -{ - int line, column; - getLocationLineAndColumn( location, line, column ); - char buffer[18+16+16+1]; - sprintf( buffer, "Line %d, Column %d", line, column ); - return buffer; -} - - -std::string -Reader::getFormatedErrorMessages() const -{ - std::string formattedMessage; - for ( Errors::const_iterator itError = errors_.begin(); - itError != errors_.end(); - ++itError ) - { - const ErrorInfo &error = *itError; - formattedMessage += "* " + getLocationLineAndColumn( error.token_.start_ ) + "\n"; - formattedMessage += " " + error.message_ + "\n"; - if ( error.extra_ ) - formattedMessage += "See " + getLocationLineAndColumn( error.extra_ ) + " for detail.\n"; - } - return formattedMessage; -} - - -std::istream& operator>>( std::istream &sin, Value &root ) -{ - Json::Reader reader; - bool ok = reader.parse(sin, root, true); - //JSON_ASSERT( ok ); - if (!ok) throw std::runtime_error(reader.getFormatedErrorMessages()); - return sin; -} - - -} // namespace Json diff --git a/thirdparty/jsoncpp/src/lib_json/json_value.cpp b/thirdparty/jsoncpp/src/lib_json/json_value.cpp deleted file mode 100644 index 0c387599..00000000 --- a/thirdparty/jsoncpp/src/lib_json/json_value.cpp +++ /dev/null @@ -1,1717 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#ifdef JSON_USE_CPPTL -# include -#endif -#ifndef JSON_USE_SIMPLE_INTERNAL_ALLOCATOR -# include "json_batchallocator.h" -#endif // #ifndef JSON_USE_SIMPLE_INTERNAL_ALLOCATOR - -#define JSON_ASSERT_UNREACHABLE assert( false ) -#define JSON_ASSERT( condition ) assert( condition ); // @todo <= change this into an exception throw -#define JSON_ASSERT_MESSAGE( condition, message ) if (!( condition )) throw std::runtime_error( message ); - -namespace Json { - -const Value Value::null; -const Int Value::minInt = Int( ~(UInt(-1)/2) ); -const Int Value::maxInt = Int( UInt(-1)/2 ); -const UInt Value::maxUInt = UInt(-1); - -// A "safe" implementation of strdup. Allow null pointer to be passed. -// Also avoid warning on msvc80. -// -//inline char *safeStringDup( const char *czstring ) -//{ -// if ( czstring ) -// { -// const size_t length = (unsigned int)( strlen(czstring) + 1 ); -// char *newString = static_cast( malloc( length ) ); -// memcpy( newString, czstring, length ); -// return newString; -// } -// return 0; -//} -// -//inline char *safeStringDup( const std::string &str ) -//{ -// if ( !str.empty() ) -// { -// const size_t length = str.length(); -// char *newString = static_cast( malloc( length + 1 ) ); -// memcpy( newString, str.c_str(), length ); -// newString[length] = 0; -// return newString; -// } -// return 0; -//} - -ValueAllocator::~ValueAllocator() -{ -} - -class DefaultValueAllocator : public ValueAllocator -{ -public: - virtual ~DefaultValueAllocator() - { - } - - virtual char *makeMemberName( const char *memberName ) - { - return duplicateStringValue( memberName ); - } - - virtual void releaseMemberName( char *memberName ) - { - releaseStringValue( memberName ); - } - - virtual char *duplicateStringValue( const char *value, - unsigned int length = unknown ) - { - //@todo invesgate this old optimization - //if ( !value || value[0] == 0 ) - // return 0; - - if ( length == unknown ) - length = (unsigned int)strlen(value); - char *newString = static_cast( malloc( length + 1 ) ); - memcpy( newString, value, length ); - newString[length] = 0; - return newString; - } - - virtual void releaseStringValue( char *value ) - { - if ( value ) - free( value ); - } -}; - -static ValueAllocator *&valueAllocator() -{ - static DefaultValueAllocator defaultAllocator; - static ValueAllocator *valueAllocator = &defaultAllocator; - return valueAllocator; -} - -static struct DummyValueAllocatorInitializer { - DummyValueAllocatorInitializer() - { - valueAllocator(); // ensure valueAllocator() statics are initialized before main(). - } -} dummyValueAllocatorInitializer; - - - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ValueInternals... -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -#ifdef JSON_VALUE_USE_INTERNAL_MAP -# include "json_internalarray.inl" -# include "json_internalmap.inl" -#endif // JSON_VALUE_USE_INTERNAL_MAP - -# include "json_valueiterator.inl" - - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// class Value::CommentInfo -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// - - -Value::CommentInfo::CommentInfo() - : comment_( 0 ) -{ -} - -Value::CommentInfo::~CommentInfo() -{ - if ( comment_ ) - valueAllocator()->releaseStringValue( comment_ ); -} - - -void -Value::CommentInfo::setComment( const char *text ) -{ - if ( comment_ ) - valueAllocator()->releaseStringValue( comment_ ); - JSON_ASSERT( text ); - JSON_ASSERT_MESSAGE( text[0]=='\0' || text[0]=='/', "Comments must start with /"); - // It seems that /**/ style comments are acceptable as well. - comment_ = valueAllocator()->duplicateStringValue( text ); -} - - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// class Value::CZString -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -# ifndef JSON_VALUE_USE_INTERNAL_MAP - -// Notes: index_ indicates if the string was allocated when -// a string is stored. - -Value::CZString::CZString( int index ) - : cstr_( 0 ) - , index_( index ) -{ -} - -Value::CZString::CZString( const char *cstr, DuplicationPolicy allocate ) - : cstr_( allocate == duplicate ? valueAllocator()->makeMemberName(cstr) - : cstr ) - , index_( allocate ) -{ -} - -Value::CZString::CZString( const CZString &other ) -: cstr_( other.index_ != noDuplication && other.cstr_ != 0 - ? valueAllocator()->makeMemberName( other.cstr_ ) - : other.cstr_ ) - , index_( other.cstr_ ? (other.index_ == noDuplication ? noDuplication : duplicate) - : other.index_ ) -{ -} - -Value::CZString::~CZString() -{ - if ( cstr_ && index_ == duplicate ) - valueAllocator()->releaseMemberName( const_cast( cstr_ ) ); -} - -void -Value::CZString::swap( CZString &other ) -{ - std::swap( cstr_, other.cstr_ ); - std::swap( index_, other.index_ ); -} - -Value::CZString & -Value::CZString::operator =( const CZString &other ) -{ - CZString temp( other ); - swap( temp ); - return *this; -} - -bool -Value::CZString::operator<( const CZString &other ) const -{ - if ( cstr_ ) - return strcmp( cstr_, other.cstr_ ) < 0; - return index_ < other.index_; -} - -bool -Value::CZString::operator==( const CZString &other ) const -{ - if ( cstr_ ) - return strcmp( cstr_, other.cstr_ ) == 0; - return index_ == other.index_; -} - - -int -Value::CZString::index() const -{ - return index_; -} - - -const char * -Value::CZString::c_str() const -{ - return cstr_; -} - -bool -Value::CZString::isStaticString() const -{ - return index_ == noDuplication; -} - -#endif // ifndef JSON_VALUE_USE_INTERNAL_MAP - - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// class Value::Value -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// - -/*! \internal Default constructor initialization must be equivalent to: - * memset( this, 0, sizeof(Value) ) - * This optimization is used in ValueInternalMap fast allocator. - */ -Value::Value( ValueType type ) - : type_( type ) - , allocated_( 0 ) - , comments_( 0 ) -# ifdef JSON_VALUE_USE_INTERNAL_MAP - , itemIsUsed_( 0 ) -#endif -{ - switch ( type ) - { - case nullValue: - break; - case intValue: - case uintValue: - value_.int_ = 0; - break; - case realValue: - value_.real_ = 0.0; - break; - case stringValue: - value_.string_ = 0; - break; -#ifndef JSON_VALUE_USE_INTERNAL_MAP - case arrayValue: - case objectValue: - value_.map_ = new ObjectValues(); - break; -#else - case arrayValue: - value_.array_ = arrayAllocator()->newArray(); - break; - case objectValue: - value_.map_ = mapAllocator()->newMap(); - break; -#endif - case booleanValue: - value_.bool_ = false; - break; - default: - JSON_ASSERT_UNREACHABLE; - } -} - - -Value::Value( Int value ) - : type_( intValue ) - , comments_( 0 ) -# ifdef JSON_VALUE_USE_INTERNAL_MAP - , itemIsUsed_( 0 ) -#endif -{ - value_.int_ = value; -} - - -Value::Value( UInt value ) - : type_( uintValue ) - , comments_( 0 ) -# ifdef JSON_VALUE_USE_INTERNAL_MAP - , itemIsUsed_( 0 ) -#endif -{ - value_.uint_ = value; -} - -Value::Value( double value ) - : type_( realValue ) - , comments_( 0 ) -# ifdef JSON_VALUE_USE_INTERNAL_MAP - , itemIsUsed_( 0 ) -#endif -{ - value_.real_ = value; -} - -Value::Value( const char *value ) - : type_( stringValue ) - , allocated_( true ) - , comments_( 0 ) -# ifdef JSON_VALUE_USE_INTERNAL_MAP - , itemIsUsed_( 0 ) -#endif -{ - value_.string_ = valueAllocator()->duplicateStringValue( value ); -} - - -Value::Value( const char *beginValue, - const char *endValue ) - : type_( stringValue ) - , allocated_( true ) - , comments_( 0 ) -# ifdef JSON_VALUE_USE_INTERNAL_MAP - , itemIsUsed_( 0 ) -#endif -{ - value_.string_ = valueAllocator()->duplicateStringValue( beginValue, - UInt(endValue - beginValue) ); -} - - -Value::Value( const std::string &value ) - : type_( stringValue ) - , allocated_( true ) - , comments_( 0 ) -# ifdef JSON_VALUE_USE_INTERNAL_MAP - , itemIsUsed_( 0 ) -#endif -{ - value_.string_ = valueAllocator()->duplicateStringValue( value.c_str(), - (unsigned int)value.length() ); - -} - -Value::Value( const StaticString &value ) - : type_( stringValue ) - , allocated_( false ) - , comments_( 0 ) -# ifdef JSON_VALUE_USE_INTERNAL_MAP - , itemIsUsed_( 0 ) -#endif -{ - value_.string_ = const_cast( value.c_str() ); -} - - -# ifdef JSON_USE_CPPTL -Value::Value( const CppTL::ConstString &value ) - : type_( stringValue ) - , allocated_( true ) - , comments_( 0 ) -# ifdef JSON_VALUE_USE_INTERNAL_MAP - , itemIsUsed_( 0 ) -#endif -{ - value_.string_ = valueAllocator()->duplicateStringValue( value, value.length() ); -} -# endif - -Value::Value( bool value ) - : type_( booleanValue ) - , comments_( 0 ) -# ifdef JSON_VALUE_USE_INTERNAL_MAP - , itemIsUsed_( 0 ) -#endif -{ - value_.bool_ = value; -} - - -Value::Value( const Value &other ) - : type_( other.type_ ) - , comments_( 0 ) -# ifdef JSON_VALUE_USE_INTERNAL_MAP - , itemIsUsed_( 0 ) -#endif -{ - switch ( type_ ) - { - case nullValue: - case intValue: - case uintValue: - case realValue: - case booleanValue: - value_ = other.value_; - break; - case stringValue: - if ( other.value_.string_ ) - { - value_.string_ = valueAllocator()->duplicateStringValue( other.value_.string_ ); - allocated_ = true; - } - else - value_.string_ = 0; - break; -#ifndef JSON_VALUE_USE_INTERNAL_MAP - case arrayValue: - case objectValue: - value_.map_ = new ObjectValues( *other.value_.map_ ); - break; -#else - case arrayValue: - value_.array_ = arrayAllocator()->newArrayCopy( *other.value_.array_ ); - break; - case objectValue: - value_.map_ = mapAllocator()->newMapCopy( *other.value_.map_ ); - break; -#endif - default: - JSON_ASSERT_UNREACHABLE; - } - if ( other.comments_ ) - { - comments_ = new CommentInfo[numberOfCommentPlacement]; - for ( int comment =0; comment < numberOfCommentPlacement; ++comment ) - { - const CommentInfo &otherComment = other.comments_[comment]; - if ( otherComment.comment_ ) - comments_[comment].setComment( otherComment.comment_ ); - } - } -} - - -Value::~Value() -{ - switch ( type_ ) - { - case nullValue: - case intValue: - case uintValue: - case realValue: - case booleanValue: - break; - case stringValue: - if ( allocated_ ) - valueAllocator()->releaseStringValue( value_.string_ ); - break; -#ifndef JSON_VALUE_USE_INTERNAL_MAP - case arrayValue: - case objectValue: - delete value_.map_; - break; -#else - case arrayValue: - arrayAllocator()->destructArray( value_.array_ ); - break; - case objectValue: - mapAllocator()->destructMap( value_.map_ ); - break; -#endif - default: - JSON_ASSERT_UNREACHABLE; - } - - if ( comments_ ) - delete[] comments_; -} - -Value & -Value::operator=( const Value &other ) -{ - Value temp( other ); - swap( temp ); - return *this; -} - -void -Value::swap( Value &other ) -{ - ValueType temp = type_; - type_ = other.type_; - other.type_ = temp; - std::swap( value_, other.value_ ); - int temp2 = allocated_; - allocated_ = other.allocated_; - other.allocated_ = temp2; -} - -ValueType -Value::type() const -{ - return type_; -} - - -int -Value::compare( const Value &other ) -{ - /* - int typeDelta = other.type_ - type_; - switch ( type_ ) - { - case nullValue: - - return other.type_ == type_; - case intValue: - if ( other.type_.isNumeric() - case uintValue: - case realValue: - case booleanValue: - break; - case stringValue, - break; - case arrayValue: - delete value_.array_; - break; - case objectValue: - delete value_.map_; - default: - JSON_ASSERT_UNREACHABLE; - } - */ - return 0; // unreachable -} - -bool -Value::operator <( const Value &other ) const -{ - int typeDelta = type_ - other.type_; - if ( typeDelta ) - return typeDelta < 0 ? true : false; - switch ( type_ ) - { - case nullValue: - return false; - case intValue: - return value_.int_ < other.value_.int_; - case uintValue: - return value_.uint_ < other.value_.uint_; - case realValue: - return value_.real_ < other.value_.real_; - case booleanValue: - return value_.bool_ < other.value_.bool_; - case stringValue: - return ( value_.string_ == 0 && other.value_.string_ ) - || ( other.value_.string_ - && value_.string_ - && strcmp( value_.string_, other.value_.string_ ) < 0 ); -#ifndef JSON_VALUE_USE_INTERNAL_MAP - case arrayValue: - case objectValue: - { - int delta = int( value_.map_->size() - other.value_.map_->size() ); - if ( delta ) - return delta < 0; - return (*value_.map_) < (*other.value_.map_); - } -#else - case arrayValue: - return value_.array_->compare( *(other.value_.array_) ) < 0; - case objectValue: - return value_.map_->compare( *(other.value_.map_) ) < 0; -#endif - default: - JSON_ASSERT_UNREACHABLE; - } - return 0; // unreachable -} - -bool -Value::operator <=( const Value &other ) const -{ - return !(other > *this); -} - -bool -Value::operator >=( const Value &other ) const -{ - return !(*this < other); -} - -bool -Value::operator >( const Value &other ) const -{ - return other < *this; -} - -bool -Value::operator ==( const Value &other ) const -{ - //if ( type_ != other.type_ ) - // GCC 2.95.3 says: - // attempt to take address of bit-field structure member `Json::Value::type_' - // Beats me, but a temp solves the problem. - int temp = other.type_; - if ( type_ != temp ) - return false; - switch ( type_ ) - { - case nullValue: - return true; - case intValue: - return value_.int_ == other.value_.int_; - case uintValue: - return value_.uint_ == other.value_.uint_; - case realValue: - return value_.real_ == other.value_.real_; - case booleanValue: - return value_.bool_ == other.value_.bool_; - case stringValue: - return ( value_.string_ == other.value_.string_ ) - || ( other.value_.string_ - && value_.string_ - && strcmp( value_.string_, other.value_.string_ ) == 0 ); -#ifndef JSON_VALUE_USE_INTERNAL_MAP - case arrayValue: - case objectValue: - return value_.map_->size() == other.value_.map_->size() - && (*value_.map_) == (*other.value_.map_); -#else - case arrayValue: - return value_.array_->compare( *(other.value_.array_) ) == 0; - case objectValue: - return value_.map_->compare( *(other.value_.map_) ) == 0; -#endif - default: - JSON_ASSERT_UNREACHABLE; - } - return 0; // unreachable -} - -bool -Value::operator !=( const Value &other ) const -{ - return !( *this == other ); -} - -const char * -Value::asCString() const -{ - JSON_ASSERT( type_ == stringValue ); - return value_.string_; -} - - -std::string -Value::asString() const -{ - switch ( type_ ) - { - case nullValue: - return ""; - case stringValue: - return value_.string_ ? value_.string_ : ""; - case booleanValue: - return value_.bool_ ? "true" : "false"; - case intValue: - case uintValue: - case realValue: - case arrayValue: - case objectValue: - JSON_ASSERT_MESSAGE( false, "Type is not convertible to string" ); - default: - JSON_ASSERT_UNREACHABLE; - } - return ""; // unreachable -} - -# ifdef JSON_USE_CPPTL -CppTL::ConstString -Value::asConstString() const -{ - return CppTL::ConstString( asString().c_str() ); -} -# endif - -Value::Int -Value::asInt() const -{ - switch ( type_ ) - { - case nullValue: - return 0; - case intValue: - return value_.int_; - case uintValue: - JSON_ASSERT_MESSAGE( value_.uint_ < (unsigned)maxInt, "integer out of signed integer range" ); - return value_.uint_; - case realValue: - JSON_ASSERT_MESSAGE( value_.real_ >= minInt && value_.real_ <= maxInt, "Real out of signed integer range" ); - return Int( value_.real_ ); - case booleanValue: - return value_.bool_ ? 1 : 0; - case stringValue: - case arrayValue: - case objectValue: - JSON_ASSERT_MESSAGE( false, "Type is not convertible to int" ); - default: - JSON_ASSERT_UNREACHABLE; - } - return 0; // unreachable; -} - -Value::UInt -Value::asUInt() const -{ - switch ( type_ ) - { - case nullValue: - return 0; - case intValue: - JSON_ASSERT_MESSAGE( value_.int_ >= 0, "Negative integer can not be converted to unsigned integer" ); - return value_.int_; - case uintValue: - return value_.uint_; - case realValue: - JSON_ASSERT_MESSAGE( value_.real_ >= 0 && value_.real_ <= maxUInt, "Real out of unsigned integer range" ); - return UInt( value_.real_ ); - case booleanValue: - return value_.bool_ ? 1 : 0; - case stringValue: - case arrayValue: - case objectValue: - JSON_ASSERT_MESSAGE( false, "Type is not convertible to uint" ); - default: - JSON_ASSERT_UNREACHABLE; - } - return 0; // unreachable; -} - -double -Value::asDouble() const -{ - switch ( type_ ) - { - case nullValue: - return 0.0; - case intValue: - return value_.int_; - case uintValue: - return value_.uint_; - case realValue: - return value_.real_; - case booleanValue: - return value_.bool_ ? 1.0 : 0.0; - case stringValue: - case arrayValue: - case objectValue: - JSON_ASSERT_MESSAGE( false, "Type is not convertible to double" ); - default: - JSON_ASSERT_UNREACHABLE; - } - return 0; // unreachable; -} - -bool -Value::asBool() const -{ - switch ( type_ ) - { - case nullValue: - return false; - case intValue: - case uintValue: - return value_.int_ != 0; - case realValue: - return value_.real_ != 0.0; - case booleanValue: - return value_.bool_; - case stringValue: - return value_.string_ && value_.string_[0] != 0; - case arrayValue: - case objectValue: - return value_.map_->size() != 0; - default: - JSON_ASSERT_UNREACHABLE; - } - return false; // unreachable; -} - - -bool -Value::isConvertibleTo( ValueType other ) const -{ - switch ( type_ ) - { - case nullValue: - return true; - case intValue: - return ( other == nullValue && value_.int_ == 0 ) - || other == intValue - || ( other == uintValue && value_.int_ >= 0 ) - || other == realValue - || other == stringValue - || other == booleanValue; - case uintValue: - return ( other == nullValue && value_.uint_ == 0 ) - || ( other == intValue && value_.uint_ <= (unsigned)maxInt ) - || other == uintValue - || other == realValue - || other == stringValue - || other == booleanValue; - case realValue: - return ( other == nullValue && value_.real_ == 0.0 ) - || ( other == intValue && value_.real_ >= minInt && value_.real_ <= maxInt ) - || ( other == uintValue && value_.real_ >= 0 && value_.real_ <= maxUInt ) - || other == realValue - || other == stringValue - || other == booleanValue; - case booleanValue: - return ( other == nullValue && value_.bool_ == false ) - || other == intValue - || other == uintValue - || other == realValue - || other == stringValue - || other == booleanValue; - case stringValue: - return other == stringValue - || ( other == nullValue && (!value_.string_ || value_.string_[0] == 0) ); - case arrayValue: - return other == arrayValue - || ( other == nullValue && value_.map_->size() == 0 ); - case objectValue: - return other == objectValue - || ( other == nullValue && value_.map_->size() == 0 ); - default: - JSON_ASSERT_UNREACHABLE; - } - return false; // unreachable; -} - - -/// Number of values in array or object -Value::UInt -Value::size() const -{ - switch ( type_ ) - { - case nullValue: - case intValue: - case uintValue: - case realValue: - case booleanValue: - case stringValue: - return 0; -#ifndef JSON_VALUE_USE_INTERNAL_MAP - case arrayValue: // size of the array is highest index + 1 - if ( !value_.map_->empty() ) - { - ObjectValues::const_iterator itLast = value_.map_->end(); - --itLast; - return (*itLast).first.index()+1; - } - return 0; - case objectValue: - return Int( value_.map_->size() ); -#else - case arrayValue: - return Int( value_.array_->size() ); - case objectValue: - return Int( value_.map_->size() ); -#endif - default: - JSON_ASSERT_UNREACHABLE; - } - return 0; // unreachable; -} - - -bool -Value::empty() const -{ - if ( isNull() || isArray() || isObject() ) - return size() == 0u; - else - return false; -} - - -bool -Value::operator!() const -{ - return isNull(); -} - - -void -Value::clear() -{ - JSON_ASSERT( type_ == nullValue || type_ == arrayValue || type_ == objectValue ); - - switch ( type_ ) - { -#ifndef JSON_VALUE_USE_INTERNAL_MAP - case arrayValue: - case objectValue: - value_.map_->clear(); - break; -#else - case arrayValue: - value_.array_->clear(); - break; - case objectValue: - value_.map_->clear(); - break; -#endif - default: - break; - } -} - -void -Value::resize( UInt newSize ) -{ - JSON_ASSERT( type_ == nullValue || type_ == arrayValue ); - if ( type_ == nullValue ) - *this = Value( arrayValue ); -#ifndef JSON_VALUE_USE_INTERNAL_MAP - UInt oldSize = size(); - if ( newSize == 0 ) - clear(); - else if ( newSize > oldSize ) - (*this)[ newSize - 1 ]; - else - { - for ( UInt index = newSize; index < oldSize; ++index ) - value_.map_->erase( index ); - assert( size() == newSize ); - } -#else - value_.array_->resize( newSize ); -#endif -} - - -Value & -Value::operator[]( UInt index ) -{ - JSON_ASSERT( type_ == nullValue || type_ == arrayValue ); - if ( type_ == nullValue ) - *this = Value( arrayValue ); -#ifndef JSON_VALUE_USE_INTERNAL_MAP - CZString key( index ); - ObjectValues::iterator it = value_.map_->lower_bound( key ); - if ( it != value_.map_->end() && (*it).first == key ) - return (*it).second; - - ObjectValues::value_type defaultValue( key, null ); - it = value_.map_->insert( it, defaultValue ); - return (*it).second; -#else - return value_.array_->resolveReference( index ); -#endif -} - - -const Value & -Value::operator[]( UInt index ) const -{ - JSON_ASSERT( type_ == nullValue || type_ == arrayValue ); - if ( type_ == nullValue ) - return null; -#ifndef JSON_VALUE_USE_INTERNAL_MAP - CZString key( index ); - ObjectValues::const_iterator it = value_.map_->find( key ); - if ( it == value_.map_->end() ) - return null; - return (*it).second; -#else - Value *value = value_.array_->find( index ); - return value ? *value : null; -#endif -} - - -Value & -Value::operator[]( const char *key ) -{ - return resolveReference( key, false ); -} - - -Value & -Value::resolveReference( const char *key, - bool isStatic ) -{ - JSON_ASSERT( type_ == nullValue || type_ == objectValue ); - if ( type_ == nullValue ) - *this = Value( objectValue ); -#ifndef JSON_VALUE_USE_INTERNAL_MAP - CZString actualKey( key, isStatic ? CZString::noDuplication - : CZString::duplicateOnCopy ); - ObjectValues::iterator it = value_.map_->lower_bound( actualKey ); - if ( it != value_.map_->end() && (*it).first == actualKey ) - return (*it).second; - - ObjectValues::value_type defaultValue( actualKey, null ); - it = value_.map_->insert( it, defaultValue ); - Value &value = (*it).second; - return value; -#else - return value_.map_->resolveReference( key, isStatic ); -#endif -} - - -Value -Value::get( UInt index, - const Value &defaultValue ) const -{ - const Value *value = &((*this)[index]); - return value == &null ? defaultValue : *value; -} - - -bool -Value::isValidIndex( UInt index ) const -{ - return index < size(); -} - - - -const Value & -Value::operator[]( const char *key ) const -{ - JSON_ASSERT( type_ == nullValue || type_ == objectValue ); - if ( type_ == nullValue ) - return null; -#ifndef JSON_VALUE_USE_INTERNAL_MAP - CZString actualKey( key, CZString::noDuplication ); - ObjectValues::const_iterator it = value_.map_->find( actualKey ); - if ( it == value_.map_->end() ) - return null; - return (*it).second; -#else - const Value *value = value_.map_->find( key ); - return value ? *value : null; -#endif -} - - -Value & -Value::operator[]( const std::string &key ) -{ - return (*this)[ key.c_str() ]; -} - - -const Value & -Value::operator[]( const std::string &key ) const -{ - return (*this)[ key.c_str() ]; -} - -Value & -Value::operator[]( const StaticString &key ) -{ - return resolveReference( key, true ); -} - - -# ifdef JSON_USE_CPPTL -Value & -Value::operator[]( const CppTL::ConstString &key ) -{ - return (*this)[ key.c_str() ]; -} - - -const Value & -Value::operator[]( const CppTL::ConstString &key ) const -{ - return (*this)[ key.c_str() ]; -} -# endif - - -Value & -Value::append( const Value &value ) -{ - return (*this)[size()] = value; -} - - -Value -Value::get( const char *key, - const Value &defaultValue ) const -{ - const Value *value = &((*this)[key]); - return value == &null ? defaultValue : *value; -} - - -Value -Value::get( const std::string &key, - const Value &defaultValue ) const -{ - return get( key.c_str(), defaultValue ); -} - -Value -Value::removeMember( const char* key ) -{ - JSON_ASSERT( type_ == nullValue || type_ == objectValue ); - if ( type_ == nullValue ) - return null; -#ifndef JSON_VALUE_USE_INTERNAL_MAP - CZString actualKey( key, CZString::noDuplication ); - ObjectValues::iterator it = value_.map_->find( actualKey ); - if ( it == value_.map_->end() ) - return null; - Value old(it->second); - value_.map_->erase(it); - return old; -#else - Value *value = value_.map_->find( key ); - if (value){ - Value old(*value); - value_.map_.remove( key ); - return old; - } else { - return null; - } -#endif -} - -Value -Value::removeMember( const std::string &key ) -{ - return removeMember( key.c_str() ); -} - -# ifdef JSON_USE_CPPTL -Value -Value::get( const CppTL::ConstString &key, - const Value &defaultValue ) const -{ - return get( key.c_str(), defaultValue ); -} -# endif - -bool -Value::isMember( const char *key ) const -{ - const Value *value = &((*this)[key]); - return value != &null; -} - - -bool -Value::isMember( const std::string &key ) const -{ - return isMember( key.c_str() ); -} - - -# ifdef JSON_USE_CPPTL -bool -Value::isMember( const CppTL::ConstString &key ) const -{ - return isMember( key.c_str() ); -} -#endif - -Value::Members -Value::getMemberNames() const -{ - JSON_ASSERT( type_ == nullValue || type_ == objectValue ); - if ( type_ == nullValue ) - return Value::Members(); - Members members; - members.reserve( value_.map_->size() ); -#ifndef JSON_VALUE_USE_INTERNAL_MAP - ObjectValues::const_iterator it = value_.map_->begin(); - ObjectValues::const_iterator itEnd = value_.map_->end(); - for ( ; it != itEnd; ++it ) - members.push_back( std::string( (*it).first.c_str() ) ); -#else - ValueInternalMap::IteratorState it; - ValueInternalMap::IteratorState itEnd; - value_.map_->makeBeginIterator( it ); - value_.map_->makeEndIterator( itEnd ); - for ( ; !ValueInternalMap::equals( it, itEnd ); ValueInternalMap::increment(it) ) - members.push_back( std::string( ValueInternalMap::key( it ) ) ); -#endif - return members; -} -// -//# ifdef JSON_USE_CPPTL -//EnumMemberNames -//Value::enumMemberNames() const -//{ -// if ( type_ == objectValue ) -// { -// return CppTL::Enum::any( CppTL::Enum::transform( -// CppTL::Enum::keys( *(value_.map_), CppTL::Type() ), -// MemberNamesTransform() ) ); -// } -// return EnumMemberNames(); -//} -// -// -//EnumValues -//Value::enumValues() const -//{ -// if ( type_ == objectValue || type_ == arrayValue ) -// return CppTL::Enum::anyValues( *(value_.map_), -// CppTL::Type() ); -// return EnumValues(); -//} -// -//# endif - - -bool -Value::isNull() const -{ - return type_ == nullValue; -} - - -bool -Value::isBool() const -{ - return type_ == booleanValue; -} - - -bool -Value::isInt() const -{ - return type_ == intValue; -} - - -bool -Value::isUInt() const -{ - return type_ == uintValue; -} - - -bool -Value::isIntegral() const -{ - return type_ == intValue - || type_ == uintValue - || type_ == booleanValue; -} - - -bool -Value::isDouble() const -{ - return type_ == realValue; -} - - -bool -Value::isNumeric() const -{ - return isIntegral() || isDouble(); -} - - -bool -Value::isString() const -{ - return type_ == stringValue; -} - - -bool -Value::isArray() const -{ - return type_ == nullValue || type_ == arrayValue; -} - - -bool -Value::isObject() const -{ - return type_ == nullValue || type_ == objectValue; -} - - -void -Value::setComment( const char *comment, - CommentPlacement placement ) -{ - if ( !comments_ ) - comments_ = new CommentInfo[numberOfCommentPlacement]; - comments_[placement].setComment( comment ); -} - - -void -Value::setComment( const std::string &comment, - CommentPlacement placement ) -{ - setComment( comment.c_str(), placement ); -} - - -bool -Value::hasComment( CommentPlacement placement ) const -{ - return comments_ != 0 && comments_[placement].comment_ != 0; -} - -std::string -Value::getComment( CommentPlacement placement ) const -{ - if ( hasComment(placement) ) - return comments_[placement].comment_; - return ""; -} - - -std::string -Value::toStyledString() const -{ - StyledWriter writer; - return writer.write( *this ); -} - - -Value::const_iterator -Value::begin() const -{ - switch ( type_ ) - { -#ifdef JSON_VALUE_USE_INTERNAL_MAP - case arrayValue: - if ( value_.array_ ) - { - ValueInternalArray::IteratorState it; - value_.array_->makeBeginIterator( it ); - return const_iterator( it ); - } - break; - case objectValue: - if ( value_.map_ ) - { - ValueInternalMap::IteratorState it; - value_.map_->makeBeginIterator( it ); - return const_iterator( it ); - } - break; -#else - case arrayValue: - case objectValue: - if ( value_.map_ ) - return const_iterator( value_.map_->begin() ); - break; -#endif - default: - break; - } - return const_iterator(); -} - -Value::const_iterator -Value::end() const -{ - switch ( type_ ) - { -#ifdef JSON_VALUE_USE_INTERNAL_MAP - case arrayValue: - if ( value_.array_ ) - { - ValueInternalArray::IteratorState it; - value_.array_->makeEndIterator( it ); - return const_iterator( it ); - } - break; - case objectValue: - if ( value_.map_ ) - { - ValueInternalMap::IteratorState it; - value_.map_->makeEndIterator( it ); - return const_iterator( it ); - } - break; -#else - case arrayValue: - case objectValue: - if ( value_.map_ ) - return const_iterator( value_.map_->end() ); - break; -#endif - default: - break; - } - return const_iterator(); -} - - -Value::iterator -Value::begin() -{ - switch ( type_ ) - { -#ifdef JSON_VALUE_USE_INTERNAL_MAP - case arrayValue: - if ( value_.array_ ) - { - ValueInternalArray::IteratorState it; - value_.array_->makeBeginIterator( it ); - return iterator( it ); - } - break; - case objectValue: - if ( value_.map_ ) - { - ValueInternalMap::IteratorState it; - value_.map_->makeBeginIterator( it ); - return iterator( it ); - } - break; -#else - case arrayValue: - case objectValue: - if ( value_.map_ ) - return iterator( value_.map_->begin() ); - break; -#endif - default: - break; - } - return iterator(); -} - -Value::iterator -Value::end() -{ - switch ( type_ ) - { -#ifdef JSON_VALUE_USE_INTERNAL_MAP - case arrayValue: - if ( value_.array_ ) - { - ValueInternalArray::IteratorState it; - value_.array_->makeEndIterator( it ); - return iterator( it ); - } - break; - case objectValue: - if ( value_.map_ ) - { - ValueInternalMap::IteratorState it; - value_.map_->makeEndIterator( it ); - return iterator( it ); - } - break; -#else - case arrayValue: - case objectValue: - if ( value_.map_ ) - return iterator( value_.map_->end() ); - break; -#endif - default: - break; - } - return iterator(); -} - - -// class PathArgument -// ////////////////////////////////////////////////////////////////// - -PathArgument::PathArgument() - : kind_( kindNone ) -{ -} - - -PathArgument::PathArgument( Value::UInt index ) - : index_( index ) - , kind_( kindIndex ) -{ -} - - -PathArgument::PathArgument( const char *key ) - : key_( key ) - , kind_( kindKey ) -{ -} - - -PathArgument::PathArgument( const std::string &key ) - : key_( key.c_str() ) - , kind_( kindKey ) -{ -} - -// class Path -// ////////////////////////////////////////////////////////////////// - -Path::Path( const std::string &path, - const PathArgument &a1, - const PathArgument &a2, - const PathArgument &a3, - const PathArgument &a4, - const PathArgument &a5 ) -{ - InArgs in; - in.push_back( &a1 ); - in.push_back( &a2 ); - in.push_back( &a3 ); - in.push_back( &a4 ); - in.push_back( &a5 ); - makePath( path, in ); -} - - -void -Path::makePath( const std::string &path, - const InArgs &in ) -{ - const char *current = path.c_str(); - const char *end = current + path.length(); - InArgs::const_iterator itInArg = in.begin(); - while ( current != end ) - { - if ( *current == '[' ) - { - ++current; - if ( *current == '%' ) - addPathInArg( path, in, itInArg, PathArgument::kindIndex ); - else - { - Value::UInt index = 0; - for ( ; current != end && *current >= '0' && *current <= '9'; ++current ) - index = index * 10 + Value::UInt(*current - '0'); - args_.push_back( index ); - } - if ( current == end || *current++ != ']' ) - invalidPath( path, int(current - path.c_str()) ); - } - else if ( *current == '%' ) - { - addPathInArg( path, in, itInArg, PathArgument::kindKey ); - ++current; - } - else if ( *current == '.' ) - { - ++current; - } - else - { - const char *beginName = current; - while ( current != end && !strchr( "[.", *current ) ) - ++current; - args_.push_back( std::string( beginName, current ) ); - } - } -} - - -void -Path::addPathInArg( const std::string &path, - const InArgs &in, - InArgs::const_iterator &itInArg, - PathArgument::Kind kind ) -{ - if ( itInArg == in.end() ) - { - // Error: missing argument %d - } - else if ( (*itInArg)->kind_ != kind ) - { - // Error: bad argument type - } - else - { - args_.push_back( **itInArg ); - } -} - - -void -Path::invalidPath( const std::string &path, - int location ) -{ - // Error: invalid path. -} - - -const Value & -Path::resolve( const Value &root ) const -{ - const Value *node = &root; - for ( Args::const_iterator it = args_.begin(); it != args_.end(); ++it ) - { - const PathArgument &arg = *it; - if ( arg.kind_ == PathArgument::kindIndex ) - { - if ( !node->isArray() || node->isValidIndex( arg.index_ ) ) - { - // Error: unable to resolve path (array value expected at position... - } - node = &((*node)[arg.index_]); - } - else if ( arg.kind_ == PathArgument::kindKey ) - { - if ( !node->isObject() ) - { - // Error: unable to resolve path (object value expected at position...) - } - node = &((*node)[arg.key_]); - if ( node == &Value::null ) - { - // Error: unable to resolve path (object has no member named '' at position...) - } - } - } - return *node; -} - - -Value -Path::resolve( const Value &root, - const Value &defaultValue ) const -{ - const Value *node = &root; - for ( Args::const_iterator it = args_.begin(); it != args_.end(); ++it ) - { - const PathArgument &arg = *it; - if ( arg.kind_ == PathArgument::kindIndex ) - { - if ( !node->isArray() || node->isValidIndex( arg.index_ ) ) - return defaultValue; - node = &((*node)[arg.index_]); - } - else if ( arg.kind_ == PathArgument::kindKey ) - { - if ( !node->isObject() ) - return defaultValue; - node = &((*node)[arg.key_]); - if ( node == &Value::null ) - return defaultValue; - } - } - return *node; -} - - -Value & -Path::make( Value &root ) const -{ - Value *node = &root; - for ( Args::const_iterator it = args_.begin(); it != args_.end(); ++it ) - { - const PathArgument &arg = *it; - if ( arg.kind_ == PathArgument::kindIndex ) - { - if ( !node->isArray() ) - { - // Error: node is not an array at position ... - } - node = &((*node)[arg.index_]); - } - else if ( arg.kind_ == PathArgument::kindKey ) - { - if ( !node->isObject() ) - { - // Error: node is not an object at position... - } - node = &((*node)[arg.key_]); - } - } - return *node; -} - - -} // namespace Json diff --git a/thirdparty/jsoncpp/src/lib_json/json_valueiterator.inl b/thirdparty/jsoncpp/src/lib_json/json_valueiterator.inl deleted file mode 100644 index 736e260e..00000000 --- a/thirdparty/jsoncpp/src/lib_json/json_valueiterator.inl +++ /dev/null @@ -1,292 +0,0 @@ -// included by json_value.cpp -// everything is within Json namespace - - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// class ValueIteratorBase -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// - -ValueIteratorBase::ValueIteratorBase() -#ifndef JSON_VALUE_USE_INTERNAL_MAP - : current_() - , isNull_( true ) -{ -} -#else - : isArray_( true ) - , isNull_( true ) -{ - iterator_.array_ = ValueInternalArray::IteratorState(); -} -#endif - - -#ifndef JSON_VALUE_USE_INTERNAL_MAP -ValueIteratorBase::ValueIteratorBase( const Value::ObjectValues::iterator ¤t ) - : current_( current ) - , isNull_( false ) -{ -} -#else -ValueIteratorBase::ValueIteratorBase( const ValueInternalArray::IteratorState &state ) - : isArray_( true ) -{ - iterator_.array_ = state; -} - - -ValueIteratorBase::ValueIteratorBase( const ValueInternalMap::IteratorState &state ) - : isArray_( false ) -{ - iterator_.map_ = state; -} -#endif - -Value & -ValueIteratorBase::deref() const -{ -#ifndef JSON_VALUE_USE_INTERNAL_MAP - return current_->second; -#else - if ( isArray_ ) - return ValueInternalArray::dereference( iterator_.array_ ); - return ValueInternalMap::value( iterator_.map_ ); -#endif -} - - -void -ValueIteratorBase::increment() -{ -#ifndef JSON_VALUE_USE_INTERNAL_MAP - ++current_; -#else - if ( isArray_ ) - ValueInternalArray::increment( iterator_.array_ ); - ValueInternalMap::increment( iterator_.map_ ); -#endif -} - - -void -ValueIteratorBase::decrement() -{ -#ifndef JSON_VALUE_USE_INTERNAL_MAP - --current_; -#else - if ( isArray_ ) - ValueInternalArray::decrement( iterator_.array_ ); - ValueInternalMap::decrement( iterator_.map_ ); -#endif -} - - -ValueIteratorBase::difference_type -ValueIteratorBase::computeDistance( const SelfType &other ) const -{ -#ifndef JSON_VALUE_USE_INTERNAL_MAP -# ifdef JSON_USE_CPPTL_SMALLMAP - return current_ - other.current_; -# else - // Iterator for null value are initialized using the default - // constructor, which initialize current_ to the default - // std::map::iterator. As begin() and end() are two instance - // of the default std::map::iterator, they can not be compared. - // To allow this, we handle this comparison specifically. - if ( isNull_ && other.isNull_ ) - { - return 0; - } - - - // Usage of std::distance is not portable (does not compile with Sun Studio 12 RogueWave STL, - // which is the one used by default). - // Using a portable hand-made version for non random iterator instead: - // return difference_type( std::distance( current_, other.current_ ) ); - difference_type myDistance = 0; - for ( Value::ObjectValues::iterator it = current_; it != other.current_; ++it ) - { - ++myDistance; - } - return myDistance; -# endif -#else - if ( isArray_ ) - return ValueInternalArray::distance( iterator_.array_, other.iterator_.array_ ); - return ValueInternalMap::distance( iterator_.map_, other.iterator_.map_ ); -#endif -} - - -bool -ValueIteratorBase::isEqual( const SelfType &other ) const -{ -#ifndef JSON_VALUE_USE_INTERNAL_MAP - if ( isNull_ ) - { - return other.isNull_; - } - return current_ == other.current_; -#else - if ( isArray_ ) - return ValueInternalArray::equals( iterator_.array_, other.iterator_.array_ ); - return ValueInternalMap::equals( iterator_.map_, other.iterator_.map_ ); -#endif -} - - -void -ValueIteratorBase::copy( const SelfType &other ) -{ -#ifndef JSON_VALUE_USE_INTERNAL_MAP - current_ = other.current_; -#else - if ( isArray_ ) - iterator_.array_ = other.iterator_.array_; - iterator_.map_ = other.iterator_.map_; -#endif -} - - -Value -ValueIteratorBase::key() const -{ -#ifndef JSON_VALUE_USE_INTERNAL_MAP - const Value::CZString czstring = (*current_).first; - if ( czstring.c_str() ) - { - if ( czstring.isStaticString() ) - return Value( StaticString( czstring.c_str() ) ); - return Value( czstring.c_str() ); - } - return Value( czstring.index() ); -#else - if ( isArray_ ) - return Value( ValueInternalArray::indexOf( iterator_.array_ ) ); - bool isStatic; - const char *memberName = ValueInternalMap::key( iterator_.map_, isStatic ); - if ( isStatic ) - return Value( StaticString( memberName ) ); - return Value( memberName ); -#endif -} - - -UInt -ValueIteratorBase::index() const -{ -#ifndef JSON_VALUE_USE_INTERNAL_MAP - const Value::CZString czstring = (*current_).first; - if ( !czstring.c_str() ) - return czstring.index(); - return Value::UInt( -1 ); -#else - if ( isArray_ ) - return Value::UInt( ValueInternalArray::indexOf( iterator_.array_ ) ); - return Value::UInt( -1 ); -#endif -} - - -const char * -ValueIteratorBase::memberName() const -{ -#ifndef JSON_VALUE_USE_INTERNAL_MAP - const char *name = (*current_).first.c_str(); - return name ? name : ""; -#else - if ( !isArray_ ) - return ValueInternalMap::key( iterator_.map_ ); - return ""; -#endif -} - - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// class ValueConstIterator -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// - -ValueConstIterator::ValueConstIterator() -{ -} - - -#ifndef JSON_VALUE_USE_INTERNAL_MAP -ValueConstIterator::ValueConstIterator( const Value::ObjectValues::iterator ¤t ) - : ValueIteratorBase( current ) -{ -} -#else -ValueConstIterator::ValueConstIterator( const ValueInternalArray::IteratorState &state ) - : ValueIteratorBase( state ) -{ -} - -ValueConstIterator::ValueConstIterator( const ValueInternalMap::IteratorState &state ) - : ValueIteratorBase( state ) -{ -} -#endif - -ValueConstIterator & -ValueConstIterator::operator =( const ValueIteratorBase &other ) -{ - copy( other ); - return *this; -} - - -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// class ValueIterator -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////// - -ValueIterator::ValueIterator() -{ -} - - -#ifndef JSON_VALUE_USE_INTERNAL_MAP -ValueIterator::ValueIterator( const Value::ObjectValues::iterator ¤t ) - : ValueIteratorBase( current ) -{ -} -#else -ValueIterator::ValueIterator( const ValueInternalArray::IteratorState &state ) - : ValueIteratorBase( state ) -{ -} - -ValueIterator::ValueIterator( const ValueInternalMap::IteratorState &state ) - : ValueIteratorBase( state ) -{ -} -#endif - -ValueIterator::ValueIterator( const ValueConstIterator &other ) - : ValueIteratorBase( other ) -{ -} - -ValueIterator::ValueIterator( const ValueIterator &other ) - : ValueIteratorBase( other ) -{ -} - -ValueIterator & -ValueIterator::operator =( const SelfType &other ) -{ - copy( other ); - return *this; -} diff --git a/thirdparty/jsoncpp/src/lib_json/json_writer.cpp b/thirdparty/jsoncpp/src/lib_json/json_writer.cpp deleted file mode 100644 index e96a5e98..00000000 --- a/thirdparty/jsoncpp/src/lib_json/json_writer.cpp +++ /dev/null @@ -1,828 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#if _MSC_VER >= 1400 // VC++ 8.0 -#pragma warning( disable : 4996 ) // disable warning about strdup being deprecated. -#endif - -namespace Json { - -static bool isControlCharacter(char ch) -{ - return ch > 0 && ch <= 0x1F; -} - -static bool containsControlCharacter( const char* str ) -{ - while ( *str ) - { - if ( isControlCharacter( *(str++) ) ) - return true; - } - return false; -} -static void uintToString( unsigned int value, - char *¤t ) -{ - *--current = 0; - do - { - *--current = (value % 10) + '0'; - value /= 10; - } - while ( value != 0 ); -} - -std::string valueToString( Int value ) -{ - char buffer[32]; - char *current = buffer + sizeof(buffer); - bool isNegative = value < 0; - if ( isNegative ) - value = -value; - uintToString( UInt(value), current ); - if ( isNegative ) - *--current = '-'; - assert( current >= buffer ); - return current; -} - - -std::string valueToString( UInt value ) -{ - char buffer[32]; - char *current = buffer + sizeof(buffer); - uintToString( value, current ); - assert( current >= buffer ); - return current; -} - -std::string valueToString( double value ) -{ - char buffer[32]; -#if defined(_MSC_VER) && defined(__STDC_SECURE_LIB__) // Use secure version with visual studio 2005 to avoid warning. - sprintf_s(buffer, sizeof(buffer), "%#.16g", value); -#else - sprintf(buffer, "%#.16g", value); -#endif - char* ch = buffer + strlen(buffer) - 1; - if (*ch != '0') return buffer; // nothing to truncate, so save time - while(ch > buffer && *ch == '0'){ - --ch; - } - char* last_nonzero = ch; - while(ch >= buffer){ - switch(*ch){ - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - --ch; - continue; - case '.': - // Truncate zeroes to save bytes in output, but keep one. - *(last_nonzero+2) = '\0'; - return buffer; - default: - return buffer; - } - } - return buffer; -} - - -std::string valueToString( bool value ) -{ - return value ? "true" : "false"; -} - -std::string valueToQuotedString( const char *value ) -{ - // Not sure how to handle unicode... - if (strpbrk(value, "\"\\\b\f\n\r\t") == NULL && !containsControlCharacter( value )) - return std::string("\"") + value + "\""; - // We have to walk value and escape any special characters. - // Appending to std::string is not efficient, but this should be rare. - // (Note: forward slashes are *not* rare, but I am not escaping them.) - unsigned maxsize = strlen(value)*2 + 3; // allescaped+quotes+NULL - std::string result; - result.reserve(maxsize); // to avoid lots of mallocs - result += "\""; - for (const char* c=value; *c != 0; ++c) - { - switch(*c) - { - case '\"': - result += "\\\""; - break; - case '\\': - result += "\\\\"; - break; - case '\b': - result += "\\b"; - break; - case '\f': - result += "\\f"; - break; - case '\n': - result += "\\n"; - break; - case '\r': - result += "\\r"; - break; - case '\t': - result += "\\t"; - break; - //case '/': - // Even though \/ is considered a legal escape in JSON, a bare - // slash is also legal, so I see no reason to escape it. - // (I hope I am not misunderstanding something. - // blep notes: actually escaping \/ may be useful in javascript to avoid (*c); - result += oss.str(); - } - else - { - result += *c; - } - break; - } - } - result += "\""; - return result; -} - -// Class Writer -// ////////////////////////////////////////////////////////////////// -Writer::~Writer() -{ -} - - -// Class FastWriter -// ////////////////////////////////////////////////////////////////// - -FastWriter::FastWriter() - : yamlCompatiblityEnabled_( false ) -{ -} - - -void -FastWriter::enableYAMLCompatibility() -{ - yamlCompatiblityEnabled_ = true; -} - - -std::string -FastWriter::write( const Value &root ) -{ - document_ = ""; - writeValue( root ); - document_ += "\n"; - return document_; -} - - -void -FastWriter::writeValue( const Value &value ) -{ - switch ( value.type() ) - { - case nullValue: - document_ += "null"; - break; - case intValue: - document_ += valueToString( value.asInt() ); - break; - case uintValue: - document_ += valueToString( value.asUInt() ); - break; - case realValue: - document_ += valueToString( value.asDouble() ); - break; - case stringValue: - document_ += valueToQuotedString( value.asCString() ); - break; - case booleanValue: - document_ += valueToString( value.asBool() ); - break; - case arrayValue: - { - document_ += "["; - int size = value.size(); - for ( int index =0; index < size; ++index ) - { - if ( index > 0 ) - document_ += ","; - writeValue( value[index] ); - } - document_ += "]"; - } - break; - case objectValue: - { - Value::Members members( value.getMemberNames() ); - document_ += "{"; - for ( Value::Members::iterator it = members.begin(); - it != members.end(); - ++it ) - { - const std::string &name = *it; - if ( it != members.begin() ) - document_ += ","; - document_ += valueToQuotedString( name.c_str() ); - document_ += yamlCompatiblityEnabled_ ? ": " - : ":"; - writeValue( value[name] ); - } - document_ += "}"; - } - break; - } -} - - -// Class StyledWriter -// ////////////////////////////////////////////////////////////////// - -StyledWriter::StyledWriter() - : rightMargin_( 74 ) - , indentSize_( 3 ) -{ -} - - -std::string -StyledWriter::write( const Value &root ) -{ - document_ = ""; - addChildValues_ = false; - indentString_ = ""; - writeCommentBeforeValue( root ); - writeValue( root ); - writeCommentAfterValueOnSameLine( root ); - document_ += "\n"; - return document_; -} - - -void -StyledWriter::writeValue( const Value &value ) -{ - switch ( value.type() ) - { - case nullValue: - pushValue( "null" ); - break; - case intValue: - pushValue( valueToString( value.asInt() ) ); - break; - case uintValue: - pushValue( valueToString( value.asUInt() ) ); - break; - case realValue: - pushValue( valueToString( value.asDouble() ) ); - break; - case stringValue: - pushValue( valueToQuotedString( value.asCString() ) ); - break; - case booleanValue: - pushValue( valueToString( value.asBool() ) ); - break; - case arrayValue: - writeArrayValue( value); - break; - case objectValue: - { - Value::Members members( value.getMemberNames() ); - if ( members.empty() ) - pushValue( "{}" ); - else - { - writeWithIndent( "{" ); - indent(); - Value::Members::iterator it = members.begin(); - while ( true ) - { - const std::string &name = *it; - const Value &childValue = value[name]; - writeCommentBeforeValue( childValue ); - writeWithIndent( valueToQuotedString( name.c_str() ) ); - document_ += " : "; - writeValue( childValue ); - if ( ++it == members.end() ) - { - writeCommentAfterValueOnSameLine( childValue ); - break; - } - document_ += ","; - writeCommentAfterValueOnSameLine( childValue ); - } - unindent(); - writeWithIndent( "}" ); - } - } - break; - } -} - - -void -StyledWriter::writeArrayValue( const Value &value ) -{ - unsigned size = value.size(); - if ( size == 0 ) - pushValue( "[]" ); - else - { - bool isArrayMultiLine = isMultineArray( value ); - if ( isArrayMultiLine ) - { - writeWithIndent( "[" ); - indent(); - bool hasChildValue = !childValues_.empty(); - unsigned index =0; - while ( true ) - { - const Value &childValue = value[index]; - writeCommentBeforeValue( childValue ); - if ( hasChildValue ) - writeWithIndent( childValues_[index] ); - else - { - writeIndent(); - writeValue( childValue ); - } - if ( ++index == size ) - { - writeCommentAfterValueOnSameLine( childValue ); - break; - } - document_ += ","; - writeCommentAfterValueOnSameLine( childValue ); - } - unindent(); - writeWithIndent( "]" ); - } - else // output on a single line - { - assert( childValues_.size() == size ); - document_ += "[ "; - for ( unsigned index =0; index < size; ++index ) - { - if ( index > 0 ) - document_ += ", "; - document_ += childValues_[index]; - } - document_ += " ]"; - } - } -} - - -bool -StyledWriter::isMultineArray( const Value &value ) -{ - int size = value.size(); - bool isMultiLine = size*3 >= rightMargin_ ; - childValues_.clear(); - for ( int index =0; index < size && !isMultiLine; ++index ) - { - const Value &childValue = value[index]; - isMultiLine = isMultiLine || - ( (childValue.isArray() || childValue.isObject()) && - childValue.size() > 0 ); - } - if ( !isMultiLine ) // check if line length > max line length - { - childValues_.reserve( size ); - addChildValues_ = true; - int lineLength = 4 + (size-1)*2; // '[ ' + ', '*n + ' ]' - for ( int index =0; index < size && !isMultiLine; ++index ) - { - writeValue( value[index] ); - lineLength += int( childValues_[index].length() ); - isMultiLine = isMultiLine && hasCommentForValue( value[index] ); - } - addChildValues_ = false; - isMultiLine = isMultiLine || lineLength >= rightMargin_; - } - return isMultiLine; -} - - -void -StyledWriter::pushValue( const std::string &value ) -{ - if ( addChildValues_ ) - childValues_.push_back( value ); - else - document_ += value; -} - - -void -StyledWriter::writeIndent() -{ - if ( !document_.empty() ) - { - char last = document_[document_.length()-1]; - if ( last == ' ' ) // already indented - return; - if ( last != '\n' ) // Comments may add new-line - document_ += '\n'; - } - document_ += indentString_; -} - - -void -StyledWriter::writeWithIndent( const std::string &value ) -{ - writeIndent(); - document_ += value; -} - - -void -StyledWriter::indent() -{ - indentString_ += std::string( indentSize_, ' ' ); -} - - -void -StyledWriter::unindent() -{ - assert( int(indentString_.size()) >= indentSize_ ); - indentString_.resize( indentString_.size() - indentSize_ ); -} - - -void -StyledWriter::writeCommentBeforeValue( const Value &root ) -{ - if ( !root.hasComment( commentBefore ) ) - return; - document_ += normalizeEOL( root.getComment( commentBefore ) ); - document_ += "\n"; -} - - -void -StyledWriter::writeCommentAfterValueOnSameLine( const Value &root ) -{ - if ( root.hasComment( commentAfterOnSameLine ) ) - document_ += " " + normalizeEOL( root.getComment( commentAfterOnSameLine ) ); - - if ( root.hasComment( commentAfter ) ) - { - document_ += "\n"; - document_ += normalizeEOL( root.getComment( commentAfter ) ); - document_ += "\n"; - } -} - - -bool -StyledWriter::hasCommentForValue( const Value &value ) -{ - return value.hasComment( commentBefore ) - || value.hasComment( commentAfterOnSameLine ) - || value.hasComment( commentAfter ); -} - - -std::string -StyledWriter::normalizeEOL( const std::string &text ) -{ - std::string normalized; - normalized.reserve( text.length() ); - const char *begin = text.c_str(); - const char *end = begin + text.length(); - const char *current = begin; - while ( current != end ) - { - char c = *current++; - if ( c == '\r' ) // mac or dos EOL - { - if ( *current == '\n' ) // convert dos EOL - ++current; - normalized += '\n'; - } - else // handle unix EOL & other char - normalized += c; - } - return normalized; -} - - -// Class StyledStreamWriter -// ////////////////////////////////////////////////////////////////// - -StyledStreamWriter::StyledStreamWriter( std::string indentation ) - : document_(NULL) - , rightMargin_( 74 ) - , indentation_( indentation ) -{ -} - - -void -StyledStreamWriter::write( std::ostream &out, const Value &root ) -{ - document_ = &out; - addChildValues_ = false; - indentString_ = ""; - writeCommentBeforeValue( root ); - writeValue( root ); - writeCommentAfterValueOnSameLine( root ); - *document_ << "\n"; - document_ = NULL; // Forget the stream, for safety. -} - - -void -StyledStreamWriter::writeValue( const Value &value ) -{ - switch ( value.type() ) - { - case nullValue: - pushValue( "null" ); - break; - case intValue: - pushValue( valueToString( value.asInt() ) ); - break; - case uintValue: - pushValue( valueToString( value.asUInt() ) ); - break; - case realValue: - pushValue( valueToString( value.asDouble() ) ); - break; - case stringValue: - pushValue( valueToQuotedString( value.asCString() ) ); - break; - case booleanValue: - pushValue( valueToString( value.asBool() ) ); - break; - case arrayValue: - writeArrayValue( value); - break; - case objectValue: - { - Value::Members members( value.getMemberNames() ); - if ( members.empty() ) - pushValue( "{}" ); - else - { - writeWithIndent( "{" ); - indent(); - Value::Members::iterator it = members.begin(); - while ( true ) - { - const std::string &name = *it; - const Value &childValue = value[name]; - writeCommentBeforeValue( childValue ); - writeWithIndent( valueToQuotedString( name.c_str() ) ); - *document_ << " : "; - writeValue( childValue ); - if ( ++it == members.end() ) - { - writeCommentAfterValueOnSameLine( childValue ); - break; - } - *document_ << ","; - writeCommentAfterValueOnSameLine( childValue ); - } - unindent(); - writeWithIndent( "}" ); - } - } - break; - } -} - - -void -StyledStreamWriter::writeArrayValue( const Value &value ) -{ - unsigned size = value.size(); - if ( size == 0 ) - pushValue( "[]" ); - else - { - bool isArrayMultiLine = isMultineArray( value ); - if ( isArrayMultiLine ) - { - writeWithIndent( "[" ); - indent(); - bool hasChildValue = !childValues_.empty(); - unsigned index =0; - while ( true ) - { - const Value &childValue = value[index]; - writeCommentBeforeValue( childValue ); - if ( hasChildValue ) - writeWithIndent( childValues_[index] ); - else - { - writeIndent(); - writeValue( childValue ); - } - if ( ++index == size ) - { - writeCommentAfterValueOnSameLine( childValue ); - break; - } - *document_ << ","; - writeCommentAfterValueOnSameLine( childValue ); - } - unindent(); - writeWithIndent( "]" ); - } - else // output on a single line - { - assert( childValues_.size() == size ); - *document_ << "[ "; - for ( unsigned index =0; index < size; ++index ) - { - if ( index > 0 ) - *document_ << ", "; - *document_ << childValues_[index]; - } - *document_ << " ]"; - } - } -} - - -bool -StyledStreamWriter::isMultineArray( const Value &value ) -{ - int size = value.size(); - bool isMultiLine = size*3 >= rightMargin_ ; - childValues_.clear(); - for ( int index =0; index < size && !isMultiLine; ++index ) - { - const Value &childValue = value[index]; - isMultiLine = isMultiLine || - ( (childValue.isArray() || childValue.isObject()) && - childValue.size() > 0 ); - } - if ( !isMultiLine ) // check if line length > max line length - { - childValues_.reserve( size ); - addChildValues_ = true; - int lineLength = 4 + (size-1)*2; // '[ ' + ', '*n + ' ]' - for ( int index =0; index < size && !isMultiLine; ++index ) - { - writeValue( value[index] ); - lineLength += int( childValues_[index].length() ); - isMultiLine = isMultiLine && hasCommentForValue( value[index] ); - } - addChildValues_ = false; - isMultiLine = isMultiLine || lineLength >= rightMargin_; - } - return isMultiLine; -} - - -void -StyledStreamWriter::pushValue( const std::string &value ) -{ - if ( addChildValues_ ) - childValues_.push_back( value ); - else - *document_ << value; -} - - -void -StyledStreamWriter::writeIndent() -{ - /* - Some comments in this method would have been nice. ;-) - - if ( !document_.empty() ) - { - char last = document_[document_.length()-1]; - if ( last == ' ' ) // already indented - return; - if ( last != '\n' ) // Comments may add new-line - *document_ << '\n'; - } - */ - *document_ << '\n' << indentString_; -} - - -void -StyledStreamWriter::writeWithIndent( const std::string &value ) -{ - writeIndent(); - *document_ << value; -} - - -void -StyledStreamWriter::indent() -{ - indentString_ += indentation_; -} - - -void -StyledStreamWriter::unindent() -{ - assert( indentString_.size() >= indentation_.size() ); - indentString_.resize( indentString_.size() - indentation_.size() ); -} - - -void -StyledStreamWriter::writeCommentBeforeValue( const Value &root ) -{ - if ( !root.hasComment( commentBefore ) ) - return; - *document_ << normalizeEOL( root.getComment( commentBefore ) ); - *document_ << "\n"; -} - - -void -StyledStreamWriter::writeCommentAfterValueOnSameLine( const Value &root ) -{ - if ( root.hasComment( commentAfterOnSameLine ) ) - *document_ << " " + normalizeEOL( root.getComment( commentAfterOnSameLine ) ); - - if ( root.hasComment( commentAfter ) ) - { - *document_ << "\n"; - *document_ << normalizeEOL( root.getComment( commentAfter ) ); - *document_ << "\n"; - } -} - - -bool -StyledStreamWriter::hasCommentForValue( const Value &value ) -{ - return value.hasComment( commentBefore ) - || value.hasComment( commentAfterOnSameLine ) - || value.hasComment( commentAfter ); -} - - -std::string -StyledStreamWriter::normalizeEOL( const std::string &text ) -{ - std::string normalized; - normalized.reserve( text.length() ); - const char *begin = text.c_str(); - const char *end = begin + text.length(); - const char *current = begin; - while ( current != end ) - { - char c = *current++; - if ( c == '\r' ) // mac or dos EOL - { - if ( *current == '\n' ) // convert dos EOL - ++current; - normalized += '\n'; - } - else // handle unix EOL & other char - normalized += c; - } - return normalized; -} - - -std::ostream& operator<<( std::ostream &sout, const Value &root ) -{ - Json::StyledStreamWriter writer; - writer.write(sout, root); - return sout; -} - - -} // namespace Json diff --git a/thirdparty/jsoncpp/src/lib_json/sconscript b/thirdparty/jsoncpp/src/lib_json/sconscript deleted file mode 100644 index f6520d18..00000000 --- a/thirdparty/jsoncpp/src/lib_json/sconscript +++ /dev/null @@ -1,8 +0,0 @@ -Import( 'env buildLibrary' ) - -buildLibrary( env, Split( """ - json_reader.cpp - json_value.cpp - json_writer.cpp - """ ), - 'json' ) From 744a4f3ec1b3c123e4628c7ec45be2fe430d7e9c Mon Sep 17 00:00:00 2001 From: "FeRD (Frank Dana)" Date: Wed, 19 Jun 2019 21:20:04 -0400 Subject: [PATCH 213/223] Remove Json:Reader `Json::Reader` has been deprecated for some time, so we replace it with `Json::CharReader` generated by a `Json::CharReaderBuilder`, or (in the one instance where we have a stream as input) `Json::parseFromStream();` --- src/CacheDisk.cpp | 16 ++++++++++++---- src/CacheMemory.cpp | 17 +++++++++++++---- src/ChunkReader.cpp | 14 ++++++++++---- src/Clip.cpp | 8 ++++++-- src/Color.cpp | 8 ++++++-- src/Coordinate.cpp | 8 ++++++-- src/DecklinkReader.cpp | 8 ++++++-- src/DummyReader.cpp | 8 ++++++-- src/EffectBase.cpp | 8 ++++++-- src/FFmpegReader.cpp | 8 ++++++-- src/FrameMapper.cpp | 8 ++++++-- src/ImageReader.cpp | 10 +++++++--- src/KeyFrame.cpp | 8 ++++++-- src/Point.cpp | 8 ++++++-- src/Profiles.cpp | 8 ++++++-- src/QtImageReader.cpp | 8 ++++++-- src/TextReader.cpp | 8 ++++++-- src/Timeline.cpp | 16 ++++++++++++---- src/WriterBase.cpp | 8 ++++++-- src/effects/Bars.cpp | 8 ++++++-- src/effects/Blur.cpp | 8 ++++++-- src/effects/Brightness.cpp | 8 ++++++-- src/effects/ChromaKey.cpp | 8 ++++++-- src/effects/ColorShift.cpp | 8 ++++++-- src/effects/Crop.cpp | 9 ++++++--- src/effects/Deinterlace.cpp | 8 ++++++-- src/effects/Hue.cpp | 8 ++++++-- src/effects/Mask.cpp | 8 ++++++-- src/effects/Negate.cpp | 8 ++++++-- src/effects/Pixelate.cpp | 8 ++++++-- src/effects/Saturation.cpp | 8 ++++++-- src/effects/Shift.cpp | 8 ++++++-- src/effects/Wave.cpp | 8 ++++++-- tests/Clip_Tests.cpp | 16 +++++++++++----- 34 files changed, 233 insertions(+), 81 deletions(-) diff --git a/src/CacheDisk.cpp b/src/CacheDisk.cpp index 23854f3a..148dc1db 100644 --- a/src/CacheDisk.cpp +++ b/src/CacheDisk.cpp @@ -486,8 +486,12 @@ Json::Value CacheDisk::JsonValue() { // Parse and append range data (if any) Json::Value ranges; - Json::Reader reader; - bool success = reader.parse( json_ranges, ranges ); + Json::CharReaderBuilder rbuilder; + Json::CharReader* reader(rbuilder.newCharReader()); + + string errors; + bool success = reader->parse( json_ranges.c_str(), + json_ranges.c_str() + json_ranges.size(), &ranges, &errors ); if (success) root["ranges"] = ranges; @@ -500,8 +504,12 @@ void CacheDisk::SetJson(string value) { // Parse JSON string into JSON objects Json::Value root; - Json::Reader reader; - bool success = reader.parse( value, root ); + Json::CharReaderBuilder rbuilder; + Json::CharReader* reader(rbuilder.newCharReader()); + + string errors; + bool success = reader->parse( value.c_str(), + value.c_str() + value.size(), &root, &errors ); if (!success) // Raise exception throw InvalidJSON("JSON could not be parsed (or is invalid)", ""); diff --git a/src/CacheMemory.cpp b/src/CacheMemory.cpp index f830d74e..2cc79e7b 100644 --- a/src/CacheMemory.cpp +++ b/src/CacheMemory.cpp @@ -340,8 +340,13 @@ Json::Value CacheMemory::JsonValue() { // Parse and append range data (if any) Json::Value ranges; - Json::Reader reader; - bool success = reader.parse( json_ranges, ranges ); + Json::CharReaderBuilder rbuilder; + Json::CharReader* reader(rbuilder.newCharReader()); + + string errors; + bool success = reader->parse( json_ranges.c_str(), + json_ranges.c_str() + json_ranges.size(), &ranges, &errors ); + if (success) root["ranges"] = ranges; @@ -354,8 +359,12 @@ void CacheMemory::SetJson(string value) { // Parse JSON string into JSON objects Json::Value root; - Json::Reader reader; - bool success = reader.parse( value, root ); + Json::CharReaderBuilder rbuilder; + Json::CharReader* reader(rbuilder.newCharReader()); + + string errors; + bool success = reader->parse( value.c_str(), + value.c_str() + value.size(), &root, &errors ); if (!success) // Raise exception throw InvalidJSON("JSON could not be parsed (or is invalid)", ""); diff --git a/src/ChunkReader.cpp b/src/ChunkReader.cpp index fe552243..44f887b7 100644 --- a/src/ChunkReader.cpp +++ b/src/ChunkReader.cpp @@ -76,8 +76,10 @@ void ChunkReader::load_json() // Parse JSON string into JSON objects Json::Value root; - Json::Reader reader; - bool success = reader.parse( json_string.str(), root ); + Json::CharReaderBuilder rbuilder; + + string errors; + bool success = Json::parseFromStream(rbuilder, json_string, &root, &errors); if (!success) // Raise exception throw InvalidJSON("Chunk folder could not be opened.", path); @@ -278,8 +280,12 @@ void ChunkReader::SetJson(string value) { // Parse JSON string into JSON objects Json::Value root; - Json::Reader reader; - bool success = reader.parse( value, root ); + Json::CharReaderBuilder rbuilder; + Json::CharReader* reader(rbuilder.newCharReader()); + + string errors; + bool success = reader->parse( value.c_str(), + value.c_str() + value.size(), &root, &errors ); if (!success) // Raise exception throw InvalidJSON("JSON could not be parsed (or is invalid)", ""); diff --git a/src/Clip.cpp b/src/Clip.cpp index 2099705d..0cb88cfe 100644 --- a/src/Clip.cpp +++ b/src/Clip.cpp @@ -786,8 +786,12 @@ void Clip::SetJson(string value) { // Parse JSON string into JSON objects Json::Value root; - Json::Reader reader; - bool success = reader.parse( value, root ); + Json::CharReaderBuilder rbuilder; + Json::CharReader* reader(rbuilder.newCharReader()); + + string errors; + bool success = reader->parse( value.c_str(), + value.c_str() + value.size(), &root, &errors ); if (!success) // Raise exception throw InvalidJSON("JSON could not be parsed (or is invalid)", ""); diff --git a/src/Color.cpp b/src/Color.cpp index df2cf097..e9bf438a 100644 --- a/src/Color.cpp +++ b/src/Color.cpp @@ -107,8 +107,12 @@ void Color::SetJson(string value) { // Parse JSON string into JSON objects Json::Value root; - Json::Reader reader; - bool success = reader.parse( value, root ); + Json::CharReaderBuilder rbuilder; + Json::CharReader* reader(rbuilder.newCharReader()); + + string errors; + bool success = reader->parse( value.c_str(), + value.c_str() + value.size(), &root, &errors ); if (!success) // Raise exception throw InvalidJSON("JSON could not be parsed (or is invalid)", ""); diff --git a/src/Coordinate.cpp b/src/Coordinate.cpp index 60ea90b2..835cbbf0 100644 --- a/src/Coordinate.cpp +++ b/src/Coordinate.cpp @@ -70,8 +70,12 @@ void Coordinate::SetJson(string value) { // Parse JSON string into JSON objects Json::Value root; - Json::Reader reader; - bool success = reader.parse( value, root ); + Json::CharReaderBuilder rbuilder; + Json::CharReader* reader(rbuilder.newCharReader()); + + string errors; + bool success = reader->parse( value.c_str(), + value.c_str() + value.size(), &root, &errors ); if (!success) // Raise exception throw InvalidJSON("JSON could not be parsed (or is invalid)", ""); diff --git a/src/DecklinkReader.cpp b/src/DecklinkReader.cpp index 1d9b70b9..7e491424 100644 --- a/src/DecklinkReader.cpp +++ b/src/DecklinkReader.cpp @@ -265,8 +265,12 @@ void DecklinkReader::SetJson(string value) { // Parse JSON string into JSON objects Json::Value root; - Json::Reader reader; - bool success = reader.parse( value, root ); + Json::CharReaderBuilder rbuilder; + Json::CharReader* reader(rbuilder.newCharReader()); + + string errors; + bool success = reader->parse( value.c_str(), + value.c_str() + value.size(), &root, &errors ); if (!success) // Raise exception throw InvalidJSON("JSON could not be parsed (or is invalid)", ""); diff --git a/src/DummyReader.cpp b/src/DummyReader.cpp index dc77db5b..bb079abb 100644 --- a/src/DummyReader.cpp +++ b/src/DummyReader.cpp @@ -143,8 +143,12 @@ void DummyReader::SetJson(string value) { // Parse JSON string into JSON objects Json::Value root; - Json::Reader reader; - bool success = reader.parse( value, root ); + Json::CharReaderBuilder rbuilder; + Json::CharReader* reader(rbuilder.newCharReader()); + + string errors; + bool success = reader->parse( value.c_str(), + value.c_str() + value.size(), &root, &errors ); if (!success) // Raise exception throw InvalidJSON("JSON could not be parsed (or is invalid)", ""); diff --git a/src/EffectBase.cpp b/src/EffectBase.cpp index c0afded8..48dd266f 100644 --- a/src/EffectBase.cpp +++ b/src/EffectBase.cpp @@ -99,8 +99,12 @@ void EffectBase::SetJson(string value) { // Parse JSON string into JSON objects Json::Value root; - Json::Reader reader; - bool success = reader.parse( value, root ); + Json::CharReaderBuilder rbuilder; + Json::CharReader* reader(rbuilder.newCharReader()); + + string errors; + bool success = reader->parse( value.c_str(), + value.c_str() + value.size(), &root, &errors ); if (!success) // Raise exception throw InvalidJSON("JSON could not be parsed (or is invalid)", ""); diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 6aa0938e..0e28971d 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -2422,8 +2422,12 @@ void FFmpegReader::SetJson(string value) { // Parse JSON string into JSON objects Json::Value root; - Json::Reader reader; - bool success = reader.parse(value, root); + Json::CharReaderBuilder rbuilder; + Json::CharReader* reader(rbuilder.newCharReader()); + + string errors; + bool success = reader->parse(value.c_str(), value.c_str() + value.size(), + &root, &errors); if (!success) // Raise exception throw InvalidJSON("JSON could not be parsed (or is invalid)", ""); diff --git a/src/FrameMapper.cpp b/src/FrameMapper.cpp index 113171a2..22cf4a97 100644 --- a/src/FrameMapper.cpp +++ b/src/FrameMapper.cpp @@ -694,8 +694,12 @@ void FrameMapper::SetJson(string value) { // Parse JSON string into JSON objects Json::Value root; - Json::Reader reader; - bool success = reader.parse( value, root ); + Json::CharReaderBuilder rbuilder; + Json::CharReader* reader(rbuilder.newCharReader()); + + string errors; + bool success = reader->parse( value.c_str(), + value.c_str() + value.size(), &root, &errors ); if (!success) // Raise exception throw InvalidJSON("JSON could not be parsed (or is invalid)", ""); diff --git a/src/ImageReader.cpp b/src/ImageReader.cpp index f535666a..3c1d334a 100644 --- a/src/ImageReader.cpp +++ b/src/ImageReader.cpp @@ -106,7 +106,7 @@ void ImageReader::Close() { // Mark as "closed" is_open = false; - + // Delete the image image.reset(); } @@ -153,8 +153,12 @@ void ImageReader::SetJson(string value) { // Parse JSON string into JSON objects Json::Value root; - Json::Reader reader; - bool success = reader.parse( value, root ); + Json::CharReaderBuilder rbuilder; + Json::CharReader* reader(rbuilder.newCharReader()); + + string errors; + bool success = reader->parse( value.c_str(), + value.c_str() + value.size(), &root, &errors ); if (!success) // Raise exception throw InvalidJSON("JSON could not be parsed (or is invalid)", ""); diff --git a/src/KeyFrame.cpp b/src/KeyFrame.cpp index 2b0389de..f585333d 100644 --- a/src/KeyFrame.cpp +++ b/src/KeyFrame.cpp @@ -366,8 +366,12 @@ void Keyframe::SetJson(string value) { // Parse JSON string into JSON objects Json::Value root; - Json::Reader reader; - bool success = reader.parse( value, root ); + Json::CharReaderBuilder rbuilder; + Json::CharReader* reader(rbuilder.newCharReader()); + + string errors; + bool success = reader->parse( value.c_str(), + value.c_str() + value.size(), &root, &errors ); if (!success) // Raise exception throw InvalidJSON("JSON could not be parsed (or is invalid)", ""); diff --git a/src/Point.cpp b/src/Point.cpp index e55e6453..e41883bf 100644 --- a/src/Point.cpp +++ b/src/Point.cpp @@ -133,8 +133,12 @@ void Point::SetJson(string value) { // Parse JSON string into JSON objects Json::Value root; - Json::Reader reader; - bool success = reader.parse( value, root ); + Json::CharReaderBuilder rbuilder; + Json::CharReader* reader(rbuilder.newCharReader()); + + string errors; + bool success = reader->parse( value.c_str(), + value.c_str() + value.size(), &root, &errors ); if (!success) // Raise exception throw InvalidJSON("JSON could not be parsed (or is invalid)", ""); diff --git a/src/Profiles.cpp b/src/Profiles.cpp index 390e5765..53a3424b 100644 --- a/src/Profiles.cpp +++ b/src/Profiles.cpp @@ -164,8 +164,12 @@ void Profile::SetJson(string value) { // Parse JSON string into JSON objects Json::Value root; - Json::Reader reader; - bool success = reader.parse( value, root ); + Json::CharReaderBuilder rbuilder; + Json::CharReader* reader(rbuilder.newCharReader()); + + string errors; + bool success = reader->parse( value.c_str(), + value.c_str() + value.size(), &root, &errors ); if (!success) // Raise exception throw InvalidJSON("JSON could not be parsed (or is invalid)", ""); diff --git a/src/QtImageReader.cpp b/src/QtImageReader.cpp index 502ddb9a..7630a693 100644 --- a/src/QtImageReader.cpp +++ b/src/QtImageReader.cpp @@ -286,8 +286,12 @@ void QtImageReader::SetJson(string value) { // Parse JSON string into JSON objects Json::Value root; - Json::Reader reader; - bool success = reader.parse( value, root ); + Json::CharReaderBuilder rbuilder; + Json::CharReader* reader(rbuilder.newCharReader()); + + string errors; + bool success = reader->parse( value.c_str(), + value.c_str() + value.size(), &root, &errors ); if (!success) // Raise exception throw InvalidJSON("JSON could not be parsed (or is invalid)", ""); diff --git a/src/TextReader.cpp b/src/TextReader.cpp index 8234aa5d..a24f26e4 100644 --- a/src/TextReader.cpp +++ b/src/TextReader.cpp @@ -201,8 +201,12 @@ void TextReader::SetJson(string value) { // Parse JSON string into JSON objects Json::Value root; - Json::Reader reader; - bool success = reader.parse( value, root ); + Json::CharReaderBuilder rbuilder; + Json::CharReader* reader(rbuilder.newCharReader()); + + string errors; + bool success = reader->parse( value.c_str(), + value.c_str() + value.size(), &root, &errors ); if (!success) // Raise exception throw InvalidJSON("JSON could not be parsed (or is invalid)", ""); diff --git a/src/Timeline.cpp b/src/Timeline.cpp index 37d3f71c..1758479b 100644 --- a/src/Timeline.cpp +++ b/src/Timeline.cpp @@ -990,8 +990,12 @@ void Timeline::SetJson(string value) { // Parse JSON string into JSON objects Json::Value root; - Json::Reader reader; - bool success = reader.parse( value, root ); + Json::CharReaderBuilder rbuilder; + Json::CharReader* reader(rbuilder.newCharReader()); + + string errors; + bool success = reader->parse( value.c_str(), + value.c_str() + value.size(), &root, &errors ); if (!success) // Raise exception throw InvalidJSON("JSON could not be parsed (or is invalid)", ""); @@ -1083,8 +1087,12 @@ void Timeline::ApplyJsonDiff(string value) { // Parse JSON string into JSON objects Json::Value root; - Json::Reader reader; - bool success = reader.parse( value, root ); + Json::CharReaderBuilder rbuilder; + Json::CharReader* reader(rbuilder.newCharReader()); + + string errors; + bool success = reader->parse( value.c_str(), + value.c_str() + value.size(), &root, &errors ); if (!success || !root.isArray()) // Raise exception throw InvalidJSON("JSON could not be parsed (or is invalid).", ""); diff --git a/src/WriterBase.cpp b/src/WriterBase.cpp index 3697c52a..3aedec67 100644 --- a/src/WriterBase.cpp +++ b/src/WriterBase.cpp @@ -196,8 +196,12 @@ void WriterBase::SetJson(string value) { // Parse JSON string into JSON objects Json::Value root; - Json::Reader reader; - bool success = reader.parse( value, root ); + Json::CharReaderBuilder rbuilder; + Json::CharReader* reader(rbuilder.newCharReader()); + + string errors; + bool success = reader->parse( value.c_str(), + value.c_str() + value.size(), &root, &errors ); if (!success) // Raise exception throw InvalidJSON("JSON could not be parsed (or is invalid)", ""); diff --git a/src/effects/Bars.cpp b/src/effects/Bars.cpp index a4a72c19..f7219215 100644 --- a/src/effects/Bars.cpp +++ b/src/effects/Bars.cpp @@ -138,8 +138,12 @@ void Bars::SetJson(string value) { // Parse JSON string into JSON objects Json::Value root; - Json::Reader reader; - bool success = reader.parse( value, root ); + Json::CharReaderBuilder rbuilder; + Json::CharReader* reader(rbuilder.newCharReader()); + + string errors; + bool success = reader->parse( value.c_str(), + value.c_str() + value.size(), &root, &errors ); if (!success) // Raise exception throw InvalidJSON("JSON could not be parsed (or is invalid)", ""); diff --git a/src/effects/Blur.cpp b/src/effects/Blur.cpp index 6eafc2a1..dbbfbdbf 100644 --- a/src/effects/Blur.cpp +++ b/src/effects/Blur.cpp @@ -275,8 +275,12 @@ void Blur::SetJson(string value) { // Parse JSON string into JSON objects Json::Value root; - Json::Reader reader; - bool success = reader.parse( value, root ); + Json::CharReaderBuilder rbuilder; + Json::CharReader* reader(rbuilder.newCharReader()); + + string errors; + bool success = reader->parse( value.c_str(), + value.c_str() + value.size(), &root, &errors ); if (!success) // Raise exception throw InvalidJSON("JSON could not be parsed (or is invalid)", ""); diff --git a/src/effects/Brightness.cpp b/src/effects/Brightness.cpp index 591bce6a..e817a062 100644 --- a/src/effects/Brightness.cpp +++ b/src/effects/Brightness.cpp @@ -129,8 +129,12 @@ void Brightness::SetJson(string value) { // Parse JSON string into JSON objects Json::Value root; - Json::Reader reader; - bool success = reader.parse( value, root ); + Json::CharReaderBuilder rbuilder; + Json::CharReader* reader(rbuilder.newCharReader()); + + string errors; + bool success = reader->parse( value.c_str(), + value.c_str() + value.size(), &root, &errors ); if (!success) // Raise exception throw InvalidJSON("JSON could not be parsed (or is invalid)", ""); diff --git a/src/effects/ChromaKey.cpp b/src/effects/ChromaKey.cpp index 2c3008f2..fe6a2687 100644 --- a/src/effects/ChromaKey.cpp +++ b/src/effects/ChromaKey.cpp @@ -122,8 +122,12 @@ void ChromaKey::SetJson(string value) { // Parse JSON string into JSON objects Json::Value root; - Json::Reader reader; - bool success = reader.parse( value, root ); + Json::CharReaderBuilder rbuilder; + Json::CharReader* reader(rbuilder.newCharReader()); + + string errors; + bool success = reader->parse( value.c_str(), + value.c_str() + value.size(), &root, &errors ); if (!success) // Raise exception throw InvalidJSON("JSON could not be parsed (or is invalid)", ""); diff --git a/src/effects/ColorShift.cpp b/src/effects/ColorShift.cpp index 00300561..e2a72d7e 100644 --- a/src/effects/ColorShift.cpp +++ b/src/effects/ColorShift.cpp @@ -221,8 +221,12 @@ void ColorShift::SetJson(string value) { // Parse JSON string into JSON objects Json::Value root; - Json::Reader reader; - bool success = reader.parse( value, root ); + Json::CharReaderBuilder rbuilder; + Json::CharReader* reader(rbuilder.newCharReader()); + + string errors; + bool success = reader->parse( value.c_str(), + value.c_str() + value.size(), &root, &errors ); if (!success) // Raise exception throw InvalidJSON("JSON could not be parsed (or is invalid)", ""); diff --git a/src/effects/Crop.cpp b/src/effects/Crop.cpp index 53953ec2..245307ef 100644 --- a/src/effects/Crop.cpp +++ b/src/effects/Crop.cpp @@ -137,8 +137,12 @@ void Crop::SetJson(string value) { // Parse JSON string into JSON objects Json::Value root; - Json::Reader reader; - bool success = reader.parse( value, root ); + Json::CharReaderBuilder rbuilder; + Json::CharReader* reader(rbuilder.newCharReader()); + + string errors; + bool success = reader->parse( value.c_str(), + value.c_str() + value.size(), &root, &errors ); if (!success) // Raise exception throw InvalidJSON("JSON could not be parsed (or is invalid)", ""); @@ -193,4 +197,3 @@ string Crop::PropertiesJSON(int64_t requested_frame) { // Return formatted string return root.toStyledString(); } - diff --git a/src/effects/Deinterlace.cpp b/src/effects/Deinterlace.cpp index bf34d13f..dc35cd9a 100644 --- a/src/effects/Deinterlace.cpp +++ b/src/effects/Deinterlace.cpp @@ -116,8 +116,12 @@ void Deinterlace::SetJson(string value) { // Parse JSON string into JSON objects Json::Value root; - Json::Reader reader; - bool success = reader.parse( value, root ); + Json::CharReaderBuilder rbuilder; + Json::CharReader* reader(rbuilder.newCharReader()); + + string errors; + bool success = reader->parse( value.c_str(), + value.c_str() + value.size(), &root, &errors ); if (!success) // Raise exception throw InvalidJSON("JSON could not be parsed (or is invalid)", ""); diff --git a/src/effects/Hue.cpp b/src/effects/Hue.cpp index ae401a8b..4ea3bcd5 100644 --- a/src/effects/Hue.cpp +++ b/src/effects/Hue.cpp @@ -123,8 +123,12 @@ void Hue::SetJson(string value) { // Parse JSON string into JSON objects Json::Value root; - Json::Reader reader; - bool success = reader.parse( value, root ); + Json::CharReaderBuilder rbuilder; + Json::CharReader* reader(rbuilder.newCharReader()); + + string errors; + bool success = reader->parse( value.c_str(), + value.c_str() + value.size(), &root, &errors ); if (!success) // Raise exception throw InvalidJSON("JSON could not be parsed (or is invalid)", ""); diff --git a/src/effects/Mask.cpp b/src/effects/Mask.cpp index f8f34ac6..95dd3489 100644 --- a/src/effects/Mask.cpp +++ b/src/effects/Mask.cpp @@ -176,8 +176,12 @@ void Mask::SetJson(string value) { // Parse JSON string into JSON objects Json::Value root; - Json::Reader reader; - bool success = reader.parse( value, root ); + Json::CharReaderBuilder rbuilder; + Json::CharReader* reader(rbuilder.newCharReader()); + + string errors; + bool success = reader->parse( value.c_str(), + value.c_str() + value.size(), &root, &errors ); if (!success) // Raise exception throw InvalidJSON("JSON could not be parsed (or is invalid)", ""); diff --git a/src/effects/Negate.cpp b/src/effects/Negate.cpp index 8ff6e0d6..98b79213 100644 --- a/src/effects/Negate.cpp +++ b/src/effects/Negate.cpp @@ -77,8 +77,12 @@ void Negate::SetJson(string value) { // Parse JSON string into JSON objects Json::Value root; - Json::Reader reader; - bool success = reader.parse( value, root ); + Json::CharReaderBuilder rbuilder; + Json::CharReader* reader(rbuilder.newCharReader()); + + string errors; + bool success = reader->parse( value.c_str(), + value.c_str() + value.size(), &root, &errors ); if (!success) // Raise exception throw InvalidJSON("JSON could not be parsed (or is invalid)", ""); diff --git a/src/effects/Pixelate.cpp b/src/effects/Pixelate.cpp index 78030283..bad0fa15 100644 --- a/src/effects/Pixelate.cpp +++ b/src/effects/Pixelate.cpp @@ -134,8 +134,12 @@ void Pixelate::SetJson(string value) { // Parse JSON string into JSON objects Json::Value root; - Json::Reader reader; - bool success = reader.parse( value, root ); + Json::CharReaderBuilder rbuilder; + Json::CharReader* reader(rbuilder.newCharReader()); + + string errors; + bool success = reader->parse( value.c_str(), + value.c_str() + value.size(), &root, &errors ); if (!success) // Raise exception throw InvalidJSON("JSON could not be parsed (or is invalid)", ""); diff --git a/src/effects/Saturation.cpp b/src/effects/Saturation.cpp index ecb3165f..0393ed0b 100644 --- a/src/effects/Saturation.cpp +++ b/src/effects/Saturation.cpp @@ -134,8 +134,12 @@ void Saturation::SetJson(string value) { // Parse JSON string into JSON objects Json::Value root; - Json::Reader reader; - bool success = reader.parse( value, root ); + Json::CharReaderBuilder rbuilder; + Json::CharReader* reader(rbuilder.newCharReader()); + + string errors; + bool success = reader->parse( value.c_str(), + value.c_str() + value.size(), &root, &errors ); if (!success) // Raise exception throw InvalidJSON("JSON could not be parsed (or is invalid)", ""); diff --git a/src/effects/Shift.cpp b/src/effects/Shift.cpp index 80a7b2d7..89ad36d1 100644 --- a/src/effects/Shift.cpp +++ b/src/effects/Shift.cpp @@ -154,8 +154,12 @@ void Shift::SetJson(string value) { // Parse JSON string into JSON objects Json::Value root; - Json::Reader reader; - bool success = reader.parse( value, root ); + Json::CharReaderBuilder rbuilder; + Json::CharReader* reader(rbuilder.newCharReader()); + + string errors; + bool success = reader->parse( value.c_str(), + value.c_str() + value.size(), &root, &errors ); if (!success) // Raise exception throw InvalidJSON("JSON could not be parsed (or is invalid)", ""); diff --git a/src/effects/Wave.cpp b/src/effects/Wave.cpp index 0a1c5273..2325213f 100644 --- a/src/effects/Wave.cpp +++ b/src/effects/Wave.cpp @@ -137,8 +137,12 @@ void Wave::SetJson(string value) { // Parse JSON string into JSON objects Json::Value root; - Json::Reader reader; - bool success = reader.parse( value, root ); + Json::CharReaderBuilder rbuilder; + Json::CharReader* reader(rbuilder.newCharReader()); + + string errors; + bool success = reader->parse( value.c_str(), + value.c_str() + value.size(), &root, &errors ); if (!success) // Raise exception throw InvalidJSON("JSON could not be parsed (or is invalid)", ""); diff --git a/tests/Clip_Tests.cpp b/tests/Clip_Tests.cpp index 1134e8ab..0ae00d65 100644 --- a/tests/Clip_Tests.cpp +++ b/tests/Clip_Tests.cpp @@ -110,8 +110,11 @@ TEST(Clip_Properties) // Parse JSON string into JSON objects Json::Value root; - Json::Reader reader; - bool success = reader.parse( properties, root ); + Json::CharReaderBuilder rbuilder; + Json::CharReader* reader(rbuilder.newCharReader()); + string errors; + bool success = reader->parse( properties.c_str(), + properties.c_str() + properties.size(), &root, &errors ); if (!success) // Raise exception throw InvalidJSON("JSON could not be parsed (or is invalid)", ""); @@ -135,7 +138,8 @@ TEST(Clip_Properties) // Parse JSON string into JSON objects root.clear(); - success = reader.parse( properties, root ); + success = reader->parse( properties.c_str(), + properties.c_str() + properties.size(), &root, &errors ); if (!success) // Raise exception throw InvalidJSON("JSON could not be parsed (or is invalid)", ""); @@ -159,7 +163,8 @@ TEST(Clip_Properties) // Parse JSON string into JSON objects root.clear(); - success = reader.parse( properties, root ); + success = reader->parse( properties.c_str(), + properties.c_str() + properties.size(), &root, &errors ); if (!success) // Raise exception throw InvalidJSON("JSON could not be parsed (or is invalid)", ""); @@ -182,7 +187,8 @@ TEST(Clip_Properties) // Parse JSON string into JSON objects root.clear(); - success = reader.parse( properties, root ); + success = reader->parse( properties.c_str(), + properties.c_str() + properties.size(), &root, &errors ); if (!success) // Raise exception throw InvalidJSON("JSON could not be parsed (or is invalid)", ""); From 9378225d76dce86222c7aa6684bc916b6a292598 Mon Sep 17 00:00:00 2001 From: "FeRD (Frank Dana)" Date: Wed, 19 Jun 2019 22:21:50 -0400 Subject: [PATCH 214/223] Add -no-integrated-cpp for G++ < 9 --- CMakeLists.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2e3a49c3..03c5a761 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,6 +75,13 @@ Generating build files for OpenShot SO/API/ABI Version: ${SO_VERSION} ") +#### Work around a GCC < 9 bug with handling of _Pragma() in macros +#### See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=55578 +if ((${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU") AND + (${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS "9.0.0")) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -no-integrated-cpp") +endif() + #### Enable C++11 (for std::shared_ptr support) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) From ddd78215bb356d29986e4477a6ccd2fe2ad45441 Mon Sep 17 00:00:00 2001 From: "FeRD (Frank Dana)" Date: Wed, 19 Jun 2019 22:53:42 -0400 Subject: [PATCH 215/223] Also adjust tests for new jsoncpp --- tests/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e358fb44..da04c0da 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -194,7 +194,7 @@ if (USE_SYSTEM_JSONCPP) find_package(JsonCpp REQUIRED) include_directories(${JSONCPP_INCLUDE_DIRS}) else() - include_directories("../thirdparty/jsoncpp/include") + include_directories("../thirdparty/jsoncpp") endif(USE_SYSTEM_JSONCPP) IF (NOT DISABLE_TESTS) From 40b9891d8a02458beb4528ec734d88f399b32661 Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Thu, 20 Jun 2019 13:32:30 -0700 Subject: [PATCH 216/223] Update FFmpegWriter.cpp Reverse 1st attempt --- src/FFmpegWriter.cpp | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index 64618ce5..072ac6d7 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -992,14 +992,7 @@ AVStream *FFmpegWriter::add_audio_stream() { throw InvalidCodec("A valid audio codec could not be found for this file.", path); // Create a new audio stream -#if (IS_FFMPEG_3_2 && (LIBAVFORMAT_VERSION_MAJOR < 58)) -_Pragma ("GCC diagnostic push") -_Pragma ("GCC diagnostic ignored \"-Wdeprecated-declarations\"") -#endif AV_FORMAT_NEW_STREAM(oc, audio_codec, codec, st) -#if (IS_FFMPEG_3_2 && (LIBAVFORMAT_VERSION_MAJOR < 58)) -_Pragma ("GCC diagnostic pop") -#endif c->codec_id = codec->id; #if LIBAVFORMAT_VERSION_MAJOR >= 53 @@ -1082,14 +1075,7 @@ AVStream *FFmpegWriter::add_video_stream() { throw InvalidCodec("A valid video codec could not be found for this file.", path); // Create a new video stream -#if (IS_FFMPEG_3_2 && (LIBAVFORMAT_VERSION_MAJOR < 58)) -_Pragma ("GCC diagnostic push") -_Pragma ("GCC diagnostic ignored \"-Wdeprecated-declarations\"") -#endif AV_FORMAT_NEW_STREAM(oc, video_codec, codec, st) -#if (IS_FFMPEG_3_2 && (LIBAVFORMAT_VERSION_MAJOR < 58)) -_Pragma ("GCC diagnostic pop") -#endif c->codec_id = codec->id; #if LIBAVFORMAT_VERSION_MAJOR >= 53 From c54a3705ff00e72b7c08cb153da2f56c4aa063ba Mon Sep 17 00:00:00 2001 From: eisneinechse <42617957+eisneinechse@users.noreply.github.com> Date: Thu, 20 Jun 2019 13:41:03 -0700 Subject: [PATCH 217/223] Update FFmpegUtilities.h Warnings are now silenced in FFmpegUtilities.h, where it should be. --- include/FFmpegUtilities.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/include/FFmpegUtilities.h b/include/FFmpegUtilities.h index 0d12ba72..4ac8b158 100644 --- a/include/FFmpegUtilities.h +++ b/include/FFmpegUtilities.h @@ -209,9 +209,12 @@ #define AV_FORMAT_NEW_STREAM(oc, st_codec, av_codec, av_st) av_st = avformat_new_stream(oc, NULL);\ if (!av_st) \ throw OutOfMemory("Could not allocate memory for the video stream.", path); \ - c = avcodec_alloc_context3(av_codec); \ - st_codec = c; \ - av_st->codecpar->codec_id = av_codec->id; + _Pragma ("GCC diagnostic push"); \ + _Pragma ("GCC diagnostic ignored \"-Wdeprecated-declarations\""); \ + avcodec_get_context_defaults3(av_st->codec, av_codec); \ + c = av_st->codec; \ + _Pragma ("GCC diagnostic pop"); \ + st_codec = c; #define AV_COPY_PARAMS_FROM_CONTEXT(av_stream, av_codec) avcodec_parameters_from_context(av_stream->codecpar, av_codec); #elif LIBAVFORMAT_VERSION_MAJOR >= 55 #define AV_REGISTER_ALL av_register_all(); From a47d5b58fd4b55a3fd3cce451f8839a830baf203 Mon Sep 17 00:00:00 2001 From: Frank Dana Date: Fri, 21 Jun 2019 01:07:49 -0400 Subject: [PATCH 218/223] Add backwards-compatible Imagemagick 7 support (#252) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add ImageMagick 7 compatibility A new header, `imclude/MagickUtilities.h`, is created to hold the compatibility `#define`s. The image-conversion code in `src/Frame.cpp` received the only major changes — instead of doing the export by hand (and having to account for changes in the underlying API), it uses the `MagickCore::ExportImagePixels()` function which does basically the same work, but accounts for all of the API changes for us. The API of that function is _unchanged_ from IM6 to IM7. TODO: `MagickCore::ExportImagePixels()` will return an `exception` struct if it encounters any problems. Currently the code ignores that, which it should not. * Add ImageMagick 7 compatibility A new header, `imclude/MagickUtilities.h`, is created to hold the compatibility `#define`s. The image-conversion code in `src/Frame.cpp` received the only major changes — instead of doing the export by hand (and having to account for changes in the underlying API), it uses the `MagickCore::ExportImagePixels()` function which does basically the same work, but accounts for all of the API changes for us. The API of that function is _unchanged_ from IM6 to IM7. TODO: `MagickCore::ExportImagePixels()` will return an `exception` struct if it encounters any problems. Currently the code ignores that, which it should not. Thanks @ferdnyc --- include/Frame.h | 6 ++-- include/ImageReader.h | 8 +++-- include/ImageWriter.h | 9 +++--- include/MagickUtilities.h | 61 +++++++++++++++++++++++++++++++++++++++ include/TextReader.h | 10 +++++-- include/effects/Mask.h | 7 +++-- src/Frame.cpp | 20 ++++--------- src/ImageReader.cpp | 7 ++++- src/ImageWriter.cpp | 6 +++- src/TextReader.cpp | 5 ++++ src/effects/Mask.cpp | 9 +++--- 11 files changed, 112 insertions(+), 36 deletions(-) create mode 100644 include/MagickUtilities.h diff --git a/include/Frame.h b/include/Frame.h index 6b682edb..1c4dd26c 100644 --- a/include/Frame.h +++ b/include/Frame.h @@ -53,14 +53,14 @@ #include #include #include "ZmqLogger.h" -#ifdef USE_IMAGEMAGICK - #include "Magick++.h" -#endif #include "ChannelLayouts.h" #include "AudioBufferSource.h" #include "AudioResampler.h" #include "Fraction.h" #include "JuceHeader.h" +#ifdef USE_IMAGEMAGICK + #include "MagickUtilities.h" +#endif #pragma SWIG nowarn=362 using namespace std; diff --git a/include/ImageReader.h b/include/ImageReader.h index e698e0c1..7ad23173 100644 --- a/include/ImageReader.h +++ b/include/ImageReader.h @@ -28,6 +28,9 @@ #ifndef OPENSHOT_IMAGE_READER_H #define OPENSHOT_IMAGE_READER_H +// Require ImageMagick support +#ifdef USE_IMAGEMAGICK + #include "ReaderBase.h" #include @@ -36,9 +39,9 @@ #include #include #include -#include "Magick++.h" #include "CacheMemory.h" #include "Exceptions.h" +#include "MagickUtilities.h" using namespace std; @@ -113,4 +116,5 @@ namespace openshot } -#endif +#endif //USE_IMAGEMAGICK +#endif //OPENSHOT_IMAGE_READER_H diff --git a/include/ImageWriter.h b/include/ImageWriter.h index 25177134..b7dd7dc2 100644 --- a/include/ImageWriter.h +++ b/include/ImageWriter.h @@ -32,10 +32,11 @@ * along with OpenShot Library. If not, see . */ - #ifndef OPENSHOT_IMAGE_WRITER_H #define OPENSHOT_IMAGE_WRITER_H +#ifdef USE_IMAGEMAGICK + #include "ReaderBase.h" #include "WriterBase.h" @@ -44,11 +45,10 @@ #include #include #include -#include "Magick++.h" #include "CacheMemory.h" #include "Exceptions.h" #include "OpenMPUtilities.h" - +#include "MagickUtilities.h" using namespace std; @@ -145,4 +145,5 @@ namespace openshot } -#endif +#endif //USE_IMAGEMAGICK +#endif //OPENSHOT_IMAGE_WRITER_H diff --git a/include/MagickUtilities.h b/include/MagickUtilities.h new file mode 100644 index 00000000..f3b7ea12 --- /dev/null +++ b/include/MagickUtilities.h @@ -0,0 +1,61 @@ +/** + * @file + * @brief Header file for MagickUtilities (IM6/IM7 compatibility overlay) + * @author Jonathan Thomas + * @author FeRD (Frank Dana) + */ + +/* 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 . + */ + +#ifndef OPENSHOT_MAGICK_UTILITIES_H +#define OPENSHOT_MAGICK_UTILITIES_H + +#ifdef USE_IMAGEMAGICK + + #include "Magick++.h" + + // Determine ImageMagick version, as IM7 isn't fully + // backwards compatible + #ifndef NEW_MAGICK + #define NEW_MAGICK (MagickLibVersion >= 0x700) + #endif + + // IM7: ->alpha(bool) + // IM6: ->matte(bool) + #if NEW_MAGICK + #define MAGICK_IMAGE_ALPHA(im, a) im->alpha((a)) + #else + #define MAGICK_IMAGE_ALPHA(im, a) im->matte((a)) + #endif + + // IM7: vector + // IM6: list + // (both have the push_back() method which is all we use) + #if NEW_MAGICK + #define MAGICK_DRAWABLE vector + #else + #define MAGICK_DRAWABLE list + #endif + +#endif +#endif diff --git a/include/TextReader.h b/include/TextReader.h index d7d653d2..bb4bdc22 100644 --- a/include/TextReader.h +++ b/include/TextReader.h @@ -28,6 +28,9 @@ #ifndef OPENSHOT_TEXT_READER_H #define OPENSHOT_TEXT_READER_H +// Require ImageMagick support +#ifdef USE_IMAGEMAGICK + #include "ReaderBase.h" #include @@ -36,10 +39,10 @@ #include #include #include -#include "Magick++.h" #include "CacheMemory.h" #include "Enums.h" #include "Exceptions.h" +#include "MagickUtilities.h" using namespace std; @@ -91,7 +94,7 @@ namespace openshot string text_color; string background_color; std::shared_ptr image; - list lines; + MAGICK_DRAWABLE lines; bool is_open; GravityType gravity; @@ -144,4 +147,5 @@ namespace openshot } -#endif +#endif //USE_IMAGEMAGICK +#endif //OPENSHOT_TEXT_READER_H diff --git a/include/effects/Mask.h b/include/effects/Mask.h index ef707f5f..e5b8f649 100644 --- a/include/effects/Mask.h +++ b/include/effects/Mask.h @@ -25,8 +25,8 @@ * along with OpenShot Library. If not, see . */ -#ifndef OPENSHOT_WIPE_EFFECT_H -#define OPENSHOT_WIPE_EFFECT_H +#ifndef OPENSHOT_MASK_EFFECT_H +#define OPENSHOT_MASK_EFFECT_H #include "../EffectBase.h" @@ -45,6 +45,7 @@ #include "../QtImageReader.h" #include "../ChunkReader.h" #ifdef USE_IMAGEMAGICK + #include "../MagickUtilities.h" #include "../ImageReader.h" #endif @@ -54,7 +55,7 @@ namespace openshot { /** - * @brief This class uses the ImageMagick++ libraries, to apply alpha (or transparency) masks + * @brief This class uses the image libraries to apply alpha (or transparency) masks * to any frame. It can also be animated, and used as a powerful Wipe transition. * * These masks / wipes can also be combined, such as a transparency mask on top of a clip, which diff --git a/src/Frame.cpp b/src/Frame.cpp index aa7c0d87..5e06e61e 100644 --- a/src/Frame.cpp +++ b/src/Frame.cpp @@ -926,7 +926,7 @@ std::shared_ptr Frame::GetMagickImage() // Give image a transparent background color magick_image->backgroundColor(Magick::Color("none")); magick_image->virtualPixelMethod(Magick::TransparentVirtualPixelMethod); - magick_image->matte(true); + MAGICK_IMAGE_ALPHA(magick_image, true); return magick_image; } @@ -945,20 +945,12 @@ void Frame::AddMagickImage(std::shared_ptr new_image) qbuffer = new unsigned char[bufferSize](); unsigned char *buffer = (unsigned char*)qbuffer; - // Iterate through the pixel packets, and load our own buffer - // Each color needs to be scaled to 8 bit (using the ImageMagick built-in ScaleQuantumToChar function) - int numcopied = 0; - Magick::PixelPacket *pixels = new_image->getPixels(0,0, new_image->columns(), new_image->rows()); - for (int n = 0, i = 0; n < new_image->columns() * new_image->rows(); n += 1, i += 4) { - buffer[i+0] = MagickCore::ScaleQuantumToChar((Magick::Quantum) pixels[n].red); - buffer[i+1] = MagickCore::ScaleQuantumToChar((Magick::Quantum) pixels[n].green); - buffer[i+2] = MagickCore::ScaleQuantumToChar((Magick::Quantum) pixels[n].blue); - buffer[i+3] = 255 - MagickCore::ScaleQuantumToChar((Magick::Quantum) pixels[n].opacity); - numcopied+=4; - } + MagickCore::ExceptionInfo exception; + // TODO: Actually do something, if we get an exception here + MagickCore::ExportImagePixels(new_image->constImage(), 0, 0, new_image->columns(), new_image->rows(), "RGBA", Magick::CharPixel, buffer, &exception); - // Create QImage of frame data - image = std::shared_ptr(new QImage(qbuffer, width, height, width * BPP, QImage::Format_RGBA8888, (QImageCleanupFunction) &cleanUpBuffer, (void*) qbuffer)); + // Create QImage of frame data + image = std::shared_ptr(new QImage(qbuffer, width, height, width * BPP, QImage::Format_RGBA8888, (QImageCleanupFunction) &cleanUpBuffer, (void*) qbuffer)); // Update height and width width = image->width(); diff --git a/src/ImageReader.cpp b/src/ImageReader.cpp index 3c1d334a..491e1c5d 100644 --- a/src/ImageReader.cpp +++ b/src/ImageReader.cpp @@ -25,6 +25,9 @@ * along with OpenShot Library. If not, see . */ +// Require ImageMagick support +#ifdef USE_IMAGEMAGICK + #include "../include/ImageReader.h" using namespace openshot; @@ -59,7 +62,7 @@ void ImageReader::Open() // Give image a transparent background color image->backgroundColor(Magick::Color("none")); - image->matte(true); + MAGICK_IMAGE_ALPHA(image, true); } catch (Magick::Exception e) { // raise exception @@ -192,3 +195,5 @@ void ImageReader::SetJsonValue(Json::Value root) { Open(); } } + +#endif //USE_IMAGEMAGICK diff --git a/src/ImageWriter.cpp b/src/ImageWriter.cpp index 41626b09..5bc0367a 100644 --- a/src/ImageWriter.cpp +++ b/src/ImageWriter.cpp @@ -28,6 +28,9 @@ * along with OpenShot Library. If not, see . */ +//Require ImageMagick support +#ifdef USE_IMAGEMAGICK + #include "../include/ImageWriter.h" using namespace openshot; @@ -97,7 +100,7 @@ void ImageWriter::WriteFrame(std::shared_ptr frame) std::shared_ptr frame_image = frame->GetMagickImage(); frame_image->magick( info.vcodec ); frame_image->backgroundColor(Magick::Color("none")); - frame_image->matte(true); + MAGICK_IMAGE_ALPHA(frame_image, true); frame_image->quality(image_quality); frame_image->animationDelay(info.video_timebase.ToFloat() * 100); frame_image->animationIterations(number_of_loops); @@ -153,3 +156,4 @@ void ImageWriter::Close() ZmqLogger::Instance()->AppendDebugMethod("ImageWriter::Close", "", -1, "", -1, "", -1, "", -1, "", -1, "", -1); } +#endif //USE_IMAGEMAGICK diff --git a/src/TextReader.cpp b/src/TextReader.cpp index a24f26e4..b38c5f18 100644 --- a/src/TextReader.cpp +++ b/src/TextReader.cpp @@ -25,6 +25,9 @@ * along with OpenShot Library. If not, see . */ +// Require ImageMagick support +#ifdef USE_IMAGEMAGICK + #include "../include/TextReader.h" using namespace openshot; @@ -258,3 +261,5 @@ void TextReader::SetJsonValue(Json::Value root) { Open(); } } + +#endif //USE_IMAGEMAGICK diff --git a/src/effects/Mask.cpp b/src/effects/Mask.cpp index 95dd3489..e1c2db4d 100644 --- a/src/effects/Mask.cpp +++ b/src/effects/Mask.cpp @@ -238,11 +238,11 @@ void Mask::SetJsonValue(Json::Value root) { reader->SetJsonValue(root["reader"]); #ifdef USE_IMAGEMAGICK - } else if (type == "ImageReader") { + } else if (type == "ImageReader") { - // Create new reader - reader = new ImageReader(root["reader"]["path"].asString()); - reader->SetJsonValue(root["reader"]); + // Create new reader + reader = new ImageReader(root["reader"]["path"].asString()); + reader->SetJsonValue(root["reader"]); #endif } else if (type == "QtImageReader") { @@ -294,4 +294,3 @@ string Mask::PropertiesJSON(int64_t requested_frame) { // Return formatted string return root.toStyledString(); } - From bf9e45b5725ec19703df2a5534a5451711eaeacf Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Fri, 21 Jun 2019 02:02:55 -0500 Subject: [PATCH 219/223] Make docs on Linux builder, and auto-update doc files for develop branch --- .gitlab-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 42656302..0d044dd8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -20,6 +20,8 @@ linux-builder: - cmake -D"CMAKE_INSTALL_PREFIX:PATH=$CI_PROJECT_DIR/build/install-x64" -D"CMAKE_BUILD_TYPE:STRING=Release" ../ - make - make install + - make doc + - ~/.auto-update-docs "$CI_PROJECT_DIR/build" "$CI_COMMIT_REF_NAME" - mv install-x64/lib/python3.4/site-packages/*openshot* install-x64/python - echo -e "CI_PROJECT_NAME:$CI_PROJECT_NAME\nCI_COMMIT_REF_NAME:$CI_COMMIT_REF_NAME\nCI_COMMIT_SHA:$CI_COMMIT_SHA\nCI_JOB_ID:$CI_JOB_ID" > "install-x64/share/$CI_PROJECT_NAME" - git log $(git describe --tags --abbrev=0)..HEAD --oneline --pretty=format:"%C(auto,yellow)%h%C(auto,magenta)% %C(auto,blue)%>(12,trunc)%ad %C(auto,green)%<(25,trunc)%aN%C(auto,reset)%s%C(auto,red)% gD% D" --date=short > "install-x64/share/$CI_PROJECT_NAME.log" From c7371bc7f14b481151dff0bd4a7e86bd21b53c36 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Fri, 21 Jun 2019 02:18:23 -0500 Subject: [PATCH 220/223] Fixing invalid script path --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0d044dd8..d74c1ef6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -21,7 +21,7 @@ linux-builder: - make - make install - make doc - - ~/.auto-update-docs "$CI_PROJECT_DIR/build" "$CI_COMMIT_REF_NAME" + - ~/auto-update-docs "$CI_PROJECT_DIR/build" "$CI_COMMIT_REF_NAME" - mv install-x64/lib/python3.4/site-packages/*openshot* install-x64/python - echo -e "CI_PROJECT_NAME:$CI_PROJECT_NAME\nCI_COMMIT_REF_NAME:$CI_COMMIT_REF_NAME\nCI_COMMIT_SHA:$CI_COMMIT_SHA\nCI_JOB_ID:$CI_JOB_ID" > "install-x64/share/$CI_PROJECT_NAME" - git log $(git describe --tags --abbrev=0)..HEAD --oneline --pretty=format:"%C(auto,yellow)%h%C(auto,magenta)% %C(auto,blue)%>(12,trunc)%ad %C(auto,green)%<(25,trunc)%aN%C(auto,reset)%s%C(auto,red)% gD% D" --date=short > "install-x64/share/$CI_PROJECT_NAME.log" From ac8876f81051f69074bc50f21d66ba2706620a2f Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Fri, 21 Jun 2019 15:57:41 -0500 Subject: [PATCH 221/223] Removing duplicated destructor definitions and implementations... so our virtual destructors will not break on older toolchains. --- include/CacheBase.h | 2 -- include/ClipBase.h | 1 - include/ReaderBase.h | 2 -- src/CacheBase.cpp | 3 --- src/ClipBase.cpp | 3 --- src/ReaderBase.cpp | 3 --- 6 files changed, 14 deletions(-) diff --git a/include/CacheBase.h b/include/CacheBase.h index 12108680..3760e84b 100644 --- a/include/CacheBase.h +++ b/include/CacheBase.h @@ -63,8 +63,6 @@ namespace openshot { /// @param max_bytes The maximum bytes to allow in the cache. Once exceeded, the cache will purge the oldest frames. CacheBase(int64_t max_bytes); - virtual ~CacheBase(); - /// @brief Add a Frame to the cache /// @param frame The openshot::Frame object needing to be cached. virtual void Add(std::shared_ptr frame) = 0; diff --git a/include/ClipBase.h b/include/ClipBase.h index 05f2747e..ab3f0637 100644 --- a/include/ClipBase.h +++ b/include/ClipBase.h @@ -72,7 +72,6 @@ namespace openshot { /// Constructor for the base clip ClipBase() { }; - virtual ~ClipBase(); // Compare a clip using the Position() property bool operator< ( ClipBase& a) { return (Position() < a.Position()); } diff --git a/include/ReaderBase.h b/include/ReaderBase.h index b2e8f19e..0d14ea19 100644 --- a/include/ReaderBase.h +++ b/include/ReaderBase.h @@ -110,8 +110,6 @@ namespace openshot /// Constructor for the base reader, where many things are initialized. ReaderBase(); - virtual ~ReaderBase(); - /// Information about the current media file ReaderInfo info; diff --git a/src/CacheBase.cpp b/src/CacheBase.cpp index 8270b393..0016694a 100644 --- a/src/CacheBase.cpp +++ b/src/CacheBase.cpp @@ -45,9 +45,6 @@ CacheBase::CacheBase(int64_t max_bytes) : max_bytes(max_bytes) { cacheCriticalSection = new CriticalSection(); }; -CacheBase::~CacheBase() { -}; - // Set maximum bytes to a different amount based on a ReaderInfo struct void CacheBase::SetMaxBytesFromInfo(int64_t number_of_frames, int width, int height, int sample_rate, int channels) { diff --git a/src/ClipBase.cpp b/src/ClipBase.cpp index 1517a7e3..60bdb633 100644 --- a/src/ClipBase.cpp +++ b/src/ClipBase.cpp @@ -32,9 +32,6 @@ using namespace openshot; -ClipBase::~ClipBase() { -} - // Generate Json::JsonValue for this object Json::Value ClipBase::JsonValue() { diff --git a/src/ReaderBase.cpp b/src/ReaderBase.cpp index ece0684f..30706866 100644 --- a/src/ReaderBase.cpp +++ b/src/ReaderBase.cpp @@ -66,9 +66,6 @@ ReaderBase::ReaderBase() parent = NULL; } -ReaderBase::~ReaderBase() { -} - // Display file information void ReaderBase::DisplayInfo() { cout << fixed << setprecision(2) << boolalpha; From 9d09b65e70ef242bfbc673e4b337c3b64534b4bd Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Fri, 21 Jun 2019 16:47:37 -0500 Subject: [PATCH 222/223] Revert "Don't break Python install path detection on Debian" --- src/bindings/python/CMakeLists.txt | 50 ++++++++++++------------------ 1 file changed, 20 insertions(+), 30 deletions(-) diff --git a/src/bindings/python/CMakeLists.txt b/src/bindings/python/CMakeLists.txt index 0496bef6..eb7c989a 100644 --- a/src/bindings/python/CMakeLists.txt +++ b/src/bindings/python/CMakeLists.txt @@ -65,39 +65,29 @@ if (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) target_link_libraries(${SWIG_MODULE_pyopenshot_REAL_NAME} ${PYTHON_LIBRARIES} openshot) - ### FIND THE PYTHON INTERPRETER (AND THE SITE PACKAGES FOLDER) - if (UNIX AND NOT APPLE) - ### Special-case for Debian's crazy, by checking to see if pybuild - ### is available. We don't use it, except as a canary in a coal mine - find_program(PYBUILD_EXECUTABLE pybuild - DOC "Path to Debian's pybuild utility") - if (PYBUILD_EXECUTABLE) - # We're on a Debian derivative, fall back to old path detection - set(py_detection "import site; print(site.getsitepackages()[0])") - set(PY_INSTALL_PREFIX "/usr/local") # An assumption (bad one?) - else() - # Use distutils to detect install path - set (py_detection "\ + ### Check if the following Debian-friendly python module path exists + SET(PYTHON_MODULE_PATH "${CMAKE_INSTALL_PREFIX}/lib/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/site-packages") + if (NOT EXISTS ${PYTHON_MODULE_PATH}) + + ### Check if another Debian-friendly python module path exists + SET(PYTHON_MODULE_PATH "${CMAKE_INSTALL_PREFIX}/lib/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/dist-packages") + if (NOT EXISTS ${PYTHON_MODULE_PATH}) + + ### Calculate the python module path (using distutils) + execute_process ( COMMAND ${PYTHON_EXECUTABLE} -c "\ from distutils.sysconfig import get_python_lib; \ -print( get_python_lib( plat_specific=True, prefix='${CMAKE_INSTALL_PREFIX}' ) )") - set(PY_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}) +print( get_python_lib( plat_specific=True, prefix='${CMAKE_INSTALL_PREFIX}' ) )" + OUTPUT_VARIABLE _ABS_PYTHON_MODULE_PATH + OUTPUT_STRIP_TRAILING_WHITESPACE ) + + GET_FILENAME_COMPONENT(_ABS_PYTHON_MODULE_PATH + "${_ABS_PYTHON_MODULE_PATH}" ABSOLUTE) + FILE(RELATIVE_PATH _REL_PYTHON_MODULE_PATH + ${CMAKE_INSTALL_PREFIX} ${_ABS_PYTHON_MODULE_PATH}) + SET(PYTHON_MODULE_PATH ${_ABS_PYTHON_MODULE_PATH}) endif() endif() - - if (NOT PYTHON_MODULE_PATH) - execute_process ( COMMAND ${PYTHON_EXECUTABLE} -c "${py_detection}" - OUTPUT_VARIABLE _ABS_PYTHON_MODULE_PATH - OUTPUT_STRIP_TRAILING_WHITESPACE ) - - GET_FILENAME_COMPONENT(_ABS_PYTHON_MODULE_PATH - "${_ABS_PYTHON_MODULE_PATH}" ABSOLUTE) - FILE(RELATIVE_PATH _REL_PYTHON_MODULE_PATH - ${PY_INSTALL_PREFIX} ${_ABS_PYTHON_MODULE_PATH}) - SET(PYTHON_MODULE_PATH ${_REL_PYTHON_MODULE_PATH} - CACHE PATH "Install path for Python modules (relative to prefix)") - endif() - - message(STATUS "Will install Python module to: ${PYTHON_MODULE_PATH}") + message("PYTHON_MODULE_PATH: ${PYTHON_MODULE_PATH}") ############### INSTALL HEADERS & LIBRARY ################ ### Install Python bindings From 8f42a9ff07585be22c4d40f46c6e8bf6666d4b3b Mon Sep 17 00:00:00 2001 From: Frank Dana Date: Wed, 26 Jun 2019 02:07:02 -0400 Subject: [PATCH 223/223] Fix tabs-vs-spaces indent in Timeline.h --- include/Timeline.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/Timeline.h b/include/Timeline.h index a90399cd..3793f301 100644 --- a/include/Timeline.h +++ b/include/Timeline.h @@ -104,7 +104,7 @@ namespace openshot { * Fraction(25,1), // framerate * 44100, // sample rate * 2 // channels - * ChannelLayout::LAYOUT_STEREO, + * ChannelLayout::LAYOUT_STEREO, * ); * * // Create some clips