Compare commits
188
Commits
bb09872a2f
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4edea377bf | ||
|
|
0e68806351 | ||
|
|
a8836275dc | ||
|
|
a5208b6cae | ||
|
|
c4705cbb2c | ||
|
|
9b35a9c6d4 | ||
|
|
aee368cb78 | ||
|
|
6b497ccb97 | ||
|
|
b8f9e755c4 | ||
|
|
244d5e860f | ||
|
|
7e2b3cb4b8 | ||
|
|
e7483ba735 | ||
|
|
e8d08c77f0 | ||
|
|
2721225a3f | ||
|
|
2289bc70d8 | ||
|
|
b4c8a00ce7 | ||
|
|
ca33f310bb | ||
|
|
69ab45a103 | ||
|
|
cea28f8bbc | ||
|
|
4637a84c85 | ||
|
|
1564844bab | ||
|
|
08269a680d | ||
|
|
24a2ce80e1 | ||
|
|
6946226a0a | ||
|
|
40c8043a76 | ||
|
|
d48b04da5a | ||
|
|
9aab5623bf | ||
|
|
7323a6d2be | ||
|
|
cada2f4c3a | ||
|
|
4622a1af1e | ||
|
|
3efdee5a10 | ||
|
|
d9dc16ec26 | ||
|
|
498669509a | ||
|
|
eacc47930a | ||
|
|
e163702a6b | ||
|
|
8660cbcf97 | ||
|
|
bea50f4d80 | ||
|
|
e837277557 | ||
|
|
b5a2ea7697 | ||
|
|
7fb9c92931 | ||
|
|
032a3a559b | ||
|
|
cd95514cf8 | ||
|
|
2794f8bb80 | ||
|
|
74b18ea71a | ||
|
|
ed7389ea95 | ||
|
|
f6e7bf20a4 | ||
|
|
32cd8f46f0 | ||
|
|
551909df62 | ||
|
|
738120bffc | ||
|
|
f6a7dfd0b3 | ||
|
|
13b1799c71 | ||
|
|
a43302d9b7 | ||
|
|
980c41acec | ||
|
|
4f3c140b28 | ||
|
|
e8aac4a30d | ||
|
|
c9d557e127 | ||
|
|
09cfd9f09e | ||
|
|
2d4342ff48 | ||
|
|
d5b8cd99d6 | ||
|
|
eff845a5cd | ||
|
|
fa7d8153bd | ||
|
|
e1871494ae | ||
|
|
6b91748a5a | ||
|
|
05fda5bcfc | ||
|
|
a1e4f47300 | ||
|
|
3c31c67f99 | ||
|
|
9c77d27b7f | ||
|
|
9003b78176 | ||
|
|
0a7f3766e2 | ||
|
|
12ffce77db | ||
|
|
c5455c7521 | ||
|
|
5498504398 | ||
|
|
bf0416c50b | ||
|
|
73bb686940 | ||
|
|
cc0a15919f | ||
|
|
02cea7e3ee | ||
|
|
d7cbeba850 | ||
|
|
877f577564 | ||
|
|
1f6ed30423 | ||
|
|
844c747681 | ||
|
|
55bd1132cd | ||
|
|
b4ceaa9cab | ||
|
|
29696479b5 | ||
|
|
dffa94127a | ||
|
|
35b9ad9541 | ||
|
|
4dd4ceb08a | ||
|
|
46e43b51d9 | ||
|
|
9e9c6d9863 | ||
|
|
a17d5be56a | ||
|
|
345c464e55 | ||
|
|
6dc8285f2a | ||
|
|
adf2cef32c | ||
|
|
194d761ade | ||
|
|
e0aa597f1e | ||
|
|
ec7b7e6d13 | ||
|
|
d61acb9e0a | ||
|
|
f12cf3ffef | ||
|
|
cf459d7e4c | ||
|
|
cad1d93544 | ||
|
|
74b080f88a | ||
|
|
431b9ed2b1 | ||
|
|
f2af92958a | ||
|
|
2db1615453 | ||
|
|
9daa266189 | ||
|
|
6576113a69 | ||
|
|
33db5657e0 | ||
|
|
b8a3beaee4 | ||
|
|
61da70ecf8 | ||
|
|
b36cbd6b6f | ||
|
|
ede03d0bfe | ||
|
|
21cc2fba24 | ||
|
|
c5f1d0d76c | ||
|
|
022c0a7a5a | ||
|
|
a45a7af2ac | ||
|
|
8a2e45225f | ||
|
|
f59c7c8476 | ||
|
|
4f6c903edb | ||
|
|
b2d353fe99 | ||
|
|
2958464bd7 | ||
|
|
061aab9180 | ||
|
|
36ca413a84 | ||
|
|
f3f3748173 | ||
|
|
8334894984 | ||
|
|
4e5d8747b5 | ||
|
|
e346ea5338 | ||
|
|
466cdcb7d7 | ||
|
|
92daff1a83 | ||
|
|
23e6fa7a5b | ||
|
|
693e2df5eb | ||
|
|
0f82092e95 | ||
|
|
025dc88c25 | ||
|
|
5fabad8efd | ||
|
|
b35f3b49bc | ||
|
|
3dfeed67f5 | ||
|
|
530bc762dc | ||
|
|
78d8073c89 | ||
|
|
ab1a2e9c7b | ||
|
|
2248be8567 | ||
|
|
48d3027d50 | ||
|
|
4240df1d71 | ||
|
|
d460d57805 | ||
|
|
c8c282f06c | ||
|
|
f0517fe6af | ||
|
|
c5287ff234 | ||
|
|
5afa95c80e | ||
|
|
3625e37081 | ||
|
|
06f47235b2 | ||
|
|
f1f4124b18 | ||
|
|
18ff60f147 | ||
|
|
a209f63d52 | ||
|
|
05e42668cb | ||
|
|
cdda643d64 | ||
|
|
f86a895c8a | ||
|
|
43003d0e33 | ||
|
|
7b295b7661 | ||
|
|
f908724a7a | ||
|
|
26009a5ea5 | ||
|
|
aa5fcef66e | ||
|
|
b564e7a71f | ||
|
|
063837446d | ||
|
|
3110e5cc3a | ||
|
|
d9c477365a | ||
|
|
013a175707 | ||
|
|
f8540e3892 | ||
|
|
96e57b3705 | ||
|
|
e1423553c7 | ||
|
|
f0d551d4a8 | ||
|
|
0ca9cad448 | ||
|
|
e563b340ac | ||
|
|
d11d80ea53 | ||
|
|
cac41d92c1 | ||
|
|
4b24aa9d67 | ||
|
|
5a564f30ca | ||
|
|
81431d180d | ||
|
|
fd2111d560 | ||
|
|
0462842f8c | ||
|
|
67176281d9 | ||
|
|
295ea1bfe5 | ||
|
|
da2ab512c6 | ||
|
|
40a336276f | ||
|
|
6ded1d2d63 | ||
|
|
66831a870e | ||
|
|
354df2e194 | ||
|
|
3d004c081b | ||
|
|
edbd3913af | ||
|
|
d77348ad9f | ||
|
|
c50017127b | ||
|
|
bad52feda3 |
@@ -0,0 +1,24 @@
|
||||
# Oak Video Editor - Non-Linear Video Editor
|
||||
# Copyright (C) 2026 Oak 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/>.
|
||||
|
||||
# The project FFmpeg (built by tooling/ffmpeg/build-ffmpeg.sh into
|
||||
# .cache/ffmpeg) is the only supported FFmpeg: ffmpeg-sys-next reads
|
||||
# FFMPEG_DIR at build-script time, which cannot come from a .env file —
|
||||
# a relative [env] entry here is the only machine-agnostic way to set
|
||||
# it. Run tooling/ffmpeg/build-ffmpeg.sh once before the first build.
|
||||
|
||||
[env]
|
||||
FFMPEG_DIR = { value = ".cache/ffmpeg", relative = true }
|
||||
-116
@@ -1,116 +0,0 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# clang-format configuration file. Intended for clang-format >= 11.
|
||||
#
|
||||
# For more information, see:
|
||||
#
|
||||
# Documentation/dev-tools/clang-format.rst
|
||||
# https://clang.llvm.org/docs/ClangFormat.html
|
||||
# https://clang.llvm.org/docs/ClangFormatStyleOptions.html
|
||||
#
|
||||
---
|
||||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
AlignEscapedNewlines: Left
|
||||
AlignOperands: true
|
||||
AlignTrailingComments: false
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowShortBlocksOnASingleLine: false
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: None
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AlwaysBreakTemplateDeclarations: false
|
||||
BinPackArguments: true
|
||||
BinPackParameters: true
|
||||
BraceWrapping:
|
||||
AfterClass: false
|
||||
AfterControlStatement: false
|
||||
AfterEnum: false
|
||||
AfterFunction: true
|
||||
AfterNamespace: true
|
||||
AfterObjCDeclaration: false
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
AfterExternBlock: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: true
|
||||
SplitEmptyRecord: true
|
||||
SplitEmptyNamespace: true
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeBraces: Custom
|
||||
BreakBeforeInheritanceComma: false
|
||||
BreakBeforeTernaryOperators: false
|
||||
BreakConstructorInitializersBeforeComma: false
|
||||
BreakConstructorInitializers: BeforeComma
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakStringLiterals: false
|
||||
ColumnLimit: 80
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: false
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
FixNamespaceComments: false
|
||||
IncludeBlocks: Preserve
|
||||
IncludeCategories:
|
||||
- Regex: '.*'
|
||||
Priority: 1
|
||||
IncludeIsMainRegex: '(Test)?$'
|
||||
IndentCaseLabels: false
|
||||
IndentGotoLabels: false
|
||||
IndentPPDirectives: None
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: false
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
ObjCBinPackProtocolList: Auto
|
||||
ObjCBlockIndentWidth: 4
|
||||
ObjCSpaceAfterProperty: true
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
|
||||
# Taken from git's rules
|
||||
PenaltyBreakAssignment: 10
|
||||
PenaltyBreakBeforeFirstCallParameter: 30
|
||||
PenaltyBreakComment: 10
|
||||
PenaltyBreakFirstLessLess: 0
|
||||
PenaltyBreakString: 10
|
||||
PenaltyExcessCharacter: 100
|
||||
PenaltyReturnTypeOnItsOwnLine: 60
|
||||
|
||||
PointerAlignment: Right
|
||||
ReflowComments: false
|
||||
SortIncludes: false
|
||||
SortUsingDeclarations: false
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterTemplateKeyword: true
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: false
|
||||
SpacesInContainerLiterals: false
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
TabWidth: 4
|
||||
UseTab: Always
|
||||
...
|
||||
-182
@@ -1,182 +0,0 @@
|
||||
# Generated from CLion Inspection settings
|
||||
---
|
||||
Checks: '-*,
|
||||
bugprone-argument-comment,
|
||||
bugprone-assert-side-effect,
|
||||
bugprone-bad-signal-to-kill-thread,
|
||||
bugprone-branch-clone,
|
||||
bugprone-copy-constructor-init,
|
||||
bugprone-dangling-handle,
|
||||
bugprone-dynamic-static-initializers,
|
||||
bugprone-fold-init-type,
|
||||
bugprone-forward-declaration-namespace,
|
||||
bugprone-forwarding-reference-overload,
|
||||
bugprone-inaccurate-erase,
|
||||
bugprone-incorrect-roundings,
|
||||
bugprone-integer-division,
|
||||
bugprone-lambda-function-name,
|
||||
bugprone-macro-parentheses,
|
||||
bugprone-macro-repeated-side-effects,
|
||||
bugprone-misplaced-operator-in-strlen-in-alloc,
|
||||
bugprone-misplaced-pointer-arithmetic-in-alloc,
|
||||
bugprone-misplaced-widening-cast,
|
||||
bugprone-move-forwarding-reference,
|
||||
bugprone-multiple-statement-macro,
|
||||
bugprone-no-escape,
|
||||
bugprone-parent-virtual-call,
|
||||
bugprone-posix-return,
|
||||
bugprone-reserved-identifier,
|
||||
bugprone-sizeof-container,
|
||||
bugprone-sizeof-expression,
|
||||
bugprone-spuriously-wake-up-functions,
|
||||
bugprone-string-constructor,
|
||||
bugprone-string-integer-assignment,
|
||||
bugprone-string-literal-with-embedded-nul,
|
||||
bugprone-suspicious-enum-usage,
|
||||
bugprone-suspicious-include,
|
||||
bugprone-suspicious-memset-usage,
|
||||
bugprone-suspicious-missing-comma,
|
||||
bugprone-suspicious-semicolon,
|
||||
bugprone-suspicious-string-compare,
|
||||
bugprone-suspicious-memory-comparison,
|
||||
bugprone-suspicious-realloc-usage,
|
||||
bugprone-swapped-arguments,
|
||||
bugprone-terminating-continue,
|
||||
bugprone-throw-keyword-missing,
|
||||
bugprone-too-small-loop-variable,
|
||||
bugprone-undefined-memory-manipulation,
|
||||
bugprone-undelegated-constructor,
|
||||
bugprone-unhandled-self-assignment,
|
||||
bugprone-unused-raii,
|
||||
bugprone-unused-return-value,
|
||||
bugprone-use-after-move,
|
||||
bugprone-virtual-near-miss,
|
||||
cert-dcl21-cpp,
|
||||
cert-dcl58-cpp,
|
||||
cert-err34-c,
|
||||
cert-err52-cpp,
|
||||
cert-err60-cpp,
|
||||
cert-flp30-c,
|
||||
cert-msc50-cpp,
|
||||
cert-msc51-cpp,
|
||||
cert-str34-c,
|
||||
cppcoreguidelines-interfaces-global-init,
|
||||
cppcoreguidelines-narrowing-conversions,
|
||||
cppcoreguidelines-pro-type-member-init,
|
||||
cppcoreguidelines-pro-type-static-cast-downcast,
|
||||
cppcoreguidelines-slicing,
|
||||
google-default-arguments,
|
||||
google-explicit-constructor,
|
||||
google-runtime-operator,
|
||||
hicpp-exception-baseclass,
|
||||
hicpp-multiway-paths-covered,
|
||||
misc-misplaced-const,
|
||||
misc-new-delete-overloads,
|
||||
misc-no-recursion,
|
||||
misc-non-copyable-objects,
|
||||
misc-throw-by-value-catch-by-reference,
|
||||
misc-unconventional-assign-operator,
|
||||
misc-uniqueptr-reset-release,
|
||||
modernize-avoid-bind,
|
||||
modernize-concat-nested-namespaces,
|
||||
modernize-deprecated-headers,
|
||||
modernize-deprecated-ios-base-aliases,
|
||||
modernize-loop-convert,
|
||||
modernize-make-shared,
|
||||
modernize-make-unique,
|
||||
modernize-pass-by-value,
|
||||
modernize-raw-string-literal,
|
||||
modernize-redundant-void-arg,
|
||||
modernize-replace-auto-ptr,
|
||||
modernize-replace-disallow-copy-and-assign-macro,
|
||||
modernize-replace-random-shuffle,
|
||||
modernize-return-braced-init-list,
|
||||
modernize-shrink-to-fit,
|
||||
modernize-unary-static-assert,
|
||||
modernize-use-auto,
|
||||
modernize-use-bool-literals,
|
||||
modernize-use-emplace,
|
||||
modernize-use-equals-default,
|
||||
modernize-use-equals-delete,
|
||||
modernize-use-nodiscard,
|
||||
modernize-use-noexcept,
|
||||
modernize-use-nullptr,
|
||||
modernize-use-override,
|
||||
modernize-use-transparent-functors,
|
||||
modernize-use-uncaught-exceptions,
|
||||
mpi-buffer-deref,
|
||||
mpi-type-mismatch,
|
||||
openmp-use-default-none,
|
||||
performance-faster-string-find,
|
||||
performance-for-range-copy,
|
||||
performance-implicit-conversion-in-loop,
|
||||
performance-inefficient-algorithm,
|
||||
performance-inefficient-string-concatenation,
|
||||
performance-inefficient-vector-operation,
|
||||
performance-move-const-arg,
|
||||
performance-move-constructor-init,
|
||||
performance-no-automatic-move,
|
||||
performance-noexcept-move-constructor,
|
||||
performance-trivially-destructible,
|
||||
performance-type-promotion-in-math-fn,
|
||||
performance-unnecessary-copy-initialization,
|
||||
performance-unnecessary-value-param,
|
||||
portability-simd-intrinsics,
|
||||
readability-avoid-const-params-in-decls,
|
||||
readability-const-return-type,
|
||||
readability-container-size-empty,
|
||||
readability-convert-member-functions-to-static,
|
||||
readability-delete-null-pointer,
|
||||
readability-deleted-default,
|
||||
readability-inconsistent-declaration-parameter-name,
|
||||
readability-make-member-function-const,
|
||||
readability-misleading-indentation,
|
||||
readability-misplaced-array-index,
|
||||
readability-non-const-parameter,
|
||||
readability-redundant-control-flow,
|
||||
readability-redundant-declaration,
|
||||
readability-redundant-function-ptr-dereference,
|
||||
readability-redundant-smartptr-get,
|
||||
readability-redundant-string-cstr,
|
||||
readability-redundant-string-init,
|
||||
readability-simplify-subscript-expr,
|
||||
readability-static-accessed-through-instance,
|
||||
readability-static-definition-in-anonymous-namespace,
|
||||
readability-string-compare,
|
||||
readability-uniqueptr-delete-release,
|
||||
readability-use-anyofallof,
|
||||
readability-identifier-naming'
|
||||
CheckOptions:
|
||||
readability-identifier-naming.ClassCase: CamelCase
|
||||
readability-identifier-naming.StructCase: CamelCase
|
||||
readability-identifier-naming.EnumCase: CamelCase
|
||||
readability-identifier-naming.UnionCase: CamelCase
|
||||
readability-identifier-naming.TypeAliasCase: CamelCase
|
||||
readability-identifier-naming.TypeAliasIgnoredRegexp: '^(const_)?(reverse_)?iterator$|^const_(reference|pointer)$|^(value|size|difference|reference|pointer)_type$'
|
||||
readability-identifier-naming.UsingCase: CamelCase
|
||||
readability-identifier-naming.UsingIgnoredRegexp: '^(const_)?(reverse_)?iterator$|^const_(reference|pointer)$|^(value|size|difference|reference|pointer)_type$'
|
||||
readability-identifier-naming.StaticVariableCase: lower_case
|
||||
readability-identifier-naming.StaticVariableSuffix: _
|
||||
readability-identifier-naming.TemplateParameterCase: CamelCase
|
||||
readability-identifier-naming.EnumConstantCase: lower_case
|
||||
readability-identifier-naming.ConstantCase: lower_case
|
||||
readability-identifier-naming.ConstexprVariableCase: lower_case
|
||||
readability-identifier-naming.ClassConstantCase: lower_case
|
||||
readability-identifier-naming.StaticConstantCase: lower_case
|
||||
readability-identifier-naming.GlobalConstantCase: lower_case
|
||||
readability-identifier-naming.LocalConstantCase: lower_case
|
||||
readability-identifier-naming.VariableCase: lower_case
|
||||
readability-identifier-naming.ParameterCase: lower_case
|
||||
readability-identifier-naming.LocalVariableCase: lower_case
|
||||
readability-identifier-naming.MemberCase: lower_case
|
||||
readability-identifier-naming.PrivateMemberSuffix: _
|
||||
readability-identifier-naming.ProtectedMemberSuffix: _
|
||||
readability-identifier-naming.FunctionCase: lower_case
|
||||
readability-identifier-naming.ClassMethodCase: lower_case
|
||||
readability-identifier-naming.GlobalFunctionCase: lower_case
|
||||
readability-identifier-naming.NamespaceCase: lower_case
|
||||
readability-identifier-naming.MacroDefinitionIgnoredRegexp: '.*'
|
||||
# Qt and third-party (OFX) virtual overrides / framework callbacks keep
|
||||
# their original names — renaming them would break the override.
|
||||
readability-identifier-naming.FunctionIgnoredRegexp: '^(.*Event|eventFilter|sizeHint|minimumSizeHint|heightForWidth|hasHeightForWidth|initializeGL|resizeGL|paintGL|readData|writeData|readLineData|itemChange|boundingRect|sceneEvent|drawForeground|drawBackground|createEditor|setEditorData|setModelData|updateEditorGeometry|editorEvent|canFetchMore|fetchMore|mimeData|mimeTypes|dropMimeData|canDropMimeData|supportedDropActions|supportedDragActions|roleNames|connectNotify|disconnectNotify|initStyleOption|isSequential|showPopup|hidePopup|inputMethodQuery|viewportEvent|scrollContentsBy|updateGeometries|keyboardSearch|startDrag|viewOptions|setSelection|currentChanged|selectionChanged|createWidget|deleteWidget|createMimeDataFromSelection|canInsertFromMimeData|insertFromMimeData|dropIndicatorPosition|qHash|get[A-Z].*|set[A-Z].*|can[A-Z].*|calc[A-Z].*|is[A-Z].*|.*Action|new.*|addParam|multiThread.*|mutex.*|timeLine.*|editBegin|editEnd|freeMem|copyFrom|deleteKey|deleteAllKeys|makeDescriptor|initDescriptor|initParamDescriptor|loadFromPlugin|examineOutArgs|paramChangedByPlugin|saveXML|verifyMagic|pluginSupported|loadingStatus|confirmPlugin|callEntry|mainEntry|deriveV|integrateV|getV|setV|swapBuffers|loadTexture|flushOpenGLResources|clearPersistentMessage|progressStart|progressEnd|progressUpdate|beginXmlParsing|endXmlParsing|xmlCharacterHandler|xmlElementBegin|xmlElementEnd)$'
|
||||
readability-identifier-naming.ClassMethodIgnoredRegexp: '^(.*Event|eventFilter|sizeHint|minimumSizeHint|heightForWidth|hasHeightForWidth|initializeGL|resizeGL|paintGL|readData|writeData|readLineData|itemChange|boundingRect|sceneEvent|drawForeground|drawBackground|createEditor|setEditorData|setModelData|updateEditorGeometry|editorEvent|canFetchMore|fetchMore|mimeData|mimeTypes|dropMimeData|canDropMimeData|supportedDropActions|supportedDragActions|roleNames|connectNotify|disconnectNotify|initStyleOption|isSequential|showPopup|hidePopup|inputMethodQuery|viewportEvent|scrollContentsBy|updateGeometries|keyboardSearch|startDrag|viewOptions|setSelection|currentChanged|selectionChanged|createWidget|deleteWidget|createMimeDataFromSelection|canInsertFromMimeData|insertFromMimeData|dropIndicatorPosition|get[A-Z].*|set[A-Z].*|can[A-Z].*|calc[A-Z].*|is[A-Z].*|.*Action|new.*|addParam|multiThread.*|mutex.*|timeLine.*|editBegin|editEnd|freeMem|copyFrom|deleteKey|deleteAllKeys|makeDescriptor|initDescriptor|initParamDescriptor|loadFromPlugin|examineOutArgs|paramChangedByPlugin|saveXML|verifyMagic|pluginSupported|loadingStatus|confirmPlugin|callEntry|mainEntry|deriveV|integrateV|getV|setV|swapBuffers|loadTexture|flushOpenGLResources|clearPersistentMessage|progressStart|progressEnd|progressUpdate|beginXmlParsing|endXmlParsing|xmlCharacterHandler|xmlElementBegin|xmlElementEnd)$'
|
||||
@@ -6,3 +6,14 @@
|
||||
*.cpp text eol=lf
|
||||
*.sh text eol=lf
|
||||
*.desktop text eol=lf
|
||||
|
||||
# Golden/project data files are byte-compared by tests — keep LF on
|
||||
# Windows checkouts too (text=auto would hand out CRLF there).
|
||||
*.json text eol=lf
|
||||
*.ove text eol=lf
|
||||
|
||||
# Workflow manifests: a CRLF ci.yml turns every step command into
|
||||
# "cmd\r", and bash then cannot find the referenced scripts on the
|
||||
# Windows runner ("No such file or directory").
|
||||
*.yml text eol=lf
|
||||
*.yaml text eol=lf
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# actionlint configuration: whitelist the custom self-hosted runner labels
|
||||
# used by the CI/CD workflows (Warp.dev runners on GitHub; the oak Gitea
|
||||
# instance's runners) so `actionlint ci.yml` / `actionlint cd.yml` passes.
|
||||
# See https://github.com/rhysd/actionlint/blob/main/docs/config.md
|
||||
self-hosted-runner:
|
||||
labels:
|
||||
- ubuntu-latest
|
||||
- windows-latest
|
||||
- oak-ubuntu-2404
|
||||
- oak-windows-2025
|
||||
@@ -0,0 +1,446 @@
|
||||
name: CD
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
# ------------------------------------------------------------------
|
||||
# Linux: deb + AppImage + pacman in one job. cargo-packager does not
|
||||
# support rpm (its format list is deb/appimage/pacman/nsis/dmg/app/wix),
|
||||
# and its "pacman" format emits a PKGBUILD + source tarball rather than a
|
||||
# compiled pkg.tar.zst — both are upstream limitations.
|
||||
# ------------------------------------------------------------------
|
||||
# ------------------------------------------------------------------
|
||||
# Linux: one native package per distro, each built INSIDE that
|
||||
# distro's container so the declared dependencies always resolve to
|
||||
# the distro's own package names (dpkg-shlibdeps / rpmbuild
|
||||
# auto-requires / Arch static base list). deb: hand-rolled dpkg-deb;
|
||||
# rpm: rpmbuild; arch: makepkg. AppImage stays on the Ubuntu runner
|
||||
# (self-contained by design).
|
||||
# ------------------------------------------------------------------
|
||||
linux:
|
||||
name: Linux packages (${{ matrix.distro }})
|
||||
runs-on: warp-ubuntu-latest-x64-8x
|
||||
container: ${{ matrix.image }}
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- distro: debian
|
||||
image: debian:12
|
||||
- distro: fedora
|
||||
image: fedora:41
|
||||
- distro: arch
|
||||
image: archlinux:latest
|
||||
|
||||
steps:
|
||||
# git/curl must land BEFORE actions/checkout runs inside the
|
||||
# container.
|
||||
- name: Install git and fetch tools
|
||||
run: |
|
||||
case "${{ matrix.distro }}" in
|
||||
debian) apt-get update && apt-get install -y git curl ;;
|
||||
fedora) dnf install -y git curl ;;
|
||||
arch) pacman -Sy --noconfirm git curl ;;
|
||||
esac
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Install Rust (stable)
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
case "${{ matrix.distro }}" in
|
||||
debian)
|
||||
apt-get install -y \
|
||||
build-essential cmake pkg-config nasm dpkg-dev \
|
||||
libpipewire-0.3-dev libspa-0.2-dev libjack-jackd2-dev \
|
||||
libasound2-dev libpulse-dev libsndfile1-dev \
|
||||
libgl1-mesa-dev libgl1-mesa-dri mesa-vulkan-drivers \
|
||||
libvulkan-dev libxkbcommon-dev libxkbcommon-x11-dev \
|
||||
librsvg2-bin
|
||||
;;
|
||||
fedora)
|
||||
dnf install -y \
|
||||
gcc gcc-c++ cmake pkgconf-pkg-config nasm \
|
||||
pipewire-devel jack-audio-connection-kit-devel \
|
||||
alsa-lib-devel pulseaudio-libs-devel libsndfile-devel \
|
||||
mesa-libGL-devel mesa-vulkan-drivers \
|
||||
vulkan-headers vulkan-loader-devel \
|
||||
libxkbcommon-devel libxkbcommon-x11-devel \
|
||||
rpm-build librsvg2-tools
|
||||
;;
|
||||
arch)
|
||||
pacman -S --needed --noconfirm \
|
||||
base-devel cmake pkgconf nasm \
|
||||
pipewire jack2 alsa-lib libpulse libsndfile \
|
||||
mesa vulkan-headers vulkan-icd-loader \
|
||||
libxkbcommon libxkbcommon-x11 librsvg
|
||||
;;
|
||||
esac
|
||||
|
||||
- name: Configure build environment
|
||||
run: |
|
||||
# OCIO builds from the ocio-sys vendored source (static) on
|
||||
# Linux; the distro packages are too old for the bridge.
|
||||
echo "OCIO_RS_ENABLE_REAL=1" >> "$GITHUB_ENV"
|
||||
echo "OCIO_RS_LINK=static" >> "$GITHUB_ENV"
|
||||
|
||||
# TEMP: cache disabled until the Gitea instance cache is provisioned (Cache cargo artifacts)
|
||||
# uses: Swatinem/rust-cache@v2
|
||||
# with:
|
||||
# shared-key: oak-${{ matrix.distro }}
|
||||
# cache-on-failure: true
|
||||
|
||||
# TEMP: cache disabled until the Gitea instance cache is provisioned (Cache project FFmpeg)
|
||||
# uses: actions/cache@v4
|
||||
# with:
|
||||
# path: .cache/ffmpeg
|
||||
# key: ffmpeg-${{ matrix.distro }}-${{ hashFiles('tooling/ffmpeg/build-ffmpeg.sh') }}
|
||||
|
||||
- name: Build project FFmpeg (static, GPL + free codecs + hwaccel)
|
||||
run: tooling/ffmpeg/build-ffmpeg.sh
|
||||
|
||||
- name: Build (release)
|
||||
run: cargo build --release --locked
|
||||
|
||||
- name: Generate app icon (PNG from Oak_Icon.svg)
|
||||
run: rsvg-convert -w 512 -h 512 Oak_Icon.svg -o icons/icon.png
|
||||
|
||||
- name: Package
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# The release version lives in [workspace.package] of the root
|
||||
# Cargo.toml (single source of truth; tags do not carry it).
|
||||
VERSION=$(sed -n '/^\[workspace\.package\]/,/^\[/s/^version = "\(.*\)"/\1/p' Cargo.toml | head -1)
|
||||
case "${{ matrix.distro }}" in
|
||||
debian) tooling/package/build-deb.sh "$VERSION" ;;
|
||||
fedora) tooling/package/build-rpm.sh "$VERSION" ;;
|
||||
arch) tooling/package/build-pkg.sh "$VERSION" ;;
|
||||
esac
|
||||
shell: bash
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: oak-linux-${{ matrix.distro }}
|
||||
path: |
|
||||
target/release/*.deb
|
||||
target/release/*.rpm
|
||||
target/release/*.pkg.tar.zst
|
||||
if-no-files-found: error
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# AppImage (self-contained; cargo-packager on the Ubuntu runner).
|
||||
# ------------------------------------------------------------------
|
||||
appimage:
|
||||
name: Linux AppImage
|
||||
runs-on: warp-ubuntu-latest-x64-8x
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Install Rust (stable)
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
tooling/install-deps.sh
|
||||
sudo apt-get install -y \
|
||||
cmake librsvg2-bin \
|
||||
libpipewire-0.3-dev libspa-0.2-dev libjack-jackd2-dev \
|
||||
libasound2-dev libpulse-dev libsndfile1-dev \
|
||||
libgl1-mesa-dev libgl1-mesa-dri mesa-vulkan-drivers \
|
||||
libvulkan-dev libxkbcommon-dev libxkbcommon-x11-dev
|
||||
|
||||
- name: Configure build environment
|
||||
run: |
|
||||
echo "OCIO_RS_ENABLE_REAL=1" >> "$GITHUB_ENV"
|
||||
echo "OCIO_RS_LINK=static" >> "$GITHUB_ENV"
|
||||
|
||||
# TEMP: cache disabled until the Gitea instance cache is provisioned (Cache cargo artifacts)
|
||||
# uses: Swatinem/rust-cache@v2
|
||||
# with:
|
||||
# shared-key: oak-appimage
|
||||
# cache-on-failure: true
|
||||
|
||||
# TEMP: cache disabled until the Gitea instance cache is provisioned (Cache project FFmpeg)
|
||||
# uses: actions/cache@v4
|
||||
# with:
|
||||
# path: .cache/ffmpeg
|
||||
# key: ffmpeg-appimage-${{ hashFiles('tooling/ffmpeg/build-ffmpeg.sh') }}
|
||||
|
||||
- name: Build project FFmpeg (static, GPL + free codecs + hwaccel)
|
||||
run: tooling/ffmpeg/build-ffmpeg.sh
|
||||
|
||||
- name: Install cargo-packager
|
||||
run: cargo install cargo-packager --locked
|
||||
|
||||
- name: Generate app icon (PNG from Oak_Icon.svg)
|
||||
run: |
|
||||
mkdir -p icons
|
||||
rsvg-convert -w 512 -h 512 Oak_Icon.svg -o icons/icon.png
|
||||
file icons/icon.png
|
||||
|
||||
- name: Build (release)
|
||||
run: cargo build --release --locked
|
||||
|
||||
- name: Package (AppImage)
|
||||
run: cargo packager --release --formats appimage
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: oak-linux-appimage
|
||||
path: target/release/*.AppImage
|
||||
if-no-files-found: error
|
||||
|
||||
macos:
|
||||
name: macOS DMG (Apple Silicon)
|
||||
runs-on: warp-macos-15-arm64-6x
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Install Rust (stable)
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
tooling/install-deps.sh
|
||||
brew install cmake librsvg
|
||||
|
||||
- name: Configure build environment
|
||||
run: |
|
||||
{
|
||||
# OCIO comes from the ocio-sys vendored source build (same on
|
||||
# every platform); no OCIO_INSTALL_DIR override.
|
||||
echo "OCIO_RS_ENABLE_REAL=1"
|
||||
echo "OCIO_RS_LINK=static"
|
||||
echo "CFLAGS=-I/opt/homebrew/include"
|
||||
echo "LDFLAGS=-L/opt/homebrew/lib"
|
||||
echo "PKG_CONFIG_PATH=/opt/homebrew/lib/pkgconfig/openjpeg"
|
||||
} >> "$GITHUB_ENV"
|
||||
|
||||
# TEMP: cache disabled until the Gitea instance cache is provisioned (Cache cargo artifacts)
|
||||
# uses: Swatinem/rust-cache@v2
|
||||
# with:
|
||||
# shared-key: oak-workspace
|
||||
# cache-on-failure: true
|
||||
|
||||
# TEMP: cache disabled until the Gitea instance cache is provisioned (Cache project FFmpeg)
|
||||
# uses: actions/cache@v4
|
||||
# with:
|
||||
# path: .cache/ffmpeg
|
||||
# key: ffmpeg-${{ runner.os }}-${{ hashFiles('tooling/ffmpeg/build-ffmpeg.sh') }}
|
||||
|
||||
- name: Build project FFmpeg (static, GPL + free codecs + hwaccel)
|
||||
run: |
|
||||
tooling/ffmpeg/build-ffmpeg.sh
|
||||
echo "FFMPEG_DIR=$PWD/.cache/ffmpeg" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Install cargo-packager
|
||||
run: cargo install cargo-packager --locked
|
||||
|
||||
- name: Generate app icon (PNG from Oak_Icon.svg)
|
||||
run: |
|
||||
mkdir -p icons
|
||||
# cargo-packager's tauri-icns 0.1.0 maps only 512x512@1x (and
|
||||
# 1024x1024@2x); a plain 1024x1024 PNG aborts with "No matching
|
||||
# IconType", so render 512x512.
|
||||
rsvg-convert -w 512 -h 512 Oak_Icon.svg -o icons/icon.png
|
||||
file icons/icon.png
|
||||
|
||||
# Build the packaged binaries (default members: the app, oak-cli,
|
||||
# oak-worker).
|
||||
- name: Build (release)
|
||||
run: cargo build --release --locked
|
||||
|
||||
- name: Package .app bundle
|
||||
run: cargo packager --release --formats app
|
||||
|
||||
# Pull the Homebrew dylibs the binaries reference into
|
||||
# Contents/Frameworks and rewrite install names to
|
||||
# @executable_path-relative (the script ad-hoc re-signs the bundle).
|
||||
- name: Bundle dylibs into the .app
|
||||
run: tooling/package/bundle-dylibs-macos.sh target/release/Oak.app
|
||||
|
||||
- name: Create DMG
|
||||
run: |
|
||||
rm -rf dmg-staging
|
||||
mkdir -p dmg-staging
|
||||
cp -R target/release/Oak.app dmg-staging/
|
||||
ln -s /Applications dmg-staging/Applications
|
||||
hdiutil create -volname "Oak Video Editor" \
|
||||
-srcfolder dmg-staging -ov -format UDZO Oak-macOS-arm64.dmg
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: oak-macos
|
||||
path: Oak-macOS-arm64.dmg
|
||||
if-no-files-found: error
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Windows: NSIS installer (restored; cargo-packager downloads its own
|
||||
# makensis, SHA-1 verified). The obsolete `-p oakengine` cdylib prebuild
|
||||
# from before M14 R4 is dropped — no packaged binary links the cdylib.
|
||||
# ------------------------------------------------------------------
|
||||
windows:
|
||||
name: Windows installer (NSIS)
|
||||
runs-on: warp-windows-latest-x64-16x
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: msys2 {0}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Install Rust (stable)
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Setup MSYS2
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: UCRT64
|
||||
update: true
|
||||
# MSYS2's own Rust targets x86_64-pc-windows-gnu by default —
|
||||
# the Windows build is GNU-target (the MSVC linker rejects the
|
||||
# Unix-style link args the build scripts emit).
|
||||
install: >-
|
||||
git
|
||||
mingw-w64-ucrt-x86_64-gcc
|
||||
mingw-w64-ucrt-x86_64-rust
|
||||
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
bash tooling/install-deps.sh
|
||||
pacman -S --needed --noconfirm \
|
||||
mingw-w64-ucrt-x86_64-cmake \
|
||||
mingw-w64-ucrt-x86_64-opencolorio \
|
||||
mingw-w64-ucrt-x86_64-librsvg
|
||||
|
||||
- name: Configure build environment
|
||||
run: |
|
||||
# Windows uses the MSYS2 OpenColorIO package (the exact 2.5.2 the
|
||||
# bridge targets; the vendored source needs MSVC-only constructs).
|
||||
# Dynamic; the DLLs are packaged next to the binaries.
|
||||
echo "OCIO_RS_ENABLE_REAL=1" >> "$GITHUB_ENV"
|
||||
echo "OCIO_INSTALL_DIR=/ucrt64" >> "$GITHUB_ENV"
|
||||
echo "OCIO_RS_LINK=dynamic" >> "$GITHUB_ENV"
|
||||
# ocio-sys' build.rs force-adds the MSVC + Windows SDK include
|
||||
# dirs on Windows (meant for MSVC hosts); with the GNU toolchain
|
||||
# that drags MSVC-only headers into the g++ compile. Unpack the
|
||||
# crate and gate that block behind OCIO_RS_NO_MSVC_INCLUDES.
|
||||
echo "OCIO_RS_NO_MSVC_INCLUDES=1" >> "$GITHUB_ENV"
|
||||
CH=$(cygpath -u "${CARGO_HOME:-$HOME/.cargo}")
|
||||
cargo fetch --locked
|
||||
for cache in "$CH"/registry/cache/*/; do
|
||||
src="$CH/registry/src/$(basename "$cache")"
|
||||
mkdir -p "$src"
|
||||
[ -f "$cache/ocio-sys-0.2.1.crate" ] && tar xzf "$cache/ocio-sys-0.2.1.crate" -C "$src"
|
||||
done
|
||||
BS=$(ls "$CH"/registry/src/*/ocio-sys-0.2.1/build.rs)
|
||||
grep -q 'OCIO_RS_NO_MSVC_INCLUDES' "$BS" || sed -i \
|
||||
's|if cfg!(target_os = "windows") && has_real_ocio {|if cfg!(target_os = "windows") \&\& has_real_ocio \&\& std::env::var_os("OCIO_RS_NO_MSVC_INCLUDES").is_none() {|' \
|
||||
"$BS"
|
||||
grep -q 'OCIO_RS_NO_MSVC_INCLUDES' "$BS"
|
||||
|
||||
# TEMP: cache disabled until the Gitea instance cache is provisioned (Cache cargo artifacts)
|
||||
# uses: Swatinem/rust-cache@v2
|
||||
# with:
|
||||
# shared-key: oak-workspace
|
||||
# cache-on-failure: true
|
||||
|
||||
# TEMP: cache disabled until the Gitea instance cache is provisioned (Cache project FFmpeg)
|
||||
# uses: actions/cache@v4
|
||||
# with:
|
||||
# path: .cache/ffmpeg
|
||||
# key: ffmpeg-${{ runner.os }}-${{ hashFiles('tooling/ffmpeg/build-ffmpeg.sh') }}
|
||||
|
||||
- name: Build project FFmpeg (static, GPL + free codecs + hwaccel)
|
||||
run: |
|
||||
bash tooling/ffmpeg/build-ffmpeg.sh
|
||||
echo "FFMPEG_DIR=$(cygpath -m "$PWD/.cache/ffmpeg")" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Install cargo-packager
|
||||
run: cargo install cargo-packager --locked
|
||||
|
||||
- name: Generate app icon (PNG from Oak_Icon.svg)
|
||||
run: |
|
||||
mkdir -p icons
|
||||
rsvg-convert -w 512 -h 512 Oak_Icon.svg -o icons/icon.png
|
||||
|
||||
- name: Build (release)
|
||||
run: |
|
||||
# Clear the job-hook-injected MSVC INCLUDE/LIB before the GNU
|
||||
# build (they poison the MinGW compiles with MSVC SDK headers).
|
||||
unset INCLUDE LIB
|
||||
cargo build --release --locked
|
||||
|
||||
# Collect the MSYS2 runtime DLLs (libstdc++/libgcc/OpenColorIO/...)
|
||||
# into target/pkg/win-dlls; the packager `resources` glob then
|
||||
# installs them next to the executables.
|
||||
- name: Bundle runtime DLLs
|
||||
run: |
|
||||
unset INCLUDE LIB
|
||||
pacman -S --needed --noconfirm mingw-w64-ucrt-x86_64-ntldd
|
||||
tooling/package/bundle-dylibs-windows.sh target/pkg/win-dlls \
|
||||
target/release/oak-editor.exe target/release/oak-cli.exe \
|
||||
target/release/oak-worker.exe
|
||||
|
||||
- name: Package (NSIS)
|
||||
run: cargo packager --release --formats nsis
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: oak-windows
|
||||
path: target/release/*-setup.exe
|
||||
if-no-files-found: error
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Publish: attach every platform package to the v* tag's GitHub release
|
||||
# (skipped on workflow_dispatch, which only uploads artifacts).
|
||||
# ------------------------------------------------------------------
|
||||
release:
|
||||
name: Publish GitHub release
|
||||
needs: [linux, appimage, macos, windows]
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
runs-on: warp-ubuntu-latest-x64-8x
|
||||
steps:
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
merge-multiple: true
|
||||
|
||||
- name: Publish release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ github.ref_name }}
|
||||
name: ${{ github.ref_name }}
|
||||
draft: false
|
||||
files: artifacts/*
|
||||
|
||||
@@ -0,0 +1,299 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
name: Build & test (Linux)
|
||||
runs-on: oak-ubuntu-2404
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# gpui/ is a git submodule; its crates are workspace members of
|
||||
# their own repo and build as path dependencies of oakapp.
|
||||
submodules: true
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# System dependencies
|
||||
# ------------------------------------------------------------------
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
# Codec/filter libraries for the ffmpeg-sys-next build feature
|
||||
# (see tooling/install-deps.sh).
|
||||
tooling/install-deps.sh
|
||||
# cmake/make for the vendored OpenColorIO build (ocio-sys
|
||||
# `bundled`; Ubuntu's libopencolorio-dev is 2.1, older than the
|
||||
# bridge's API floor) plus the headless test infra gpui needs:
|
||||
# X11, software Mesa Vulkan (lavapipe) and xvfb.
|
||||
sudo apt-get install -y \
|
||||
cmake \
|
||||
libpipewire-0.3-dev libspa-0.2-dev libjack-jackd2-dev \
|
||||
libasound2-dev libpulse-dev libsndfile1-dev \
|
||||
libgl1-mesa-dev libgl1-mesa-dri mesa-vulkan-drivers \
|
||||
libvulkan-dev libxkbcommon-dev libxkbcommon-x11-dev xvfb build-essential clang libclang-dev
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Build environment
|
||||
# ------------------------------------------------------------------
|
||||
# ocio-sys builds a stub bridge unless these are set; the oak-common
|
||||
# ocioutils tests need the real library (see crates/oak-common/.cargo/
|
||||
# config.toml, which only applies to builds run from that directory).
|
||||
# ocio-sys builds its vendored OpenColorIO from source on Linux (the
|
||||
# `bundled` feature; the distro package is too old for the bridge),
|
||||
# so no OCIO_INSTALL_DIR here.
|
||||
- name: Configure build environment
|
||||
run: |
|
||||
echo "OCIO_RS_ENABLE_REAL=1" >> "$GITHUB_ENV"
|
||||
echo "OCIO_RS_LINK=static" >> "$GITHUB_ENV"
|
||||
echo "RUSTUP_HOME=/opt/rust/rustup" >> "$GITHUB_ENV"
|
||||
echo "CARGO_HOME=/opt/rust/cargo" >> "$GITHUB_ENV"
|
||||
echo "PATH=$PATH:/opt/rust/cargo/bin" >> "$GITHUB_ENV"
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Caches
|
||||
# ------------------------------------------------------------------
|
||||
# Covers the whole target/ dir plus ~/.cargo; shared across branches
|
||||
# of the same OS.
|
||||
# TEMP: cache disabled ntil the Gitea instance cache is provisioned (Cache cargo artifacts)
|
||||
# uses: Swatinem/rust-cache@v2
|
||||
# with:
|
||||
# shared-key: oak-workspace
|
||||
# cache-on-failure: true
|
||||
|
||||
# The project FFmpeg (release/8.0, static, all free codecs + hwaccel)
|
||||
# is built by tooling/ffmpeg/build-ffmpeg.sh — 10-20 min on a cold
|
||||
# cache. It does not depend on the Rust toolchain, so key it on the
|
||||
# script itself and keep it out of rust-cache.
|
||||
# TEMP: cache disabled until the Gitea instance cache is provisioned (Cache project FFmpeg)
|
||||
# uses: actions/cache@v4
|
||||
# with:
|
||||
# path: .cache/ffmpeg
|
||||
# key: ffmpeg-${{ runner.os }}-${{ hashFiles('tooling/ffmpeg/build-ffmpeg.sh') }}
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Project FFmpeg (script + FFMPEG_DIR; see docs/build.md)
|
||||
# ------------------------------------------------------------------
|
||||
- name: Build project FFmpeg
|
||||
run: |
|
||||
tooling/ffmpeg/build-ffmpeg.sh
|
||||
echo "FFMPEG_DIR=$PWD/.cache/ffmpeg" >> "$GITHUB_ENV"
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Build & test
|
||||
# ------------------------------------------------------------------
|
||||
- name: Build
|
||||
run: cargo build --workspace --locked
|
||||
|
||||
# xvfb + 24-bit screen: the gpui #[gpui::test] tests open real windows
|
||||
# and render through wgpu on Mesa's software Vulkan (lavapipe).
|
||||
# The watchdog bounds the step: a deadlocked test produces no output
|
||||
# and no failure, so after 1500 s (a green run needs ~4 min) it dumps
|
||||
# every hung process's thread stacks and kills the suite.
|
||||
- name: Test
|
||||
run: |
|
||||
sudo apt-get install -y gdb
|
||||
xvfb-run -a -s "-screen 0 1920x1080x24" cargo test --workspace --locked &
|
||||
TEST_PID=$!
|
||||
(
|
||||
sleep 1500
|
||||
echo "::warning::test suite exceeded 1500s; dumping hung-process stacks"
|
||||
for p in $(pgrep -f 'target/debug/deps/|target/debug/oak-worker'); do
|
||||
echo "===== thread stacks of pid $p ($(readlink /proc/$p/exe 2>/dev/null)) ====="
|
||||
sudo gdb -batch -ex 'thread apply all bt' -p "$p" || true
|
||||
done
|
||||
pkill -9 -f 'target/debug/deps/' || true
|
||||
pkill -9 -f 'target/debug/oak-worker' || true
|
||||
) &
|
||||
WATCHDOG_PID=$!
|
||||
wait $TEST_PID
|
||||
rc=$?
|
||||
kill $WATCHDOG_PID 2>/dev/null || true
|
||||
exit $rc
|
||||
|
||||
# A crashing (SIGSEGV) test gives no Rust backtrace; rerun the
|
||||
# crashing test binaries under gdb to capture the native stack.
|
||||
# `--args` is required — plain `--` makes gdb treat the test args as
|
||||
# a core file. The extra probes target loader-stage crashes (the
|
||||
# copier_test SIGSEGV happens inside ld.so's dl_main): si_addr/si_code
|
||||
# pin down the fault type, the dynsym dump exposes symbols the
|
||||
# executable exports for interposition, strace shows the last loader
|
||||
# syscalls, and valgrind catches a corrupting static initializer.
|
||||
- name: Backtrace on test failure
|
||||
if: failure()
|
||||
run: |
|
||||
sudo apt-get install -y gdb strace valgrind
|
||||
for name in node_e2e_test suites_test copier_test; do
|
||||
BIN=$(ls -t target/debug/deps/$name-* | grep -v '\.d$' | head -1)
|
||||
[ -n "$BIN" ] || continue
|
||||
echo "===== $BIN ====="
|
||||
file "$BIN" || true
|
||||
echo "--- exported defined dynsyms:"
|
||||
readelf --dyn-syms -W "$BIN" 2>/dev/null | grep -v ' UND ' | tail -n +4 | head -30 || true
|
||||
echo "--- strace tail:"
|
||||
strace -f "$BIN" --list 2>&1 | tail -15 || true
|
||||
echo "--- valgrind tail:"
|
||||
valgrind -q "$BIN" --list 2>&1 | tail -25 || true
|
||||
echo "--- gdb:"
|
||||
xvfb-run -a gdb -batch \
|
||||
-ex run \
|
||||
-ex 'bt' \
|
||||
-ex 'p $_siginfo.si_code' \
|
||||
-ex 'p/x $_siginfo._sifields._sigfault.si_addr' \
|
||||
-ex 'x/6i $rip' \
|
||||
--args "$BIN" --nocapture || true
|
||||
done
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# OFX plugin discovery end-to-end
|
||||
# ------------------------------------------------------------------
|
||||
# Build a minimal but real OFX plugin into a .ofx.bundle, point
|
||||
# OFX_PLUGIN_PATH at it and let the scan_probe example run the full
|
||||
# host path (directory scan -> dlopen -> setHost -> load -> describe
|
||||
# -> register). The assertion is the plugin's registration line; CI
|
||||
# machines have no system-wide OFX plugins, so the fixture is the
|
||||
# only discovery.
|
||||
- name: Build OFX fixture plugin
|
||||
run: crates/oak-plugin/tests/fixtures/build_fixture.sh .cache/ofx-fixture
|
||||
|
||||
- name: Probe OFX plugin discovery
|
||||
run: |
|
||||
OFX_PLUGIN_PATH="$PWD/.cache/ofx-fixture" \
|
||||
cargo run --locked -p oak-plugin --example scan_probe > probe.log 2>&1
|
||||
grep -q 'type_id=rs.oak.CiTestPlugin' probe.log
|
||||
# A project carrying a plugin node must survive save/load (the
|
||||
# serializer resolves plugin types via the dynamic factory).
|
||||
OAK_OFX_FIXTURE_DIR="$PWD/.cache/ofx-fixtre" \
|
||||
cargo test --locked -p oak-plugin --test ofx_roundtrip
|
||||
|
||||
windows:
|
||||
name: Build & test (Windows)
|
||||
runs-on: oak-windows-2025
|
||||
defaults:
|
||||
run:
|
||||
shell: msys2 {0}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# gpui/ is a git submodule; its crates are workspace members of
|
||||
# their own repo and build as path dependencies of oakapp.
|
||||
submodules: true
|
||||
# ------------------------------------------------------------------
|
||||
# System dependencies
|
||||
# ------------------------------------------------------------------
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
cd "$GITHUB_WORKSPACE"
|
||||
bash ./tooling/install-deps.sh
|
||||
pacman -S --needed --noconfirm \
|
||||
mingw-w64-ucrt-x86_64-cmake \
|
||||
mingw-w64-ucrt-x86_64-opencolorio
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Build environment
|
||||
# ------------------------------------------------------------------
|
||||
# Windows uses the MSYS2 OpenColorIO package (the exact 2.5.2 the
|
||||
# bridge targets; the vendored source needs MSVC-only constructs).
|
||||
# Dynamic here — the CD packages the DLLs next to the binaries.
|
||||
- name: Configure build environment
|
||||
run: |
|
||||
cd "$GITHUB_WORKSPACE"
|
||||
echo "OCIO_RS_ENABLE_REAL=1" >> "$GITHUB_ENV"
|
||||
echo "OCIO_INSTALL_DIR=/ucrt64" >> "$GITHUB_ENV"
|
||||
echo "OCIO_RS_LINK=dynamic" >> "$GITHUB_ENV"
|
||||
# ocio-sys' build.rs force-adds the MSVC + Windows SDK include
|
||||
# dirs on Windows (meant for MSVC hosts); with the GNU toolchain
|
||||
# that drags MSVC-only headers into the g++ compile. Unpack the
|
||||
# crate and gate that block behind OCIO_RS_NO_MSVC_INCLUDES.
|
||||
echo "OCIO_RS_NO_MSVC_INCLUDES=1" >> "$GITHUB_ENV"
|
||||
CH=$(cygpath -u "${CARGO_HOME:-$HOME/.cargo}")
|
||||
cargo fetch --locked
|
||||
for cache in "$CH"/registry/cache/*/; do
|
||||
src="$CH/registry/src/$(basename "$cache")"
|
||||
mkdir -p "$src"
|
||||
[ -f "$cache/ocio-sys-0.2.1.crate" ] && tar xzf "$cache/ocio-sys-0.2.1.crate" -C "$src"
|
||||
done
|
||||
BS=$(ls "$CH"/registry/src/*/ocio-sys-0.2.1/build.rs)
|
||||
grep -q 'OCIO_RS_NO_MSVC_INCLUDES' "$BS" || sed -i \
|
||||
's|if cfg!(target_os = "windows") && has_real_ocio {|if cfg!(target_os = "windows") \&\& has_real_ocio \&\& std::env::var_os("OCIO_RS_NO_MSVC_INCLUDES").is_none() {|' \
|
||||
"$BS"
|
||||
grep -q 'OCIO_RS_NO_MSVC_INCLUDES' "$BS"
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Caches
|
||||
# ------------------------------------------------------------------
|
||||
# Covers the whole target/ dir plus ~/.cargo; shared across branches
|
||||
# of the same OS.
|
||||
# TEMP: cache disabled until the Gitea instance cache is provisioned (Cache cargo artifacts)
|
||||
# uses: Swatinem/rust-cache@v2
|
||||
# with:
|
||||
# shared-key: oak-workspace
|
||||
# cache-on-failure: true
|
||||
|
||||
# The project FFmpeg (release/8.0, static, all free codecs + hwaccel)
|
||||
# is built by tooling/ffmpeg/build-ffmpeg.sh — 10-20 min on a cold
|
||||
# cache. It does not depend on the Rust toolchain, so key it on the
|
||||
# script itself and keep it out of rust-cache.
|
||||
# TEMP: cache disabled until the Gitea instance cache is provisioned (Cache project FFmpeg)
|
||||
# uses: actions/cache@v4
|
||||
# with:
|
||||
# path: .cache/ffmpeg
|
||||
# key: ffmpeg-${{ runner.os }}-${{ hashFiles('tooling/ffmpeg/build-ffmpeg.sh') }}
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Project FFmpeg (script + FFMPEG_DIR; see docs/build.md)
|
||||
# ------------------------------------------------------------------
|
||||
- name: Build project FFmpeg
|
||||
run: |
|
||||
cd "$GITHUB_WORKSPACE"
|
||||
bash tooling/ffmpeg/build-ffmpeg.sh
|
||||
echo "FFMPEG_DIR=$(cygpath -m "$PWD/.cache/ffmpeg")" >> "$GITHUB_ENV"
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Build & test
|
||||
# ------------------------------------------------------------------
|
||||
- name: Build
|
||||
run: |
|
||||
cd "$GITHUB_WORKSPACE"
|
||||
# The runner's job hook injects the MSVC INCLUDE/LIB into every
|
||||
# step; clear them in-step (they poison the MinGW compiles with
|
||||
# MSVC SDK headers).
|
||||
unset INCLUDE LIB
|
||||
# mingw-w64 >= Nov 2025 forwards _assert to __msvcrt_assert inside
|
||||
# libmingwex.a; rustc's link order puts -lmingwex last, so any
|
||||
# binary that pulls _assert.o leaves _fileno/_setmode/
|
||||
# __imp___msvcrt_assert unresolved. A trailing -lmsvcrt re-scans
|
||||
# the CRT import lib after libmingwex.
|
||||
export RUSTFLAGS="-C link-args=-lmsvcrt"
|
||||
cargo build --workspace --locked
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
cd "$GITHUB_WORKSPACE"
|
||||
unset INCLUDE LIB
|
||||
# See Build (Windows): trailing -lmsvcrt for the mingw-w64
|
||||
# _assert/__msvcrt_assert link-order breakage.
|
||||
export RUSTFLAGS="-C link-args=-lmsvcrt"
|
||||
if ! cargo test --workspace --locked; then
|
||||
# Retry once: a few gpui keystroke tests flake on Windows CI —
|
||||
# a synthetic keystroke is occasionally never delivered (the
|
||||
# undo/redo pair and a plain 's' toggle both failed once,
|
||||
# each identically to its pass state). A real regression
|
||||
# fails both passes.
|
||||
echo "first pass failed; retrying once for gpui keystroke flakes"
|
||||
cargo test --workspace --locked
|
||||
fi
|
||||
@@ -1,616 +0,0 @@
|
||||
name: CD
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
# ------------------------------------------------------------------
|
||||
# Windows Installer (MSYS2)
|
||||
# ------------------------------------------------------------------
|
||||
windows:
|
||||
runs-on: warp-windows-latest-x64-32x
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup MSYS2
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: UCRT64
|
||||
update: true
|
||||
install: >-
|
||||
git
|
||||
mingw-w64-ucrt-x86_64-cmake
|
||||
mingw-w64-ucrt-x86_64-ninja
|
||||
mingw-w64-ucrt-x86_64-gcc
|
||||
mingw-w64-ucrt-x86_64-qt6-base
|
||||
mingw-w64-ucrt-x86_64-qt6-tools
|
||||
mingw-w64-ucrt-x86_64-ffmpeg
|
||||
mingw-w64-ucrt-x86_64-openimageio
|
||||
mingw-w64-ucrt-x86_64-opencolorio
|
||||
mingw-w64-ucrt-x86_64-openexr
|
||||
mingw-w64-ucrt-x86_64-fmt
|
||||
mingw-w64-ucrt-x86_64-expat
|
||||
mingw-w64-ucrt-x86_64-portaudio
|
||||
mingw-w64-ucrt-x86_64-vulkan-headers
|
||||
mingw-w64-ucrt-x86_64-vulkan-loader
|
||||
mingw-w64-ucrt-x86_64-nsis
|
||||
mingw-w64-ucrt-x86_64-ntldd
|
||||
|
||||
- name: Build OpenTimelineIO
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
git clone --depth 1 --branch v0.16.0 https://github.com/PixarAnimationStudios/OpenTimelineIO.git
|
||||
cmake -S OpenTimelineIO -B OpenTimelineIO/build -G Ninja \
|
||||
-DOTIO_SHARED_LIBS=ON \
|
||||
-DOTIO_PYTHON_BINDINGS=OFF \
|
||||
-DOTIO_FIND_IMATH=ON \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX="${PWD}/otio-install"
|
||||
cmake --build OpenTimelineIO/build
|
||||
cmake --install OpenTimelineIO/build
|
||||
|
||||
- name: Configure
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
cmake -S . -B build -G Ninja \
|
||||
-DBUILD_QT6=ON \
|
||||
-DOTIO_LOCATION="${PWD}/otio-install" \
|
||||
-DCMAKE_BUILD_TYPE=Release
|
||||
|
||||
- name: Build
|
||||
shell: msys2 {0}
|
||||
run: cmake --build build --config Release
|
||||
|
||||
- name: Deploy dependencies
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
# Let ntldd discover the OTIO DLLs (no rpath on Windows)
|
||||
export PATH="${GITHUB_WORKSPACE}/otio-install/bin:${PATH}"
|
||||
mkdir -p app/packaging/windows/nsis/oak-editor
|
||||
cp build/app/oak-editor.exe app/packaging/windows/nsis/oak-editor/
|
||||
cp build/worker/oak-render-worker.exe app/packaging/windows/nsis/oak-editor/
|
||||
cp build/app/oakgl.dll app/packaging/windows/nsis/oak-editor/
|
||||
if [ -f build/app/oakvulkan.dll ]; then
|
||||
cp build/app/oakvulkan.dll app/packaging/windows/nsis/oak-editor/
|
||||
fi
|
||||
# The FFmpeg bridge DLL is built outside app/ and is not reachable
|
||||
# via PATH, so ntldd cannot discover it from the executables alone
|
||||
cp build/ffmpeg_bridge/bin/ffmpeg_bridge.dll app/packaging/windows/nsis/oak-editor/
|
||||
# Same for the liboakcore DLL
|
||||
cp build/core/oakcore.dll app/packaging/windows/nsis/oak-editor/
|
||||
# Same for the liboakengine DLL
|
||||
cp build/engine/oakengine.dll app/packaging/windows/nsis/oak-editor/
|
||||
windeployqt6 app/packaging/windows/nsis/oak-editor/oak-editor.exe
|
||||
# Copy all non-Qt MSYS2 DLLs recursively for every binary we ship
|
||||
cd app/packaging/windows/nsis/oak-editor
|
||||
for binary in oak-editor.exe oak-render-worker.exe oakgl.dll oakvulkan.dll ffmpeg_bridge.dll oakcore.dll oakengine.dll; do
|
||||
[ -f "$binary" ] || continue
|
||||
for l in $(ntldd -R "$binary" | grep -E 'mingw64|ucrt64|clang64' | sed 's/^[ \t]*//' | cut -d' ' -f3); do
|
||||
cp -v "$l" .
|
||||
done
|
||||
done
|
||||
|
||||
- name: Build installer
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
cd app/packaging/windows/nsis
|
||||
cp "${GITHUB_WORKSPACE}"/LICENSE .
|
||||
makensis oak.nsi
|
||||
mv setup.exe "${GITHUB_WORKSPACE}"/Oak-Video-Editor-Windows-x64.exe
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: oak-windows-installer
|
||||
path: Oak-Video-Editor-Windows-x64.exe
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# macOS DMG
|
||||
# ------------------------------------------------------------------
|
||||
macos:
|
||||
runs-on: warp-macos-15-arm64-12x
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
brew update
|
||||
brew install cmake ninja pkg-config qt@6 ffmpeg openimageio opencolorio openexr portaudio expat molten-vk vulkan-headers vulkan-loader dylibbundler
|
||||
|
||||
- name: Build OpenTimelineIO
|
||||
run: |
|
||||
git clone --depth 1 --branch v0.16.0 https://github.com/PixarAnimationStudios/OpenTimelineIO.git
|
||||
cmake -S OpenTimelineIO -B OpenTimelineIO/build -G Ninja \
|
||||
-DOTIO_SHARED_LIBS=ON \
|
||||
-DOTIO_PYTHON_BINDINGS=OFF \
|
||||
-DOTIO_FIND_IMATH=ON \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX="${PWD}/otio-install"
|
||||
cmake --build OpenTimelineIO/build
|
||||
cmake --install OpenTimelineIO/build
|
||||
|
||||
- name: Configure
|
||||
run: |
|
||||
export PATH="$(brew --prefix qt@6)/bin:$PATH"
|
||||
export CMAKE_PREFIX_PATH="$(brew --prefix qt@6)"
|
||||
# CMake's FindVulkan locates the Homebrew loader via VULKAN_SDK
|
||||
export VULKAN_SDK="$(brew --prefix vulkan-loader)"
|
||||
cmake -S . -B build -G Ninja \
|
||||
-DBUILD_QT6=ON \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DOTIO_LOCATION="${PWD}/otio-install" \
|
||||
-DOCIO_LOCATION="$(brew --prefix opencolorio)"
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build --config Release
|
||||
|
||||
- name: Deploy Qt dependencies
|
||||
run: |
|
||||
export PATH="$(brew --prefix qt@6)/bin:$PATH"
|
||||
macdeployqt build/app/Oak.app \
|
||||
-verbose=2 \
|
||||
-executable=build/app/Oak.app/Contents/MacOS/oak-render-worker
|
||||
# Re-sign after macdeployqt (it breaks signatures when modifying libs)
|
||||
codesign --force --deep --sign - build/app/Oak.app
|
||||
|
||||
- name: Verify macOS bundle plugins
|
||||
run: |
|
||||
ls -la build/app/Oak.app/Contents/PlugIns/platforms/ || (echo "Missing platform plugins!" && exit 1)
|
||||
ls -la build/app/Oak.app/Contents/PlugIns/imageformats/ || true
|
||||
echo "=== otool -L main binary ==="
|
||||
otool -L build/app/Oak.app/Contents/MacOS/Oak | head -30
|
||||
echo "=== otool -L worker ==="
|
||||
otool -L build/app/Oak.app/Contents/MacOS/oak-render-worker | head -30
|
||||
|
||||
- name: Bundle non-Qt libraries
|
||||
run: |
|
||||
mkdir -p build/app/Oak.app/Contents/Frameworks
|
||||
python3 << 'PYEOF'
|
||||
import os, shutil, subprocess
|
||||
|
||||
APP = "build/app/Oak.app"
|
||||
MACOS_DIR = f"{APP}/Contents/MacOS"
|
||||
DEST = f"{APP}/Contents/Frameworks"
|
||||
os.makedirs(DEST, exist_ok=True)
|
||||
|
||||
QT_PREFIXES = ("Qt", "libQt")
|
||||
|
||||
def is_qt_lib(name):
|
||||
return name.startswith(QT_PREFIXES)
|
||||
|
||||
def get_rpaths(binary):
|
||||
out = subprocess.run(["otool", "-l", binary], capture_output=True, text=True).stdout
|
||||
rpaths = []
|
||||
for i, line in enumerate(out.splitlines()):
|
||||
if "cmd LC_RPATH" in line and i + 2 < len(out.splitlines()):
|
||||
parts = out.splitlines()[i + 2].strip().split()
|
||||
if len(parts) >= 2:
|
||||
rpaths.append(parts[1])
|
||||
return rpaths
|
||||
|
||||
def get_deps(binary):
|
||||
out = subprocess.run(["otool", "-L", binary], capture_output=True, text=True).stdout
|
||||
deps = []
|
||||
for line in out.splitlines()[1:]:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
dep = line.split()[0]
|
||||
if dep.startswith("/usr/lib/") or dep.startswith("/System/"):
|
||||
continue
|
||||
deps.append(dep)
|
||||
return deps
|
||||
|
||||
def resolve(ref, rpaths, loader_dir=""):
|
||||
if ref.startswith("/"):
|
||||
return ref if os.path.isfile(ref) else None
|
||||
if ref.startswith("@rpath/"):
|
||||
for rp in rpaths:
|
||||
p = os.path.join(rp, ref[7:])
|
||||
if os.path.isfile(p):
|
||||
return p
|
||||
if ref.startswith("@loader_path/") and loader_dir:
|
||||
p = os.path.join(loader_dir, ref[13:])
|
||||
if os.path.isfile(p):
|
||||
return p
|
||||
if ref.startswith("@executable_path/"):
|
||||
p = os.path.join(APP, "Contents/MacOS", ref[17:])
|
||||
if os.path.isfile(p):
|
||||
return p
|
||||
return None
|
||||
|
||||
PROCESSED = set()
|
||||
|
||||
def process(target, exec_rpaths):
|
||||
tdir = os.path.dirname(target)
|
||||
print(f"Processing: {target}")
|
||||
for dep in get_deps(target):
|
||||
# Skip Qt frameworks and Qt dylibs; macdeployqt already deploys them.
|
||||
dep_base = os.path.basename(dep)
|
||||
if is_qt_lib(dep_base):
|
||||
continue
|
||||
resolved = resolve(dep, exec_rpaths, tdir)
|
||||
if not resolved or not os.path.isfile(resolved):
|
||||
continue
|
||||
resolved = os.path.realpath(resolved)
|
||||
if not (resolved.startswith("/opt/homebrew/") or resolved.startswith("/usr/local/") or resolved.startswith(os.path.realpath(os.getcwd()))):
|
||||
continue
|
||||
base = os.path.basename(resolved)
|
||||
if is_qt_lib(base):
|
||||
continue
|
||||
if base in PROCESSED:
|
||||
subprocess.run(["install_name_tool", "-change", dep, f"@rpath/{base}", target], capture_output=True)
|
||||
continue
|
||||
PROCESSED.add(base)
|
||||
dst = os.path.join(DEST, base)
|
||||
if os.path.abspath(resolved) == os.path.abspath(dst):
|
||||
subprocess.run(["install_name_tool", "-id", f"@rpath/{base}", dst], capture_output=True)
|
||||
subprocess.run(["install_name_tool", "-change", dep, f"@rpath/{base}", target], capture_output=True)
|
||||
process(dst, exec_rpaths)
|
||||
continue
|
||||
print(f" Copying: {resolved} -> {dst}")
|
||||
shutil.copy2(resolved, dst)
|
||||
subprocess.run(["install_name_tool", "-id", f"@rpath/{base}", dst], capture_output=True)
|
||||
subprocess.run(["install_name_tool", "-change", dep, f"@rpath/{base}", target], capture_output=True)
|
||||
process(dst, exec_rpaths)
|
||||
|
||||
binaries = [os.path.join(MACOS_DIR, f) for f in os.listdir(MACOS_DIR)
|
||||
if os.path.isfile(os.path.join(MACOS_DIR, f))]
|
||||
if not binaries:
|
||||
raise RuntimeError(f"No binaries found in {MACOS_DIR}")
|
||||
|
||||
main_binary = os.path.join(MACOS_DIR, "Oak")
|
||||
main_rpaths = get_rpaths(main_binary)
|
||||
|
||||
for binary in binaries:
|
||||
process(binary, main_rpaths)
|
||||
# Ensure every binary can find libraries in Frameworks
|
||||
for rp in get_rpaths(binary):
|
||||
subprocess.run(["install_name_tool", "-delete_rpath", rp, binary], capture_output=True)
|
||||
subprocess.run(["install_name_tool", "-add_rpath", "@executable_path/../Frameworks", binary], capture_output=True)
|
||||
|
||||
subprocess.run(["codesign", "--force", "--deep", "--sign", "-", APP], check=True)
|
||||
print("Done. Frameworks:")
|
||||
for f in sorted(os.listdir(DEST)):
|
||||
print(f" {f}")
|
||||
PYEOF
|
||||
|
||||
- name: Debug bundle contents
|
||||
run: |
|
||||
echo "=== otool -L ==="
|
||||
otool -L build/app/Oak.app/Contents/MacOS/Oak
|
||||
echo "=== Frameworks dir ==="
|
||||
ls -la build/app/Oak.app/Contents/Frameworks/ || echo "(empty)"
|
||||
echo "=== App bundle size ==="
|
||||
du -sh build/app/Oak.app
|
||||
|
||||
- name: Create DMG
|
||||
run: |
|
||||
mkdir -p build/app/dmg-staging
|
||||
cp -R build/app/Oak.app build/app/dmg-staging/
|
||||
ln -s /Applications build/app/dmg-staging/Applications
|
||||
hdiutil create \
|
||||
-srcfolder build/app/dmg-staging \
|
||||
-volname "Oak Video Editor" \
|
||||
-fs HFS+ \
|
||||
-format UDZO \
|
||||
Oak-Video-Editor-macOS.dmg
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: oak-macos-dmg
|
||||
path: Oak-Video-Editor-macOS.dmg
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Linux AppImage (Ubuntu 24.04 — distro FFmpeg 6.x)
|
||||
# ------------------------------------------------------------------
|
||||
appimage:
|
||||
runs-on: warp-ubuntu-latest-x64-16x
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
cmake ninja-build pkg-config \
|
||||
qt6-base-dev qt6-base-dev-tools qt6-base-private-dev qt6-tools-dev qt6-tools-dev-tools \
|
||||
libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libavfilter-dev \
|
||||
libopencolorio-dev libopenimageio-dev libopenexr-dev libexpat1-dev \
|
||||
portaudio19-dev libgl1-mesa-dev libxkbcommon-dev \
|
||||
libvulkan-dev \
|
||||
wget fuse3 desktop-file-utils
|
||||
|
||||
- name: Build OpenTimelineIO
|
||||
run: |
|
||||
git clone --depth 1 --branch v0.16.0 https://github.com/PixarAnimationStudios/OpenTimelineIO.git
|
||||
cmake -S OpenTimelineIO -B OpenTimelineIO/build -G Ninja \
|
||||
-DOTIO_SHARED_LIBS=ON \
|
||||
-DOTIO_PYTHON_BINDINGS=OFF \
|
||||
-DOTIO_FIND_IMATH=ON \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX="${PWD}/otio-install"
|
||||
cmake --build OpenTimelineIO/build
|
||||
cmake --install OpenTimelineIO/build
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
cmake -S . -B build -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||
-DOTIO_LOCATION="${PWD}/otio-install" \
|
||||
-DBUILD_QT6=ON
|
||||
cmake --build build
|
||||
|
||||
- name: Install to AppDir
|
||||
run: |
|
||||
DESTDIR="${PWD}"/AppDir cmake --install build
|
||||
# Copy custom AppRun
|
||||
cp app/packaging/linux/AppRun AppDir/
|
||||
|
||||
- name: Create AppImage
|
||||
env:
|
||||
APPIMAGE_EXTRACT_AND_RUN: 1
|
||||
run: |
|
||||
wget -q "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage"
|
||||
wget -q "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage"
|
||||
wget -q "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage"
|
||||
chmod +x linuxdeploy-x86_64.AppImage linuxdeploy-plugin-qt-x86_64.AppImage appimagetool-x86_64.AppImage
|
||||
export PATH="${PWD}:${PATH}"
|
||||
DEPLOY_ARGS=""
|
||||
# The Vulkan backend is dlopen'ed at runtime, so linuxdeploy cannot
|
||||
# discover it (and its dependencies) from the executable alone
|
||||
if [ -f AppDir/usr/lib/liboakvulkan.so ]; then
|
||||
DEPLOY_ARGS="--library AppDir/usr/lib/liboakvulkan.so"
|
||||
fi
|
||||
./linuxdeploy-x86_64.AppImage \
|
||||
--appdir AppDir \
|
||||
--plugin qt \
|
||||
$DEPLOY_ARGS \
|
||||
--desktop-file AppDir/usr/share/applications/org.oakvideoeditor.Oak.desktop \
|
||||
--icon-file AppDir/usr/share/icons/hicolor/512x512/apps/org.oakvideoeditor.Oak.png \
|
||||
--output appimage
|
||||
|
||||
- name: Verify bundled libraries
|
||||
run: |
|
||||
# Ensure FFmpeg shared libraries are present inside the AppDir
|
||||
ls AppDir/usr/lib/libavcodec.so* || (echo "Missing libavcodec" && exit 1)
|
||||
ls AppDir/usr/lib/libavformat.so* || (echo "Missing libavformat" && exit 1)
|
||||
ls AppDir/usr/lib/libavutil.so* || (echo "Missing libavutil" && exit 1)
|
||||
ls AppDir/usr/lib/libswscale.so* || (echo "Missing libswscale" && exit 1)
|
||||
ls AppDir/usr/lib/libswresample.so* || (echo "Missing libswresample" && exit 1)
|
||||
# Ensure other runtime dependencies are present
|
||||
ls AppDir/usr/lib/libOpenImageIO.so* || (echo "Missing libOpenImageIO" && exit 1)
|
||||
ls AppDir/usr/lib/libOpenColorIO.so* || (echo "Missing libOpenColorIO" && exit 1)
|
||||
ls AppDir/usr/lib/libportaudio.so* || (echo "Missing libportaudio" && exit 1)
|
||||
ls AppDir/usr/lib/libOpenEXR*.so* || (echo "Missing libOpenEXR" && exit 1)
|
||||
ls AppDir/usr/lib/libopentimelineio.so* || (echo "Missing libopentimelineio" && exit 1)
|
||||
ls AppDir/usr/lib/libopentime.so* || (echo "Missing libopentime" && exit 1)
|
||||
# Ensure Oak's own binaries and shared libraries are present
|
||||
ls AppDir/usr/bin/oak-editor || (echo "Missing oak-editor" && exit 1)
|
||||
ls AppDir/usr/bin/oak-render-worker || (echo "Missing oak-render-worker" && exit 1)
|
||||
ls AppDir/usr/lib/liboakgl.so || (echo "Missing liboakgl.so" && exit 1)
|
||||
ls AppDir/usr/lib/liboakvulkan.so || (echo "Missing liboakvulkan.so" && exit 1)
|
||||
ls AppDir/usr/lib/libvulkan.so* || (echo "Missing libvulkan" && exit 1)
|
||||
ls AppDir/usr/lib/libffmpeg_bridge.so || (echo "Missing libffmpeg_bridge.so" && exit 1)
|
||||
ls AppDir/usr/lib/liboakcore.so || (echo "Missing liboakcore.so" && exit 1)
|
||||
ls AppDir/usr/lib/liboakengine.so || (echo "Missing liboakengine.so" && exit 1)
|
||||
# Ensure every shipped binary resolves all of its dependencies
|
||||
! ldd AppDir/usr/bin/oak-editor | grep "not found"
|
||||
! ldd AppDir/usr/bin/oak-render-worker | grep "not found"
|
||||
! ldd AppDir/usr/lib/liboakcore.so | grep "not found"
|
||||
! ldd AppDir/usr/lib/liboakengine.so | grep "not found"
|
||||
! ldd AppDir/usr/lib/liboakgl.so | grep "not found"
|
||||
! ldd AppDir/usr/lib/liboakvulkan.so | grep "not found"
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: oak-linux-appimage
|
||||
path: Oak_Video_Editor-*.AppImage
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Debian package (.deb)
|
||||
# ------------------------------------------------------------------
|
||||
deb:
|
||||
runs-on: warp-ubuntu-latest-x64-16x
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
cmake ninja-build pkg-config \
|
||||
qt6-base-dev qt6-base-dev-tools qt6-base-private-dev qt6-tools-dev qt6-tools-dev-tools \
|
||||
libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libavfilter-dev \
|
||||
libopencolorio-dev libopenimageio-dev libopenexr-dev libexpat1-dev \
|
||||
portaudio19-dev libgl1-mesa-dev libxkbcommon-dev \
|
||||
libvulkan-dev
|
||||
|
||||
- name: Build OpenTimelineIO
|
||||
run: |
|
||||
git clone --depth 1 --branch v0.16.0 https://github.com/PixarAnimationStudios/OpenTimelineIO.git
|
||||
cmake -S OpenTimelineIO -B OpenTimelineIO/build -G Ninja \
|
||||
-DOTIO_SHARED_LIBS=ON \
|
||||
-DOTIO_PYTHON_BINDINGS=OFF \
|
||||
-DOTIO_FIND_IMATH=ON \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX="${PWD}/otio-install"
|
||||
cmake --build OpenTimelineIO/build
|
||||
cmake --install OpenTimelineIO/build
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
cmake -S . -B build -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||
-DOTIO_LOCATION="${PWD}/otio-install" \
|
||||
-DBUILD_QT6=ON
|
||||
cmake --build build
|
||||
|
||||
- name: Create DEB package
|
||||
run: |
|
||||
cd build
|
||||
cpack -G DEB
|
||||
mv *.deb "${GITHUB_WORKSPACE}/"
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: oak-deb-package
|
||||
path: oak-video-editor_*.deb
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# RPM package (.rpm)
|
||||
# ------------------------------------------------------------------
|
||||
rpm:
|
||||
runs-on: warp-ubuntu-latest-x64-16x
|
||||
container:
|
||||
image: fedora:latest
|
||||
steps:
|
||||
- name: Install Git
|
||||
run: dnf install -y git
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
dnf install -y \
|
||||
cmake ninja-build pkgconf-pkg-config \
|
||||
qt6-qtbase-devel qt6-qtbase-private-devel qt6-qttools-devel \
|
||||
ffmpeg-free-devel \
|
||||
OpenImageIO-devel \
|
||||
OpenColorIO-devel \
|
||||
openexr-devel \
|
||||
expat-devel \
|
||||
portaudio-devel \
|
||||
mesa-libGL-devel \
|
||||
vulkan-headers \
|
||||
vulkan-loader-devel \
|
||||
libxkbcommon-devel \
|
||||
gcc-c++ \
|
||||
bzip2-devel \
|
||||
rpm-build
|
||||
|
||||
- name: Build OpenTimelineIO
|
||||
run: |
|
||||
git clone --depth 1 --branch v0.16.0 https://github.com/PixarAnimationStudios/OpenTimelineIO.git
|
||||
cmake -S OpenTimelineIO -B OpenTimelineIO/build -G Ninja \
|
||||
-DOTIO_SHARED_LIBS=ON \
|
||||
-DOTIO_PYTHON_BINDINGS=OFF \
|
||||
-DOTIO_FIND_IMATH=ON \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX="${PWD}/otio-install"
|
||||
cmake --build OpenTimelineIO/build
|
||||
cmake --install OpenTimelineIO/build
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
cmake -S . -B build -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||
-DOTIO_LOCATION="${PWD}/otio-install" \
|
||||
-DBUILD_QT6=ON
|
||||
cmake --build build
|
||||
|
||||
- name: Create RPM package
|
||||
run: |
|
||||
cd build
|
||||
cpack -G RPM
|
||||
mv *.rpm "${GITHUB_WORKSPACE}/"
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: oak-rpm-package
|
||||
path: oak-video-editor-*.rpm
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Arch Linux package (.pkg.tar.zst)
|
||||
# ------------------------------------------------------------------
|
||||
archlinux:
|
||||
runs-on: warp-ubuntu-latest-x64-16x
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Prepare source tarball and PKGBUILD
|
||||
run: |
|
||||
VERSION="${GITHUB_REF_NAME#v}"
|
||||
# Arch pkgver cannot contain hyphens
|
||||
PKGVER="${VERSION//-/_}"
|
||||
TARBALL="oak-video-editor-${PKGVER}.tar.gz"
|
||||
tar czf "/tmp/${TARBALL}" \
|
||||
--transform "s,^,oak-video-editor-${PKGVER}/," \
|
||||
--exclude='.git' \
|
||||
--exclude='build' \
|
||||
--exclude='*.tar.gz' \
|
||||
.
|
||||
mv "/tmp/${TARBALL}" "${TARBALL}"
|
||||
sed "s/@VERSION@/${PKGVER}/g" app/packaging/arch/PKGBUILD.in > PKGBUILD
|
||||
|
||||
- name: Build package in Arch Linux container
|
||||
run: |
|
||||
docker run --rm \
|
||||
-v "${PWD}:/build" \
|
||||
-w /build \
|
||||
archlinux:base-devel \
|
||||
bash -c '
|
||||
set -e
|
||||
pacman-key --init
|
||||
pacman -Syu --noconfirm
|
||||
pacman -S --noconfirm --needed \
|
||||
cmake ninja git pkgconf \
|
||||
qt6-base qt6-tools ffmpeg openimageio opencolorio openexr expat portaudio \
|
||||
mesa vulkan-headers vulkan-icd-loader libxkbcommon fmt \
|
||||
opentimelineio
|
||||
useradd -m builder
|
||||
chown -R builder /build
|
||||
su builder -c "makepkg -s --noconfirm"
|
||||
'
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: oak-arch-package
|
||||
path: oak-video-editor-*.pkg.tar.zst
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Create Draft Release
|
||||
# ------------------------------------------------------------------
|
||||
release:
|
||||
needs: [windows, macos, appimage, deb, rpm, archlinux]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
merge-multiple: true
|
||||
|
||||
- name: Create Draft Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
draft: true
|
||||
tag_name: ${{ github.ref_name }}
|
||||
name: ${{ github.ref_name }}
|
||||
files: artifacts/*
|
||||
@@ -1,378 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
schedule:
|
||||
# Daily at 03:17 UTC to keep ccache/dependency caches warm
|
||||
- cron: '17 3 * * *'
|
||||
|
||||
jobs:
|
||||
build-test:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [warp-ubuntu-latest-x64-8x, warp-macos-15-arm64-6x, warp-windows-latest-x64-16x]
|
||||
env:
|
||||
CMAKE_BUILD_TYPE: Release
|
||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
CCACHE_MAXSIZE: 1G
|
||||
# Enable OFX integration tests that require external plugin bundles
|
||||
RUN_OFX_ITEST: "1"
|
||||
# Enable plugin subsystem smoke tests
|
||||
OAK_PLUGIN_SMOKE_TEST: "1"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Linux dependencies
|
||||
# ------------------------------------------------------------------
|
||||
- name: Install dependencies (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
ninja-build pkg-config nasm \
|
||||
qt6-base-dev qt6-base-dev-tools qt6-base-private-dev qt6-tools-dev qt6-tools-dev-tools \
|
||||
libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libavfilter-dev \
|
||||
ffmpeg \
|
||||
libopencolorio-dev libopenimageio-dev libopenexr-dev libexpat1-dev \
|
||||
portaudio19-dev libgl1-mesa-dev libgl1-mesa-dri libxkbcommon-dev ccache \
|
||||
xvfb libvulkan-dev mesa-vulkan-drivers vulkan-tools libshaderc-dev
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# macOS dependencies
|
||||
# ------------------------------------------------------------------
|
||||
- name: Install dependencies (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
run: |
|
||||
brew update
|
||||
brew install ninja pkg-config ccache qt@6 ffmpeg openimageio opencolorio openexr portaudio expat molten-vk vulkan-headers
|
||||
echo "$(brew --prefix qt@6)/bin" >> "$GITHUB_PATH"
|
||||
echo "CMAKE_PREFIX_PATH=$(brew --prefix qt@6)" >> "$GITHUB_ENV"
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# OpenTimelineIO (required dependency, built from source; cached)
|
||||
# ------------------------------------------------------------------
|
||||
- name: Cache OpenTimelineIO (Unix)
|
||||
if: runner.os != 'Windows'
|
||||
id: otio-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: otio-install
|
||||
key: otio-0.16.0-${{ runner.os }}-v2
|
||||
|
||||
- name: Set OTIO location (Unix)
|
||||
if: runner.os != 'Windows'
|
||||
run: |
|
||||
echo "OTIO_LOCATION=${PWD}/otio-install" >> "$GITHUB_ENV"
|
||||
# With OTIO_FIND_IMATH=ON the system (brew/apt) Imath must be the
|
||||
# only Imath header set in play; drop any bundled headers a stale
|
||||
# cache entry may have brought back (they collide with the system
|
||||
# include guards and break ImathBox.h).
|
||||
rm -rf "${PWD}/otio-install/include/Imath"
|
||||
|
||||
- name: Build OpenTimelineIO (Unix)
|
||||
if: runner.os != 'Windows' && steps.otio-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
git clone --depth 1 --branch v0.16.0 https://github.com/PixarAnimationStudios/OpenTimelineIO.git
|
||||
cmake -S OpenTimelineIO -B OpenTimelineIO/build -G Ninja \
|
||||
-DOTIO_SHARED_LIBS=ON \
|
||||
-DOTIO_PYTHON_BINDINGS=OFF \
|
||||
-DOTIO_FIND_IMATH=ON \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX="${PWD}/otio-install"
|
||||
cmake --build OpenTimelineIO/build
|
||||
cmake --install OpenTimelineIO/build
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Windows dependencies (MSYS2)
|
||||
# ------------------------------------------------------------------
|
||||
- name: Setup MSYS2
|
||||
if: runner.os == 'Windows'
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: UCRT64
|
||||
update: true
|
||||
cache: true
|
||||
install: >-
|
||||
git
|
||||
mingw-w64-ucrt-x86_64-cmake
|
||||
mingw-w64-ucrt-x86_64-ninja
|
||||
mingw-w64-ucrt-x86_64-gcc
|
||||
mingw-w64-ucrt-x86_64-qt6-base
|
||||
mingw-w64-ucrt-x86_64-qt6-tools
|
||||
mingw-w64-ucrt-x86_64-ffmpeg
|
||||
mingw-w64-ucrt-x86_64-openimageio
|
||||
mingw-w64-ucrt-x86_64-opencolorio
|
||||
mingw-w64-ucrt-x86_64-openexr
|
||||
mingw-w64-ucrt-x86_64-fmt
|
||||
mingw-w64-ucrt-x86_64-expat
|
||||
mingw-w64-ucrt-x86_64-portaudio
|
||||
mingw-w64-ucrt-x86_64-vulkan-headers
|
||||
mingw-w64-ucrt-x86_64-vulkan-loader
|
||||
mingw-w64-ucrt-x86_64-make
|
||||
mingw-w64-ucrt-x86_64-ccache
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# OpenTimelineIO (required dependency, built from source; cached).
|
||||
# Must come after Setup MSYS2 (uses the msys2 shell).
|
||||
# ------------------------------------------------------------------
|
||||
- name: Cache OpenTimelineIO (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
id: otio-cache-win
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: otio-install
|
||||
key: otio-0.16.0-${{ runner.os }}-v2
|
||||
|
||||
- name: Set OTIO location (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
echo "OTIO_LOCATION=${PWD}/otio-install" >> "$GITHUB_ENV"
|
||||
# No rpath on Windows: test executables need the OTIO DLLs on PATH.
|
||||
# OTIO's MinGW install puts the DLLs in lib/, not bin/.
|
||||
echo "$(cygpath -w "${PWD}/otio-install/lib")" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Build OpenTimelineIO (Windows)
|
||||
if: runner.os == 'Windows' && steps.otio-cache-win.outputs.cache-hit != 'true'
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
git clone --depth 1 --branch v0.16.0 https://github.com/PixarAnimationStudios/OpenTimelineIO.git
|
||||
cmake -S OpenTimelineIO -B OpenTimelineIO/build -G Ninja \
|
||||
-DOTIO_SHARED_LIBS=ON \
|
||||
-DOTIO_PYTHON_BINDINGS=OFF \
|
||||
-DOTIO_FIND_IMATH=ON \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX="${PWD}/otio-install"
|
||||
cmake --build OpenTimelineIO/build
|
||||
cmake --install OpenTimelineIO/build
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Compile cache (ccache) — restored before configure so the launcher
|
||||
# flags below pick it up on all three platforms
|
||||
# ------------------------------------------------------------------
|
||||
- name: Restore ccache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ github.workspace }}/.ccache
|
||||
key: ccache-${{ runner.os }}-${{ github.ref_name }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
ccache-${{ runner.os }}-${{ github.ref_name }}-
|
||||
ccache-${{ runner.os }}-
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Configure
|
||||
# ------------------------------------------------------------------
|
||||
- name: Configure (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
cmake -S . -B build -G Ninja \
|
||||
-DBUILD_TESTS=ON \
|
||||
-DBUILD_QT6=ON \
|
||||
-DOCIO_LOCATION=/usr \
|
||||
-DOTIO_LOCATION=${OTIO_LOCATION} \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
|
||||
|
||||
- name: Configure (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
run: |
|
||||
cmake -S . -B build -G Ninja \
|
||||
-DBUILD_TESTS=ON \
|
||||
-DBUILD_QT6=ON \
|
||||
-DOCIO_LOCATION=$(brew --prefix opencolorio) \
|
||||
-DOTIO_LOCATION=${OTIO_LOCATION} \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
|
||||
|
||||
- name: Configure (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
cmake -S . -B build -G Ninja \
|
||||
-DBUILD_TESTS=ON \
|
||||
-DBUILD_QT6=ON \
|
||||
-DOTIO_LOCATION=${OTIO_LOCATION} \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Build Oak
|
||||
# ------------------------------------------------------------------
|
||||
- name: Build
|
||||
if: runner.os != 'Windows'
|
||||
run: |
|
||||
cmake --build build --config ${{ env.CMAKE_BUILD_TYPE }}
|
||||
ccache -s
|
||||
|
||||
- name: Build (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
cmake --build build --config ${{ env.CMAKE_BUILD_TYPE }}
|
||||
ccache -s
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Clone and build OpenFX-Misc plugins (used by OFX integration tests)
|
||||
# ------------------------------------------------------------------
|
||||
- name: Clone OpenFX-Misc
|
||||
run: git clone --recursive --depth 1 https://github.com/NatronGitHub/openfx-misc.git
|
||||
|
||||
- name: Build OpenFX-Misc plugins (Unix)
|
||||
if: runner.os != 'Windows'
|
||||
run: |
|
||||
cd openfx-misc
|
||||
make -j"$(nproc)"
|
||||
sudo make install
|
||||
PLUGIN_FILE=$(find /usr/OFX/Plugins /usr/local/OFX/Plugins . -name "*.ofx.bundle" -print -quit 2>/dev/null | head -1)
|
||||
if [ -n "$PLUGIN_FILE" ]; then
|
||||
PLUGIN_DIR=$(dirname "$PLUGIN_FILE")
|
||||
else
|
||||
PLUGIN_DIR="$PWD"
|
||||
fi
|
||||
echo "OAK_OFX_ITEST=1" >> "$GITHUB_ENV"
|
||||
echo "OAK_OFX_PLUGIN_PATH=$PLUGIN_DIR" >> "$GITHUB_ENV"
|
||||
echo "OAK_OFX_PLUGIN_ID=net.sf.openfx.ChromaKeyerPlugin" >> "$GITHUB_ENV"
|
||||
|
||||
# The Windows runners have no GPU: WGL falls back to the GDI software
|
||||
# OpenGL 1.1 implementation and every render worker dies calling 3.2
|
||||
# core entry points. Drop Mesa's llvmpipe opengl32.dll next to every
|
||||
# binary that creates a GL context (the application directory wins the
|
||||
# DLL search order over System32).
|
||||
- name: Install Mesa software OpenGL (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
curl -sSL -o /tmp/mesa.7z \
|
||||
https://github.com/pal1000/mesa-dist-win/releases/download/26.1.5/mesa3d-26.1.5-release-mingw.7z
|
||||
"/c/Program Files/7-Zip/7z.exe" x /tmp/mesa.7z -o/tmp/mesa > /dev/null
|
||||
ls -la /tmp/mesa /tmp/mesa/x64
|
||||
for d in build/engine build/worker build/cli build/tests/gtest build/app; do
|
||||
if [ -d "$d" ]; then
|
||||
cp /tmp/mesa/x64/*.dll "$d/"
|
||||
# Qt ignores an app-local opengl32.dll for WGL (it always binds
|
||||
# the System32 one); its documented software-GL override is
|
||||
# opengl32sw.dll, loaded when QT_OPENGL=software.
|
||||
cp "$d/opengl32.dll" "$d/opengl32sw.dll"
|
||||
ls "$d"/opengl32sw.dll "$d"/libgallium_wgl.dll
|
||||
fi
|
||||
done
|
||||
echo "QT_OPENGL=software" >> "$GITHUB_ENV"
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Run all tests (including previously environment-gated OFX tests)
|
||||
# ------------------------------------------------------------------
|
||||
- name: Test (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
env:
|
||||
QT_QPA_PLATFORM: offscreen
|
||||
run: xvfb-run -a ctest --test-dir build --output-on-failure -C ${{ env.CMAKE_BUILD_TYPE }}
|
||||
|
||||
# Diagnostic: if the Linux test run failed, re-run the suspect gtest
|
||||
# filter under gdb to capture a native backtrace of the segfault.
|
||||
# Also break on dlclose to see who unloads the render backend library
|
||||
# before RenderManager's destructor calls into it.
|
||||
- name: gtest segfault backtrace (Linux)
|
||||
if: runner.os == 'Linux' && failure()
|
||||
env:
|
||||
QT_QPA_PLATFORM: offscreen
|
||||
run: |
|
||||
sudo apt-get update && sudo apt-get install -y gdb
|
||||
xvfb-run -a gdb -batch \
|
||||
-ex run \
|
||||
-ex 'bt full' \
|
||||
-ex 'info proc mappings' \
|
||||
-ex 'info symbol $pc' \
|
||||
--args ./build/tests/gtest/olive-gtest \
|
||||
--gtest_filter='MainWindow.ConstructsOffscreenWithPanelsAndMenus' || true
|
||||
|
||||
- name: Test (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
run: ctest --test-dir build --output-on-failure -C ${{ env.CMAKE_BUILD_TYPE }}
|
||||
|
||||
- name: Test (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
shell: msys2 {0}
|
||||
run: ctest --test-dir build --output-on-failure -C ${{ env.CMAKE_BUILD_TYPE }}
|
||||
|
||||
# Diagnostic: if the Windows test run failed, show which DLLs the
|
||||
# engine test executables cannot resolve (0xc0000135).
|
||||
- name: Missing DLL diagnosis (Windows)
|
||||
if: runner.os == 'Windows' && failure()
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
echo "PATH=$PATH"
|
||||
ldd build/engine/oakengine_ipc_test.exe | grep -i "not found" || true
|
||||
objdump -p build/engine/oakengine_ipc_test.exe | grep "DLL Name" | sort -u
|
||||
ls otio-install/lib otio-install/bin 2>/dev/null || true
|
||||
echo "=== render worker stderr logs ==="
|
||||
for f in "$TEMP"/oak-render-worker-*.stderr.log "$TMP"/oak-render-worker-*.stderr.log /tmp/oak-render-worker-*.stderr.log; do
|
||||
[ -f "$f" ] && { echo "--- $f"; tail -50 "$f"; }
|
||||
done
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Filtered gtest runs for clearer CI output
|
||||
# ------------------------------------------------------------------
|
||||
- name: Plugin Smoke Tests (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
env:
|
||||
QT_QPA_PLATFORM: offscreen
|
||||
run: xvfb-run -a ./build/tests/gtest/olive-gtest --gtest_filter="PluginSmoke*"
|
||||
|
||||
- name: Plugin Smoke Tests (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
run: ./build/tests/gtest/olive-gtest --gtest_filter="PluginSmoke*"
|
||||
|
||||
- name: Plugin Smoke Tests (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
shell: msys2 {0}
|
||||
run: ./build/tests/gtest/olive-gtest --gtest_filter="PluginSmoke*"
|
||||
|
||||
- name: OFX Integration Tests (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
env:
|
||||
QT_QPA_PLATFORM: offscreen
|
||||
run: xvfb-run -a ./build/tests/gtest/olive-gtest --gtest_filter="PluginIntegration.*:PluginMisc.*"
|
||||
|
||||
- name: OFX Integration Tests (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
run: ./build/tests/gtest/olive-gtest --gtest_filter="PluginIntegration.*:PluginMisc.*"
|
||||
|
||||
- name: Audio Smoke Tests (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
env:
|
||||
QT_QPA_PLATFORM: offscreen
|
||||
run: xvfb-run -a ./build/tests/gtest/olive-gtest --gtest_filter="AudioSmoke*"
|
||||
|
||||
- name: Audio Smoke Tests (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
run: ./build/tests/gtest/olive-gtest --gtest_filter="AudioSmoke*"
|
||||
|
||||
- name: Audio Smoke Tests (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
shell: msys2 {0}
|
||||
run: ./build/tests/gtest/olive-gtest --gtest_filter="AudioSmoke*"
|
||||
|
||||
- name: Viewer Smoke Tests (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
env:
|
||||
QT_QPA_PLATFORM: offscreen
|
||||
run: xvfb-run -a ./build/tests/gtest/olive-gtest --gtest_filter="ViewerSmoke*"
|
||||
|
||||
- name: Viewer Smoke Tests (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
run: ./build/tests/gtest/olive-gtest --gtest_filter="ViewerSmoke*"
|
||||
|
||||
- name: Viewer Smoke Tests (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
shell: msys2 {0}
|
||||
run: ./build/tests/gtest/olive-gtest --gtest_filter="ViewerSmoke*"
|
||||
+12
@@ -15,6 +15,10 @@ CmakeSettings.json
|
||||
# clangd's index and likely other things that need not be in the repository
|
||||
.cache/
|
||||
|
||||
# Generated packaging assets (icons/icon.png is produced from Oak_Icon.svg
|
||||
# by rsvg-convert in the CD workflow; see .github/workflows/cd.yml)
|
||||
/icons/
|
||||
|
||||
# macOS General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
@@ -70,6 +74,7 @@ ui_*.h
|
||||
*.jsc
|
||||
Makefile*
|
||||
*build-*
|
||||
!tooling/ffmpeg/build-ffmpeg.sh
|
||||
*.qm
|
||||
*.prl
|
||||
|
||||
@@ -109,3 +114,10 @@ act
|
||||
|
||||
# Local OpenTimelineIO build (see docs/build.md)
|
||||
otio-install/
|
||||
|
||||
# Rust
|
||||
**/target/
|
||||
tarpaulin-out/
|
||||
.env
|
||||
# CD packaging artifacts
|
||||
/*.dmg
|
||||
|
||||
+3
-4
@@ -1,4 +1,3 @@
|
||||
[submodule "third_party/KDDockWidgets"]
|
||||
path = third_party/KDDockWidgets
|
||||
url = https://github.com/OliveCommunity/KDDockWidgets.git
|
||||
branch = main
|
||||
[submodule "gpui"]
|
||||
path = gpui
|
||||
url = https://git.oakvideoeditor.org/oak-team/oak-gpui.git
|
||||
|
||||
-403
@@ -1,403 +0,0 @@
|
||||
# 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/>.
|
||||
|
||||
cmake_minimum_required(VERSION 3.13 FATAL_ERROR)
|
||||
|
||||
project(olive-editor VERSION 0.4.1 LANGUAGES CXX)
|
||||
# Fallback version used only when git is unavailable (e.g. source tarball);
|
||||
# normally overridden by the git tag / commit hash logic further below.
|
||||
# Edit version.txt to change it.
|
||||
if(EXISTS "${CMAKE_SOURCE_DIR}/version.txt")
|
||||
file(STRINGS "${CMAKE_SOURCE_DIR}/version.txt" PROJECT_VERSION LIMIT_COUNT 1)
|
||||
string(STRIP "${PROJECT_VERSION}" PROJECT_VERSION)
|
||||
else()
|
||||
set(PROJECT_VERSION "0.0.0-unknown")
|
||||
endif()
|
||||
|
||||
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DOFX_SUPPORTS_OPENGLRENDER")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DOFX_SUPPORTS_OPENGLRENDER")
|
||||
option(BUILD_QT6 "Build with Qt 6 over 5 (experimental)" ON)
|
||||
option(BUILD_DOXYGEN "Build Doxygen documentation" OFF)
|
||||
option(BUILD_TESTS "Build unit tests" OFF)
|
||||
option(USE_WERROR "Error on compile warning" OFF)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
# Generates a compile_commands.json in the build dir, link that to the repo
|
||||
# root to enrich your IDE with clangd language server protocol functionalities
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
# Sanitizers
|
||||
add_library(olive-sanitizers INTERFACE)
|
||||
include(cmake/Sanitizers.cmake)
|
||||
enable_sanitizers(olive-sanitizers)
|
||||
list(APPEND OLIVE_LIBRARIES olive-sanitizers)
|
||||
|
||||
# Set compiler options
|
||||
if(MSVC)
|
||||
set(OLIVE_COMPILE_OPTIONS
|
||||
/wd4267
|
||||
/wd4244
|
||||
/experimental:external
|
||||
/external:anglebrackets
|
||||
/external:W0
|
||||
"$<$<CONFIG:RELEASE>:/O2>"
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:/MP>"
|
||||
/DOFX_SUPPORTS_OPENGLRENDER
|
||||
)
|
||||
if (USE_WERROR)
|
||||
list(APPEND OLIVE_COMPILE_OPTIONS "/WX")
|
||||
endif()
|
||||
else()
|
||||
set(OLIVE_COMPILE_OPTIONS
|
||||
"$<$<CONFIG:RELEASE>:-O2>"
|
||||
-Wuninitialized
|
||||
-pedantic-errors
|
||||
-Wall
|
||||
-Wextra
|
||||
-Wno-unused-parameter
|
||||
-Wshadow
|
||||
-DOFX_SUPPORTS_OPENGLRENDER
|
||||
|
||||
)
|
||||
if (USE_WERROR)
|
||||
list(APPEND OLIVE_COMPILE_OPTIONS "-Werror")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# MinGW does not define WIN32/WINDOWS the way OpenFX HostSupport expects.
|
||||
# Ensure these are visible so ofxhBinary.h et al. pick the Windows code path.
|
||||
if(MINGW)
|
||||
add_compile_definitions(WIN32 WINDOWS)
|
||||
endif()
|
||||
|
||||
set(OLIVE_DEFINITIONS -DQT_DEPRECATED_WARNINGS)
|
||||
if (WIN32)
|
||||
list(APPEND OLIVE_DEFINITIONS -DUNICODE -D_UNICODE)
|
||||
endif()
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
|
||||
|
||||
# OFX HostSupport requires expat::expat
|
||||
find_package(EXPAT REQUIRED)
|
||||
if (TARGET EXPAT::EXPAT AND NOT TARGET expat::expat)
|
||||
add_library(expat::expat ALIAS EXPAT::EXPAT)
|
||||
endif()
|
||||
|
||||
# Link OpenGL
|
||||
if(UNIX AND NOT APPLE AND NOT DEFINED OpenGL_GL_PREFERENCE)
|
||||
set(OpenGL_GL_PREFERENCE LEGACY)
|
||||
endif()
|
||||
find_package(OpenGL REQUIRED)
|
||||
list(APPEND OLIVE_LIBRARIES OpenGL::GL)
|
||||
|
||||
# Optional: Link Vulkan and shaderc (for the Vulkan render backend)
|
||||
find_package(Vulkan)
|
||||
find_package(PkgConfig)
|
||||
if(PkgConfig_FOUND)
|
||||
pkg_check_modules(SHADERC shaderc)
|
||||
endif()
|
||||
if(Vulkan_FOUND)
|
||||
message(STATUS "Vulkan found: ${Vulkan_LIBRARIES}")
|
||||
else()
|
||||
message(STATUS " Vulkan not found. The Vulkan render backend will be disabled.")
|
||||
endif()
|
||||
|
||||
# Link OpenColorIO
|
||||
find_package(OpenColorIO 2.1.1 REQUIRED)
|
||||
list(APPEND OLIVE_LIBRARIES ${OCIO_LIBRARIES})
|
||||
list(APPEND OLIVE_INCLUDE_DIRS ${OCIO_INCLUDE_DIRS})
|
||||
|
||||
# Link OpenImageIO
|
||||
find_package(OpenImageIO 2.1.12 REQUIRED)
|
||||
list(APPEND OLIVE_LIBRARIES ${OIIO_LIBRARIES})
|
||||
list(APPEND OLIVE_INCLUDE_DIRS ${OIIO_INCLUDE_DIRS})
|
||||
|
||||
# Link OpenEXR
|
||||
find_package(OpenEXR REQUIRED)
|
||||
list(APPEND OLIVE_LIBRARIES ${OPENEXR_LIBRARIES})
|
||||
list(APPEND OLIVE_INCLUDE_DIRS ${OPENEXR_INCLUDES})
|
||||
|
||||
# Link Olive
|
||||
list(APPEND OLIVE_LIBRARIES olivecore)
|
||||
list(APPEND OLIVE_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/core/include)
|
||||
|
||||
# Shared header-only utilities (used by both engine/ and app/)
|
||||
list(APPEND OLIVE_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/shared/include)
|
||||
|
||||
|
||||
# Link Qt
|
||||
set(QT_LIBRARIES
|
||||
Core
|
||||
Gui
|
||||
Widgets
|
||||
OpenGL
|
||||
LinguistTools
|
||||
Concurrent
|
||||
)
|
||||
if (UNIX AND NOT APPLE)
|
||||
list(APPEND QT_LIBRARIES DBus)
|
||||
endif()
|
||||
if (BUILD_QT6)
|
||||
set(QT_NAME Qt6)
|
||||
else()
|
||||
set(QT_NAME Qt5)
|
||||
endif()
|
||||
find_package(QT
|
||||
NAMES
|
||||
${QT_NAME}
|
||||
REQUIRED
|
||||
COMPONENTS
|
||||
${QT_LIBRARIES}
|
||||
OPTIONAL_COMPONENTS
|
||||
Network
|
||||
)
|
||||
find_package(Qt${QT_VERSION_MAJOR} REQUIRED
|
||||
COMPONENTS
|
||||
${QT_LIBRARIES}
|
||||
OPTIONAL_COMPONENTS
|
||||
Network
|
||||
)
|
||||
if (NOT Qt${QT_VERSION_MAJOR}Network_FOUND)
|
||||
message(" Qt${QT_VERSION_MAJOR}::Network module not found, crash reporting will be disabled.")
|
||||
endif()
|
||||
list(APPEND OLIVE_LIBRARIES
|
||||
Qt${QT_VERSION_MAJOR}::Core
|
||||
Qt${QT_VERSION_MAJOR}::Gui
|
||||
Qt${QT_VERSION_MAJOR}::Widgets
|
||||
Qt${QT_VERSION_MAJOR}::OpenGL
|
||||
Qt${QT_VERSION_MAJOR}::Concurrent
|
||||
)
|
||||
|
||||
if (${QT_VERSION_MAJOR} EQUAL "6")
|
||||
find_package(Qt${QT_VERSION_MAJOR}
|
||||
REQUIRED
|
||||
OpenGLWidgets
|
||||
)
|
||||
|
||||
list(APPEND OLIVE_LIBRARIES
|
||||
Qt${QT_VERSION_MAJOR}::OpenGLWidgets
|
||||
)
|
||||
|
||||
# Link KDDockWidgets
|
||||
#find_package(KDDockWidgets-qt6 CONFIG REQUIRED)
|
||||
else()
|
||||
# Link KDDockWidgets
|
||||
#find_package(KDDockWidgets CONFIG REQUIRED)
|
||||
endif()
|
||||
|
||||
list(APPEND OLIVE_LIBRARIES
|
||||
KDAB::kddockwidgets
|
||||
)
|
||||
|
||||
# Link OFX HostSupport wherever libolive-editor objects are used.
|
||||
list(APPEND OLIVE_LIBRARIES OfxHost)
|
||||
|
||||
# FFmpeg isolation: all FFmpeg access in the editor goes through this shared
|
||||
# library's pure C API. It is the only component that links FFmpeg.
|
||||
add_subdirectory(ffmpeg_bridge)
|
||||
list(APPEND OLIVE_LIBRARIES ffmpeg_bridge)
|
||||
list(APPEND OLIVE_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/ffmpeg_bridge/include)
|
||||
|
||||
# Link PortAudio
|
||||
find_package(PortAudio REQUIRED)
|
||||
set(CMAKE_REQUIRED_INCLUDES ${PORTAUDIO_INCLUDE_DIRS})
|
||||
include(CheckIncludeFileCXX)
|
||||
check_include_file_cxx( "pa_jack.h" PA_HAS_JACK)
|
||||
if (PA_HAS_JACK)
|
||||
list(APPEND OLIVE_DEFINITIONS PA_HAS_JACK)
|
||||
endif()
|
||||
|
||||
list(APPEND OLIVE_INCLUDE_DIRS ${PORTAUDIO_INCLUDE_DIRS})
|
||||
list(APPEND OLIVE_LIBRARIES ${PORTAUDIO_LIBRARIES})
|
||||
|
||||
# Required: Link OpenTimelineIO
|
||||
find_package(OpenTimelineIO REQUIRED)
|
||||
list(APPEND OLIVE_DEFINITIONS USE_OTIO)
|
||||
list(APPEND OLIVE_INCLUDE_DIRS ${OTIO_INCLUDE_DIRS})
|
||||
list(APPEND OLIVE_LIBRARIES ${OTIO_LIBRARIES})
|
||||
|
||||
# Bundle the OTIO shared libraries into our install tree so that packages
|
||||
# (deb/rpm/AppImage/Windows installer) ship them — most distros have no OTIO
|
||||
# package to depend on. Distro-native packaging (e.g. Arch PKGBUILD, where
|
||||
# opentimelineio is a proper depends entry) should pass -DOAK_BUNDLE_OTIO=OFF.
|
||||
option(OAK_BUNDLE_OTIO "Install OTIO runtime libraries alongside Oak" ON)
|
||||
if (OAK_BUNDLE_OTIO AND UNIX AND NOT APPLE)
|
||||
include(GNUInstallDirs)
|
||||
file(GLOB _otio_runtime_libs
|
||||
"${OTIO_LIBRARY_DIR}/libopentimelineio.so*"
|
||||
"${OTIO_LIBRARY_DIR}/libopentime.so*")
|
||||
if (_otio_runtime_libs)
|
||||
install(FILES ${_otio_runtime_libs} DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# OTIO's macOS dylibs use @loader_path install names: every binary that
|
||||
# (transitively) links them needs a copy of the dylibs next to itself.
|
||||
# Windows has no rpath either, so its DLLs must also sit next to each
|
||||
# executable (relying on PATH is fragile in CI shells).
|
||||
# Call oak_copy_otio_runtime(<target>) for each executable/shared library.
|
||||
if (OAK_BUNDLE_OTIO AND (APPLE OR WIN32) AND OTIO_LIBRARY_DIR)
|
||||
if (APPLE)
|
||||
file(GLOB OAK_OTIO_DYLIBS
|
||||
"${OTIO_LIBRARY_DIR}/libopentimelineio*.dylib"
|
||||
"${OTIO_LIBRARY_DIR}/libopentime*.dylib")
|
||||
else()
|
||||
file(GLOB OAK_OTIO_DYLIBS
|
||||
"${OTIO_LIBRARY_DIR}/libopentimelineio*.dll"
|
||||
"${OTIO_LIBRARY_DIR}/libopentime*.dll")
|
||||
endif()
|
||||
function(oak_copy_otio_runtime target)
|
||||
add_custom_command(TARGET ${target} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
${OAK_OTIO_DYLIBS} $<TARGET_FILE_DIR:${target}>)
|
||||
endfunction()
|
||||
endif()
|
||||
|
||||
# Optional: Link Google Crashpad
|
||||
find_package(GoogleCrashpad)
|
||||
if (GoogleCrashpad_FOUND)
|
||||
list(APPEND OLIVE_DEFINITIONS USE_CRASHPAD)
|
||||
list(APPEND OLIVE_INCLUDE_DIRS ${CRASHPAD_INCLUDE_DIRS})
|
||||
list(APPEND OLIVE_LIBRARIES ${CRASHPAD_LIBRARIES})
|
||||
else()
|
||||
message(" Automatic crash reporting will be disabled.")
|
||||
|
||||
if (APPLE)
|
||||
# Enables use of special functions for slider dragging, only linked if Crashpad isn't found
|
||||
# because Crashpad links it itself and will cause duplicate references if we also link it
|
||||
list(APPEND OLIVE_LIBRARIES "-framework ApplicationServices")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (APPLE)
|
||||
list(APPEND OLIVE_LIBRARIES "-framework IOKit")
|
||||
elseif(UNIX)
|
||||
list(APPEND OLIVE_LIBRARIES Qt${QT_VERSION_MAJOR}::DBus)
|
||||
endif()
|
||||
|
||||
# Determine version from git: the tag name if HEAD is exactly on a tag,
|
||||
# otherwise the first 8 hex digits of the commit hash. Falls back to
|
||||
# version.txt (read above) when git is unavailable (e.g. tarball).
|
||||
set(PROJECT_LONG_VERSION ${PROJECT_VERSION})
|
||||
if(EXISTS "${CMAKE_SOURCE_DIR}/.git")
|
||||
find_package(Git)
|
||||
if(GIT_FOUND)
|
||||
execute_process(COMMAND ${GIT_EXECUTABLE} describe --exact-match --tags HEAD
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE GIT_TAG
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_QUIET
|
||||
)
|
||||
|
||||
if(GIT_TAG)
|
||||
# Tags are named like "v0.4.1-alpha"; drop the leading "v"
|
||||
string(REGEX REPLACE "^v" "" PROJECT_VERSION "${GIT_TAG}")
|
||||
else()
|
||||
execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse --short=8 HEAD
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE PROJECT_VERSION
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
endif()
|
||||
|
||||
set(PROJECT_LONG_VERSION ${PROJECT_VERSION})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Optional: Find Doxygen if requested
|
||||
if(BUILD_DOXYGEN)
|
||||
find_package(Doxygen)
|
||||
endif()
|
||||
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
list(APPEND OLIVE_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/third_party)
|
||||
|
||||
# Google Test discovery (shared by core/tests, engine/tests, tests/)
|
||||
if (BUILD_TESTS)
|
||||
include(FetchContent)
|
||||
find_package(GTest QUIET)
|
||||
if (NOT GTest_FOUND)
|
||||
FetchContent_Declare(
|
||||
googletest
|
||||
URL https://github.com/google/googletest/archive/refs/tags/v1.15.2.zip
|
||||
)
|
||||
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
|
||||
FetchContent_MakeAvailable(googletest)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_subdirectory(core)
|
||||
|
||||
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(third_party/openfx/HostSupport)
|
||||
add_subdirectory(engine)
|
||||
add_subdirectory(cli)
|
||||
|
||||
# Internal consumers (app, tests) link oakengine-obj directly instead of the
|
||||
# oakengine shared library (see app/CMakeLists.txt). Linking both breaks the
|
||||
# Windows build: the DLL import library re-defines every symbol already
|
||||
# provided by the object files (multiple definition errors at link time).
|
||||
|
||||
add_subdirectory(app)
|
||||
add_subdirectory(worker)
|
||||
|
||||
if (BUILD_TESTS)
|
||||
enable_testing()
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# CPack / system package configuration
|
||||
# ------------------------------------------------------------------------------
|
||||
set(CPACK_PACKAGE_NAME "oak-video-editor")
|
||||
set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION})
|
||||
set(CPACK_PACKAGE_VENDOR "Oak Video Editor Team")
|
||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Oak - Non-linear video editor")
|
||||
set(CPACK_PACKAGE_HOMEPAGE_URL "https://oakvideoeditor.org")
|
||||
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE")
|
||||
set(CPACK_PACKAGING_INSTALL_PREFIX "/usr")
|
||||
|
||||
# Debian
|
||||
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Oak Video Editor Team")
|
||||
set(CPACK_DEBIAN_FILE_NAME "DEB-DEFAULT")
|
||||
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS OFF)
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS
|
||||
"libqt6core6, libqt6gui6, libqt6widgets6, libqt6opengl6, libqt6openglwidgets6, libqt6network6, libqt6concurrent6, libavcodec60, libavformat60, libavutil58, libswscale7, libswresample4, libavfilter9, libopenimageio2.4, libopencolorio2, libopenexr-3-1-30, libexpat1, libportaudio2, libgl1, libvulkan1, libxkbcommon0")
|
||||
|
||||
# RPM
|
||||
set(CPACK_RPM_PACKAGE_LICENSE "GPLv3")
|
||||
set(CPACK_RPM_PACKAGE_GROUP "Applications/Multimedia")
|
||||
set(CPACK_RPM_PACKAGE_URL "https://oakvideoeditor.org")
|
||||
set(CPACK_RPM_PACKAGE_REQUIRES
|
||||
"qt6-qtbase >= 6.0, qt6-qtbase-gui >= 6.0, qt6-qttools, ffmpeg-libs >= 6.0, OpenImageIO >= 2.4, OpenColorIO >= 2.0, openexr >= 3.1, expat, portaudio, mesa-libGL, vulkan-loader, libxkbcommon")
|
||||
|
||||
include(CPack)
|
||||
|
||||
+1
-21
@@ -13,24 +13,4 @@ implementation details.
|
||||
### Code Standards
|
||||
|
||||
In order to keep the code as readable and maintainable as possible, code
|
||||
submitted should abide by the following standards:
|
||||
|
||||
* The code style generally follows the
|
||||
[Linux Kernel Coding Style](https://www.kernel.org/doc/html/latest/process/coding-style.html)
|
||||
with the following project-specific exceptions and notes:
|
||||
* Indentation uses **tabs**, not spaces.
|
||||
* Documentation comments should use **Javadoc-style** (`/** ... */`) where appropriate.
|
||||
* Naming rules (enforced by `readability-identifier-naming` in `.clang-tidy`):
|
||||
* Types (`class`, `struct`, `enum`, type aliases, template parameters): `PascalCase`
|
||||
* `typedef` of structs is permitted (e.g. the opaque-handle pattern `typedef struct OakEngineNode OakEngineNode;`); struct typedefs follow `PascalCase`
|
||||
* Functions, variables, member variables: `snake_case`
|
||||
* Private/protected members: trailing underscore, `class_member_variables_`
|
||||
* Constants and enum values: `snake_case` (e.g. `k_dry_run_interval`, `k_linear`); `ALL_CAPS` is reserved for macros — save the fear for things that are actually dangerous
|
||||
* Macros: `OAK_ALL_CAPS` (project prefix), and avoid them when a constant or function will do
|
||||
* File names: all lowercase, `mystring.h` / `mystring.cpp`
|
||||
* Namespaces: short `snake_case`
|
||||
* Getters: same name as the private member without the trailing underscore (`foo_` → `foo()`); setters: `set_foo()`
|
||||
* Exception: Qt and third-party (e.g. OpenFX) virtual overrides and framework callbacks keep their original names (`paintEvent`, `getParams`, ...) — renaming them would break the override
|
||||
* Tests are written with **Google Test** (`TEST`/`TEST_F`/`TEST_P` + `EXPECT_*`/`ASSERT_*`). Do not add hand-written test `main()`s, raw `assert()`-based test files, or custom test macros/frameworks. CTest stays the runner only — register cases through `gtest_discover_tests()`; use `GTEST_SKIP()` for environment-dependent cases (GPU, missing codecs) instead of relying on crashes or timeouts..
|
||||
* 100 column limit (where it doesn't impair readability)
|
||||
* Unix line endings (only LF no CRLF)
|
||||
submitted should be formatted using cargo fmt.
|
||||
Generated
+9800
File diff suppressed because it is too large
Load Diff
+62
@@ -0,0 +1,62 @@
|
||||
# Oak Video Editor - Non-Linear Video Editor
|
||||
# Copyright (C) 2026 Oak 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/>.
|
||||
|
||||
# Root manifest: the Cargo workspace only — every package lives under
|
||||
# crates/ (the GUI app in oak-app, the CLI in oak-cli, the render worker
|
||||
# in oak-worker, the module rlibs in oak-*) or examples/ (standalone
|
||||
# integration crates, each with its own Cargo.toml).
|
||||
#
|
||||
# gpui (the oak-gpui fork at gpui/) is excluded: it is a separate git
|
||||
# repository with its own workspace (resolver 3, edition 2024,
|
||||
# workspace.package/workspace.dependencies). Without the exclusion its
|
||||
# crates would be auto-included here via oak-app's path dependencies and
|
||||
# would inherit from THIS workspace's [workspace.package] (which lacks the
|
||||
# keys gpui expects). Excluded, each gpui crate resolves against gpui's own
|
||||
# workspace root, exactly as before the monorepo workspace existed.
|
||||
[workspace]
|
||||
members = ["crates/*", "examples/*"]
|
||||
exclude = ["gpui", "crates/oakengine.bk"]
|
||||
# NOTE: oak-storage is a workspace member but NOT a default member (it
|
||||
# stays out of the default-members test matrix to keep `cargo test` at
|
||||
# the root fast; the app links it as a normal path dependency, so it
|
||||
# builds with the app). Build/test it explicitly with `cargo test -p oak-storage`.
|
||||
# NOTE: `crates/oakengine` (the frozen C-ABI facade cdylib) is retired:
|
||||
# every consumer (app/cli/worker/plugins) links the module rlibs
|
||||
# directly, so nothing in the workspace referenced it. The sources are
|
||||
# kept at crates/oakengine.bk (excluded from the workspace) as a
|
||||
# reference snapshot; git history is the authoritative backup.
|
||||
default-members = ["crates/oak-app", "crates/oak-cli", "crates/oak-worker"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
# Single source of truth for the release version: the app package
|
||||
# inherits it, and with it cargo-packager and the CD packaging scripts.
|
||||
version = "0.5.0"
|
||||
|
||||
[profile.release]
|
||||
# FFI discipline: every module crate exports an `extern "C"` ABI whose
|
||||
# entry points must never unwind/abort across the boundary; panics are
|
||||
# caught by catch_unwind and mapped to error codes instead. `unwind` is
|
||||
# also rustc's default, but this makes the project-wide policy explicit
|
||||
# (it used to live in each member's Cargo.toml, which a workspace root
|
||||
# ignores).
|
||||
panic = "unwind"
|
||||
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 1
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 1
|
||||
@@ -23,6 +23,11 @@ The binary can be downloaded here:
|
||||
|
||||
See [`docs/build.md`](docs/build.md) for build instructions on Windows (MSYS2), Linux (Debian/Ubuntu, Fedora, Arch Linux), and macOS.
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Project Storage Architecture](docs/project-storage.md) ([中文](docs/zh/project-storage.md)) — database write-through persistence, node-granular journal, persistent undo
|
||||
- [Build guide](docs/build.md) · [工程文件格式](docs/zh/project-file-reference.md)
|
||||
|
||||
## Roadmap
|
||||
|
||||
| Version | Theme | Core Deliverables | Boundary Notes |
|
||||
|
||||
@@ -1,221 +0,0 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive Team
|
||||
# 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/>.
|
||||
|
||||
# Set Olive sources and resources
|
||||
set(OLIVE_SOURCES
|
||||
core.h
|
||||
core.cpp
|
||||
engineeventbridge.h
|
||||
engineeventbridge.cpp
|
||||
common/colorcodingapp.h
|
||||
common/colorcodingapp.cpp
|
||||
common/nodevaluehandle.h
|
||||
playback/playbackcontroller.h
|
||||
playback/playbackcontroller.cpp
|
||||
)
|
||||
|
||||
#set(OLIVE_RESOURCES)
|
||||
|
||||
# Add subdirectories, which will populate the above variables
|
||||
add_subdirectory(dialog)
|
||||
add_subdirectory(packaging)
|
||||
add_subdirectory(panel)
|
||||
add_subdirectory(timeline)
|
||||
add_subdirectory(ts)
|
||||
add_subdirectory(ui)
|
||||
add_subdirectory(widget)
|
||||
add_subdirectory(window)
|
||||
|
||||
# Add translations
|
||||
qt_add_translation(OLIVE_QM_FILES ${OLIVE_TS_FILES})
|
||||
|
||||
set(QRC_BODY "")
|
||||
foreach (QM_FILE ${OLIVE_QM_FILES})
|
||||
get_filename_component(QM_FILENAME_COMPONENT ${QM_FILE} NAME_WE)
|
||||
string(APPEND QRC_BODY "<file alias=\"${QM_FILENAME_COMPONENT}\">${QM_FILE}</file>\n")
|
||||
endforeach ()
|
||||
configure_file(ts/translations.qrc.in ts/translations.qrc @ONLY)
|
||||
|
||||
set(OLIVE_RESOURCES
|
||||
${OLIVE_RESOURCES}
|
||||
${CMAKE_CURRENT_BINARY_DIR}/ts/translations.qrc
|
||||
widget/nodeparamview/nodeparambutton.cpp
|
||||
widget/nodeparamview/nodeparambutton.h
|
||||
)
|
||||
|
||||
# Add main library
|
||||
add_library(libolive-editor
|
||||
OBJECT
|
||||
${OLIVE_SOURCES}
|
||||
${OLIVE_RESOURCES}
|
||||
)
|
||||
|
||||
target_compile_features(libolive-editor PUBLIC cxx_std_23)
|
||||
include_directories(../third_party/openfx/include
|
||||
../third_party/openfx/HostSupport/include)
|
||||
# Remove prefix - prevents CMake calling it "liblibolive-editor"
|
||||
set_target_properties(libolive-editor PROPERTIES PREFIX "")
|
||||
|
||||
option(OAK_ENABLE_DYNAMIC_RENDER_BACKEND "Build and use the dynamic render backend adapter" ON)
|
||||
if (OAK_ENABLE_DYNAMIC_RENDER_BACKEND)
|
||||
set_target_properties(libolive-editor PROPERTIES POSITION_INDEPENDENT_CODE ON)
|
||||
target_compile_definitions(libolive-editor PRIVATE OAK_ENABLE_DYNAMIC_RENDER_BACKEND)
|
||||
|
||||
foreach (target olivecore kddockwidgets)
|
||||
if (TARGET ${target})
|
||||
set_target_properties(${target} PROPERTIES POSITION_INDEPENDENT_CODE ON)
|
||||
endif ()
|
||||
endforeach ()
|
||||
endif ()
|
||||
|
||||
# Add application
|
||||
add_executable(olive-editor
|
||||
main.cpp
|
||||
$<TARGET_OBJECTS:libolive-editor>
|
||||
$<TARGET_OBJECTS:olive-version-obj>
|
||||
)
|
||||
|
||||
if (COMMAND oak_copy_otio_runtime)
|
||||
oak_copy_otio_runtime(olive-editor)
|
||||
endif()
|
||||
|
||||
target_link_libraries(olive-editor PUBLIC OfxHost)
|
||||
add_dependencies(olive-editor oakgl-cabi-check)
|
||||
if (OAK_ENABLE_DYNAMIC_RENDER_BACKEND)
|
||||
add_dependencies(olive-editor oakgl)
|
||||
if (TARGET oakvulkan)
|
||||
add_dependencies(olive-editor oakvulkan)
|
||||
endif ()
|
||||
endif ()
|
||||
set_target_properties(olive-editor PROPERTIES OUTPUT_NAME "oak-editor")
|
||||
|
||||
# Create docs if doxygen was found
|
||||
if (DOXYGEN_FOUND)
|
||||
set(DOXYGEN_PROJECT_NAME "Oak Video Editor")
|
||||
set(DOXYGEN_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/docs")
|
||||
set(DOXYGEN_EXTRACT_ALL "YES")
|
||||
set(DOXYGEN_EXTRACT_PRIVATE "YES")
|
||||
doxygen_add_docs(docs ALL ${OLIVE_SOURCES})
|
||||
endif ()
|
||||
|
||||
# Platform-specific deployment preferences
|
||||
if (WIN32)
|
||||
# Set Windows application icon
|
||||
target_sources(olive-editor PRIVATE packaging/windows/resources.rc)
|
||||
|
||||
# Preserve folder structure in visual studio
|
||||
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${OLIVE_SOURCES})
|
||||
|
||||
elseif (APPLE)
|
||||
# Set Mac application icon
|
||||
set(OLIVE_ICON packaging/macos/oak.icns)
|
||||
target_sources(olive-editor PRIVATE ${OLIVE_ICON})
|
||||
|
||||
# Set Mac bundle properties
|
||||
set_target_properties(olive-editor PROPERTIES
|
||||
MACOSX_BUNDLE TRUE
|
||||
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/packaging/macos/MacOSXBundleInfo.plist.in
|
||||
MACOSX_BUNDLE_GUI_IDENTIFIER org.oakvideoeditor.Oak
|
||||
MACOSX_BUNDLE_ICON_FILE oak.icns
|
||||
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
|
||||
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION}
|
||||
MACOSX_BUNDLE_BUNDLE_NAME "Oak Video Editor"
|
||||
MACOSX_BUNDLE_INFO_STRING "Oak Video Editor ${PROJECT_LONG_VERSION}"
|
||||
MACOSX_BUNDLE_COPYRIGHT "©2018-2021 Olive Studios LLC and others. Fork maintained by Oak Video Editor Team."
|
||||
RESOURCE "${OLIVE_ICON}"
|
||||
OUTPUT_NAME "Oak"
|
||||
)
|
||||
|
||||
# Copy the render worker, dynamic render backends, and the FFmpeg bridge
|
||||
# library into the app bundle. They are looked up in
|
||||
# QCoreApplication::applicationDirPath(), which on macOS points to
|
||||
# Oak.app/Contents/MacOS.
|
||||
add_custom_command(TARGET olive-editor POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory $<TARGET_BUNDLE_DIR:olive-editor>/Contents/MacOS
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:olive-render-worker> $<TARGET_BUNDLE_DIR:olive-editor>/Contents/MacOS/
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:oakgl> $<TARGET_BUNDLE_DIR:olive-editor>/Contents/MacOS/
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:ffmpeg_bridge> $<TARGET_BUNDLE_DIR:olive-editor>/Contents/MacOS/
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:olivecore> $<TARGET_BUNDLE_DIR:olive-editor>/Contents/MacOS/
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:oakengine> $<TARGET_BUNDLE_DIR:olive-editor>/Contents/MacOS/
|
||||
COMMENT "Copying oak-render-worker, render backends, ffmpeg_bridge, liboakcore and liboakengine into Oak.app"
|
||||
)
|
||||
if (TARGET oakvulkan)
|
||||
add_custom_command(TARGET olive-editor POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:oakvulkan> $<TARGET_BUNDLE_DIR:olive-editor>/Contents/MacOS/
|
||||
)
|
||||
endif ()
|
||||
elseif (UNIX)
|
||||
# Set Linux-specific properties for application
|
||||
install(TARGETS olive-editor RUNTIME DESTINATION bin)
|
||||
endif ()
|
||||
|
||||
if (WIN32)
|
||||
# Windows has no RPATH: shared libraries must sit next to the executable
|
||||
add_custom_command(TARGET olive-editor POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:ffmpeg_bridge> $<TARGET_FILE_DIR:olive-editor>
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:olivecore> $<TARGET_FILE_DIR:olive-editor>
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:oakengine> $<TARGET_FILE_DIR:olive-editor>
|
||||
)
|
||||
endif ()
|
||||
|
||||
# Set link libraries
|
||||
target_link_libraries(olive-editor PRIVATE ${OLIVE_LIBRARIES})
|
||||
target_link_libraries(libolive-editor PRIVATE ${OLIVE_LIBRARIES})
|
||||
# The app is an internal consumer that still uses engine C++ classes directly.
|
||||
# Link the object library to bypass the version-script restrictions on
|
||||
# liboakengine.so (which only exposes the C ABI for external consumers).
|
||||
target_link_libraries(olive-editor PRIVATE oakengine-obj)
|
||||
target_link_libraries(libolive-editor PRIVATE oakengine-obj)
|
||||
|
||||
# The ffmpeg_bridge shared library ships next to the binaries: inside the
|
||||
# macOS app bundle (Contents/MacOS, resolved via @loader_path), and in the
|
||||
# standard lib directory on other platforms.
|
||||
if (APPLE)
|
||||
# macOS bundles are distributed via POST_BUILD copies (not install()), so
|
||||
# @loader_path must already be in the build-tree binaries' RPATH.
|
||||
set(OLIVE_FB_RPATH "@loader_path")
|
||||
set_target_properties(olive-editor PROPERTIES
|
||||
BUILD_RPATH "@loader_path")
|
||||
elseif (UNIX)
|
||||
set(OLIVE_FB_RPATH "$ORIGIN/../lib")
|
||||
endif ()
|
||||
if (OLIVE_FB_RPATH)
|
||||
set_target_properties(olive-editor PROPERTIES INSTALL_RPATH "${OLIVE_FB_RPATH}")
|
||||
if (TARGET oakgl)
|
||||
set_target_properties(oakgl PROPERTIES INSTALL_RPATH "${OLIVE_FB_RPATH}")
|
||||
endif ()
|
||||
if (TARGET oakvulkan)
|
||||
set_target_properties(oakvulkan PROPERTIES INSTALL_RPATH "${OLIVE_FB_RPATH}")
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
# Set compile options
|
||||
target_compile_options(olive-editor PRIVATE ${OLIVE_COMPILE_OPTIONS})
|
||||
target_compile_options(libolive-editor PRIVATE ${OLIVE_COMPILE_OPTIONS})
|
||||
|
||||
# Set global definitions
|
||||
target_compile_definitions(olive-editor PRIVATE ${OLIVE_DEFINITIONS})
|
||||
target_compile_definitions(libolive-editor PRIVATE ${OLIVE_DEFINITIONS})
|
||||
|
||||
# Set include dirs
|
||||
target_include_directories(olive-editor PRIVATE ${OLIVE_INCLUDE_DIRS})
|
||||
target_include_directories(libolive-editor PRIVATE ${OLIVE_INCLUDE_DIRS})
|
||||
|
||||
# Add crash handler
|
||||
if (GoogleCrashpad_FOUND AND Qt${QT_VERSION_MAJOR}Network_FOUND)
|
||||
add_subdirectory(crashhandler)
|
||||
endif ()
|
||||
@@ -1,81 +0,0 @@
|
||||
/***
|
||||
|
||||
Oak - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak 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/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "common/colorcodingapp.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
QVector<Color> AppColorCoding::colors = {
|
||||
Color(0.545f, 0.255f, 0.255f), Color(0.412f, 0.188f, 0.259f),
|
||||
Color(0.561f, 0.427f, 0.239f), Color(0.486f, 0.306f, 0.235f),
|
||||
Color(0.631f, 0.612f, 0.212f), Color(0.404f, 0.478f, 0.243f),
|
||||
Color(0.349f, 0.576f, 0.275f), Color(0.224f, 0.459f, 0.251f),
|
||||
Color(0.259f, 0.471f, 0.541f), Color(0.184f, 0.376f, 0.329f),
|
||||
Color(0.259f, 0.365f, 0.541f), Color(0.196f, 0.216f, 0.412f),
|
||||
Color(0.612f, 0.294f, 0.502f), Color(0.404f, 0.220f, 0.459f),
|
||||
Color(0.800f, 0.800f, 0.800f), Color(0.502f, 0.502f, 0.502f)
|
||||
};
|
||||
|
||||
const QVector<Color> &AppColorCoding::standard_colors()
|
||||
{
|
||||
return colors;
|
||||
}
|
||||
|
||||
QString AppColorCoding::get_color_name(int c)
|
||||
{
|
||||
switch (c) {
|
||||
case k_red: return QObject::tr("Red");
|
||||
case k_maroon: return QObject::tr("Maroon");
|
||||
case k_orange: return QObject::tr("Orange");
|
||||
case k_brown: return QObject::tr("Brown");
|
||||
case k_yellow: return QObject::tr("Yellow");
|
||||
case k_olive: return QObject::tr("Oak");
|
||||
case k_lime: return QObject::tr("Lime");
|
||||
case k_green: return QObject::tr("Green");
|
||||
case k_cyan: return QObject::tr("Cyan");
|
||||
case k_teal: return QObject::tr("Teal");
|
||||
case k_blue: return QObject::tr("Blue");
|
||||
case k_navy: return QObject::tr("Navy");
|
||||
case k_pink: return QObject::tr("Pink");
|
||||
case k_purple: return QObject::tr("Purple");
|
||||
case k_silver: return QObject::tr("Silver");
|
||||
case k_gray: return QObject::tr("Gray");
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
Color AppColorCoding::get_color(int c)
|
||||
{
|
||||
return colors.at(c);
|
||||
}
|
||||
|
||||
Qt::GlobalColor AppColorCoding::get_ui_selector_color(const Color &c)
|
||||
{
|
||||
if (c.get_rough_luminance() > 0.40f) {
|
||||
return Qt::black;
|
||||
} else {
|
||||
return Qt::white;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
/***
|
||||
|
||||
Oak - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak 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 OAK_COLORCODINGAPP_H
|
||||
#define OAK_COLORCODINGAPP_H
|
||||
|
||||
#include <olive/core/core.h>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
using namespace core;
|
||||
|
||||
/**
|
||||
* @brief App-side color label mapping (moved from engine/ui/colorcoding.h)
|
||||
*
|
||||
* Provides the same static color-label mapping as the engine version but
|
||||
* without QObject inheritance (no moc symbols). Only the static methods
|
||||
* used by app code are included.
|
||||
*/
|
||||
class AppColorCoding {
|
||||
public:
|
||||
enum Code {
|
||||
k_red,
|
||||
k_maroon,
|
||||
k_orange,
|
||||
k_brown,
|
||||
k_yellow,
|
||||
k_olive,
|
||||
k_lime,
|
||||
k_green,
|
||||
k_cyan,
|
||||
k_teal,
|
||||
k_blue,
|
||||
k_navy,
|
||||
k_pink,
|
||||
k_purple,
|
||||
k_silver,
|
||||
k_gray
|
||||
};
|
||||
|
||||
static QString get_color_name(int c);
|
||||
|
||||
static Color get_color(int c);
|
||||
|
||||
static Qt::GlobalColor get_ui_selector_color(const Color &c);
|
||||
|
||||
static const QVector<Color> &standard_colors();
|
||||
|
||||
private:
|
||||
static QVector<Color> colors;
|
||||
};
|
||||
|
||||
} // namespace olive
|
||||
|
||||
#endif // OAK_COLORCODINGAPP_H
|
||||
@@ -1,213 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 OAK_CONFIGWRAPPER_H
|
||||
#define OAK_CONFIGWRAPPER_H
|
||||
|
||||
#include <QVariant>
|
||||
|
||||
#include "olive/core/util/rational.h"
|
||||
#include "oakengine/config.h"
|
||||
|
||||
// Facade migration B9b: replace the engine's OAK_CONFIG macro (which
|
||||
// references olive::Config::current()/operator[] and brings C++ symbols into
|
||||
// the editor binary) with a thin header-only wrapper around the C ABI.
|
||||
//
|
||||
// Include this header instead of "config/config.h" in app code. It undefines
|
||||
// the engine macros and redefines them to return an inline OakConfigValue that
|
||||
// forwards reads/writes to oakengine_config_*().
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class OakConfigValue {
|
||||
public:
|
||||
explicit OakConfigValue(const QString &key) : key_(key) {}
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return oakengine_config_get_int(key_utf8(), 0) != 0;
|
||||
}
|
||||
operator int() const
|
||||
{
|
||||
return static_cast<int>(oakengine_config_get_int(key_utf8(), 0));
|
||||
}
|
||||
operator qint64() const
|
||||
{
|
||||
return static_cast<qint64>(oakengine_config_get_int(key_utf8(), 0));
|
||||
}
|
||||
operator quint64() const
|
||||
{
|
||||
return static_cast<quint64>(oakengine_config_get_int(key_utf8(), 0));
|
||||
}
|
||||
// int64_t/uint64_t overloads only exist where they differ from
|
||||
// qint64/quint64 (Linux LP64: int64_t is long; on macOS/Windows both are
|
||||
// long long, where declaring them would be a redeclaration).
|
||||
#if defined(__linux__)
|
||||
operator int64_t() const
|
||||
{
|
||||
return oakengine_config_get_int(key_utf8(), 0);
|
||||
}
|
||||
operator uint64_t() const
|
||||
{
|
||||
return static_cast<uint64_t>(oakengine_config_get_int(key_utf8(), 0));
|
||||
}
|
||||
#endif
|
||||
operator QString() const
|
||||
{
|
||||
char buf[1024];
|
||||
const int len = oakengine_config_get_string(key_utf8(), buf,
|
||||
sizeof(buf));
|
||||
return QString::fromUtf8(buf, len);
|
||||
}
|
||||
operator QVariant() const
|
||||
{
|
||||
return QVariant(static_cast<QString>(*this));
|
||||
}
|
||||
|
||||
OakConfigValue &operator=(bool v)
|
||||
{
|
||||
oakengine_config_set_int(key_utf8(), v ? 1 : 0);
|
||||
return *this;
|
||||
}
|
||||
OakConfigValue &operator=(int v)
|
||||
{
|
||||
oakengine_config_set_int(key_utf8(), static_cast<int64_t>(v));
|
||||
return *this;
|
||||
}
|
||||
OakConfigValue &operator=(uint v)
|
||||
{
|
||||
oakengine_config_set_int(key_utf8(), static_cast<int64_t>(v));
|
||||
return *this;
|
||||
}
|
||||
OakConfigValue &operator=(qint64 v)
|
||||
{
|
||||
oakengine_config_set_int(key_utf8(), static_cast<int64_t>(v));
|
||||
return *this;
|
||||
}
|
||||
OakConfigValue &operator=(quint64 v)
|
||||
{
|
||||
oakengine_config_set_int(key_utf8(), static_cast<int64_t>(v));
|
||||
return *this;
|
||||
}
|
||||
#if defined(__linux__)
|
||||
OakConfigValue &operator=(int64_t v)
|
||||
{
|
||||
oakengine_config_set_int(key_utf8(), v);
|
||||
return *this;
|
||||
}
|
||||
OakConfigValue &operator=(uint64_t v)
|
||||
{
|
||||
oakengine_config_set_int(key_utf8(), static_cast<int64_t>(v));
|
||||
return *this;
|
||||
}
|
||||
#endif
|
||||
OakConfigValue &operator=(const QString &v)
|
||||
{
|
||||
const QByteArray utf8 = v.toUtf8();
|
||||
oakengine_config_set_string(key_utf8(), utf8.constData());
|
||||
return *this;
|
||||
}
|
||||
OakConfigValue &operator=(const char *v)
|
||||
{
|
||||
oakengine_config_set_string(key_utf8(), v ? v : "");
|
||||
return *this;
|
||||
}
|
||||
OakConfigValue &operator=(const QVariant &v)
|
||||
{
|
||||
switch (v.typeId()) {
|
||||
case QMetaType::Bool:
|
||||
*this = v.toBool();
|
||||
break;
|
||||
case QMetaType::Int:
|
||||
case QMetaType::UInt:
|
||||
case QMetaType::LongLong:
|
||||
case QMetaType::ULongLong:
|
||||
case QMetaType::Long:
|
||||
case QMetaType::Short:
|
||||
case QMetaType::Char:
|
||||
case QMetaType::ULong:
|
||||
case QMetaType::UShort:
|
||||
case QMetaType::UChar:
|
||||
*this = v.toLongLong();
|
||||
break;
|
||||
case QMetaType::Double:
|
||||
case QMetaType::Float:
|
||||
*this = static_cast<int64_t>(v.toDouble());
|
||||
break;
|
||||
default:
|
||||
*this = v.toString();
|
||||
break;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool toBool() const { return static_cast<bool>(*this); }
|
||||
int toInt() const { return static_cast<int>(*this); }
|
||||
qint64 toLongLong() const { return static_cast<qint64>(*this); }
|
||||
quint64 toULongLong() const { return static_cast<quint64>(*this); }
|
||||
QString toString() const { return static_cast<QString>(*this); }
|
||||
|
||||
bool operator==(int rhs) const { return toInt() == rhs; }
|
||||
bool operator!=(int rhs) const { return toInt() != rhs; }
|
||||
bool operator==(qint64 rhs) const { return toLongLong() == rhs; }
|
||||
bool operator!=(qint64 rhs) const { return toLongLong() != rhs; }
|
||||
bool operator==(const QString &rhs) const { return toString() == rhs; }
|
||||
bool operator!=(const QString &rhs) const { return toString() != rhs; }
|
||||
bool operator==(const char *rhs) const { return toString() == QString::fromUtf8(rhs); }
|
||||
bool operator!=(const char *rhs) const { return toString() != QString::fromUtf8(rhs); }
|
||||
|
||||
template <typename T> T value() const
|
||||
{
|
||||
if constexpr (std::is_same_v<T, olive::core::Rational>) {
|
||||
const QString s = static_cast<QString>(*this);
|
||||
const QByteArray utf8 = s.toUtf8();
|
||||
return olive::core::Rational::from_string(
|
||||
std::string(utf8.constData(), size_t(utf8.size())));
|
||||
} else {
|
||||
return static_cast<T>(*this);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const char *key_utf8() const
|
||||
{
|
||||
key_utf8_ = key_.toUtf8();
|
||||
return key_utf8_.constData();
|
||||
}
|
||||
|
||||
QString key_;
|
||||
mutable QByteArray key_utf8_;
|
||||
};
|
||||
|
||||
} // namespace olive
|
||||
|
||||
#ifdef OAK_CONFIG
|
||||
#undef OAK_CONFIG
|
||||
#endif
|
||||
#ifdef OAK_CONFIG_STR
|
||||
#undef OAK_CONFIG_STR
|
||||
#endif
|
||||
|
||||
#define OAK_CONFIG(x) olive::OakConfigValue(QStringLiteral(x))
|
||||
#define OAK_CONFIG_STR(x) olive::OakConfigValue(x)
|
||||
|
||||
#endif // OAK_CONFIGWRAPPER_H
|
||||
@@ -1,87 +0,0 @@
|
||||
/***
|
||||
|
||||
Oak - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak 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 OAK_DEBUGAPP_H
|
||||
#define OAK_DEBUGAPP_H
|
||||
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QDir>
|
||||
#include <QDateTime>
|
||||
#include <QMutex>
|
||||
#include <QTextStream>
|
||||
#include <iostream>
|
||||
|
||||
namespace olive {
|
||||
|
||||
/**
|
||||
* @brief App-side debug handler (moved from engine/common/debug.cpp)
|
||||
*
|
||||
* Replaces engine's olive::debug_handler so oak-editor doesn't import
|
||||
* that symbol. Only used in main.cpp's qInstallMessageHandler.
|
||||
*/
|
||||
[[maybe_unused]] [[maybe_unused]] static void debug_handler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
|
||||
{
|
||||
// Suppress noisy warnings from Qt's QXcbIntegration
|
||||
if (type == QtWarningMsg && msg.contains("QXcbIntegration")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Suppress all Qt warnings during automated testing
|
||||
static const bool is_testing = qEnvironmentVariableIsSet("OAK_TESTING");
|
||||
if (is_testing && type == QtWarningMsg) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString log_line;
|
||||
|
||||
switch (type) {
|
||||
case QtDebugMsg:
|
||||
log_line = QStringLiteral("Debug: %1 (%2:%3, %4)\n");
|
||||
break;
|
||||
case QtInfoMsg:
|
||||
log_line = QStringLiteral("Info: %1 (%2:%3, %4)\n");
|
||||
break;
|
||||
case QtWarningMsg:
|
||||
log_line = QStringLiteral("Warning: %1 (%2:%3, %4)\n");
|
||||
break;
|
||||
case QtCriticalMsg:
|
||||
log_line = QStringLiteral("Critical: %1 (%2:%3, %4)\n");
|
||||
break;
|
||||
case QtFatalMsg:
|
||||
log_line = QStringLiteral("Fatal: %1 (%2:%3, %4)\n");
|
||||
break;
|
||||
}
|
||||
|
||||
log_line = log_line.arg(msg, context.file != nullptr ? context.file : "<null>",
|
||||
QString::number(context.line), context.function != nullptr ?
|
||||
context.function : "<null>");
|
||||
|
||||
std::cerr << log_line.toUtf8().constData();
|
||||
|
||||
if (type == QtFatalMsg) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace olive
|
||||
|
||||
#endif // OAK_DEBUGAPP_H
|
||||
@@ -1,98 +0,0 @@
|
||||
/***
|
||||
|
||||
Oak - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak 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/>.
|
||||
|
||||
***/
|
||||
|
||||
// App-side implementations of FileFunctions methods that would otherwise
|
||||
// be imported from liboakengine. The declarations live in the engine header
|
||||
// (common/filefunctions.h) which is on the public include path; these
|
||||
// definitions resolve the symbols locally in the app binary.
|
||||
|
||||
#include "oakutil/filefunctions.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QStandardPaths>
|
||||
#include <QTextStream>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
bool FileFunctions::directory_is_valid(const QDir &d,
|
||||
bool try_to_create_if_not_exists)
|
||||
{
|
||||
return d.exists() ||
|
||||
(try_to_create_if_not_exists && d.mkpath(QStringLiteral(".")));
|
||||
}
|
||||
|
||||
QString FileFunctions::read_file_as_string(const QString &filename)
|
||||
{
|
||||
QFile f(filename);
|
||||
QString file_data;
|
||||
if (f.open(QFile::ReadOnly | QFile::Text)) {
|
||||
QTextStream text_stream(&f);
|
||||
file_data = text_stream.readAll();
|
||||
f.close();
|
||||
}
|
||||
return file_data;
|
||||
}
|
||||
|
||||
QString FileFunctions::get_auto_recovery_root()
|
||||
{
|
||||
return QDir(QStandardPaths::writableLocation(
|
||||
QStandardPaths::AppLocalDataLocation))
|
||||
.filePath(QStringLiteral("autorecovery"));
|
||||
}
|
||||
|
||||
QString FileFunctions::ensure_filename_extension(QString fn,
|
||||
const QString &extension)
|
||||
{
|
||||
if (!fn.isEmpty() && !extension.isEmpty()) {
|
||||
QString extension_with_dot;
|
||||
extension_with_dot.append('.');
|
||||
extension_with_dot.append(extension);
|
||||
if (!fn.endsWith(extension_with_dot, Qt::CaseInsensitive)) {
|
||||
fn.append(extension_with_dot);
|
||||
}
|
||||
}
|
||||
return fn;
|
||||
}
|
||||
|
||||
QString FileFunctions::get_configuration_location()
|
||||
{
|
||||
if (is_portable()) {
|
||||
return get_application_path();
|
||||
} else {
|
||||
QString s = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||
QDir(s).mkpath(".");
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
bool FileFunctions::is_portable()
|
||||
{
|
||||
return QFileInfo::exists(QDir(get_application_path()).filePath("portable"));
|
||||
}
|
||||
|
||||
QString FileFunctions::get_application_path()
|
||||
{
|
||||
return QCoreApplication::applicationDirPath();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,507 +0,0 @@
|
||||
/*
|
||||
* Oak Video Editor - 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/>.
|
||||
*/
|
||||
|
||||
#include "htmlapp.h"
|
||||
|
||||
#include <QAbstractTextDocumentLayout>
|
||||
#include <QFont>
|
||||
#include <QTextBlock>
|
||||
#include <QTextBlockFormat>
|
||||
#include <QTextCharFormat>
|
||||
#include <QTextDocument>
|
||||
#include <QTextFragment>
|
||||
#include <QTextList>
|
||||
#include <QXmlStreamReader>
|
||||
#include <QXmlStreamWriter>
|
||||
|
||||
#include "oakutil/xmlutils.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QTextBlock>
|
||||
|
||||
#include "oakutil/xmlutils.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
const QVector<QString> Html::k_block_tags = { QStringLiteral("p"),
|
||||
QStringLiteral("div") };
|
||||
|
||||
inline bool str_equals(const QStringView &a, const QStringView &b)
|
||||
{
|
||||
return !a.compare(b, Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
QString Html::doc_to_html(const QTextDocument *doc)
|
||||
{
|
||||
QString html;
|
||||
QXmlStreamWriter writer(&html);
|
||||
|
||||
//writer.setAutoFormatting(true);
|
||||
|
||||
for (auto it = doc->begin(); it != doc->end(); it = it.next()) {
|
||||
write_block(&writer, it);
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
struct HtmlNode {
|
||||
QString tag;
|
||||
QTextCharFormat format;
|
||||
};
|
||||
|
||||
QTextCharFormat merge_html_formats(const QVector<HtmlNode> &stack)
|
||||
{
|
||||
QTextCharFormat f;
|
||||
|
||||
for (int i = 0; i < stack.size(); i++) {
|
||||
f.merge(stack.at(i).format);
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
void Html::html_to_doc(QTextDocument *doc, const QString &html)
|
||||
{
|
||||
// Empty doc
|
||||
doc->clear();
|
||||
bool inside_block = true;
|
||||
|
||||
// Create cursor, which appears to be Qt's official way of inserting blocks and fragments
|
||||
QTextCursor c(doc);
|
||||
|
||||
QString wrapped = QStringLiteral("<html>").append(html).append("</html>");
|
||||
QXmlStreamReader reader(wrapped);
|
||||
|
||||
QVector<HtmlNode> fmt_stack;
|
||||
|
||||
QTextCharFormat default_fmt;
|
||||
default_fmt.setFontWeight(QFont::Normal);
|
||||
fmt_stack.append({ QStringLiteral("html"), default_fmt });
|
||||
|
||||
QTextCharFormat current_fmt;
|
||||
|
||||
while (!reader.atEnd()) {
|
||||
reader.readNext();
|
||||
|
||||
if (reader.tokenType() == QXmlStreamReader::StartElement) {
|
||||
QString tag = reader.name().toString().toLower();
|
||||
|
||||
fmt_stack.append({ tag, read_char_format(reader.attributes()) });
|
||||
current_fmt = merge_html_formats(fmt_stack);
|
||||
|
||||
if (k_block_tags.contains(tag)) {
|
||||
QTextBlockFormat block_fmt =
|
||||
read_block_format(reader.attributes());
|
||||
if (inside_block) {
|
||||
c.setBlockFormat(block_fmt);
|
||||
c.setBlockCharFormat(current_fmt);
|
||||
} else {
|
||||
c.insertBlock(block_fmt, current_fmt);
|
||||
inside_block = true;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (reader.tokenType() == QXmlStreamReader::Characters) {
|
||||
QString characters = reader.text().toString();
|
||||
c.insertText(characters, current_fmt);
|
||||
|
||||
} else if (reader.tokenType() == QXmlStreamReader::EndElement) {
|
||||
QString tag = reader.name().toString().toLower();
|
||||
|
||||
for (int i = fmt_stack.size() - 1; i >= 0; i--) {
|
||||
if (fmt_stack.at(i).tag == tag) {
|
||||
fmt_stack.removeAt(i);
|
||||
current_fmt = merge_html_formats(fmt_stack);
|
||||
|
||||
if (k_block_tags.contains(tag)) {
|
||||
inside_block = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (reader.error()) {
|
||||
qCritical() << "Failed to parse HTML:" << reader.errorString();
|
||||
}
|
||||
}
|
||||
|
||||
void Html::write_block(QXmlStreamWriter *writer, const QTextBlock &block)
|
||||
{
|
||||
writer->writeStartElement(QStringLiteral("p"));
|
||||
|
||||
const QTextBlockFormat &fmt = block.blockFormat();
|
||||
|
||||
// Write block alignment
|
||||
if (!(fmt.alignment() & Qt::AlignLeft)) {
|
||||
if (fmt.alignment() & Qt::AlignRight) {
|
||||
writer->writeAttribute(QStringLiteral("align"),
|
||||
QStringLiteral("right"));
|
||||
} else if (fmt.alignment() & Qt::AlignHCenter) {
|
||||
writer->writeAttribute(QStringLiteral("align"),
|
||||
QStringLiteral("center"));
|
||||
} else if (fmt.alignment() & Qt::AlignJustify) {
|
||||
writer->writeAttribute(QStringLiteral("align"),
|
||||
QStringLiteral("justify"));
|
||||
}
|
||||
}
|
||||
|
||||
// RTL support
|
||||
if (block.textDirection() == Qt::RightToLeft) {
|
||||
writer->writeAttribute(QStringLiteral("dir"), QStringLiteral("rtl"));
|
||||
}
|
||||
|
||||
// Write CSS attributes
|
||||
QString style;
|
||||
|
||||
if (fmt.lineHeightType() != QTextBlockFormat::SingleHeight) {
|
||||
write_css_property(&style, QStringLiteral("line-height"),
|
||||
QStringLiteral("%1%").arg(fmt.lineHeight()));
|
||||
}
|
||||
|
||||
write_char_format(&style, block.charFormat());
|
||||
|
||||
if (!style.isEmpty()) {
|
||||
writer->writeAttribute(QStringLiteral("style"), style);
|
||||
}
|
||||
|
||||
auto it = block.begin();
|
||||
|
||||
if (it != block.end()) {
|
||||
for (; it != block.end(); it++) {
|
||||
write_fragment(writer, it.fragment());
|
||||
}
|
||||
}
|
||||
|
||||
writer->writeEndElement(); // p
|
||||
}
|
||||
|
||||
void Html::write_fragment(QXmlStreamWriter *writer,
|
||||
const QTextFragment &fragment)
|
||||
{
|
||||
const QTextCharFormat &fmt = fragment.charFormat();
|
||||
|
||||
writer->writeStartElement(QStringLiteral("span"));
|
||||
|
||||
// Write CSS attributes
|
||||
QString style;
|
||||
|
||||
write_char_format(&style, fmt);
|
||||
|
||||
if (!style.isEmpty()) {
|
||||
writer->writeAttribute(QStringLiteral("style"), style);
|
||||
}
|
||||
|
||||
QStringList lines = fragment.text().split(QChar::LineSeparator);
|
||||
bool first_line = true;
|
||||
foreach (const QString &l, lines) {
|
||||
if (first_line) {
|
||||
first_line = false;
|
||||
} else {
|
||||
writer->writeEmptyElement(QStringLiteral("br"));
|
||||
}
|
||||
writer->writeCharacters(l);
|
||||
}
|
||||
|
||||
writer->writeEndElement(); // span
|
||||
}
|
||||
|
||||
void Html::write_css_property(QString *style, const QString &key,
|
||||
const QStringList &values)
|
||||
{
|
||||
QString value;
|
||||
foreach (QString v, values) {
|
||||
if (v.contains(' ')) {
|
||||
v = QStringLiteral("'%1'").arg(v);
|
||||
}
|
||||
|
||||
append_string_auto_space(&value, v);
|
||||
}
|
||||
|
||||
append_string_auto_space(style, QStringLiteral("%1: %2;").arg(key, value));
|
||||
}
|
||||
|
||||
void Html::write_char_format(QString *style, const QTextCharFormat &fmt)
|
||||
{
|
||||
QStringList families = fmt.fontFamilies().toStringList();
|
||||
if (!families.isEmpty()) {
|
||||
write_css_property(style, QStringLiteral("font-family"),
|
||||
families.first());
|
||||
}
|
||||
|
||||
if (fmt.hasProperty(QTextFormat::FontPointSize)) {
|
||||
write_css_property(
|
||||
style, QStringLiteral("font-size"),
|
||||
QStringLiteral("%1pt").arg(QString::number(fmt.fontPointSize())));
|
||||
}
|
||||
|
||||
if (fmt.hasProperty(QTextFormat::FontWeight)) {
|
||||
write_css_property(style, QStringLiteral("font-weight"),
|
||||
QString::number(fmt.fontWeight() * 8));
|
||||
}
|
||||
|
||||
if (fmt.hasProperty(QTextFormat::FontItalic)) {
|
||||
write_css_property(style, QStringLiteral("font-style"),
|
||||
fmt.fontItalic() ? QStringLiteral("italic") :
|
||||
QStringLiteral("normal"));
|
||||
}
|
||||
|
||||
if (fmt.hasProperty(QTextFormat::FontStyleName)) {
|
||||
write_css_property(style, QStringLiteral("-ove-font-style"),
|
||||
fmt.fontStyleName().toString());
|
||||
}
|
||||
|
||||
QStringList deco;
|
||||
|
||||
if (fmt.fontUnderline()) {
|
||||
deco.append(QStringLiteral("underline"));
|
||||
}
|
||||
|
||||
if (fmt.fontStrikeOut()) {
|
||||
deco.append(QStringLiteral("line-through"));
|
||||
}
|
||||
|
||||
if (fmt.fontOverline()) {
|
||||
deco.append(QStringLiteral("overline"));
|
||||
}
|
||||
|
||||
if (!deco.isEmpty()) {
|
||||
write_css_property(style, QStringLiteral("text-decoration"), deco);
|
||||
}
|
||||
|
||||
if (fmt.foreground().style() != Qt::NoBrush) {
|
||||
const QColor &color = fmt.foreground().color();
|
||||
QString cs;
|
||||
|
||||
if (color.alpha() == 255) {
|
||||
cs = color.name();
|
||||
} else if (color.alpha()) {
|
||||
cs = QStringLiteral("rgba(%1, %2, %3, %4)")
|
||||
.arg(QString::number(color.red()),
|
||||
QString::number(color.green()),
|
||||
QString::number(color.blue()),
|
||||
QString::number(color.alphaF()));
|
||||
}
|
||||
|
||||
write_css_property(style, QStringLiteral("color"), cs);
|
||||
}
|
||||
|
||||
if (fmt.fontCapitalization() != QFont::MixedCase) {
|
||||
if (fmt.fontCapitalization() == QFont::SmallCaps) {
|
||||
write_css_property(style, QStringLiteral("font-variant"),
|
||||
QStringLiteral("small-caps"));
|
||||
// TODO: Add others
|
||||
}
|
||||
}
|
||||
|
||||
if (fmt.fontLetterSpacing() != 0.0) {
|
||||
write_css_property(style, QStringLiteral("letter-spacing"),
|
||||
QStringLiteral("%1%").arg(
|
||||
QString::number(fmt.fontLetterSpacing())));
|
||||
}
|
||||
|
||||
if (fmt.fontStretch() != 0) {
|
||||
write_css_property(
|
||||
style, QStringLiteral("font-stretch"),
|
||||
QStringLiteral("%1%").arg(QString::number(fmt.fontStretch())));
|
||||
}
|
||||
}
|
||||
|
||||
QTextCharFormat Html::read_char_format(const QXmlStreamAttributes &attributes)
|
||||
{
|
||||
QTextCharFormat fmt;
|
||||
|
||||
foreach (const QXmlStreamAttribute &attr, attributes) {
|
||||
if (str_equals(attr.name(), QStringLiteral("style"))) {
|
||||
auto css = get_css_from_style(attr.value().toString());
|
||||
|
||||
for (auto it = css.begin(); it != css.end(); it++) {
|
||||
const QString &first_val = it.value().first();
|
||||
|
||||
if (it.key() == QStringLiteral("font-family")) {
|
||||
fmt.setFontFamilies({ first_val });
|
||||
} else if (it.key() == QStringLiteral("font-size")) {
|
||||
if (first_val.endsWith(QStringLiteral("pt"),
|
||||
Qt::CaseInsensitive)) {
|
||||
fmt.setFontPointSize(first_val.chopped(2).toDouble());
|
||||
}
|
||||
} else if (it.key() == QStringLiteral("font-weight")) {
|
||||
fmt.setFontWeight(first_val.toInt() / 8);
|
||||
} else if (it.key() == QStringLiteral("font-style")) {
|
||||
fmt.setFontItalic(
|
||||
str_equals(first_val, QStringLiteral("italic")));
|
||||
} else if (it.key() == QStringLiteral("text-decoration")) {
|
||||
foreach (const QString &v, it.value()) {
|
||||
if (str_equals(v, QStringLiteral("underline"))) {
|
||||
fmt.setFontUnderline(true);
|
||||
} else if (str_equals(v,
|
||||
QStringLiteral("line-through"))) {
|
||||
fmt.setFontStrikeOut(true);
|
||||
} else if (str_equals(v, QStringLiteral("overline"))) {
|
||||
fmt.setFontOverline(true);
|
||||
}
|
||||
}
|
||||
} else if (it.key() == QStringLiteral("color")) {
|
||||
if (first_val.startsWith(QStringLiteral("rgba"),
|
||||
Qt::CaseInsensitive)) {
|
||||
QString vals_only = first_val;
|
||||
vals_only.remove(QStringLiteral("rgba"));
|
||||
vals_only.remove(QStringLiteral("("));
|
||||
vals_only.remove(QStringLiteral(")"));
|
||||
QStringList rgba = vals_only.split(',');
|
||||
if (rgba.size() == 4) {
|
||||
QColor c;
|
||||
c.setRed(rgba.at(0).toInt()); // Writer emits 0-255 RGB (CSS rgba() convention)
|
||||
c.setGreen(rgba.at(1).toInt());
|
||||
c.setBlue(rgba.at(2).toInt());
|
||||
c.setAlphaF(rgba.at(3).toDouble());
|
||||
fmt.setForeground(c);
|
||||
}
|
||||
} else {
|
||||
fmt.setForeground(QColor(first_val));
|
||||
}
|
||||
} else if (it.key() == QStringLiteral("font-variant")) {
|
||||
if (str_equals(first_val, QStringLiteral("small-caps"))) {
|
||||
fmt.setFontCapitalization(QFont::SmallCaps);
|
||||
}
|
||||
} else if (it.key() == QStringLiteral("letter-spacing")) {
|
||||
if (first_val.contains(QChar('%'))) {
|
||||
fmt.setFontLetterSpacing(
|
||||
first_val.chopped(1).toDouble());
|
||||
}
|
||||
} else if (it.key() == QStringLiteral("font-stretch")) {
|
||||
if (first_val.contains(QChar('%'))) {
|
||||
fmt.setFontStretch(first_val.chopped(1).toInt());
|
||||
}
|
||||
} else if (it.key() == QStringLiteral("-ove-font-style")) {
|
||||
fmt.setFontStyleName(first_val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return fmt;
|
||||
}
|
||||
|
||||
QTextBlockFormat Html::read_block_format(const QXmlStreamAttributes &attributes)
|
||||
{
|
||||
QTextBlockFormat block_fmt;
|
||||
|
||||
foreach (const QXmlStreamAttribute &attr, attributes) {
|
||||
if (str_equals(attr.name(), QStringLiteral("align"))) {
|
||||
if (str_equals(attr.value(), QStringLiteral("right"))) {
|
||||
block_fmt.setAlignment(Qt::AlignRight);
|
||||
} else if (str_equals(attr.value(), QStringLiteral("center"))) {
|
||||
block_fmt.setAlignment(Qt::AlignHCenter);
|
||||
} else if (str_equals(attr.value(), QStringLiteral("justify"))) {
|
||||
block_fmt.setAlignment(Qt::AlignJustify);
|
||||
}
|
||||
} else if (str_equals(attr.name(), QStringLiteral("dir"))) {
|
||||
if (str_equals(attr.value(), QStringLiteral("rtl"))) {
|
||||
block_fmt.setLayoutDirection(Qt::RightToLeft);
|
||||
}
|
||||
} else if (str_equals(attr.name(), QStringLiteral("style"))) {
|
||||
auto css = get_css_from_style(attr.value().toString());
|
||||
|
||||
for (auto it = css.begin(); it != css.end(); it++) {
|
||||
if (it.key() == QStringLiteral("line-height")) {
|
||||
const QString &first_val = it.value().constFirst();
|
||||
if (first_val.contains(QChar('%'))) {
|
||||
block_fmt.setLineHeight(
|
||||
first_val.chopped(1).toDouble(),
|
||||
QTextBlockFormat::ProportionalHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return block_fmt;
|
||||
}
|
||||
|
||||
void Html::append_string_auto_space(QString *s, const QString &append)
|
||||
{
|
||||
if (!s->isEmpty()) {
|
||||
s->append(QChar(' '));
|
||||
}
|
||||
|
||||
s->append(append);
|
||||
}
|
||||
|
||||
QMap<QString, QStringList> Html::get_css_from_style(const QString &s)
|
||||
{
|
||||
QMap<QString, QStringList> map;
|
||||
|
||||
QStringList list = s.split(QChar(';'));
|
||||
|
||||
foreach (const QString &a, list) {
|
||||
QStringList kv = a.split(QChar(':'));
|
||||
|
||||
if (kv.size() != 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// I'm sure there's regex that could do this, but I couldn't figure it out. It needs to split
|
||||
// by space EXCEPT within quotes OR double-quotes, and said quotes should be EXCLUDED from each
|
||||
// match. Also commas should be filtered out.
|
||||
QStringList values;
|
||||
const QString &val = kv.at(1);
|
||||
QChar in_quote(0);
|
||||
QString current_str;
|
||||
for (int i = 0; i < val.size(); i++) {
|
||||
const QChar ¤t_char = val.at(i);
|
||||
|
||||
if (!in_quote.isNull()) {
|
||||
// If inside quotes and character isn't quote, indiscriminately append char
|
||||
if (current_char == in_quote) {
|
||||
in_quote = QChar(0);
|
||||
} else {
|
||||
current_str.append(current_char);
|
||||
}
|
||||
} else if (current_char.isSpace() || current_char == QChar(',')) {
|
||||
// Dump current
|
||||
if (!current_str.isEmpty()) {
|
||||
values.append(current_str);
|
||||
current_str.clear();
|
||||
}
|
||||
} else if (in_quote.isNull() && (current_char == QChar('\'') ||
|
||||
current_char == QChar('"'))) {
|
||||
in_quote = current_char;
|
||||
} else {
|
||||
current_str.append(current_char);
|
||||
}
|
||||
}
|
||||
|
||||
if (!current_str.isEmpty()) {
|
||||
values.append(current_str);
|
||||
}
|
||||
|
||||
// Not sure if this will ever happen, but just in case, we will avoid assert failures with this
|
||||
if (values.isEmpty()) {
|
||||
values.append(QString());
|
||||
}
|
||||
|
||||
map[kv.at(0).trimmed().toLower()] = values;
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
* Oak Video Editor - 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 OAK_HTMLAPP_H
|
||||
#define OAK_HTMLAPP_H
|
||||
|
||||
#include <QTextDocument>
|
||||
#include <QTextFragment>
|
||||
#include <QXmlStreamReader>
|
||||
#include <QXmlStreamWriter>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Functions for converting HTML to QTextDocument and vice versa
|
||||
*
|
||||
* Qt does contain its own functions for this, however they have some limitations. Some things that
|
||||
* we want to support (e.g. kerning/spacing and font stretch) are not implemented in Qt's
|
||||
* QTextHtmlExporter and QTextHtmlParser. Additionally, since these functions are not part of Qt's
|
||||
* public API, and make many references to other parts of Qt that are not part of the public API,
|
||||
* there is no way to subclass or extend their functionality without forking Qt as a whole.
|
||||
*
|
||||
* Therefore, it became necessary to write a custom class for the conversion so that we can
|
||||
* ensure support for the features we need.
|
||||
*
|
||||
* If someone wishes to extend this class for more feature support, feel free to open a pull
|
||||
* request. But this is NOT intended to be an exhaustive HTML implementation, and is primarily
|
||||
* designed to store rich text in a standard format for the purpose of text formatting for video.
|
||||
*/
|
||||
class Html {
|
||||
public:
|
||||
static QString doc_to_html(const QTextDocument *doc);
|
||||
|
||||
static void html_to_doc(QTextDocument *doc, const QString &html);
|
||||
|
||||
private:
|
||||
static void write_block(QXmlStreamWriter *writer, const QTextBlock &block);
|
||||
|
||||
static void write_fragment(QXmlStreamWriter *writer,
|
||||
const QTextFragment &fragment);
|
||||
|
||||
static void write_css_property(QString *style, const QString &key,
|
||||
const QStringList &value);
|
||||
static void write_css_property(QString *style, const QString &key,
|
||||
const QString &value)
|
||||
{
|
||||
write_css_property(style, key, QStringList({ value }));
|
||||
}
|
||||
|
||||
static void write_char_format(QString *style, const QTextCharFormat &fmt);
|
||||
|
||||
static QTextCharFormat
|
||||
read_char_format(const QXmlStreamAttributes &attributes);
|
||||
|
||||
static QTextBlockFormat
|
||||
read_block_format(const QXmlStreamAttributes &attributes);
|
||||
|
||||
static void append_string_auto_space(QString *s, const QString &append);
|
||||
|
||||
static QMap<QString, QStringList> get_css_from_style(const QString &s);
|
||||
|
||||
static const QVector<QString> k_block_tags;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_HTML_H
|
||||
@@ -1,59 +0,0 @@
|
||||
/***
|
||||
|
||||
Oak - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak 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 OAK_KEYFRAMETYPES_H
|
||||
#define OAK_KEYFRAMETYPES_H
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief App-side keyframe enum mirrors.
|
||||
*
|
||||
* The engine olive::NodeKeyframe::Type mirror lives in
|
||||
* app/common/nodevaluehandle.h as NodeKeyframeType (kept with the
|
||||
* NodeValueType mirror); the enums below cover the remaining keyframe
|
||||
* domains. Enumerator ordinals must stay in sync with the engine / facade:
|
||||
* the C ABI transports these as ints.
|
||||
*/
|
||||
class KeyframeTypes {
|
||||
public:
|
||||
/// Mirror of engine's olive::NodeKeyframe::BezierType
|
||||
/// (engine/node/keyframe.h; oakengine_keyframe_opposing_bezier_type()
|
||||
/// transports these ordinals).
|
||||
enum BezierType { k_in_handle, k_out_handle };
|
||||
|
||||
/**
|
||||
* @brief Facade easing order used by the C ABI
|
||||
* (oakengine_keyframe_get_type(), oak::Keyframe::type()): NOT the same
|
||||
* order as the engine Type enum (see NodeKeyframeType in
|
||||
* common/nodevaluehandle.h).
|
||||
*/
|
||||
enum FacadeType {
|
||||
k_facade_invalid = -1,
|
||||
k_facade_linear = 0,
|
||||
k_facade_bezier = 1,
|
||||
k_facade_hold = 2
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_KEYFRAMETYPES_H
|
||||
@@ -1,46 +0,0 @@
|
||||
/***
|
||||
|
||||
Oak - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak 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 OAK_NODEDATATYPES_H
|
||||
#define OAK_NODEDATATYPES_H
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief App-side mirror of engine's olive::Node::DataType
|
||||
* (engine/node/node.h).
|
||||
*
|
||||
* Enumerator ordinals must stay in sync with the engine enum: the C ABI
|
||||
* oakengine_node_get_data() (and the oak::Node::data() wrapper) takes the
|
||||
* `role` argument as a plain int carrying these ordinals.
|
||||
*/
|
||||
enum NodeDataType {
|
||||
k_node_data_icon,
|
||||
k_node_data_duration,
|
||||
k_node_data_created_time,
|
||||
k_node_data_modified_time,
|
||||
k_node_data_frequency_rate,
|
||||
k_node_data_tooltip
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_NODEDATATYPES_H
|
||||
@@ -1,141 +0,0 @@
|
||||
/***
|
||||
|
||||
Oak - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak 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 OAK_NODEVALUEHANDLE_H
|
||||
#define OAK_NODEVALUEHANDLE_H
|
||||
|
||||
#include "oakengine/node.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief App-side mirror of engine NodeValue::Type (engine/node/value.h).
|
||||
*
|
||||
* Ordinals MUST stay in sync with the engine enum: call sites that still
|
||||
* hold an engine NodeValue::Type reach the helpers below through an
|
||||
* implicit int conversion. The static_asserts pin the engine ordinals as
|
||||
* of the R8 wave 1 migration; update both sides together.
|
||||
*
|
||||
* NOTE: the C ABI oak_node_value_type (OAK_NODE_VALUE_*) does NOT share
|
||||
* these ordinals (e.g. k_boolean=4 vs OAK_NODE_VALUE_BOOL=3), so a plain
|
||||
* int cast between the two domains is a bug. Convert with
|
||||
* node_value_type_to_c().
|
||||
*/
|
||||
class NodeValueType
|
||||
{
|
||||
public:
|
||||
enum Type {
|
||||
k_none = 0,
|
||||
k_int,
|
||||
k_float,
|
||||
k_rational,
|
||||
k_boolean,
|
||||
k_color,
|
||||
k_matrix,
|
||||
k_text,
|
||||
k_font,
|
||||
k_file,
|
||||
k_texture,
|
||||
k_samples,
|
||||
k_vec2,
|
||||
k_vec3,
|
||||
k_vec4,
|
||||
k_bezier,
|
||||
k_combo,
|
||||
k_str_combo,
|
||||
k_video_params,
|
||||
k_audio_params,
|
||||
k_subtitle_params,
|
||||
k_binary,
|
||||
k_push_button,
|
||||
k_data_type_count
|
||||
};
|
||||
};
|
||||
|
||||
// Ordinal sync guards against engine/node/value.h.
|
||||
static_assert(NodeValueType::k_boolean == 4,
|
||||
"NodeValueType out of sync with engine NodeValue::Type");
|
||||
static_assert(NodeValueType::k_vec2 == 12,
|
||||
"NodeValueType out of sync with engine NodeValue::Type");
|
||||
static_assert(NodeValueType::k_data_type_count == 23,
|
||||
"NodeValueType out of sync with engine NodeValue::Type");
|
||||
|
||||
/**
|
||||
* @brief App-side mirror of engine NodeKeyframe::Type
|
||||
* (engine/node/keyframe.h).
|
||||
*
|
||||
* Ordinals MUST stay in sync with the engine enum (k_invalid=-1,
|
||||
* k_linear=0, k_hold=1, k_bezier=2). The facade easing type transported
|
||||
* over the C ABI is a DIFFERENT numbering: 0=linear, 1=bezier, 2=hold
|
||||
* (see oakengine/node.h) — convert with NodeKeyframeTypeToFacade() in
|
||||
* oakvaluehelper.h, never with a plain cast.
|
||||
*/
|
||||
class NodeKeyframeType
|
||||
{
|
||||
public:
|
||||
enum Type { k_invalid = -1, k_linear = 0, k_hold = 1, k_bezier = 2 };
|
||||
};
|
||||
|
||||
// Ordinal sync guard against engine/node/keyframe.h.
|
||||
static_assert(NodeKeyframeType::k_bezier == 2,
|
||||
"NodeKeyframeType out of sync with engine NodeKeyframe::Type");
|
||||
|
||||
/**
|
||||
* @brief Convert engine NodeValue::Type ordinals to oak_node_value_type (app-side).
|
||||
*
|
||||
* `t` uses engine NodeValue::Type ordinals (see the NodeValueType mirror
|
||||
* above); callers holding an engine NodeValue::Type pass it through an
|
||||
* implicit int conversion. The two enums do NOT share ordinals (e.g.
|
||||
* k_boolean=4 vs BOOL=3), so a plain int cast is a bug. Mirrors
|
||||
* from_c_type() in engine/src/capi/node.cpp. Lives in an app header, NOT
|
||||
* in the public facade headers — the C ABI surface stays pure C (see
|
||||
* docs/zh/r6-cleanup-plan.md red line 3 context). Returns -1 for types the
|
||||
* facade cannot represent (caller falls back to the input's declared type).
|
||||
*/
|
||||
inline int node_value_type_to_c(int t)
|
||||
{
|
||||
switch (t) {
|
||||
case NodeValueType::k_int: return OAK_NODE_VALUE_INT;
|
||||
case NodeValueType::k_float: return OAK_NODE_VALUE_FLOAT;
|
||||
case NodeValueType::k_boolean: return OAK_NODE_VALUE_BOOL;
|
||||
case NodeValueType::k_rational: return OAK_NODE_VALUE_RATIONAL;
|
||||
case NodeValueType::k_color: return OAK_NODE_VALUE_COLOR;
|
||||
case NodeValueType::k_vec2: return OAK_NODE_VALUE_VEC2;
|
||||
case NodeValueType::k_vec3: return OAK_NODE_VALUE_VEC3;
|
||||
case NodeValueType::k_vec4: return OAK_NODE_VALUE_VEC4;
|
||||
case NodeValueType::k_combo: return OAK_NODE_VALUE_COMBO;
|
||||
case NodeValueType::k_file: return OAK_NODE_VALUE_STRING;
|
||||
case NodeValueType::k_text: return OAK_NODE_VALUE_TEXT;
|
||||
case NodeValueType::k_font: return OAK_NODE_VALUE_FONT;
|
||||
case NodeValueType::k_str_combo: return OAK_NODE_VALUE_STR_COMBO;
|
||||
case NodeValueType::k_binary: return OAK_NODE_VALUE_BINARY;
|
||||
case NodeValueType::k_bezier: return OAK_NODE_VALUE_BEZIER;
|
||||
case NodeValueType::k_texture: return OAK_NODE_VALUE_TEXTURE;
|
||||
case NodeValueType::k_samples: return OAK_NODE_VALUE_SAMPLES;
|
||||
case NodeValueType::k_video_params: return OAK_NODE_VALUE_VIDEO_PARAMS;
|
||||
case NodeValueType::k_audio_params: return OAK_NODE_VALUE_AUDIO_PARAMS;
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace olive
|
||||
|
||||
#endif // OAK_NODEVALUEHANDLE_H
|
||||
@@ -1,233 +0,0 @@
|
||||
/***
|
||||
|
||||
Oak - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak 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 OAKVALUEHELPER_H
|
||||
#define OAKVALUEHELPER_H
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include <QVariant>
|
||||
#include <QVector2D>
|
||||
#include <QVector3D>
|
||||
#include <QVector4D>
|
||||
|
||||
#include "nodevaluehandle.h"
|
||||
#include "oakengine/node.h"
|
||||
#include "olive/core/util/color.h"
|
||||
#include "olive/core/util/rational.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
/**
|
||||
* @brief Convert a per-track component QVariant into the C ABI oak_node_value POD.
|
||||
*
|
||||
* `type` uses engine NodeValue::Type ordinals (see the NodeValueType mirror
|
||||
* in nodevaluehandle.h; an engine NodeValue::Type converts implicitly) and
|
||||
* is the declared input data type (e.g. k_float/k_color). For split-track
|
||||
* types the component is the track-0 scalar (float for k_color's red
|
||||
* channel, etc.). Returns false for types that have no POD representation.
|
||||
*/
|
||||
static inline bool QVariantToOakNodeValue(int type, const QVariant &v,
|
||||
oak_node_value *out)
|
||||
{
|
||||
memset(out, 0, sizeof(*out));
|
||||
switch (type) {
|
||||
case NodeValueType::k_int:
|
||||
case NodeValueType::k_combo:
|
||||
out->type = (type == NodeValueType::k_combo) ? OAK_NODE_VALUE_COMBO
|
||||
: OAK_NODE_VALUE_INT;
|
||||
out->num = v.toLongLong();
|
||||
return true;
|
||||
case NodeValueType::k_float:
|
||||
out->type = OAK_NODE_VALUE_FLOAT;
|
||||
out->f[0] = v.toDouble();
|
||||
return true;
|
||||
case NodeValueType::k_boolean:
|
||||
out->type = OAK_NODE_VALUE_BOOL;
|
||||
out->num = v.toBool() ? 1 : 0;
|
||||
return true;
|
||||
case NodeValueType::k_rational:
|
||||
out->type = OAK_NODE_VALUE_RATIONAL;
|
||||
{
|
||||
const core::Rational r = v.value<core::Rational>();
|
||||
out->num = r.numerator();
|
||||
out->den = r.denominator();
|
||||
}
|
||||
return true;
|
||||
case NodeValueType::k_color:
|
||||
out->type = OAK_NODE_VALUE_COLOR;
|
||||
{
|
||||
const core::Color c = v.value<core::Color>();
|
||||
out->f[0] = c.red();
|
||||
out->f[1] = c.green();
|
||||
out->f[2] = c.blue();
|
||||
out->f[3] = c.alpha();
|
||||
}
|
||||
return true;
|
||||
case NodeValueType::k_vec2:
|
||||
out->type = OAK_NODE_VALUE_VEC2;
|
||||
{
|
||||
const QVector2D vec = v.value<QVector2D>();
|
||||
out->f[0] = vec.x();
|
||||
out->f[1] = vec.y();
|
||||
}
|
||||
return true;
|
||||
case NodeValueType::k_vec3:
|
||||
out->type = OAK_NODE_VALUE_VEC3;
|
||||
{
|
||||
const QVector3D vec = v.value<QVector3D>();
|
||||
out->f[0] = vec.x();
|
||||
out->f[1] = vec.y();
|
||||
out->f[2] = vec.z();
|
||||
}
|
||||
return true;
|
||||
case NodeValueType::k_vec4:
|
||||
out->type = OAK_NODE_VALUE_VEC4;
|
||||
{
|
||||
const QVector4D vec = v.value<QVector4D>();
|
||||
out->f[0] = vec.x();
|
||||
out->f[1] = vec.y();
|
||||
out->f[2] = vec.z();
|
||||
out->f[3] = vec.w();
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert a per-track component QVariant into the C ABI oak_node_value POD.
|
||||
*
|
||||
* Unlike QVariantToOakNodeValue() which takes a full normal value, this takes a
|
||||
* single track's component (e.g. one float for a k_color channel). The resulting
|
||||
* POD has the input's declared type with the component in f[0]/num, exactly what
|
||||
* the per-track facade commands expect.
|
||||
*
|
||||
* `type` uses engine NodeValue::Type ordinals (NodeValueType mirror).
|
||||
*/
|
||||
static inline bool NodeTrackComponentToOakNodeValue(int type,
|
||||
const QVariant &v,
|
||||
oak_node_value *out)
|
||||
{
|
||||
memset(out, 0, sizeof(*out));
|
||||
switch (type) {
|
||||
case NodeValueType::k_int:
|
||||
case NodeValueType::k_combo:
|
||||
out->type = (type == NodeValueType::k_combo) ? OAK_NODE_VALUE_COMBO
|
||||
: OAK_NODE_VALUE_INT;
|
||||
out->num = v.toLongLong();
|
||||
return true;
|
||||
case NodeValueType::k_float:
|
||||
case NodeValueType::k_bezier:
|
||||
out->type = OAK_NODE_VALUE_FLOAT;
|
||||
out->f[0] = v.toDouble();
|
||||
return true;
|
||||
case NodeValueType::k_boolean:
|
||||
out->type = OAK_NODE_VALUE_BOOL;
|
||||
out->num = v.toBool() ? 1 : 0;
|
||||
return true;
|
||||
case NodeValueType::k_rational:
|
||||
out->type = OAK_NODE_VALUE_RATIONAL;
|
||||
{
|
||||
const core::Rational r = v.value<core::Rational>();
|
||||
out->num = r.numerator();
|
||||
out->den = r.denominator();
|
||||
}
|
||||
return true;
|
||||
case NodeValueType::k_color:
|
||||
out->type = OAK_NODE_VALUE_COLOR;
|
||||
out->f[0] = v.toFloat();
|
||||
return true;
|
||||
case NodeValueType::k_vec2:
|
||||
out->type = OAK_NODE_VALUE_VEC2;
|
||||
out->f[0] = v.toFloat();
|
||||
return true;
|
||||
case NodeValueType::k_vec3:
|
||||
out->type = OAK_NODE_VALUE_VEC3;
|
||||
out->f[0] = v.toFloat();
|
||||
return true;
|
||||
case NodeValueType::k_vec4:
|
||||
out->type = OAK_NODE_VALUE_VEC4;
|
||||
out->f[0] = v.toFloat();
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert a full C ABI oak_node_value POD back into a QVariant.
|
||||
*
|
||||
* Mirrors QVariantToOakNodeValue(). String/binary/bezier are not represented
|
||||
* in the POD and return an invalid QVariant; use the dedicated string/binary/
|
||||
* bezier facade getters for those.
|
||||
*/
|
||||
static inline QVariant OakNodeValueToQVariant(const oak_node_value &v)
|
||||
{
|
||||
switch (v.type) {
|
||||
case OAK_NODE_VALUE_INT:
|
||||
return QVariant::fromValue<qlonglong>(v.num);
|
||||
case OAK_NODE_VALUE_FLOAT:
|
||||
return QVariant::fromValue(v.f[0]);
|
||||
case OAK_NODE_VALUE_BOOL:
|
||||
return QVariant::fromValue(v.num != 0);
|
||||
case OAK_NODE_VALUE_RATIONAL:
|
||||
return QVariant::fromValue(
|
||||
core::Rational(int(v.num), int(v.den)));
|
||||
case OAK_NODE_VALUE_COLOR:
|
||||
return QVariant::fromValue(core::Color(
|
||||
float(v.f[0]), float(v.f[1]), float(v.f[2]), float(v.f[3])));
|
||||
case OAK_NODE_VALUE_VEC2:
|
||||
return QVariant::fromValue(
|
||||
QVector2D(float(v.f[0]), float(v.f[1])));
|
||||
case OAK_NODE_VALUE_VEC3:
|
||||
return QVariant::fromValue(
|
||||
QVector3D(float(v.f[0]), float(v.f[1]), float(v.f[2])));
|
||||
case OAK_NODE_VALUE_VEC4:
|
||||
return QVariant::fromValue(
|
||||
QVector4D(float(v.f[0]), float(v.f[1]), float(v.f[2]), float(v.f[3])));
|
||||
case OAK_NODE_VALUE_COMBO:
|
||||
return QVariant::fromValue<int>(int(v.num));
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Map engine NodeKeyframe::Type ordinals (NodeKeyframeType mirror)
|
||||
* to the facade easing type (0=linear, 1=bezier, 2=hold).
|
||||
*/
|
||||
static inline int NodeKeyframeTypeToFacade(int type)
|
||||
{
|
||||
switch (type) {
|
||||
case NodeKeyframeType::k_bezier:
|
||||
return 1;
|
||||
case NodeKeyframeType::k_hold:
|
||||
return 2;
|
||||
case NodeKeyframeType::k_linear:
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace olive
|
||||
|
||||
#endif // OAKVALUEHELPER_H
|
||||
@@ -1,46 +0,0 @@
|
||||
/***
|
||||
|
||||
Oak - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak 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 OAK_PROJECTTYPES_H
|
||||
#define OAK_PROJECTTYPES_H
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief App-side mirror of the engine's olive::Project enum(s)
|
||||
* (engine/node/project.h).
|
||||
*
|
||||
* Enumerator ordinals must stay in sync with the engine enum: the C ABI
|
||||
* (oakengine_project_get_cache_location_setting() etc.) transports these
|
||||
* values as plain ints.
|
||||
*/
|
||||
class Project {
|
||||
public:
|
||||
enum CacheSetting {
|
||||
k_cache_use_default_location,
|
||||
k_cache_store_alongside_project,
|
||||
k_cache_custom_path
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_PROJECTTYPES_H
|
||||
@@ -1,144 +0,0 @@
|
||||
/***
|
||||
|
||||
Oak - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak 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/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "oakutil/qtutils.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
int QtUtils::q_font_metrics_width(QFontMetrics fm, const QString &s)
|
||||
{
|
||||
return fm.horizontalAdvance(s);
|
||||
}
|
||||
|
||||
QFrame *QtUtils::create_horizontal_line()
|
||||
{
|
||||
QFrame *horizontal_line = new QFrame();
|
||||
horizontal_line->setFrameShape(QFrame::HLine);
|
||||
horizontal_line->setFrameShadow(QFrame::Sunken);
|
||||
return horizontal_line;
|
||||
}
|
||||
|
||||
QFrame *QtUtils::create_vertical_line()
|
||||
{
|
||||
QFrame *l = create_horizontal_line();
|
||||
l->setFrameShape(QFrame::VLine);
|
||||
return l;
|
||||
}
|
||||
|
||||
QString QtUtils::get_formatted_date_time(const QDateTime &dt)
|
||||
{
|
||||
// ISO-style date/time (e.g. "2026-06-03 20:25") per the UI design reference
|
||||
return dt.toString(QStringLiteral("yyyy-MM-dd HH:mm"));
|
||||
}
|
||||
|
||||
QStringList QtUtils::word_wrap_string(const QString &s, const QFontMetrics &fm,
|
||||
int bounding_width)
|
||||
{
|
||||
QStringList list;
|
||||
QStringList lines = s.split('\n');
|
||||
for (int i = 0; i < lines.size(); i++) {
|
||||
QString this_line = lines.at(i);
|
||||
while (this_line.size() > 1 &&
|
||||
q_font_metrics_width(fm, this_line) >= bounding_width) {
|
||||
int old_size = this_line.size();
|
||||
int hard_break = -1;
|
||||
for (int j = this_line.size() - 1; j >= 0; j--) {
|
||||
const QChar &char_test = this_line.at(j);
|
||||
if (char_test.isSpace() || char_test == '-') {
|
||||
if (q_font_metrics_width(fm, this_line.left(j)) <
|
||||
bounding_width) {
|
||||
if (!char_test.isSpace()) j++;
|
||||
list.append(this_line.left(j));
|
||||
while (j < this_line.size() &&
|
||||
this_line.at(j).isSpace()) j++;
|
||||
this_line.remove(0, j);
|
||||
break;
|
||||
}
|
||||
} else if (hard_break == -1 &&
|
||||
q_font_metrics_width(fm, this_line.left(j)) <
|
||||
bounding_width) {
|
||||
hard_break = j;
|
||||
}
|
||||
}
|
||||
if (old_size == this_line.size()) {
|
||||
if (hard_break != -1) {
|
||||
list.append(this_line.left(hard_break));
|
||||
this_line.remove(0, hard_break);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!this_line.isEmpty()) {
|
||||
list.append(this_line);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
Qt::KeyboardModifiers
|
||||
QtUtils::flip_control_and_shift_modifiers(Qt::KeyboardModifiers e)
|
||||
{
|
||||
if (e & Qt::ControlModifier & Qt::ShiftModifier) return e;
|
||||
if (e & Qt::ShiftModifier) {
|
||||
e |= Qt::ControlModifier;
|
||||
e &= ~Qt::ShiftModifier;
|
||||
} else if (e & Qt::ControlModifier) {
|
||||
e |= Qt::ShiftModifier;
|
||||
e &= ~Qt::ControlModifier;
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
void QtUtils::set_combo_box_data(QComboBox *cb, int data)
|
||||
{
|
||||
for (int i = 0; i < cb->count(); i++) {
|
||||
if (cb->itemData(i).toInt() == data) {
|
||||
cb->setCurrentIndex(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QtUtils::set_combo_box_data(QComboBox *cb, const QString &data)
|
||||
{
|
||||
for (int i = 0; i < cb->count(); i++) {
|
||||
if (cb->itemData(i).toString() == data) {
|
||||
cb->setCurrentIndex(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QColor QtUtils::to_q_color(const core::Color &i)
|
||||
{
|
||||
QColor c;
|
||||
|
||||
// QColor only supports values from 0.0 to 1.0 and are only used for UI representations
|
||||
c.setRedF(std::clamp(i.red(), 0.0f, 1.0f));
|
||||
c.setGreenF(std::clamp(i.green(), 0.0f, 1.0f));
|
||||
c.setBlueF(std::clamp(i.blue(), 0.0f, 1.0f));
|
||||
c.setAlphaF(std::clamp(i.alpha(), 0.0f, 1.0f));
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
/***
|
||||
|
||||
Oak - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak 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 OAK_SERIALIZEDLAYOUTINFOAPP_H
|
||||
#define OAK_SERIALIZEDLAYOUTINFOAPP_H
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
|
||||
#include "oakengine/node.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief App-side mirror of the engine's olive::SerializedLayoutInfo
|
||||
* (engine/node/project/serializer/serializedlayoutinfo.h).
|
||||
*
|
||||
* SYNC OBLIGATION: the data member layout (types AND order) must stay
|
||||
* identical to the engine type. Instances of this struct cross the engine
|
||||
* boundary as `void *` (Core::save_project_internal() passes one to
|
||||
* oakengine_task_create_project_save(), and the engine hands one back
|
||||
* through the load_layout callback); the engine side reinterprets the
|
||||
* pointer as its own olive::SerializedLayoutInfo and copies the members,
|
||||
* so any layout divergence is silent memory corruption.
|
||||
*
|
||||
* The engine's std::vector<Folder*> / std::vector<Sequence*> /
|
||||
* std::vector<ViewerOutput*> members are mirrored as
|
||||
* std::vector<OakEngineNode*> (same pointer size and semantics: borrowed
|
||||
* node handles). panel_data mirrors
|
||||
* std::map<QString, PanelLayoutInfo> where PanelLayoutInfo is
|
||||
* std::map<QString, QString> (identical to PanelWidget::Info).
|
||||
*
|
||||
* Only the data members are mirrored; the engine type's XML
|
||||
* (de)serialization methods (to_xml/from_xml) stay engine-side.
|
||||
*/
|
||||
class SerializedLayoutInfo {
|
||||
public:
|
||||
SerializedLayoutInfo() = default;
|
||||
|
||||
QByteArray state;
|
||||
|
||||
std::vector<OakEngineNode *> open_folders;
|
||||
|
||||
std::vector<OakEngineNode *> open_sequences;
|
||||
|
||||
std::vector<OakEngineNode *> open_viewers;
|
||||
|
||||
std::map<QString, std::map<QString, QString>> panel_data;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(olive::SerializedLayoutInfo)
|
||||
|
||||
#endif // OAK_SERIALIZEDLAYOUTINFOAPP_H
|
||||
@@ -1,87 +0,0 @@
|
||||
/***
|
||||
|
||||
Oak - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak 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 OAK_SUBTITLEAPP_H
|
||||
#define OAK_SUBTITLEAPP_H
|
||||
|
||||
#include <QString>
|
||||
#include <QVariant>
|
||||
|
||||
#include <olive/core/util/timerange.h>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
using olive::core::TimeRange;
|
||||
|
||||
/**
|
||||
* @brief App-side mirror of the engine Subtitle value class
|
||||
* (engine/render/subtitleparams.h).
|
||||
*
|
||||
* Pure value type (time range + text), identical semantics to the engine
|
||||
* version. It is named SubtitleApp (not Subtitle) because the engine class
|
||||
* still reaches some app translation units transitively (e.g. via
|
||||
* engine/node/output/viewer/viewer.h) and an identical name would be an
|
||||
* ODR redefinition there.
|
||||
*
|
||||
* The member layout MUST stay in sync with the engine class:
|
||||
* oakengine_viewer_get_subtitle_at() returns pointers to engine Subtitle
|
||||
* objects which the app reads through this mirror. Update both sides
|
||||
* together.
|
||||
*/
|
||||
class SubtitleApp {
|
||||
public:
|
||||
SubtitleApp() = default;
|
||||
|
||||
SubtitleApp(const TimeRange &time, const QString &text)
|
||||
: range_(time)
|
||||
, text_(text)
|
||||
{
|
||||
}
|
||||
|
||||
const TimeRange &time() const
|
||||
{
|
||||
return range_;
|
||||
}
|
||||
void set_time(const TimeRange &t)
|
||||
{
|
||||
range_ = t;
|
||||
}
|
||||
|
||||
const QString &text() const
|
||||
{
|
||||
return text_;
|
||||
}
|
||||
void set_text(const QString &t)
|
||||
{
|
||||
text_ = t;
|
||||
}
|
||||
|
||||
private:
|
||||
TimeRange range_;
|
||||
|
||||
QString text_;
|
||||
};
|
||||
|
||||
} // namespace olive
|
||||
|
||||
Q_DECLARE_METATYPE(olive::SubtitleApp)
|
||||
|
||||
#endif // OAK_SUBTITLEAPP_H
|
||||
@@ -1,168 +0,0 @@
|
||||
/***
|
||||
|
||||
Oak - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak 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 OAK_TOOLTYPES_H
|
||||
#define OAK_TOOLTYPES_H
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QString>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief App-side mirror of engine's olive::Tool (engine/tool/tool.h).
|
||||
*
|
||||
* Pure enum + static string mapping, identical to the engine version.
|
||||
* Enumerator ordinals must stay in sync with the engine enum: the C ABI
|
||||
* (oakengine_app_tool() etc.) transports these as ints.
|
||||
*/
|
||||
class Tool {
|
||||
public:
|
||||
/**
|
||||
* @brief A list of tools that can be used throughout the application
|
||||
*/
|
||||
enum Item {
|
||||
/// No tool. This should never be set as the application tool, its only real purpose is to indicate the lack of
|
||||
/// a tool somewhere similar to nullptr.
|
||||
k_none,
|
||||
|
||||
/// Pointer tool
|
||||
k_pointer,
|
||||
|
||||
/// Edit tool
|
||||
k_edit,
|
||||
|
||||
/// Ripple tool
|
||||
k_ripple,
|
||||
|
||||
/// Rolling tool
|
||||
k_rolling,
|
||||
|
||||
/// Razor tool
|
||||
k_razor,
|
||||
|
||||
/// Slip tool
|
||||
k_slip,
|
||||
|
||||
/// Slide tool
|
||||
k_slide,
|
||||
|
||||
/// Hand tool
|
||||
k_hand,
|
||||
|
||||
/// Zoom tool
|
||||
k_zoom,
|
||||
|
||||
/// Transition tool
|
||||
k_transition,
|
||||
|
||||
/// Record tool
|
||||
k_record,
|
||||
|
||||
/// Add tool
|
||||
k_add,
|
||||
|
||||
/// Track select tool
|
||||
k_track_select,
|
||||
|
||||
k_count
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Tools that can be added using the kAdd tool
|
||||
*/
|
||||
enum AddableObject {
|
||||
/// An empty clip
|
||||
k_addable_empty,
|
||||
|
||||
/// A video clip showing a generic video placeholder
|
||||
k_addable_bars,
|
||||
|
||||
/// A video clip showing a primitive shape
|
||||
k_addable_shape,
|
||||
|
||||
/// A video clip with a solid connected
|
||||
k_addable_solid,
|
||||
|
||||
/// A video clip with a title connected
|
||||
k_addable_title,
|
||||
|
||||
/// An audio clip with a sine connected to it
|
||||
k_addable_tone,
|
||||
|
||||
/// A subtitle clip
|
||||
k_addable_subtitle,
|
||||
|
||||
k_addable_count
|
||||
};
|
||||
|
||||
static QString get_addable_object_name(const AddableObject &a)
|
||||
{
|
||||
switch (a) {
|
||||
case k_addable_empty:
|
||||
return QCoreApplication::translate("Tool", "Empty");
|
||||
case k_addable_bars:
|
||||
return QCoreApplication::translate("Tool", "Bars");
|
||||
case k_addable_shape:
|
||||
return QCoreApplication::translate("Tool", "Shape");
|
||||
case k_addable_solid:
|
||||
return QCoreApplication::translate("Tool", "Solid");
|
||||
case k_addable_title:
|
||||
return QCoreApplication::translate("Tool", "Title");
|
||||
case k_addable_tone:
|
||||
return QCoreApplication::translate("Tool", "Tone");
|
||||
case k_addable_subtitle:
|
||||
return QCoreApplication::translate("Tool", "Subtitle");
|
||||
case k_addable_count:
|
||||
break;
|
||||
}
|
||||
|
||||
return QCoreApplication::translate("Tool", "Unknown");
|
||||
}
|
||||
|
||||
static QString get_addable_object_id(const AddableObject &a)
|
||||
{
|
||||
switch (a) {
|
||||
case k_addable_empty:
|
||||
return QStringLiteral("empty");
|
||||
case k_addable_bars:
|
||||
return QStringLiteral("bars");
|
||||
case k_addable_shape:
|
||||
return QStringLiteral("shape");
|
||||
case k_addable_solid:
|
||||
return QStringLiteral("solid");
|
||||
case k_addable_title:
|
||||
return QStringLiteral("title");
|
||||
case k_addable_tone:
|
||||
return QStringLiteral("tone");
|
||||
case k_addable_subtitle:
|
||||
return QStringLiteral("subtitle");
|
||||
case k_addable_count:
|
||||
break;
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_TOOLTYPES_H
|
||||
@@ -1,220 +0,0 @@
|
||||
/***
|
||||
|
||||
Oak - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak 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 OAK_TRACKREFERENCEHANDLE_H
|
||||
#define OAK_TRACKREFERENCEHANDLE_H
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDataStream>
|
||||
#include <QHash>
|
||||
#include <QString>
|
||||
|
||||
#include "oakengine/timeline.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief App-side mirror of the engine Track::Reference value class
|
||||
* (engine/node/output/track/track.h).
|
||||
*
|
||||
* Pure value type (track type + index) used throughout timeline UI code.
|
||||
* Semantics are identical to the engine version. The nested Type enum
|
||||
* mirrors engine Track::Type; ordinals MUST stay in sync with the engine
|
||||
* enum — the C ABI transports track types as ints
|
||||
* (OAKENGINE_TRACK_TYPE_* in oakengine/timeline.h), pinned by the
|
||||
* static_asserts below. Update both sides together.
|
||||
*/
|
||||
class TrackReference
|
||||
{
|
||||
public:
|
||||
enum Type { k_none = -1, k_video, k_audio, k_subtitle, k_count };
|
||||
|
||||
TrackReference()
|
||||
: type_(k_none)
|
||||
, index_(-1)
|
||||
{
|
||||
}
|
||||
|
||||
TrackReference(const Type &type, const int &index)
|
||||
: type_(type)
|
||||
, index_(index)
|
||||
{
|
||||
}
|
||||
|
||||
const Type &type() const
|
||||
{
|
||||
return type_;
|
||||
}
|
||||
|
||||
const int &index() const
|
||||
{
|
||||
return index_;
|
||||
}
|
||||
|
||||
bool operator==(const TrackReference &ref) const
|
||||
{
|
||||
return type_ == ref.type_ && index_ == ref.index_;
|
||||
}
|
||||
|
||||
bool operator!=(const TrackReference &ref) const
|
||||
{
|
||||
return !(*this == ref);
|
||||
}
|
||||
|
||||
bool operator<(const TrackReference &rhs) const
|
||||
{
|
||||
if (type_ != rhs.type_) {
|
||||
return type_ < rhs.type_;
|
||||
}
|
||||
|
||||
return index_ < rhs.index_;
|
||||
}
|
||||
|
||||
QString to_string() const
|
||||
{
|
||||
QString type_string = type_to_string(type_);
|
||||
if (type_string.isEmpty()) {
|
||||
return QString();
|
||||
} else {
|
||||
return QStringLiteral("%1:%2").arg(type_string,
|
||||
QString::number(index_));
|
||||
}
|
||||
}
|
||||
|
||||
/// For IDs that shouldn't change between localizations
|
||||
static QString type_to_string(Type type)
|
||||
{
|
||||
switch (type) {
|
||||
case k_video:
|
||||
return QStringLiteral("v");
|
||||
case k_audio:
|
||||
return QStringLiteral("a");
|
||||
case k_subtitle:
|
||||
return QStringLiteral("s");
|
||||
case k_count:
|
||||
case k_none:
|
||||
break;
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
/// For human-facing strings (translation context "Track" kept
|
||||
/// identical to the engine version)
|
||||
static QString type_to_translated_string(Type type)
|
||||
{
|
||||
switch (type) {
|
||||
case k_video:
|
||||
return QCoreApplication::translate("Track", "V");
|
||||
case k_audio:
|
||||
return QCoreApplication::translate("Track", "A");
|
||||
case k_subtitle:
|
||||
return QCoreApplication::translate("Track", "S");
|
||||
case k_count:
|
||||
case k_none:
|
||||
break;
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
static Type type_from_string(const QString &s)
|
||||
{
|
||||
if (s.size() >= 3) {
|
||||
if (s.at(1) == ':') {
|
||||
if (s.at(0) == 'v') {
|
||||
// Video stream
|
||||
return k_video;
|
||||
} else if (s.at(0) == 'a') {
|
||||
// Audio stream
|
||||
return k_audio;
|
||||
} else if (s.at(0) == 's') {
|
||||
// Subtitle stream
|
||||
return k_subtitle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return k_none;
|
||||
}
|
||||
|
||||
static TrackReference from_string(const QString &s)
|
||||
{
|
||||
TrackReference ref;
|
||||
Type parse_type = type_from_string(s);
|
||||
|
||||
if (parse_type != k_none) {
|
||||
bool ok;
|
||||
int parse_index = s.mid(2).toInt(&ok);
|
||||
|
||||
if (ok) {
|
||||
ref.type_ = parse_type;
|
||||
ref.index_ = parse_index;
|
||||
}
|
||||
}
|
||||
|
||||
return ref;
|
||||
}
|
||||
|
||||
bool is_valid() const
|
||||
{
|
||||
return type_ > k_none && type_ < k_count && index_ >= 0;
|
||||
}
|
||||
|
||||
private:
|
||||
Type type_;
|
||||
|
||||
int index_;
|
||||
};
|
||||
|
||||
// Ordinal sync guards: C ABI OAKENGINE_TRACK_TYPE_* (oakengine/timeline.h)
|
||||
// carry the same values as engine Track::Type, and this mirror matches both.
|
||||
static_assert(TrackReference::k_video == OAKENGINE_TRACK_TYPE_VIDEO,
|
||||
"TrackReference::Type out of sync with C ABI track types");
|
||||
static_assert(TrackReference::k_audio == OAKENGINE_TRACK_TYPE_AUDIO,
|
||||
"TrackReference::Type out of sync with C ABI track types");
|
||||
static_assert(TrackReference::k_subtitle == OAKENGINE_TRACK_TYPE_SUBTITLE,
|
||||
"TrackReference::Type out of sync with C ABI track types");
|
||||
|
||||
inline uint qHash(const TrackReference &r, uint seed = 0)
|
||||
{
|
||||
return ::qHash(QStringLiteral("%1:%2").arg(QString::number(r.type()),
|
||||
QString::number(r.index())),
|
||||
seed);
|
||||
}
|
||||
|
||||
inline QDataStream &operator<<(QDataStream &out, const TrackReference &ref)
|
||||
{
|
||||
out << static_cast<int>(ref.type()) << ref.index();
|
||||
return out;
|
||||
}
|
||||
|
||||
inline QDataStream &operator>>(QDataStream &in, TrackReference &ref)
|
||||
{
|
||||
int type, index;
|
||||
in >> type >> index;
|
||||
ref = TrackReference(static_cast<TrackReference::Type>(type), index);
|
||||
return in;
|
||||
}
|
||||
|
||||
} // namespace olive
|
||||
|
||||
#endif // OAK_TRACKREFERENCEHANDLE_H
|
||||
@@ -1,60 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 OAK_UNDOWRAPPER_H
|
||||
#define OAK_UNDOWRAPPER_H
|
||||
|
||||
#include "oakengine/undo.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* Wrap an app-side undo command object in the facade custom-command API.
|
||||
*
|
||||
* `Cmd` must provide public `redo()` and `undo()` methods. Ownership of `cmd`
|
||||
* is transferred to the returned opaque command pointer; the wrapper deletes
|
||||
* `cmd` when the engine command is destroyed.
|
||||
*
|
||||
* This helper lets app code keep small app-state undo commands (selections,
|
||||
* splitter sizes, etc.) without defining new subclasses of olive::UndoCommand,
|
||||
* which would keep olive::UndoCommand symbols in the editor binary.
|
||||
*/
|
||||
template <typename Cmd>
|
||||
void *wrap_app_undo_command(const char *name, Cmd *cmd)
|
||||
{
|
||||
return oakengine_undo_command_create(
|
||||
name,
|
||||
[](void *userdata) {
|
||||
static_cast<Cmd *>(userdata)->redo();
|
||||
},
|
||||
[](void *userdata) {
|
||||
static_cast<Cmd *>(userdata)->undo();
|
||||
},
|
||||
[](void *userdata) {
|
||||
delete static_cast<Cmd *>(userdata);
|
||||
},
|
||||
cmd);
|
||||
}
|
||||
|
||||
} // namespace olive
|
||||
|
||||
#endif // OAK_UNDOWRAPPER_H
|
||||
@@ -1,47 +0,0 @@
|
||||
/***
|
||||
|
||||
Oak - Non-Linear Video Editor
|
||||
Copyright (C) 2026 Oak 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/>.
|
||||
|
||||
***/
|
||||
|
||||
// App-side implementation of xml_read_next_start_element
|
||||
// Provides a local definition so the app doesn't import this from liboakengine.
|
||||
|
||||
#include "oakutil/xmlutils.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
bool xml_read_next_start_element(QXmlStreamReader *reader,
|
||||
void *cancel_atom)
|
||||
{
|
||||
QXmlStreamReader::TokenType token;
|
||||
|
||||
while ((token = reader->readNext()) != QXmlStreamReader::Invalid &&
|
||||
token != QXmlStreamReader::EndDocument &&
|
||||
(!cancel_atom)) {
|
||||
if (reader->isEndElement()) {
|
||||
return false;
|
||||
} else if (reader->isStartElement()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
-1589
File diff suppressed because it is too large
Load Diff
-381
@@ -1,381 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 OAK_CORE_H
|
||||
#define OAK_CORE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <olive/core/core.h>
|
||||
#include "common/tooltypes.h"
|
||||
#include "oakutil/qtutils.h"
|
||||
#include "oakengine/app.h"
|
||||
#include "oakengine/node.h"
|
||||
#include "oakengine/project.h"
|
||||
#include "oakengine/timeline.h"
|
||||
#include "oakengine/undo.h"
|
||||
#include "oakengine/init.h"
|
||||
#include "oakengine/task.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
using namespace core;
|
||||
|
||||
class MainWindow;
|
||||
|
||||
/**
|
||||
* @brief The main central Olive application instance_
|
||||
*
|
||||
* This is the UI-facing application controller. It holds an EngineCore
|
||||
* member for UI-independent engine state and adds the main window, dialogs
|
||||
* and other user interaction on top of it.
|
||||
*
|
||||
* EngineCore is NOT a base class — it is a member, so the MOC-generated
|
||||
* code for Core does not pull in EngineCore's Q_OBJECT symbols.
|
||||
*
|
||||
* The "public slots" are usually user-triggered actions and can be connected to UI elements (e.g. creating a folder,
|
||||
* opening the import dialog, etc.)
|
||||
*/
|
||||
class Core : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
/**
|
||||
* @brief Core Constructor
|
||||
*
|
||||
* Creates the EngineCore engine instance and registers the UI handlers
|
||||
* that the engine uses to request user interaction.
|
||||
*/
|
||||
Core(const OakEngineAppParams *params = nullptr);
|
||||
|
||||
~Core()
|
||||
{
|
||||
instance_ = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Core object accessible from anywhere in the code
|
||||
*
|
||||
* Returns the application Core singleton (no EngineCore::instance() call).
|
||||
*/
|
||||
static Core *instance()
|
||||
{
|
||||
return instance_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Start Olive Core
|
||||
*
|
||||
* Main application launcher. Starts the engine first, then the GUI (if entering a GUI mode).
|
||||
*/
|
||||
void start();
|
||||
|
||||
/**
|
||||
* @brief Stop Olive Core
|
||||
*
|
||||
* Tears down the UI services first, then the engine, ready for the application to exit.
|
||||
*/
|
||||
void stop();
|
||||
|
||||
/**
|
||||
* @brief Retrieve main window instance_
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* Pointer to the olive::MainWindow object, or nullptr if running in CLI mode.
|
||||
*/
|
||||
MainWindow *main_window();
|
||||
|
||||
/**
|
||||
* @brief Import a list of files
|
||||
*
|
||||
* FIXME: I kind of hate this, it needs a model to update correctly. Is there a way that Items can signal enough to
|
||||
* make passing references to the model unnecessary?
|
||||
*
|
||||
* @param urls
|
||||
*/
|
||||
void import_files(const QStringList &urls, OakEngineNode *parent);
|
||||
|
||||
/**
|
||||
* @brief Get the currently active project
|
||||
*
|
||||
* Uses the UI/Panel system to determine which Project was the last focused on and assumes this is the active Project
|
||||
* that the user wishes to work on.
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* The active Project file, or nullptr if the heuristic couldn't find one.
|
||||
*/
|
||||
OakEngineProject *get_active_project() const;
|
||||
OakEngineNode *get_selected_folder_in_active_project() const;
|
||||
|
||||
/**
|
||||
* @brief Show a dialog to the user to rename a set of nodes
|
||||
*/
|
||||
bool label_nodes(const QVector<OakEngineNode *> &nodes,
|
||||
void *parent = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Opens a project from the recently opened list
|
||||
*/
|
||||
void open_project_from_recent_list(int index);
|
||||
|
||||
/**
|
||||
* @brief Closes a project
|
||||
*/
|
||||
bool close_project(bool auto_open_new, bool ignore_modified = false);
|
||||
|
||||
/**
|
||||
* @brief Runs a modal cache task on the currently active sequence
|
||||
*/
|
||||
void cache_active_sequence(bool in_out_only);
|
||||
|
||||
void open_recovery_project(const QString &filename);
|
||||
|
||||
void open_node_in_viewer(OakEngineNode *viewer);
|
||||
|
||||
void open_export_dialog_for_viewer(OakEngineNode *viewer,
|
||||
bool start_still_image);
|
||||
|
||||
bool add_open_project_from_task(OakEngineTask *task, bool add_to_recents);
|
||||
bool add_recovery_project_from_task(OakEngineTask *task);
|
||||
|
||||
public slots:
|
||||
/**
|
||||
* @brief Starts an open file dialog to load a project from file
|
||||
*/
|
||||
void open_project();
|
||||
|
||||
/**
|
||||
* @brief Saves the current project
|
||||
*/
|
||||
bool save_project();
|
||||
|
||||
/**
|
||||
* @brief Performs a "save as" on the current project
|
||||
*/
|
||||
bool save_project_as();
|
||||
|
||||
void revert_project();
|
||||
|
||||
/**
|
||||
* @brief Show an About dialog
|
||||
*/
|
||||
void dialog_about_show();
|
||||
|
||||
/**
|
||||
* @brief Open the import footage dialog and import the files selected (runs ImportFiles())
|
||||
*/
|
||||
void dialog_import_show();
|
||||
|
||||
/**
|
||||
* @brief Show Preferences dialog
|
||||
*/
|
||||
void dialog_preferences_show(int start_tab = 0);
|
||||
|
||||
/**
|
||||
* @brief Show Project Properties dialog
|
||||
*/
|
||||
void dialog_project_properties_show();
|
||||
|
||||
/**
|
||||
* @brief Show Export dialog
|
||||
*/
|
||||
void dialog_export_show();
|
||||
|
||||
/**
|
||||
* @brief Create a new folder in the currently active project
|
||||
*/
|
||||
void create_new_folder();
|
||||
|
||||
/**
|
||||
* @brief Create a new sequence in the currently active project
|
||||
*/
|
||||
void create_new_sequence();
|
||||
|
||||
void check_for_auto_recoveries();
|
||||
|
||||
void browse_auto_recoveries();
|
||||
|
||||
public:
|
||||
// The following methods are ordinary member functions, NOT slots. They are
|
||||
// deliberately kept out of the `public slots:` section; none of them are
|
||||
// connect() targets: every connection involving Core uses the new-style
|
||||
// member-function syntax, which works with plain methods.
|
||||
|
||||
/**
|
||||
* @brief Show OTIO import dialog
|
||||
*/
|
||||
#ifdef USE_OTIO
|
||||
bool DialogImportOTIOShow(const QList<OakEngineSequence *> &sequences);
|
||||
#endif
|
||||
|
||||
// ---- Facade-wrapping methods (shadow EngineCore to avoid symbol refs) ----
|
||||
|
||||
Tool::Item tool() const;
|
||||
void set_tool(const Tool::Item &tool);
|
||||
|
||||
bool snapping() const;
|
||||
void set_snapping(const bool &b);
|
||||
|
||||
Timecode::Display get_timecode_display() const;
|
||||
void set_timecode_display(Timecode::Display d);
|
||||
|
||||
void show_status_bar_message(const QString &s, int timeout = 0);
|
||||
void clear_status_bar_message();
|
||||
|
||||
static QString footage_file_dialog_filter();
|
||||
static bool is_footage_extension_allowed(const QString &path);
|
||||
|
||||
void create_new_project();
|
||||
OakEngineSequence *create_new_sequence_for_project(const QString &format,
|
||||
OakEngineProject *project);
|
||||
static OakEngineSequence *create_new_sequence_for_project(OakEngineProject *project);
|
||||
|
||||
void clear_open_recent_list();
|
||||
void set_use_proxy_media(bool enabled);
|
||||
|
||||
void request_pixel_sampling_in_viewers(bool e);
|
||||
|
||||
Tool::AddableObject get_selected_addable_object() const;
|
||||
void set_selected_addable_object(const Tool::AddableObject &obj);
|
||||
void set_selected_transition_object(const QString &obj);
|
||||
|
||||
static void copy_string_to_clipboard(const QString &s);
|
||||
|
||||
void set_magic(bool e);
|
||||
|
||||
// Recent project list accessors (replaces EngineCore::get_recent_projects())
|
||||
int get_recent_project_count() const;
|
||||
QString get_recent_project_at(int index) const;
|
||||
|
||||
// Facade-wrapping methods (delegate through the C ABI)
|
||||
|
||||
bool set_language(const QString &locale);
|
||||
void set_autorecovery_interval(int minutes);
|
||||
|
||||
void on_project_saved(OakEngineProject *p);
|
||||
static QString get_auto_recovery_index_filename();
|
||||
void add_open_project(OakEngineProject *p, bool add_to_recents = false);
|
||||
void remove_recently_opened_project(int index);
|
||||
void set_active_project(OakEngineProject *p);
|
||||
QString get_selected_transition() const;
|
||||
|
||||
signals:
|
||||
// Forwarding signals (shadow EngineCore signals so connect() resolves here)
|
||||
void tool_changed(const Tool::Item &tool);
|
||||
void addable_object_changed(Tool::AddableObject o);
|
||||
void snapping_changed(const bool &b);
|
||||
void timecode_display_changed(Timecode::Display d);
|
||||
void open_recent_list_changed();
|
||||
void color_picker_enabled(bool e);
|
||||
void color_picker_color_emitted(const Color &reference, const Color &display);
|
||||
|
||||
/**
|
||||
* @brief App-internal re-broadcast of the engine undo-stack index change
|
||||
* (issue 7 of the EventBridge elimination plan). Widgets connect to this
|
||||
* instead of raw oakengine_event_subscribe callbacks on
|
||||
* OAKENGINE_EVENT_UNDO_INDEX_CHANGED. The argument is the new stack index.
|
||||
*/
|
||||
void undo_index_changed(int index);
|
||||
|
||||
/**
|
||||
* @brief App-internal re-broadcast of the active project's modified-flag
|
||||
* change (issue 8 of the EventBridge elimination plan). The main window
|
||||
* drives setWindowModified from this instead of a raw
|
||||
* oakengine_event_subscribe callback on
|
||||
* OAKENGINE_EVENT_PROJECT_MODIFIED_CHANGED.
|
||||
*/
|
||||
void project_modified_changed(bool modified);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Get the file filter than can be used with QFileDialog to open and save compatible projects
|
||||
*/
|
||||
static QString get_project_filter(bool include_any_filter);
|
||||
|
||||
/**
|
||||
* @brief Start GUI portion of Olive
|
||||
*
|
||||
* Starts services and objects required for the GUI of Olive. It's guaranteed that running without this function will
|
||||
* create an application instance_ that is completely valid minus the UI (e.g. for CLI modes).
|
||||
*/
|
||||
void start_gui(bool full_screen);
|
||||
|
||||
/**
|
||||
* @brief Internal function for saving a project to a file
|
||||
*/
|
||||
void save_project_internal(const QString &override_filename = QString());
|
||||
|
||||
/**
|
||||
* @brief Retrieves the currently most active sequence for exporting
|
||||
*/
|
||||
OakEngineNode *get_sequence_to_export();
|
||||
|
||||
bool revert_project_internal(bool by_opening_existing);
|
||||
|
||||
/**
|
||||
* @brief Shows the "disk cache full" warning (connected to EngineCore::cache_full_warning_requested)
|
||||
*/
|
||||
void show_cache_full_warning();
|
||||
|
||||
/**
|
||||
* @brief Applies a new active project to the main window (connected to EngineCore::active_project_changed)
|
||||
*/
|
||||
void on_active_project_changed(OakEngineProject *p);
|
||||
|
||||
/**
|
||||
* @brief Internal main window object
|
||||
*/
|
||||
MainWindow *main_window_;
|
||||
|
||||
/**
|
||||
* @brief Cached Core* singleton
|
||||
*/
|
||||
static Core *instance_;
|
||||
|
||||
private slots:
|
||||
void project_save_succeeded(OakEngineTask *task);
|
||||
|
||||
bool add_open_project_from_task_and_add_to_recents(OakEngineTask *task)
|
||||
{
|
||||
return instance()->add_open_project_from_task(task, true);
|
||||
}
|
||||
|
||||
void import_task_complete(OakEngineTask *task);
|
||||
|
||||
bool confirm_image_sequence(const QString &filename);
|
||||
|
||||
bool start_headless_export();
|
||||
|
||||
void open_startup_project();
|
||||
|
||||
/**
|
||||
* @brief Internal project open
|
||||
*/
|
||||
void open_project_internal(const QString &filename,
|
||||
bool recovery_project = false);
|
||||
|
||||
void import_single_file(const QString &f);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_CORE_H
|
||||
@@ -1,68 +0,0 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive 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/>.
|
||||
|
||||
# Create crash handler executable
|
||||
add_executable(
|
||||
olive-crashhandler
|
||||
crashhandler.cpp
|
||||
crashhandler.h
|
||||
$<TARGET_OBJECTS:olive-version-obj>
|
||||
)
|
||||
|
||||
# Rename the generated binary to oak-crashhandler so it matches the Oak branding.
|
||||
set_target_properties(olive-crashhandler PROPERTIES OUTPUT_NAME "oak-crashhandler")
|
||||
|
||||
# Disable console appearing on crash handler dialog
|
||||
set_target_properties(olive-crashhandler PROPERTIES
|
||||
WIN32_EXECUTABLE TRUE
|
||||
)
|
||||
|
||||
# Set crash handler includes
|
||||
target_include_directories(
|
||||
olive-crashhandler
|
||||
PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/app
|
||||
${CRASHPAD_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
# Set crash handler libs
|
||||
target_link_libraries(
|
||||
olive-crashhandler
|
||||
PRIVATE
|
||||
Qt${QT_VERSION_MAJOR}::Core
|
||||
Qt${QT_VERSION_MAJOR}::Gui
|
||||
Qt${QT_VERSION_MAJOR}::Widgets
|
||||
Qt${QT_VERSION_MAJOR}::Network
|
||||
${CRASHPAD_LIBRARIES}
|
||||
)
|
||||
|
||||
set(CRASHPAD_HANDLER "crashpad_handler${CMAKE_EXECUTABLE_SUFFIX}")
|
||||
set(MINIDUMP_STACKWALK "minidump_stackwalk${CMAKE_EXECUTABLE_SUFFIX}")
|
||||
|
||||
if (APPLE)
|
||||
# Move crash handler executables inside Mac app bundle
|
||||
add_custom_command(TARGET olive-crashhandler POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:olive-crashhandler> $<TARGET_FILE_DIR:olive-editor>
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CRASHPAD_LIBRARY_DIRS}/${CRASHPAD_HANDLER} $<TARGET_FILE_DIR:olive-editor>
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${BREAKPAD_BIN_DIR}/${MINIDUMP_STACKWALK} $<TARGET_FILE_DIR:olive-editor>
|
||||
)
|
||||
elseif (UNIX)
|
||||
install(TARGETS olive-crashhandler RUNTIME DESTINATION bin)
|
||||
install(PROGRAMS ${CRASHPAD_LIBRARY_DIRS}/${CRASHPAD_HANDLER} DESTINATION bin)
|
||||
install(PROGRAMS ${BREAKPAD_BIN_DIR}/${MINIDUMP_STACKWALK} DESTINATION bin)
|
||||
endif ()
|
||||
|
||||
target_compile_definitions(olive-crashhandler PRIVATE ${OLIVE_DEFINITIONS})
|
||||
@@ -1,410 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 "crashhandler.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QCloseEvent>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFontDatabase>
|
||||
#include <QLabel>
|
||||
#include <QHttpMultiPart>
|
||||
#include <QMessageBox>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QProcess>
|
||||
#include <QScrollBar>
|
||||
#include <QSplitter>
|
||||
#include <QThread>
|
||||
#include <QTimer>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "oakutil/crashpadutils.h"
|
||||
#include "oakutil/filefunctions.h"
|
||||
#include "version.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
CrashHandlerDialog::CrashHandlerDialog(const QString &report_path)
|
||||
{
|
||||
setWindowTitle(tr("Oak Video Editor"));
|
||||
setWindowFlags(Qt::WindowStaysOnTopHint);
|
||||
|
||||
report_filename_ = report_path;
|
||||
waiting_for_upload_ = false;
|
||||
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
|
||||
layout->addWidget(new QLabel(tr(
|
||||
"We're sorry, Oak Video Editor has crashed. Please help us fix it by "
|
||||
"sending an error report.")));
|
||||
|
||||
QSplitter *splitter = new QSplitter(Qt::Vertical);
|
||||
splitter->setChildrenCollapsible(false);
|
||||
layout->addWidget(splitter);
|
||||
|
||||
summary_edit_ = new QTextEdit();
|
||||
summary_edit_->setPlaceholderText(
|
||||
tr("Describe what you were doing in as much detail as "
|
||||
"possible. If you can, provide steps to reproduce this crash."));
|
||||
|
||||
splitter->addWidget(summary_edit_);
|
||||
|
||||
QWidget *crash_widget = new QWidget();
|
||||
QVBoxLayout *crash_widget_layout = new QVBoxLayout(crash_widget);
|
||||
crash_widget_layout->setMargin(0);
|
||||
|
||||
crash_widget_layout->addWidget(new QLabel(tr("Crash Report:")));
|
||||
|
||||
crash_report_ = new QTextEdit();
|
||||
crash_report_->setReadOnly(true);
|
||||
crash_report_->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
|
||||
crash_widget_layout->addWidget(crash_report_);
|
||||
|
||||
splitter->addWidget(crash_widget);
|
||||
|
||||
QHBoxLayout *btn_layout = new QHBoxLayout();
|
||||
btn_layout->setMargin(0);
|
||||
btn_layout->addStretch();
|
||||
|
||||
send_report_btn_ = new QPushButton(tr("Send Error Report"));
|
||||
connect(send_report_btn_, &QPushButton::clicked, this,
|
||||
&CrashHandlerDialog::SendErrorReport);
|
||||
btn_layout->addWidget(send_report_btn_);
|
||||
|
||||
dont_send_btn_ = new QPushButton(tr("Don't Send"));
|
||||
connect(dont_send_btn_, &QPushButton::clicked, this,
|
||||
&CrashHandlerDialog::reject);
|
||||
btn_layout->addWidget(dont_send_btn_);
|
||||
|
||||
layout->addLayout(btn_layout);
|
||||
|
||||
crash_report_->setEnabled(false);
|
||||
send_report_btn_->setEnabled(false);
|
||||
|
||||
crash_report_->setText(tr("Waiting for crash report to be generated..."));
|
||||
|
||||
GenerateReport();
|
||||
}
|
||||
|
||||
void CrashHandlerDialog::SetGUIObjectsEnabled(bool e)
|
||||
{
|
||||
summary_edit_->setEnabled(e);
|
||||
crash_report_->setEnabled(e);
|
||||
send_report_btn_->setEnabled(e);
|
||||
dont_send_btn_->setEnabled(e);
|
||||
}
|
||||
|
||||
QString CrashHandlerDialog::GetSymbolPath()
|
||||
{
|
||||
QDir app_path(qApp->applicationDirPath());
|
||||
QString symbols_path;
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
symbols_path = app_path.filePath(QStringLiteral("symbols"));
|
||||
#elif BUILDFLAG(IS_LINUX)
|
||||
app_path.cdUp();
|
||||
symbols_path =
|
||||
app_path.filePath(QStringLiteral("share/oak-editor/symbols"));
|
||||
#elif BUILDFLAG(IS_APPLE)
|
||||
app_path.cdUp();
|
||||
symbols_path = app_path.filePath(QStringLiteral("Resources/symbols"));
|
||||
#endif
|
||||
|
||||
return symbols_path;
|
||||
}
|
||||
|
||||
void CrashHandlerDialog::GenerateReport()
|
||||
{
|
||||
QProcess *p = new QProcess();
|
||||
|
||||
connect(p, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
|
||||
this, &CrashHandlerDialog::ReadProcessFinished);
|
||||
connect(p, &QProcess::readyReadStandardOutput, this,
|
||||
&CrashHandlerDialog::ReadProcessHasData);
|
||||
|
||||
QString stackwalk_filename =
|
||||
FileFunctions::GetFormattedExecutableForPlatform(
|
||||
QStringLiteral("minidump_stackwalk"));
|
||||
|
||||
QString stackwalk_bin =
|
||||
QDir(qApp->applicationDirPath()).filePath(stackwalk_filename);
|
||||
p->start(stackwalk_bin, { report_filename_, GetSymbolPath() });
|
||||
crash_report_->setText(
|
||||
QStringLiteral("Trying to run: %1").arg(stackwalk_bin));
|
||||
}
|
||||
|
||||
void CrashHandlerDialog::ReplyFinished(QNetworkReply *reply)
|
||||
{
|
||||
waiting_for_upload_ = false;
|
||||
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
// Close dialog
|
||||
QDialog::accept();
|
||||
} else {
|
||||
QMessageBox b(this);
|
||||
b.setIcon(QMessageBox::Critical);
|
||||
b.setWindowModality(Qt::WindowModal);
|
||||
b.setWindowTitle(tr("Upload Failed"));
|
||||
b.setText(
|
||||
tr("Failed to send error report (%1). Please try again later.")
|
||||
.arg(QString::number(reply->error())));
|
||||
b.addButton(QMessageBox::Ok);
|
||||
b.exec();
|
||||
|
||||
SetGUIObjectsEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
void CrashHandlerDialog::HandleSslErrors(QNetworkReply *reply,
|
||||
const QList<QSslError> &se)
|
||||
{
|
||||
QStringList errors;
|
||||
for (const QSslError &err : se) {
|
||||
errors.append(err.errorString());
|
||||
}
|
||||
|
||||
QMessageBox b(this);
|
||||
b.setIcon(QMessageBox::Critical);
|
||||
b.setWindowModality(Qt::WindowModal);
|
||||
b.setWindowTitle(tr("SSL Error"));
|
||||
b.setText(tr("Encountered the following SSL errors:\n\n%1")
|
||||
.arg(errors.join('\n')));
|
||||
b.addButton(QMessageBox::Ok);
|
||||
b.exec();
|
||||
}
|
||||
|
||||
void CrashHandlerDialog::AttemptToFindReport()
|
||||
{
|
||||
// If we found it, use it, otherwise wait a second and try again
|
||||
if (report_filename_.isEmpty()) {
|
||||
// Couldn't find report, try again in one second
|
||||
QTimer::singleShot(500, this, &CrashHandlerDialog::AttemptToFindReport);
|
||||
} else {
|
||||
GenerateReport();
|
||||
}
|
||||
}
|
||||
|
||||
void CrashHandlerDialog::ReadProcessHasData()
|
||||
{
|
||||
report_data_.append(
|
||||
static_cast<QProcess *>(sender())->readAllStandardOutput());
|
||||
}
|
||||
|
||||
void CrashHandlerDialog::ReadProcessFinished()
|
||||
{
|
||||
SetGUIObjectsEnabled(true);
|
||||
crash_report_->setText(report_data_);
|
||||
delete sender();
|
||||
}
|
||||
|
||||
void CrashHandlerDialog::SendErrorReport()
|
||||
{
|
||||
if (summary_edit_->document()->isEmpty()) {
|
||||
QMessageBox b(this);
|
||||
b.setIcon(QMessageBox::Question);
|
||||
b.setWindowModality(Qt::WindowModal);
|
||||
b.setText(
|
||||
tr("You must write a description to submit this crash report."));
|
||||
b.addButton(QMessageBox::Ok);
|
||||
b.exec();
|
||||
return;
|
||||
}
|
||||
|
||||
QNetworkAccessManager *manager = new QNetworkAccessManager();
|
||||
connect(manager, &QNetworkAccessManager::finished, this,
|
||||
&CrashHandlerDialog::ReplyFinished);
|
||||
connect(manager, &QNetworkAccessManager::sslErrors, this,
|
||||
&CrashHandlerDialog::HandleSslErrors);
|
||||
|
||||
QNetworkRequest request;
|
||||
request.setSslConfiguration(QSslConfiguration::defaultConfiguration());
|
||||
request.setUrl(
|
||||
QStringLiteral("https://olivevideoeditor.org/crashpad/report.php"));
|
||||
|
||||
// Create HTTP form
|
||||
QHttpMultiPart *multipart =
|
||||
new QHttpMultiPart(QHttpMultiPart::FormDataType);
|
||||
|
||||
// Create description section
|
||||
QHttpPart desc_part;
|
||||
desc_part.setHeader(QNetworkRequest::ContentTypeHeader,
|
||||
QStringLiteral("text/plain; charset=UTF-8"));
|
||||
desc_part.setHeader(QNetworkRequest::ContentDispositionHeader,
|
||||
QStringLiteral("form-data; name=\"description\""));
|
||||
desc_part.setBody(summary_edit_->toPlainText().toUtf8());
|
||||
multipart->append(desc_part);
|
||||
|
||||
// Create report section
|
||||
QHttpPart report_part;
|
||||
report_part.setHeader(QNetworkRequest::ContentTypeHeader,
|
||||
QStringLiteral("text/plain; charset=UTF-8"));
|
||||
report_part.setHeader(QNetworkRequest::ContentDispositionHeader,
|
||||
QStringLiteral("form-data; name=\"report\""));
|
||||
report_part.setBody(report_data_);
|
||||
multipart->append(report_part);
|
||||
|
||||
// Create commit section
|
||||
QHttpPart commit_part;
|
||||
commit_part.setHeader(QNetworkRequest::ContentTypeHeader,
|
||||
QStringLiteral("text/plain; charset=UTF-8"));
|
||||
commit_part.setHeader(QNetworkRequest::ContentDispositionHeader,
|
||||
QStringLiteral("form-data; name=\"commit\""));
|
||||
commit_part.setBody(kAppVersionLong.toUtf8());
|
||||
multipart->append(commit_part);
|
||||
|
||||
// Create dump section
|
||||
QHttpPart dump_part;
|
||||
dump_part.setHeader(QNetworkRequest::ContentTypeHeader,
|
||||
QStringLiteral("application/octet-stream"));
|
||||
dump_part.setHeader(
|
||||
QNetworkRequest::ContentDispositionHeader,
|
||||
QStringLiteral("form-data; name=\"dump\"; filename=\"%1\"")
|
||||
.arg(QFileInfo(report_filename_).fileName()));
|
||||
QFile *dump_file = new QFile(report_filename_);
|
||||
dump_file->open(QFile::ReadOnly);
|
||||
dump_part.setBodyDevice(dump_file);
|
||||
dump_file->setParent(multipart); // Delete file with multipart
|
||||
multipart->append(dump_part);
|
||||
|
||||
// Find symbol file
|
||||
QDir symbol_dir(GetSymbolPath());
|
||||
|
||||
QString symbol_bin_name;
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
symbol_bin_name = QStringLiteral("oak-editor.pdb");
|
||||
#elif BUILDFLAG(IS_APPLE)
|
||||
symbol_bin_name = QStringLiteral("Oak");
|
||||
#else
|
||||
symbol_bin_name = QStringLiteral("oak-editor");
|
||||
#endif
|
||||
symbol_dir = QDir(symbol_dir.filePath(symbol_bin_name));
|
||||
|
||||
QStringList folders_in_symbol_path =
|
||||
symbol_dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||
|
||||
if (folders_in_symbol_path.size() > 0) {
|
||||
symbol_dir = QDir(symbol_dir.filePath(folders_in_symbol_path.first()));
|
||||
} else {
|
||||
QMessageBox b(this);
|
||||
b.setIcon(QMessageBox::Critical);
|
||||
b.setWindowModality(Qt::WindowModal);
|
||||
b.setWindowTitle(tr("Failed to send report"));
|
||||
b.setText(tr("Failed to find symbols necessary to send report. "
|
||||
"This is a packaging issue. Please notify "
|
||||
"the maintainers of this package."));
|
||||
b.addButton(QMessageBox::Ok);
|
||||
b.exec();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create sym section
|
||||
QString symbol_filename;
|
||||
#if BUILDFLAG(IS_APPLE)
|
||||
symbol_filename = QStringLiteral("Oak.sym");
|
||||
#else
|
||||
symbol_filename = QStringLiteral("oak-editor.sym");
|
||||
#endif
|
||||
QString symbol_full_path = symbol_dir.filePath(symbol_filename);
|
||||
QHttpPart sym_part;
|
||||
sym_part.setHeader(QNetworkRequest::ContentTypeHeader,
|
||||
QStringLiteral("application/octet-stream"));
|
||||
sym_part.setHeader(
|
||||
QNetworkRequest::ContentDispositionHeader,
|
||||
QStringLiteral("form-data; name=\"sym\"; filename=\"%1\"")
|
||||
.arg(symbol_filename));
|
||||
QFile sym_file(symbol_full_path);
|
||||
|
||||
if (!sym_file.open(QFile::ReadOnly)) {
|
||||
QMessageBox b(this);
|
||||
b.setIcon(QMessageBox::Critical);
|
||||
b.setWindowModality(Qt::WindowModal);
|
||||
b.setWindowTitle(tr("Failed to send report"));
|
||||
b.setText(tr("Failed to open symbol file. You may not have "
|
||||
"permission to access it."));
|
||||
b.addButton(QMessageBox::Ok);
|
||||
b.exec();
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray symbol_data = qCompress(sym_file.readAll(), 9);
|
||||
sym_file.close();
|
||||
|
||||
sym_part.setBody(symbol_data);
|
||||
multipart->append(sym_part);
|
||||
|
||||
manager->post(request, multipart);
|
||||
|
||||
SetGUIObjectsEnabled(false);
|
||||
waiting_for_upload_ = true;
|
||||
}
|
||||
|
||||
void CrashHandlerDialog::closeEvent(QCloseEvent *e)
|
||||
{
|
||||
QMessageBox b(this);
|
||||
b.setIcon(QMessageBox::Warning);
|
||||
b.setWindowModality(Qt::WindowModal);
|
||||
b.setWindowTitle(tr("Confirm Close"));
|
||||
b.setText(
|
||||
tr("Crash report is still uploading. Closing now may result in no "
|
||||
"report being sent. Are you sure you wish to close?"));
|
||||
b.addButton(QMessageBox::Ok);
|
||||
b.addButton(QMessageBox::Cancel);
|
||||
|
||||
if (waiting_for_upload_ && b.exec() == QMessageBox::Cancel) {
|
||||
e->ignore();
|
||||
} else {
|
||||
e->accept();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QString report;
|
||||
|
||||
#ifdef Q_OS_WINDOWS
|
||||
int num_args;
|
||||
LPWSTR *args = CommandLineToArgvW(GetCommandLineW(), &num_args);
|
||||
if (num_args < 2) {
|
||||
LocalFree(args);
|
||||
return 1;
|
||||
}
|
||||
|
||||
report = QString::fromWCharArray(args[1]);
|
||||
LocalFree(args);
|
||||
#else
|
||||
if (argc < 2) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
report = argv[1];
|
||||
#endif
|
||||
|
||||
QApplication a(argc, argv);
|
||||
|
||||
olive::CrashHandlerDialog chd(report);
|
||||
chd.open();
|
||||
|
||||
return a.exec();
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 OAK_CRASHHANDLERDIALOG_H
|
||||
#define OAK_CRASHHANDLERDIALOG_H
|
||||
|
||||
#include <client/crash_report_database.h>
|
||||
#include <QDialog>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QNetworkReply>
|
||||
#include <QPushButton>
|
||||
#include <QTextEdit>
|
||||
|
||||
#include "oakutil/define.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class CrashHandlerDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
CrashHandlerDialog(const QString &report_path);
|
||||
|
||||
private:
|
||||
void SetGUIObjectsEnabled(bool e);
|
||||
|
||||
void GenerateReport();
|
||||
|
||||
static QString GetSymbolPath();
|
||||
|
||||
QTextEdit *summary_edit_;
|
||||
|
||||
QTextEdit *crash_report_;
|
||||
|
||||
QPushButton *send_report_btn_;
|
||||
|
||||
QPushButton *dont_send_btn_;
|
||||
|
||||
QString report_filename_;
|
||||
|
||||
QByteArray report_data_;
|
||||
|
||||
bool waiting_for_upload_;
|
||||
|
||||
protected:
|
||||
virtual void closeEvent(QCloseEvent *e) override;
|
||||
|
||||
private slots:
|
||||
void ReplyFinished(QNetworkReply *reply);
|
||||
|
||||
void HandleSslErrors(QNetworkReply *reply, const QList<QSslError> &errors);
|
||||
|
||||
void AttemptToFindReport();
|
||||
|
||||
void ReadProcessHasData();
|
||||
|
||||
void ReadProcessFinished();
|
||||
|
||||
void SendErrorReport();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_CRASHHANDLERDIALOG_H
|
||||
@@ -1,47 +0,0 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive 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/>.
|
||||
|
||||
add_subdirectory(about)
|
||||
add_subdirectory(actionsearch)
|
||||
add_subdirectory(autorecovery)
|
||||
add_subdirectory(color)
|
||||
add_subdirectory(configbase)
|
||||
add_subdirectory(diskcache)
|
||||
add_subdirectory(export)
|
||||
add_subdirectory(footageproperties)
|
||||
add_subdirectory(footagerelink)
|
||||
add_subdirectory(keyframeproperties)
|
||||
add_subdirectory(markerproperties)
|
||||
if (OpenTimelineIO_FOUND)
|
||||
add_subdirectory(otioproperties)
|
||||
endif ()
|
||||
add_subdirectory(preferences)
|
||||
add_subdirectory(progress)
|
||||
add_subdirectory(projectimport)
|
||||
add_subdirectory(proxy)
|
||||
add_subdirectory(projectproperties)
|
||||
add_subdirectory(rendercancel)
|
||||
add_subdirectory(sequence)
|
||||
add_subdirectory(speedduration)
|
||||
add_subdirectory(task)
|
||||
add_subdirectory(text)
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
dialog/ratiodialog.cpp
|
||||
dialog/ratiodialog.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -1,25 +0,0 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive 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/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
dialog/about/about.cpp
|
||||
dialog/about/about.h
|
||||
dialog/about/patreon.h
|
||||
dialog/about/scrollinglabel.cpp
|
||||
dialog/about/scrollinglabel.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -1,159 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 "about.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QLabel>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "common/configwrapper.h"
|
||||
#include "patreon.h"
|
||||
#include "scrollinglabel.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
AboutDialog::AboutDialog(bool welcome_dialog, QWidget *parent)
|
||||
: QDialog(parent)
|
||||
{
|
||||
if (welcome_dialog) {
|
||||
setWindowTitle(
|
||||
tr("Welcome to %1").arg(QApplication::applicationName()));
|
||||
} else {
|
||||
setWindowTitle(tr("About %1").arg(QApplication::applicationName()));
|
||||
}
|
||||
|
||||
QFontMetrics fm = fontMetrics();
|
||||
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
layout->setContentsMargins(fm.height(), fm.height(), fm.height(),
|
||||
fm.height());
|
||||
|
||||
QHBoxLayout *horiz_layout = new QHBoxLayout();
|
||||
horiz_layout->setContentsMargins(fm.height(), fm.height(), fm.height(),
|
||||
fm.height());
|
||||
horiz_layout->setSpacing(fm.height() * 2);
|
||||
|
||||
QLabel *icon = new QLabel(
|
||||
QStringLiteral("<html><img src=':/graphics/oak-logo.png'></html>"));
|
||||
icon->setAlignment(Qt::AlignCenter);
|
||||
horiz_layout->addWidget(icon);
|
||||
|
||||
// Construct About text
|
||||
QLabel *label = new QLabel(
|
||||
QStringLiteral("<html><head/><body>"
|
||||
"<p><b>%1</b> %2</p>" // AppName (version identifier)
|
||||
"<p>%3</p>" // Description
|
||||
"<p>%4</p>" // Fork notice
|
||||
"<p>%5</p>" // Special thanks
|
||||
"</body></html>")
|
||||
.arg(
|
||||
QApplication::applicationName(),
|
||||
QApplication::applicationVersion(),
|
||||
tr("Oak Video Editor is a free open source non-linear video editor. "
|
||||
"This software is licensed under the GNU GPL Version 3."),
|
||||
tr("This project is a fork of "
|
||||
"<a href=\"https://github.com/olive-editor/olive\">Olive Video Editor</a>."),
|
||||
tr("Special thanks to Enzo GD, administrator of the Olive "
|
||||
"Facebook user group, for his generous support in spreading "
|
||||
"the word about this project in its early days.")));
|
||||
|
||||
// Set text formatting
|
||||
label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||
label->setWordWrap(true);
|
||||
label->setOpenExternalLinks(true);
|
||||
label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
|
||||
label->setTextInteractionFlags(Qt::TextSelectableByMouse |
|
||||
Qt::LinksAccessibleByMouse);
|
||||
label->setCursor(Qt::IBeamCursor);
|
||||
horiz_layout->addWidget(label);
|
||||
|
||||
layout->addLayout(horiz_layout);
|
||||
|
||||
// Patrons where possible
|
||||
layout->addWidget(new QLabel());
|
||||
|
||||
QString opening_statement;
|
||||
|
||||
if (welcome_dialog || patrons.isEmpty()) {
|
||||
opening_statement = tr(
|
||||
"<b>Oak Video Editor relies on support from the community to continue its development.</b>");
|
||||
} else {
|
||||
opening_statement = tr(
|
||||
"Oak Video Editor wouldn't be possible without the support of gracious donations from the following people.");
|
||||
}
|
||||
|
||||
QLabel *support_lbl = new QLabel(
|
||||
tr("<html>%1 "
|
||||
"If you like this project, please consider making a "
|
||||
"one-time donation or pledging monthly to support its development.</html>")
|
||||
.arg(opening_statement));
|
||||
support_lbl->setWordWrap(true);
|
||||
support_lbl->setAlignment(Qt::AlignCenter);
|
||||
support_lbl->setOpenExternalLinks(true);
|
||||
layout->addWidget(support_lbl);
|
||||
|
||||
if (!patrons.isEmpty()) {
|
||||
ScrollingLabel *scroll = new ScrollingLabel(patrons);
|
||||
scroll->start_animating();
|
||||
layout->addWidget(scroll);
|
||||
}
|
||||
|
||||
layout->addWidget(new QLabel());
|
||||
|
||||
QHBoxLayout *btn_layout = new QHBoxLayout();
|
||||
btn_layout->setContentsMargins(0, 0, 0, 0);
|
||||
btn_layout->setSpacing(0);
|
||||
|
||||
if (welcome_dialog) {
|
||||
dont_show_again_checkbox_ =
|
||||
new QCheckBox(tr("Don't show this message again"));
|
||||
btn_layout->addWidget(dont_show_again_checkbox_);
|
||||
} else {
|
||||
dont_show_again_checkbox_ = nullptr;
|
||||
}
|
||||
|
||||
QDialogButtonBox *buttons =
|
||||
new QDialogButtonBox(QDialogButtonBox::Ok, this);
|
||||
if (!welcome_dialog) {
|
||||
buttons->setCenterButtons(true);
|
||||
}
|
||||
btn_layout->addWidget(buttons);
|
||||
|
||||
layout->addLayout(btn_layout);
|
||||
|
||||
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
|
||||
setFixedSize(sizeHint());
|
||||
}
|
||||
|
||||
void AboutDialog::accept()
|
||||
{
|
||||
if (dont_show_again_checkbox_ && dont_show_again_checkbox_->isChecked()) {
|
||||
OAK_CONFIG("ShowWelcomeDialog") = false;
|
||||
}
|
||||
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 OAK_ABOUTDIALOG_H
|
||||
#define OAK_ABOUTDIALOG_H
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QDialog>
|
||||
|
||||
#include "oakutil/define.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief The AboutDialog class
|
||||
*
|
||||
* The About dialog (accessible through Help > About). Contains license and version information. This can be run from
|
||||
* anywhere
|
||||
*/
|
||||
class AboutDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
/**
|
||||
* @brief AboutDialog Constructor
|
||||
*
|
||||
* Creates About dialog.
|
||||
*
|
||||
* @param parent
|
||||
*
|
||||
* QWidget parent object. Usually this will be MainWindow.
|
||||
*/
|
||||
explicit AboutDialog(bool welcome_dialog, QWidget *parent = nullptr);
|
||||
|
||||
public slots:
|
||||
virtual void accept() override;
|
||||
|
||||
private:
|
||||
QCheckBox *dont_show_again_checkbox_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_ABOUTDIALOG_H
|
||||
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* Oak Video Editor - 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 OAK_PATREON_H
|
||||
#define OAK_PATREON_H
|
||||
|
||||
#include <QStringList>
|
||||
|
||||
QStringList patrons;
|
||||
|
||||
#endif // OAK_PATREON_H
|
||||
@@ -1,84 +0,0 @@
|
||||
# Oak Video Editor - 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/>.
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
# /***
|
||||
#
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
# ***/
|
||||
#
|
||||
|
||||
import json
|
||||
import requests
|
||||
import os
|
||||
|
||||
url = 'https://www.patreon.com/api/oauth2/v2/campaigns/1478705/members?include=currently_entitled_tiers&fields%5Bmember%5D=full_name'
|
||||
name_list = ''
|
||||
|
||||
while True:
|
||||
member_data = requests.get(url, headers={"authorization": "Bearer " + os.environ.get('PATREON_KEY')})
|
||||
member_data_decoded = json.loads(member_data.text)
|
||||
|
||||
for member in member_data_decoded["data"]:
|
||||
if len(member["relationships"]["currently_entitled_tiers"]["data"]) > 0:
|
||||
if member["relationships"]["currently_entitled_tiers"]["data"][0]["id"] == "3952333":
|
||||
if len(name_list) > 0:
|
||||
name_list += ',\n'
|
||||
name = member["attributes"]["full_name"]
|
||||
name_list += " QStringLiteral(\""
|
||||
name_list += name.translate(str.maketrans({
|
||||
"\"": "\\\"",
|
||||
"\\": "\\\\"
|
||||
}))
|
||||
name_list += "\")"
|
||||
|
||||
if "links" in member_data_decoded:
|
||||
url = member_data_decoded["links"]["next"]
|
||||
else:
|
||||
break
|
||||
|
||||
text_file = open("patreon.h", "w", encoding="utf-8")
|
||||
text_file.write(
|
||||
"#ifndef PATREON_H\n#define PATREON_H\n\n#include <QStringList>\n\nQStringList patrons = {\n%s\n};\n\n#endif // PATREON_H\n" % name_list)
|
||||
text_file.close()
|
||||
@@ -1,125 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 "scrollinglabel.h"
|
||||
|
||||
#include <QPainter>
|
||||
|
||||
#include "oakutil/qtutils.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
const int ScrollingLabel::k_min_line_height = 10;
|
||||
|
||||
ScrollingLabel::ScrollingLabel(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, animate_(0)
|
||||
{
|
||||
timer_.setInterval(50);
|
||||
connect(&timer_, &QTimer::timeout, this, &ScrollingLabel::animation_update);
|
||||
}
|
||||
|
||||
ScrollingLabel::ScrollingLabel(const QStringList &text, QWidget *parent)
|
||||
: ScrollingLabel(parent)
|
||||
{
|
||||
set_text(text);
|
||||
}
|
||||
|
||||
void ScrollingLabel::set_text(const QStringList &text)
|
||||
{
|
||||
text_ = text;
|
||||
|
||||
QFontMetrics fm = fontMetrics();
|
||||
text_height_ = fm.height();
|
||||
|
||||
int width = 0;
|
||||
foreach (const QString &s, text_) {
|
||||
width = qMax(width, QtUtils::q_font_metrics_width(fm, s));
|
||||
}
|
||||
|
||||
setMinimumSize(width, text_height_ * k_min_line_height);
|
||||
}
|
||||
|
||||
void ScrollingLabel::paintEvent(QPaintEvent *e)
|
||||
{
|
||||
QImage map(width(), height(), QImage::Format_RGBA8888_Premultiplied);
|
||||
map.fill(Qt::transparent);
|
||||
|
||||
{
|
||||
QPainter p(&map);
|
||||
p.setPen(palette().text().color());
|
||||
|
||||
QFontMetrics fm = p.fontMetrics();
|
||||
int half_width = width();
|
||||
|
||||
for (int i = 0; i < text_.size(); i++) {
|
||||
int text_y = fm.ascent() + height() - animate_ + (fm.height() * i);
|
||||
int text_bottom = text_y + fm.descent();
|
||||
int text_top = text_y - fm.ascent();
|
||||
|
||||
if (text_bottom < 0 || text_top >= height()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const QString &s = text_.at(i);
|
||||
|
||||
int width = QtUtils::q_font_metrics_width(fm, s);
|
||||
p.drawText(half_width / 2 - width / 2, text_y, s);
|
||||
}
|
||||
|
||||
for (int y = 0; y < text_height_; y++) {
|
||||
double mul = double(y) / double(text_height_);
|
||||
|
||||
set_opacity_of_scan_line(map.scanLine(y), map.width(), 4, mul);
|
||||
set_opacity_of_scan_line(map.scanLine(map.height() - 1 - y),
|
||||
map.width(), 4, mul);
|
||||
}
|
||||
}
|
||||
|
||||
QPainter wp(this);
|
||||
wp.drawImage(0, 0, map);
|
||||
}
|
||||
|
||||
void ScrollingLabel::set_opacity_of_scan_line(uchar *scan_line, int width,
|
||||
int channels, double mul)
|
||||
{
|
||||
for (int x = 0; x < width; x++) {
|
||||
uchar *pixel = &scan_line[x * 4];
|
||||
|
||||
for (int c = 0; c < channels; c++) {
|
||||
pixel[c] *= mul;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScrollingLabel::animation_update()
|
||||
{
|
||||
animate_++;
|
||||
|
||||
if (animate_ >= height() + text_.size() * text_height_) {
|
||||
animate_ = 0;
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 OAK_SCROLLINGLABEL_H
|
||||
#define OAK_SCROLLINGLABEL_H
|
||||
|
||||
#include <QTimer>
|
||||
#include <QWidget>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class ScrollingLabel : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ScrollingLabel(QWidget *parent = nullptr);
|
||||
ScrollingLabel(const QStringList &text, QWidget *parent = nullptr);
|
||||
|
||||
void set_text(const QStringList &text);
|
||||
|
||||
void start_animating()
|
||||
{
|
||||
timer_.start();
|
||||
}
|
||||
|
||||
void stop_animating()
|
||||
{
|
||||
timer_.stop();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
private:
|
||||
static void set_opacity_of_scan_line(uchar *scan_line, int width, int channels,
|
||||
double mul);
|
||||
|
||||
static const int k_min_line_height;
|
||||
|
||||
QStringList text_;
|
||||
|
||||
int text_height_;
|
||||
|
||||
QTimer timer_;
|
||||
|
||||
int animate_;
|
||||
|
||||
private slots:
|
||||
void animation_update();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_SCROLLINGLABEL_H
|
||||
@@ -1,22 +0,0 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive 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/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
dialog/actionsearch/actionsearch.h
|
||||
dialog/actionsearch/actionsearch.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -1,276 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
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 "actionsearch.h"
|
||||
|
||||
#include <QKeyEvent>
|
||||
#include <QLabel>
|
||||
#include <QMenuBar>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
ActionSearch::ActionSearch(QWidget *parent)
|
||||
: QDialog(parent)
|
||||
, menu_bar_(nullptr)
|
||||
{
|
||||
// ActionSearch requires a parent widget
|
||||
Q_ASSERT(parent != nullptr);
|
||||
|
||||
// Set styling (object name is required for CSS specific to this object)
|
||||
setObjectName("ASDiag");
|
||||
setStyleSheet("#ASDiag{border: 2px solid #808080;}");
|
||||
|
||||
// Size proportionally to the parent (usually MainWindow).
|
||||
resize(parent->width() / 3, parent->height() / 3);
|
||||
|
||||
// Show dialog as a "popup", which will make the dialog close if the user clicks out of it.
|
||||
setWindowFlags(Qt::Popup);
|
||||
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
|
||||
// Construct the main entry text field.
|
||||
ActionSearchEntry *entry_field = new ActionSearchEntry(this);
|
||||
|
||||
// Set the main entry field font size to 1.2x its standard font size.
|
||||
QFont entry_field_font = entry_field->font();
|
||||
entry_field_font.setPointSize(qRound(entry_field_font.pointSize() * 1.2));
|
||||
entry_field->setFont(entry_field_font);
|
||||
|
||||
// Set placeholder text for the main entry field
|
||||
entry_field->setPlaceholderText(tr("Search for action..."));
|
||||
|
||||
// Connect signals/slots
|
||||
connect(entry_field, SIGNAL(textChanged(const QString &)), this,
|
||||
SLOT(search_update(const QString &)));
|
||||
connect(entry_field, SIGNAL(returnPressed()), this, SLOT(perform_action()));
|
||||
|
||||
// moveSelectionUp() and moveSelectionDown() are emitted when the user pressed up or down on the text field.
|
||||
// We override it here to select the upper or lower item in the list.
|
||||
connect(entry_field, SIGNAL(move_selection_up()), this,
|
||||
SLOT(move_selection_up()));
|
||||
connect(entry_field, SIGNAL(move_selection_down()), this,
|
||||
SLOT(move_selection_down()));
|
||||
layout->addWidget(entry_field);
|
||||
|
||||
// Construct list of actions
|
||||
list_widget_ = new ActionSearchList(this);
|
||||
|
||||
// Set list's font to 1.2x its standard font size
|
||||
QFont list_widget_font = list_widget_->font();
|
||||
list_widget_font.setPointSize(qRound(list_widget_font.pointSize() * 1.2));
|
||||
list_widget_->setFont(list_widget_font);
|
||||
|
||||
layout->addWidget(list_widget_);
|
||||
|
||||
connect(list_widget_, SIGNAL(dbl_click()), this, SLOT(perform_action()));
|
||||
|
||||
// Instantly focus on the entry field to allow for fully keyboard operation (if this popup was initiated by keyboard
|
||||
// shortcut for example).
|
||||
entry_field->setFocus();
|
||||
}
|
||||
|
||||
void ActionSearch::set_menu_bar(QMenuBar *menu_bar)
|
||||
{
|
||||
menu_bar_ = menu_bar;
|
||||
}
|
||||
|
||||
void ActionSearch::search_update(const QString &s, const QString &p,
|
||||
QMenu *parent)
|
||||
{
|
||||
// Do nothing if there's no menu bar to work with
|
||||
if (menu_bar_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This function is recursive, using the `parent` parameter to loop through a menu's items. It functions in two
|
||||
// modes - the parent being NULL, meaning it'll get MainWindow's menubar and loop over its menus, and the parent
|
||||
// referring to a menu at which point it'll loop over its actions (and call itself recursively if it finds any
|
||||
// submenus).
|
||||
|
||||
if (parent == nullptr) {
|
||||
// If parent is NULL, we'll pull from the MainWindow's menubar and call this recursively on all of its submenus
|
||||
// (and their submenus).
|
||||
|
||||
// We'll clear all the current items in the list since if we're here, we're just starting.
|
||||
list_widget_->clear();
|
||||
|
||||
QList<QAction *> menus = menu_bar_->actions();
|
||||
|
||||
// Loop through all menus from the menubar and run this function on each one.
|
||||
for (int i = 0; i < menus.size(); i++) {
|
||||
QMenu *menu = menus.at(i)->menu();
|
||||
|
||||
search_update(s, p, menu);
|
||||
}
|
||||
|
||||
// Once we're here, all the recursion/item retrieval is complete. We auto-select the first item for better
|
||||
// keyboard-exclusive functionality.
|
||||
if (list_widget_->count() > 0) {
|
||||
list_widget_->item(0)->setSelected(true);
|
||||
}
|
||||
|
||||
} else {
|
||||
// Parent was not NULL, so we loop over the actions in the menu we were given in `parent`.
|
||||
|
||||
// The list shows a '>' delimited hierarchy of the menus in which this action came from. We construct it here by
|
||||
// adding the current menu's text to the existing hierarchy (passed in `p`).
|
||||
QString menu_text;
|
||||
if (!p.isEmpty())
|
||||
menu_text += p + " > ";
|
||||
menu_text += parent->title().replace(
|
||||
"&", ""); // Strip out any &s used in menu action names
|
||||
|
||||
// Loop over the menu's actions
|
||||
QList<QAction *> actions = parent->actions();
|
||||
for (int i = 0; i < actions.size(); i++) {
|
||||
QAction *a = actions.at(i);
|
||||
|
||||
// Ignore separator actions
|
||||
if (!a->isSeparator()) {
|
||||
if (a->menu() != nullptr) {
|
||||
// If the action is a menu, run this function recursively on it
|
||||
search_update(s, menu_text, a->menu());
|
||||
|
||||
} else {
|
||||
// This is a valid non-separator non-menu action, so check it against the currently entered string.
|
||||
|
||||
// Strip out all &s from the action's name
|
||||
QString comp = a->text().replace("&", "");
|
||||
|
||||
// See if the action's name contains any of the currently entered string
|
||||
if (comp.contains(s, Qt::CaseInsensitive)) {
|
||||
// If so, we add it to the list widget.
|
||||
QListWidgetItem *item = new QListWidgetItem(
|
||||
QStringLiteral("%1\n(%2)").arg(comp, menu_text),
|
||||
list_widget_);
|
||||
|
||||
// Add a pointer to the original QAction in the item's data
|
||||
item->setData(Qt::UserRole + 1,
|
||||
reinterpret_cast<quintptr>(a));
|
||||
|
||||
list_widget_->addItem(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ActionSearch::perform_action()
|
||||
{
|
||||
// Loop over all the items in the list and if we find one that's selected, we trigger it.
|
||||
QList<QListWidgetItem *> selected_items = list_widget_->selectedItems();
|
||||
if (list_widget_->count() > 0 && selected_items.size() > 0) {
|
||||
QListWidgetItem *item = selected_items.at(0);
|
||||
|
||||
// Get QAction pointer from item's data
|
||||
QAction *a = reinterpret_cast<QAction *>(
|
||||
item->data(Qt::UserRole + 1).value<quintptr>());
|
||||
|
||||
a->trigger();
|
||||
}
|
||||
|
||||
// Close this popup
|
||||
accept();
|
||||
}
|
||||
|
||||
void ActionSearch::move_selection_up()
|
||||
{
|
||||
// Here we loop over all the items to find the currently selected one, and then select the one above it. We start
|
||||
// iterating at 1 (instead of 0) to efficiently ignore the first item (since the selection can't go below the very
|
||||
// bottom item).
|
||||
|
||||
int lim = list_widget_->count();
|
||||
for (int i = 1; i < lim; i++) {
|
||||
if (list_widget_->item(i)->isSelected()) {
|
||||
list_widget_->item(i - 1)->setSelected(true);
|
||||
list_widget_->scrollToItem(list_widget_->item(i - 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ActionSearch::move_selection_down()
|
||||
{
|
||||
// Here we loop over all the items to find the currently selected one, and then select the one below it. We limit it
|
||||
// one entry before count() to efficiently ignore the item at the end (since the selection can't go below the very
|
||||
// bottom item).
|
||||
|
||||
int lim = list_widget_->count() - 1;
|
||||
for (int i = 0; i < lim; i++) {
|
||||
if (list_widget_->item(i)->isSelected()) {
|
||||
list_widget_->item(i + 1)->setSelected(true);
|
||||
list_widget_->scrollToItem(list_widget_->item(i + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ActionSearchEntry::ActionSearchEntry(QWidget *parent)
|
||||
: QLineEdit(parent)
|
||||
{
|
||||
}
|
||||
|
||||
bool ActionSearchEntry::event(QEvent *e)
|
||||
{
|
||||
switch (e->type()) {
|
||||
case QEvent::ShortcutOverride:
|
||||
switch (static_cast<QKeyEvent *>(e)->key()) {
|
||||
case Qt::Key_Up:
|
||||
case Qt::Key_Down:
|
||||
e->accept();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case QEvent::KeyPress:
|
||||
// Listen for up/down, otherwise pass the key event to the base class.
|
||||
switch (static_cast<QKeyEvent *>(e)->key()) {
|
||||
case Qt::Key_Up:
|
||||
e->accept();
|
||||
emit move_selection_up();
|
||||
return true;
|
||||
case Qt::Key_Down:
|
||||
e->accept();
|
||||
emit move_selection_down();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return QLineEdit::event(e);
|
||||
}
|
||||
|
||||
ActionSearchList::ActionSearchList(QWidget *parent)
|
||||
: QListWidget(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void ActionSearchList::mouseDoubleClickEvent(QMouseEvent *)
|
||||
{
|
||||
// Indiscriminately emit a signal on any double click
|
||||
emit dbl_click();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
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 OAK_ACTIONSEARCH_H
|
||||
#define OAK_ACTIONSEARCH_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QLineEdit>
|
||||
#include <QListWidget>
|
||||
#include <QMenu>
|
||||
#include <QMenuBar>
|
||||
|
||||
#include "oakutil/define.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class ActionSearchList;
|
||||
|
||||
/**
|
||||
* @brief The ActionSearch class
|
||||
*
|
||||
* A popup window (accessible through Help > Action Search) that allows users to search for a menu command by typing
|
||||
* rather than browsing through the menu bar. This can be created from anywhere provided olive::MainWindow is valid.
|
||||
*/
|
||||
class ActionSearch : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
/**
|
||||
* @brief ActionSearch Constructor
|
||||
*
|
||||
* Create ActionSearch popup.
|
||||
*
|
||||
* @param parent
|
||||
*
|
||||
* QWidget parent. Usually MainWindow.
|
||||
*/
|
||||
ActionSearch(QWidget *parent);
|
||||
|
||||
/**
|
||||
* @brief Set the menu bar to use in this action search
|
||||
*/
|
||||
void set_menu_bar(QMenuBar *menu_bar);
|
||||
private slots:
|
||||
/**
|
||||
* @brief Update the list of actions according to a search query
|
||||
*
|
||||
* This function adds/removes actions in the action list according to a given search query entered by the user.
|
||||
*
|
||||
* To loop over the menubar and all of its menus and submenus, this function will call itself recursively. As such
|
||||
* some of its parameters do not need to be set externally, as these will be set by the function itself as it calls
|
||||
* itself.
|
||||
*
|
||||
* @param s
|
||||
*
|
||||
* The search text. This is the only parameter that should be set externally.
|
||||
*
|
||||
* @param p
|
||||
*
|
||||
* The current parent hierarchy. In most cases, this should be left as nullptr when called externally.
|
||||
* search_update() will fill this automatically as it needs while calling itself recursively.
|
||||
*
|
||||
* @param parent
|
||||
*
|
||||
* The current menu to loop over. In most cases, this should be left as nullptr when called externally.
|
||||
* search_update() will fill this automatically as it needs while calling itself recursively.
|
||||
*/
|
||||
void search_update(const QString &s, const QString &p = nullptr,
|
||||
QMenu *parent = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Perform the currently selected action
|
||||
*
|
||||
* Usually triggered by pressing Enter on the ActionSearchEntry field, this will trigger whatever action is currently
|
||||
* highlighted and then close this popup. If no entries are highlighted (i.e. the list is empty), no action is
|
||||
* triggered and the popup closes anyway.
|
||||
*/
|
||||
void perform_action();
|
||||
|
||||
/**
|
||||
* @brief Move selection up
|
||||
*
|
||||
* A slot for pressing up on the ActionSearchEntry field. Moves the selection in the list up once. If the
|
||||
* selection is already at the top of the list, this is a no-op.
|
||||
*/
|
||||
void move_selection_up();
|
||||
|
||||
/**
|
||||
* @brief Move selection down
|
||||
*
|
||||
* A slot for pressing down on the ActionSearchEntry field. Moves the selection in the list down once. If the
|
||||
* selection is already at the bottom of the list, this is a no-op.
|
||||
*/
|
||||
void move_selection_down();
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Main widget that shows the list of commands
|
||||
*/
|
||||
ActionSearchList *list_widget_;
|
||||
|
||||
/**
|
||||
* @brief Attached menu bar object
|
||||
*/
|
||||
QMenuBar *menu_bar_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The ActionSearchList class
|
||||
*
|
||||
* Simple wrapper around QListWidget that emits a signal when an item is double clicked that ActionSearch connects
|
||||
* to a slot that triggers the currently selected action.
|
||||
*/
|
||||
class ActionSearchList : public QListWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
/**
|
||||
* @brief ActionSearchList Constructor
|
||||
* @param parent
|
||||
*
|
||||
* Usually ActionSearch.
|
||||
*/
|
||||
ActionSearchList(QWidget *parent);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Override of QListWidget's double click event that emits a signal.
|
||||
*/
|
||||
void mouseDoubleClickEvent(QMouseEvent *);
|
||||
signals:
|
||||
/**
|
||||
* @brief Signal emitted when a QListWidget item is double clicked.
|
||||
*/
|
||||
void dbl_click();
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The ActionSearchEntry class
|
||||
*
|
||||
* Simple wrapper around QLineEdit that emits signals when the up or down arrow keys are pressed so that ActionSearch
|
||||
* can connect them to moving the current selection up or down.
|
||||
*/
|
||||
class ActionSearchEntry : public QLineEdit {
|
||||
Q_OBJECT
|
||||
public:
|
||||
/**
|
||||
* @brief ActionSearchEntry
|
||||
* @param parent
|
||||
*
|
||||
* Usually ActionSearch.
|
||||
*/
|
||||
ActionSearchEntry(QWidget *parent);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Override of QLineEdit's key press event that listens for up/down key presses.
|
||||
* @param event
|
||||
*/
|
||||
virtual bool event(QEvent *e) override;
|
||||
|
||||
signals:
|
||||
/**
|
||||
* @brief Emitted when the user presses the up arrow key.
|
||||
*/
|
||||
void move_selection_up();
|
||||
|
||||
/**
|
||||
* @brief Emitted when the user presses the down arrow key.
|
||||
*/
|
||||
void move_selection_down();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_ACTIONSEARCH_H
|
||||
@@ -1,22 +0,0 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive 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/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
dialog/autorecovery/autorecoverydialog.h
|
||||
dialog/autorecovery/autorecoverydialog.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -1,157 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 "autorecoverydialog.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QDir>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "core.h"
|
||||
#include "oakutil/filefunctions.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
#define super QDialog
|
||||
|
||||
AutoRecoveryDialog::AutoRecoveryDialog(const QString &message,
|
||||
const QStringList &recoveries,
|
||||
bool autocheck_latest, QWidget *parent)
|
||||
: QDialog(parent)
|
||||
{
|
||||
init(message);
|
||||
|
||||
populate_tree(recoveries, autocheck_latest);
|
||||
}
|
||||
|
||||
void AutoRecoveryDialog::accept()
|
||||
{
|
||||
foreach (QTreeWidgetItem *checkable, checkable_items_) {
|
||||
if (checkable->checkState(0) == Qt::Checked) {
|
||||
QString filename = checkable->data(0, k_filename_role).toString();
|
||||
Core::instance()->open_recovery_project(filename);
|
||||
}
|
||||
}
|
||||
|
||||
super::accept();
|
||||
}
|
||||
|
||||
void AutoRecoveryDialog::init(const QString &header_text)
|
||||
{
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
|
||||
setWindowTitle(tr("Auto-Recovery"));
|
||||
|
||||
layout->addWidget(new QLabel(header_text));
|
||||
|
||||
tree_widget_ = new QTreeWidget();
|
||||
tree_widget_->setHeaderHidden(true);
|
||||
layout->addWidget(tree_widget_);
|
||||
|
||||
QDialogButtonBox *buttons =
|
||||
new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
buttons->button(QDialogButtonBox::Ok)->setText(tr("Load"));
|
||||
connect(buttons, &QDialogButtonBox::accepted, this,
|
||||
&AutoRecoveryDialog::accept);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this,
|
||||
&AutoRecoveryDialog::reject);
|
||||
layout->addWidget(buttons);
|
||||
}
|
||||
|
||||
void AutoRecoveryDialog::populate_tree(const QStringList &recoveries,
|
||||
bool autocheck_latest)
|
||||
{
|
||||
// Each entry in `recoveries` is a directory with 1+ recovery projects in it
|
||||
QDir autorecovery_root(FileFunctions::get_auto_recovery_root());
|
||||
|
||||
foreach (const QString &recovery_folder, recoveries) {
|
||||
QDir recovery_dir(autorecovery_root.filePath(recovery_folder));
|
||||
|
||||
QString pretty_name;
|
||||
{
|
||||
// Retrieve pretty name
|
||||
QFile pretty_name_file(
|
||||
recovery_dir.filePath(QStringLiteral("realname.txt")));
|
||||
if (pretty_name_file.open(QFile::ReadOnly)) {
|
||||
// Read pretty name that we should have written in the autorecovery process
|
||||
pretty_name = QString::fromUtf8(pretty_name_file.readAll());
|
||||
pretty_name_file.close();
|
||||
}
|
||||
|
||||
if (pretty_name.isEmpty()) {
|
||||
// Fallback to just the UUID. While it won't mean much to the user, it's better than nothing.
|
||||
pretty_name = recovery_dir.dirName();
|
||||
}
|
||||
}
|
||||
|
||||
QTreeWidgetItem *top_level = new QTreeWidgetItem(tree_widget_);
|
||||
top_level->setText(0, pretty_name);
|
||||
|
||||
{
|
||||
// Populate with recoveries
|
||||
QStringList entries =
|
||||
recovery_dir.entryList(QDir::Files | QDir::NoDotAndDotDot,
|
||||
QDir::Name | QDir::Reversed);
|
||||
for (int i = 0; i < entries.size(); i++) {
|
||||
const QString &entry = entries.at(i);
|
||||
|
||||
if (entry.endsWith(QStringLiteral(".ove"),
|
||||
Qt::CaseInsensitive)) {
|
||||
QTreeWidgetItem *entry_item =
|
||||
new QTreeWidgetItem(top_level);
|
||||
|
||||
bool ok;
|
||||
qint64 recovery_time =
|
||||
entry.left(entry.indexOf('.')).toLongLong(&ok);
|
||||
QString entry_name;
|
||||
|
||||
if (ok) {
|
||||
// Set as time/date of recovery
|
||||
entry_name =
|
||||
QDateTime::fromSecsSinceEpoch(recovery_time)
|
||||
.toString();
|
||||
} else {
|
||||
// Fallback if we couldn't discern a date from this
|
||||
entry_name = entry;
|
||||
}
|
||||
|
||||
entry_item->setText(0, entry_name);
|
||||
entry_item->setData(0, k_filename_role,
|
||||
recovery_dir.filePath(entry));
|
||||
|
||||
// Allow to be checked, auto-checking the first entry
|
||||
entry_item->setCheckState(
|
||||
0, (autocheck_latest && top_level->childCount() == 1) ?
|
||||
Qt::Checked :
|
||||
Qt::Unchecked);
|
||||
|
||||
checkable_items_.append(entry_item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 OAK_AUTORECOVERYDIALOG_H
|
||||
#define OAK_AUTORECOVERYDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QTreeWidget>
|
||||
|
||||
#include "oakutil/define.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class AutoRecoveryDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
AutoRecoveryDialog(const QString &message, const QStringList &recoveries,
|
||||
bool autocheck_latest, QWidget *parent);
|
||||
|
||||
public slots:
|
||||
virtual void accept() override;
|
||||
|
||||
private:
|
||||
void init(const QString &header_text);
|
||||
|
||||
void populate_tree(const QStringList &recoveries, bool autocheck);
|
||||
|
||||
QTreeWidget *tree_widget_;
|
||||
|
||||
QVector<QTreeWidgetItem *> checkable_items_;
|
||||
|
||||
enum DataRole { k_filename_role = Qt::UserRole };
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_AUTORECOVERYDIALOG_H
|
||||
@@ -1,22 +0,0 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive 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/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
dialog/color/colordialog.h
|
||||
dialog/color/colordialog.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -1,252 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 "colordialog.h"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QSplitter>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "oakutil/qtutils.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
ColorDialog::ColorDialog(OakEngineColorManager *color_manager, const ManagedColor &start,
|
||||
QWidget *parent)
|
||||
: QDialog(parent)
|
||||
, color_manager_(color_manager)
|
||||
{
|
||||
setWindowTitle(tr("Select Color"));
|
||||
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
|
||||
QSplitter *splitter = new QSplitter(Qt::Horizontal);
|
||||
splitter->setChildrenCollapsible(false);
|
||||
layout->addWidget(splitter);
|
||||
|
||||
QWidget *graphics_area = new QWidget();
|
||||
splitter->addWidget(graphics_area);
|
||||
|
||||
QVBoxLayout *graphics_layout = new QVBoxLayout(graphics_area);
|
||||
|
||||
QHBoxLayout *wheel_layout = new QHBoxLayout();
|
||||
graphics_layout->addLayout(wheel_layout);
|
||||
|
||||
color_wheel_ = new ColorWheelWidget();
|
||||
wheel_layout->addWidget(color_wheel_);
|
||||
|
||||
hsv_value_gradient_ = new ColorGradientWidget(Qt::Vertical);
|
||||
hsv_value_gradient_->setFixedWidth(
|
||||
QtUtils::q_font_metrics_width(fontMetrics(), QStringLiteral("HHH")));
|
||||
wheel_layout->addWidget(hsv_value_gradient_);
|
||||
|
||||
QHBoxLayout *swatch_layout = new QHBoxLayout();
|
||||
graphics_layout->addLayout(swatch_layout);
|
||||
|
||||
swatch_layout->addStretch();
|
||||
|
||||
swatch_ = new ColorSwatchChooser(color_manager_);
|
||||
swatch_->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
|
||||
swatch_layout->addWidget(swatch_);
|
||||
|
||||
swatch_layout->addStretch();
|
||||
|
||||
QWidget *value_area = new QWidget();
|
||||
QVBoxLayout *value_layout = new QVBoxLayout(value_area);
|
||||
value_layout->setSpacing(0);
|
||||
splitter->addWidget(value_area);
|
||||
|
||||
color_values_widget_ = new ColorValuesWidget(color_manager_);
|
||||
color_values_widget_->ignore_pick_from(this);
|
||||
value_layout->addWidget(color_values_widget_);
|
||||
|
||||
chooser_ = new ColorSpaceChooser(color_manager_);
|
||||
|
||||
value_layout->addWidget(chooser_);
|
||||
|
||||
// Split window 50/50
|
||||
splitter->setSizes({ INT_MAX, INT_MAX });
|
||||
|
||||
connect(color_wheel_, &ColorWheelWidget::selected_color_changed,
|
||||
color_values_widget_, &ColorValuesWidget::set_color);
|
||||
connect(color_wheel_, &ColorWheelWidget::selected_color_changed,
|
||||
hsv_value_gradient_, &ColorGradientWidget::set_selected_color);
|
||||
connect(color_wheel_, &ColorWheelWidget::selected_color_changed, swatch_,
|
||||
&ColorSwatchChooser::set_current_color);
|
||||
connect(hsv_value_gradient_, &ColorGradientWidget::selected_color_changed,
|
||||
color_values_widget_, &ColorValuesWidget::set_color);
|
||||
connect(hsv_value_gradient_, &ColorGradientWidget::selected_color_changed,
|
||||
color_wheel_, &ColorWheelWidget::set_selected_color);
|
||||
connect(hsv_value_gradient_, &ColorGradientWidget::selected_color_changed,
|
||||
swatch_, &ColorSwatchChooser::set_current_color);
|
||||
connect(color_values_widget_, &ColorValuesWidget::color_changed,
|
||||
hsv_value_gradient_, &ColorGradientWidget::set_selected_color);
|
||||
connect(color_values_widget_, &ColorValuesWidget::color_changed,
|
||||
color_wheel_, &ColorWheelWidget::set_selected_color);
|
||||
connect(color_values_widget_, &ColorValuesWidget::color_changed, swatch_,
|
||||
&ColorSwatchChooser::set_current_color);
|
||||
connect(swatch_, &ColorSwatchChooser::color_clicked, hsv_value_gradient_,
|
||||
&ColorGradientWidget::set_selected_color);
|
||||
connect(swatch_, &ColorSwatchChooser::color_clicked, color_wheel_,
|
||||
&ColorWheelWidget::set_selected_color);
|
||||
connect(swatch_, &ColorSwatchChooser::color_clicked, color_values_widget_,
|
||||
&ColorValuesWidget::set_color);
|
||||
|
||||
connect(color_wheel_, &ColorWheelWidget::diameter_changed,
|
||||
hsv_value_gradient_, &ColorGradientWidget::setFixedHeight);
|
||||
|
||||
QDialogButtonBox *buttons =
|
||||
new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
layout->addWidget(buttons);
|
||||
|
||||
set_color(start);
|
||||
|
||||
connect(chooser_, &ColorSpaceChooser::color_space_changed, this,
|
||||
&ColorDialog::color_space_changed);
|
||||
color_space_changed(chooser_->input(), chooser_->output());
|
||||
|
||||
// Set default size ratio to 2:1
|
||||
resize(sizeHint().height() * 2, sizeHint().height());
|
||||
}
|
||||
|
||||
void ColorDialog::set_color(const ManagedColor &start)
|
||||
{
|
||||
chooser_->set_input(start.color_input());
|
||||
chooser_->set_output(start.color_output());
|
||||
|
||||
Color managed_start;
|
||||
|
||||
if (start.color_input().isEmpty()) {
|
||||
managed_start = start;
|
||||
|
||||
} else {
|
||||
// Convert reference color to the input space
|
||||
QByteArray ref_cs = oak_query_string([this](char *buf, int size) {
|
||||
return oakengine_color_manager_reference_color_space(
|
||||
color_manager_, buf, size);
|
||||
}).toUtf8();
|
||||
QByteArray in_cs = start.color_input().toUtf8();
|
||||
oak_color_transform in_pod;
|
||||
in_pod.is_display = 0;
|
||||
in_pod.output = in_cs.constData();
|
||||
in_pod.view = nullptr;
|
||||
in_pod.look = nullptr;
|
||||
ColorProcessorHandlePtr linear_to_input(
|
||||
oakengine_color_processor_create(color_manager_, ref_cs.constData(),
|
||||
&in_pod,
|
||||
OAKENGINE_COLOR_PROCESSOR_NORMAL),
|
||||
ColorProcessorHandleDeleter());
|
||||
|
||||
managed_start = oak_convert_color(linear_to_input, start);
|
||||
}
|
||||
|
||||
color_wheel_->set_selected_color(managed_start);
|
||||
hsv_value_gradient_->set_selected_color(managed_start);
|
||||
color_values_widget_->set_color(managed_start);
|
||||
swatch_->set_current_color(managed_start);
|
||||
}
|
||||
|
||||
ManagedColor ColorDialog::get_selected_color() const
|
||||
{
|
||||
ManagedColor selected = color_wheel_->get_selected_color();
|
||||
|
||||
// Convert to linear and return a linear color
|
||||
if (input_to_ref_processor_) {
|
||||
selected = oak_convert_color(input_to_ref_processor_, selected);
|
||||
}
|
||||
|
||||
selected.set_color_input(get_color_space_input());
|
||||
selected.set_color_output(get_color_space_output());
|
||||
|
||||
return selected;
|
||||
}
|
||||
|
||||
QString ColorDialog::get_color_space_input() const
|
||||
{
|
||||
return chooser_->input();
|
||||
}
|
||||
|
||||
oak::ColorTransform ColorDialog::get_color_space_output() const
|
||||
{
|
||||
return chooser_->output();
|
||||
}
|
||||
|
||||
void ColorDialog::color_space_changed(const QString &input,
|
||||
const oak::ColorTransform &output)
|
||||
{
|
||||
QByteArray ref_cs = oak_query_string([this](char *buf, int size) {
|
||||
return oakengine_color_manager_reference_color_space(
|
||||
color_manager_, buf, size);
|
||||
}).toUtf8();
|
||||
QByteArray in = input.toUtf8();
|
||||
QByteArray o, v, l;
|
||||
oak_color_transform out_pod = oak_to_transform(output, &o, &v, &l);
|
||||
|
||||
auto make_proc = [&](const char *input_cs, const oak_color_transform *dest,
|
||||
int dir) -> ColorProcessorHandlePtr {
|
||||
return ColorProcessorHandlePtr(
|
||||
oakengine_color_processor_create(color_manager_, input_cs, dest,
|
||||
dir),
|
||||
ColorProcessorHandleDeleter());
|
||||
};
|
||||
|
||||
input_to_ref_processor_ = make_proc(in.constData(), &out_pod,
|
||||
OAKENGINE_COLOR_PROCESSOR_NORMAL);
|
||||
|
||||
oak_color_transform ref_display_pod;
|
||||
ref_display_pod.is_display = out_pod.is_display;
|
||||
ref_display_pod.output = out_pod.output;
|
||||
ref_display_pod.view = out_pod.view;
|
||||
ref_display_pod.look = out_pod.look;
|
||||
ColorProcessorHandlePtr ref_to_display = make_proc(
|
||||
ref_cs.constData(), &ref_display_pod,
|
||||
OAKENGINE_COLOR_PROCESSOR_NORMAL);
|
||||
|
||||
oak_color_transform ref_input_pod;
|
||||
ref_input_pod.is_display = 0;
|
||||
ref_input_pod.output = in.constData();
|
||||
ref_input_pod.view = nullptr;
|
||||
ref_input_pod.look = nullptr;
|
||||
ColorProcessorHandlePtr ref_to_input = make_proc(
|
||||
ref_cs.constData(), &ref_input_pod,
|
||||
OAKENGINE_COLOR_PROCESSOR_NORMAL);
|
||||
|
||||
// Display -> reference is the inverse of the display transform. Older OCIO
|
||||
// versions crashed on TRANSFORM_DIR_INVERSE; guard by requiring a valid
|
||||
// processor and fall back to disabling the display tab if creation fails.
|
||||
ColorProcessorHandlePtr display_to_ref = make_proc(
|
||||
ref_cs.constData(), &ref_display_pod,
|
||||
OAKENGINE_COLOR_PROCESSOR_INVERSE);
|
||||
if (display_to_ref && !oakengine_color_processor_is_valid(display_to_ref.get())) {
|
||||
display_to_ref = nullptr;
|
||||
}
|
||||
|
||||
color_wheel_->set_color_processor(input_to_ref_processor_, ref_to_display);
|
||||
hsv_value_gradient_->set_color_processor(input_to_ref_processor_,
|
||||
ref_to_display);
|
||||
color_values_widget_->set_color_processor(
|
||||
input_to_ref_processor_, ref_to_display, display_to_ref, ref_to_input);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 OAK_COLORDIALOG_H
|
||||
#define OAK_COLORDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
#include "oakengine/color.h"
|
||||
#include "widget/manageddisplay/colorprocessorhandle.h"
|
||||
#include "widget/colorwheel/colorgradientwidget.h"
|
||||
#include "widget/colorwheel/colorspacechooser.h"
|
||||
#include "widget/colorwheel/colorswatchchooser.h"
|
||||
#include "widget/colorwheel/colorvalueswidget.h"
|
||||
#include "widget/colorwheel/colorwheelwidget.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class ColorDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
/**
|
||||
* @brief ColorDialog Constructor
|
||||
*
|
||||
* @param color_manager
|
||||
*
|
||||
* The ColorManager to use for color management. This must be valid.
|
||||
*
|
||||
* @param start
|
||||
*
|
||||
* The color to start with. This must be in the color_manager's reference space
|
||||
*
|
||||
* @param input_cs
|
||||
*
|
||||
* The input range that the user should see. The start color will be converted to this for UI object.
|
||||
*
|
||||
* @param parent
|
||||
*
|
||||
* QWidget parent.
|
||||
*/
|
||||
ColorDialog(OakEngineColorManager *color_manager,
|
||||
const ManagedColor &start = Color(1.0f, 1.0f, 1.0f),
|
||||
QWidget *parent = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Retrieves the color selected by the user
|
||||
*
|
||||
* The color is always returned in the ColorManager's reference space (usually scene linear).
|
||||
*/
|
||||
ManagedColor get_selected_color() const;
|
||||
|
||||
QString get_color_space_input() const;
|
||||
|
||||
oak::ColorTransform get_color_space_output() const;
|
||||
|
||||
public slots:
|
||||
void set_color(const ManagedColor &c);
|
||||
|
||||
private:
|
||||
OakEngineColorManager *color_manager_;
|
||||
|
||||
ColorWheelWidget *color_wheel_;
|
||||
|
||||
ColorValuesWidget *color_values_widget_;
|
||||
|
||||
ColorGradientWidget *hsv_value_gradient_;
|
||||
|
||||
ColorProcessorHandlePtr input_to_ref_processor_;
|
||||
|
||||
ColorSpaceChooser *chooser_;
|
||||
|
||||
ColorSwatchChooser *swatch_;
|
||||
|
||||
private slots:
|
||||
void color_space_changed(const QString &input, const oak::ColorTransform &output);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_COLORDIALOG_H
|
||||
@@ -1,24 +0,0 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive 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/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
dialog/configbase/configdialogbase.cpp
|
||||
dialog/configbase/configdialogbase.h
|
||||
dialog/configbase/configdialogbasetab.cpp
|
||||
dialog/configbase/configdialogbasetab.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -1,102 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 "configdialogbase.h"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QSplitter>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "core.h"
|
||||
|
||||
#include "oakengine/undo.h"
|
||||
namespace olive
|
||||
{
|
||||
|
||||
ConfigDialogBase::ConfigDialogBase(QWidget *parent)
|
||||
: QDialog(parent)
|
||||
{
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
|
||||
QSplitter *splitter = new QSplitter();
|
||||
splitter->setChildrenCollapsible(false);
|
||||
layout->addWidget(splitter);
|
||||
|
||||
list_widget_ = new QListWidget();
|
||||
|
||||
preference_pane_stack_ = new QStackedWidget(this);
|
||||
|
||||
splitter->addWidget(list_widget_);
|
||||
splitter->addWidget(preference_pane_stack_);
|
||||
|
||||
QDialogButtonBox *button_box = new QDialogButtonBox(this);
|
||||
button_box->setOrientation(Qt::Horizontal);
|
||||
button_box->setStandardButtons(QDialogButtonBox::Cancel |
|
||||
QDialogButtonBox::Ok);
|
||||
|
||||
layout->addWidget(button_box);
|
||||
|
||||
connect(button_box, &QDialogButtonBox::accepted, this,
|
||||
&ConfigDialogBase::accept);
|
||||
connect(button_box, &QDialogButtonBox::rejected, this,
|
||||
&ConfigDialogBase::reject);
|
||||
|
||||
connect(list_widget_, &QListWidget::currentRowChanged,
|
||||
preference_pane_stack_, &QStackedWidget::setCurrentIndex);
|
||||
}
|
||||
|
||||
void ConfigDialogBase::accept()
|
||||
{
|
||||
foreach (ConfigDialogBaseTab *tab, tabs_) {
|
||||
if (!tab->validate()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void *command = oakengine_undo_command_create_multi();
|
||||
|
||||
foreach (ConfigDialogBaseTab *tab, tabs_) {
|
||||
tab->accept(command);
|
||||
}
|
||||
|
||||
oakengine_undo_push(command, tr("Set Configuration").toUtf8().constData());
|
||||
|
||||
AcceptEvent();
|
||||
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
void ConfigDialogBase::add_tab(ConfigDialogBaseTab *tab, const QString &title)
|
||||
{
|
||||
list_widget_->addItem(title);
|
||||
preference_pane_stack_->addWidget(tab);
|
||||
|
||||
tabs_.append(tab);
|
||||
}
|
||||
|
||||
void ConfigDialogBase::set_current_tab(int index)
|
||||
{
|
||||
if (index >= 0 && index < list_widget_->count()) {
|
||||
list_widget_->setCurrentRow(index);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 OAK_CONFIGBASE_H
|
||||
#define OAK_CONFIGBASE_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QListWidget>
|
||||
#include <QStackedWidget>
|
||||
|
||||
#include "configdialogbasetab.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class ConfigDialogBase : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ConfigDialogBase(QWidget *parent = nullptr);
|
||||
|
||||
void set_current_tab(int index);
|
||||
|
||||
private slots:
|
||||
/**
|
||||
* @brief Override of accept to save preferences to Config.
|
||||
*/
|
||||
virtual void accept() override;
|
||||
|
||||
protected:
|
||||
void add_tab(ConfigDialogBaseTab *tab, const QString &title);
|
||||
|
||||
virtual void AcceptEvent()
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
QListWidget *list_widget_;
|
||||
|
||||
QStackedWidget *preference_pane_stack_;
|
||||
|
||||
QList<ConfigDialogBaseTab *> tabs_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_CONFIGBASE_H
|
||||
@@ -1,32 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 "configdialogbasetab.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
bool ConfigDialogBaseTab::validate()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 OAK_PREFERENCESTAB_H
|
||||
#define OAK_PREFERENCESTAB_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include "common/configwrapper.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class ConfigDialogBaseTab : public QWidget {
|
||||
public:
|
||||
ConfigDialogBaseTab() = default;
|
||||
|
||||
virtual bool validate();
|
||||
|
||||
virtual void accept(void *parent) = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_PREFERENCESTAB_H
|
||||
@@ -1,22 +0,0 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive 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/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
dialog/diskcache/diskcachedialog.h
|
||||
dialog/diskcache/diskcachedialog.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -1,151 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 "diskcachedialog.h"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "oakengine/disk.h"
|
||||
#include "oakutil/define.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
/// DiskCacheFolder::get_path() through the C ABI (buf/size convention)
|
||||
QString disk_folder_path(const void *folder)
|
||||
{
|
||||
char buf[1024];
|
||||
buf[0] = '\0';
|
||||
oakengine_disk_folder_get_path(folder, buf, sizeof(buf));
|
||||
return QString::fromUtf8(buf);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
DiskCacheDialog::DiskCacheDialog(void *folder, QWidget *parent)
|
||||
: QDialog(parent)
|
||||
, folder_(folder)
|
||||
{
|
||||
QGridLayout *layout = new QGridLayout(this);
|
||||
|
||||
int row = 0;
|
||||
|
||||
layout->addWidget(
|
||||
new QLabel(tr("Disk Cache: %1").arg(disk_folder_path(folder))), row,
|
||||
0, 1, 2);
|
||||
setWindowTitle(tr("Disk Cache Settings"));
|
||||
|
||||
row++;
|
||||
|
||||
layout->addWidget(new QLabel(tr("Maximum Disk Cache:")), row, 0);
|
||||
|
||||
maximum_cache_slider_ = new FloatSlider();
|
||||
maximum_cache_slider_->set_format(tr("%1 GB"));
|
||||
maximum_cache_slider_->set_minimum(1.0);
|
||||
// The folder limit is a byte count; the slider works in GB
|
||||
maximum_cache_slider_->set_value(oakengine_disk_folder_get_limit(folder) /
|
||||
static_cast<double>(k_bytes_in_gigabyte));
|
||||
layout->addWidget(maximum_cache_slider_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
clear_cache_btn_ = new QPushButton(tr("Clear Disk Cache"));
|
||||
connect(clear_cache_btn_, &QPushButton::clicked, this,
|
||||
static_cast<void (DiskCacheDialog::*)()>(
|
||||
&DiskCacheDialog::clear_disk_cache));
|
||||
layout->addWidget(clear_cache_btn_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
clear_disk_cache_ =
|
||||
new QCheckBox(tr("Automatically clear disk cache on close"));
|
||||
clear_disk_cache_->setChecked(
|
||||
oakengine_disk_folder_get_clear_on_close(folder) != 0);
|
||||
layout->addWidget(clear_disk_cache_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
QDialogButtonBox *buttons =
|
||||
new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
connect(buttons, &QDialogButtonBox::accepted, this,
|
||||
&DiskCacheDialog::accept);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this,
|
||||
&DiskCacheDialog::reject);
|
||||
layout->addWidget(buttons, row, 0, 1, 2);
|
||||
}
|
||||
|
||||
void DiskCacheDialog::accept()
|
||||
{
|
||||
qint64 new_disk_cache_limit =
|
||||
qRound64(maximum_cache_slider_->get_value() * k_bytes_in_gigabyte);
|
||||
if (static_cast<double>(new_disk_cache_limit) !=
|
||||
oakengine_disk_folder_get_limit(folder_)) {
|
||||
oakengine_disk_folder_set_limit(
|
||||
folder_, static_cast<double>(new_disk_cache_limit));
|
||||
}
|
||||
|
||||
const bool clear_on_close = clear_disk_cache_->isChecked();
|
||||
if ((oakengine_disk_folder_get_clear_on_close(folder_) != 0) !=
|
||||
clear_on_close) {
|
||||
oakengine_disk_folder_set_clear_on_close(folder_,
|
||||
clear_on_close ? 1 : 0);
|
||||
}
|
||||
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
void DiskCacheDialog::clear_disk_cache()
|
||||
{
|
||||
clear_disk_cache(disk_folder_path(folder_), this, clear_cache_btn_);
|
||||
}
|
||||
|
||||
void DiskCacheDialog::clear_disk_cache(const QString &path, QWidget *parent,
|
||||
QPushButton *clear_btn)
|
||||
{
|
||||
if (QMessageBox::question(
|
||||
parent, tr("Clear Disk Cache"),
|
||||
tr("Are you sure you want to clear the disk cache in '%1'?")
|
||||
.arg(path),
|
||||
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
|
||||
if (clear_btn)
|
||||
clear_btn->setEnabled(false);
|
||||
|
||||
if (oakengine_disk_clear_cache(path.toUtf8().constData())) {
|
||||
if (clear_btn)
|
||||
clear_btn->setText(tr("Disk Cache Cleared"));
|
||||
} else {
|
||||
QMessageBox::information(
|
||||
parent, tr("Clear Disk Cache"),
|
||||
tr("Disk cache failed to fully clear. You may have to delete the cache files manually."),
|
||||
QMessageBox::Ok);
|
||||
if (clear_btn)
|
||||
clear_btn->setText(tr("Disk Cache Partially Cleared"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 OAK_DISKCACHEDIALOG_H
|
||||
#define OAK_DISKCACHEDIALOG_H
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QDialog>
|
||||
#include <QPushButton>
|
||||
|
||||
#include "widget/slider/floatslider.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class DiskCacheDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
/**
|
||||
* @param folder Opaque engine DiskCacheFolder handle (from
|
||||
* oakengine_disk_get_open_folder()), accessed through the
|
||||
* oakengine_disk_folder_* C ABI.
|
||||
*/
|
||||
DiskCacheDialog(void *folder, QWidget *parent = nullptr);
|
||||
|
||||
static void clear_disk_cache(const QString &path, QWidget *parent,
|
||||
QPushButton *clear_btn = nullptr);
|
||||
|
||||
public slots:
|
||||
virtual void accept() override;
|
||||
|
||||
private:
|
||||
void *folder_;
|
||||
|
||||
FloatSlider *maximum_cache_slider_;
|
||||
|
||||
QCheckBox *clear_disk_cache_;
|
||||
|
||||
QPushButton *clear_cache_btn_;
|
||||
|
||||
private slots:
|
||||
void clear_disk_cache();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_DISKCACHEDIALOG_H
|
||||
@@ -1,36 +0,0 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive 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/>.
|
||||
|
||||
add_subdirectory(codec)
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
dialog/export/export.cpp
|
||||
dialog/export/export.h
|
||||
dialog/export/exportadvancedvideodialog.cpp
|
||||
dialog/export/exportadvancedvideodialog.h
|
||||
dialog/export/exportaudiotab.cpp
|
||||
dialog/export/exportaudiotab.h
|
||||
dialog/export/exportformatcombobox.cpp
|
||||
dialog/export/exportformatcombobox.h
|
||||
dialog/export/exportsavepresetdialog.cpp
|
||||
dialog/export/exportsavepresetdialog.h
|
||||
dialog/export/exportsubtitlestab.cpp
|
||||
dialog/export/exportsubtitlestab.h
|
||||
dialog/export/exportvideotab.cpp
|
||||
dialog/export/exportvideotab.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -1,32 +0,0 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 Olive 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/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
dialog/export/codec/av1section.cpp
|
||||
dialog/export/codec/av1section.h
|
||||
dialog/export/codec/cineformsection.cpp
|
||||
dialog/export/codec/cineformsection.h
|
||||
dialog/export/codec/codecsection.cpp
|
||||
dialog/export/codec/codecsection.h
|
||||
dialog/export/codec/codecstack.cpp
|
||||
dialog/export/codec/codecstack.h
|
||||
dialog/export/codec/h264section.cpp
|
||||
dialog/export/codec/h264section.h
|
||||
dialog/export/codec/imagesection.cpp
|
||||
dialog/export/codec/imagesection.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -1,141 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 "av1section.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
|
||||
#include "oakutil/qtutils.h"
|
||||
#include "widget/slider/integerslider.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
AV1Section::AV1Section(QWidget *parent)
|
||||
: AV1Section(AV1CRFSection::k_default_a_v1_crf, parent)
|
||||
{
|
||||
}
|
||||
|
||||
AV1Section::AV1Section(int default_crf, QWidget *parent)
|
||||
: CodecSection(parent)
|
||||
{
|
||||
QGridLayout *layout = new QGridLayout(this);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
int row = 0;
|
||||
layout->addWidget(new QLabel(tr("Preset:")), row, 0);
|
||||
|
||||
preset_combobox_ = new QComboBox();
|
||||
preset_combobox_->setToolTip(tr(
|
||||
"This parameter governs the efficiency/encode-time trade-off.\n"
|
||||
"Lower presets will result in an output with better quality for a given file size, but will take longer to encode.\n"
|
||||
"Higher presets can result in a very fast encode, but will make some compromises on visual quality for a given crf value."));
|
||||
|
||||
for (int i = 0; i <= 13; i++)
|
||||
preset_combobox_->addItem(QString::number(i));
|
||||
|
||||
preset_combobox_->setCurrentIndex(8);
|
||||
|
||||
layout->addWidget(preset_combobox_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
layout->addWidget(new QLabel(tr("Compression Method:")), row, 0);
|
||||
|
||||
QComboBox *compression_box = new QComboBox();
|
||||
compression_box->setToolTip(tr(
|
||||
"This parameter governs the quality/size trade-off.\n"
|
||||
"Higher CRF values will result in a final output that takes less space, but begins to lose detail.\n"
|
||||
"Lower CRF values retain more detail at the cost of larger file sizes.\n"
|
||||
"The possible range of CRF in SVT-AV1 is 1-63."));
|
||||
|
||||
// These items must correspond to the CompressionMethod enum
|
||||
compression_box->addItem(tr("Constant Rate Factor"));
|
||||
|
||||
layout->addWidget(compression_box, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
compression_method_stack_ = new QStackedWidget();
|
||||
layout->addWidget(compression_method_stack_, row, 0, 1, 2);
|
||||
|
||||
crf_section_ = new AV1CRFSection(default_crf);
|
||||
compression_method_stack_->addWidget(crf_section_);
|
||||
|
||||
connect(
|
||||
compression_box,
|
||||
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
|
||||
compression_method_stack_, &QStackedWidget::setCurrentIndex);
|
||||
}
|
||||
|
||||
void AV1Section::add_opts(OakEngineEncodingParams *params)
|
||||
{
|
||||
CompressionMethod method = static_cast<CompressionMethod>(
|
||||
compression_method_stack_->currentIndex());
|
||||
|
||||
if (method == k_constant_rate_factor) {
|
||||
// Set Quantizer value
|
||||
oakengine_encoding_params_set_video_option(
|
||||
params, "qp",
|
||||
QByteArray::number(crf_section_->get_value()).constData());
|
||||
}
|
||||
|
||||
oakengine_encoding_params_set_video_option(
|
||||
params, "preset",
|
||||
QByteArray::number(preset_combobox_->currentIndex()).constData());
|
||||
}
|
||||
|
||||
AV1CRFSection::AV1CRFSection(int default_crf, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
QHBoxLayout *layout = new QHBoxLayout(this);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
crf_slider_ = new QSlider(Qt::Horizontal);
|
||||
crf_slider_->setMinimum(k_minimum_crf);
|
||||
crf_slider_->setMaximum(k_maximum_crf);
|
||||
crf_slider_->setValue(default_crf);
|
||||
layout->addWidget(crf_slider_);
|
||||
|
||||
IntegerSlider *crf_input = new IntegerSlider();
|
||||
crf_input->setMaximumWidth(QtUtils::q_font_metrics_width(
|
||||
crf_input->fontMetrics(), QStringLiteral("HHHH")));
|
||||
crf_input->set_minimum(k_minimum_crf);
|
||||
crf_input->set_maximum(k_maximum_crf);
|
||||
crf_input->set_value(default_crf);
|
||||
crf_input->SetDefaultValue(default_crf);
|
||||
layout->addWidget(crf_input);
|
||||
|
||||
connect(crf_slider_, &QSlider::valueChanged, crf_input,
|
||||
&IntegerSlider::set_value);
|
||||
connect(crf_input, &IntegerSlider::value_changed, crf_slider_,
|
||||
&QSlider::setValue);
|
||||
}
|
||||
|
||||
int AV1CRFSection::get_value() const
|
||||
{
|
||||
return crf_slider_->value();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 OAK_AV1SECTION_H
|
||||
#define OAK_AV1SECTION_H
|
||||
|
||||
#include <QSlider>
|
||||
#include <QStackedWidget>
|
||||
#include <QComboBox>
|
||||
|
||||
#include "codecsection.h"
|
||||
#include "widget/slider/floatslider.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class AV1CRFSection : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
AV1CRFSection(int default_crf, QWidget *parent = nullptr);
|
||||
|
||||
int get_value() const;
|
||||
|
||||
static const int k_default_a_v1_crf = 30;
|
||||
|
||||
private:
|
||||
static const int k_minimum_crf = 0;
|
||||
static const int k_maximum_crf = 63;
|
||||
|
||||
QSlider *crf_slider_;
|
||||
};
|
||||
|
||||
class AV1Section : public CodecSection {
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum CompressionMethod {
|
||||
k_constant_rate_factor,
|
||||
};
|
||||
|
||||
AV1Section(QWidget *parent = nullptr);
|
||||
AV1Section(int default_crf, QWidget *parent);
|
||||
|
||||
virtual void add_opts(OakEngineEncodingParams *params) override;
|
||||
|
||||
private:
|
||||
QStackedWidget *compression_method_stack_;
|
||||
|
||||
AV1CRFSection *crf_section_;
|
||||
|
||||
QComboBox *preset_combobox_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_AV1SECTION_H
|
||||
@@ -1,99 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 "cineformsection.h"
|
||||
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
CineformSection::CineformSection(QWidget *parent)
|
||||
: CodecSection(parent)
|
||||
{
|
||||
QGridLayout *layout = new QGridLayout(this);
|
||||
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
int row = 0;
|
||||
|
||||
layout->addWidget(new QLabel(tr("Quality:")), row, 0);
|
||||
|
||||
quality_combobox_ = new QComboBox();
|
||||
|
||||
/* Correspond to the following indexes for FFmpeg
|
||||
*
|
||||
* -quality <int> E..V....... set quality (from 0 to 12) (default film3+)
|
||||
* film3+ 0 E..V.......
|
||||
* film3 1 E..V.......
|
||||
* film2+ 2 E..V.......
|
||||
* film2 3 E..V.......
|
||||
* film1.5 4 E..V.......
|
||||
* film1+ 5 E..V.......
|
||||
* film1 6 E..V.......
|
||||
* high+ 7 E..V.......
|
||||
* high 8 E..V.......
|
||||
* medium+ 9 E..V.......
|
||||
* medium 10 E..V.......
|
||||
* low+ 11 E..V.......
|
||||
* low 12 E..V.......
|
||||
*
|
||||
*/
|
||||
|
||||
quality_combobox_->addItem(tr("Film Scan 3+"));
|
||||
quality_combobox_->addItem(tr("Film Scan 3"));
|
||||
quality_combobox_->addItem(tr("Film Scan 2+"));
|
||||
quality_combobox_->addItem(tr("Film Scan 2"));
|
||||
quality_combobox_->addItem(tr("Film Scan 1.5"));
|
||||
quality_combobox_->addItem(tr("Film Scan 1+"));
|
||||
quality_combobox_->addItem(tr("Film Scan 1"));
|
||||
quality_combobox_->addItem(tr("High+"));
|
||||
quality_combobox_->addItem(tr("High"));
|
||||
quality_combobox_->addItem(tr("Medium+"));
|
||||
quality_combobox_->addItem(tr("Medium"));
|
||||
quality_combobox_->addItem(tr("Low+"));
|
||||
quality_combobox_->addItem(tr("Low"));
|
||||
|
||||
// Default to "medium"
|
||||
quality_combobox_->setCurrentIndex(10);
|
||||
|
||||
layout->addWidget(quality_combobox_, row, 1);
|
||||
}
|
||||
|
||||
void CineformSection::add_opts(OakEngineEncodingParams *params)
|
||||
{
|
||||
oakengine_encoding_params_set_video_option(
|
||||
params, "quality",
|
||||
QByteArray::number(quality_combobox_->currentIndex()).constData());
|
||||
}
|
||||
|
||||
void CineformSection::set_opts(const OakEngineEncodingParams *p)
|
||||
{
|
||||
char buf[64];
|
||||
const int ret = oakengine_encoding_params_video_option(
|
||||
p, "quality", buf, static_cast<int>(sizeof(buf)));
|
||||
if (ret > 0) {
|
||||
quality_combobox_->setCurrentIndex(QString::fromUtf8(buf).toInt());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 OAK_CINEFORMSECTION_H
|
||||
#define OAK_CINEFORMSECTION_H
|
||||
|
||||
#include <QComboBox>
|
||||
|
||||
#include "codecsection.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class CineformSection : public CodecSection {
|
||||
Q_OBJECT
|
||||
public:
|
||||
CineformSection(QWidget *parent = nullptr);
|
||||
|
||||
virtual void add_opts(OakEngineEncodingParams *params) override;
|
||||
|
||||
virtual void set_opts(const OakEngineEncodingParams *p) override;
|
||||
|
||||
private:
|
||||
QComboBox *quality_combobox_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_CINEFORMSECTION_H
|
||||
@@ -1,32 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 "codecsection.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
CodecSection::CodecSection(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 OAK_CODECSECTION_H
|
||||
#define OAK_CODECSECTION_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include "oakengine/encoding.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class CodecSection : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
CodecSection(QWidget *parent = nullptr);
|
||||
|
||||
virtual void add_opts(OakEngineEncodingParams *params)
|
||||
{
|
||||
Q_UNUSED(params)
|
||||
}
|
||||
|
||||
virtual void set_opts(const OakEngineEncodingParams *p)
|
||||
{
|
||||
Q_UNUSED(p)
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_CODECSECTION_H
|
||||
@@ -1,57 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 "codecstack.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
#define super QStackedWidget
|
||||
|
||||
CodecStack::CodecStack(QWidget *parent)
|
||||
: super{ parent }
|
||||
{
|
||||
connect(this, &CodecStack::currentChanged, this, &CodecStack::on_change);
|
||||
}
|
||||
|
||||
void CodecStack::addWidget(QWidget *widget)
|
||||
{
|
||||
super::addWidget(widget);
|
||||
|
||||
on_change(currentIndex());
|
||||
}
|
||||
|
||||
void CodecStack::on_change(int index)
|
||||
{
|
||||
for (int i = 0; i < count(); i++) {
|
||||
if (i == index) {
|
||||
widget(i)->setSizePolicy(QSizePolicy::Expanding,
|
||||
QSizePolicy::Expanding);
|
||||
} else {
|
||||
widget(i)->setSizePolicy(QSizePolicy::Ignored,
|
||||
QSizePolicy::Ignored);
|
||||
}
|
||||
widget(i)->adjustSize();
|
||||
}
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 OAK_CODECSTACK_H
|
||||
#define OAK_CODECSTACK_H
|
||||
|
||||
#include <QStackedWidget>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class CodecStack : public QStackedWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit CodecStack(QWidget *parent = nullptr);
|
||||
|
||||
void addWidget(QWidget *widget);
|
||||
|
||||
signals:
|
||||
|
||||
private slots:
|
||||
void on_change(int index);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_CODECSTACK_H
|
||||
@@ -1,336 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 "h264section.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
|
||||
#include "oakutil/qtutils.h"
|
||||
#include "widget/slider/integerslider.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
H264Section::H264Section(QWidget *parent)
|
||||
: H264Section(H264CRFSection::k_default_h264_crf, parent)
|
||||
{
|
||||
}
|
||||
|
||||
H264Section::H264Section(int default_crf, QWidget *parent)
|
||||
: CodecSection(parent)
|
||||
{
|
||||
QGridLayout *layout = new QGridLayout(this);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
int row = 0;
|
||||
layout->addWidget(new QLabel(tr("Encode Speed:")), row, 0);
|
||||
|
||||
preset_combobox_ = new QComboBox();
|
||||
preset_combobox_->setToolTip(tr(
|
||||
"This setting allows you to tweak the ratio of export speed to compression quality. \n\n"
|
||||
"If using Constant Rate Factor, slower speeds will result in smaller file sizes for the same quality. \n\n"
|
||||
"If using Target Bit Rate or Target File Size, slower speeds will result in higher quality for the same bitrate/filesize. \n\n"
|
||||
"This setting is equivalent to the `preset` setting in libx264."));
|
||||
|
||||
preset_combobox_->addItem(tr("Ultra Fast"));
|
||||
preset_combobox_->addItem(tr("Super Fast"));
|
||||
preset_combobox_->addItem(tr("Very Fast"));
|
||||
preset_combobox_->addItem(tr("Faster"));
|
||||
preset_combobox_->addItem(tr("Fast"));
|
||||
preset_combobox_->addItem(tr("Medium"));
|
||||
preset_combobox_->addItem(tr("Slow"));
|
||||
preset_combobox_->addItem(tr("Slower"));
|
||||
preset_combobox_->addItem(tr("Very Slow"));
|
||||
|
||||
//Default to "medium"
|
||||
preset_combobox_->setCurrentIndex(5);
|
||||
|
||||
layout->addWidget(preset_combobox_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
layout->addWidget(new QLabel(tr("Compression Method:")), row, 0);
|
||||
|
||||
QComboBox *compression_box = new QComboBox();
|
||||
|
||||
// These items must correspond to the CompressionMethod enum
|
||||
compression_box->addItem(tr("Constant Rate Factor"));
|
||||
compression_box->addItem(tr("Target Bit Rate"));
|
||||
compression_box->addItem(tr("Target File Size"));
|
||||
|
||||
layout->addWidget(compression_box, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
compression_method_stack_ = new QStackedWidget();
|
||||
layout->addWidget(compression_method_stack_, row, 0, 1, 2);
|
||||
|
||||
crf_section_ = new H264CRFSection(default_crf);
|
||||
compression_method_stack_->addWidget(crf_section_);
|
||||
|
||||
bitrate_section_ = new H264BitRateSection();
|
||||
compression_method_stack_->addWidget(bitrate_section_);
|
||||
|
||||
filesize_section_ = new H264FileSizeSection();
|
||||
compression_method_stack_->addWidget(filesize_section_);
|
||||
|
||||
connect(
|
||||
compression_box,
|
||||
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
|
||||
compression_method_stack_, &QStackedWidget::setCurrentIndex);
|
||||
}
|
||||
|
||||
void H264Section::add_opts(OakEngineEncodingParams *params)
|
||||
{
|
||||
// FIXME: Implement two-pass
|
||||
|
||||
CompressionMethod method = static_cast<CompressionMethod>(
|
||||
compression_method_stack_->currentIndex());
|
||||
|
||||
// This option is not used by the encoder (nor is anything with the ove_ prefix), it's to help us
|
||||
// identify which option was chosen when params are restored
|
||||
oakengine_encoding_params_set_video_option(
|
||||
params, "ove_compressionmethod",
|
||||
QByteArray::number(method).constData());
|
||||
|
||||
if (method == k_constant_rate_factor) {
|
||||
// Simply set CRF value
|
||||
oakengine_encoding_params_set_video_option(
|
||||
params, "crf",
|
||||
QByteArray::number(crf_section_->get_value()).constData());
|
||||
|
||||
} else {
|
||||
int64_t target_rate, max_rate, min_rate;
|
||||
|
||||
if (method == k_target_bit_rate) {
|
||||
// Use user-supplied values for the bit rate
|
||||
target_rate = bitrate_section_->get_target_bit_rate();
|
||||
min_rate = 0;
|
||||
max_rate = bitrate_section_->get_maximum_bit_rate();
|
||||
} else {
|
||||
// Calculate the bit rate from the file size divided by the sequence length in seconds (bits per second)
|
||||
int64_t target_fs = filesize_section_->get_file_size();
|
||||
int export_len_num = 0, export_len_den = 1;
|
||||
oakengine_encoding_params_get_export_length(
|
||||
params, &export_len_num, &export_len_den);
|
||||
const double export_len_sec =
|
||||
(export_len_den > 0)
|
||||
? static_cast<double>(export_len_num)
|
||||
/ static_cast<double>(export_len_den)
|
||||
: 1.0;
|
||||
target_rate = qRound64(static_cast<double>(target_fs) / export_len_sec);
|
||||
min_rate = target_rate;
|
||||
max_rate = target_rate;
|
||||
|
||||
oakengine_encoding_params_set_video_option(
|
||||
params, "ove_targetfilesize",
|
||||
QByteArray::number(target_fs).constData());
|
||||
}
|
||||
|
||||
// Disable CRF encoding
|
||||
oakengine_encoding_params_set_video_option(params, "crf", "-1");
|
||||
|
||||
oakengine_encoding_params_set_video_bit_rate(params, target_rate);
|
||||
oakengine_encoding_params_set_video_min_bit_rate(params, min_rate);
|
||||
oakengine_encoding_params_set_video_max_bit_rate(params, max_rate);
|
||||
oakengine_encoding_params_set_video_buffer_size(params, 2000000);
|
||||
}
|
||||
|
||||
oakengine_encoding_params_set_video_option(
|
||||
params, "preset",
|
||||
QByteArray::number(preset_combobox_->currentIndex()).constData());
|
||||
}
|
||||
|
||||
void H264Section::set_opts(const OakEngineEncodingParams *p)
|
||||
{
|
||||
char buf[64];
|
||||
|
||||
CompressionMethod method = k_constant_rate_factor;
|
||||
if (oakengine_encoding_params_video_option(
|
||||
p, "ove_compressionmethod", buf,
|
||||
static_cast<int>(sizeof(buf))) > 0) {
|
||||
method = static_cast<CompressionMethod>(QString::fromUtf8(buf).toInt());
|
||||
}
|
||||
|
||||
compression_method_stack_->setCurrentIndex(method);
|
||||
|
||||
if (method == k_constant_rate_factor) {
|
||||
if (oakengine_encoding_params_video_option(
|
||||
p, "crf", buf, static_cast<int>(sizeof(buf))) > 0) {
|
||||
crf_section_->set_value(QString::fromUtf8(buf).toInt());
|
||||
}
|
||||
} else {
|
||||
int64_t target_rate = oakengine_encoding_params_video_bit_rate(p);
|
||||
int64_t max_rate = oakengine_encoding_params_video_max_bit_rate(p);
|
||||
|
||||
if (method == k_target_bit_rate) {
|
||||
// Use user-supplied values for the bit rate
|
||||
bitrate_section_->set_target_bit_rate(target_rate);
|
||||
bitrate_section_->set_maximum_bit_rate(max_rate);
|
||||
} else {
|
||||
// Calculate the bit rate from the file size divided by the sequence length in seconds (bits per second)
|
||||
if (oakengine_encoding_params_video_option(
|
||||
p, "ove_targetfilesize", buf,
|
||||
static_cast<int>(sizeof(buf))) > 0) {
|
||||
filesize_section_->set_file_size(
|
||||
QString::fromUtf8(buf).toLongLong());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
H264CRFSection::H264CRFSection(int default_crf, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
QHBoxLayout *layout = new QHBoxLayout(this);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
crf_slider_ = new QSlider(Qt::Horizontal);
|
||||
crf_slider_->setMinimum(k_minimum_crf);
|
||||
crf_slider_->setMaximum(k_maximum_crf);
|
||||
crf_slider_->setValue(default_crf);
|
||||
layout->addWidget(crf_slider_);
|
||||
|
||||
IntegerSlider *crf_input = new IntegerSlider();
|
||||
crf_input->setMaximumWidth(QtUtils::q_font_metrics_width(
|
||||
crf_input->fontMetrics(), QStringLiteral("HHHH")));
|
||||
crf_input->set_minimum(k_minimum_crf);
|
||||
crf_input->set_maximum(k_maximum_crf);
|
||||
crf_input->set_value(default_crf);
|
||||
crf_input->SetDefaultValue(default_crf);
|
||||
layout->addWidget(crf_input);
|
||||
|
||||
connect(crf_slider_, &QSlider::valueChanged, crf_input,
|
||||
&IntegerSlider::set_value);
|
||||
connect(crf_input, &IntegerSlider::value_changed, crf_slider_,
|
||||
&QSlider::setValue);
|
||||
}
|
||||
|
||||
int H264CRFSection::get_value() const
|
||||
{
|
||||
return crf_slider_->value();
|
||||
}
|
||||
|
||||
void H264CRFSection::set_value(int c)
|
||||
{
|
||||
crf_slider_->setValue(c);
|
||||
}
|
||||
|
||||
H264BitRateSection::H264BitRateSection(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
QGridLayout *layout = new QGridLayout(this);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
int row = 0;
|
||||
|
||||
layout->addWidget(new QLabel(tr("Target Bit Rate (Mbps):")), row, 0);
|
||||
|
||||
target_rate_ = new FloatSlider();
|
||||
target_rate_->set_minimum(0);
|
||||
layout->addWidget(target_rate_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
layout->addWidget(new QLabel(tr("Maximum Bit Rate (Mbps):")), row, 0);
|
||||
|
||||
max_rate_ = new FloatSlider();
|
||||
max_rate_->set_minimum(0);
|
||||
layout->addWidget(max_rate_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
layout->addWidget(new QLabel(tr("Two-Pass")), row, 0);
|
||||
|
||||
QCheckBox *two_pass_box = new QCheckBox();
|
||||
layout->addWidget(two_pass_box, row, 1);
|
||||
|
||||
// Bit rate defaults
|
||||
target_rate_->set_value(16.0);
|
||||
max_rate_->set_value(32.0);
|
||||
}
|
||||
|
||||
int64_t H264BitRateSection::get_target_bit_rate() const
|
||||
{
|
||||
return qRound64(target_rate_->get_value() * 1000000.0);
|
||||
}
|
||||
|
||||
void H264BitRateSection::set_target_bit_rate(int64_t b)
|
||||
{
|
||||
target_rate_->set_value(double(b) * 0.000001);
|
||||
}
|
||||
|
||||
int64_t H264BitRateSection::get_maximum_bit_rate() const
|
||||
{
|
||||
return qRound64(max_rate_->get_value() * 1000000.0);
|
||||
}
|
||||
|
||||
void H264BitRateSection::set_maximum_bit_rate(int64_t b)
|
||||
{
|
||||
max_rate_->set_value(double(b) * 0.000001);
|
||||
}
|
||||
|
||||
H264FileSizeSection::H264FileSizeSection(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
QGridLayout *layout = new QGridLayout(this);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
int row = 0;
|
||||
|
||||
layout->addWidget(new QLabel(tr("Target File Size (MB):")), row, 0);
|
||||
|
||||
file_size_ = new FloatSlider();
|
||||
file_size_->set_minimum(0);
|
||||
layout->addWidget(file_size_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
layout->addWidget(new QLabel(tr("Two-Pass")), row, 0);
|
||||
|
||||
QCheckBox *two_pass_box = new QCheckBox();
|
||||
layout->addWidget(two_pass_box, row, 1);
|
||||
|
||||
// File size defaults
|
||||
file_size_->set_value(700.0);
|
||||
}
|
||||
|
||||
int64_t H264FileSizeSection::get_file_size() const
|
||||
{
|
||||
// Convert megabytes to BITS
|
||||
return qRound64(file_size_->get_value() * 1024.0 * 1024.0 * 8.0);
|
||||
}
|
||||
|
||||
void H264FileSizeSection::set_file_size(int64_t f)
|
||||
{
|
||||
// Convert bits back to megabytes
|
||||
file_size_->set_value(double(f) / 8.0 / 1024.0 / 1024.0);
|
||||
}
|
||||
|
||||
H265Section::H265Section(QWidget *parent)
|
||||
: H264Section(H264CRFSection::k_default_h265_crf, parent)
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 OAK_H264SECTION_H
|
||||
#define OAK_H264SECTION_H
|
||||
|
||||
#include <QSlider>
|
||||
#include <QStackedWidget>
|
||||
#include <QComboBox>
|
||||
|
||||
#include "codecsection.h"
|
||||
#include "widget/slider/floatslider.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class H264CRFSection : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
H264CRFSection(int default_crf, QWidget *parent = nullptr);
|
||||
|
||||
int get_value() const;
|
||||
void set_value(int c);
|
||||
|
||||
static constexpr int k_default_h264_crf = 18;
|
||||
static constexpr int k_default_h265_crf = 23;
|
||||
|
||||
private:
|
||||
static constexpr int k_minimum_crf = 0;
|
||||
static constexpr int k_maximum_crf = 51;
|
||||
|
||||
QSlider *crf_slider_;
|
||||
};
|
||||
|
||||
class H264BitRateSection : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
H264BitRateSection(QWidget *parent = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Get user-selected target bit rate (returns in BITS)
|
||||
*/
|
||||
int64_t get_target_bit_rate() const;
|
||||
void set_target_bit_rate(int64_t b);
|
||||
|
||||
/**
|
||||
* @brief Get user-selected maximum bit rate (returns in BITS)
|
||||
*/
|
||||
int64_t get_maximum_bit_rate() const;
|
||||
void set_maximum_bit_rate(int64_t b);
|
||||
|
||||
private:
|
||||
FloatSlider *target_rate_;
|
||||
|
||||
FloatSlider *max_rate_;
|
||||
};
|
||||
|
||||
class H264FileSizeSection : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
H264FileSizeSection(QWidget *parent = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Returns file size in BITS
|
||||
*/
|
||||
int64_t get_file_size() const;
|
||||
void set_file_size(int64_t f);
|
||||
|
||||
private:
|
||||
FloatSlider *file_size_;
|
||||
};
|
||||
|
||||
class H264Section : public CodecSection {
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum CompressionMethod {
|
||||
k_constant_rate_factor,
|
||||
k_target_bit_rate,
|
||||
k_target_file_size
|
||||
};
|
||||
|
||||
H264Section(QWidget *parent = nullptr);
|
||||
H264Section(int default_crf, QWidget *parent);
|
||||
|
||||
virtual void add_opts(OakEngineEncodingParams *params) override;
|
||||
|
||||
virtual void set_opts(const OakEngineEncodingParams *p) override;
|
||||
|
||||
private:
|
||||
QStackedWidget *compression_method_stack_;
|
||||
|
||||
H264CRFSection *crf_section_;
|
||||
|
||||
H264BitRateSection *bitrate_section_;
|
||||
|
||||
H264FileSizeSection *filesize_section_;
|
||||
|
||||
QComboBox *preset_combobox_;
|
||||
};
|
||||
|
||||
class H265Section : public H264Section {
|
||||
Q_OBJECT
|
||||
public:
|
||||
H265Section(QWidget *parent = nullptr);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_H264SECTION_H
|
||||
@@ -1,63 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 "imagesection.h"
|
||||
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
ImageSection::ImageSection(QWidget *parent)
|
||||
: CodecSection(parent)
|
||||
{
|
||||
QGridLayout *layout = new QGridLayout(this);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
int row = 0;
|
||||
|
||||
layout->addWidget(new QLabel(tr("Image Sequence:")), row, 0);
|
||||
|
||||
image_sequence_checkbox_ = new QCheckBox();
|
||||
connect(image_sequence_checkbox_, &QCheckBox::toggled, this,
|
||||
&ImageSection::image_sequence_check_box_toggled);
|
||||
layout->addWidget(image_sequence_checkbox_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
layout->addWidget(new QLabel(tr("Frame to Export:")), row, 0);
|
||||
|
||||
frame_slider_ = new RationalSlider();
|
||||
frame_slider_->set_minimum(0);
|
||||
frame_slider_->set_value(0);
|
||||
frame_slider_->set_display_type(slider::k_time);
|
||||
connect(frame_slider_, &RationalSlider::value_changed, this,
|
||||
&ImageSection::time_changed);
|
||||
layout->addWidget(frame_slider_, row, 1);
|
||||
}
|
||||
|
||||
void ImageSection::image_sequence_check_box_toggled(bool e)
|
||||
{
|
||||
frame_slider_->setEnabled(!e);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 OAK_IMAGESECTION_H
|
||||
#define OAK_IMAGESECTION_H
|
||||
|
||||
#include <QCheckBox>
|
||||
|
||||
#include "codecsection.h"
|
||||
#include "widget/slider/rationalslider.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class ImageSection : public CodecSection {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ImageSection(QWidget *parent = nullptr);
|
||||
|
||||
bool is_image_sequence_checked() const
|
||||
{
|
||||
return image_sequence_checkbox_->isChecked();
|
||||
}
|
||||
|
||||
void set_image_sequence_checked(bool e)
|
||||
{
|
||||
image_sequence_checkbox_->setChecked(e);
|
||||
}
|
||||
|
||||
void set_timebase(const Rational &r)
|
||||
{
|
||||
frame_slider_->set_timebase(r);
|
||||
}
|
||||
|
||||
Rational get_time() const
|
||||
{
|
||||
return frame_slider_->get_value();
|
||||
}
|
||||
|
||||
void set_time(const Rational &t)
|
||||
{
|
||||
frame_slider_->set_value(t);
|
||||
}
|
||||
|
||||
signals:
|
||||
void time_changed(const Rational &t);
|
||||
|
||||
private:
|
||||
QCheckBox *image_sequence_checkbox_;
|
||||
|
||||
RationalSlider *frame_slider_;
|
||||
|
||||
private slots:
|
||||
void image_sequence_check_box_toggled(bool e);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_IMAGESECTION_H
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,143 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 OAK_EXPORTDIALOG_H
|
||||
#define OAK_EXPORTDIALOG_H
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QDialog>
|
||||
#include <cstdint>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QLineEdit>
|
||||
#include <QProgressBar>
|
||||
|
||||
#include "dialog/export/exportformatcombobox.h"
|
||||
#include "exportaudiotab.h"
|
||||
#include "exportsubtitlestab.h"
|
||||
#include "exportvideotab.h"
|
||||
#include "oakengine/encoding.h"
|
||||
#include "widget/nodeparamview/nodeparamviewwidgetbridge.h"
|
||||
#include "widget/viewer/viewer.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class ExportDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ExportDialog(OakEngineNode *viewer_node, bool stills_only_mode,
|
||||
QWidget *parent = nullptr);
|
||||
ExportDialog(OakEngineNode *viewer_node, QWidget *parent = nullptr)
|
||||
: ExportDialog(viewer_node, false, parent)
|
||||
{
|
||||
}
|
||||
|
||||
Rational get_selected_timebase() const;
|
||||
void set_selected_timebase(const Rational &r);
|
||||
|
||||
OakEngineEncodingParams *generate_params() const;
|
||||
void set_params(const OakEngineEncodingParams *e);
|
||||
|
||||
virtual bool eventFilter(QObject *o, QEvent *e) override;
|
||||
|
||||
public slots:
|
||||
virtual void done(int r) override;
|
||||
|
||||
signals:
|
||||
void request_import_file(const QString &s);
|
||||
|
||||
private:
|
||||
void add_preferences_tab(QWidget *inner_widget, const QString &title);
|
||||
|
||||
void load_presets();
|
||||
void set_default_filename();
|
||||
|
||||
bool sequence_has_subtitles() const;
|
||||
|
||||
void set_defaults();
|
||||
|
||||
OakEngineNode *viewer_node_;
|
||||
|
||||
int previously_selected_format_;
|
||||
|
||||
Rational get_export_length() const;
|
||||
int64_t get_export_length_in_timebase_units() const;
|
||||
|
||||
enum RangeSelection { k_range_entire_sequence, k_range_in_to_out };
|
||||
|
||||
enum AutoPreset {
|
||||
k_preset_default = -1,
|
||||
k_preset_last_used = -2,
|
||||
};
|
||||
|
||||
QTabWidget *preferences_tabs_;
|
||||
|
||||
QComboBox *preset_combobox_;
|
||||
QComboBox *range_combobox_;
|
||||
std::vector<OakEngineEncodingParams *> presets_;
|
||||
|
||||
QCheckBox *video_enabled_;
|
||||
QCheckBox *audio_enabled_;
|
||||
QCheckBox *subtitles_enabled_;
|
||||
|
||||
ViewerWidget *preview_viewer_;
|
||||
QLineEdit *filename_edit_;
|
||||
ExportFormatComboBox *format_combobox_;
|
||||
|
||||
ExportVideoTab *video_tab_;
|
||||
ExportAudioTab *audio_tab_;
|
||||
ExportSubtitlesTab *subtitle_tab_;
|
||||
|
||||
double video_aspect_ratio_;
|
||||
|
||||
OakEngineColorManager *color_manager_;
|
||||
|
||||
QWidget *preferences_area_;
|
||||
QCheckBox *export_bkg_box_;
|
||||
QCheckBox *import_file_after_export_;
|
||||
|
||||
bool stills_only_mode_;
|
||||
|
||||
bool loading_presets_;
|
||||
|
||||
private slots:
|
||||
void browse_filename();
|
||||
|
||||
void format_changed(int current_format);
|
||||
|
||||
void resolution_changed();
|
||||
|
||||
void update_viewer_dimensions();
|
||||
|
||||
void start_export();
|
||||
|
||||
void export_finished();
|
||||
|
||||
void image_sequence_check_box_changed(bool e);
|
||||
|
||||
void save_preset();
|
||||
|
||||
void preset_combo_box_changed();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_EXPORTDIALOG_H
|
||||
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
* Oak Video Editor - 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/>.
|
||||
*/
|
||||
|
||||
#include "exportadvancedvideodialog.h"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QGridLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QLabel>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
ExportAdvancedVideoDialog::ExportAdvancedVideoDialog(
|
||||
const QList<QString> &pix_fmts, QWidget *parent)
|
||||
: QDialog(parent)
|
||||
{
|
||||
setWindowTitle(tr("Advanced"));
|
||||
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
|
||||
{
|
||||
// Pixel Settings
|
||||
QGroupBox *pixel_group = new QGroupBox();
|
||||
layout->addWidget(pixel_group);
|
||||
pixel_group->setTitle(tr("Pixel"));
|
||||
|
||||
QGridLayout *pixel_layout = new QGridLayout(pixel_group);
|
||||
|
||||
int row = 0;
|
||||
|
||||
pixel_layout->addWidget(new QLabel(tr("Pixel Format:")), row, 0);
|
||||
|
||||
pixel_format_combobox_ = new QComboBox();
|
||||
pixel_format_combobox_->addItems(pix_fmts);
|
||||
pixel_layout->addWidget(pixel_format_combobox_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
pixel_layout->addWidget(new QLabel(tr("YUV Color Range:")), row, 0);
|
||||
|
||||
yuv_color_range_combobox_ = new QComboBox();
|
||||
yuv_color_range_combobox_->addItems(
|
||||
{ tr("Limited (16-235)"), tr("Full (0-255)") });
|
||||
pixel_layout->addWidget(yuv_color_range_combobox_, row, 1);
|
||||
}
|
||||
|
||||
{
|
||||
// Performance Settings
|
||||
QGroupBox *performance_group = new QGroupBox();
|
||||
layout->addWidget(performance_group);
|
||||
performance_group->setTitle(tr("Performance"));
|
||||
|
||||
QGridLayout *performance_layout = new QGridLayout(performance_group);
|
||||
|
||||
int row = 0;
|
||||
|
||||
performance_layout->addWidget(new QLabel(tr("Threads:")), row, 0);
|
||||
|
||||
thread_slider_ = new IntegerSlider();
|
||||
thread_slider_->set_minimum(0);
|
||||
thread_slider_->SetDefaultValue(0);
|
||||
thread_slider_->insert_label_substitution(0, tr("Auto"));
|
||||
performance_layout->addWidget(thread_slider_, row, 1);
|
||||
|
||||
row++;
|
||||
}
|
||||
|
||||
QDialogButtonBox *buttons =
|
||||
new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
connect(buttons, &QDialogButtonBox::accepted, this,
|
||||
&ExportAdvancedVideoDialog::accept);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this,
|
||||
&ExportAdvancedVideoDialog::reject);
|
||||
layout->addWidget(buttons);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* Oak Video Editor - 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 OAK_EXPORTADVANCEDVIDEODIALOG_H
|
||||
#define OAK_EXPORTADVANCEDVIDEODIALOG_H
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QDialog>
|
||||
|
||||
#include "widget/slider/integerslider.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class ExportAdvancedVideoDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ExportAdvancedVideoDialog(const QList<QString> &pix_fmts,
|
||||
QWidget *parent = nullptr);
|
||||
|
||||
int threads() const
|
||||
{
|
||||
return static_cast<int>(thread_slider_->get_value());
|
||||
}
|
||||
|
||||
void set_threads(int t)
|
||||
{
|
||||
thread_slider_->set_value(t);
|
||||
}
|
||||
|
||||
QString pix_fmt() const
|
||||
{
|
||||
return pixel_format_combobox_->currentText();
|
||||
}
|
||||
|
||||
void set_pix_fmt(const QString &s)
|
||||
{
|
||||
pixel_format_combobox_->setCurrentText(s);
|
||||
}
|
||||
|
||||
int yuv_range() const
|
||||
{
|
||||
return static_cast<int>(
|
||||
yuv_color_range_combobox_->currentIndex());
|
||||
}
|
||||
|
||||
void set_yuv_range(int i)
|
||||
{
|
||||
yuv_color_range_combobox_->setCurrentIndex(i);
|
||||
}
|
||||
|
||||
private:
|
||||
IntegerSlider *thread_slider_;
|
||||
|
||||
QComboBox *pixel_format_combobox_;
|
||||
|
||||
QComboBox *yuv_color_range_combobox_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_EXPORTADVANCEDVIDEODIALOG_H
|
||||
@@ -1,139 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 "exportaudiotab.h"
|
||||
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
|
||||
#include <olive/core/core.h>
|
||||
#include "oakengine/encoding.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
const int ExportAudioTab::k_default_bit_rate = 320;
|
||||
|
||||
ExportAudioTab::ExportAudioTab(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
QVBoxLayout *outer_layout = new QVBoxLayout(this);
|
||||
|
||||
QGridLayout *layout = new QGridLayout();
|
||||
outer_layout->addLayout(layout);
|
||||
|
||||
int row = 0;
|
||||
|
||||
layout->addWidget(new QLabel(tr("Codec:")), row, 0);
|
||||
|
||||
codec_combobox_ = new QComboBox();
|
||||
connect(
|
||||
codec_combobox_,
|
||||
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
|
||||
this, &ExportAudioTab::update_sample_formats);
|
||||
connect(
|
||||
codec_combobox_,
|
||||
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
|
||||
this, &ExportAudioTab::update_bit_rate_enabled);
|
||||
layout->addWidget(codec_combobox_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
layout->addWidget(new QLabel(tr("Sample Rate:")), row, 0);
|
||||
|
||||
sample_rate_combobox_ = new SampleRateComboBox();
|
||||
layout->addWidget(sample_rate_combobox_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
layout->addWidget(new QLabel(tr("Channel Layout:")), row, 0);
|
||||
|
||||
channel_layout_combobox_ = new ChannelLayoutComboBox();
|
||||
layout->addWidget(channel_layout_combobox_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
layout->addWidget(new QLabel(tr("Format:")), row, 0);
|
||||
|
||||
sample_format_combobox_ = new SampleFormatComboBox();
|
||||
layout->addWidget(sample_format_combobox_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
layout->addWidget(new QLabel(tr("Bit Rate:")), row, 0);
|
||||
|
||||
bit_rate_slider_ = new IntegerSlider();
|
||||
bit_rate_slider_->set_minimum(32);
|
||||
bit_rate_slider_->set_maximum(320);
|
||||
bit_rate_slider_->set_value(k_default_bit_rate);
|
||||
bit_rate_slider_->set_format(tr("%1 kbps"));
|
||||
layout->addWidget(bit_rate_slider_, row, 1);
|
||||
|
||||
outer_layout->addStretch();
|
||||
}
|
||||
|
||||
int ExportAudioTab::set_format(int format)
|
||||
{
|
||||
const int acodec_count = oakengine_encoding_format_audio_codec_count(format);
|
||||
setEnabled(acodec_count > 0);
|
||||
codec_combobox_->blockSignals(true);
|
||||
codec_combobox_->clear();
|
||||
for (int i = 0; i < acodec_count; i++) {
|
||||
int codec = oakengine_encoding_format_audio_codec_at(format, i);
|
||||
char buf[256];
|
||||
oakengine_encoding_codec_name(codec, buf, sizeof(buf));
|
||||
codec_combobox_->addItem(QString::fromUtf8(buf), codec);
|
||||
}
|
||||
codec_combobox_->blockSignals(false);
|
||||
fmt_ = format;
|
||||
|
||||
update_sample_formats();
|
||||
update_bit_rate_enabled();
|
||||
|
||||
return acodec_count;
|
||||
}
|
||||
|
||||
void ExportAudioTab::update_sample_formats()
|
||||
{
|
||||
// Use oakengine to get sample format values and build the vector
|
||||
const int count = oakengine_encoding_sample_format_count(fmt_, get_codec());
|
||||
std::vector<olive::core::SampleFormat> fmts;
|
||||
fmts.reserve(count);
|
||||
for (int i = 0; i < count; i++) {
|
||||
int val = oakengine_encoding_sample_format_at(fmt_, get_codec(), i);
|
||||
fmts.push_back(olive::core::SampleFormat(static_cast<olive::core::SampleFormat::Format>(val)));
|
||||
}
|
||||
sample_format_combobox_->set_available_formats(fmts);
|
||||
}
|
||||
|
||||
void ExportAudioTab::update_bit_rate_enabled()
|
||||
{
|
||||
bool uses_bitrate = !oakengine_encoding_codec_is_lossless(get_codec());
|
||||
bit_rate_slider_->setEnabled(uses_bitrate);
|
||||
|
||||
if (!uses_bitrate) {
|
||||
bit_rate_slider_->set_tristate();
|
||||
} else {
|
||||
bit_rate_slider_->set_value(k_default_bit_rate);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 OAK_EXPORTAUDIOTAB_H
|
||||
#define OAK_EXPORTAUDIOTAB_H
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QWidget>
|
||||
|
||||
#include "oakutil/define.h"
|
||||
#include "widget/slider/integerslider.h"
|
||||
#include "widget/standardcombos/standardcombos.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class ExportAudioTab : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ExportAudioTab(QWidget *parent = nullptr);
|
||||
|
||||
int get_codec() const
|
||||
{
|
||||
return codec_combobox_->currentData().toInt();
|
||||
}
|
||||
|
||||
void set_codec(int c)
|
||||
{
|
||||
for (int i = 0; i < codec_combobox_->count(); i++) {
|
||||
if (codec_combobox_->itemData(i) == c) {
|
||||
codec_combobox_->setCurrentIndex(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SampleRateComboBox *sample_rate_combobox() const
|
||||
{
|
||||
return sample_rate_combobox_;
|
||||
}
|
||||
|
||||
SampleFormatComboBox *sample_format_combobox() const
|
||||
{
|
||||
return sample_format_combobox_;
|
||||
}
|
||||
|
||||
ChannelLayoutComboBox *channel_layout_combobox() const
|
||||
{
|
||||
return channel_layout_combobox_;
|
||||
}
|
||||
|
||||
IntegerSlider *bit_rate_slider() const
|
||||
{
|
||||
return bit_rate_slider_;
|
||||
}
|
||||
|
||||
public slots:
|
||||
int set_format(int format);
|
||||
|
||||
private:
|
||||
int fmt_;
|
||||
QComboBox *codec_combobox_;
|
||||
SampleRateComboBox *sample_rate_combobox_;
|
||||
ChannelLayoutComboBox *channel_layout_combobox_;
|
||||
SampleFormatComboBox *sample_format_combobox_;
|
||||
IntegerSlider *bit_rate_slider_;
|
||||
|
||||
static const int k_default_bit_rate;
|
||||
|
||||
private slots:
|
||||
void update_sample_formats();
|
||||
|
||||
void update_bit_rate_enabled();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_EXPORTAUDIOTAB_H
|
||||
@@ -1,151 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 "exportformatcombobox.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
|
||||
#include "oakengine/encoding.h"
|
||||
#include "ui/icons/icons.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
ExportFormatComboBox::ExportFormatComboBox(Mode mode, QWidget *parent)
|
||||
: QComboBox(parent)
|
||||
{
|
||||
// The invalid placeholder format is the format count itself
|
||||
// (ExportFormat::k_format_count), not -1.
|
||||
current_ = oakengine_encoding_format_count();
|
||||
|
||||
custom_menu_ = new Menu(this);
|
||||
|
||||
// Populate combobox formats
|
||||
switch (mode) {
|
||||
case k_show_all_formats:
|
||||
custom_menu_->addAction(create_header(icon::video, tr("Video")));
|
||||
populate_type(TrackReference::k_video);
|
||||
custom_menu_->addSeparator();
|
||||
|
||||
custom_menu_->addAction(create_header(icon::audio, tr("Audio")));
|
||||
populate_type(TrackReference::k_audio);
|
||||
custom_menu_->addSeparator();
|
||||
|
||||
custom_menu_->addAction(create_header(icon::subtitles, tr("Subtitle")));
|
||||
populate_type(TrackReference::k_subtitle);
|
||||
break;
|
||||
case k_show_audio_only:
|
||||
populate_type(TrackReference::k_audio);
|
||||
break;
|
||||
case k_show_video_only:
|
||||
populate_type(TrackReference::k_video);
|
||||
break;
|
||||
case k_show_subtitles_only:
|
||||
populate_type(TrackReference::k_subtitle);
|
||||
break;
|
||||
}
|
||||
|
||||
connect(custom_menu_, &Menu::triggered, this,
|
||||
&ExportFormatComboBox::handle_index_change);
|
||||
}
|
||||
|
||||
void ExportFormatComboBox::showPopup()
|
||||
{
|
||||
custom_menu_->setMinimumWidth(this->width());
|
||||
custom_menu_->exec(mapToGlobal(QPoint(0, 0)));
|
||||
}
|
||||
|
||||
void ExportFormatComboBox::set_format(int fmt)
|
||||
{
|
||||
current_ = fmt;
|
||||
clear();
|
||||
char buf[256];
|
||||
oakengine_encoding_format_name(fmt, buf, sizeof(buf));
|
||||
addItem(QString::fromUtf8(buf));
|
||||
}
|
||||
|
||||
void ExportFormatComboBox::handle_index_change(QAction *a)
|
||||
{
|
||||
int f = a->data().toInt();
|
||||
set_format(f);
|
||||
emit format_changed(f);
|
||||
}
|
||||
|
||||
void ExportFormatComboBox::populate_type(TrackReference::Type type)
|
||||
{
|
||||
const int fmt_count = oakengine_encoding_format_count();
|
||||
for (int i = 0; i < fmt_count; i++) {
|
||||
int f = i;
|
||||
char buf[256];
|
||||
|
||||
bool has_video = oakengine_encoding_format_video_codec_count(f) > 0;
|
||||
bool has_audio = oakengine_encoding_format_audio_codec_count(f) > 0;
|
||||
bool has_sub = oakengine_encoding_format_subtitle_codec_count(f) > 0;
|
||||
|
||||
if (type == TrackReference::k_video && has_video) {
|
||||
// Do nothing
|
||||
} else if (type == TrackReference::k_audio && !has_video && has_audio) {
|
||||
// Do nothing
|
||||
} else if (type == TrackReference::k_subtitle && !has_video && !has_audio && has_sub) {
|
||||
// Do nothing
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
oakengine_encoding_format_name(f, buf, sizeof(buf));
|
||||
QString format_name = QString::fromUtf8(buf);
|
||||
|
||||
QAction *a = custom_menu_->addAction(format_name);
|
||||
a->setData(i);
|
||||
a->setIconVisibleInMenu(false);
|
||||
}
|
||||
}
|
||||
|
||||
QWidgetAction *ExportFormatComboBox::create_header(const QIcon &icon,
|
||||
const QString &title)
|
||||
{
|
||||
QWidgetAction *a = new QWidgetAction(this);
|
||||
|
||||
QWidget *w = new QWidget();
|
||||
QHBoxLayout *layout = new QHBoxLayout(w);
|
||||
|
||||
QLabel *icon_lbl = new QLabel();
|
||||
|
||||
QLabel *text_lbl = new QLabel(title);
|
||||
text_lbl->setAlignment(Qt::AlignCenter);
|
||||
QFont f = text_lbl->font();
|
||||
f.setWeight(QFont::Bold);
|
||||
text_lbl->setFont(f);
|
||||
|
||||
icon_lbl->setPixmap(icon.pixmap(text_lbl->sizeHint()));
|
||||
|
||||
layout->addStretch();
|
||||
layout->addWidget(icon_lbl);
|
||||
layout->addWidget(text_lbl);
|
||||
layout->addStretch();
|
||||
|
||||
a->setDefaultWidget(w);
|
||||
a->setEnabled(false);
|
||||
return a;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 OAK_EXPORTFORMATCOMBOBOX_H
|
||||
#define OAK_EXPORTFORMATCOMBOBOX_H
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QWidgetAction>
|
||||
|
||||
#include "common/trackreferencehandle.h"
|
||||
#include "widget/menu/menu.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class ExportFormatComboBox : public QComboBox {
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Mode {
|
||||
k_show_all_formats,
|
||||
k_show_audio_only,
|
||||
k_show_video_only,
|
||||
k_show_subtitles_only
|
||||
};
|
||||
|
||||
ExportFormatComboBox(Mode mode, QWidget *parent = nullptr);
|
||||
ExportFormatComboBox(QWidget *parent = nullptr)
|
||||
: ExportFormatComboBox(k_show_all_formats, parent)
|
||||
{
|
||||
}
|
||||
|
||||
int get_format() const
|
||||
{
|
||||
return current_;
|
||||
}
|
||||
|
||||
void showPopup();
|
||||
|
||||
signals:
|
||||
void format_changed(int fmt);
|
||||
|
||||
public slots:
|
||||
void set_format(int fmt);
|
||||
|
||||
private slots:
|
||||
void handle_index_change(QAction *a);
|
||||
|
||||
private:
|
||||
void populate_type(TrackReference::Type type);
|
||||
|
||||
QWidgetAction *create_header(const QIcon &icon, const QString &title);
|
||||
|
||||
Menu *custom_menu_;
|
||||
|
||||
int current_ = -1; // was ExportFormat::k_format_count
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_EXPORTFORMATCOMBOBOX_H
|
||||
@@ -1,127 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 "exportsavepresetdialog.h"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QDir>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
ExportSavePresetDialog::ExportSavePresetDialog(const OakEngineEncodingParams *p,
|
||||
QWidget *parent)
|
||||
: QDialog(parent)
|
||||
, params_(p)
|
||||
{
|
||||
auto layout = new QVBoxLayout(this);
|
||||
|
||||
name_edit_ = new QLineEdit();
|
||||
|
||||
// Populate existing list
|
||||
QStringList l;
|
||||
{
|
||||
const int n = oakengine_encoding_preset_count();
|
||||
for (int i = 0; i < n; i++) {
|
||||
char name_buf[256];
|
||||
if (oakengine_encoding_preset_name(
|
||||
i, name_buf, static_cast<int>(sizeof(name_buf))) > 0) {
|
||||
l.append(QString::fromUtf8(name_buf));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!l.empty()) {
|
||||
auto list_widget = new QListWidget();
|
||||
for (const QString &f : l) {
|
||||
list_widget->addItem(f);
|
||||
}
|
||||
connect(list_widget, &QListWidget::currentTextChanged, name_edit_,
|
||||
&QLineEdit::setText);
|
||||
layout->addWidget(list_widget);
|
||||
}
|
||||
|
||||
auto name_layout = new QHBoxLayout();
|
||||
layout->addLayout(name_layout);
|
||||
|
||||
name_layout->addWidget(new QLabel(tr("Name:")));
|
||||
|
||||
name_edit_->setFocus();
|
||||
name_layout->addWidget(name_edit_);
|
||||
|
||||
auto btns =
|
||||
new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
connect(btns, &QDialogButtonBox::accepted, this,
|
||||
&ExportSavePresetDialog::accept);
|
||||
connect(btns, &QDialogButtonBox::rejected, this,
|
||||
&ExportSavePresetDialog::reject);
|
||||
layout->addWidget(btns);
|
||||
|
||||
setWindowTitle(tr("Save Export Preset"));
|
||||
}
|
||||
|
||||
void ExportSavePresetDialog::accept()
|
||||
{
|
||||
if (name_edit_->text().isEmpty()) {
|
||||
QMessageBox::critical(
|
||||
this, tr("Invalid Name"),
|
||||
tr("You must enter a name to save an export preset."));
|
||||
return;
|
||||
}
|
||||
|
||||
char preset_path_buf[1024];
|
||||
preset_path_buf[0] = '\0';
|
||||
oakengine_encoding_preset_path(
|
||||
preset_path_buf, static_cast<int>(sizeof(preset_path_buf)));
|
||||
QDir d(QString::fromUtf8(preset_path_buf));
|
||||
|
||||
if (!d.exists()) {
|
||||
d.mkpath(QStringLiteral("."));
|
||||
}
|
||||
|
||||
if (d.exists(name_edit_->text())) {
|
||||
if (QMessageBox::question(
|
||||
this, tr("Overwrite Preset"),
|
||||
tr("A preset with the name \"%1\" already exists. Do you wish to overwrite it?")
|
||||
.arg(name_edit_->text()),
|
||||
QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const QByteArray full_path =
|
||||
d.filePath(name_edit_->text()).toUtf8();
|
||||
const int rc = oakengine_encoding_params_save_file(
|
||||
params_, full_path.constData());
|
||||
if (rc != OAKENGINE_OK) {
|
||||
QMessageBox::critical(
|
||||
this, tr("Write Error"),
|
||||
tr("Failed to save preset to \"%1\".").arg(
|
||||
QString::fromUtf8(full_path)));
|
||||
return;
|
||||
}
|
||||
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 OAK_EXPORTSAVEPRESETDIALOG_H
|
||||
#define OAK_EXPORTSAVEPRESETDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QLineEdit>
|
||||
#include <QListWidget>
|
||||
|
||||
#include "oakengine/encoding.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class ExportSavePresetDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ExportSavePresetDialog(const OakEngineEncodingParams *p, QWidget *parent = nullptr);
|
||||
|
||||
QString get_selected_preset_name() const
|
||||
{
|
||||
return name_edit_->text();
|
||||
}
|
||||
|
||||
public slots:
|
||||
virtual void accept() override;
|
||||
|
||||
private:
|
||||
QLineEdit *name_edit_;
|
||||
|
||||
const OakEngineEncodingParams *params_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_EXPORTSAVEPRESETDIALOG_H
|
||||
@@ -1,80 +0,0 @@
|
||||
#include "exportsubtitlestab.h"
|
||||
|
||||
#include <QGridLayout>
|
||||
|
||||
#include "oakengine/encoding.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
ExportSubtitlesTab::ExportSubtitlesTab(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
QVBoxLayout *outer_layout = new QVBoxLayout(this);
|
||||
|
||||
QGridLayout *layout = new QGridLayout();
|
||||
outer_layout->addLayout(layout);
|
||||
|
||||
int row = 0;
|
||||
|
||||
sidecar_checkbox_ = new QCheckBox(tr("Export to sidecar file"));
|
||||
layout->addWidget(sidecar_checkbox_, row, 0, 1, 2);
|
||||
|
||||
row++;
|
||||
|
||||
sidecar_format_label_ = new QLabel(tr("Sidecar Format:"));
|
||||
sidecar_format_label_->setVisible(false);
|
||||
layout->addWidget(sidecar_format_label_, row, 0);
|
||||
|
||||
sidecar_format_combobox_ =
|
||||
new ExportFormatComboBox(ExportFormatComboBox::k_show_subtitles_only);
|
||||
sidecar_format_combobox_->setVisible(true);
|
||||
layout->addWidget(sidecar_format_combobox_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
layout->addWidget(new QLabel(tr("Codec:")), row, 0);
|
||||
|
||||
codec_combobox_ = new QComboBox();
|
||||
layout->addWidget(codec_combobox_, row, 1);
|
||||
|
||||
outer_layout->addStretch();
|
||||
|
||||
connect(sidecar_checkbox_, &QCheckBox::toggled, sidecar_format_label_,
|
||||
&QWidget::setVisible);
|
||||
connect(sidecar_checkbox_, &QCheckBox::toggled, sidecar_format_combobox_,
|
||||
&QWidget::setVisible);
|
||||
}
|
||||
|
||||
int ExportSubtitlesTab::set_format(int format)
|
||||
{
|
||||
const bool has_video = oakengine_encoding_format_video_codec_count(format) > 0;
|
||||
const bool has_audio = oakengine_encoding_format_audio_codec_count(format) > 0;
|
||||
int scodec_count = oakengine_encoding_format_subtitle_codec_count(format);
|
||||
|
||||
if (scodec_count > 0 && !has_video && !has_audio) {
|
||||
// If format supports ONLY scodecs, default this to off and disable it
|
||||
sidecar_checkbox_->setChecked(false);
|
||||
sidecar_checkbox_->setEnabled(false);
|
||||
} else {
|
||||
// If format does not support scodecs, default this to checked and disable it
|
||||
sidecar_checkbox_->setChecked(scodec_count == 0);
|
||||
sidecar_checkbox_->setEnabled(scodec_count > 0);
|
||||
}
|
||||
|
||||
// Refresh for sidecar format
|
||||
int sidecar_fmt = sidecar_format_combobox_->get_format();
|
||||
scodec_count = oakengine_encoding_format_subtitle_codec_count(sidecar_fmt);
|
||||
|
||||
codec_combobox_->clear();
|
||||
for (int i = 0; i < scodec_count; i++) {
|
||||
int scodec = oakengine_encoding_format_subtitle_codec_at(sidecar_fmt, i);
|
||||
char buf[256];
|
||||
oakengine_encoding_codec_name(scodec, buf, sizeof(buf));
|
||||
codec_combobox_->addItem(QString::fromUtf8(buf), scodec);
|
||||
}
|
||||
|
||||
return scodec_count;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 OAK_EXPORTSUBTITLESTAB_H
|
||||
#define OAK_EXPORTSUBTITLESTAB_H
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QLabel>
|
||||
|
||||
#include "oakutil/qtutils.h"
|
||||
#include "dialog/export/exportformatcombobox.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class ExportSubtitlesTab : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ExportSubtitlesTab(QWidget *parent = nullptr);
|
||||
|
||||
bool get_sidecar_enabled() const
|
||||
{
|
||||
return sidecar_checkbox_->isChecked();
|
||||
}
|
||||
void set_sidecar_enabled(bool e)
|
||||
{
|
||||
sidecar_checkbox_->setChecked(e);
|
||||
}
|
||||
|
||||
int get_sidecar_format() const
|
||||
{
|
||||
return sidecar_format_combobox_->get_format();
|
||||
}
|
||||
void set_sidecar_format(int f)
|
||||
{
|
||||
sidecar_format_combobox_->set_format(f);
|
||||
}
|
||||
|
||||
int set_format(int format);
|
||||
|
||||
int get_subtitle_codec()
|
||||
{
|
||||
return codec_combobox_->currentData().toInt();
|
||||
}
|
||||
|
||||
void set_subtitle_codec(int c)
|
||||
{
|
||||
QtUtils::set_combo_box_data(codec_combobox_, c);
|
||||
}
|
||||
|
||||
private:
|
||||
QCheckBox *sidecar_checkbox_;
|
||||
|
||||
QLabel *sidecar_format_label_;
|
||||
ExportFormatComboBox *sidecar_format_combobox_;
|
||||
|
||||
QComboBox *codec_combobox_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_EXPORTSUBTITLESTAB_H
|
||||
@@ -1,314 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 "exportvideotab.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QGridLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
|
||||
#include "exportadvancedvideodialog.h"
|
||||
#include "oakengine/encoding.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
ExportVideoTab::ExportVideoTab(OakEngineColorManager *color_manager, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, color_manager_(color_manager)
|
||||
, threads_(0)
|
||||
, color_range_(0) // k_color_range_default
|
||||
{
|
||||
QVBoxLayout *outer_layout = new QVBoxLayout(this);
|
||||
|
||||
outer_layout->addWidget(setup_resolution_section());
|
||||
|
||||
outer_layout->addWidget(setup_codec_section());
|
||||
|
||||
outer_layout->addWidget(setup_color_section());
|
||||
|
||||
outer_layout->addStretch();
|
||||
}
|
||||
|
||||
int ExportVideoTab::set_format(int format)
|
||||
{
|
||||
format_ = format;
|
||||
|
||||
const int vcodec_count = oakengine_encoding_format_video_codec_count(format);
|
||||
setEnabled(vcodec_count > 0);
|
||||
codec_combobox()->clear();
|
||||
for (int i = 0; i < vcodec_count; i++) {
|
||||
int vcodec = oakengine_encoding_format_video_codec_at(format, i);
|
||||
char buf[256];
|
||||
oakengine_encoding_codec_name(vcodec, buf, sizeof(buf));
|
||||
codec_combobox()->addItem(QString::fromUtf8(buf), vcodec);
|
||||
}
|
||||
return vcodec_count;
|
||||
}
|
||||
|
||||
bool ExportVideoTab::is_image_sequence_set() const
|
||||
{
|
||||
ImageSection *img_section =
|
||||
dynamic_cast<ImageSection *>(codec_stack_->currentWidget());
|
||||
|
||||
return (img_section && img_section->is_image_sequence_checked());
|
||||
}
|
||||
|
||||
void ExportVideoTab::set_image_sequence(bool e) const
|
||||
{
|
||||
if (ImageSection *img_section =
|
||||
dynamic_cast<ImageSection *>(codec_stack_->currentWidget())) {
|
||||
img_section->set_image_sequence_checked(e);
|
||||
}
|
||||
}
|
||||
|
||||
QWidget *ExportVideoTab::setup_resolution_section()
|
||||
{
|
||||
int row = 0;
|
||||
|
||||
QGroupBox *resolution_group = new QGroupBox();
|
||||
resolution_group->setTitle(tr("General"));
|
||||
|
||||
QGridLayout *layout = new QGridLayout(resolution_group);
|
||||
|
||||
layout->addWidget(new QLabel(tr("Width:")), row, 0);
|
||||
|
||||
width_slider_ = new IntegerSlider();
|
||||
width_slider_->set_minimum(1);
|
||||
layout->addWidget(width_slider_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
layout->addWidget(new QLabel(tr("Height:")), row, 0);
|
||||
|
||||
height_slider_ = new IntegerSlider();
|
||||
height_slider_->set_minimum(1);
|
||||
layout->addWidget(height_slider_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
layout->addWidget(new QLabel(tr("Maintain Aspect Ratio:")), row, 0);
|
||||
|
||||
maintain_aspect_checkbox_ = new QCheckBox();
|
||||
maintain_aspect_checkbox_->setChecked(true);
|
||||
layout->addWidget(maintain_aspect_checkbox_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
layout->addWidget(new QLabel(tr("Scaling Method:")), row, 0);
|
||||
|
||||
scaling_method_combobox_ = new QComboBox();
|
||||
scaling_method_combobox_->setEnabled(false);
|
||||
scaling_method_combobox_->addItem(tr("Fit"), OAKENGINE_ENCODING_SCALING_FIT);
|
||||
scaling_method_combobox_->addItem(tr("Stretch"), OAKENGINE_ENCODING_SCALING_STRETCH);
|
||||
scaling_method_combobox_->addItem(tr("Crop"), OAKENGINE_ENCODING_SCALING_CROP);
|
||||
layout->addWidget(scaling_method_combobox_, row, 1);
|
||||
|
||||
// Automatically enable/disable the scaling method depending on maintain aspect ratio
|
||||
connect(maintain_aspect_checkbox_, &QCheckBox::toggled, this,
|
||||
&ExportVideoTab::maintain_aspect_ratio_changed);
|
||||
|
||||
row++;
|
||||
|
||||
layout->addWidget(new QLabel(tr("Frame Rate:")), row, 0);
|
||||
|
||||
frame_rate_combobox_ = new FrameRateComboBox();
|
||||
connect(frame_rate_combobox_, &FrameRateComboBox::frame_rate_changed, this,
|
||||
&ExportVideoTab::update_frame_rate);
|
||||
layout->addWidget(frame_rate_combobox_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
layout->addWidget(new QLabel(tr("Pixel Aspect Ratio:")), row, 0);
|
||||
|
||||
pixel_aspect_combobox_ = new PixelAspectRatioComboBox();
|
||||
layout->addWidget(pixel_aspect_combobox_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
layout->addWidget(new QLabel(tr("Interlacing:")), row, 0);
|
||||
|
||||
interlaced_combobox_ = new InterlacedComboBox();
|
||||
layout->addWidget(interlaced_combobox_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
layout->addWidget(new QLabel(tr("Quality:")), row, 0);
|
||||
|
||||
pixel_format_field_ = new PixelFormatComboBox(false);
|
||||
layout->addWidget(pixel_format_field_, row, 1);
|
||||
|
||||
return resolution_group;
|
||||
}
|
||||
|
||||
QWidget *ExportVideoTab::setup_color_section()
|
||||
{
|
||||
color_space_chooser_ = new ColorSpaceChooser(color_manager_, true, false);
|
||||
connect(color_space_chooser_, &ColorSpaceChooser::input_color_space_changed,
|
||||
this, &ExportVideoTab::color_space_changed);
|
||||
return color_space_chooser_;
|
||||
}
|
||||
|
||||
QWidget *ExportVideoTab::setup_codec_section()
|
||||
{
|
||||
int row = 0;
|
||||
|
||||
QGroupBox *codec_group = new QGroupBox();
|
||||
codec_group->setTitle(tr("Codec"));
|
||||
|
||||
QGridLayout *codec_layout = new QGridLayout(codec_group);
|
||||
|
||||
codec_layout->addWidget(new QLabel(tr("Codec:")), row, 0);
|
||||
|
||||
codec_combobox_ = new QComboBox();
|
||||
codec_layout->addWidget(codec_combobox_, row, 1);
|
||||
connect(
|
||||
codec_combobox_,
|
||||
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
|
||||
this, &ExportVideoTab::video_codec_changed);
|
||||
|
||||
row++;
|
||||
|
||||
codec_stack_ = new CodecStack();
|
||||
codec_layout->addWidget(codec_stack_, row, 0, 1, 2);
|
||||
|
||||
image_section_ = new ImageSection();
|
||||
connect(image_section_, &ImageSection::time_changed, this,
|
||||
&ExportVideoTab::time_changed);
|
||||
codec_stack_->addWidget(image_section_);
|
||||
|
||||
h264_section_ = new H264Section();
|
||||
codec_stack_->addWidget(h264_section_);
|
||||
|
||||
h265_section_ = new H265Section();
|
||||
codec_stack_->addWidget(h265_section_);
|
||||
|
||||
av1_section_ = new AV1Section();
|
||||
codec_stack_->addWidget(av1_section_);
|
||||
|
||||
cineform_section_ = new CineformSection();
|
||||
codec_stack_->addWidget(cineform_section_);
|
||||
|
||||
row++;
|
||||
|
||||
QPushButton *advanced_btn = new QPushButton(tr("Advanced"));
|
||||
connect(advanced_btn, &QPushButton::clicked, this,
|
||||
&ExportVideoTab::open_advanced_dialog);
|
||||
codec_layout->addWidget(advanced_btn, row, 1);
|
||||
|
||||
return codec_group;
|
||||
}
|
||||
|
||||
void ExportVideoTab::maintain_aspect_ratio_changed(bool val)
|
||||
{
|
||||
scaling_method_combobox_->setEnabled(!val);
|
||||
}
|
||||
|
||||
void ExportVideoTab::open_advanced_dialog()
|
||||
{
|
||||
// Find pixel formats compatible with this encoder
|
||||
QStringList pixel_formats;
|
||||
const int pix_count = oakengine_encoding_pix_fmt_count(format_, get_selected_codec());
|
||||
for (int i = 0; i < pix_count; i++) {
|
||||
char buf[64];
|
||||
oakengine_encoding_pix_fmt_at(format_, get_selected_codec(), i, buf, sizeof(buf));
|
||||
pixel_formats.append(QString::fromUtf8(buf));
|
||||
}
|
||||
|
||||
ExportAdvancedVideoDialog d(pixel_formats, this);
|
||||
|
||||
d.set_threads(threads_);
|
||||
d.set_pix_fmt(pix_fmt_);
|
||||
d.set_yuv_range(color_range_);
|
||||
|
||||
if (d.exec() == QDialog::Accepted) {
|
||||
threads_ = d.threads();
|
||||
pix_fmt_ = d.pix_fmt();
|
||||
color_range_ = d.yuv_range();
|
||||
}
|
||||
}
|
||||
|
||||
void ExportVideoTab::update_frame_rate(Rational r)
|
||||
{
|
||||
// Convert frame rate to timebase
|
||||
r.flip();
|
||||
|
||||
for (int i = 0; i < codec_stack_->count(); i++) {
|
||||
ImageSection *img =
|
||||
dynamic_cast<ImageSection *>(codec_stack_->widget(i));
|
||||
if (img) {
|
||||
img->set_timebase(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ExportVideoTab::video_codec_changed()
|
||||
{
|
||||
int codec = get_selected_codec();
|
||||
|
||||
switch (codec) {
|
||||
case OAKENGINE_ENCODING_CODEC_H264:
|
||||
case OAKENGINE_ENCODING_CODEC_H264RGB:
|
||||
set_codec_section(h264_section_);
|
||||
break;
|
||||
case OAKENGINE_ENCODING_CODEC_H265:
|
||||
set_codec_section(h265_section_);
|
||||
break;
|
||||
case OAKENGINE_ENCODING_CODEC_AV1:
|
||||
set_codec_section(av1_section_);
|
||||
break;
|
||||
case OAKENGINE_ENCODING_CODEC_CINEFORM:
|
||||
set_codec_section(cineform_section_);
|
||||
break;
|
||||
default:
|
||||
set_codec_section(
|
||||
oakengine_encoding_codec_is_still_image(codec) ? image_section_ : nullptr);
|
||||
}
|
||||
|
||||
// Set default pixel format
|
||||
QStringList pix_fmts;
|
||||
const int pix_count = oakengine_encoding_pix_fmt_count(format_, codec);
|
||||
for (int i = 0; i < pix_count; i++) {
|
||||
char buf[64];
|
||||
oakengine_encoding_pix_fmt_at(format_, codec, i, buf, sizeof(buf));
|
||||
pix_fmts.append(QString::fromUtf8(buf));
|
||||
}
|
||||
if (!pix_fmts.isEmpty()) {
|
||||
pix_fmt_ = pix_fmts.first();
|
||||
} else {
|
||||
pix_fmt_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void ExportVideoTab::set_time(const Rational &time)
|
||||
{
|
||||
for (int i = 0; i < codec_stack_->count(); i++) {
|
||||
ImageSection *img =
|
||||
dynamic_cast<ImageSection *>(codec_stack_->widget(i));
|
||||
if (img) {
|
||||
img->set_time(time);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,230 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
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 OAK_EXPORTVIDEOTAB_H
|
||||
#define OAK_EXPORTVIDEOTAB_H
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QWidget>
|
||||
|
||||
#include "oakutil/qtutils.h"
|
||||
#include "dialog/export/codec/av1section.h"
|
||||
#include "dialog/export/codec/cineformsection.h"
|
||||
#include "dialog/export/codec/codecstack.h"
|
||||
#include "dialog/export/codec/h264section.h"
|
||||
#include "dialog/export/codec/imagesection.h"
|
||||
#include "oakengine/color.h"
|
||||
#include "widget/colorwheel/colorspacechooser.h"
|
||||
#include "widget/manageddisplay/colorprocessorhandle.h"
|
||||
#include "widget/slider/integerslider.h"
|
||||
#include "widget/standardcombos/standardcombos.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class ExportVideoTab : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ExportVideoTab(OakEngineColorManager *color_manager, QWidget *parent = nullptr);
|
||||
|
||||
int set_format(int format);
|
||||
|
||||
bool is_image_sequence_set() const;
|
||||
void set_image_sequence(bool e) const;
|
||||
|
||||
Rational get_still_image_time() const
|
||||
{
|
||||
return image_section_->get_time();
|
||||
}
|
||||
|
||||
int get_selected_codec() const
|
||||
{
|
||||
return codec_combobox()->currentData().toInt();
|
||||
}
|
||||
|
||||
void set_selected_codec(int c)
|
||||
{
|
||||
QtUtils::set_combo_box_data(codec_combobox(), c);
|
||||
}
|
||||
|
||||
QComboBox *codec_combobox() const
|
||||
{
|
||||
return codec_combobox_;
|
||||
}
|
||||
|
||||
IntegerSlider *width_slider() const
|
||||
{
|
||||
return width_slider_;
|
||||
}
|
||||
|
||||
IntegerSlider *height_slider() const
|
||||
{
|
||||
return height_slider_;
|
||||
}
|
||||
|
||||
QCheckBox *maintain_aspect_checkbox() const
|
||||
{
|
||||
return maintain_aspect_checkbox_;
|
||||
}
|
||||
|
||||
QComboBox *scaling_method_combobox() const
|
||||
{
|
||||
return scaling_method_combobox_;
|
||||
}
|
||||
|
||||
Rational get_selected_frame_rate() const
|
||||
{
|
||||
return frame_rate_combobox_->get_frame_rate();
|
||||
}
|
||||
|
||||
void set_selected_frame_rate(const Rational &fr)
|
||||
{
|
||||
frame_rate_combobox_->set_frame_rate(fr);
|
||||
update_frame_rate(fr);
|
||||
}
|
||||
|
||||
QString current_ocio_color_space()
|
||||
{
|
||||
return color_space_chooser_->input();
|
||||
}
|
||||
|
||||
void set_ocio_color_space(const QString &s)
|
||||
{
|
||||
color_space_chooser_->set_input(s);
|
||||
}
|
||||
|
||||
CodecSection *get_codec_section() const
|
||||
{
|
||||
return static_cast<CodecSection *>(codec_stack_->currentWidget());
|
||||
}
|
||||
|
||||
void set_codec_section(CodecSection *section)
|
||||
{
|
||||
if (section) {
|
||||
codec_stack_->setVisible(true);
|
||||
codec_stack_->setCurrentWidget(section);
|
||||
} else {
|
||||
codec_stack_->setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
InterlacedComboBox *interlaced_combobox() const
|
||||
{
|
||||
return interlaced_combobox_;
|
||||
}
|
||||
|
||||
PixelAspectRatioComboBox *pixel_aspect_combobox() const
|
||||
{
|
||||
return pixel_aspect_combobox_;
|
||||
}
|
||||
|
||||
PixelFormatComboBox *pixel_format_field() const
|
||||
{
|
||||
return pixel_format_field_;
|
||||
}
|
||||
|
||||
const int &threads() const
|
||||
{
|
||||
return threads_;
|
||||
}
|
||||
|
||||
void set_threads(int t)
|
||||
{
|
||||
threads_ = t;
|
||||
}
|
||||
|
||||
const QString &pix_fmt() const
|
||||
{
|
||||
return pix_fmt_;
|
||||
}
|
||||
void set_pix_fmt(const QString &s)
|
||||
{
|
||||
pix_fmt_ = s;
|
||||
}
|
||||
|
||||
int color_range() const
|
||||
{
|
||||
return color_range_;
|
||||
}
|
||||
void set_color_range(int c)
|
||||
{
|
||||
color_range_ = c;
|
||||
}
|
||||
|
||||
public slots:
|
||||
void video_codec_changed();
|
||||
|
||||
void set_time(const Rational &time);
|
||||
|
||||
signals:
|
||||
void color_space_changed(const QString &colorspace);
|
||||
|
||||
void image_sequence_check_box_changed(bool e);
|
||||
|
||||
void time_changed(const Rational &time);
|
||||
|
||||
private:
|
||||
QWidget *setup_resolution_section();
|
||||
QWidget *setup_color_section();
|
||||
QWidget *setup_codec_section();
|
||||
|
||||
QComboBox *codec_combobox_;
|
||||
FrameRateComboBox *frame_rate_combobox_;
|
||||
QCheckBox *maintain_aspect_checkbox_;
|
||||
QComboBox *scaling_method_combobox_;
|
||||
|
||||
CodecStack *codec_stack_;
|
||||
ImageSection *image_section_;
|
||||
H264Section *h264_section_;
|
||||
H264Section *h265_section_;
|
||||
AV1Section *av1_section_;
|
||||
CineformSection *cineform_section_;
|
||||
|
||||
ColorSpaceChooser *color_space_chooser_;
|
||||
|
||||
IntegerSlider *width_slider_;
|
||||
IntegerSlider *height_slider_;
|
||||
|
||||
OakEngineColorManager *color_manager_;
|
||||
|
||||
InterlacedComboBox *interlaced_combobox_;
|
||||
PixelAspectRatioComboBox *pixel_aspect_combobox_;
|
||||
PixelFormatComboBox *pixel_format_field_;
|
||||
|
||||
int threads_;
|
||||
|
||||
QString pix_fmt_;
|
||||
int color_range_;
|
||||
|
||||
int format_;
|
||||
|
||||
private slots:
|
||||
void maintain_aspect_ratio_changed(bool val);
|
||||
|
||||
void open_advanced_dialog();
|
||||
|
||||
void update_frame_rate(Rational r);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_EXPORTVIDEOTAB_H
|
||||
@@ -1,24 +0,0 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2020 Olive 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/>.
|
||||
|
||||
add_subdirectory(streamproperties)
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
dialog/footageproperties/footageproperties.cpp
|
||||
dialog/footageproperties/footageproperties.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -1,340 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2020 Olive Team
|
||||
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 "footageproperties.h"
|
||||
|
||||
#include <QGridLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QComboBox>
|
||||
#include <QLineEdit>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QTreeWidgetItem>
|
||||
#include <QGroupBox>
|
||||
#include <QListWidget>
|
||||
#include <QCheckBox>
|
||||
#include <QSpinBox>
|
||||
|
||||
#include "core.h"
|
||||
#include "oakengine/footage.h"
|
||||
#include "oakengine/node.h"
|
||||
#include "oakengine/timeline.h"
|
||||
#include "oakengine/undo.h"
|
||||
#include "oakutil/oaknode.h"
|
||||
#include "streamproperties/audiostreamproperties.h"
|
||||
#include "streamproperties/videostreamproperties.h"
|
||||
#include "widget/viewer/vieweroutpututils.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
FootagePropertiesDialog::FootagePropertiesDialog(QWidget *parent,
|
||||
OakEngineNode *footage)
|
||||
: QDialog(parent)
|
||||
, footage_(footage)
|
||||
{
|
||||
QGridLayout *layout = new QGridLayout(this);
|
||||
|
||||
// WRAPPER-GAP: oakengine_node_get_label_or_name -- emulate inline
|
||||
// (Node::get_label_or_name(): the label, falling back to the name).
|
||||
const QString footage_label = oak::Node(footage_).get_label();
|
||||
setWindowTitle(
|
||||
tr("\"%1\" Properties")
|
||||
.arg(footage_label.isEmpty() ? oak::Node(footage_).name() :
|
||||
footage_label));
|
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
|
||||
int row = 0;
|
||||
|
||||
layout->addWidget(new QLabel(tr("Name:")), row, 0);
|
||||
|
||||
footage_name_field_ = new QLineEdit(footage_label);
|
||||
layout->addWidget(footage_name_field_, row, 1);
|
||||
row++;
|
||||
|
||||
// Manual source start time: audio/timecode sync relies on this value,
|
||||
// which is otherwise only auto-detected from file metadata
|
||||
layout->addWidget(new QLabel(tr("Source Start Time:")), row, 0);
|
||||
|
||||
{
|
||||
QHBoxLayout *start_time_layout = new QHBoxLayout();
|
||||
|
||||
OakEngineFootage *start_time_handle = oakengine_footage_borrow(footage_);
|
||||
int sst_num = 0, sst_den = 1;
|
||||
const bool has_sst =
|
||||
oakengine_footage_get_source_start_time(start_time_handle,
|
||||
&sst_num,
|
||||
&sst_den) == 1;
|
||||
|
||||
source_start_time_enable_ = new QCheckBox(tr("Set"));
|
||||
source_start_time_enable_->setChecked(has_sst);
|
||||
start_time_layout->addWidget(source_start_time_enable_);
|
||||
|
||||
source_start_time_spin_ = new QDoubleSpinBox();
|
||||
source_start_time_spin_->setRange(-86400.0, 86400.0);
|
||||
source_start_time_spin_->setDecimals(3);
|
||||
source_start_time_spin_->setSuffix(QStringLiteral(" s"));
|
||||
source_start_time_spin_->setValue(
|
||||
has_sst ? double(sst_num) / double(sst_den) : 0.0);
|
||||
source_start_time_spin_->setEnabled(
|
||||
source_start_time_enable_->isChecked());
|
||||
start_time_layout->addWidget(source_start_time_spin_, 1);
|
||||
|
||||
QString detection_note;
|
||||
// Detection source comes through the facade (auto-detected field or
|
||||
// "manual"), matching the engine's stored value.
|
||||
if (has_sst) {
|
||||
char source_buf[64];
|
||||
source_buf[0] = '\0';
|
||||
oakengine_footage_get_source_start_time_source(
|
||||
start_time_handle, source_buf, sizeof(source_buf));
|
||||
const QString source = QString::fromUtf8(source_buf);
|
||||
detection_note =
|
||||
(source == QStringLiteral("manual")) ?
|
||||
tr("(set manually)") :
|
||||
tr("(auto-detected: %1)").arg(source);
|
||||
} else {
|
||||
detection_note = tr("(not detected)");
|
||||
}
|
||||
oakengine_footage_free(start_time_handle);
|
||||
start_time_layout->addWidget(new QLabel(detection_note));
|
||||
|
||||
connect(source_start_time_enable_, &QCheckBox::toggled,
|
||||
source_start_time_spin_, &QDoubleSpinBox::setEnabled);
|
||||
|
||||
layout->addLayout(start_time_layout, row, 1);
|
||||
}
|
||||
row++;
|
||||
|
||||
layout->addWidget(new QLabel(tr("Tracks:")), row, 0, 1, 2);
|
||||
row++;
|
||||
|
||||
track_list_ = new QListWidget();
|
||||
layout->addWidget(track_list_, row, 0, 1, 2);
|
||||
|
||||
row++;
|
||||
|
||||
stacked_widget_ = new QStackedWidget();
|
||||
layout->addWidget(stacked_widget_, row, 0, 1, 2);
|
||||
|
||||
int first_usable_stream = -1;
|
||||
|
||||
int total_stream_count = 0;
|
||||
{
|
||||
OakEngineFootage *count_handle = oakengine_footage_borrow(footage_);
|
||||
total_stream_count =
|
||||
oakengine_footage_get_video_stream_count(count_handle) +
|
||||
oakengine_footage_get_audio_stream_count(count_handle) +
|
||||
oakengine_footage_get_subtitle_stream_count(count_handle);
|
||||
oakengine_footage_free(count_handle);
|
||||
}
|
||||
|
||||
for (int i = 0; i < total_stream_count; i++) {
|
||||
QString description;
|
||||
bool is_enabled = false;
|
||||
|
||||
OakEngineFootage *facade_handle = oakengine_footage_borrow(footage_);
|
||||
|
||||
// (track_type, stream_index) pair for this real stream index;
|
||||
// track types are OAKENGINE_TRACK_TYPE_* ordinals (identical to
|
||||
// engine Track::Type).
|
||||
int reference_type = -1;
|
||||
int reference_index = -1;
|
||||
oakengine_footage_get_stream_reference(facade_handle, i,
|
||||
&reference_type,
|
||||
&reference_index);
|
||||
|
||||
switch (reference_type) {
|
||||
case OAKENGINE_TRACK_TYPE_VIDEO: {
|
||||
stacked_widget_->addWidget(
|
||||
new VideoStreamProperties(footage_, reference_index));
|
||||
|
||||
is_enabled = oakengine_viewer_get_stream_enabled(
|
||||
reinterpret_cast<const OakEngineNode *>(footage_),
|
||||
OAKENGINE_TRACK_TYPE_VIDEO, reference_index) == 1;
|
||||
{
|
||||
char desc_buf[256];
|
||||
oakengine_footage_describe_video_stream(
|
||||
facade_handle, reference_index, desc_buf,
|
||||
sizeof(desc_buf));
|
||||
description = QString::fromUtf8(desc_buf);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OAKENGINE_TRACK_TYPE_AUDIO: {
|
||||
stacked_widget_->addWidget(
|
||||
new AudioStreamProperties(footage_, reference_index));
|
||||
|
||||
AudioParams ap = viewer_output_audio_params(footage_, reference_index);
|
||||
is_enabled = ap.enabled();
|
||||
{
|
||||
char desc_buf[256];
|
||||
oakengine_footage_describe_audio_stream(
|
||||
facade_handle, reference_index, desc_buf,
|
||||
sizeof(desc_buf));
|
||||
description = QString::fromUtf8(desc_buf);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OAKENGINE_TRACK_TYPE_SUBTITLE: {
|
||||
is_enabled = oakengine_footage_get_stream_enabled(
|
||||
facade_handle, OAKENGINE_TRACK_TYPE_SUBTITLE, reference_index);
|
||||
|
||||
// FIXME: Language?
|
||||
description = tr("Subtitles");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
stacked_widget_->addWidget(new StreamProperties());
|
||||
description = tr("Unknown");
|
||||
break;
|
||||
}
|
||||
|
||||
oakengine_footage_free(facade_handle);
|
||||
|
||||
QListWidgetItem *item = new QListWidgetItem(description, track_list_);
|
||||
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
||||
item->setCheckState(is_enabled ? Qt::Checked : Qt::Unchecked);
|
||||
track_list_->addItem(item);
|
||||
|
||||
if (first_usable_stream == -1 &&
|
||||
(reference_type == OAKENGINE_TRACK_TYPE_VIDEO ||
|
||||
reference_type == OAKENGINE_TRACK_TYPE_AUDIO ||
|
||||
reference_type == OAKENGINE_TRACK_TYPE_SUBTITLE)) {
|
||||
first_usable_stream = i;
|
||||
}
|
||||
}
|
||||
|
||||
row++;
|
||||
|
||||
QDialogButtonBox *buttons =
|
||||
new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
buttons->setCenterButtons(true);
|
||||
layout->addWidget(buttons, row, 0, 1, 2);
|
||||
|
||||
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
|
||||
connect(track_list_, &QListWidget::currentRowChanged, stacked_widget_,
|
||||
&QStackedWidget::setCurrentIndex);
|
||||
|
||||
// Auto-select first item that actually has properties
|
||||
if (first_usable_stream >= 0) {
|
||||
track_list_->setCurrentRow(first_usable_stream);
|
||||
}
|
||||
track_list_->setFocus();
|
||||
}
|
||||
|
||||
void FootagePropertiesDialog::accept()
|
||||
{
|
||||
// Perform sanity check on all pages
|
||||
for (int i = 0; i < stacked_widget_->count(); i++) {
|
||||
if (!static_cast<StreamProperties *>(stacked_widget_->widget(i))
|
||||
->sanity_check()) {
|
||||
// Switch to the failed panel in question
|
||||
stacked_widget_->setCurrentIndex(i);
|
||||
|
||||
// Do nothing (it's up to the property panel itself to throw the error message)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
OakEngineFootage *facade_handle = oakengine_footage_borrow(footage_);
|
||||
|
||||
// All writes go through the liboakengine C ABI facade; each call lands
|
||||
// on the shared undo stack as an undoable command (replacing this
|
||||
// dialog's own undo command classes with identical semantics).
|
||||
if (oak::Node(footage_).get_label() != footage_name_field_->text()) {
|
||||
oakengine_node_set_label(
|
||||
footage_, footage_name_field_->text().toUtf8().constData());
|
||||
}
|
||||
|
||||
// Apply source start time changes
|
||||
{
|
||||
const bool new_enabled = source_start_time_enable_->isChecked();
|
||||
const Rational new_time =
|
||||
Rational::from_double(source_start_time_spin_->value());
|
||||
int cur_sst_num = 0, cur_sst_den = 1;
|
||||
const bool cur_has_sst =
|
||||
oakengine_footage_get_source_start_time(facade_handle,
|
||||
&cur_sst_num,
|
||||
&cur_sst_den) == 1;
|
||||
if (new_enabled != cur_has_sst ||
|
||||
(new_enabled &&
|
||||
new_time != Rational(cur_sst_num, cur_sst_den))) {
|
||||
oakengine_footage_set_source_start_time(
|
||||
facade_handle, new_enabled ? 1 : 0, new_time.numerator(),
|
||||
new_time.denominator());
|
||||
}
|
||||
}
|
||||
|
||||
int total_stream_count =
|
||||
oakengine_footage_get_video_stream_count(facade_handle) +
|
||||
oakengine_footage_get_audio_stream_count(facade_handle) +
|
||||
oakengine_footage_get_subtitle_stream_count(facade_handle);
|
||||
|
||||
for (int i = 0; i < total_stream_count; i++) {
|
||||
int reference_type = -1;
|
||||
int reference_index = -1;
|
||||
oakengine_footage_get_stream_reference(facade_handle, i,
|
||||
&reference_type,
|
||||
&reference_index);
|
||||
bool new_stream_enabled =
|
||||
(track_list_->item(i)->checkState() == Qt::Checked);
|
||||
bool old_stream_enabled = new_stream_enabled;
|
||||
|
||||
switch (reference_type) {
|
||||
case OAKENGINE_TRACK_TYPE_VIDEO:
|
||||
old_stream_enabled = oakengine_footage_get_stream_enabled(
|
||||
facade_handle, OAKENGINE_TRACK_TYPE_VIDEO, reference_index);
|
||||
break;
|
||||
case OAKENGINE_TRACK_TYPE_AUDIO:
|
||||
old_stream_enabled = oakengine_footage_get_stream_enabled(
|
||||
facade_handle, OAKENGINE_TRACK_TYPE_AUDIO, reference_index);
|
||||
break;
|
||||
case OAKENGINE_TRACK_TYPE_SUBTITLE:
|
||||
old_stream_enabled = oakengine_footage_get_stream_enabled(
|
||||
facade_handle, OAKENGINE_TRACK_TYPE_SUBTITLE, reference_index);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (old_stream_enabled != new_stream_enabled) {
|
||||
oakengine_footage_set_stream_enabled(
|
||||
facade_handle, reference_type, reference_index,
|
||||
new_stream_enabled ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
oakengine_footage_free(facade_handle);
|
||||
|
||||
void *command = oakengine_undo_command_create_multi();
|
||||
for (int i = 0; i < stacked_widget_->count(); i++) {
|
||||
static_cast<StreamProperties *>(stacked_widget_->widget(i))
|
||||
->accept(command);
|
||||
}
|
||||
oakengine_undo_command_free(command); // stream pages write through the facade directly
|
||||
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2020 Olive Team
|
||||
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 OAK_MEDIAPROPERTIESDIALOG_H
|
||||
#define OAK_MEDIAPROPERTIESDIALOG_H
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QDialog>
|
||||
#include <QDoubleSpinBox>
|
||||
#include <QLineEdit>
|
||||
#include <QListWidget>
|
||||
#include <QStackedWidget>
|
||||
|
||||
struct OakEngineNode;
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief The MediaPropertiesDialog class
|
||||
*
|
||||
* A dialog for setting properties on Media. This can be loaded from any part of the application provided it's given
|
||||
* a valid Media object.
|
||||
*/
|
||||
class FootagePropertiesDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
/**
|
||||
* @brief MediaPropertiesDialog Constructor
|
||||
*
|
||||
* @param parent
|
||||
*
|
||||
* QWidget parent. Usually MainWindow or Project panel.
|
||||
*
|
||||
* @param i
|
||||
*
|
||||
* Media object to set properties for.
|
||||
*/
|
||||
FootagePropertiesDialog(QWidget *parent, OakEngineNode *footage);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Stack of widgets that changes based on whether the stream is a video or audio stream
|
||||
*/
|
||||
QStackedWidget *stacked_widget_;
|
||||
|
||||
/**
|
||||
* @brief Media name text field
|
||||
*/
|
||||
QLineEdit *footage_name_field_;
|
||||
|
||||
/**
|
||||
* @brief Whether a manual source start time should be used
|
||||
*/
|
||||
QCheckBox *source_start_time_enable_;
|
||||
|
||||
/**
|
||||
* @brief Source start time in seconds
|
||||
*/
|
||||
QDoubleSpinBox *source_start_time_spin_;
|
||||
|
||||
/**
|
||||
* @brief Internal handle to the footage node (set in constructor)
|
||||
*/
|
||||
OakEngineNode *footage_;
|
||||
|
||||
/**
|
||||
* @brief A list widget for listing the tracks in Media
|
||||
*/
|
||||
QListWidget *track_list_;
|
||||
|
||||
/**
|
||||
* @brief Frame rate to conform to
|
||||
*/
|
||||
QDoubleSpinBox *conform_fr_;
|
||||
|
||||
private slots:
|
||||
/**
|
||||
* @brief Overridden accept function for saving the properties back to the Media class
|
||||
*/
|
||||
void accept();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_MEDIAPROPERTIESDIALOG_H
|
||||
@@ -1,26 +0,0 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2020 Olive 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/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
dialog/footageproperties/streamproperties/streamproperties.h
|
||||
dialog/footageproperties/streamproperties/streamproperties.cpp
|
||||
dialog/footageproperties/streamproperties/audiostreamproperties.h
|
||||
dialog/footageproperties/streamproperties/audiostreamproperties.cpp
|
||||
dialog/footageproperties/streamproperties/videostreamproperties.h
|
||||
dialog/footageproperties/streamproperties/videostreamproperties.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user