submodule: change core into nomarl folder, and move KDockWidgets into third_party.
This commit is contained in:
+2
-6
@@ -1,8 +1,4 @@
|
|||||||
[submodule "ext/core"]
|
[submodule "third_party/KDDockWidgets"]
|
||||||
path = ext/core
|
path = third_party/KDDockWidgets
|
||||||
url = https://github.com/OliveCommunity/core.git
|
|
||||||
branch = dev
|
|
||||||
[submodule "ext/KDDockWidgets"]
|
|
||||||
path = ext/KDDockWidgets
|
|
||||||
url = https://github.com/OliveCommunity/KDDockWidgets.git
|
url = https://github.com/OliveCommunity/KDDockWidgets.git
|
||||||
branch = main
|
branch = main
|
||||||
|
|||||||
+11
-3
@@ -133,7 +133,7 @@ list(APPEND OLIVE_INCLUDE_DIRS ${OPENEXR_INCLUDES})
|
|||||||
|
|
||||||
# Link Olive
|
# Link Olive
|
||||||
list(APPEND OLIVE_LIBRARIES olivecore)
|
list(APPEND OLIVE_LIBRARIES olivecore)
|
||||||
list(APPEND OLIVE_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/ext/core/include)
|
list(APPEND OLIVE_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/core/include)
|
||||||
|
|
||||||
|
|
||||||
# Link Qt
|
# Link Qt
|
||||||
@@ -327,9 +327,17 @@ if(BUILD_DOXYGEN)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||||
list(APPEND OLIVE_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/ext)
|
list(APPEND OLIVE_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/third_party)
|
||||||
|
|
||||||
|
add_subdirectory(core EXCLUDE_FROM_ALL)
|
||||||
|
|
||||||
|
set(KDDockWidgets_STATIC ON CACHE INTERNAL "Force KDDockWidgets to build statically")
|
||||||
|
set(KDDockWidgets_QT6 ${BUILD_QT6} CACHE INTERNAL "Conform KDDockWidgets' Qt 6 setting to ours")
|
||||||
|
# Oak only uses the QtWidgets frontend; building the QtQuick frontend causes
|
||||||
|
# duplicate QML module registration on macOS and pulls in unused dependencies.
|
||||||
|
set(KDDockWidgets_FRONTENDS "qtwidgets" CACHE INTERNAL "Only build the QtWidgets frontend for Oak")
|
||||||
|
add_subdirectory(third_party/KDDockWidgets EXCLUDE_FROM_ALL)
|
||||||
|
|
||||||
add_subdirectory(ext)
|
|
||||||
add_subdirectory(third_party/openfx/HostSupport)
|
add_subdirectory(third_party/openfx/HostSupport)
|
||||||
add_subdirectory(app)
|
add_subdirectory(app)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
*.user
|
||||||
|
build/
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
# libolivecore
|
||||||
|
# Copyright (C) 2023 Olive Studios LLC
|
||||||
|
# Modifications Copyright (C) 2025 mikesolar
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program 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 General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 3.13 FATAL_ERROR)
|
||||||
|
|
||||||
|
project(libolivecore VERSION 1.0.0 LANGUAGES CXX)
|
||||||
|
|
||||||
|
option(OLIVECORE_BUILD_TESTS ON)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||||
|
|
||||||
|
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||||
|
|
||||||
|
# Link avutil
|
||||||
|
find_package(FFMPEG 6.0 REQUIRED
|
||||||
|
COMPONENTS avutil
|
||||||
|
)
|
||||||
|
|
||||||
|
# Link Imath
|
||||||
|
find_package(Imath REQUIRED CONFIG)
|
||||||
|
|
||||||
|
# Link OpenGL
|
||||||
|
if(UNIX AND NOT APPLE AND NOT DEFINED OpenGL_GL_PREFERENCE)
|
||||||
|
set(OpenGL_GL_PREFERENCE LEGACY)
|
||||||
|
endif()
|
||||||
|
find_package(OpenGL REQUIRED)
|
||||||
|
|
||||||
|
add_library(olivecore
|
||||||
|
src/render/audioparams.cpp
|
||||||
|
src/render/samplebuffer.cpp
|
||||||
|
src/util/bezier.cpp
|
||||||
|
src/util/color.cpp
|
||||||
|
src/util/rational.cpp
|
||||||
|
src/util/stringutils.cpp
|
||||||
|
src/util/tests.cpp
|
||||||
|
src/util/timecodefunctions.cpp
|
||||||
|
src/util/timerange.cpp
|
||||||
|
src/util/value.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(olivecore PRIVATE
|
||||||
|
${FFMPEG_INCLUDE_DIRS}
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/include/olive/core"
|
||||||
|
"${CMAKE_SOURCE_DIR}/third_party/openfx/include/"
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(olivecore PRIVATE
|
||||||
|
OpenGL::GL
|
||||||
|
Imath::Imath
|
||||||
|
FFMPEG::avutil
|
||||||
|
)
|
||||||
|
|
||||||
|
# Link OpenTimelineIO (optional)
|
||||||
|
find_package(OpenTimelineIO)
|
||||||
|
if (OpenTimelineIO_FOUND)
|
||||||
|
target_compile_definitions(olivecore PRIVATE USE_OTIO)
|
||||||
|
target_include_directories(olivecore PRIVATE ${OTIO_INCLUDE_DIRS})
|
||||||
|
target_link_libraries(olivecore PRIVATE ${OTIO_LIBRARIES})
|
||||||
|
else()
|
||||||
|
message(" OpenTimelineIO interchange will be disabled.")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
install(TARGETS olivecore)
|
||||||
|
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include/olive" DESTINATION "${CMAKE_INSTALL_PREFIX}/include")
|
||||||
|
|
||||||
|
if (OLIVECORE_BUILD_TESTS)
|
||||||
|
enable_testing()
|
||||||
|
|
||||||
|
function(make_test name)
|
||||||
|
add_executable(${name}
|
||||||
|
tests/${name}.cpp
|
||||||
|
)
|
||||||
|
target_link_libraries(${name} PRIVATE olivecore)
|
||||||
|
target_include_directories(${name} PRIVATE
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/include/olive/core"
|
||||||
|
)
|
||||||
|
add_test(${name} ${name})
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
make_test(rational-test)
|
||||||
|
make_test(stringutils-test)
|
||||||
|
make_test(timecode-test)
|
||||||
|
make_test(timerange-test)
|
||||||
|
endif()
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# libolivecore
|
||||||
|
|
||||||
|
GPU core library used for various video-related applications. Currently supports OpenGL, will soon support Vulkan.
|
||||||
Executable
+197
@@ -0,0 +1,197 @@
|
|||||||
|
#[==[
|
||||||
|
Provides the following variables:
|
||||||
|
|
||||||
|
* `FFMPEG_INCLUDE_DIRS`: Include directories necessary to use FFMPEG.
|
||||||
|
* `FFMPEG_LIBRARIES`: Libraries necessary to use FFMPEG. Note that this only
|
||||||
|
includes libraries for the components requested.
|
||||||
|
* `FFMPEG_VERSION`: The version of FFMPEG found.
|
||||||
|
|
||||||
|
The following components are supported:
|
||||||
|
|
||||||
|
* `avcodec`
|
||||||
|
* `avdevice`
|
||||||
|
* `avfilter`
|
||||||
|
* `avformat`
|
||||||
|
* `avresample`
|
||||||
|
* `avutil`
|
||||||
|
* `swresample`
|
||||||
|
* `swscale`
|
||||||
|
|
||||||
|
For each component, the following are provided:
|
||||||
|
|
||||||
|
* `FFMPEG_<component>_FOUND`: Libraries for the component.
|
||||||
|
* `FFMPEG_<component>_INCLUDE_DIRS`: Include directories for
|
||||||
|
the component.
|
||||||
|
* `FFMPEG_<component>_LIBRARIES`: Libraries for the component.
|
||||||
|
* `FFMPEG::<component>`: A target to use with `target_link_libraries`.
|
||||||
|
|
||||||
|
Note that only components requested with `COMPONENTS` or `OPTIONAL_COMPONENTS`
|
||||||
|
are guaranteed to set these variables or provide targets.
|
||||||
|
#]==]
|
||||||
|
|
||||||
|
function (_ffmpeg_find component headername)
|
||||||
|
if (${FFMPEG_${component}_FOUND})
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
find_path("FFMPEG_${component}_INCLUDE_DIR"
|
||||||
|
NAMES
|
||||||
|
"lib${component}/${headername}"
|
||||||
|
PATHS
|
||||||
|
"${FFMPEG_ROOT}/include"
|
||||||
|
~/Library/Frameworks
|
||||||
|
/Library/Frameworks
|
||||||
|
/usr/local/include
|
||||||
|
/usr/include
|
||||||
|
/sw/include # Fink
|
||||||
|
/opt/local/include # DarwinPorts
|
||||||
|
/opt/csw/include # Blastwave
|
||||||
|
/opt/include
|
||||||
|
/usr/freeware/include
|
||||||
|
PATH_SUFFIXES
|
||||||
|
ffmpeg
|
||||||
|
DOC "FFMPEG's ${component} include directory")
|
||||||
|
mark_as_advanced("FFMPEG_${component}_INCLUDE_DIR")
|
||||||
|
|
||||||
|
# On Windows, static FFMPEG is sometimes built as `lib<name>.a`.
|
||||||
|
if (WIN32)
|
||||||
|
list(APPEND CMAKE_FIND_LIBRARY_SUFFIXES ".a" ".lib")
|
||||||
|
list(APPEND CMAKE_FIND_LIBRARY_PREFIXES "" "lib")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
find_library("FFMPEG_${component}_LIBRARY"
|
||||||
|
NAMES
|
||||||
|
"${component}"
|
||||||
|
PATHS
|
||||||
|
"${FFMPEG_ROOT}/lib"
|
||||||
|
~/Library/Frameworks
|
||||||
|
/Library/Frameworks
|
||||||
|
/usr/local/lib
|
||||||
|
/usr/local/lib64
|
||||||
|
/usr/lib
|
||||||
|
/usr/lib64
|
||||||
|
/sw/lib
|
||||||
|
/opt/local/lib
|
||||||
|
/opt/csw/lib
|
||||||
|
/opt/lib
|
||||||
|
/usr/freeware/lib64
|
||||||
|
"${FFMPEG_ROOT}/bin"
|
||||||
|
DOC "FFMPEG's ${component} library")
|
||||||
|
mark_as_advanced("FFMPEG_${component}_LIBRARY")
|
||||||
|
|
||||||
|
if (FFMPEG_${component}_LIBRARY AND FFMPEG_${component}_INCLUDE_DIR)
|
||||||
|
set(_deps_found TRUE)
|
||||||
|
set(_deps_link)
|
||||||
|
foreach (_ffmpeg_dep IN LISTS ARGN)
|
||||||
|
if (TARGET "FFMPEG::${_ffmpeg_dep}")
|
||||||
|
list(APPEND _deps_link "FFMPEG::${_ffmpeg_dep}")
|
||||||
|
else ()
|
||||||
|
set(_deps_found FALSE)
|
||||||
|
endif ()
|
||||||
|
endforeach ()
|
||||||
|
if (_deps_found)
|
||||||
|
add_library("FFMPEG::${component}" UNKNOWN IMPORTED)
|
||||||
|
set_target_properties("FFMPEG::${component}" PROPERTIES
|
||||||
|
IMPORTED_LOCATION "${FFMPEG_${component}_LIBRARY}"
|
||||||
|
INTERFACE_INCLUDE_DIRECTORIES "${FFMPEG_${component}_INCLUDE_DIR}"
|
||||||
|
IMPORTED_LINK_INTERFACE_LIBRARIES "${_deps_link}")
|
||||||
|
set("FFMPEG_${component}_FOUND" 1
|
||||||
|
PARENT_SCOPE)
|
||||||
|
|
||||||
|
set(version_header_path "${FFMPEG_${component}_INCLUDE_DIR}/lib${component}/version.h")
|
||||||
|
if (EXISTS "${version_header_path}")
|
||||||
|
string(TOUPPER "${component}" component_upper)
|
||||||
|
file(STRINGS "${version_header_path}" version
|
||||||
|
REGEX "#define *LIB${component_upper}_VERSION_(MAJOR|MINOR|MICRO) ")
|
||||||
|
string(REGEX REPLACE ".*_MAJOR *\([0-9]*\).*" "\\1" major "${version}")
|
||||||
|
string(REGEX REPLACE ".*_MINOR *\([0-9]*\).*" "\\1" minor "${version}")
|
||||||
|
string(REGEX REPLACE ".*_MICRO *\([0-9]*\).*" "\\1" micro "${version}")
|
||||||
|
if (NOT major STREQUAL "" AND
|
||||||
|
NOT minor STREQUAL "" AND
|
||||||
|
NOT micro STREQUAL "")
|
||||||
|
set("FFMPEG_${component}_VERSION" "${major}.${minor}.${micro}"
|
||||||
|
PARENT_SCOPE)
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
else ()
|
||||||
|
set("FFMPEG_${component}_FOUND" 0
|
||||||
|
PARENT_SCOPE)
|
||||||
|
set(what)
|
||||||
|
if (NOT FFMPEG_${component}_LIBRARY)
|
||||||
|
set(what "library")
|
||||||
|
endif ()
|
||||||
|
if (NOT FFMPEG_${component}_INCLUDE_DIR)
|
||||||
|
if (what)
|
||||||
|
string(APPEND what " or headers")
|
||||||
|
else ()
|
||||||
|
set(what "headers")
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
set("FFMPEG_${component}_NOT_FOUND_MESSAGE"
|
||||||
|
"Could not find the ${what} for ${component}."
|
||||||
|
PARENT_SCOPE)
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
endfunction ()
|
||||||
|
|
||||||
|
_ffmpeg_find(avutil avutil.h)
|
||||||
|
_ffmpeg_find(avresample avresample.h
|
||||||
|
avutil)
|
||||||
|
_ffmpeg_find(swresample swresample.h
|
||||||
|
avutil)
|
||||||
|
_ffmpeg_find(swscale swscale.h
|
||||||
|
avutil)
|
||||||
|
_ffmpeg_find(avcodec avcodec.h
|
||||||
|
avutil)
|
||||||
|
_ffmpeg_find(avformat avformat.h
|
||||||
|
avcodec avutil)
|
||||||
|
_ffmpeg_find(avfilter avfilter.h
|
||||||
|
avutil)
|
||||||
|
_ffmpeg_find(avdevice avdevice.h
|
||||||
|
avformat avutil)
|
||||||
|
|
||||||
|
if (TARGET FFMPEG::avutil)
|
||||||
|
set(_ffmpeg_version_header_path "${FFMPEG_avutil_INCLUDE_DIR}/libavutil/ffversion.h")
|
||||||
|
if (EXISTS "${_ffmpeg_version_header_path}")
|
||||||
|
file(STRINGS "${_ffmpeg_version_header_path}" _ffmpeg_version
|
||||||
|
REGEX "FFMPEG_VERSION")
|
||||||
|
string(REGEX REPLACE ".*\"n?\(.*\)\"" "\\1" FFMPEG_VERSION "${_ffmpeg_version}")
|
||||||
|
unset(_ffmpeg_version)
|
||||||
|
else ()
|
||||||
|
set(FFMPEG_VERSION FFMPEG_VERSION-NOTFOUND)
|
||||||
|
endif ()
|
||||||
|
unset(_ffmpeg_version_header_path)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
set(FFMPEG_INCLUDE_DIRS)
|
||||||
|
set(FFMPEG_LIBRARIES)
|
||||||
|
set(_ffmpeg_required_vars)
|
||||||
|
foreach (_ffmpeg_component IN LISTS FFMPEG_FIND_COMPONENTS)
|
||||||
|
if (TARGET "FFMPEG::${_ffmpeg_component}")
|
||||||
|
set(FFMPEG_${_ffmpeg_component}_INCLUDE_DIRS
|
||||||
|
"${FFMPEG_${_ffmpeg_component}_INCLUDE_DIR}")
|
||||||
|
set(FFMPEG_${_ffmpeg_component}_LIBRARIES
|
||||||
|
"${FFMPEG_${_ffmpeg_component}_LIBRARY}")
|
||||||
|
list(APPEND FFMPEG_INCLUDE_DIRS
|
||||||
|
"${FFMPEG_${_ffmpeg_component}_INCLUDE_DIRS}")
|
||||||
|
list(APPEND FFMPEG_LIBRARIES
|
||||||
|
"${FFMPEG_${_ffmpeg_component}_LIBRARIES}")
|
||||||
|
if (FFMEG_FIND_REQUIRED_${_ffmpeg_component})
|
||||||
|
list(APPEND _ffmpeg_required_vars
|
||||||
|
"FFMPEG_${_ffmpeg_required_vars}_INCLUDE_DIRS"
|
||||||
|
"FFMPEG_${_ffmpeg_required_vars}_LIBRARIES")
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
endforeach ()
|
||||||
|
unset(_ffmpeg_component)
|
||||||
|
|
||||||
|
if (FFMPEG_INCLUDE_DIRS)
|
||||||
|
list(REMOVE_DUPLICATES FFMPEG_INCLUDE_DIRS)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
include(FindPackageHandleStandardArgs)
|
||||||
|
find_package_handle_standard_args(FFMPEG
|
||||||
|
REQUIRED_VARS FFMPEG_INCLUDE_DIRS FFMPEG_LIBRARIES ${_ffmpeg_required_vars}
|
||||||
|
VERSION_VAR FFMPEG_VERSION
|
||||||
|
HANDLE_COMPONENTS)
|
||||||
|
unset(_ffmpeg_required_vars)
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
# Olive - Non-Linear Video Editor
|
||||||
|
# Copyright (C) 2023 Olive Studios LLC
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program 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 General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
if(UNIX)
|
||||||
|
find_path(OTIO_BASE_DIR
|
||||||
|
include/opentimelineio/timeline.h
|
||||||
|
HINTS
|
||||||
|
"${OTIO_LOCATION}"
|
||||||
|
"$ENV{OTIO_LOCATION}"
|
||||||
|
"/opt/otio"
|
||||||
|
)
|
||||||
|
find_path(OTIO_LIBRARY_DIR
|
||||||
|
libopentimelineio.so
|
||||||
|
HINTS
|
||||||
|
"${OTIO_LOCATION}"
|
||||||
|
"$ENV{OTIO_LOCATION}"
|
||||||
|
"${OTIO_BASE_DIR}"
|
||||||
|
PATH_SUFFIXES
|
||||||
|
lib/
|
||||||
|
DOC
|
||||||
|
"OpenTimelineIO library path"
|
||||||
|
)
|
||||||
|
elseif(WIN32)
|
||||||
|
find_path(OTIO_BASE_DIR
|
||||||
|
include/opentimelineio/timeline.h
|
||||||
|
HINTS
|
||||||
|
"${OTIO_LOCATION}"
|
||||||
|
"$ENV{OTIO_LOCATION}"
|
||||||
|
)
|
||||||
|
find_path(OTIO_LIBRARY_DIR
|
||||||
|
opentimelineio.lib
|
||||||
|
HINTS
|
||||||
|
"${OTIO_LOCATION}"
|
||||||
|
"$ENV{OTIO_LOCATION}"
|
||||||
|
"${OTIO_BASE_DIR}"
|
||||||
|
PATH_SUFFIXES
|
||||||
|
lib/
|
||||||
|
DOC
|
||||||
|
"OpenTimelineIO library path"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
find_path(OTIO_INCLUDE_DIR
|
||||||
|
opentimelineio/timeline.h
|
||||||
|
HINTS
|
||||||
|
"${OTIO_LOCATION}"
|
||||||
|
"$ENV{OTIO_LOCATION}"
|
||||||
|
"${OTIO_BASE_DIR}"
|
||||||
|
PATH_SUFFIXES
|
||||||
|
include/
|
||||||
|
DOC
|
||||||
|
"OpenTimelineIO headers path"
|
||||||
|
)
|
||||||
|
|
||||||
|
list(APPEND OTIO_INCLUDE_DIRS ${OTIO_INCLUDE_DIR})
|
||||||
|
|
||||||
|
find_path(OTIO_DEPS_INCLUDE_DIR
|
||||||
|
any/any.hpp
|
||||||
|
HINTS
|
||||||
|
"${OTIO_LOCATION}"
|
||||||
|
"$ENV{OTIO_LOCATION}"
|
||||||
|
"${OTIO_BASE_DIR}"
|
||||||
|
PATH_SUFFIXES
|
||||||
|
include/opentimelineio/deps/
|
||||||
|
DOC
|
||||||
|
"OpenTimelineIO headers path"
|
||||||
|
)
|
||||||
|
|
||||||
|
list(APPEND OTIO_INCLUDE_DIRS ${OTIO_DEPS_INCLUDE_DIR})
|
||||||
|
|
||||||
|
find_path(OT_INCLUDE_DIR
|
||||||
|
opentime/rationalTime.h
|
||||||
|
HINTS
|
||||||
|
"${OTIO_LOCATION}"
|
||||||
|
"$ENV{OTIO_LOCATION}"
|
||||||
|
"${OTIO_BASE_DIR}"
|
||||||
|
PATH_SUFFIXES
|
||||||
|
include/
|
||||||
|
DOC
|
||||||
|
"OpenTime headers path"
|
||||||
|
)
|
||||||
|
|
||||||
|
list(APPEND OTIO_INCLUDE_DIRS ${OT_INCLUDE_DIR})
|
||||||
|
|
||||||
|
find_library(OTIO_LIBRARY
|
||||||
|
opentimelineio
|
||||||
|
HINTS
|
||||||
|
"${OTIO_LOCATION}"
|
||||||
|
"$ENV{OTIO_LOCATION}"
|
||||||
|
"${OTIO_BASE_DIR}"
|
||||||
|
PATH_SUFFIXES
|
||||||
|
lib/
|
||||||
|
DOC
|
||||||
|
"OTIO's ${OTIO_LIB} library path"
|
||||||
|
)
|
||||||
|
|
||||||
|
list(APPEND OTIO_LIBRARIES ${OTIO_LIBRARY})
|
||||||
|
|
||||||
|
find_library(OT_LIBRARY
|
||||||
|
opentime
|
||||||
|
HINTS
|
||||||
|
"${OTIO_LOCATION}"
|
||||||
|
"$ENV{OTIO_LOCATION}"
|
||||||
|
"${OTIO_BASE_DIR}"
|
||||||
|
PATH_SUFFIXES
|
||||||
|
lib/
|
||||||
|
DOC
|
||||||
|
"OpenTime's ${OTIO_LIB} library path"
|
||||||
|
)
|
||||||
|
|
||||||
|
list(APPEND OTIO_LIBRARIES ${OT_LIBRARY})
|
||||||
|
|
||||||
|
include(FindPackageHandleStandardArgs)
|
||||||
|
|
||||||
|
find_package_handle_standard_args(OpenTimelineIO
|
||||||
|
REQUIRED_VARS
|
||||||
|
OTIO_LIBRARIES
|
||||||
|
OTIO_INCLUDE_DIRS
|
||||||
|
OTIO_DEPS_INCLUDE_DIR
|
||||||
|
)
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* Olive Community Edition - Non-Linear Video Editor
|
||||||
|
* Copyright (C) 2025 Olive CE Team
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program 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 General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef LIBOLIVECORE_H
|
||||||
|
#define LIBOLIVECORE_H
|
||||||
|
|
||||||
|
#include "render/audioparams.h"
|
||||||
|
#include "render/pixelformat.h"
|
||||||
|
#include "render/samplebuffer.h"
|
||||||
|
#include "render/sampleformat.h"
|
||||||
|
#include "util/bezier.h"
|
||||||
|
#include "util/color.h"
|
||||||
|
#include "util/cpuoptimize.h"
|
||||||
|
#include "util/log.h"
|
||||||
|
#include "util/math.h"
|
||||||
|
#include "util/rational.h"
|
||||||
|
#include "util/stringutils.h"
|
||||||
|
#include "util/tests.h"
|
||||||
|
#include "util/timecodefunctions.h"
|
||||||
|
#include "util/timerange.h"
|
||||||
|
#include "util/value.h"
|
||||||
|
|
||||||
|
#endif // LIBOLIVECORE_H
|
||||||
@@ -0,0 +1,315 @@
|
|||||||
|
/***
|
||||||
|
|
||||||
|
Olive - Non-Linear Video Editor
|
||||||
|
Copyright (C) 2023 Olive Studios LLC
|
||||||
|
Modifications Copyright (C) 2025 mikesolar
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program 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 General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***/
|
||||||
|
|
||||||
|
#ifndef LIBOLIVECORE_AUDIOPARAMS_H
|
||||||
|
#define LIBOLIVECORE_AUDIOPARAMS_H
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include <libavutil/channel_layout.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "sampleformat.h"
|
||||||
|
#include "../util/rational.h"
|
||||||
|
|
||||||
|
namespace olive::core
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Audio parameters class managing audio stream configuration
|
||||||
|
*
|
||||||
|
* CRITICAL NOTE: This class manages AVChannelLayout which contains dynamic memory
|
||||||
|
* (custom channel maps via u.map pointer). Prior to the Rule of Three implementation,
|
||||||
|
* shallow copies could occur when:
|
||||||
|
* - AudioParams stored in QVector (QVector reallocations)
|
||||||
|
* - AudioParams passed by value to RenderVideoParams
|
||||||
|
* - AudioParams copied during Node graph duplication in ProjectCopier
|
||||||
|
*
|
||||||
|
* When shallow copies occurred, one copy's set_channel_layout() could free the
|
||||||
|
* shared u.map pointer, corrupting other copies. This manifested as:
|
||||||
|
* - channel_layouts=0x0 errors in AudioProcessor::Open()
|
||||||
|
* - is_valid() returning false unexpectedly
|
||||||
|
*
|
||||||
|
* The Rule of Three (copy ctor, copy assignment, destructor) was added to ensure
|
||||||
|
* proper deep copies of AVChannelLayout using av_channel_layout_copy().
|
||||||
|
*/
|
||||||
|
class AudioParams {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Default constructor creates invalid AudioParams
|
||||||
|
* sample_rate=0, channel_layout empty, format=INVALID
|
||||||
|
*/
|
||||||
|
AudioParams()
|
||||||
|
: sample_rate_(0)
|
||||||
|
, channel_layout_{}
|
||||||
|
, channel_count_(0)
|
||||||
|
, format_(SampleFormat::INVALID)
|
||||||
|
{
|
||||||
|
set_default_footage_parameters();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructor from AVChannelLayout (deep copy)
|
||||||
|
* @param sample_rate Audio sample rate (e.g., 48000)
|
||||||
|
* @param channel_layout FFmpeg channel layout (copied via av_channel_layout_copy)
|
||||||
|
* @param format Sample format (e.g., SampleFormat::F32P)
|
||||||
|
*
|
||||||
|
* NOTE: The channel_layout parameter is deep-copied. The original can be
|
||||||
|
* safely uninit'd after this constructor returns.
|
||||||
|
*/
|
||||||
|
AudioParams(const int &sample_rate, const AVChannelLayout &channel_layout,
|
||||||
|
const SampleFormat &format)
|
||||||
|
: sample_rate_(sample_rate)
|
||||||
|
, channel_layout_{}
|
||||||
|
, channel_count_(0)
|
||||||
|
, format_(format)
|
||||||
|
{
|
||||||
|
set_default_footage_parameters();
|
||||||
|
timebase_ = sample_rate_as_time_base();
|
||||||
|
av_channel_layout_uninit(&channel_layout_);
|
||||||
|
av_channel_layout_copy(&channel_layout_, &channel_layout);
|
||||||
|
|
||||||
|
// Cache channel count from the copied layout
|
||||||
|
calculate_channel_count();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructor from channel layout mask
|
||||||
|
* @param sample_rate Audio sample rate
|
||||||
|
* @param channel_layout Channel layout mask (e.g., AV_CH_LAYOUT_STEREO)
|
||||||
|
* @param format Sample format
|
||||||
|
*
|
||||||
|
* This is the most common constructor used in Olive. The mask is converted
|
||||||
|
* to AVChannelLayout via av_channel_layout_from_mask().
|
||||||
|
*/
|
||||||
|
AudioParams(const int &sample_rate, uint64_t channel_layout,
|
||||||
|
const SampleFormat &format)
|
||||||
|
: sample_rate_(sample_rate)
|
||||||
|
, channel_layout_{}
|
||||||
|
, channel_count_(0)
|
||||||
|
, format_(format)
|
||||||
|
{
|
||||||
|
set_default_footage_parameters();
|
||||||
|
timebase_ = sample_rate_as_time_base();
|
||||||
|
av_channel_layout_uninit(&channel_layout_);
|
||||||
|
av_channel_layout_from_mask(&channel_layout_, channel_layout);
|
||||||
|
// Cache channel count
|
||||||
|
calculate_channel_count();
|
||||||
|
}
|
||||||
|
int sample_rate() const
|
||||||
|
{
|
||||||
|
return sample_rate_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_sample_rate(int sample_rate)
|
||||||
|
{
|
||||||
|
sample_rate_ = sample_rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AVChannelLayout &channel_layout() const
|
||||||
|
{
|
||||||
|
return channel_layout_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set channel layout from AVChannelLayout (deep copy)
|
||||||
|
* @param channel_layout Source channel layout to copy
|
||||||
|
*
|
||||||
|
* CRITICAL: This function first uninitializes the current layout (freeing any
|
||||||
|
* dynamic memory), then deep-copies the new layout. This is safe only if
|
||||||
|
* copies are properly managed via Rule of Three.
|
||||||
|
*
|
||||||
|
* If called on a shallow-copied AudioParams, this would corrupt other copies
|
||||||
|
* that share the same u.map pointer.
|
||||||
|
*/
|
||||||
|
void set_channel_layout(const AVChannelLayout &channel_layout)
|
||||||
|
{
|
||||||
|
av_channel_layout_uninit(&channel_layout_);
|
||||||
|
av_channel_layout_copy(&channel_layout_, &channel_layout);
|
||||||
|
calculate_channel_count();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set channel layout from mask
|
||||||
|
* @param mask Channel layout mask (e.g., AV_CH_LAYOUT_STEREO)
|
||||||
|
*/
|
||||||
|
void set_channel_layout(uint64_t mask)
|
||||||
|
{
|
||||||
|
av_channel_layout_uninit(&channel_layout_);
|
||||||
|
av_channel_layout_from_mask(&channel_layout_, mask);
|
||||||
|
calculate_channel_count();
|
||||||
|
}
|
||||||
|
rational time_base() const
|
||||||
|
{
|
||||||
|
return timebase_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_time_base(const rational &timebase)
|
||||||
|
{
|
||||||
|
timebase_ = timebase;
|
||||||
|
}
|
||||||
|
|
||||||
|
rational sample_rate_as_time_base() const
|
||||||
|
{
|
||||||
|
return rational(1, sample_rate());
|
||||||
|
}
|
||||||
|
|
||||||
|
SampleFormat format() const
|
||||||
|
{
|
||||||
|
return format_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_format(SampleFormat format)
|
||||||
|
{
|
||||||
|
format_ = format;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool enabled() const
|
||||||
|
{
|
||||||
|
return enabled_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_enabled(bool e)
|
||||||
|
{
|
||||||
|
enabled_ = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
int stream_index() const
|
||||||
|
{
|
||||||
|
return stream_index_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_stream_index(int s)
|
||||||
|
{
|
||||||
|
stream_index_ = s;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t duration() const
|
||||||
|
{
|
||||||
|
return duration_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_duration(int64_t duration)
|
||||||
|
{
|
||||||
|
duration_ = duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t time_to_bytes(const double &time) const;
|
||||||
|
int64_t time_to_bytes(const rational &time) const;
|
||||||
|
int64_t time_to_bytes_per_channel(const double &time) const;
|
||||||
|
int64_t time_to_bytes_per_channel(const rational &time) const;
|
||||||
|
int64_t time_to_samples(const double &time) const;
|
||||||
|
int64_t time_to_samples(const rational &time) const;
|
||||||
|
int64_t samples_to_bytes(const int64_t &samples) const;
|
||||||
|
int64_t samples_to_bytes_per_channel(const int64_t &samples) const;
|
||||||
|
rational samples_to_time(const int64_t &samples) const;
|
||||||
|
int64_t bytes_to_samples(const int64_t &bytes) const;
|
||||||
|
rational bytes_to_time(const int64_t &bytes) const;
|
||||||
|
rational bytes_per_channel_to_time(const int64_t &bytes) const;
|
||||||
|
int channel_count() const;
|
||||||
|
int bytes_per_sample_per_channel() const;
|
||||||
|
int bits_per_sample() const;
|
||||||
|
bool is_valid() const;
|
||||||
|
|
||||||
|
bool operator==(const AudioParams &other) const;
|
||||||
|
bool operator!=(const AudioParams &other) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name Rule of Three Implementation
|
||||||
|
*
|
||||||
|
* These are required because AVChannelLayout (FFmpeg >= 5.0) contains a union
|
||||||
|
* with a pointer member (u.map for custom channel maps). Without proper
|
||||||
|
* deep copy management:
|
||||||
|
*
|
||||||
|
* 1. Default copy constructor: Shallow copies u.map pointer, leading to
|
||||||
|
* double-free when original and copy are destroyed
|
||||||
|
* 2. Default copy assignment: Same issue as copy constructor
|
||||||
|
* 3. Default destructor: Doesn't free u.map, causing memory leaks
|
||||||
|
*
|
||||||
|
* The implementations use av_channel_layout_copy() and av_channel_layout_uninit()
|
||||||
|
* for proper FFmpeg-managed memory handling.
|
||||||
|
*
|
||||||
|
* Context where this matters:
|
||||||
|
* - QVector<AudioParams> in FootageDescription (vector reallocations)
|
||||||
|
* - RenderVideoParams passing AudioParams by value
|
||||||
|
* - ProjectCopier duplicating node graphs with audio parameters
|
||||||
|
*/
|
||||||
|
///@{
|
||||||
|
AudioParams(const AudioParams &other);
|
||||||
|
AudioParams &operator=(const AudioParams &other);
|
||||||
|
~AudioParams();
|
||||||
|
///@}
|
||||||
|
|
||||||
|
static const std::vector<uint64_t> kSupportedChannelLayouts;
|
||||||
|
static const std::vector<int> kSupportedSampleRates;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void set_default_footage_parameters()
|
||||||
|
{
|
||||||
|
enabled_ = true;
|
||||||
|
stream_index_ = 0;
|
||||||
|
duration_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Updates channel_count_ from the current channel_layout_
|
||||||
|
* Called after any channel layout modification.
|
||||||
|
*/
|
||||||
|
void calculate_channel_count();
|
||||||
|
|
||||||
|
int sample_rate_; ///< Audio sample rate in Hz (e.g., 48000)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief FFmpeg channel layout structure
|
||||||
|
*
|
||||||
|
* WARNING: This struct contains a union with a pointer member (u.map) when
|
||||||
|
* using custom channel layouts (order == AV_CHANNEL_ORDER_CUSTOM). The pointer
|
||||||
|
* must be properly managed via av_channel_layout_copy/uninit.
|
||||||
|
*
|
||||||
|
* Layout variants:
|
||||||
|
* - order == AV_CHANNEL_ORDER_UNSPEC: u.mask is undefined, nb_channels valid
|
||||||
|
* - order == AV_CHANNEL_ORDER_NATIVE: u.mask contains channel bitmask
|
||||||
|
* - order == AV_CHANNEL_ORDER_CUSTOM: u.map points to AVChannelCustom array
|
||||||
|
*
|
||||||
|
* Corruption symptoms:
|
||||||
|
* - u.mask == 0 when order should be NATIVE
|
||||||
|
* - av_channel_layout_check() returns false
|
||||||
|
* - is_valid() returns false
|
||||||
|
*/
|
||||||
|
AVChannelLayout channel_layout_;
|
||||||
|
|
||||||
|
int channel_count_; ///< Cached channel count from layout
|
||||||
|
|
||||||
|
SampleFormat format_; ///< Audio sample format
|
||||||
|
|
||||||
|
// Footage-specific parameters (serialized with footage metadata)
|
||||||
|
int enabled_; // Using int instead of bool fixes GCC 11 stringop-overflow issue (byte alignment)
|
||||||
|
int stream_index_; ///< Index in the source file's stream list
|
||||||
|
int64_t duration_; ///< Stream duration in timebase units
|
||||||
|
rational timebase_; ///< Timebase for this audio stream
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // LIBOLIVECORE_AUDIOPARAMS_H
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
/***
|
||||||
|
|
||||||
|
Olive - Non-Linear Video Editor
|
||||||
|
Copyright (C) 2023 Olive Studios LLC
|
||||||
|
Modifications Copyright (C) 2025 mikesolar
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program 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 General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***/
|
||||||
|
|
||||||
|
#ifndef LIBOLIVECORE_PIXELFORMAT_H
|
||||||
|
#define LIBOLIVECORE_PIXELFORMAT_H
|
||||||
|
#include "ofxCore.h"
|
||||||
|
#include <string>
|
||||||
|
namespace olive::core
|
||||||
|
{
|
||||||
|
|
||||||
|
class PixelFormat {
|
||||||
|
public:
|
||||||
|
enum Format { INVALID = -1, U8, U10, U16, F16, F32, COUNT };
|
||||||
|
|
||||||
|
PixelFormat(Format f = INVALID)
|
||||||
|
{
|
||||||
|
f_ = f;
|
||||||
|
}
|
||||||
|
|
||||||
|
operator Format() const
|
||||||
|
{
|
||||||
|
return f_;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PixelFormat from_ofx(std::string ofxFormat){
|
||||||
|
if(ofxFormat == kOfxBitDepthByte){
|
||||||
|
return PixelFormat::U8;
|
||||||
|
}
|
||||||
|
else if (ofxFormat == kOfxBitDepthShort){
|
||||||
|
return PixelFormat::U16;
|
||||||
|
}
|
||||||
|
else if(ofxFormat == kOfxBitDepthHalf){
|
||||||
|
return PixelFormat::F16;
|
||||||
|
}
|
||||||
|
else if(ofxFormat == kOfxBitDepthFloat){
|
||||||
|
return PixelFormat::F32;
|
||||||
|
}
|
||||||
|
return PixelFormat::INVALID;
|
||||||
|
}
|
||||||
|
static int byte_count(Format f)
|
||||||
|
{
|
||||||
|
switch (f) {
|
||||||
|
case INVALID:
|
||||||
|
case COUNT:
|
||||||
|
break;
|
||||||
|
case U8:
|
||||||
|
return 1;
|
||||||
|
case U10:
|
||||||
|
return 4; // packed RGBA10A2, treated as 4 bytes per pixel
|
||||||
|
case U16:
|
||||||
|
case F16:
|
||||||
|
return 2;
|
||||||
|
case F32:
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *to_string() const
|
||||||
|
{
|
||||||
|
switch (f_) {
|
||||||
|
case U8:
|
||||||
|
return "u8";
|
||||||
|
case U10:
|
||||||
|
return "u10";
|
||||||
|
case U16:
|
||||||
|
return "u16";
|
||||||
|
case F16:
|
||||||
|
return "f16";
|
||||||
|
case F32:
|
||||||
|
return "f32";
|
||||||
|
case INVALID:
|
||||||
|
case COUNT:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
int byte_count() const
|
||||||
|
{
|
||||||
|
return byte_count(f_);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_float(Format f)
|
||||||
|
{
|
||||||
|
switch (f) {
|
||||||
|
case INVALID:
|
||||||
|
case COUNT:
|
||||||
|
case U8:
|
||||||
|
case U10:
|
||||||
|
case U16:
|
||||||
|
break;
|
||||||
|
case F16:
|
||||||
|
case F32:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_float() const
|
||||||
|
{
|
||||||
|
return is_float(f_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Format f_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // LIBOLIVECORE_PIXELFORMAT_H
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
/***
|
||||||
|
|
||||||
|
Olive - Non-Linear Video Editor
|
||||||
|
Copyright (C) 2023 Olive Studios LLC
|
||||||
|
Modifications Copyright (C) 2025 mikesolar
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program 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 General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***/
|
||||||
|
|
||||||
|
#ifndef LIBOLIVECORE_SAMPLEBUFFER_H
|
||||||
|
#define LIBOLIVECORE_SAMPLEBUFFER_H
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "audioparams.h"
|
||||||
|
#include "../util/rational.h"
|
||||||
|
|
||||||
|
namespace olive::core
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A buffer of audio samples
|
||||||
|
*
|
||||||
|
* Audio samples in this structure are always stored in PLANAR (separated by channel). This is done to simplify audio
|
||||||
|
* rendering code. This replaces the old system of using QByteArrays (containing packed audio) and while SampleBuffer
|
||||||
|
* replaces many of those in the rendering/processing side of things, QByteArrays are currently still in use for
|
||||||
|
* playback, including reading to and from the cache.
|
||||||
|
*/
|
||||||
|
class SampleBuffer {
|
||||||
|
public:
|
||||||
|
SampleBuffer();
|
||||||
|
SampleBuffer(const AudioParams &audio_params, const rational &length);
|
||||||
|
SampleBuffer(const AudioParams &audio_params, size_t samples_per_channel);
|
||||||
|
|
||||||
|
SampleBuffer rip_channel(int channel) const;
|
||||||
|
std::vector<float> rip_channel_vector(int channel) const;
|
||||||
|
|
||||||
|
const AudioParams &audio_params() const;
|
||||||
|
void set_audio_params(const AudioParams ¶ms);
|
||||||
|
|
||||||
|
const size_t &sample_count() const
|
||||||
|
{
|
||||||
|
return sample_count_per_channel_;
|
||||||
|
}
|
||||||
|
void set_sample_count(const size_t &sample_count);
|
||||||
|
void set_sample_count(const rational &length)
|
||||||
|
{
|
||||||
|
set_sample_count(audio_params_.time_to_samples(length));
|
||||||
|
}
|
||||||
|
|
||||||
|
float *data(int channel)
|
||||||
|
{
|
||||||
|
return data_[channel].data();
|
||||||
|
}
|
||||||
|
|
||||||
|
const float *data(int channel) const
|
||||||
|
{
|
||||||
|
return data_.at(channel).data();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<float *> to_raw_ptrs()
|
||||||
|
{
|
||||||
|
std::vector<float *> r(data_.size());
|
||||||
|
for (size_t i = 0; i < r.size(); i++) {
|
||||||
|
r[i] = data_[i].data();
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
int channel_count() const
|
||||||
|
{
|
||||||
|
return data_.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_allocated() const
|
||||||
|
{
|
||||||
|
return !data_.empty();
|
||||||
|
}
|
||||||
|
void allocate();
|
||||||
|
void destroy();
|
||||||
|
|
||||||
|
void reverse();
|
||||||
|
void speed(double speed);
|
||||||
|
|
||||||
|
void transform_volume(float f);
|
||||||
|
void transform_volume_for_channel(int channel, float volume);
|
||||||
|
static void transform_volume(float f, const SampleBuffer *input,
|
||||||
|
SampleBuffer *output);
|
||||||
|
static void transform_volume_for_channel(int channel, float volume,
|
||||||
|
const SampleBuffer *input,
|
||||||
|
SampleBuffer *output);
|
||||||
|
|
||||||
|
void transform_volume_for_sample(size_t sample_index, float volume);
|
||||||
|
void transform_volume_for_sample_on_channel(size_t sample_index,
|
||||||
|
int channel, float volume);
|
||||||
|
|
||||||
|
void clamp();
|
||||||
|
|
||||||
|
void silence();
|
||||||
|
void silence(size_t start_sample, size_t end_sample);
|
||||||
|
void silence_bytes(size_t start_byte, size_t end_byte);
|
||||||
|
|
||||||
|
void set(int channel, const float *data, size_t sample_offset,
|
||||||
|
size_t sample_length);
|
||||||
|
void set(int channel, const float *data, size_t sample_length)
|
||||||
|
{
|
||||||
|
set(channel, data, 0, sample_length);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fast_set(const SampleBuffer &other, int to, int from = -1);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void clamp_channel(int channel);
|
||||||
|
|
||||||
|
AudioParams audio_params_;
|
||||||
|
|
||||||
|
size_t sample_count_per_channel_;
|
||||||
|
|
||||||
|
std::vector<std::vector<float>> data_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // LIBOLIVECORE_SAMPLEBUFFER_H
|
||||||
@@ -0,0 +1,288 @@
|
|||||||
|
/***
|
||||||
|
|
||||||
|
Olive - Non-Linear Video Editor
|
||||||
|
Copyright (C) 2023 Olive Studios LLC
|
||||||
|
Modifications Copyright (C) 2025 mikesolar
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program 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 General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***/
|
||||||
|
|
||||||
|
#ifndef LIBOLIVECORE_SAMPLEFORMAT_H
|
||||||
|
#define LIBOLIVECORE_SAMPLEFORMAT_H
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace olive::core
|
||||||
|
{
|
||||||
|
|
||||||
|
class SampleFormat {
|
||||||
|
public:
|
||||||
|
enum Format {
|
||||||
|
INVALID = -1,
|
||||||
|
|
||||||
|
U8P,
|
||||||
|
S16P,
|
||||||
|
S32P,
|
||||||
|
S64P,
|
||||||
|
F32P,
|
||||||
|
F64P,
|
||||||
|
|
||||||
|
U8,
|
||||||
|
S16,
|
||||||
|
S32,
|
||||||
|
S64,
|
||||||
|
F32,
|
||||||
|
F64,
|
||||||
|
|
||||||
|
COUNT,
|
||||||
|
|
||||||
|
PLANAR_START = U8P,
|
||||||
|
PACKED_START = U8,
|
||||||
|
PLANAR_END = PACKED_START,
|
||||||
|
PACKED_END = COUNT,
|
||||||
|
};
|
||||||
|
|
||||||
|
SampleFormat(Format f = INVALID)
|
||||||
|
{
|
||||||
|
f_ = f;
|
||||||
|
}
|
||||||
|
|
||||||
|
operator Format() const
|
||||||
|
{
|
||||||
|
return f_;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int byte_count(Format f)
|
||||||
|
{
|
||||||
|
switch (f) {
|
||||||
|
case U8:
|
||||||
|
case U8P:
|
||||||
|
return 1;
|
||||||
|
case S16:
|
||||||
|
case S16P:
|
||||||
|
return 2;
|
||||||
|
case S32:
|
||||||
|
case F32:
|
||||||
|
case S32P:
|
||||||
|
case F32P:
|
||||||
|
return 4;
|
||||||
|
case S64:
|
||||||
|
case F64:
|
||||||
|
case S64P:
|
||||||
|
case F64P:
|
||||||
|
return 8;
|
||||||
|
case INVALID:
|
||||||
|
case COUNT:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int byte_count() const
|
||||||
|
{
|
||||||
|
return byte_count(f_);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string to_string(Format f)
|
||||||
|
{
|
||||||
|
switch (f) {
|
||||||
|
case INVALID:
|
||||||
|
case COUNT:
|
||||||
|
break;
|
||||||
|
case U8:
|
||||||
|
return "u8";
|
||||||
|
case S16:
|
||||||
|
return "s16";
|
||||||
|
case S32:
|
||||||
|
return "s32";
|
||||||
|
case S64:
|
||||||
|
return "s64";
|
||||||
|
case F32:
|
||||||
|
return "f32";
|
||||||
|
case F64:
|
||||||
|
return "f64";
|
||||||
|
case U8P:
|
||||||
|
return "u8p";
|
||||||
|
case S16P:
|
||||||
|
return "s16p";
|
||||||
|
case S32P:
|
||||||
|
return "s32p";
|
||||||
|
case S64P:
|
||||||
|
return "s64p";
|
||||||
|
case F32P:
|
||||||
|
return "f32p";
|
||||||
|
case F64P:
|
||||||
|
return "f64p";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string to_string() const
|
||||||
|
{
|
||||||
|
return to_string(f_);
|
||||||
|
}
|
||||||
|
|
||||||
|
static SampleFormat from_string(const std::string &s)
|
||||||
|
{
|
||||||
|
if (s.empty()) {
|
||||||
|
return INVALID;
|
||||||
|
} else if (s == "u8") {
|
||||||
|
return U8;
|
||||||
|
} else if (s == "s16") {
|
||||||
|
return S16;
|
||||||
|
} else if (s == "s32") {
|
||||||
|
return S32;
|
||||||
|
} else if (s == "s64") {
|
||||||
|
return S64;
|
||||||
|
} else if (s == "f32") {
|
||||||
|
return F32;
|
||||||
|
} else if (s == "f64") {
|
||||||
|
return F64;
|
||||||
|
} else if (s == "u8p") {
|
||||||
|
return U8P;
|
||||||
|
} else if (s == "s16p") {
|
||||||
|
return S16P;
|
||||||
|
} else if (s == "s32p") {
|
||||||
|
return S32P;
|
||||||
|
} else if (s == "s64p") {
|
||||||
|
return S64P;
|
||||||
|
} else if (s == "f32p") {
|
||||||
|
return F32P;
|
||||||
|
} else if (s == "f64p") {
|
||||||
|
return F64P;
|
||||||
|
} else {
|
||||||
|
// Deprecated: sample formats used to be serialized as an integer. Handle that here, but we'll
|
||||||
|
// probably remove that eventually.
|
||||||
|
try {
|
||||||
|
int i = std::stoi(s);
|
||||||
|
if (i > INVALID && i < COUNT) {
|
||||||
|
return static_cast<Format>(i);
|
||||||
|
}
|
||||||
|
} catch (const std::invalid_argument &e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Failed to deserialize from string
|
||||||
|
return INVALID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_packed(Format f)
|
||||||
|
{
|
||||||
|
return f >= PACKED_START && f < PACKED_END;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_packed() const
|
||||||
|
{
|
||||||
|
return is_packed(f_);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_planar(Format f)
|
||||||
|
{
|
||||||
|
return f >= PLANAR_START && f < PLANAR_END;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_planar() const
|
||||||
|
{
|
||||||
|
return is_planar(f_);
|
||||||
|
}
|
||||||
|
|
||||||
|
static SampleFormat to_packed_equivalent(SampleFormat fmt)
|
||||||
|
{
|
||||||
|
switch (fmt) {
|
||||||
|
// For packed input, just return input
|
||||||
|
case U8:
|
||||||
|
case S16:
|
||||||
|
case S32:
|
||||||
|
case S64:
|
||||||
|
case F32:
|
||||||
|
case F64:
|
||||||
|
return fmt;
|
||||||
|
|
||||||
|
// Convert to packed
|
||||||
|
case U8P:
|
||||||
|
return U8;
|
||||||
|
case S16P:
|
||||||
|
return S16;
|
||||||
|
case S32P:
|
||||||
|
return S32;
|
||||||
|
case S64P:
|
||||||
|
return S64;
|
||||||
|
case F32P:
|
||||||
|
return F32;
|
||||||
|
case F64P:
|
||||||
|
return F64;
|
||||||
|
|
||||||
|
case INVALID:
|
||||||
|
case COUNT:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
SampleFormat to_packed_equivalent() const
|
||||||
|
{
|
||||||
|
return to_packed_equivalent(f_);
|
||||||
|
}
|
||||||
|
|
||||||
|
static SampleFormat to_planar_equivalent(SampleFormat fmt)
|
||||||
|
{
|
||||||
|
switch (fmt) {
|
||||||
|
// Convert to planar
|
||||||
|
case U8:
|
||||||
|
return U8P;
|
||||||
|
case S16:
|
||||||
|
return S16P;
|
||||||
|
case S32:
|
||||||
|
return S32P;
|
||||||
|
case S64:
|
||||||
|
return S64P;
|
||||||
|
case F32:
|
||||||
|
return F32P;
|
||||||
|
case F64:
|
||||||
|
return F64P;
|
||||||
|
|
||||||
|
// For planar input, just return input
|
||||||
|
case U8P:
|
||||||
|
case S16P:
|
||||||
|
case S32P:
|
||||||
|
case S64P:
|
||||||
|
case F32P:
|
||||||
|
case F64P:
|
||||||
|
return fmt;
|
||||||
|
|
||||||
|
case INVALID:
|
||||||
|
case COUNT:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
SampleFormat to_planar_equivalent() const
|
||||||
|
{
|
||||||
|
return to_planar_equivalent(f_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Format f_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // LIBOLIVECORE_SAMPLEFORMAT_H
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
/***
|
||||||
|
|
||||||
|
Olive - Non-Linear Video Editor
|
||||||
|
Copyright (C) 2023 Olive Studios LLC
|
||||||
|
Modifications Copyright (C) 2025 mikesolar
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program 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 General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***/
|
||||||
|
|
||||||
|
#ifndef LIBOLIVECORE_BEZIER_H
|
||||||
|
#define LIBOLIVECORE_BEZIER_H
|
||||||
|
|
||||||
|
#include <Imath/ImathVec.h>
|
||||||
|
|
||||||
|
namespace olive::core
|
||||||
|
{
|
||||||
|
|
||||||
|
class Bezier {
|
||||||
|
public:
|
||||||
|
Bezier();
|
||||||
|
Bezier(double x, double y);
|
||||||
|
Bezier(double x, double y, double cp1_x, double cp1_y, double cp2_x,
|
||||||
|
double cp2_y);
|
||||||
|
|
||||||
|
const double &x() const
|
||||||
|
{
|
||||||
|
return x_;
|
||||||
|
}
|
||||||
|
const double &y() const
|
||||||
|
{
|
||||||
|
return y_;
|
||||||
|
}
|
||||||
|
const double &cp1_x() const
|
||||||
|
{
|
||||||
|
return cp1_x_;
|
||||||
|
}
|
||||||
|
const double &cp1_y() const
|
||||||
|
{
|
||||||
|
return cp1_y_;
|
||||||
|
}
|
||||||
|
const double &cp2_x() const
|
||||||
|
{
|
||||||
|
return cp2_x_;
|
||||||
|
}
|
||||||
|
const double &cp2_y() const
|
||||||
|
{
|
||||||
|
return cp2_y_;
|
||||||
|
}
|
||||||
|
|
||||||
|
Imath::V2d to_vec() const
|
||||||
|
{
|
||||||
|
return Imath::V2d(x_, y_);
|
||||||
|
}
|
||||||
|
|
||||||
|
Imath::V2d control_point_1_to_vec() const
|
||||||
|
{
|
||||||
|
return Imath::V2d(cp1_x_, cp1_y_);
|
||||||
|
}
|
||||||
|
|
||||||
|
Imath::V2d control_point_2_to_vec() const
|
||||||
|
{
|
||||||
|
return Imath::V2d(cp2_x_, cp2_y_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_x(const double &x)
|
||||||
|
{
|
||||||
|
x_ = x;
|
||||||
|
}
|
||||||
|
void set_y(const double &y)
|
||||||
|
{
|
||||||
|
y_ = y;
|
||||||
|
}
|
||||||
|
void set_cp1_x(const double &cp1_x)
|
||||||
|
{
|
||||||
|
cp1_x_ = cp1_x;
|
||||||
|
}
|
||||||
|
void set_cp1_y(const double &cp1_y)
|
||||||
|
{
|
||||||
|
cp1_y_ = cp1_y;
|
||||||
|
}
|
||||||
|
void set_cp2_x(const double &cp2_x)
|
||||||
|
{
|
||||||
|
cp2_x_ = cp2_x;
|
||||||
|
}
|
||||||
|
void set_cp2_y(const double &cp2_y)
|
||||||
|
{
|
||||||
|
cp2_y_ = cp2_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
static double QuadraticXtoT(double x, double a, double b, double c);
|
||||||
|
|
||||||
|
static double QuadraticTtoY(double a, double b, double c, double t);
|
||||||
|
|
||||||
|
static double QuadraticXtoY(double x, const Imath::V2d &a,
|
||||||
|
const Imath::V2d &b, const Imath::V2d &c)
|
||||||
|
{
|
||||||
|
return QuadraticTtoY(a.y, b.y, c.y, QuadraticXtoT(x, a.x, b.x, c.x));
|
||||||
|
}
|
||||||
|
|
||||||
|
static double CubicXtoT(double x, double a, double b, double c, double d);
|
||||||
|
|
||||||
|
static double CubicTtoY(double a, double b, double c, double d, double t);
|
||||||
|
|
||||||
|
static double CubicXtoY(double x, const Imath::V2d &a, const Imath::V2d &b,
|
||||||
|
const Imath::V2d &c, const Imath::V2d &d)
|
||||||
|
{
|
||||||
|
return CubicTtoY(a.y, b.y, c.y, d.y, CubicXtoT(x, a.x, b.x, c.x, d.x));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static double CalculateTFromX(bool cubic, double x, double a, double b,
|
||||||
|
double c, double d);
|
||||||
|
|
||||||
|
double x_;
|
||||||
|
double y_;
|
||||||
|
|
||||||
|
double cp1_x_;
|
||||||
|
double cp1_y_;
|
||||||
|
|
||||||
|
double cp2_x_;
|
||||||
|
double cp2_y_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // LIBOLIVECORE_BEZIER_H
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
/***
|
||||||
|
|
||||||
|
Olive - Non-Linear Video Editor
|
||||||
|
Copyright (C) 2023 Olive Studios LLC
|
||||||
|
Modifications Copyright (C) 2025 mikesolar
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program 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 General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***/
|
||||||
|
|
||||||
|
#ifndef LIBOLIVECORE_COLOR_H
|
||||||
|
#define LIBOLIVECORE_COLOR_H
|
||||||
|
|
||||||
|
#include "../render/pixelformat.h"
|
||||||
|
|
||||||
|
namespace olive::core
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief High precision 32-bit DataType based RGBA color value
|
||||||
|
*/
|
||||||
|
class Color {
|
||||||
|
public:
|
||||||
|
using DataType = float;
|
||||||
|
static constexpr unsigned int RGBA = 4;
|
||||||
|
|
||||||
|
Color()
|
||||||
|
{
|
||||||
|
for (unsigned int i = 0; i < RGBA; i++) {
|
||||||
|
data_[i] = 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Color(const DataType &r, const DataType &g, const DataType &b,
|
||||||
|
const DataType &a = 1.0f)
|
||||||
|
{
|
||||||
|
data_[0] = r;
|
||||||
|
data_[1] = g;
|
||||||
|
data_[2] = b;
|
||||||
|
data_[3] = a;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color(const char *data, const PixelFormat &format, int ch_layout);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Creates a Color struct from hue/saturation/value
|
||||||
|
*
|
||||||
|
* Hue expects a value between 0.0 and 360.0. Saturation and Value expect a value between 0.0 and 1.0.
|
||||||
|
*/
|
||||||
|
static Color fromHsv(const DataType &h, const DataType &s,
|
||||||
|
const DataType &v);
|
||||||
|
|
||||||
|
const DataType &red() const
|
||||||
|
{
|
||||||
|
return data_[0];
|
||||||
|
}
|
||||||
|
const DataType &green() const
|
||||||
|
{
|
||||||
|
return data_[1];
|
||||||
|
}
|
||||||
|
const DataType &blue() const
|
||||||
|
{
|
||||||
|
return data_[2];
|
||||||
|
}
|
||||||
|
const DataType &alpha() const
|
||||||
|
{
|
||||||
|
return data_[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
void toHsv(DataType *hue, DataType *sat, DataType *val) const;
|
||||||
|
DataType hsv_hue() const;
|
||||||
|
DataType hsv_saturation() const;
|
||||||
|
DataType value() const;
|
||||||
|
|
||||||
|
void toHsl(DataType *hue, DataType *sat, DataType *lightness) const;
|
||||||
|
DataType hsl_hue() const;
|
||||||
|
DataType hsl_saturation() const;
|
||||||
|
DataType lightness() const;
|
||||||
|
|
||||||
|
void set_red(const DataType &red)
|
||||||
|
{
|
||||||
|
data_[0] = red;
|
||||||
|
}
|
||||||
|
void set_green(const DataType &green)
|
||||||
|
{
|
||||||
|
data_[1] = green;
|
||||||
|
}
|
||||||
|
void set_blue(const DataType &blue)
|
||||||
|
{
|
||||||
|
data_[2] = blue;
|
||||||
|
}
|
||||||
|
void set_alpha(const DataType &alpha)
|
||||||
|
{
|
||||||
|
data_[3] = alpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
DataType *data()
|
||||||
|
{
|
||||||
|
return data_;
|
||||||
|
}
|
||||||
|
const DataType *data() const
|
||||||
|
{
|
||||||
|
return data_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void toData(char *out, const PixelFormat &format,
|
||||||
|
unsigned int nb_channels) const;
|
||||||
|
|
||||||
|
static Color fromData(const char *in, const PixelFormat &format,
|
||||||
|
unsigned int nb_channels);
|
||||||
|
|
||||||
|
// Suuuuper rough luminance value mostly used for UI (determining whether to overlay with black
|
||||||
|
// or white text)
|
||||||
|
DataType GetRoughLuminance() const;
|
||||||
|
|
||||||
|
// Assignment math operators
|
||||||
|
Color &operator+=(const Color &rhs);
|
||||||
|
Color &operator-=(const Color &rhs);
|
||||||
|
Color &operator+=(const DataType &rhs);
|
||||||
|
Color &operator-=(const DataType &rhs);
|
||||||
|
Color &operator*=(const DataType &rhs);
|
||||||
|
Color &operator/=(const DataType &rhs);
|
||||||
|
|
||||||
|
// Binary math operators
|
||||||
|
Color operator+(const Color &rhs) const
|
||||||
|
{
|
||||||
|
Color c(*this);
|
||||||
|
c += rhs;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color operator-(const Color &rhs) const
|
||||||
|
{
|
||||||
|
Color c(*this);
|
||||||
|
c -= rhs;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color operator+(const DataType &rhs) const
|
||||||
|
{
|
||||||
|
Color c(*this);
|
||||||
|
c += rhs;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color operator-(const DataType &rhs) const
|
||||||
|
{
|
||||||
|
Color c(*this);
|
||||||
|
c -= rhs;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color operator*(const DataType &rhs) const
|
||||||
|
{
|
||||||
|
Color c(*this);
|
||||||
|
c *= rhs;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color operator/(const DataType &rhs) const
|
||||||
|
{
|
||||||
|
Color c(*this);
|
||||||
|
c /= rhs;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
DataType data_[RGBA];
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // LIBOLIVECORE_COLOR_H
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* Olive Community Edition - Non-Linear Video Editor
|
||||||
|
* Copyright (C) 2025 Olive CE Team
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program 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 General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef LIBOLIVECORE_CPUOPTIMIZE_H
|
||||||
|
#define LIBOLIVECORE_CPUOPTIMIZE_H
|
||||||
|
|
||||||
|
#if defined(__x86_64__) || defined(__i386__)
|
||||||
|
#define OLIVE_PROCESSOR_X86
|
||||||
|
#include <xmmintrin.h>
|
||||||
|
#elif defined(__aarch64__)
|
||||||
|
#define OLIVE_PROCESSOR_ARM
|
||||||
|
#include "sse2neon.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // LIBOLIVECORE_CPUOPTIMIZE_H
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* Olive Community Edition - Non-Linear Video Editor
|
||||||
|
* Copyright (C) 2025 Olive CE Team
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program 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 General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef LOG_H
|
||||||
|
#define LOG_H
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace olive::core
|
||||||
|
{
|
||||||
|
|
||||||
|
class Log {
|
||||||
|
public:
|
||||||
|
Log(const char *type)
|
||||||
|
{
|
||||||
|
std::cerr << "[" << type << "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
~Log()
|
||||||
|
{
|
||||||
|
std::cerr << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T> Log &operator<<(const T &t)
|
||||||
|
{
|
||||||
|
std::cerr << " " << t;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Log Debug()
|
||||||
|
{
|
||||||
|
return Log("DEBUG");
|
||||||
|
}
|
||||||
|
|
||||||
|
static Log Info()
|
||||||
|
{
|
||||||
|
return Log("INFO");
|
||||||
|
}
|
||||||
|
|
||||||
|
static Log Warning()
|
||||||
|
{
|
||||||
|
return Log("WARNING");
|
||||||
|
}
|
||||||
|
|
||||||
|
static Log Error()
|
||||||
|
{
|
||||||
|
return Log("ERROR");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // LOG_H
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/***
|
||||||
|
|
||||||
|
Olive - Non-Linear Video Editor
|
||||||
|
Copyright (C) 2023 Olive Studios LLC
|
||||||
|
Modifications Copyright (C) 2025 mikesolar
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program 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 General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***/
|
||||||
|
|
||||||
|
#ifndef LIBOLIVECORE_MATH_H
|
||||||
|
#define LIBOLIVECORE_MATH_H
|
||||||
|
|
||||||
|
namespace olive::core
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // LIBOLIVECORE_MATH_H
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
/***
|
||||||
|
|
||||||
|
Olive - Non-Linear Video Editor
|
||||||
|
Copyright (C) 2023 Olive Studios LLC
|
||||||
|
Modifications Copyright (C) 2025 mikesolar
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program 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 General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***/
|
||||||
|
|
||||||
|
#ifndef LIBOLIVECORE_RATIONAL_H
|
||||||
|
#define LIBOLIVECORE_RATIONAL_H
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include <libavutil/rational.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#ifdef USE_OTIO
|
||||||
|
#include <opentime/rationalTime.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace olive::core
|
||||||
|
{
|
||||||
|
|
||||||
|
class rational {
|
||||||
|
public:
|
||||||
|
rational(const int &numerator = 0)
|
||||||
|
{
|
||||||
|
r_.num = numerator;
|
||||||
|
r_.den = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
rational(const int &numerator, const int &denominator)
|
||||||
|
{
|
||||||
|
r_.num = numerator;
|
||||||
|
r_.den = denominator;
|
||||||
|
|
||||||
|
fix_signs();
|
||||||
|
reduce();
|
||||||
|
}
|
||||||
|
|
||||||
|
rational(const rational &rhs) = default;
|
||||||
|
|
||||||
|
rational(const AVRational &r)
|
||||||
|
{
|
||||||
|
r_ = r;
|
||||||
|
|
||||||
|
fix_signs();
|
||||||
|
}
|
||||||
|
|
||||||
|
static rational fromDouble(const double &flt, bool *ok = nullptr);
|
||||||
|
static rational fromString(const std::string &str, bool *ok = nullptr);
|
||||||
|
|
||||||
|
static const rational NaN;
|
||||||
|
|
||||||
|
//Assignment Operators
|
||||||
|
const rational &operator=(const rational &rhs);
|
||||||
|
const rational &operator+=(const rational &rhs);
|
||||||
|
const rational &operator-=(const rational &rhs);
|
||||||
|
const rational &operator/=(const rational &rhs);
|
||||||
|
const rational &operator*=(const rational &rhs);
|
||||||
|
|
||||||
|
//Binary math operators
|
||||||
|
rational operator+(const rational &rhs) const;
|
||||||
|
rational operator-(const rational &rhs) const;
|
||||||
|
rational operator/(const rational &rhs) const;
|
||||||
|
rational operator*(const rational &rhs) const;
|
||||||
|
|
||||||
|
//Relational and equality operators
|
||||||
|
bool operator<(const rational &rhs) const;
|
||||||
|
bool operator<=(const rational &rhs) const;
|
||||||
|
bool operator>(const rational &rhs) const;
|
||||||
|
bool operator>=(const rational &rhs) const;
|
||||||
|
bool operator==(const rational &rhs) const;
|
||||||
|
bool operator!=(const rational &rhs) const;
|
||||||
|
|
||||||
|
//Unary operators
|
||||||
|
const rational &operator+() const
|
||||||
|
{
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
rational operator-() const
|
||||||
|
{
|
||||||
|
return rational(r_.num, -r_.den);
|
||||||
|
}
|
||||||
|
bool operator!() const
|
||||||
|
{
|
||||||
|
return !r_.num;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Function: convert to double
|
||||||
|
double toDouble() const;
|
||||||
|
|
||||||
|
AVRational toAVRational() const;
|
||||||
|
|
||||||
|
#ifdef USE_OTIO
|
||||||
|
static rational fromRationalTime(const opentime::RationalTime &t)
|
||||||
|
{
|
||||||
|
// Is this the best way to do this?
|
||||||
|
return fromDouble(t.to_seconds());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert Olive rationals to opentime rationals with the given framerate (defaults to 24)
|
||||||
|
opentime::RationalTime toRationalTime(double framerate = 24) const;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Produce "flipped" version
|
||||||
|
rational flipped() const;
|
||||||
|
void flip();
|
||||||
|
|
||||||
|
// Returns whether the rational is valid but equal to zero or not
|
||||||
|
//
|
||||||
|
// A NaN is always a null, but a null is not always a NaN
|
||||||
|
bool isNull() const
|
||||||
|
{
|
||||||
|
return r_.num == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns whether this rational is not a valid number (denominator == 0)
|
||||||
|
bool isNaN() const
|
||||||
|
{
|
||||||
|
return r_.den == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int &numerator() const
|
||||||
|
{
|
||||||
|
return r_.num;
|
||||||
|
}
|
||||||
|
const int &denominator() const
|
||||||
|
{
|
||||||
|
return r_.den;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string toString() const;
|
||||||
|
|
||||||
|
friend std::ostream &operator<<(std::ostream &out, const rational &value)
|
||||||
|
{
|
||||||
|
out << value.r_.num << '/' << value.r_.den;
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void fix_signs();
|
||||||
|
void reduce();
|
||||||
|
|
||||||
|
AVRational r_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define RATIONAL_MIN rational(INT_MIN)
|
||||||
|
#define RATIONAL_MAX rational(INT_MAX)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // LIBOLIVECORE_RATIONAL_H
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,211 @@
|
|||||||
|
/***
|
||||||
|
|
||||||
|
Olive - Non-Linear Video Editor
|
||||||
|
Copyright (C) 2023 Olive Studios LLC
|
||||||
|
Modifications Copyright (C) 2025 mikesolar
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program 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 General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***/
|
||||||
|
|
||||||
|
#ifndef LIBOLIVECORE_STRINGUTILS_H
|
||||||
|
#define LIBOLIVECORE_STRINGUTILS_H
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <regex>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace olive::core
|
||||||
|
{
|
||||||
|
|
||||||
|
class StringUtils {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Split a string into a list of strings using a specific delimiter
|
||||||
|
*
|
||||||
|
* @param s
|
||||||
|
*
|
||||||
|
* The string to split.
|
||||||
|
*
|
||||||
|
* @param separator
|
||||||
|
*
|
||||||
|
* The character to split the string on.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*
|
||||||
|
* A vector of strings split by the specified delimiter.
|
||||||
|
*/
|
||||||
|
static std::vector<std::string> split(const std::string &s, char separator);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Splits a string into a list of strings using regular expressions.
|
||||||
|
*
|
||||||
|
* @param s
|
||||||
|
*
|
||||||
|
* The string to split.
|
||||||
|
*
|
||||||
|
* @param regex
|
||||||
|
*
|
||||||
|
* The regular expression to split the string on.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*
|
||||||
|
* A vector of strings split wherever the regular expression matched.
|
||||||
|
*/
|
||||||
|
static std::vector<std::string> split_regex(const std::string &s,
|
||||||
|
const std::regex ®ex);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Convert a string to int using a bool pointer to determine success rather than an exception
|
||||||
|
*
|
||||||
|
* @param s
|
||||||
|
*
|
||||||
|
* The string to parse an int from.
|
||||||
|
*
|
||||||
|
* @param base
|
||||||
|
*
|
||||||
|
* The base of the number in the string (usually 10, or 16 for hex).
|
||||||
|
*
|
||||||
|
* @param ok
|
||||||
|
*
|
||||||
|
* (Optional) a boolean output parameter specifying whether the conversion was successful or not.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*
|
||||||
|
* Either the int parsed from the string, or 0 (with *ok set to false) on parser error.
|
||||||
|
*/
|
||||||
|
static int to_int(const std::string &s, int base, bool *ok = nullptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Overloaded function
|
||||||
|
*
|
||||||
|
* @param s
|
||||||
|
*
|
||||||
|
* The string to parse an int from.
|
||||||
|
*
|
||||||
|
* @param ok
|
||||||
|
*
|
||||||
|
* (Optional) a boolean output parameter specifying whether the conversion was successful or not.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*
|
||||||
|
* Either the int parsed from the string, or 0 (with *ok set to false) on parser error.
|
||||||
|
*/
|
||||||
|
static int to_int(const std::string &s, bool *ok = nullptr)
|
||||||
|
{
|
||||||
|
return to_int(s, 10, ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Convert a number to a string with left padding
|
||||||
|
*
|
||||||
|
* Usually used for converting a number to a string with leading zeroes.
|
||||||
|
*
|
||||||
|
* @param val
|
||||||
|
*
|
||||||
|
* The number to convert. This is a templated function and will accept any type, e.g.
|
||||||
|
* int/long/float/double/etc.
|
||||||
|
*
|
||||||
|
* @param padding
|
||||||
|
*
|
||||||
|
* Total desired length of the string. For example, setting this to `2` will ensure the string
|
||||||
|
* is at least 2 characters in size, using `c` to pad the left side where necessary.
|
||||||
|
*
|
||||||
|
* @param c
|
||||||
|
*
|
||||||
|
* The character to pad with. This defaults to `0` assuming you'll be using this function to
|
||||||
|
* create leading zeroes.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*
|
||||||
|
* The padded string.
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
static std::string to_string_leftpad(T val, size_t padding, char c = '0')
|
||||||
|
{
|
||||||
|
std::string s = std::to_string(val);
|
||||||
|
|
||||||
|
if (s.size() < padding) {
|
||||||
|
s.insert(0, padding - s.size(), c);
|
||||||
|
}
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Format a string
|
||||||
|
*
|
||||||
|
* A sprintf wrapper that returns a std::string.
|
||||||
|
*
|
||||||
|
* @param fmt
|
||||||
|
*
|
||||||
|
* The format to use.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*
|
||||||
|
* A formatted string in std::string form.
|
||||||
|
*/
|
||||||
|
static std::string format(const char *fmt, ...);
|
||||||
|
|
||||||
|
// trim from start (in place)
|
||||||
|
static inline void ltrim(std::string &s)
|
||||||
|
{
|
||||||
|
s.erase(s.begin(),
|
||||||
|
std::find_if(s.begin(), s.end(), [](unsigned char ch) {
|
||||||
|
return !std::isspace(ch);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// trim from end (in place)
|
||||||
|
static inline void rtrim(std::string &s)
|
||||||
|
{
|
||||||
|
s.erase(std::find_if(s.rbegin(), s.rend(),
|
||||||
|
[](unsigned char ch) { return !std::isspace(ch); })
|
||||||
|
.base(),
|
||||||
|
s.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
// trim from both ends (in place)
|
||||||
|
static inline void trim(std::string &s)
|
||||||
|
{
|
||||||
|
rtrim(s);
|
||||||
|
ltrim(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
// trim from start (copying)
|
||||||
|
static inline std::string ltrimmed(std::string s)
|
||||||
|
{
|
||||||
|
ltrim(s);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
// trim from end (copying)
|
||||||
|
static inline std::string rtrimmed(std::string s)
|
||||||
|
{
|
||||||
|
rtrim(s);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
// trim from both ends (copying)
|
||||||
|
static inline std::string trimmed(std::string s)
|
||||||
|
{
|
||||||
|
trim(s);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // LIBOLIVECORE_STRINGUTILS_H
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
/***
|
||||||
|
|
||||||
|
Olive - Non-Linear Video Editor
|
||||||
|
Copyright (C) 2023 Olive Studios LLC
|
||||||
|
Modifications Copyright (C) 2025 mikesolar
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program 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 General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***/
|
||||||
|
|
||||||
|
#ifndef LIBOLIVECORE_TESTS_H
|
||||||
|
#define LIBOLIVECORE_TESTS_H
|
||||||
|
|
||||||
|
#include <list>
|
||||||
|
|
||||||
|
namespace olive::core
|
||||||
|
{
|
||||||
|
|
||||||
|
class Tester {
|
||||||
|
public:
|
||||||
|
Tester() = default;
|
||||||
|
|
||||||
|
typedef bool (*test_t)();
|
||||||
|
|
||||||
|
void add(const char *name, test_t test_function)
|
||||||
|
{
|
||||||
|
test_names_.push_back(name);
|
||||||
|
test_functions_.push_back(test_function);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool run();
|
||||||
|
|
||||||
|
int exec()
|
||||||
|
{
|
||||||
|
if (run()) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void echo(const char *fmt, ...);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::list<const char *> test_names_;
|
||||||
|
std::list<test_t> test_functions_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // LIBOLIVECORE_TESTS_H
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
/***
|
||||||
|
|
||||||
|
Olive - Non-Linear Video Editor
|
||||||
|
Copyright (C) 2023 Olive Studios LLC
|
||||||
|
Modifications Copyright (C) 2025 mikesolar
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program 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 General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***/
|
||||||
|
|
||||||
|
#ifndef LIBOLIVECORE_TIMECODEFUNCTIONS_H
|
||||||
|
#define LIBOLIVECORE_TIMECODEFUNCTIONS_H
|
||||||
|
|
||||||
|
#include "rational.h"
|
||||||
|
|
||||||
|
namespace olive::core
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Functions for converting times/timecodes/timestamps
|
||||||
|
*
|
||||||
|
* Olive uses the following terminology through its code:
|
||||||
|
*
|
||||||
|
* `time` - time in seconds presented in a rational form
|
||||||
|
* `timebase` - the base time unit of an audio/video stream in seconds
|
||||||
|
* `timestamp` - an integer representation of a time in timebase units (in many cases is used like a frame number)
|
||||||
|
* `timecode` a user-friendly string representation of a time according to Timecode::Display
|
||||||
|
*/
|
||||||
|
class Timecode {
|
||||||
|
public:
|
||||||
|
enum Display {
|
||||||
|
kTimecodeDropFrame,
|
||||||
|
kTimecodeNonDropFrame,
|
||||||
|
kTimecodeSeconds,
|
||||||
|
kFrames,
|
||||||
|
kMilliseconds
|
||||||
|
};
|
||||||
|
|
||||||
|
enum Rounding { kCeil, kFloor, kRound };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Convert a timestamp (according to a rational timebase) to a user-friendly string representation
|
||||||
|
*/
|
||||||
|
static std::string time_to_timecode(const rational &time,
|
||||||
|
const rational &timebase,
|
||||||
|
const Display &display,
|
||||||
|
bool show_plus_if_positive = false);
|
||||||
|
static rational timecode_to_time(std::string timecode,
|
||||||
|
const rational &timebase,
|
||||||
|
const Display &display,
|
||||||
|
bool *ok = nullptr);
|
||||||
|
|
||||||
|
static std::string time_to_string(int64_t ms);
|
||||||
|
|
||||||
|
static rational snap_time_to_timebase(const rational &time,
|
||||||
|
const rational &timebase,
|
||||||
|
Rounding floor = kRound);
|
||||||
|
|
||||||
|
static int64_t time_to_timestamp(const rational &time,
|
||||||
|
const rational &timebase,
|
||||||
|
Rounding floor = kRound);
|
||||||
|
static int64_t time_to_timestamp(const double &time,
|
||||||
|
const rational &timebase,
|
||||||
|
Rounding floor = kRound);
|
||||||
|
|
||||||
|
static int64_t rescale_timestamp(const int64_t &ts, const rational &source,
|
||||||
|
const rational &dest);
|
||||||
|
static int64_t rescale_timestamp_ceil(const int64_t &ts,
|
||||||
|
const rational &source,
|
||||||
|
const rational &dest);
|
||||||
|
|
||||||
|
static rational timestamp_to_time(const int64_t ×tamp,
|
||||||
|
const rational &timebase);
|
||||||
|
|
||||||
|
static bool timebase_is_drop_frame(const rational &timebase);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // LIBOLIVECORE_TIMECODEFUNCTIONS_H
|
||||||
@@ -0,0 +1,315 @@
|
|||||||
|
/***
|
||||||
|
|
||||||
|
Olive - Non-Linear Video Editor
|
||||||
|
Copyright (C) 2023 Olive Studios LLC
|
||||||
|
Modifications Copyright (C) 2025 mikesolar
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program 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 General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***/
|
||||||
|
|
||||||
|
#ifndef LIBOLIVECORE_TIMERANGE_H
|
||||||
|
#define LIBOLIVECORE_TIMERANGE_H
|
||||||
|
|
||||||
|
#include <list>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "rational.h"
|
||||||
|
|
||||||
|
namespace olive::core
|
||||||
|
{
|
||||||
|
|
||||||
|
class TimeRange {
|
||||||
|
public:
|
||||||
|
TimeRange() = default;
|
||||||
|
TimeRange(const rational &in, const rational &out);
|
||||||
|
TimeRange(const TimeRange &r)
|
||||||
|
: TimeRange(r.in(), r.out())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeRange &operator=(const TimeRange &r)
|
||||||
|
{
|
||||||
|
set_range(r.in(), r.out());
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rational &in() const;
|
||||||
|
const rational &out() const;
|
||||||
|
const rational &length() const;
|
||||||
|
|
||||||
|
void set_in(const rational &in);
|
||||||
|
void set_out(const rational &out);
|
||||||
|
void set_range(const rational &in, const rational &out);
|
||||||
|
|
||||||
|
bool operator==(const TimeRange &r) const;
|
||||||
|
bool operator!=(const TimeRange &r) const;
|
||||||
|
|
||||||
|
bool OverlapsWith(const TimeRange &a, bool in_inclusive = true,
|
||||||
|
bool out_inclusive = true) const;
|
||||||
|
bool Contains(const TimeRange &a, bool in_inclusive = true,
|
||||||
|
bool out_inclusive = true) const;
|
||||||
|
bool Contains(const rational &r) const;
|
||||||
|
|
||||||
|
TimeRange Combined(const TimeRange &a) const;
|
||||||
|
static TimeRange Combine(const TimeRange &a, const TimeRange &b);
|
||||||
|
TimeRange Intersected(const TimeRange &a) const;
|
||||||
|
static TimeRange Intersect(const TimeRange &a, const TimeRange &b);
|
||||||
|
|
||||||
|
TimeRange operator+(const rational &rhs) const;
|
||||||
|
TimeRange operator-(const rational &rhs) const;
|
||||||
|
|
||||||
|
const TimeRange &operator+=(const rational &rhs);
|
||||||
|
const TimeRange &operator-=(const rational &rhs);
|
||||||
|
|
||||||
|
std::list<TimeRange> Split(const int &chunk_size) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void normalize();
|
||||||
|
|
||||||
|
rational in_;
|
||||||
|
rational out_;
|
||||||
|
rational length_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class TimeRangeList {
|
||||||
|
public:
|
||||||
|
TimeRangeList() = default;
|
||||||
|
|
||||||
|
TimeRangeList(std::initializer_list<TimeRange> r)
|
||||||
|
: array_(r)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void insert(const TimeRangeList &list_to_add);
|
||||||
|
void insert(TimeRange range_to_add);
|
||||||
|
|
||||||
|
void remove(const TimeRange &remove);
|
||||||
|
void remove(const TimeRangeList &list);
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
static void util_remove(std::vector<T> *list, const TimeRange &remove)
|
||||||
|
{
|
||||||
|
std::vector<T> additions;
|
||||||
|
|
||||||
|
for (auto it = list->begin(); it != list->end();) {
|
||||||
|
T &compare = *it;
|
||||||
|
|
||||||
|
if (remove.Contains(compare)) {
|
||||||
|
// This element is entirely encompassed in this range, remove it
|
||||||
|
it = list->erase(it);
|
||||||
|
} else {
|
||||||
|
if (compare.Contains(remove, false, false)) {
|
||||||
|
// The remove range is within this element, only choice is to split the element into two
|
||||||
|
T new_range = compare;
|
||||||
|
new_range.set_in(remove.out());
|
||||||
|
compare.set_out(remove.in());
|
||||||
|
|
||||||
|
additions.push_back(new_range);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
if (compare.in() < remove.in() &&
|
||||||
|
compare.out() > remove.in()) {
|
||||||
|
// This element's out point overlaps the range's in, we'll trim it
|
||||||
|
compare.set_out(remove.in());
|
||||||
|
} else if (compare.in() < remove.out() &&
|
||||||
|
compare.out() > remove.out()) {
|
||||||
|
// This element's in point overlaps the range's out, we'll trim it
|
||||||
|
compare.set_in(remove.out());
|
||||||
|
}
|
||||||
|
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list->insert(list->end(), additions.begin(), additions.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool contains(const TimeRange &range, bool in_inclusive = true,
|
||||||
|
bool out_inclusive = true) const;
|
||||||
|
|
||||||
|
bool contains(const rational &r) const
|
||||||
|
{
|
||||||
|
for (const TimeRange &range : array_) {
|
||||||
|
if (range.Contains(r)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OverlapsWith(const TimeRange &r, bool in_inclusive = true,
|
||||||
|
bool out_inclusive = true) const
|
||||||
|
{
|
||||||
|
for (const TimeRange &range : array_) {
|
||||||
|
if (range.OverlapsWith(r, in_inclusive, out_inclusive)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isEmpty() const
|
||||||
|
{
|
||||||
|
return array_.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear()
|
||||||
|
{
|
||||||
|
array_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
int size() const
|
||||||
|
{
|
||||||
|
return array_.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void shift(const rational &diff);
|
||||||
|
|
||||||
|
void trim_in(const rational &diff);
|
||||||
|
|
||||||
|
void trim_out(const rational &diff);
|
||||||
|
|
||||||
|
TimeRangeList Intersects(const TimeRange &range) const;
|
||||||
|
|
||||||
|
using const_iterator = std::vector<TimeRange>::const_iterator;
|
||||||
|
|
||||||
|
const_iterator begin() const
|
||||||
|
{
|
||||||
|
return array_.cbegin();
|
||||||
|
}
|
||||||
|
|
||||||
|
const_iterator end() const
|
||||||
|
{
|
||||||
|
return array_.cend();
|
||||||
|
}
|
||||||
|
|
||||||
|
const_iterator cbegin() const
|
||||||
|
{
|
||||||
|
return begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
const_iterator cend() const
|
||||||
|
{
|
||||||
|
return end();
|
||||||
|
}
|
||||||
|
|
||||||
|
const TimeRange &first() const
|
||||||
|
{
|
||||||
|
return array_.front();
|
||||||
|
}
|
||||||
|
|
||||||
|
const TimeRange &last() const
|
||||||
|
{
|
||||||
|
return array_.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
const TimeRange &at(int index) const
|
||||||
|
{
|
||||||
|
return array_.at(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<TimeRange> &internal_array() const
|
||||||
|
{
|
||||||
|
return array_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const TimeRangeList &rhs) const
|
||||||
|
{
|
||||||
|
return array_ == rhs.array_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<TimeRange> array_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class TimeRangeListFrameIterator {
|
||||||
|
public:
|
||||||
|
TimeRangeListFrameIterator();
|
||||||
|
TimeRangeListFrameIterator(const TimeRangeList &list,
|
||||||
|
const rational &timebase);
|
||||||
|
|
||||||
|
rational Snap(const rational &r) const;
|
||||||
|
|
||||||
|
bool GetNext(rational *out);
|
||||||
|
|
||||||
|
bool HasNext() const;
|
||||||
|
|
||||||
|
std::vector<rational> ToVector() const
|
||||||
|
{
|
||||||
|
TimeRangeListFrameIterator copy(list_, timebase_);
|
||||||
|
std::vector<rational> times;
|
||||||
|
rational r;
|
||||||
|
while (copy.GetNext(&r)) {
|
||||||
|
times.push_back(r);
|
||||||
|
}
|
||||||
|
return times;
|
||||||
|
}
|
||||||
|
|
||||||
|
int size();
|
||||||
|
|
||||||
|
void reset()
|
||||||
|
{
|
||||||
|
*this = TimeRangeListFrameIterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
void insert(const TimeRange &range)
|
||||||
|
{
|
||||||
|
list_.insert(range);
|
||||||
|
}
|
||||||
|
|
||||||
|
void insert(const TimeRangeList &list)
|
||||||
|
{
|
||||||
|
list_.insert(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsCustomRange() const
|
||||||
|
{
|
||||||
|
return custom_range_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetCustomRange(bool e)
|
||||||
|
{
|
||||||
|
custom_range_ = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
int frame_index() const
|
||||||
|
{
|
||||||
|
return frame_index_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void UpdateIndexIfNecessary();
|
||||||
|
|
||||||
|
TimeRangeList list_;
|
||||||
|
|
||||||
|
rational timebase_;
|
||||||
|
|
||||||
|
rational current_;
|
||||||
|
|
||||||
|
int range_index_;
|
||||||
|
|
||||||
|
int size_;
|
||||||
|
|
||||||
|
int frame_index_;
|
||||||
|
|
||||||
|
bool custom_range_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // LIBOLIVECORE_TIMERANGE_H
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
/***
|
||||||
|
|
||||||
|
Olive - Non-Linear Video Editor
|
||||||
|
Copyright (C) 2023 Olive Studios LLC
|
||||||
|
Modifications Copyright (C) 2025 mikesolar
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program 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 General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***/
|
||||||
|
|
||||||
|
#ifndef LIBOLIVECORE_VALUE_H
|
||||||
|
#define LIBOLIVECORE_VALUE_H
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string>
|
||||||
|
#include <string.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace olive::core
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generic type container
|
||||||
|
*/
|
||||||
|
class Value {
|
||||||
|
public:
|
||||||
|
enum Type {
|
||||||
|
/// Null/no data
|
||||||
|
NONE,
|
||||||
|
|
||||||
|
/// Signed int64
|
||||||
|
INT,
|
||||||
|
|
||||||
|
/// Double-precision float
|
||||||
|
FLOAT,
|
||||||
|
|
||||||
|
/// UTF-8 string
|
||||||
|
STRING
|
||||||
|
};
|
||||||
|
|
||||||
|
Value()
|
||||||
|
{
|
||||||
|
type_ = NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value(int64_t v)
|
||||||
|
{
|
||||||
|
data_.resize(sizeof(int64_t));
|
||||||
|
memcpy(data_.data(), &v, sizeof(int64_t));
|
||||||
|
type_ = INT;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value(double v)
|
||||||
|
{
|
||||||
|
data_.resize(sizeof(double));
|
||||||
|
memcpy(data_.data(), &v, sizeof(int64_t));
|
||||||
|
type_ = FLOAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value(const char *s)
|
||||||
|
{
|
||||||
|
size_t sz = strlen(s);
|
||||||
|
data_.resize(sz);
|
||||||
|
memcpy(data_.data(), s, sz);
|
||||||
|
type_ = STRING;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value(const std::string &s)
|
||||||
|
{
|
||||||
|
data_.resize(s.size());
|
||||||
|
memcpy(data_.data(), s.data(), data_.size());
|
||||||
|
type_ = STRING;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<uint8_t> data_;
|
||||||
|
Type type_;
|
||||||
|
};
|
||||||
|
|
||||||
|
using ValueMap = std::map<std::string, Value>;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // LIBOLIVECORE_VALUE_H
|
||||||
@@ -0,0 +1,237 @@
|
|||||||
|
/***
|
||||||
|
|
||||||
|
Olive - Non-Linear Video Editor
|
||||||
|
Copyright (C) 2023 Olive Studios LLC
|
||||||
|
Modifications Copyright (C) 2025 mikesolar
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program 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 General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***/
|
||||||
|
|
||||||
|
#include "render/audioparams.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace olive::core
|
||||||
|
{
|
||||||
|
|
||||||
|
const std::vector<int> AudioParams::kSupportedSampleRates = {
|
||||||
|
8000, // 8000 Hz
|
||||||
|
11025, // 11025 Hz
|
||||||
|
16000, // 16000 Hz
|
||||||
|
22050, // 22050 Hz
|
||||||
|
24000, // 24000 Hz
|
||||||
|
32000, // 32000 Hz
|
||||||
|
44100, // 44100 Hz
|
||||||
|
48000, // 48000 Hz
|
||||||
|
88200, // 88200 Hz
|
||||||
|
96000 // 96000 Hz
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::vector<uint64_t> AudioParams::kSupportedChannelLayouts = {
|
||||||
|
AV_CH_LAYOUT_MONO, AV_CH_LAYOUT_STEREO, AV_CH_LAYOUT_2_1,
|
||||||
|
AV_CH_LAYOUT_5POINT1, AV_CH_LAYOUT_7POINT1
|
||||||
|
};
|
||||||
|
|
||||||
|
bool AudioParams::operator==(const AudioParams &other) const
|
||||||
|
{
|
||||||
|
return format() == other.format() && sample_rate() == other.sample_rate() &&
|
||||||
|
time_base() == other.time_base() &&
|
||||||
|
av_channel_layout_compare(&channel_layout_,
|
||||||
|
&other.channel_layout()) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AudioParams::operator!=(const AudioParams &other) const
|
||||||
|
{
|
||||||
|
return !(*this == other);
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t AudioParams::time_to_bytes(const double &time) const
|
||||||
|
{
|
||||||
|
return time_to_bytes_per_channel(time) * channel_count();
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t AudioParams::time_to_bytes(const rational &time) const
|
||||||
|
{
|
||||||
|
return time_to_bytes(time.toDouble());
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t AudioParams::time_to_bytes_per_channel(const double &time) const
|
||||||
|
{
|
||||||
|
assert(is_valid());
|
||||||
|
|
||||||
|
return int64_t(time_to_samples(time)) * bytes_per_sample_per_channel();
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t AudioParams::time_to_bytes_per_channel(const rational &time) const
|
||||||
|
{
|
||||||
|
return time_to_bytes_per_channel(time.toDouble());
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t AudioParams::time_to_samples(const double &time) const
|
||||||
|
{
|
||||||
|
assert(is_valid());
|
||||||
|
|
||||||
|
return std::round(double(sample_rate()) * time);
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t AudioParams::time_to_samples(const rational &time) const
|
||||||
|
{
|
||||||
|
return time_to_samples(time.toDouble());
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t AudioParams::samples_to_bytes(const int64_t &samples) const
|
||||||
|
{
|
||||||
|
assert(is_valid());
|
||||||
|
|
||||||
|
return samples_to_bytes_per_channel(samples) * channel_count();
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t AudioParams::samples_to_bytes_per_channel(const int64_t &samples) const
|
||||||
|
{
|
||||||
|
assert(is_valid());
|
||||||
|
|
||||||
|
return samples * bytes_per_sample_per_channel();
|
||||||
|
}
|
||||||
|
|
||||||
|
rational AudioParams::samples_to_time(const int64_t &samples) const
|
||||||
|
{
|
||||||
|
return sample_rate_as_time_base() * samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t AudioParams::bytes_to_samples(const int64_t &bytes) const
|
||||||
|
{
|
||||||
|
assert(is_valid());
|
||||||
|
|
||||||
|
return bytes / (channel_count() * bytes_per_sample_per_channel());
|
||||||
|
}
|
||||||
|
|
||||||
|
rational AudioParams::bytes_to_time(const int64_t &bytes) const
|
||||||
|
{
|
||||||
|
assert(is_valid());
|
||||||
|
|
||||||
|
return samples_to_time(bytes_to_samples(bytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
rational AudioParams::bytes_per_channel_to_time(const int64_t &bytes) const
|
||||||
|
{
|
||||||
|
assert(is_valid());
|
||||||
|
|
||||||
|
return samples_to_time(bytes_to_samples(bytes * channel_count()));
|
||||||
|
}
|
||||||
|
|
||||||
|
int AudioParams::channel_count() const
|
||||||
|
{
|
||||||
|
return channel_count_;
|
||||||
|
}
|
||||||
|
|
||||||
|
int AudioParams::bytes_per_sample_per_channel() const
|
||||||
|
{
|
||||||
|
return format_.byte_count();
|
||||||
|
}
|
||||||
|
|
||||||
|
int AudioParams::bits_per_sample() const
|
||||||
|
{
|
||||||
|
return bytes_per_sample_per_channel() * 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AudioParams::is_valid() const
|
||||||
|
{
|
||||||
|
return (!time_base().isNull() &&
|
||||||
|
av_channel_layout_check(&channel_layout_) &&
|
||||||
|
format_ > SampleFormat::INVALID && format_ < SampleFormat::COUNT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioParams::calculate_channel_count()
|
||||||
|
{
|
||||||
|
channel_count_ = channel_layout().nb_channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Copy constructor - deep copies AVChannelLayout
|
||||||
|
*
|
||||||
|
* This is critical because AVChannelLayout::u.map is a pointer for custom
|
||||||
|
* channel layouts. Default copy would share the pointer, leading to double-free.
|
||||||
|
*
|
||||||
|
* The member initializer list initializes channel_layout_ to zero ({}),
|
||||||
|
* then av_channel_layout_copy performs the deep copy from other.
|
||||||
|
*
|
||||||
|
* @param other Source AudioParams to copy from
|
||||||
|
*/
|
||||||
|
AudioParams::AudioParams(const AudioParams &other)
|
||||||
|
: sample_rate_(other.sample_rate_)
|
||||||
|
, channel_layout_{} // Zero-initialize before FFmpeg copy
|
||||||
|
, channel_count_(other.channel_count_)
|
||||||
|
, format_(other.format_)
|
||||||
|
, enabled_(other.enabled_)
|
||||||
|
, stream_index_(other.stream_index_)
|
||||||
|
, duration_(other.duration_)
|
||||||
|
, timebase_(other.timebase_)
|
||||||
|
{
|
||||||
|
// Deep copy AVChannelLayout using FFmpeg API
|
||||||
|
// This handles all layout types: unspecified, native (mask), and custom (map)
|
||||||
|
av_channel_layout_copy(&channel_layout_, &other.channel_layout_);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Copy assignment - cleans up existing layout before copying
|
||||||
|
*
|
||||||
|
* CRITICAL ORDER OF OPERATIONS:
|
||||||
|
* 1. Check for self-assignment (this != &other)
|
||||||
|
* 2. Copy all scalar members
|
||||||
|
* 3. Uninitialize current channel_layout_ (frees old u.map if present)
|
||||||
|
* 4. Deep copy from other's channel_layout_
|
||||||
|
*
|
||||||
|
* Step 3 must happen before step 4 to avoid memory leaks. If we copied first,
|
||||||
|
* we'd lose the pointer to the old u.map that needs to be freed.
|
||||||
|
*
|
||||||
|
* @param other Source AudioParams to copy from
|
||||||
|
* @return Reference to this for chaining
|
||||||
|
*/
|
||||||
|
AudioParams &AudioParams::operator=(const AudioParams &other)
|
||||||
|
{
|
||||||
|
if (this != &other) {
|
||||||
|
// Copy scalar members first (no dependencies)
|
||||||
|
sample_rate_ = other.sample_rate_;
|
||||||
|
format_ = other.format_;
|
||||||
|
channel_count_ = other.channel_count_;
|
||||||
|
enabled_ = other.enabled_;
|
||||||
|
stream_index_ = other.stream_index_;
|
||||||
|
duration_ = other.duration_;
|
||||||
|
timebase_ = other.timebase_;
|
||||||
|
|
||||||
|
// Free current layout's dynamic memory (u.map if custom)
|
||||||
|
av_channel_layout_uninit(&channel_layout_);
|
||||||
|
|
||||||
|
// Deep copy from other (includes allocating new u.map if needed)
|
||||||
|
av_channel_layout_copy(&channel_layout_, &other.channel_layout_);
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destructor - frees AVChannelLayout dynamic memory
|
||||||
|
*
|
||||||
|
* av_channel_layout_uninit() handles all cases:
|
||||||
|
* - Unspecified/Native: No-op (no dynamic memory)
|
||||||
|
* - Custom: Frees u.map array
|
||||||
|
*
|
||||||
|
* Without this, custom channel layouts would leak memory.
|
||||||
|
*/
|
||||||
|
AudioParams::~AudioParams()
|
||||||
|
{
|
||||||
|
av_channel_layout_uninit(&channel_layout_);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,323 @@
|
|||||||
|
/***
|
||||||
|
|
||||||
|
Olive - Non-Linear Video Editor
|
||||||
|
Copyright (C) 2023 Olive Studios LLC
|
||||||
|
Modifications Copyright (C) 2025 mikesolar
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program 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 General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***/
|
||||||
|
|
||||||
|
#include "render/samplebuffer.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <cmath>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "util/cpuoptimize.h"
|
||||||
|
#include "util/log.h"
|
||||||
|
|
||||||
|
namespace olive::core
|
||||||
|
{
|
||||||
|
|
||||||
|
SampleBuffer::SampleBuffer()
|
||||||
|
: sample_count_per_channel_(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
SampleBuffer::SampleBuffer(const AudioParams &audio_params,
|
||||||
|
const rational &length)
|
||||||
|
: audio_params_(audio_params)
|
||||||
|
{
|
||||||
|
sample_count_per_channel_ = audio_params_.time_to_samples(length);
|
||||||
|
allocate();
|
||||||
|
}
|
||||||
|
|
||||||
|
SampleBuffer::SampleBuffer(const AudioParams &audio_params,
|
||||||
|
size_t samples_per_channel)
|
||||||
|
: audio_params_(audio_params)
|
||||||
|
, sample_count_per_channel_(samples_per_channel)
|
||||||
|
{
|
||||||
|
allocate();
|
||||||
|
}
|
||||||
|
|
||||||
|
SampleBuffer SampleBuffer::rip_channel(int channel) const
|
||||||
|
{
|
||||||
|
AudioParams p = this->audio_params_;
|
||||||
|
AVChannelLayout layout;
|
||||||
|
av_channel_layout_from_mask(&layout, AV_CH_LAYOUT_MONO);
|
||||||
|
p.set_channel_layout(layout);
|
||||||
|
av_channel_layout_uninit(&layout);
|
||||||
|
|
||||||
|
SampleBuffer b(p, this->sample_count_per_channel_);
|
||||||
|
b.fast_set(*this, 0, channel);
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<float> SampleBuffer::rip_channel_vector(int channel) const
|
||||||
|
{
|
||||||
|
return data_.at(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
const AudioParams &SampleBuffer::audio_params() const
|
||||||
|
{
|
||||||
|
return audio_params_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SampleBuffer::set_audio_params(const AudioParams ¶ms)
|
||||||
|
{
|
||||||
|
if (is_allocated()) {
|
||||||
|
Log::Warning() << "Tried to set parameters on allocated sample buffer";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
audio_params_ = params;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SampleBuffer::set_sample_count(const size_t &sample_count)
|
||||||
|
{
|
||||||
|
if (is_allocated()) {
|
||||||
|
Log::Warning()
|
||||||
|
<< "Tried to set sample count on allocated sample buffer";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sample_count_per_channel_ = sample_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SampleBuffer::allocate()
|
||||||
|
{
|
||||||
|
if (!audio_params_.is_valid()) {
|
||||||
|
Log::Warning()
|
||||||
|
<< "Tried to allocate sample buffer with invalid audio parameters";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sample_count_per_channel_) {
|
||||||
|
Log::Warning()
|
||||||
|
<< "Tried to allocate sample buffer with zero sample count";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_allocated()) {
|
||||||
|
Log::Warning() << "Tried to allocate already allocated sample buffer";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
data_.resize(audio_params_.channel_count());
|
||||||
|
for (int i = 0; i < audio_params_.channel_count(); i++) {
|
||||||
|
data_[i].resize(sample_count_per_channel_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SampleBuffer::destroy()
|
||||||
|
{
|
||||||
|
data_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SampleBuffer::reverse()
|
||||||
|
{
|
||||||
|
if (!is_allocated()) {
|
||||||
|
Log::Warning() << "Tried to reverse an unallocated sample buffer";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t half_nb_sample = sample_count_per_channel_ / 2;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < half_nb_sample; i++) {
|
||||||
|
size_t opposite_ind = sample_count_per_channel_ - i - 1;
|
||||||
|
|
||||||
|
for (int j = 0; j < audio_params_.channel_count(); j++) {
|
||||||
|
std::swap(data_[j][i], data_[j][opposite_ind]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SampleBuffer::speed(double speed)
|
||||||
|
{
|
||||||
|
if (!is_allocated()) {
|
||||||
|
Log::Warning() << "Tried to speed an unallocated sample buffer";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sample_count_per_channel_ =
|
||||||
|
std::llround(static_cast<double>(sample_count_per_channel_) / speed);
|
||||||
|
|
||||||
|
std::vector<std::vector<float>> output_data;
|
||||||
|
|
||||||
|
output_data.resize(audio_params_.channel_count());
|
||||||
|
for (int i = 0; i < audio_params_.channel_count(); i++) {
|
||||||
|
output_data[i].resize(sample_count_per_channel_);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < sample_count_per_channel_; i++) {
|
||||||
|
size_t input_index = std::floor(static_cast<double>(i) * speed);
|
||||||
|
|
||||||
|
for (int j = 0; j < audio_params_.channel_count(); j++) {
|
||||||
|
output_data[j][i] = data_[j][input_index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data_ = output_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SampleBuffer::transform_volume(float f)
|
||||||
|
{
|
||||||
|
transform_volume(f, this, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SampleBuffer::transform_volume_for_channel(int channel, float volume)
|
||||||
|
{
|
||||||
|
transform_volume_for_channel(channel, volume, this, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SampleBuffer::transform_volume(float f, const SampleBuffer *input,
|
||||||
|
SampleBuffer *output)
|
||||||
|
{
|
||||||
|
assert(input->channel_count() == output->channel_count());
|
||||||
|
assert(input->sample_count_per_channel_ ==
|
||||||
|
output->sample_count_per_channel_);
|
||||||
|
|
||||||
|
for (int i = 0; i < input->audio_params().channel_count(); i++) {
|
||||||
|
transform_volume_for_channel(i, f, input, output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SampleBuffer::transform_volume_for_channel(int channel, float volume,
|
||||||
|
const SampleBuffer *input,
|
||||||
|
SampleBuffer *output)
|
||||||
|
{
|
||||||
|
const float *cdat = input->data_[channel].data();
|
||||||
|
float *odat = output->data_[channel].data();
|
||||||
|
size_t unopt_start = 0;
|
||||||
|
|
||||||
|
assert(input->channel_count() == output->channel_count());
|
||||||
|
assert(input->sample_count_per_channel_ ==
|
||||||
|
output->sample_count_per_channel_);
|
||||||
|
|
||||||
|
#if defined(OLIVE_PROCESSOR_X86) || defined(OLIVE_PROCESSOR_ARM)
|
||||||
|
__m128 mult = _mm_load1_ps(&volume);
|
||||||
|
unopt_start = (input->sample_count_per_channel_ / 4) * 4;
|
||||||
|
for (size_t j = 0; j < unopt_start; j += 4) {
|
||||||
|
const float *in_here = cdat + j;
|
||||||
|
float *out_here = odat + j;
|
||||||
|
__m128 samples = _mm_loadu_ps(in_here);
|
||||||
|
__m128 multiplied = _mm_mul_ps(samples, mult);
|
||||||
|
_mm_storeu_ps(out_here, multiplied);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
for (size_t j = unopt_start; j < input->sample_count_per_channel_; j++) {
|
||||||
|
odat[j] = cdat[j] * volume;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SampleBuffer::transform_volume_for_sample(size_t sample_index,
|
||||||
|
float volume)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < audio_params().channel_count(); i++) {
|
||||||
|
transform_volume_for_sample_on_channel(sample_index, i, volume);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SampleBuffer::transform_volume_for_sample_on_channel(size_t sample_index,
|
||||||
|
int channel,
|
||||||
|
float volume)
|
||||||
|
{
|
||||||
|
data_[channel][sample_index] *= volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SampleBuffer::clamp()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < channel_count(); i++) {
|
||||||
|
clamp_channel(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SampleBuffer::silence()
|
||||||
|
{
|
||||||
|
silence(0, sample_count_per_channel_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SampleBuffer::silence(size_t start_sample, size_t end_sample)
|
||||||
|
{
|
||||||
|
silence_bytes(start_sample * sizeof(float), end_sample * sizeof(float));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SampleBuffer::silence_bytes(size_t start_byte, size_t end_byte)
|
||||||
|
{
|
||||||
|
if (!is_allocated()) {
|
||||||
|
Log::Warning() << "Tried to fill an unallocated sample buffer";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < audio_params().channel_count(); i++) {
|
||||||
|
memset(reinterpret_cast<char *>(data_[i].data()) + start_byte, 0,
|
||||||
|
end_byte - start_byte);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SampleBuffer::set(int channel, const float *data, size_t sample_offset,
|
||||||
|
size_t sample_length)
|
||||||
|
{
|
||||||
|
if (!is_allocated()) {
|
||||||
|
Log::Warning() << "Tried to fill an unallocated sample buffer";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(&data_[channel].data()[sample_offset], data,
|
||||||
|
sizeof(float) * sample_length);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SampleBuffer::fast_set(const SampleBuffer &other, int to, int from)
|
||||||
|
{
|
||||||
|
if (from == -1) {
|
||||||
|
from = to;
|
||||||
|
}
|
||||||
|
|
||||||
|
data_[to] = other.data_[from];
|
||||||
|
}
|
||||||
|
|
||||||
|
void SampleBuffer::clamp_channel(int channel)
|
||||||
|
{
|
||||||
|
const float min = -1.0f;
|
||||||
|
const float max = 1.0f;
|
||||||
|
|
||||||
|
float *cdat = data_[channel].data();
|
||||||
|
size_t unopt_start = 0;
|
||||||
|
|
||||||
|
#if defined(OLIVE_PROCESSOR_X86) || defined(OLIVE_PROCESSOR_ARM)
|
||||||
|
__m128 min_sse = _mm_load1_ps(&min);
|
||||||
|
__m128 max_sse = _mm_load1_ps(&max);
|
||||||
|
|
||||||
|
unopt_start = (sample_count_per_channel_ / 4) * 4;
|
||||||
|
for (size_t j = 0; j < unopt_start; j += 4) {
|
||||||
|
float *here = cdat + j;
|
||||||
|
__m128 samples = _mm_loadu_ps(here);
|
||||||
|
|
||||||
|
samples = _mm_max_ps(samples, min_sse);
|
||||||
|
samples = _mm_min_ps(samples, max_sse);
|
||||||
|
|
||||||
|
_mm_storeu_ps(here, samples);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
for (size_t sample = unopt_start; sample < sample_count(); sample++) {
|
||||||
|
float &s = data(channel)[sample];
|
||||||
|
s = std::clamp(s, min, max);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
/***
|
||||||
|
|
||||||
|
Olive - Non-Linear Video Editor
|
||||||
|
Copyright (C) 2023 Olive Studios LLC
|
||||||
|
Modifications Copyright (C) 2025 mikesolar
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program 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 General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***/
|
||||||
|
|
||||||
|
#include "util/bezier.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace olive::core
|
||||||
|
{
|
||||||
|
|
||||||
|
Bezier::Bezier()
|
||||||
|
: x_(0)
|
||||||
|
, y_(0)
|
||||||
|
, cp1_x_(0)
|
||||||
|
, cp1_y_(0)
|
||||||
|
, cp2_x_(0)
|
||||||
|
, cp2_y_(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Bezier::Bezier(double x, double y)
|
||||||
|
: x_(x)
|
||||||
|
, y_(y)
|
||||||
|
, cp1_x_(0)
|
||||||
|
, cp1_y_(0)
|
||||||
|
, cp2_x_(0)
|
||||||
|
, cp2_y_(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Bezier::Bezier(double x, double y, double cp1_x, double cp1_y, double cp2_x,
|
||||||
|
double cp2_y)
|
||||||
|
: x_(x)
|
||||||
|
, y_(y)
|
||||||
|
, cp1_x_(cp1_x)
|
||||||
|
, cp1_y_(cp1_y)
|
||||||
|
, cp2_x_(cp2_x)
|
||||||
|
, cp2_y_(cp2_y)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
double Bezier::QuadraticXtoT(double x, double a, double b, double c)
|
||||||
|
{
|
||||||
|
// Clamp to prevent infinite loop
|
||||||
|
x = std::clamp(x, a, c);
|
||||||
|
|
||||||
|
return CalculateTFromX(false, x, a, b, c, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
double Bezier::QuadraticTtoY(double a, double b, double c, double t)
|
||||||
|
{
|
||||||
|
return std::pow(1.0 - t, 2) * a + 2 * (1.0 - t) * t * b +
|
||||||
|
std::pow(t, 2) * c;
|
||||||
|
}
|
||||||
|
|
||||||
|
double Bezier::CubicXtoT(double x, double a, double b, double c, double d)
|
||||||
|
{
|
||||||
|
// Clamp to prevent infinite loop
|
||||||
|
x = std::clamp(x, a, d);
|
||||||
|
|
||||||
|
return CalculateTFromX(true, x, a, b, c, d);
|
||||||
|
}
|
||||||
|
|
||||||
|
double Bezier::CubicTtoY(double a, double b, double c, double d, double t)
|
||||||
|
{
|
||||||
|
return std::pow(1.0 - t, 3) * a + 3 * std::pow(1.0 - t, 2) * t * b +
|
||||||
|
3 * (1.0 - t) * std::pow(t, 2) * c + std::pow(t, 3) * d;
|
||||||
|
}
|
||||||
|
|
||||||
|
double Bezier::CalculateTFromX(bool cubic, double x, double a, double b,
|
||||||
|
double c, double d)
|
||||||
|
{
|
||||||
|
double bottom = 0.0;
|
||||||
|
double top = 1.0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (bottom == top) {
|
||||||
|
return bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
double mid = (bottom + top) * 0.5;
|
||||||
|
double test = cubic ? CubicTtoY(a, b, c, d, mid) :
|
||||||
|
QuadraticTtoY(a, b, c, mid);
|
||||||
|
|
||||||
|
if (std::abs(test - x) < 0.000001) {
|
||||||
|
return mid;
|
||||||
|
} else if (x > test) {
|
||||||
|
bottom = mid;
|
||||||
|
} else {
|
||||||
|
top = mid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NAN;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,324 @@
|
|||||||
|
/***
|
||||||
|
|
||||||
|
Olive - Non-Linear Video Editor
|
||||||
|
Copyright (C) 2023 Olive Studios LLC
|
||||||
|
Modifications Copyright (C) 2025 mikesolar
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program 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 General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***/
|
||||||
|
|
||||||
|
#include "util/color.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <Imath/half.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
namespace olive::core
|
||||||
|
{
|
||||||
|
|
||||||
|
Color Color::fromHsv(const DataType &h, const DataType &s, const DataType &v)
|
||||||
|
{
|
||||||
|
DataType C = s * v;
|
||||||
|
DataType X = C * (1.0 - std::abs(std::fmod(h / 60.0, 2.0) - 1.0));
|
||||||
|
DataType m = v - C;
|
||||||
|
DataType Rs, Gs, Bs;
|
||||||
|
|
||||||
|
if (h >= 0.0 && h < 60.0) {
|
||||||
|
Rs = C;
|
||||||
|
Gs = X;
|
||||||
|
Bs = 0.0;
|
||||||
|
} else if (h >= 60.0 && h < 120.0) {
|
||||||
|
Rs = X;
|
||||||
|
Gs = C;
|
||||||
|
Bs = 0.0;
|
||||||
|
} else if (h >= 120.0 && h < 180.0) {
|
||||||
|
Rs = 0.0;
|
||||||
|
Gs = C;
|
||||||
|
Bs = X;
|
||||||
|
} else if (h >= 180.0 && h < 240.0) {
|
||||||
|
Rs = 0.0;
|
||||||
|
Gs = X;
|
||||||
|
Bs = C;
|
||||||
|
} else if (h >= 240.0 && h < 300.0) {
|
||||||
|
Rs = X;
|
||||||
|
Gs = 0.0;
|
||||||
|
Bs = C;
|
||||||
|
} else {
|
||||||
|
Rs = C;
|
||||||
|
Gs = 0.0;
|
||||||
|
Bs = X;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Color(Rs + m, Gs + m, Bs + m);
|
||||||
|
}
|
||||||
|
|
||||||
|
Color::Color(const char *data, const PixelFormat &format, int ch_layout)
|
||||||
|
{
|
||||||
|
*this = fromData(data, format, ch_layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Color::toHsv(DataType *hue, DataType *sat, DataType *val) const
|
||||||
|
{
|
||||||
|
DataType fCMax = std::max(std::max(red(), green()), blue());
|
||||||
|
DataType fCMin = std::min(std::min(red(), green()), blue());
|
||||||
|
DataType fDelta = fCMax - fCMin;
|
||||||
|
|
||||||
|
if (fDelta > 0) {
|
||||||
|
if (fCMax == red()) {
|
||||||
|
*hue = 60 * (fmod(((green() - blue()) / fDelta), 6));
|
||||||
|
} else if (fCMax == green()) {
|
||||||
|
*hue = 60 * (((blue() - red()) / fDelta) + 2);
|
||||||
|
} else if (fCMax == blue()) {
|
||||||
|
*hue = 60 * (((red() - green()) / fDelta) + 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fCMax > 0) {
|
||||||
|
*sat = fDelta / fCMax;
|
||||||
|
} else {
|
||||||
|
*sat = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
*val = fCMax;
|
||||||
|
} else {
|
||||||
|
*hue = 0;
|
||||||
|
*sat = 0;
|
||||||
|
*val = fCMax;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*hue < 0) {
|
||||||
|
*hue = 360 + *hue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Color::DataType Color::hsv_hue() const
|
||||||
|
{
|
||||||
|
DataType h, s, v;
|
||||||
|
toHsv(&h, &s, &v);
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color::DataType Color::hsv_saturation() const
|
||||||
|
{
|
||||||
|
DataType h, s, v;
|
||||||
|
toHsv(&h, &s, &v);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color::DataType Color::value() const
|
||||||
|
{
|
||||||
|
DataType h, s, v;
|
||||||
|
toHsv(&h, &s, &v);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Color::toHsl(DataType *hue, DataType *sat, DataType *lightness) const
|
||||||
|
{
|
||||||
|
DataType fCMin = std::min(red(), std::min(green(), blue()));
|
||||||
|
DataType fCMax = std::max(red(), std::max(green(), blue()));
|
||||||
|
|
||||||
|
*lightness = 0.5 * (fCMin + fCMax);
|
||||||
|
|
||||||
|
if (fCMin == fCMax) {
|
||||||
|
*sat = 0;
|
||||||
|
*hue = 0;
|
||||||
|
return;
|
||||||
|
|
||||||
|
} else if (*lightness < 0.5) {
|
||||||
|
*sat = (fCMax - fCMin) / (fCMax + fCMin);
|
||||||
|
} else {
|
||||||
|
*sat = (fCMax - fCMin) / (2.0 - fCMax - fCMin);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fCMax == red()) {
|
||||||
|
*hue = 60 * (green() - blue()) / (fCMax - fCMin);
|
||||||
|
}
|
||||||
|
if (fCMax == green()) {
|
||||||
|
*hue = 60 * (blue() - red()) / (fCMax - fCMin) + 120;
|
||||||
|
}
|
||||||
|
if (fCMax == blue()) {
|
||||||
|
*hue = 60 * (red() - green()) / (fCMax - fCMin) + 240;
|
||||||
|
}
|
||||||
|
if (*hue < 0) {
|
||||||
|
*hue = *hue + 360;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Color::DataType Color::hsl_hue() const
|
||||||
|
{
|
||||||
|
DataType h, s, l;
|
||||||
|
toHsl(&h, &s, &l);
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color::DataType Color::hsl_saturation() const
|
||||||
|
{
|
||||||
|
DataType h, s, l;
|
||||||
|
toHsl(&h, &s, &l);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color::DataType Color::lightness() const
|
||||||
|
{
|
||||||
|
DataType h, s, l;
|
||||||
|
toHsl(&h, &s, &l);
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Color::toData(char *out, const PixelFormat &format,
|
||||||
|
unsigned int nb_channels) const
|
||||||
|
{
|
||||||
|
unsigned int count = std::min(RGBA, nb_channels);
|
||||||
|
|
||||||
|
if (format == PixelFormat::U10 && count == 4) {
|
||||||
|
const uint32_t r = static_cast<uint32_t>(std::clamp(data_[0], DataType(0.0), DataType(1.0)) * 1023.0 + 0.5);
|
||||||
|
const uint32_t g = static_cast<uint32_t>(std::clamp(data_[1], DataType(0.0), DataType(1.0)) * 1023.0 + 0.5);
|
||||||
|
const uint32_t b = static_cast<uint32_t>(std::clamp(data_[2], DataType(0.0), DataType(1.0)) * 1023.0 + 0.5);
|
||||||
|
const uint32_t a = static_cast<uint32_t>(std::clamp(data_[3], DataType(0.0), DataType(1.0)) * 3.0 + 0.5);
|
||||||
|
reinterpret_cast<uint32_t *>(out)[0] = r | (g << 10) | (b << 20) | (a << 30);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < count; i++) {
|
||||||
|
DataType f = data_[i];
|
||||||
|
|
||||||
|
switch (format) {
|
||||||
|
case PixelFormat::INVALID:
|
||||||
|
case PixelFormat::COUNT:
|
||||||
|
break;
|
||||||
|
case PixelFormat::U8:
|
||||||
|
reinterpret_cast<uint8_t *>(out)[i] = f * 255.0;
|
||||||
|
break;
|
||||||
|
case PixelFormat::U10:
|
||||||
|
// handled above
|
||||||
|
break;
|
||||||
|
case PixelFormat::U16:
|
||||||
|
reinterpret_cast<uint16_t *>(out)[i] = f * 65535.0;
|
||||||
|
break;
|
||||||
|
case PixelFormat::F16:
|
||||||
|
reinterpret_cast<Imath::half *>(out)[i] = f;
|
||||||
|
break;
|
||||||
|
case PixelFormat::F32:
|
||||||
|
reinterpret_cast<float *>(out)[i] = f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Color Color::fromData(const char *in, const PixelFormat &format,
|
||||||
|
unsigned int nb_channels)
|
||||||
|
{
|
||||||
|
Color c;
|
||||||
|
|
||||||
|
unsigned int count = std::min(RGBA, nb_channels);
|
||||||
|
|
||||||
|
if (format == PixelFormat::U10 && count == 4) {
|
||||||
|
const uint32_t word = reinterpret_cast<const uint32_t *>(in)[0];
|
||||||
|
c.data_[0] = DataType((word & 0x3ff) / 1023.0);
|
||||||
|
c.data_[1] = DataType(((word >> 10) & 0x3ff) / 1023.0);
|
||||||
|
c.data_[2] = DataType(((word >> 20) & 0x3ff) / 1023.0);
|
||||||
|
c.data_[3] = DataType(((word >> 30) & 0x3) / 3.0);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < count; i++) {
|
||||||
|
DataType &f = c.data_[i];
|
||||||
|
|
||||||
|
switch (format) {
|
||||||
|
case PixelFormat::INVALID:
|
||||||
|
case PixelFormat::COUNT:
|
||||||
|
break;
|
||||||
|
case PixelFormat::U8:
|
||||||
|
f = DataType(reinterpret_cast<const uint8_t *>(in)[i]) / 255.0;
|
||||||
|
break;
|
||||||
|
case PixelFormat::U10:
|
||||||
|
// handled above
|
||||||
|
break;
|
||||||
|
case PixelFormat::U16:
|
||||||
|
f = DataType(reinterpret_cast<const uint16_t *>(in)[i]) / 65535.0;
|
||||||
|
break;
|
||||||
|
case PixelFormat::F16:
|
||||||
|
f = DataType(reinterpret_cast<const Imath::half *>(in)[i]);
|
||||||
|
break;
|
||||||
|
case PixelFormat::F32:
|
||||||
|
f = DataType(reinterpret_cast<const float *>(in)[i]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color::DataType Color::GetRoughLuminance() const
|
||||||
|
{
|
||||||
|
return (2 * red() + blue() + 3 * green()) / 6.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color &Color::operator+=(const Color &rhs)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < RGBA; i++) {
|
||||||
|
data_[i] += rhs.data_[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color &Color::operator-=(const Color &rhs)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < RGBA; i++) {
|
||||||
|
data_[i] -= rhs.data_[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color &Color::operator+=(const DataType &rhs)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < RGBA; i++) {
|
||||||
|
data_[i] += rhs;
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color &Color::operator-=(const DataType &rhs)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < RGBA; i++) {
|
||||||
|
data_[i] -= rhs;
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color &Color::operator*=(const DataType &rhs)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < RGBA; i++) {
|
||||||
|
data_[i] *= rhs;
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color &Color::operator/=(const DataType &rhs)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < RGBA; i++) {
|
||||||
|
data_[i] /= rhs;
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,286 @@
|
|||||||
|
/***
|
||||||
|
|
||||||
|
Olive - Non-Linear Video Editor
|
||||||
|
Copyright (C) 2023 Olive Studios LLC
|
||||||
|
Modifications Copyright (C) 2025 mikesolar
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program 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 General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***/
|
||||||
|
|
||||||
|
#include "util/rational.h"
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include "util/stringutils.h"
|
||||||
|
|
||||||
|
namespace olive::core
|
||||||
|
{
|
||||||
|
|
||||||
|
const rational rational::NaN = rational(0, 0);
|
||||||
|
|
||||||
|
rational rational::fromDouble(const double &flt, bool *ok)
|
||||||
|
{
|
||||||
|
if (isnan(flt)) {
|
||||||
|
// Return NaN rational
|
||||||
|
if (ok)
|
||||||
|
*ok = false;
|
||||||
|
return NaN;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use FFmpeg function for the time being
|
||||||
|
AVRational r = av_d2q(flt, INT_MAX);
|
||||||
|
|
||||||
|
if (r.den == 0) {
|
||||||
|
// If den == 0, we were unable to convert to a rational
|
||||||
|
if (ok) {
|
||||||
|
*ok = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Otherwise, assume we received a real rational
|
||||||
|
if (ok) {
|
||||||
|
*ok = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
rational rational::fromString(const std::string &str, bool *ok)
|
||||||
|
{
|
||||||
|
std::vector<std::string> elements = StringUtils::split(str, '/');
|
||||||
|
|
||||||
|
switch (elements.size()) {
|
||||||
|
case 1:
|
||||||
|
return rational(StringUtils::to_int(elements.front(), ok));
|
||||||
|
case 2:
|
||||||
|
return rational(StringUtils::to_int(elements.at(0), ok),
|
||||||
|
StringUtils::to_int(elements.at(1), ok));
|
||||||
|
default:
|
||||||
|
// Returns NaN with ok set to false
|
||||||
|
if (ok) {
|
||||||
|
*ok = false;
|
||||||
|
}
|
||||||
|
return NaN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Function: convert to double
|
||||||
|
|
||||||
|
double rational::toDouble() const
|
||||||
|
{
|
||||||
|
if (r_.den != 0) {
|
||||||
|
return av_q2d(r_);
|
||||||
|
} else {
|
||||||
|
return std::numeric_limits<double>::quiet_NaN();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AVRational rational::toAVRational() const
|
||||||
|
{
|
||||||
|
return r_;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef USE_OTIO
|
||||||
|
opentime::RationalTime rational::toRationalTime(double framerate) const
|
||||||
|
{
|
||||||
|
// Is this the best way of doing this?
|
||||||
|
// Olive can store rationals as 0/0 which causes errors in OTIO
|
||||||
|
opentime::RationalTime time =
|
||||||
|
opentime::RationalTime(r_.num, r_.den == 0 ? 1 : r_.den);
|
||||||
|
return time.rescaled_to(framerate);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
rational rational::flipped() const
|
||||||
|
{
|
||||||
|
rational r = *this;
|
||||||
|
r.flip();
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rational::flip()
|
||||||
|
{
|
||||||
|
if (!isNull()) {
|
||||||
|
std::swap(r_.den, r_.num);
|
||||||
|
fix_signs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string rational::toString() const
|
||||||
|
{
|
||||||
|
return StringUtils::format("%d/%d", r_.num, r_.den);
|
||||||
|
}
|
||||||
|
|
||||||
|
void rational::fix_signs()
|
||||||
|
{
|
||||||
|
if (r_.den < 0) {
|
||||||
|
// Normalize so that denominator is always positive
|
||||||
|
r_.den = -r_.den;
|
||||||
|
r_.num = -r_.num;
|
||||||
|
} else if (r_.den == 0) {
|
||||||
|
// Normalize to 0/0 (aka NaN) if denominator is zero
|
||||||
|
r_.num = 0;
|
||||||
|
} else if (r_.num == 0) {
|
||||||
|
// Normalize to 0/1 if numerator is zero
|
||||||
|
r_.den = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void rational::reduce()
|
||||||
|
{
|
||||||
|
av_reduce(&r_.num, &r_.den, r_.num, r_.den, INT_MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Assignment Operators
|
||||||
|
|
||||||
|
const rational &rational::operator=(const rational &rhs)
|
||||||
|
{
|
||||||
|
r_ = rhs.r_;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rational &rational::operator+=(const rational &rhs)
|
||||||
|
{
|
||||||
|
if (*this == RATIONAL_MIN || *this == RATIONAL_MAX || rhs == RATIONAL_MIN ||
|
||||||
|
rhs == RATIONAL_MAX) {
|
||||||
|
*this = NaN;
|
||||||
|
} else if (!isNaN()) {
|
||||||
|
if (rhs.isNaN()) {
|
||||||
|
*this = NaN;
|
||||||
|
} else {
|
||||||
|
r_ = av_add_q(r_, rhs.r_);
|
||||||
|
fix_signs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rational &rational::operator-=(const rational &rhs)
|
||||||
|
{
|
||||||
|
if (*this == RATIONAL_MIN || *this == RATIONAL_MAX || rhs == RATIONAL_MIN ||
|
||||||
|
rhs == RATIONAL_MAX) {
|
||||||
|
*this = NaN;
|
||||||
|
} else if (!isNaN()) {
|
||||||
|
if (rhs.isNaN()) {
|
||||||
|
*this = NaN;
|
||||||
|
} else {
|
||||||
|
r_ = av_sub_q(r_, rhs.r_);
|
||||||
|
fix_signs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rational &rational::operator*=(const rational &rhs)
|
||||||
|
{
|
||||||
|
if (*this == RATIONAL_MIN || *this == RATIONAL_MAX || rhs == RATIONAL_MIN ||
|
||||||
|
rhs == RATIONAL_MAX) {
|
||||||
|
*this = NaN;
|
||||||
|
} else if (!isNaN()) {
|
||||||
|
if (rhs.isNaN()) {
|
||||||
|
*this = NaN;
|
||||||
|
} else {
|
||||||
|
r_ = av_mul_q(r_, rhs.r_);
|
||||||
|
fix_signs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rational &rational::operator/=(const rational &rhs)
|
||||||
|
{
|
||||||
|
if (*this == RATIONAL_MIN || *this == RATIONAL_MAX || rhs == RATIONAL_MIN ||
|
||||||
|
rhs == RATIONAL_MAX) {
|
||||||
|
*this = NaN;
|
||||||
|
} else if (!isNaN()) {
|
||||||
|
if (rhs.isNaN()) {
|
||||||
|
*this = NaN;
|
||||||
|
} else {
|
||||||
|
r_ = av_div_q(r_, rhs.r_);
|
||||||
|
fix_signs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Binary math operators
|
||||||
|
|
||||||
|
rational rational::operator+(const rational &rhs) const
|
||||||
|
{
|
||||||
|
rational answer(*this);
|
||||||
|
answer += rhs;
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
rational rational::operator-(const rational &rhs) const
|
||||||
|
{
|
||||||
|
rational answer(*this);
|
||||||
|
answer -= rhs;
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
rational rational::operator/(const rational &rhs) const
|
||||||
|
{
|
||||||
|
rational answer(*this);
|
||||||
|
answer /= rhs;
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
rational rational::operator*(const rational &rhs) const
|
||||||
|
{
|
||||||
|
rational answer(*this);
|
||||||
|
answer *= rhs;
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Relational and equality operators
|
||||||
|
|
||||||
|
bool rational::operator<(const rational &rhs) const
|
||||||
|
{
|
||||||
|
return av_cmp_q(r_, rhs.r_) == -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rational::operator<=(const rational &rhs) const
|
||||||
|
{
|
||||||
|
int cmp = av_cmp_q(r_, rhs.r_);
|
||||||
|
return cmp == 0 || cmp == -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rational::operator>(const rational &rhs) const
|
||||||
|
{
|
||||||
|
return av_cmp_q(r_, rhs.r_) == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rational::operator>=(const rational &rhs) const
|
||||||
|
{
|
||||||
|
int cmp = av_cmp_q(r_, rhs.r_);
|
||||||
|
return cmp == 0 || cmp == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rational::operator==(const rational &rhs) const
|
||||||
|
{
|
||||||
|
return av_cmp_q(r_, rhs.r_) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rational::operator!=(const rational &rhs) const
|
||||||
|
{
|
||||||
|
return !(*this == rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
/***
|
||||||
|
|
||||||
|
Olive - Non-Linear Video Editor
|
||||||
|
Copyright (C) 2023 Olive Studios LLC
|
||||||
|
Modifications Copyright (C) 2025 mikesolar
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program 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 General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***/
|
||||||
|
|
||||||
|
#include "util/stringutils.h"
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace olive::core
|
||||||
|
{
|
||||||
|
|
||||||
|
std::vector<std::string> StringUtils::split(const std::string &s,
|
||||||
|
char separator)
|
||||||
|
{
|
||||||
|
std::vector<std::string> output;
|
||||||
|
|
||||||
|
std::string::size_type prev_pos = 0, pos = 0;
|
||||||
|
|
||||||
|
while ((pos = s.find(separator, pos)) != std::string::npos) {
|
||||||
|
std::string substring(s.substr(prev_pos, pos - prev_pos));
|
||||||
|
|
||||||
|
output.push_back(substring);
|
||||||
|
|
||||||
|
prev_pos = ++pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
output.push_back(s.substr(prev_pos, pos - prev_pos)); // Last word
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> StringUtils::split_regex(const std::string &s,
|
||||||
|
const std::regex ®ex)
|
||||||
|
{
|
||||||
|
std::vector<std::string> output;
|
||||||
|
|
||||||
|
std::sregex_token_iterator iter(s.begin(), s.end(), regex, -1);
|
||||||
|
std::sregex_token_iterator end;
|
||||||
|
for (; iter != end; iter++) {
|
||||||
|
output.push_back(*iter);
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
int StringUtils::to_int(const std::string &s, int base, bool *ok)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
int x = std::stoi(s, nullptr, base);
|
||||||
|
if (ok) {
|
||||||
|
*ok = true;
|
||||||
|
}
|
||||||
|
return x;
|
||||||
|
} catch (const std::invalid_argument &e) {
|
||||||
|
if (ok) {
|
||||||
|
*ok = false;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string StringUtils::format(const char *fmt, ...)
|
||||||
|
{
|
||||||
|
va_list ap1, ap2;
|
||||||
|
va_start(ap1, fmt);
|
||||||
|
|
||||||
|
// Need to duplicate because we call vsnprintf twice and it consumes the va_list each time
|
||||||
|
va_copy(ap2, ap1);
|
||||||
|
|
||||||
|
int s = std::vsnprintf(nullptr, 0, fmt, ap1);
|
||||||
|
|
||||||
|
// Create string with size, adding 1 because vsnprintf will want to write a null terminator
|
||||||
|
std::string r;
|
||||||
|
s++;
|
||||||
|
r.resize(s);
|
||||||
|
|
||||||
|
// Write into string
|
||||||
|
std::vsnprintf(r.data(), s, fmt, ap2);
|
||||||
|
|
||||||
|
// Pop null terminator
|
||||||
|
r.pop_back();
|
||||||
|
|
||||||
|
va_end(ap2);
|
||||||
|
va_end(ap1);
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
/***
|
||||||
|
|
||||||
|
Olive - Non-Linear Video Editor
|
||||||
|
Copyright (C) 2023 Olive Studios LLC
|
||||||
|
Modifications Copyright (C) 2025 mikesolar
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program 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 General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***/
|
||||||
|
|
||||||
|
#include "util/tests.h"
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <stdarg.h>
|
||||||
|
|
||||||
|
namespace olive::core
|
||||||
|
{
|
||||||
|
|
||||||
|
bool Tester::run()
|
||||||
|
{
|
||||||
|
size_t index = 1;
|
||||||
|
size_t count = test_functions_.size();
|
||||||
|
|
||||||
|
while (!test_functions_.empty()) {
|
||||||
|
echo("[%lu/%lu] %s :: ", index, count, test_names_.front());
|
||||||
|
|
||||||
|
if (test_functions_.front()()) {
|
||||||
|
echo("PASSED\n");
|
||||||
|
} else {
|
||||||
|
echo("FAILED\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
test_names_.pop_front();
|
||||||
|
test_functions_.pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tester::echo(const char *fmt, ...)
|
||||||
|
{
|
||||||
|
va_list a;
|
||||||
|
va_start(a, fmt);
|
||||||
|
|
||||||
|
vfprintf(stderr, fmt, a);
|
||||||
|
|
||||||
|
va_end(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,407 @@
|
|||||||
|
/***
|
||||||
|
|
||||||
|
Olive - Non-Linear Video Editor
|
||||||
|
Copyright (C) 2023 Olive Studios LLC
|
||||||
|
Modifications Copyright (C) 2025 mikesolar
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program 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 General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***/
|
||||||
|
|
||||||
|
#include "util/timecodefunctions.h"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include <libavutil/mathematics.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "util/stringutils.h"
|
||||||
|
|
||||||
|
namespace olive::core
|
||||||
|
{
|
||||||
|
|
||||||
|
std::string Timecode::time_to_timecode(const rational &time,
|
||||||
|
const rational &timebase,
|
||||||
|
const Timecode::Display &display,
|
||||||
|
bool show_plus_if_positive)
|
||||||
|
{
|
||||||
|
if (timebase.isNull() || timebase.flipped().toDouble() < 1) {
|
||||||
|
return "INVALID TIMEBASE";
|
||||||
|
}
|
||||||
|
|
||||||
|
double time_dbl = time.toDouble();
|
||||||
|
|
||||||
|
switch (display) {
|
||||||
|
case kTimecodeNonDropFrame:
|
||||||
|
case kTimecodeDropFrame:
|
||||||
|
case kTimecodeSeconds: {
|
||||||
|
const char *prefix = "";
|
||||||
|
|
||||||
|
if (time_dbl < 0) {
|
||||||
|
prefix = "-";
|
||||||
|
} else if (show_plus_if_positive) {
|
||||||
|
prefix = "+";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (display == kTimecodeSeconds) {
|
||||||
|
time_dbl = std::abs(time_dbl);
|
||||||
|
|
||||||
|
int64_t total_seconds = std::floor(time_dbl);
|
||||||
|
|
||||||
|
int64_t hours = total_seconds / 3600;
|
||||||
|
int64_t mins = total_seconds / 60 - hours * 60;
|
||||||
|
int64_t secs = total_seconds - mins * 60;
|
||||||
|
int64_t fraction = std::llround(
|
||||||
|
(time_dbl - static_cast<double>(total_seconds)) * 1000);
|
||||||
|
|
||||||
|
return StringUtils::format(
|
||||||
|
"%s%s:%s:%s.%s", prefix,
|
||||||
|
StringUtils::to_string_leftpad(hours, 2).c_str(),
|
||||||
|
StringUtils::to_string_leftpad(mins, 2).c_str(),
|
||||||
|
StringUtils::to_string_leftpad(secs, 2).c_str(),
|
||||||
|
StringUtils::to_string_leftpad(fraction, 3).c_str());
|
||||||
|
} else {
|
||||||
|
// Determine what symbol to separate frames (";" is used for drop frame, ":" is non-drop frame)
|
||||||
|
const char *frame_token;
|
||||||
|
double frame_rate = timebase.flipped().toDouble();
|
||||||
|
int rounded_frame_rate = std::llround(frame_rate);
|
||||||
|
int64_t frames, secs, mins, hours;
|
||||||
|
int64_t f = std::abs(time_to_timestamp(time, timebase));
|
||||||
|
|
||||||
|
if (display == kTimecodeDropFrame &&
|
||||||
|
timebase_is_drop_frame(timebase)) {
|
||||||
|
frame_token = ";";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CONVERT A FRAME NUMBER TO DROP FRAME TIMECODE
|
||||||
|
*
|
||||||
|
* Code by David Heidelberger, adapted from Andrew Duncan, further adapted for Olive by Olive Team
|
||||||
|
* Given an int called framenumber and a double called framerate
|
||||||
|
* Framerate should be 29.97, 59.94, or 23.976, otherwise the calculations will be off.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// If frame number is greater than 24 hrs, next operation will rollover clock
|
||||||
|
f %= (std::llround(frame_rate * 3600) * 24);
|
||||||
|
|
||||||
|
// Number of frames per ten minutes
|
||||||
|
int64_t framesPer10Minutes = std::llround(frame_rate * 600);
|
||||||
|
int64_t d = f / framesPer10Minutes;
|
||||||
|
int64_t m = f % framesPer10Minutes;
|
||||||
|
|
||||||
|
// Number of frames to drop on the minute marks is the nearest integer to 6% of the framerate
|
||||||
|
int64_t dropFrames = std::llround(frame_rate * (2.0 / 30.0));
|
||||||
|
|
||||||
|
// Number of frames per minute is the round of the framerate * 60 minus the number of dropped frames
|
||||||
|
f += dropFrames * 9 * d;
|
||||||
|
if (m > dropFrames) {
|
||||||
|
f += dropFrames *
|
||||||
|
((m - dropFrames) /
|
||||||
|
(std::llround(frame_rate) * 60 - dropFrames));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
frame_token = ":";
|
||||||
|
}
|
||||||
|
|
||||||
|
// non-drop timecode
|
||||||
|
hours = f / (3600 * rounded_frame_rate);
|
||||||
|
mins = f / (60 * rounded_frame_rate) % 60;
|
||||||
|
secs = f / rounded_frame_rate % 60;
|
||||||
|
frames = f % rounded_frame_rate;
|
||||||
|
|
||||||
|
return StringUtils::format(
|
||||||
|
"%s%s:%s:%s%s%s", prefix,
|
||||||
|
StringUtils::to_string_leftpad(hours, 2).c_str(),
|
||||||
|
StringUtils::to_string_leftpad(mins, 2).c_str(),
|
||||||
|
StringUtils::to_string_leftpad(secs, 2).c_str(), frame_token,
|
||||||
|
StringUtils::to_string_leftpad(frames, 2).c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case kFrames:
|
||||||
|
return std::to_string(time_to_timestamp(time, timebase));
|
||||||
|
case kMilliseconds:
|
||||||
|
return std::to_string(std::llround(time_dbl * 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
return "INVALID TIMECODE MODE";
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t StrToInt64EmptyTolerant(const std::string &s, bool *ok)
|
||||||
|
{
|
||||||
|
if (s.empty()) {
|
||||||
|
if (ok)
|
||||||
|
*ok = true;
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
int64_t ll = std::stoll(s);
|
||||||
|
if (ok)
|
||||||
|
*ok = true;
|
||||||
|
return ll;
|
||||||
|
} catch (const std::invalid_argument &e) {
|
||||||
|
if (ok)
|
||||||
|
*ok = false;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double StrToDoubleEmptyTolerant(const std::string &s, bool *ok)
|
||||||
|
{
|
||||||
|
if (s.empty()) {
|
||||||
|
if (ok)
|
||||||
|
*ok = true;
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
double d = std::stod(s);
|
||||||
|
if (ok)
|
||||||
|
*ok = true;
|
||||||
|
return d;
|
||||||
|
} catch (const std::invalid_argument &e) {
|
||||||
|
if (ok)
|
||||||
|
*ok = false;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rational Timecode::timecode_to_time(std::string timecode,
|
||||||
|
const rational &timebase,
|
||||||
|
const Timecode::Display &display, bool *ok)
|
||||||
|
{
|
||||||
|
StringUtils::trim(timecode);
|
||||||
|
if (timecode.empty()) {
|
||||||
|
goto err_fatal;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (display) {
|
||||||
|
case kTimecodeNonDropFrame:
|
||||||
|
case kTimecodeDropFrame:
|
||||||
|
case kTimecodeSeconds: {
|
||||||
|
std::vector<std::string> timecode_split =
|
||||||
|
StringUtils::split_regex(timecode, std::regex("(:)|(;)"));
|
||||||
|
|
||||||
|
const int element_count = display == kTimecodeSeconds ? 3 : 4;
|
||||||
|
|
||||||
|
// Remove excess tokens (we're only interested in HH:MM:SS.FF)
|
||||||
|
if (timecode_split.size() > element_count) {
|
||||||
|
timecode_split.resize(element_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For easier index calculations, ensure minimum size
|
||||||
|
if (timecode_split.size() < element_count) {
|
||||||
|
timecode_split.insert(timecode_split.begin(),
|
||||||
|
element_count - timecode_split.size(),
|
||||||
|
std::string());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool negative = (timecode.at(0) == '-');
|
||||||
|
|
||||||
|
double frame_rate = timebase.flipped().toDouble();
|
||||||
|
int rounded_frame_rate = std::lround(frame_rate);
|
||||||
|
|
||||||
|
bool valid;
|
||||||
|
rational time;
|
||||||
|
|
||||||
|
int64_t hours = StrToInt64EmptyTolerant(timecode_split.at(0), &valid);
|
||||||
|
if (!valid)
|
||||||
|
goto err_fatal;
|
||||||
|
int64_t mins = StrToInt64EmptyTolerant(timecode_split.at(1), &valid);
|
||||||
|
if (!valid)
|
||||||
|
goto err_fatal;
|
||||||
|
|
||||||
|
if (display == kTimecodeSeconds) {
|
||||||
|
double secs =
|
||||||
|
StrToDoubleEmptyTolerant(timecode_split.at(2), &valid);
|
||||||
|
if (!valid)
|
||||||
|
goto err_fatal;
|
||||||
|
|
||||||
|
time = rational::fromDouble(hours * 3600 + mins * 60 + secs);
|
||||||
|
} else {
|
||||||
|
int64_t secs =
|
||||||
|
StrToInt64EmptyTolerant(timecode_split.at(2), &valid);
|
||||||
|
if (!valid)
|
||||||
|
goto err_fatal;
|
||||||
|
int64_t frames =
|
||||||
|
StrToInt64EmptyTolerant(timecode_split.at(3), &valid);
|
||||||
|
if (!valid)
|
||||||
|
goto err_fatal;
|
||||||
|
|
||||||
|
int64_t sec_count = (hours * 3600 + mins * 60 + secs);
|
||||||
|
int64_t frame_count = sec_count * rounded_frame_rate + frames;
|
||||||
|
|
||||||
|
if (display == kTimecodeDropFrame &&
|
||||||
|
timebase_is_drop_frame(timebase)) {
|
||||||
|
// Number of frames to drop on the minute marks is the nearest integer to 6% of the framerate
|
||||||
|
int64_t dropFrames = std::llround(frame_rate * (2.0 / 30.0));
|
||||||
|
|
||||||
|
// d and m need to be calculated from
|
||||||
|
int64_t real_fr_ts =
|
||||||
|
std::llround(static_cast<double>(sec_count) * frame_rate) +
|
||||||
|
frames;
|
||||||
|
|
||||||
|
int64_t framesPer10Minutes = std::llround(frame_rate * 600);
|
||||||
|
int64_t d = real_fr_ts / framesPer10Minutes;
|
||||||
|
int64_t m = real_fr_ts % framesPer10Minutes;
|
||||||
|
|
||||||
|
if (m > dropFrames) {
|
||||||
|
frame_count -=
|
||||||
|
dropFrames *
|
||||||
|
((m - dropFrames) /
|
||||||
|
(std::llround(frame_rate) * 60 - dropFrames));
|
||||||
|
}
|
||||||
|
frame_count -= dropFrames * 9 * d;
|
||||||
|
}
|
||||||
|
|
||||||
|
time = timestamp_to_time(frame_count, timebase);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ok)
|
||||||
|
*ok = true;
|
||||||
|
|
||||||
|
if (negative)
|
||||||
|
time = -time;
|
||||||
|
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
case kMilliseconds: {
|
||||||
|
try {
|
||||||
|
double timecode_secs = std::stod(timecode);
|
||||||
|
|
||||||
|
// Convert milliseconds to seconds
|
||||||
|
timecode_secs *= 0.001;
|
||||||
|
|
||||||
|
// Convert seconds to rational
|
||||||
|
return rational::fromDouble(timecode_secs, ok);
|
||||||
|
} catch (const std::invalid_argument &e) {
|
||||||
|
goto err_fatal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case kFrames: {
|
||||||
|
try {
|
||||||
|
int64_t ts = std::stoll(timecode);
|
||||||
|
if (ok)
|
||||||
|
*ok = true;
|
||||||
|
return timestamp_to_time(ts, timebase);
|
||||||
|
} catch (const std::invalid_argument &e) {
|
||||||
|
goto err_fatal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err_fatal:
|
||||||
|
if (ok)
|
||||||
|
*ok = false;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Timecode::time_to_string(int64_t ms)
|
||||||
|
{
|
||||||
|
int64_t total_seconds = ms / 1000;
|
||||||
|
int64_t ss = total_seconds % 60;
|
||||||
|
int64_t mm = (total_seconds / 60) % 60;
|
||||||
|
int64_t hh = total_seconds / 3600;
|
||||||
|
|
||||||
|
return StringUtils::format("%s:%s:%s",
|
||||||
|
StringUtils::to_string_leftpad(hh, 2).c_str(),
|
||||||
|
StringUtils::to_string_leftpad(mm, 2).c_str(),
|
||||||
|
StringUtils::to_string_leftpad(ss, 2).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
rational Timecode::snap_time_to_timebase(const rational &time,
|
||||||
|
const rational &timebase,
|
||||||
|
Rounding floor)
|
||||||
|
{
|
||||||
|
// Just convert to a timestamp in timebase units and back
|
||||||
|
int64_t timestamp = time_to_timestamp(time, timebase, floor);
|
||||||
|
|
||||||
|
return timestamp_to_time(timestamp, timebase);
|
||||||
|
}
|
||||||
|
|
||||||
|
rational Timecode::timestamp_to_time(const int64_t ×tamp,
|
||||||
|
const rational &timebase)
|
||||||
|
{
|
||||||
|
int64_t num = int64_t(timebase.numerator()) * timestamp;
|
||||||
|
int64_t den = timebase.denominator();
|
||||||
|
|
||||||
|
int num_r, den_r;
|
||||||
|
|
||||||
|
av_reduce(&num_r, &den_r, num, den, INT_MAX);
|
||||||
|
|
||||||
|
return rational(num_r, den_r);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Timecode::timebase_is_drop_frame(const rational &timebase)
|
||||||
|
{
|
||||||
|
return (timebase.numerator() != 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t Timecode::time_to_timestamp(const rational &time,
|
||||||
|
const rational &timebase, Rounding floor)
|
||||||
|
{
|
||||||
|
return time_to_timestamp(time.toDouble(), timebase, floor);
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t Timecode::time_to_timestamp(const double &time,
|
||||||
|
const rational &timebase, Rounding floor)
|
||||||
|
{
|
||||||
|
const double d = time * timebase.flipped().toDouble();
|
||||||
|
|
||||||
|
if (std::isnan(d)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const double eps = 0.000000000001;
|
||||||
|
|
||||||
|
switch (floor) {
|
||||||
|
case kRound:
|
||||||
|
default:
|
||||||
|
return std::llround(d);
|
||||||
|
case kFloor:
|
||||||
|
if (d > std::ceil(d) - eps) {
|
||||||
|
return std::ceil(d);
|
||||||
|
} else {
|
||||||
|
return std::floor(d);
|
||||||
|
}
|
||||||
|
case kCeil:
|
||||||
|
if (d < std::floor(d) + eps) {
|
||||||
|
return std::floor(d);
|
||||||
|
} else {
|
||||||
|
return std::ceil(d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t Timecode::rescale_timestamp(const int64_t &ts, const rational &source,
|
||||||
|
const rational &dest)
|
||||||
|
{
|
||||||
|
if (source == dest) {
|
||||||
|
return ts;
|
||||||
|
}
|
||||||
|
|
||||||
|
return av_rescale_q(ts, source.toAVRational(), dest.toAVRational());
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t Timecode::rescale_timestamp_ceil(const int64_t &ts,
|
||||||
|
const rational &source,
|
||||||
|
const rational &dest)
|
||||||
|
{
|
||||||
|
if (source == dest) {
|
||||||
|
return ts;
|
||||||
|
}
|
||||||
|
|
||||||
|
return av_rescale_q_rnd(ts, source.toAVRational(), dest.toAVRational(),
|
||||||
|
AV_ROUND_UP);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,398 @@
|
|||||||
|
/***
|
||||||
|
|
||||||
|
Olive - Non-Linear Video Editor
|
||||||
|
Copyright (C) 2023 Olive Studios LLC
|
||||||
|
Modifications Copyright (C) 2025 mikesolar
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program 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 General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***/
|
||||||
|
|
||||||
|
#include "util/timerange.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "util/timecodefunctions.h"
|
||||||
|
|
||||||
|
namespace olive::core
|
||||||
|
{
|
||||||
|
|
||||||
|
TimeRange::TimeRange(const rational &in, const rational &out)
|
||||||
|
: in_(in)
|
||||||
|
, out_(out)
|
||||||
|
{
|
||||||
|
normalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
const rational &TimeRange::in() const
|
||||||
|
{
|
||||||
|
return in_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rational &TimeRange::out() const
|
||||||
|
{
|
||||||
|
return out_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rational &TimeRange::length() const
|
||||||
|
{
|
||||||
|
return length_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeRange::set_in(const rational &in)
|
||||||
|
{
|
||||||
|
in_ = in;
|
||||||
|
normalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeRange::set_out(const rational &out)
|
||||||
|
{
|
||||||
|
out_ = out;
|
||||||
|
normalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeRange::set_range(const rational &in, const rational &out)
|
||||||
|
{
|
||||||
|
in_ = in;
|
||||||
|
out_ = out;
|
||||||
|
normalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TimeRange::operator==(const TimeRange &r) const
|
||||||
|
{
|
||||||
|
return in() == r.in() && out() == r.out();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TimeRange::operator!=(const TimeRange &r) const
|
||||||
|
{
|
||||||
|
return in() != r.in() || out() != r.out();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TimeRange::OverlapsWith(const TimeRange &a, bool in_inclusive,
|
||||||
|
bool out_inclusive) const
|
||||||
|
{
|
||||||
|
bool doesnt_overlap_in = (in_inclusive) ? (a.out() < in()) :
|
||||||
|
(a.out() <= in());
|
||||||
|
|
||||||
|
bool doesnt_overlap_out = (out_inclusive) ? (a.in() > out()) :
|
||||||
|
(a.in() >= out());
|
||||||
|
|
||||||
|
return !doesnt_overlap_in && !doesnt_overlap_out;
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeRange TimeRange::Combined(const TimeRange &a) const
|
||||||
|
{
|
||||||
|
return Combine(a, *this);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TimeRange::Contains(const TimeRange &compare, bool in_inclusive,
|
||||||
|
bool out_inclusive) const
|
||||||
|
{
|
||||||
|
bool contains_in = (in_inclusive) ? (compare.in() >= in()) :
|
||||||
|
(compare.in() > in());
|
||||||
|
|
||||||
|
bool contains_out = (out_inclusive) ? (compare.out() <= out()) :
|
||||||
|
(compare.out() < out());
|
||||||
|
|
||||||
|
return contains_in && contains_out;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TimeRange::Contains(const rational &r) const
|
||||||
|
{
|
||||||
|
return r >= in_ && r < out_;
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeRange TimeRange::Combine(const TimeRange &a, const TimeRange &b)
|
||||||
|
{
|
||||||
|
return TimeRange(std::min(a.in(), b.in()), std::max(a.out(), b.out()));
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeRange TimeRange::Intersected(const TimeRange &a) const
|
||||||
|
{
|
||||||
|
return Intersect(a, *this);
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeRange TimeRange::Intersect(const TimeRange &a, const TimeRange &b)
|
||||||
|
{
|
||||||
|
return TimeRange(std::max(a.in(), b.in()), std::min(a.out(), b.out()));
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeRange TimeRange::operator+(const rational &rhs) const
|
||||||
|
{
|
||||||
|
TimeRange answer(*this);
|
||||||
|
answer += rhs;
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeRange TimeRange::operator-(const rational &rhs) const
|
||||||
|
{
|
||||||
|
TimeRange answer(*this);
|
||||||
|
answer -= rhs;
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TimeRange &TimeRange::operator+=(const rational &rhs)
|
||||||
|
{
|
||||||
|
set_range(in_ + rhs, out_ + rhs);
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TimeRange &TimeRange::operator-=(const rational &rhs)
|
||||||
|
{
|
||||||
|
set_range(in_ - rhs, out_ - rhs);
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::list<TimeRange> TimeRange::Split(const int &chunk_size) const
|
||||||
|
{
|
||||||
|
std::list<TimeRange> split_ranges;
|
||||||
|
|
||||||
|
int start_time =
|
||||||
|
std::floor(this->in().toDouble() / static_cast<double>(chunk_size)) *
|
||||||
|
chunk_size;
|
||||||
|
int end_time =
|
||||||
|
std::ceil(this->out().toDouble() / static_cast<double>(chunk_size)) *
|
||||||
|
chunk_size;
|
||||||
|
|
||||||
|
for (int i = start_time; i < end_time; i += chunk_size) {
|
||||||
|
split_ranges.push_back(
|
||||||
|
TimeRange(std::max(this->in(), rational(i)),
|
||||||
|
std::min(this->out(), rational(i + chunk_size))));
|
||||||
|
}
|
||||||
|
|
||||||
|
return split_ranges;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeRange::normalize()
|
||||||
|
{
|
||||||
|
// If `out` is earlier than `in`, swap them
|
||||||
|
if (out_ < in_) {
|
||||||
|
std::swap(out_, in_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate length
|
||||||
|
if (out_ == RATIONAL_MIN || out_ == RATIONAL_MAX || in_ == RATIONAL_MIN ||
|
||||||
|
in_ == RATIONAL_MAX) {
|
||||||
|
length_ = rational::NaN;
|
||||||
|
} else {
|
||||||
|
length_ = out_ - in_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeRangeList::insert(const TimeRangeList &list_to_add)
|
||||||
|
{
|
||||||
|
for (auto it = list_to_add.cbegin(); it != list_to_add.cend(); it++) {
|
||||||
|
insert(*it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeRangeList::insert(TimeRange range_to_add)
|
||||||
|
{
|
||||||
|
// See if list contains this range
|
||||||
|
if (contains(range_to_add)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Does not contain range, so we'll almost certainly be adding it in some way
|
||||||
|
for (auto it = array_.begin(); it != array_.end();) {
|
||||||
|
const TimeRange &compare = *it;
|
||||||
|
|
||||||
|
if (compare.OverlapsWith(range_to_add)) {
|
||||||
|
range_to_add = TimeRange::Combine(range_to_add, compare);
|
||||||
|
it = array_.erase(it);
|
||||||
|
} else {
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
array_.push_back(range_to_add);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeRangeList::remove(const TimeRange &remove)
|
||||||
|
{
|
||||||
|
util_remove(&array_, remove);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeRangeList::remove(const TimeRangeList &list)
|
||||||
|
{
|
||||||
|
for (const TimeRange &r : list) {
|
||||||
|
remove(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TimeRangeList::contains(const TimeRange &range, bool in_inclusive,
|
||||||
|
bool out_inclusive) const
|
||||||
|
{
|
||||||
|
for (int i = 0; i < size(); i++) {
|
||||||
|
if (array_.at(i).Contains(range, in_inclusive, out_inclusive)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeRangeList::shift(const rational &diff)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < array_.size(); i++) {
|
||||||
|
array_[i] += diff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeRangeList::trim_in(const rational &diff)
|
||||||
|
{
|
||||||
|
// Re-do list since we want to handle overlaps
|
||||||
|
TimeRangeList temp = *this;
|
||||||
|
|
||||||
|
clear();
|
||||||
|
|
||||||
|
for (auto it = temp.array_.begin(); it != temp.array_.end(); it++) {
|
||||||
|
TimeRange &r = *it;
|
||||||
|
r.set_in(r.in() + diff);
|
||||||
|
insert(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeRangeList::trim_out(const rational &diff)
|
||||||
|
{
|
||||||
|
// Re-do list since we want to handle overlaps
|
||||||
|
TimeRangeList temp = *this;
|
||||||
|
|
||||||
|
clear();
|
||||||
|
|
||||||
|
for (auto it = temp.array_.begin(); it != temp.array_.end(); it++) {
|
||||||
|
TimeRange &r = *it;
|
||||||
|
r.set_out(r.out() + diff);
|
||||||
|
insert(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeRangeList TimeRangeList::Intersects(const TimeRange &range) const
|
||||||
|
{
|
||||||
|
TimeRangeList intersect_list;
|
||||||
|
|
||||||
|
for (int i = 0; i < size(); i++) {
|
||||||
|
const TimeRange &compare = array_.at(i);
|
||||||
|
|
||||||
|
if (compare.out() <= range.in() || compare.in() >= range.out()) {
|
||||||
|
// No intersect
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
// Crop the time range to the range and add it to the list
|
||||||
|
TimeRange cropped(std::max(range.in(), compare.in()),
|
||||||
|
std::min(range.out(), compare.out()));
|
||||||
|
|
||||||
|
intersect_list.insert(cropped);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return intersect_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeRangeListFrameIterator::TimeRangeListFrameIterator()
|
||||||
|
: TimeRangeListFrameIterator(TimeRangeList(), rational::NaN)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeRangeListFrameIterator::TimeRangeListFrameIterator(
|
||||||
|
const TimeRangeList &list, const rational &timebase)
|
||||||
|
: list_(list)
|
||||||
|
, timebase_(timebase)
|
||||||
|
, range_index_(-1)
|
||||||
|
, size_(-1)
|
||||||
|
, frame_index_(0)
|
||||||
|
, custom_range_(false)
|
||||||
|
{
|
||||||
|
if (!list_.isEmpty() && timebase_.isNull()) {
|
||||||
|
std::cerr
|
||||||
|
<< "TimeRangeListFrameIterator created with null timebase but non-empty list, this will likely lead to infinite loops"
|
||||||
|
<< std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateIndexIfNecessary();
|
||||||
|
}
|
||||||
|
|
||||||
|
rational TimeRangeListFrameIterator::Snap(const rational &r) const
|
||||||
|
{
|
||||||
|
return Timecode::snap_time_to_timebase(r, timebase_, Timecode::kFloor);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TimeRangeListFrameIterator::GetNext(rational *out)
|
||||||
|
{
|
||||||
|
if (!HasNext()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output current value
|
||||||
|
*out = current_;
|
||||||
|
|
||||||
|
// Determine next value by adding timebase
|
||||||
|
current_ += timebase_;
|
||||||
|
|
||||||
|
// If this time is outside the current range, jump to the next one
|
||||||
|
UpdateIndexIfNecessary();
|
||||||
|
|
||||||
|
// Increment frame index
|
||||||
|
frame_index_++;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TimeRangeListFrameIterator::HasNext() const
|
||||||
|
{
|
||||||
|
return range_index_ < list_.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
int TimeRangeListFrameIterator::size()
|
||||||
|
{
|
||||||
|
if (size_ == -1) {
|
||||||
|
// Size isn't calculated automatically for optimization, so we'll calculate it now
|
||||||
|
size_ = 0;
|
||||||
|
|
||||||
|
for (const TimeRange &range : list_) {
|
||||||
|
rational start = Snap(range.in());
|
||||||
|
rational end = Timecode::snap_time_to_timebase(
|
||||||
|
range.out(), timebase_, Timecode::kFloor);
|
||||||
|
|
||||||
|
if (end == range.out()) {
|
||||||
|
end -= timebase_;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t start_ts = Timecode::time_to_timestamp(start, timebase_);
|
||||||
|
int64_t end_ts = Timecode::time_to_timestamp(end, timebase_);
|
||||||
|
|
||||||
|
size_ += 1 + (end_ts - start_ts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return size_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeRangeListFrameIterator::UpdateIndexIfNecessary()
|
||||||
|
{
|
||||||
|
while (range_index_ < list_.size() &&
|
||||||
|
(range_index_ == -1 || current_ >= list_.at(range_index_).out())) {
|
||||||
|
range_index_++;
|
||||||
|
|
||||||
|
if (range_index_ < list_.size()) {
|
||||||
|
current_ = Snap(list_.at(range_index_).in());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/***
|
||||||
|
|
||||||
|
Olive - Non-Linear Video Editor
|
||||||
|
Copyright (C) 2023 Olive Studios LLC
|
||||||
|
Modifications Copyright (C) 2025 mikesolar
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program 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 General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***/
|
||||||
|
|
||||||
|
#include "util/value.h"
|
||||||
|
|
||||||
|
namespace olive::core
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
/***
|
||||||
|
|
||||||
|
Olive - Non-Linear Video Editor
|
||||||
|
Copyright (C) 2023 Olive Studios LLC
|
||||||
|
Modifications Copyright (C) 2025 mikesolar
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program 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 General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***/
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "util/rational.h"
|
||||||
|
#include "util/tests.h"
|
||||||
|
|
||||||
|
using namespace olive::core;
|
||||||
|
|
||||||
|
bool rational_to_from_string_test()
|
||||||
|
{
|
||||||
|
rational r(1, 30);
|
||||||
|
|
||||||
|
std::string s = r.toString();
|
||||||
|
|
||||||
|
rational r2 = rational::fromString(s);
|
||||||
|
|
||||||
|
return r == r2;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rational_to_from_string_test2()
|
||||||
|
{
|
||||||
|
rational r(69, 420);
|
||||||
|
|
||||||
|
std::string s = r.toString();
|
||||||
|
|
||||||
|
rational r2 = rational::fromString(s);
|
||||||
|
|
||||||
|
return r == r2;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rational_defaults()
|
||||||
|
{
|
||||||
|
// By default, rationals are valid 0/1
|
||||||
|
rational basic_constructor;
|
||||||
|
|
||||||
|
if (!basic_constructor.isNull()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (basic_constructor.isNaN()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rational_nan()
|
||||||
|
{
|
||||||
|
// Create a NaN with a 0 denominator
|
||||||
|
rational nan = rational(0, 0);
|
||||||
|
if (!nan.isNaN())
|
||||||
|
return false;
|
||||||
|
if (!nan.isNull())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Create a non-NaN with a zero numerator
|
||||||
|
rational zero_nonnan(0, 999);
|
||||||
|
if (!zero_nonnan.isNull())
|
||||||
|
return false;
|
||||||
|
if (zero_nonnan.isNaN())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Create a non-NaN with a non-zero numerator
|
||||||
|
rational nonzer_nonnan(1, 30);
|
||||||
|
if (nonzer_nonnan.isNull())
|
||||||
|
return false;
|
||||||
|
if (nonzer_nonnan.isNaN())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rational_nan_constant()
|
||||||
|
{
|
||||||
|
return rational::NaN.isNaN();
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
Tester t;
|
||||||
|
|
||||||
|
t.add("rational::defaults", rational_defaults);
|
||||||
|
t.add("rational::NaN", rational_nan);
|
||||||
|
t.add("rational::NaN_constant", rational_nan_constant);
|
||||||
|
t.add("rational::toString/fromString", rational_to_from_string_test);
|
||||||
|
t.add("rational::toString/fromString2", rational_to_from_string_test2);
|
||||||
|
|
||||||
|
return t.exec();
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/***
|
||||||
|
|
||||||
|
Olive - Non-Linear Video Editor
|
||||||
|
Copyright (C) 2023 Olive Studios LLC
|
||||||
|
Modifications Copyright (C) 2025 mikesolar
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program 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 General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***/
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "util/stringutils.h"
|
||||||
|
#include "util/tests.h"
|
||||||
|
|
||||||
|
using namespace olive::core;
|
||||||
|
|
||||||
|
bool stringutils_format_test()
|
||||||
|
{
|
||||||
|
const char *expected = "Hello, world!";
|
||||||
|
std::string f = StringUtils::format("%s, %s!", "Hello", "world");
|
||||||
|
if (strcmp(f.c_str(), expected) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
Tester t;
|
||||||
|
|
||||||
|
t.add("StringUtils::format", stringutils_format_test);
|
||||||
|
|
||||||
|
return t.exec();
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/***
|
||||||
|
|
||||||
|
Olive - Non-Linear Video Editor
|
||||||
|
Copyright (C) 2023 Olive Studios LLC
|
||||||
|
Modifications Copyright (C) 2025 mikesolar
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program 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 General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***/
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "util/timecodefunctions.h"
|
||||||
|
#include "util/tests.h"
|
||||||
|
|
||||||
|
using namespace olive::core;
|
||||||
|
|
||||||
|
bool timecodefunctions_time_to_timecode_test()
|
||||||
|
{
|
||||||
|
rational drop_frame_30(1001, 30000);
|
||||||
|
|
||||||
|
std::string timecode = Timecode::time_to_timecode(
|
||||||
|
rational(1), drop_frame_30, Timecode::kTimecodeDropFrame);
|
||||||
|
if (strcmp(timecode.c_str(), "00:00:01;00") != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool timecodefunctions_time_to_timecode_test2()
|
||||||
|
{
|
||||||
|
rational bizarre_timebase(156632219);
|
||||||
|
|
||||||
|
std::string timecode = Timecode::time_to_timecode(
|
||||||
|
rational(0), bizarre_timebase, Timecode::kTimecodeDropFrame);
|
||||||
|
if (strcmp(timecode.c_str(), "INVALID TIMEBASE") != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
Tester t;
|
||||||
|
|
||||||
|
t.add("Timecode::time_to_timecode",
|
||||||
|
timecodefunctions_time_to_timecode_test);
|
||||||
|
t.add("Timecode::time_to_timecode2",
|
||||||
|
timecodefunctions_time_to_timecode_test2);
|
||||||
|
|
||||||
|
return t.exec();
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
/***
|
||||||
|
|
||||||
|
Olive - Non-Linear Video Editor
|
||||||
|
Copyright (C) 2023 Olive Studios LLC
|
||||||
|
Modifications Copyright (C) 2025 mikesolar
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program 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 General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***/
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "util/timerange.h"
|
||||||
|
#include "util/tests.h"
|
||||||
|
|
||||||
|
using namespace olive::core;
|
||||||
|
|
||||||
|
bool timerangelist_remove_test()
|
||||||
|
{
|
||||||
|
TimeRangeList t;
|
||||||
|
|
||||||
|
t.insert(TimeRange(0, 30));
|
||||||
|
t.remove(TimeRange(2, 5));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool timerangelist_mergeadjacent_test()
|
||||||
|
{
|
||||||
|
TimeRangeList t;
|
||||||
|
|
||||||
|
// TimeRangeList should merge 1 and 3 together since they're adjacent
|
||||||
|
t.insert(TimeRange(0, 6));
|
||||||
|
t.insert(TimeRange(20, 30));
|
||||||
|
t.insert(TimeRange(6, 10));
|
||||||
|
|
||||||
|
if (!(t.size() == 2)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!(t.first() == TimeRange(20, 30))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!(t.at(1) == TimeRange(0, 10))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimeRangeList should ignore these because it's already contained
|
||||||
|
TimeRangeList noop_test = t;
|
||||||
|
|
||||||
|
noop_test.insert(TimeRange(4, 7));
|
||||||
|
if (!(noop_test == t)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
noop_test.insert(TimeRange(0, 3));
|
||||||
|
if (!(noop_test == t)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
noop_test.insert(TimeRange(25, 30));
|
||||||
|
if (!(noop_test == t)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimeRangeList should combine all these together
|
||||||
|
TimeRangeList combine_test_no_overlap = t;
|
||||||
|
combine_test_no_overlap.insert(TimeRange(10, 20));
|
||||||
|
if (!(combine_test_no_overlap.size() == 1)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!(combine_test_no_overlap.first() == TimeRange(0, 30))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeRangeList combine_test_in_overlap = t;
|
||||||
|
combine_test_in_overlap.insert(TimeRange(9, 20));
|
||||||
|
if (!(combine_test_in_overlap.size() == 1)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!(combine_test_in_overlap.first() == TimeRange(0, 30))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeRangeList combine_test_out_overlap = t;
|
||||||
|
combine_test_out_overlap.insert(TimeRange(10, 21));
|
||||||
|
if (!(combine_test_out_overlap.size() == 1)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!(combine_test_out_overlap.first() == TimeRange(0, 30))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeRangeList combine_test_both_overlap = t;
|
||||||
|
combine_test_both_overlap.insert(TimeRange(9, 21));
|
||||||
|
if (!(combine_test_both_overlap.size() == 1)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!(combine_test_both_overlap.first() == TimeRange(0, 30))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
Tester t;
|
||||||
|
|
||||||
|
t.add("TimeRangeList::remove", timerangelist_remove_test);
|
||||||
|
t.add("TimeRangeList::merge_adjacent", timerangelist_mergeadjacent_test);
|
||||||
|
|
||||||
|
return t.exec();
|
||||||
|
}
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# Olive - Non-Linear Video Editor
|
|
||||||
# Copyright (C) 2023 Olive Studios LLC
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program 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 General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
add_subdirectory(core EXCLUDE_FROM_ALL)
|
|
||||||
|
|
||||||
set(KDDockWidgets_STATIC ON CACHE INTERNAL "Force KDDockWidgets to build statically")
|
|
||||||
set(KDDockWidgets_QT6 ${BUILD_QT6} CACHE INTERNAL "Conform KDDockWidgets' Qt 6 setting to ours")
|
|
||||||
# Oak only uses the QtWidgets frontend; building the QtQuick frontend causes
|
|
||||||
# duplicate QML module registration on macOS and pulls in unused dependencies.
|
|
||||||
set(KDDockWidgets_FRONTENDS "qtwidgets" CACHE INTERNAL "Only build the QtWidgets frontend for Oak")
|
|
||||||
add_subdirectory(KDDockWidgets EXCLUDE_FROM_ALL)
|
|
||||||
-1
Submodule ext/core deleted from c4f8f7bde1
Reference in New Issue
Block a user