Merge branch 'olive-editor:master' into master

This commit is contained in:
tecnotercio
2022-02-26 15:09:54 -03:00
committed by GitHub
2437 changed files with 938424 additions and 326072 deletions
+4 -2
View File
@@ -2,5 +2,7 @@
* text=auto
# Enforce LF line endings on source files
*.h text eol=lf
*.cpp text eol=lf
*.h text eol=lf
*.cpp text eol=lf
*.sh text eol=lf
*.desktop text eol=lf
+12
View File
@@ -0,0 +1,12 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: olivevideoeditor
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
+17
View File
@@ -0,0 +1,17 @@
---
name: Default issue template
labels: 'Triage'
---
<!-- ⚠ Do not delete this issue template! ⚠ -->
**Commit Hash** <!-- 8 character string of letters/numbers in title bar or Help > About dialog (e.g. 3ea173c9) -->
**Platform** <!-- e.g. Windows 10, Ubuntu 20.04 or macOS 10.15 -->
**Summary**
**Additional Information / Output**
@@ -0,0 +1,14 @@
---
name: Legacy Olive 0.1.x issue ⛔
about: >-
Olive 0.1 is no longer supported and issues made about it will be
automatically closed.
title: '[UNSUPPORTED] '
labels: 'Legacy (Unsupported)'
assignees: ''
---
# Olive 0.1 is unsupported
Unfortunately no one is supporting Olive 0.1 at this time. Any reports
pertaining to it will be closed to keep the issue tracker focused on supported
versions.
+31
View File
@@ -0,0 +1,31 @@
---
name: Crash
about: Report a fatal crash that resulted in Olive unexpectedly closing.
title: '[CRASH] '
labels: 'Crash, Triage'
assignees: ''
---
<!-- ⚠ Do not delete this issue template! ⚠ -->
**Commit Hash** <!-- 8 character string of letters/numbers in title bar or Help > About dialog (e.g. 3ea173c9) -->
**Platform** <!-- e.g. Windows 10, Ubuntu 20.04 or macOS 10.15 -->
**Summary**
**Steps to Reproduce**
1.
2.
3.
<details><summary><strong>Crash Report</strong></summary><pre><code>
<!-- You should have received a crash report from Olive to paste here. -->
</code></pre></details>
**Additional Information**
+22
View File
@@ -0,0 +1,22 @@
---
name: Build/Packaging
about: >-
Report an issue related to compiling or packaging.
Note that we do not officially support custom build
configurations and may not address issues involving them.
title: '[BUILD] '
labels: 'Building/Packaging, Triage'
assignees: ''
---
<!-- ⚠ Do not delete this issue template! ⚠ -->
**Commit Hash** <!-- 8 character string of letters/numbers in title bar or Help > About dialog (e.g. 3ea173c9) -->
**Platform** <!-- e.g. Windows 10, Ubuntu 20.04 or macOS 10.15 -->
**Summary**
**Additional Information / Output**
+22
View File
@@ -0,0 +1,22 @@
---
name: Disk Cache
about: >-
Report an issue related to the disk cache system, including failure to cache,
frames being set to appear at the wrong time, disk space or memory usage
issues, etc.
title: '[CACHE] '
labels: 'Disk Cache, Triage'
assignees: ''
---
<!-- ⚠ Do not delete this issue template! ⚠ -->
**Commit Hash** <!-- 8 character string of letters/numbers in title bar or Help > About dialog (e.g. 3ea173c9) -->
**Platform** <!-- e.g. Windows 10, Ubuntu 20.04 or macOS 10.15 -->
**Summary**
**Additional Information / Output**
+21
View File
@@ -0,0 +1,21 @@
---
name: Codec
about: >-
Report an issue related to codec handling, including importing footage or any
footage usage issues while editing.
title: '[CODEC] '
labels: 'Codec, Triage'
assignees: ''
---
<!-- ⚠ Do not delete this issue template! ⚠ -->
**Commit Hash** <!-- 8 character string of letters/numbers in title bar or Help > About dialog (e.g. 3ea173c9) -->
**Platform** <!-- e.g. Windows 10, Ubuntu 20.04 or macOS 10.15 -->
**Summary**
**Additional Information / Output**
+21
View File
@@ -0,0 +1,21 @@
---
name: Color Management
about: >-
Report an issue related to the management of pixels and color, including
inaccurate results, color inconsistencies, etc.
title: '[COLOR] '
labels: 'Color Management, Triage'
assignees: ''
---
<!-- ⚠ Do not delete this issue template! ⚠ -->
**Commit Hash** <!-- 8 character string of letters/numbers in title bar or Help > About dialog (e.g. 3ea173c9) -->
**Platform** <!-- e.g. Windows 10, Ubuntu 20.04 or macOS 10.15 -->
**Summary**
**Additional Information / Output**
@@ -0,0 +1,21 @@
---
name: Timeline/Editing
about: >-
Report an issue related to the overall editing experience, including usage of
the timeline, interchange, synchronization, multi-camera support, etc.
title: '[EDIT] '
labels: 'Timeline/Editing, Triage'
assignees: ''
---
<!-- ⚠ Do not delete this issue template! ⚠ -->
**Commit Hash** <!-- 8 character string of letters/numbers in title bar or Help > About dialog (e.g. 3ea173c9) -->
**Platform** <!-- e.g. Windows 10, Ubuntu 20.04 or macOS 10.15 -->
**Summary**
**Additional Information / Output**
+21
View File
@@ -0,0 +1,21 @@
---
name: Export
about: >-
Report an issue related to exporting videos from Olive, including errors
while exporting, issues with the resulting video, etc.
title: '[EXPORT] '
labels: 'Export, Triage'
assignees: ''
---
<!-- ⚠ Do not delete this issue template! ⚠ -->
**Commit Hash** <!-- 8 character string of letters/numbers in title bar or Help > About dialog (e.g. 3ea173c9) -->
**Platform** <!-- e.g. Windows 10, Ubuntu 20.04 or macOS 10.15 -->
**Summary**
**Additional Information / Output**
+21
View File
@@ -0,0 +1,21 @@
---
name: Node/Compositing
about: >-
Report an issue related to the node-based compositing system, including node graph
usability issues, issues working with node effects, issues with composited output, etc.
title: '[NODES] '
labels: 'Nodes/Compositing, Triage'
assignees: ''
---
<!-- ⚠ Do not delete this issue template! ⚠ -->
**Commit Hash** <!-- 8 character string of letters/numbers in title bar or Help > About dialog (e.g. 3ea173c9) -->
**Platform** <!-- e.g. Windows 10, Ubuntu 20.04 or macOS 10.15 -->
**Summary**
**Additional Information / Output**
@@ -0,0 +1,21 @@
---
name: Playback
about: >-
Report an issue related to the playback of video or audio, including laggy or
inconsistent playback, failure to playback, etc.
title: '[PLAYBACK] '
labels: 'Playback, Triage'
assignees: ''
---
<!-- ⚠ Do not delete this issue template! ⚠ -->
**Commit Hash** <!-- 8 character string of letters/numbers in title bar or Help > About dialog (e.g. 3ea173c9) -->
**Platform** <!-- e.g. Windows 10, Ubuntu 20.04 or macOS 10.15 -->
**Summary**
**Additional Information / Output**
@@ -0,0 +1,21 @@
---
name: Project Management
about: >-
Report an issue related to project management, including working with and
organizing imported files.
title: '[PROJECT] '
labels: 'Project, Triage'
assignees: ''
---
<!-- ⚠ Do not delete this issue template! ⚠ -->
**Commit Hash** <!-- 8 character string of letters/numbers in title bar or Help > About dialog (e.g. 3ea173c9) -->
**Platform** <!-- e.g. Windows 10, Ubuntu 20.04 or macOS 10.15 -->
**Summary**
**Additional Information / Output**
@@ -0,0 +1,21 @@
---
name: Renderer
about: >-
Report an issue related to rendering, including corrupted frames, incorrect
render results, unexpected black or white frames, etc.
title: '[RENDER] '
labels: 'Renderer, Triage'
assignees: ''
---
<!-- ⚠ Do not delete this issue template! ⚠ -->
**Commit Hash** <!-- 8 character string of letters/numbers in title bar or Help > About dialog (e.g. 3ea173c9) -->
**Platform** <!-- e.g. Windows 10, Ubuntu 20.04 or macOS 10.15 -->
**Summary**
**Additional Information / Output**
+21
View File
@@ -0,0 +1,21 @@
---
name: User Interface
about: >-
Report an issue related to general user interface usability, including
behavior issues, usability issues, look and feel, etc.
title: '[UI] '
labels: 'User Interface, Triage'
assignees: ''
---
<!-- ⚠ Do not delete this issue template! ⚠ -->
**Commit Hash** <!-- 8 character string of letters/numbers in title bar or Help > About dialog (e.g. 3ea173c9) -->
**Platform** <!-- e.g. Windows 10, Ubuntu 20.04 or macOS 10.15 -->
**Summary**
**Additional Information / Output**
-38
View File
@@ -1,38 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: "[Bug]"
labels: ''
assignees: ''
---
**Your Setup:**
- Olive version (window title or Help > About):
- Source: (e.g. AppImage, PPA, AUR, website etc.)
- Operating system: (e.g. Ubuntu 18.04 64-bit)
- CPU: (e.g. Intel i5-4300U)
- RAM: (e.g. 8GB)
- GPU: (e.g. NVIDIA Geforce GT 1030 2GB (Driver ver xxx.xx.xx))
**Describe the Bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Screenshots**
If applicable, add screenshots to help explain your problem.
** Debug Log and/or Crash Report **
```
[Paste log/report]
```
**Additional Details**
Add any other information about the problem here.
+5
View File
@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Community Support
url: https://discordapp.com/invite/4Ae9KZn
about: Join the Olive Discord server
-23
View File
@@ -1,23 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: "[Feature Request]"
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Do you know any libraries or sources that can help the Olive Team include the feature?**
List open-source libraries related to the feature.
**Additional context**
Add any other context or screenshots about the feature request here.
+451
View File
@@ -0,0 +1,451 @@
name: CI
on:
push:
branches:
- master
paths-ignore:
- '.github/ISSUE_TEMPLATE/**'
- '.github/FUNDING.yml'
- 'docker/**'
- 'CONTRIBUTING.md'
- 'README.md'
pull_request:
branches:
- master
paths-ignore:
- '.github/ISSUE_TEMPLATE/**'
- '.github/FUNDING.yml'
- 'app/ts/*.ts'
- 'docker/**'
- 'CONTRIBUTING.md'
- 'README.md'
env:
DOWNLOAD_TOOL: curl -fLOSs --retry 2 --retry-delay 60
UPLOAD_TOOL: curl -X POST --retry 2 --retry-delay 60
jobs:
linux:
strategy:
fail-fast: false
matrix:
include:
- build-type: RelWithDebInfo
cc-compiler: gcc
cxx-compiler: g++
compiler-name: GCC 9.3.1
cmake-gen: Ninja
os-name: Linux (CentOS 7)
- build-type: RelWithDebInfo
cc-compiler: clang
cxx-compiler: clang++
compiler-name: Clang 10.0.0
cmake-gen: Ninja
os-name: Linux (CentOS 7)
name: |
${{ matrix.os-name }}
<${{ matrix.compiler-name }},
${{ matrix.build-type }},
${{ matrix.cmake-gen }}>
runs-on: ubuntu-latest
container:
image: olivevideoeditor/ci-olive:2021.4
steps:
- name: Checkout Source Code
uses: actions/checkout@v2
- name: Generate Patreon List
env:
PATREON_KEY: ${{ secrets.PATREON_KEY }}
run: |
pip3 install requests
cd $GITHUB_WORKSPACE/app/dialog/about
python3 patreon.py
if: github.event_name == 'push'
continue-on-error: true
- name: Configure CMake
run: |
mkdir build
cd build
cmake .. -G "${{ matrix.cmake-gen }}" \
-DCMAKE_BUILD_TYPE="${{ matrix.build-type }}" \
-DCMAKE_C_COMPILER="${{ matrix.cc-compiler }}" \
-DCMAKE_CXX_COMPILER="${{ matrix.cxx-compiler }}"
- name: Build
working-directory: build
run: |
cmake --build .
- name: Test
working-directory: build
run: |
ctest -C ${{ matrix.build-type }} -V
- name: Create Package
id: package
working-directory: build
env:
ARCH: x86_64
run: |
# Create install tree
cmake --install app --prefix appdir/usr
# Inject custom AppRun (linuxdeployqt won't replace if it already exists)
cp $GITHUB_WORKSPACE/app/packaging/linux/AppRun appdir
# Process AppDir
/usr/local/linuxdeployqt-x86_64.AppImage \
appdir/usr/share/applications/org.olivevideoeditor.Olive.desktop \
-exclude-libs=libQt5Pdf.so,libQt5Qml.so,libQt5QmlModels.so,libQt5Quick.so,libQt5VirtualKeyboard.so \
-bundle-non-qt-libs \
-executable=appdir/usr/bin/crashpad_handler \
-executable=appdir/usr/bin/minidump_stackwalk \
-executable=appdir/usr/bin/olive-crashhandler \
--appimage-extract-and-run
# Dump Crashpad symbols
dump_syms appdir/usr/bin/olive-editor > olive-editor.sym
# HACK: For some reason, minidump_stackwalk reads identifier as all 0s
SYM_DIR=appdir/usr/share/olive-editor/symbols/olive-editor/000000000000000000000000000000000
mkdir -p "$SYM_DIR"
mv olive-editor.sym "$SYM_DIR"
# Package AppImage
$DOWNLOAD_TOOL https://github.com/AppImage/AppImageKit/releases/download/12/appimagetool-x86_64.AppImage
chmod +x appimagetool-x86_64.AppImage
VERSION=${GITHUB_SHA::8} ./appimagetool-x86_64.AppImage appdir --appimage-extract-and-run
# Set env variables
filename=$(echo Olive*.AppImage)
pkgname="${filename/x86_64/Linux-x86_64}"
mv "${filename}" "${pkgname}"
echo "::set-output name=pkgname::${pkgname}"
echo "::set-output name=artifact::${filename/x86_64/Linux-x86_64-${{ matrix.cc-compiler }}}"
- name: Upload Artifact to GitHub
uses: actions/upload-artifact@v2
continue-on-error: true
with:
name: ${{ steps.package.outputs.artifact }}
path: build/Olive*.AppImage
windows:
strategy:
matrix:
include:
- build-type: RelWithDebInfo
compiler-name: MSVC 16.x
os-name: Windows
os-arch: x86_64
os: windows-latest
cmake-gen: Ninja
name: |
${{ matrix.os-name }}
<${{ matrix.compiler-name }},
${{ matrix.build-type }},
${{ matrix.cmake-gen }}>
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Source Code
uses: actions/checkout@v2
- name: Automatically Generate Package Name
shell: bash
env:
PLATFORM: ${{ matrix.os-name }}
ARCH: ${{ matrix.os-arch }}
run: |
echo "PKGNAME=$(echo Olive-${GITHUB_SHA::8}-${PLATFORM}-${ARCH})" >> $GITHUB_ENV
- name: Create Build Folder
run: |
cmake -E make_directory ${{ runner.workspace }}/build
- name: Enable Developer Command Prompt (Windows)
uses: ilammy/msvc-dev-cmd@v1
- name: Acquire Dependencies
shell: bash
working-directory: ${{ runner.workspace }}
run: |
$DOWNLOAD_TOOL https://github.com/olive-editor/vcpkg-registry/releases/download/continuous/olive-dep-x64-windows.zip
7z x olive-dep-x64-windows.zip
VCPKG_DIR=(vcpkg-export-*)
echo "VCPKG_ROOT=$(pwd -W)/$VCPKG_DIR" >> $GITHUB_ENV
- name: Acquire Google Crashpad
shell: bash
working-directory: ${{ runner.workspace }}
run: |
$DOWNLOAD_TOOL https://github.com/olive-editor/crashpad/releases/download/continuous/crashpad-win32-RelWithDebInfo.tar.gz
tar xzf crashpad-win32-RelWithDebInfo.tar.gz
echo "$(pwd -W)/install" >> $GITHUB_PATH
echo "$(pwd -W)/install/bin" >> $GITHUB_PATH
echo "$(pwd -W)/install/crashpad" >> $GITHUB_PATH
- name: Generate Patreon List
shell: bash
env:
PATREON_KEY: ${{ secrets.PATREON_KEY }}
run: |
pip3 install requests
cd $GITHUB_WORKSPACE/app/dialog/about
python3 patreon.py
if: github.event_name == 'push'
continue-on-error: true
- name: Configure CMake
shell: bash
working-directory: ${{ runner.workspace }}/build
run: |
cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} -G "${{ matrix.cmake-gen }}" \
-DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake"
- name: Build
working-directory: ${{ runner.workspace }}/build
shell: bash
run: |
cmake --build .
- name: Test
working-directory: ${{ runner.workspace }}/build
shell: bash
run: |
PATH=$PATH:$VCPKG_ROOT/installed/x64-windows/bin ctest -C ${{ matrix.build-type }} -V
- name: Create Package
working-directory: ${{ runner.workspace }}/build
shell: bash
env:
ORIGINAL_WORKSPACE: ${{ runner.workspace }}
run: |
mkdir olive-editor
cp app/olive-editor.exe olive-editor
cp app/crashhandler/olive-crashhandler.exe olive-editor
cp app/olive-editor.pdb olive-editor
$VCPKG_ROOT/installed/x64-windows/tools/qt5/bin/windeployqt --no-angle olive-editor/olive-crashhandler.exe
$VCPKG_ROOT/installed/x64-windows/tools/qt5/bin/windeployqt --no-angle olive-editor/olive-editor.exe
cp $(cygpath $VCPKG_ROOT)/installed/x64-windows/bin/*.dll olive-editor
cp $(cygpath $ORIGINAL_WORKSPACE)/install/bin/*.dll olive-editor
cp $(cygpath $ORIGINAL_WORKSPACE)/install/crashpad/out/Default/crashpad_handler.exe olive-editor
cp $(cygpath $ORIGINAL_WORKSPACE)/install/bin/minidump_stackwalk.exe olive-editor
- name: Export Crashpad symbols
working-directory: ${{ runner.workspace }}/build
shell: bash
run: |
curl -fLSs https://github.com/google/breakpad/blob/master/src/tools/windows/binaries/dump_syms.exe?raw=true > dump_syms.exe
./dump_syms app/olive-editor.pdb > olive-editor.sym
SYM_HEADER=($(head -n 1 olive-editor.sym)) # Read first line of symbol file
SYM_DIR=olive-editor/symbols/olive-editor.pdb/${SYM_HEADER[3]}
mkdir -p "$SYM_DIR"
mv olive-editor.sym "$SYM_DIR"
- name: Deploy Packages
working-directory: ${{ runner.workspace }}/build
shell: bash
run: |
# Create Installer Executable
#$DOWNLOAD_TOOL http://web.archive.org/web/20210226132532/http://download.microsoft.com/download/3/2/2/3224B87F-CFA0-4E70-BDA3-3DE650EFEBA5/vcredist_x64.exe
cp $(cygpath $GITHUB_WORKSPACE)/app/packaging/windows/nsis/* .
cp $(cygpath $GITHUB_WORKSPACE)/LICENSE .
$DOWNLOAD_TOOL https://nsis.sourceforge.io/mediawiki/images/6/68/ShellExecAsUser_amd64-Unicode.7z
7z e ShellExecAsUser_amd64-Unicode.7z Plugins/x86-unicode/ShellExecAsUser.dll
makensis -V4 -DX64 "-XOutFile $PKGNAME.exe" "-X!AddPluginDir /x86-unicode $(pwd -W)" olive.nsi
# Create Portable ZIP
echo -n > olive-editor/portable
7z a $PKGNAME.zip olive-editor
- name: Upload Installer Artifact to GitHub
uses: actions/upload-artifact@v2
with:
name: ${{ env.PKGNAME }}.exe
path: ${{ runner.workspace }}/build/${{ env.PKGNAME }}.exe
- name: Upload Portable Artifact to GitHub
uses: actions/upload-artifact@v2
continue-on-error: true
with:
name: ${{ env.PKGNAME }}.zip
path: ${{ runner.workspace }}/build/${{ env.PKGNAME }}.zip
macos:
strategy:
matrix:
include:
- build-type: RelWithDebInfo
compiler-name: Clang LLVM
os-name: macOS
os-arch: x86_64
os: macos-10.15
cmake-gen: Ninja
min-deploy: 10.13
- build-type: RelWithDebInfo
compiler-name: Clang LLVM
os-name: macOS
os-arch: arm64
os: macos-11.0
cmake-gen: Ninja
min-deploy: 11.0
env:
DEP_LOCATION: /opt/olive-editor
name: |
${{ matrix.os-name }}
<${{ matrix.os-arch }},
${{ matrix.compiler-name }},
${{ matrix.build-type }},
${{ matrix.cmake-gen }}>
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Source Code
uses: actions/checkout@v2
- name: Automatically Generate Package Name
shell: bash
env:
PLATFORM: ${{ matrix.os-name }}
ARCH: ${{ matrix.os-arch }}
run: |
echo "PKGNAME=$(echo Olive-${GITHUB_SHA::8}-${PLATFORM}-${ARCH})" >> $GITHUB_ENV
- name: Create Build Folder
run: |
cmake -E make_directory ${{ runner.workspace }}/build
- name: Acquire Dependencies
shell: bash
working-directory: ${{ runner.workspace }}
run: |
$DOWNLOAD_TOOL https://github.com/olive-editor/dependencies/releases/download/continuous/olive-dep-mac-${{ matrix.os-arch }}.tar.gz
sudo tar xzf olive-dep-mac-${{ matrix.os-arch }}.tar.gz -C /
- name: Acquire Google Crashpad
shell: bash
working-directory: ${{ runner.workspace }}
run: |
$DOWNLOAD_TOOL https://github.com/olive-editor/crashpad/releases/download/continuous/crashpad-mac-${{ matrix.os-arch }}.tar.gz
sudo tar xzf crashpad-mac-${{ matrix.os-arch }}.tar.gz -C /
if: matrix.os-arch == 'x86_64' # TEMPORARY: Crashpad isn't compiling on ARM64 yet for some reason
- name: Generate Patreon List
env:
PATREON_KEY: ${{ secrets.PATREON_KEY }}
run: |
pip3 install requests
cd $GITHUB_WORKSPACE/app/dialog/about
python3 patreon.py
if: github.event_name == 'push'
continue-on-error: true
- name: Configure CMake
shell: bash
working-directory: ${{ runner.workspace }}/build
run: |
brew install ninja
PATH=$DEP_LOCATION:$DEP_LOCATION/bin:$DEP_LOCATION/include:$DEP_LOCATION/lib:$DEP_LOCATION/crashpad:$PATH \
cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \
-DCMAKE_OSX_DEPLOYMENT_TARGET=${{ matrix.min-deploy }} -G "${{ matrix.cmake-gen }}" \
-DCMAKE_OSX_ARCHITECTURES="${{ matrix.os-arch }}"
- name: Build
working-directory: ${{ runner.workspace }}/build
shell: bash
run: |
cmake --build .
- name: Test
working-directory: ${{ runner.workspace }}/build
shell: bash
run: |
ctest -C ${{ matrix.build-type }} -V
if: matrix.os-arch == 'x86_64' # ARM64 tests naturally won't be able to run on x86_64 runners
- name: Create Package
working-directory: ${{ runner.workspace }}/build
shell: bash
run: |
BUNDLE_NAME="Olive.app"
brew install dylibbundler
if [ "${{ matrix.os-arch }}" == "x86_64" ]
then
DYLIBBUNDLER_EXTRA_ARGS="-x $BUNDLE_NAME/Contents/MacOS/olive-crashhandler"
fi
mv app/$BUNDLE_NAME .
mkdir $BUNDLE_NAME/Contents/Frameworks
dylibbundler -b -ns -x "$BUNDLE_NAME/Contents/MacOS/Olive" -s "$DEP_LOCATION/lib" -d "$BUNDLE_NAME/Contents/Frameworks" -p "@executable_path/../Frameworks" $DYLIBBUNDLER_EXTRA_ARGS
# Copy Qt frameworks and plugins
cp -Ra $DEP_LOCATION/lib/Qt*.framework $BUNDLE_NAME/Contents/Frameworks
cp -Ra $DEP_LOCATION/plugins $BUNDLE_NAME/Contents
# HACK: On x86_64, dylibbundler doesn't resolve this symlink. Weirdly it does on ARM64,
# but perhaps I'll bring it up with them soon.
cp -a $BUNDLE_NAME/Contents/Frameworks/libpng16.16.37.0.dylib $BUNDLE_NAME/Contents/Frameworks/libpng16.16.dylib
if [ "${{ matrix.os-arch }}" == "x86_64" ]
then
# Crashpad symbols
$DEP_LOCATION/bin/dump_syms $BUNDLE_NAME/Contents/MacOS/Olive > Olive.sym
SYM_HEADER=($(head -n 1 Olive.sym)) # Read first line of symbol file
SYM_DIR=$BUNDLE_NAME/Contents/Resources/symbols/Olive/${SYM_HEADER[3]}
mkdir -p "$SYM_DIR"
mv Olive.sym "$SYM_DIR"
fi
- name: Sign Application
working-directory: ${{ runner.workspace }}/build
shell: bash
env:
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
BUNDLE_NAME="Olive.app"
# Install certificate
CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
# import certificate from secrets
echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode --output $CERTIFICATE_PATH
# create temporary keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
# import certificate to keychain
security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
# HACK: Remove unsignable frameworks
rm -r $BUNDLE_NAME/Contents/Frameworks/QtUiPlugin.framework
if [ "${{ matrix.os-arch }}" == "arm64" ]
then
rm -r $BUNDLE_NAME/Contents/Frameworks/QtZlib.framework
fi
# Sign application
codesign --deep --sign "Developer ID Application: Olive Studios LLC" $BUNDLE_NAME
- name: Deploy Packages
working-directory: ${{ runner.workspace }}/build
shell: bash
run: |
zip -r $PKGNAME.zip Olive.app
- name: Upload Artifact to GitHub
uses: actions/upload-artifact@v2
continue-on-error: true
with:
name: ${{ env.PKGNAME }}.zip
path: ${{ runner.workspace }}/build/${{ env.PKGNAME }}.zip
+42 -9
View File
@@ -1,15 +1,43 @@
*.pro.user*
Makefile
.qmake.stash
effects/frei0r
ts/*.qm
docs
history
# CMake artifacts
/build*/
.vscode
# Doxygen
/docs/
# Visual Studio (Code)
.localhistory/
.history/
.vscode/
.vs/
/out/
CmakeSettings.json
*.code-workspace
# clangd's index and likely other things that need not be in the repository
.cache/
# macOS General
.DS_Store
.AppleDouble
.LSOverride
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Windows folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares (Windows)
$RECYCLE.BIN/
# Windows shortcuts
*.lnk
#
# Qt ignores taken from https://github.com/github/gitignore
# Qt ignores taken from https://github.com/github/gitignore/blob/master/Qt.gitignore
#
# C++ objects and libs
@@ -20,6 +48,7 @@ history
*.la
*.lai
*.so
*.so.*
*.dll
*.dylib
@@ -42,6 +71,8 @@ ui_*.h
*.jsc
Makefile*
*build-*
*.qm
*.prl
# Qt unit tests
target_wrapper.*
@@ -61,3 +92,5 @@ compile_commands.json
# QtCreator local machine specific files for imported projects
*creator.user*
*_qmlcache.qrc
-38
View File
@@ -1,38 +0,0 @@
language: cpp
matrix:
include:
- os: linux
env: ARCH=x86_64
compiler: gcc
sudo: require
dist: trusty
- os: osx
addons:
homebrew:
packages:
- ffmpeg
- qt5
- grep
- opencolorio
- openimageio
update: true
# Can't build on Windows - affected by https://travis-ci.community/t/current-known-issues-please-read-this-before-posting-a-new-topic/264/10
# - os: windows
before_install:
- source ./.travis/before_install.sh
install:
- source ./.travis/install.sh
script:
- source ./.travis/script.sh
after_success:
- source ./.travis/after_success.sh
branches:
except:
- # Do not build tags that we create when we upload to GitHub Releases
- /^(?i:continuous)/
-44
View File
@@ -1,44 +0,0 @@
#!/bin/bash
# Check if there's been a new commit since this build, and if so don't upload it
GREP_PATH=grep
if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
GREP_PATH=ggrep
fi
if [ "$TRAVIS_TAG" != "" ]; then
fi
# Get current repo commit from GitHub (problems arose from trying to pipe cURL directly into grep, so we buffer it through a file)
REMOTE=$(curl -H "Authorization: token $GITHUB_TOKEN" https://api.github.com/repos/olive-editor/olive/commits/master | $GREP_PATH -Po '(?<=: \")(([a-z0-9])\w+)(?=\")' -m 1 --)
LOCAL=$(git rev-parse HEAD)
if [ "$TRAVIS_TAG" != "" || "$REMOTE" == "$LOCAL" ]
then
echo "[INFO] Still current. Uploading..."
# retrieve upload tool
#wget -c https://github.com/probonopd/uploadtool/raw/master/upload.sh
#if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
# upload final package
#bash upload.sh Olive*.zip
#elif [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
#find appdir -executable -type f -exec ldd {} \; | grep " => /usr" | cut -d " " -f 2-3 | sort | uniq
# upload final package
#bash upload.sh Olive*.AppImage*
#fi
else
echo "[INFO] No longer current. $REMOTE vs $LOCAL - aborting upload."
fi
-18
View File
@@ -1,18 +0,0 @@
#!/bin/bash
if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
# Qt 5.10.1 (may have to source newer version at some point)
sudo add-apt-repository ppa:beineri/opt-qt-5.10.1-trusty -y
# FFmpeg 4.x (uses libs from 3.x repo)
sudo add-apt-repository ppa:jonathonf/ffmpeg-3 -y
sudo add-apt-repository ppa:jonathonf/ffmpeg-4 -y
# OpenImageIO
sudo add-apt-repository ppa:olive-editor/openimageio -y
# Update apt
sudo apt-get update -qq
fi
-12
View File
@@ -1,12 +0,0 @@
#!/bin/bash
if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
#export PATH="/usr/local/opt/qt/bin:/usr/local/opt/python@2/libexec/bin:$PATH"
elif [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
sudo apt-get -y -o Dpkg::Options::="--force-overwrite" install qt510base qt510multimedia qt510svg qttools5-dev libavformat-dev libavcodec-dev libavfilter-dev libavutil-dev libswscale-dev libswresample-dev cmake libopencolorio-dev libopenimageio-dev libopenexr-dev
source /opt/qt*/bin/qt*-env.sh
fi
-48
View File
@@ -1,48 +0,0 @@
#!/bin/bash
if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
# Generate Makefile
cmake .
# Make
make -j$(sysctl -n hw.ncpu)
BUNDLE_PATH=$(find . -name "Olive.app")
echo Found app at: $BUNDLE_PATH
# Move Qt deps into bundle
macdeployqt $BUNDLE_PATH
# Fix other deps that macdeployqt missed
wget -c -nv https://github.com/arl/macdeployqtfix/raw/master/macdeployqtfix.py
python2 macdeployqtfix.py $BUNDLE_PATH/Contents/MacOS/Olive /usr/local/Cellar/qt5/5.*/
# Distribute in zip
zip -r Olive-$(git rev-parse --short HEAD)-macOS.zip $BUNDLE_PATH
elif [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
# Generate Makefile
cmake . -DQt5LinguistTools_DIR=/usr/lib/x86_64-linux-gnu/cmake/Qt5LinguistTools
# Make
make -j$(nproc)
# Use `make install` on `appdir` to place files in the correct place
make DESTDIR=appdir -j$(nproc) install
# Download linuxdeployqt
wget -c -nv "https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage"
chmod a+x linuxdeployqt-continuous-x86_64.AppImage
unset QTDIR; unset QT_PLUGIN_PATH ; unset LD_LIBRARY_PATH
# linuxdeployqt uses this for naming the file
export VERSION=$(git rev-parse --short HEAD)
# Use linuxdeployqt to set up dependencies
./linuxdeployqt-continuous-x86_64.AppImage appdir/usr/local/share/applications/*.desktop -extra-plugins=imageformats/libqsvg.so -appimage
fi
+144 -26
View File
@@ -1,5 +1,5 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2019 Olive Team
# Copyright (C) 2021 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
@@ -14,14 +14,15 @@
# 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.9 FATAL_ERROR)
cmake_minimum_required(VERSION 3.13 FATAL_ERROR)
project(olive-editor VERSION 0.2.0 LANGUAGES CXX)
option(UPDATE_TS "Update translations" OFF)
option(BUILD_DOXYGEN "Build Doxygen documentation" OFF)
option(BUILD_TESTS "Build unit tests" ON)
option(USE_WERROR "Error on compile warning" ON)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
@@ -29,32 +30,97 @@ set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
# Set compiler options
if(MSVC)
set(OLIVE_COMPILE_OPTIONS
/wd4267
/wd4244
/experimental:external
/external:anglebrackets
/external:W0
"$<$<CONFIG:RELEASE>:/O2>"
"$<$<COMPILE_LANGUAGE:CXX>:/MP>"
)
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
)
if (USE_WERROR)
list(APPEND OLIVE_COMPILE_OPTIONS "-Werror")
endif()
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")
# 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)
find_package(OpenColorIO REQUIRED)
# Link OpenColorIO
find_package(OpenColorIO 2.0.0 REQUIRED)
list(APPEND OLIVE_LIBRARIES ${OCIO_LIBRARIES})
list(APPEND OLIVE_INCLUDE_DIRS ${OCIO_INCLUDE_DIRS})
find_package(OpenImageIO REQUIRED)
# Link OpenImageIO
find_package(OpenImageIO 2.1.12 REQUIRED)
list(APPEND OLIVE_LIBRARIES ${OIIO_LIBRARIES})
list(APPEND OLIVE_INCLUDE_DIRS ${OIIO_INCLUDE_DIRS})
find_package(Qt5 5.6 REQUIRED
COMPONENTS
# Link OpenEXR
find_package(OpenEXR REQUIRED)
list(APPEND OLIVE_LIBRARIES ${OPENEXR_LIBRARIES})
list(APPEND OLIVE_INCLUDE_DIRS ${OPENEXR_INCLUDES})
# Link Qt 5
set(QT_LIBRARIES
Core
Gui
Widgets
Multimedia
OpenGL
Svg
LinguistTools
Concurrent
)
if (UNIX AND NOT APPLE)
list(APPEND QT_LIBRARIES DBus)
endif()
find_package(Qt5 5.6 REQUIRED
COMPONENTS
${QT_LIBRARIES}
OPTIONAL_COMPONENTS
Network
)
if (NOT Qt5Network_FOUND)
message(" Qt5::Network module not found, crash reporting will be disabled.")
endif()
list(APPEND OLIVE_LIBRARIES
Qt5::Core
Qt5::Gui
Qt5::Widgets
Qt5::OpenGL
Qt5::Svg
Qt5::Concurrent
)
find_package(FFMPEG 3.4 REQUIRED
# Link FFmpeg
find_package(FFMPEG 3.0 REQUIRED
COMPONENTS
avutil
avcodec
@@ -63,36 +129,88 @@ find_package(FFMPEG 3.4 REQUIRED
swscale
swresample
)
list(APPEND OLIVE_INCLUDE_DIRS ${FFMPEG_INCLUDE_DIRS})
list(APPEND OLIVE_LIBRARIES
FFMPEG::avutil
FFMPEG::avcodec
FFMPEG::avformat
FFMPEG::avfilter
FFMPEG::swscale
FFMPEG::swresample
)
# 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})
# Optional: Link OpenTimelineIO
find_package(OpenTimelineIO)
if (OpenTimelineIO_FOUND)
list(APPEND OLIVE_DEFINITIONS USE_OTIO)
list(APPEND OLIVE_INCLUDE_DIRS ${OTIO_INCLUDE_DIRS})
list(APPEND OLIVE_LIBRARIES ${OTIO_LIBRARIES})
else()
message(" OpenTimelineIO interchange will be disabled.")
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 (WIN32)
list(APPEND OLIVE_DEFINITIONS "-DUNICODE -D_UNICODE")
elseif (APPLE)
list(APPEND OLIVE_LIBRARIES "-framework IOKit")
elseif(UNIX)
list(APPEND OLIVE_LIBRARIES Qt5::DBus)
endif()
# Generate Git hash
set(PROJECT_LONG_VERSION ${PROJECT_VERSION})
if(EXISTS "${CMAKE_SOURCE_DIR}/.git")
find_package(Git)
if(GIT_FOUND)
execute_process(COMMAND ${GIT_EXECUTABLE} log -1 --format=%h
execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse --short=8 HEAD
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE GIT_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE
)
set(PROJECT_LONG_VERSION ${PROJECT_VERSION}-${GIT_HASH})
endif()
elseif(UNIX AND NOT APPLE AND NOT ${CMAKE_SYSTEM_NAME} MATCHES "OpenBSD")
# Fallback for Ubuntu/Launchpad (extracts Git hash from debian/changelog rather than Git repo)
# (see https://answers.launchpad.net/launchpad/+question/678556)
execute_process(COMMAND sh -c "grep -Po '(?<=-)(([a-z0-9])\\w+)(?=\\+)' -m 1 changelog"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/debian
OUTPUT_VARIABLE GIT_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE
)
endif()
if(DEFINED GIT_HASH)
message("Olive: Git hash = " "${GIT_HASH}")
list(APPEND OLIVE_DEFINITIONS -DGITHASH="${GIT_HASH}")
else()
message("Olive: No Git hash defined!")
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}/ext)
add_subdirectory(app)
if (BUILD_TESTS)
enable_testing()
add_subdirectory(tests)
endif()
+32 -4
View File
@@ -1,8 +1,36 @@
# Contributing to Olive
### Standards
Thank you for your interest in contributing to Olive!
When contributing to Olive, it's recommended to use the following rules:
## Reporting issues
* [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html)
* 120 column limit
Bug reports help to make the software more stable and usable.
Please read the pinned [issue #1175](https://github.com/olive-editor/olive/issues/1175)
for guidelines before you create a new issue.
## Writing code
Code contributions are welcome. Note that the code base is rapidly changing in
the current stage of development however. There is some documentation in the form
of code comments, including Javadoc in header files. Feel free to reach out via
an issue or pull request if you have questions about the architecture or
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
[Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html)
including, but not limited to:
* Indentation is 2 spaces wide, spaces only (no tabs)
* `lowercase_underscored_variable_names`
* `lowercase_underscored_functions()` or `SentenceCaseFunctions()`
* `class SentenceCaseClassesAndStructs {}`
* `kSentenceCaseConstants` prepended with a lowercase `k`
* `UPPERCASE_UNDERSCORED_MACROS` for variables or same style as functions for macro functions
* `class_member_variables_` end with a `_`
* 100 column limit (where it doesn't impair readability)
* Unix line endings (only LF no CRLF)
* Javadoc documentation where appropriate
+6 -16
View File
@@ -1,22 +1,16 @@
# Olive Video Editor [![Build Status](https://travis-ci.org/olive-editor/olive.svg?branch=master)](https://travis-ci.org/olive-editor/olive) [![Build Status](https://ci.appveyor.com/api/projects/status/5s4jabxayg51rv95?svg=true)](https://ci.appveyor.com/project/itsmattkc/olive)
# Olive Video Editor [![Build status](https://github.com/olive-editor/olive/workflows/CI/badge.svg?branch=master)](https://github.com/olive-editor/olive/actions?query=branch%3Amaster)
Olive is a free non-linear video editor for Windows, macOS, and Linux.
![screen](https://www.olivevideoeditor.org/img/screenshot.jpg)
![screen](https://olivevideoeditor.org/img/020-2.png)
**Discover more:** [Website](https://www.olivevideoeditor.org/) | [Twitter](https://twitter.com/oliveteam) | [Discord](https://discord.gg/4Ae9KZn) | [Code Documentation](http://olivevideoeditor.org/doxygen/)
**Discover more:** [Website](https://www.olivevideoeditor.org/) | [Binaries](https://olivevideoeditor.org/download.php) | [Patreon](https://www.patreon.com/olivevideoeditor) | [Twitter](https://twitter.com/oliveteam) | [Wiki](https://github.com/olive-editor/olive/wiki/Overview-Guide) | [Community Discord (Unofficial)](https://discord.gg/4Ae9KZn)
**NOTE: The issue tracker is temporarily closed while Olive's core is getting rewritten. We apologize for any inconvenience. Please check back soon for the next release, as well as the [Patreon page](https://www.patreon.com/olivevideoeditor) for news and updates.**
**NOTE: Olive is alpha software and is considered highly unstable. While we highly appreciate users testing and providing usage information, please use at your own risk.**
## Binaries
## Getting Started with Olive:
Installation of Olive is available at: https://olivevideoeditor.org/download.php
Instructions on how to use Olive are available on our wiki: https://github.com/olive-editor/olive/wiki/Overview-Guide
**NOTE: Olive is Alpha Software, and it should be treated as highly unstable. Work at your own risk.**
[Nightly binaries are available on the website.](https://olivevideoeditor.org/download.php)
## Support Olive:
@@ -24,10 +18,6 @@ Please consider supporting Olive:
[![Become a Patron](https://olivevideoeditor.org/img/become_a_patron_button.png)](https://www.patreon.com/olivevideoeditor)
**NOTE: It is strongly discouraged to use or compile the `master` branch in its current state as it's under heavy restructuring. Please use the 0.1.x code from the "Releases" tab instead.**
## Compiling from Source:
Compiling instructions for Windows, macOS, and Linux can be found [on the main site](https://olivevideoeditor.org/compile.php).
Olive has Doxygen-compatible documentation hosted at http://olivevideoeditor.org/doxygen/. You can also run `doxygen` in the source root directory to generate a local copy.
+94 -75
View File
@@ -1,5 +1,5 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2019 Olive Team
# Copyright (C) 2021 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
@@ -14,112 +14,78 @@
# 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
${OLIVE_SOURCES}
core.h
core.cpp
main.cpp
)
#set(OLIVE_RESOURCES)
# Add subdirectories, which will populate the above variables
add_subdirectory(audio)
add_subdirectory(cli)
add_subdirectory(codec)
add_subdirectory(common)
add_subdirectory(config)
add_subdirectory(decoder)
add_subdirectory(dialog)
add_subdirectory(node)
add_subdirectory(packaging)
add_subdirectory(panel)
add_subdirectory(project)
add_subdirectory(render)
add_subdirectory(shaders)
add_subdirectory(task)
add_subdirectory(threading)
add_subdirectory(timeline)
add_subdirectory(ts)
add_subdirectory(tool)
add_subdirectory(ui)
add_subdirectory(undo)
add_subdirectory(widget)
add_subdirectory(window)
set(OLIVE_TARGET "olive-editor")
if(APPLE)
set(OLIVE_TARGET "Olive")
endif()
# Add translations
qt5_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}
#effects/internal/internalshaders.qrc
${CMAKE_CURRENT_BINARY_DIR}/ts/translations.qrc
)
add_executable(${OLIVE_TARGET}
# Add version object
add_library(olive-version-obj
OBJECT
version.cpp
version.h
)
target_link_libraries(olive-version-obj PRIVATE Qt5::Core)
target_compile_options(olive-version-obj PRIVATE -DAPPVERSION="${PROJECT_VERSION}" -DAPPVERSIONLONG="${PROJECT_LONG_VERSION}" )
# Add main library
add_library(libolive-editor
OBJECT
${OLIVE_SOURCES}
${OLIVE_RESOURCES}
${OLIVE_EFFECTS}
${OLIVE_QM_FILES}
)
if(APPLE)
SET_TARGET_PROPERTIES(${OLIVE_TARGET} PROPERTIES
MACOSX_BUNDLE TRUE
MACOSX_FRAMEWORK_IDENTIFIER org.olivevideoeditor.Olive
)
endif()
# Remove prefix - prevents CMake calling it "liblibolive-editor"
set_target_properties(libolive-editor PROPERTIES PREFIX "")
target_compile_definitions(${OLIVE_TARGET} PRIVATE ${OLIVE_DEFINITIONS})
if(MSVC)
# Enabling this threw warnings from FFmpeg headers that broke compilation. See if this is fixable.
#target_compile_options(${OLIVE_TARGET} PRIVATE /W4 /WX)
else()
target_compile_options(${OLIVE_TARGET} PRIVATE -Werror -Wuninitialized -pedantic-errors -Wall -Wextra -Wconversion -Wsign-conversion)
endif()
target_include_directories(
${OLIVE_TARGET}
PRIVATE
${OPENCOLORIO_INCLUDE_DIR}
${OIIO_INCLUDE_DIRS}
${FFMPEG_INCLUDE_DIRS}
# Add application
add_executable(olive-editor
main.cpp
$<TARGET_OBJECTS:libolive-editor>
$<TARGET_OBJECTS:olive-version-obj>
)
target_link_libraries(${OLIVE_TARGET}
PRIVATE
OpenGL::GL
Qt5::Core
Qt5::Gui
Qt5::Widgets
Qt5::Multimedia
Qt5::OpenGL
Qt5::Svg
FFMPEG::avutil
FFMPEG::avcodec
FFMPEG::avformat
FFMPEG::avfilter
FFMPEG::swscale
FFMPEG::swresample
${OPENCOLORIO_LIBRARIES}
${OIIO_LIBRARIES}
)
set(OLIVE_EFFECTS
# FIXME: Empty variable
)
set(OLIVE_TS_FILES
# FIXME: Empty variable
)
if(UPDATE_TS)
qt5_create_translation(OLIVE_QM_FILES ${CMAKE_SOURCE_DIR} ${OLIVE_TS_FILES})
else()
qt5_add_translation(OLIVE_QM_FILES ${OLIVE_TS_FILES})
endif()
if(UNIX AND NOT APPLE)
install(TARGETS ${OLIVE_TARGET} RUNTIME DESTINATION bin)
install(FILES ${OLIVE_EFFECTS} DESTINATION share/olive-editor/effects)
install(FILES ${OLIVE_QM_FILES} DESTINATION share/olive-editor/ts)
endif()
add_subdirectory(packaging)
# Create docs if doxygen was found
if(DOXYGEN_FOUND)
set(DOXYGEN_PROJECT_NAME "Olive")
set(DOXYGEN_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/docs")
@@ -127,3 +93,56 @@ if(DOXYGEN_FOUND)
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/olive.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.olivevideoeditor.Olive
MACOSX_BUNDLE_ICON_FILE olive.icns
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION}
MACOSX_BUNDLE_BUNDLE_NAME "Olive"
MACOSX_BUNDLE_INFO_STRING "Olive ${PROJECT_LONG_VERSION}"
MACOSX_BUNDLE_COPYRIGHT "©2018-2021 Olive Studios LLC and others. Published under the GNU General Public License version 3.0."
RESOURCE "${OLIVE_ICON}"
OUTPUT_NAME "Olive"
)
elseif(UNIX)
# Set Linux-specific properties for application
install(TARGETS olive-editor RUNTIME DESTINATION bin)
endif()
# Set link libraries
target_link_libraries(olive-editor PRIVATE ${OLIVE_LIBRARIES})
target_link_libraries(libolive-editor PRIVATE ${OLIVE_LIBRARIES})
# 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 Qt5Network_FOUND)
add_subdirectory(crashhandler)
endif()
+10 -6
View File
@@ -1,5 +1,5 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2019 Olive Team
# Copyright (C) 2021 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
@@ -16,11 +16,15 @@
set(OLIVE_SOURCES
${OLIVE_SOURCES}
audio/audiohybriddevice.h
audio/audiohybriddevice.cpp
audio/audiomanager.h
audio/audiomanager.cpp
audio/sampleformat.h
audio/sampleformat.cpp
audio/audiomanager.h
audio/audiovisualwaveform.cpp
audio/audiovisualwaveform.h
audio/packedprocessor.cpp
audio/packedprocessor.h
audio/planarprocessor.cpp
audio/planarprocessor.h
audio/tempoprocessor.cpp
audio/tempoprocessor.h
PARENT_SCOPE
)
-151
View File
@@ -1,151 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 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/>.
***/
#include "audiohybriddevice.h"
#include <QDebug>
#include <QtMath>
AudioHybridDevice::AudioHybridDevice(QObject *parent) :
QIODevice(parent),
device_(nullptr),
sample_index_(0),
enable_sending_samples_(false)
{
}
void AudioHybridDevice::Push(const QByteArray& samples)
{
Stop();
pushed_samples_ = samples;
sample_index_ = 0;
if (!pushed_samples_.isEmpty()) {
emit HasSamples();
}
}
void AudioHybridDevice::Stop()
{
// Whatever is happening, stop it
pushed_samples_.clear();
if (device_ != nullptr) {
device_->close();
device_ = nullptr;
}
}
void AudioHybridDevice::ConnectDevice(QIODevice *device)
{
// Clear any previous device or pushed sample
Stop();
device_ = device;
if (device_ != nullptr) {
emit HasSamples();
}
}
bool AudioHybridDevice::IsIdle()
{
return device_ == nullptr && pushed_samples_.isEmpty();
}
void AudioHybridDevice::SetEnableSendingSamples(bool e)
{
enable_sending_samples_ = e;
}
qint64 AudioHybridDevice::readData(char *data, qint64 maxSize)
{
qint64 read_size = read_internal(data, maxSize);
if (enable_sending_samples_ && read_size > 0) {
// FIXME: Assumes float and stereo
float* samples = reinterpret_cast<float*>(data);
int sample_count = static_cast<int>(read_size / static_cast<int>(sizeof(float)));
int channels = 2;
// Create array of samples to send
QVector<double> averages(channels);
averages.fill(0);
// Add all samples together
for (int i=0;i<sample_count;i++) {
averages[i%channels] = qMax(averages[i%channels], static_cast<double>(qAbs(samples[i])));
}
emit SentSamples(averages);
}
return read_size;
}
qint64 AudioHybridDevice::writeData(const char *data, qint64 maxSize)
{
Q_UNUSED(data)
Q_UNUSED(maxSize)
// This device doesn't support writing
return -1;
}
qint64 AudioHybridDevice::read_internal(char *data, qint64 maxSize)
{
// If a device is connected, passthrough to it
if (device_ != nullptr) {
qint64 read_count = device_->read(data, maxSize);
// Stop reading this device
if (device_->atEnd()) {
device_->close();
device_ = nullptr;
}
return read_count;
}
// If there are samples to push, push those
if (!pushed_samples_.isEmpty()) {
qint64 read_count = qMin(maxSize, pushed_samples_.size() - sample_index_);
memcpy(data, pushed_samples_.data()+sample_index_, static_cast<size_t>(read_count));
sample_index_ += read_count;
if (sample_index_ == pushed_samples_.size()) {
pushed_samples_.clear();
}
if (read_count < maxSize) {
memset(data + read_count, 0, static_cast<size_t>(maxSize - read_count));
}
return maxSize;
}
memset(data, 0, static_cast<size_t>(maxSize));
return maxSize;
}
-144
View File
@@ -1,144 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 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/>.
***/
#ifndef AUDIOHYBRIDDEVICE_H
#define AUDIOHYBRIDDEVICE_H
#include <QIODevice>
/**
* @brief A device that can be connected to QAudioOutput and provides both "push" and "pull" functionality
*
* By default, a QAudioOutput works in either "push" or "pull" mode. Either it can automatically "pull" from a
* QIODevice (reading from it whenever it requires samples), or we can "push" samples to it constantly (on a timer and
* often in another thread) to make sure the buffer never underruns.
*
* This class functions as a "pull" device for the QAudioOutput, so the audio device automatically reads from it, but
* then provides both "pull" and "push" functionality to the rest of the system. A QIODevice (like a raw PCM file)
* can be connected and pulled from (good for continuous audio playback), and any amount of samples can also be pushed
* (good for short bursts of sound, e.g. audio scrubbing).
*
* This does not support any "queuing", running ConnectDevice() or Push() will immediately discard anything that's
* currently being sent to the device and replace it.
*/
class AudioHybridDevice : public QIODevice
{
Q_OBJECT
public:
AudioHybridDevice(QObject* parent = nullptr);
void Push(const QByteArray &samples);
/**
* @brief Stop all audio output
*
* Whatever is being done (pulling from QIODevice or samples), it is stopped and cleared placing this into
* "idle" state.
*
* Note that audio playback may not stop immediately after calling this function as the audio output may still have
* samples in its buffer to output (QAudioOutput::stop() should be called as well for more immediate feedback). This
* will prevent any further samples from being sent however.
*
* \see IsIdle()
*/
void Stop();
/**
* @brief Connect a QIODevice (e.g. QFile) to start sending to the audio output
*
* This will clear any pushed samples or QIODevices currently being read and will start reading from this next time
* the audio output requests data.
*/
void ConnectDevice(QIODevice* device);
/**
* @brief Returns true if there are no more samples to be sent
*
* This is true if a device was connected or samples were pushed but we reached the end and no more data is available
* to be sent.
*
* In this state, this device will continue returning an empty buffer (all zeroes) to the device to workaround
* QAudioOutput's buffer underrun prevention with pushed samples. Therefore the QAudioOutput will never be put into
* QAudio::IdleState. Therefore only way to determine whether the output is no longer receiving usable audio is to
* check this function.
*/
bool IsIdle();
/**
* @brief If enabled, this will emit the SentSamples() signal whenever samples are sent to the output device
*/
void SetEnableSendingSamples(bool e);
signals:
/**
* @brief Signal emitted when this leaves "idle" state and has valid audio data that is ready to be sent
*/
void HasSamples();
/**
* @brief Signal emitted when samples are sent to the output device
*
* This sends an array of values corresponding to the channel count. Each value is an average of all the samples just
* sent to that channel. Useful for connecting to AudioMonitor.
*
* Can be disabled with SetEnableSendingSamples() to save CPU usage.
*/
void SentSamples(QVector<double> averages);
protected:
/**
* @brief Internal QIODevice function for reading
*
* Returns either QIODevice::read() from the connected device, or samples from the pushed sample buffer if there is
* none.
*
* If there is neither, this function returns a complete buffer of zeroes (i.e. silence). This is to allow
* pushed samples that don't fulfill the QAudioOutput's internal buffer size. If it cannot fill its internal buffer
* with the samples we send, it won't play anything until it receives more samples (enough to fill the buffer)
* in an attempt to prevent buffer underrun).
*
* Since we don't care about buffer underrun with short bursts of sound, we return silent samples if the QAudioOutput
* requests it. However this does mean the QAudioOutput never returns to QAudio::IdleState (since we never know if a
* read is just to fill the "remainder" of a buffer - which needs zeroes - or just a general read - which technically
* doesn't). It's recommended to check IsIdle() in tandem with the QAudioOutput::notify() signal to
* determine whether the QAudioOutput is effectively idle and can be stopped.
*/
virtual qint64 readData(char *data, qint64 maxSize) override;
/**
* @brief Internal QIODevice function for writing
*
* This class does not support writing, so this always returns -1 (QIODevice's documented error code for writing)
*/
virtual qint64 writeData(const char *data, qint64 maxSize) override;
private:
qint64 read_internal(char *data, qint64 maxSize);
QIODevice* device_;
QByteArray pushed_samples_;
qint64 sample_index_;
bool enable_sending_samples_;
};
#endif // AUDIOHYBRIDDEVICE_H
+149 -181
View File
@@ -1,7 +1,7 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
Copyright (C) 2021 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
@@ -20,8 +20,16 @@
#include "audiomanager.h"
#ifdef PA_HAS_JACK
#include <pa_jack.h>
#endif
#include <QApplication>
#include "config/config.h"
namespace olive {
AudioManager* AudioManager::instance_ = nullptr;
void AudioManager::CreateInstance()
@@ -42,223 +50,183 @@ AudioManager *AudioManager::instance()
return instance_;
}
void AudioManager::RefreshDevices()
void AudioManager::SetOutputNotifyInterval(int n)
{
if (refreshing_devices_) {
output_buffer_->set_notify_interval(n);
}
int OutputCallback(const void *input, void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData)
{
PreviewAudioDevice *device = static_cast<PreviewAudioDevice*>(userData);
qint64 max_read = frameCount * device->bytes_per_frame();
qint64 read_count = device->read(reinterpret_cast<char*>(output), max_read);
if (read_count < max_read) {
memset(reinterpret_cast<uint8_t*>(output) + read_count, 0, max_read - read_count);
}
return paContinue;
}
void AudioManager::PushToOutput(const AudioParams &params, const QByteArray &samples)
{
if (output_device_ == paNoDevice) {
return;
}
input_devices_.clear();
output_devices_.clear();
if (output_params_ != params || output_stream_ == nullptr) {
output_params_ = params;
refreshing_devices_ = true;
CloseOutputStream();
refresh_thread_.start(QThread::LowPriority);
}
PaStreamParameters p;
bool AudioManager::IsRefreshing()
{
return refreshing_devices_;
}
p.channelCount = output_params_.channel_count();
p.device = output_device_;
p.hostApiSpecificStreamInfo = nullptr;
p.sampleFormat = GetPortAudioSampleFormat(output_params_.format());
p.suggestedLatency = Pa_GetDeviceInfo(output_device_)->defaultLowOutputLatency;
void AudioManager::PushToOutput(const QByteArray &samples)
{
output_manager_.Push(samples);
}
Pa_OpenStream(&output_stream_, nullptr, &p, output_params_.sample_rate(), paFramesPerBufferUnspecified, paNoFlag, OutputCallback, output_buffer_);
void AudioManager::StartOutput(QIODevice *device)
{
if (output_ == nullptr) {
return;
output_buffer_->set_bytes_per_frame(output_params_.samples_to_bytes(1));
}
output_manager_.ConnectDevice(device);
output_buffer_->write(samples);
if (!Pa_IsStreamActive(output_stream_)) {
Pa_StartStream(output_stream_);
}
}
void AudioManager::ClearBufferedOutput()
{
output_buffer_->clear();
}
PaSampleFormat AudioManager::GetPortAudioSampleFormat(AudioParams::Format fmt)
{
switch (fmt) {
case AudioParams::kFormatUnsigned8:
return paUInt8;
case AudioParams::kFormatSigned16:
return paInt16;
case AudioParams::kFormatSigned32:
return paInt32;
case AudioParams::kFormatFloat32:
return paFloat32;
case AudioParams::kFormatSigned64:
case AudioParams::kFormatFloat64:
case AudioParams::kFormatInvalid:
case AudioParams::kFormatCount:
break;
}
return 0;
}
void AudioManager::CloseOutputStream()
{
if (output_stream_) {
if (Pa_IsStreamActive(output_stream_)) {
StopOutput();
}
Pa_CloseStream(output_stream_);
output_stream_ = nullptr;
}
}
void AudioManager::StopOutput()
{
output_manager_.Stop();
if (output_ != nullptr) {
output_->stop();
// Abort the stream so playback stops immediately
if (output_stream_) {
Pa_AbortStream(output_stream_);
ClearBufferedOutput();
}
}
void AudioManager::SetOutputDevice(const QAudioDeviceInfo &info)
void AudioManager::SetOutputDevice(PaDeviceIndex device)
{
qInfo() << "Setting output audio device to" << info.deviceName();
if (device == paNoDevice) {
qInfo() << "No output device found";
} else {
qInfo() << "Setting output audio device to" << Pa_GetDeviceInfo(device)->name;
}
StopOutput();
output_device_ = device;
output_device_info_ = info;
CloseOutputStream();
}
if (output_params_.is_valid()) {
QAudioFormat format;
format.setSampleRate(output_params_.sample_rate());
format.setChannelCount(output_params_.channel_count());
format.setCodec("audio/pcm");
format.setByteOrder(QAudioFormat::LittleEndian);
void AudioManager::SetInputDevice(PaDeviceIndex device)
{
if (device == paNoDevice) {
qInfo() << "No input device found";
} else {
qInfo() << "Setting input audio device to" << Pa_GetDeviceInfo(device)->name;
}
switch (output_params_.format()) {
case SAMPLE_FMT_U8:
format.setSampleSize(8);
format.setSampleType(QAudioFormat::UnSignedInt);
break;
case SAMPLE_FMT_S16:
format.setSampleSize(16);
format.setSampleType(QAudioFormat::SignedInt);
break;
case SAMPLE_FMT_S32:
format.setSampleSize(32);
format.setSampleType(QAudioFormat::SignedInt);
break;
case SAMPLE_FMT_S64:
format.setSampleSize(64);
format.setSampleType(QAudioFormat::SignedInt);
break;
case SAMPLE_FMT_FLT:
format.setSampleSize(32);
format.setSampleType(QAudioFormat::Float);
break;
case SAMPLE_FMT_DBL:
format.setSampleSize(64);
format.setSampleType(QAudioFormat::Float);
break;
case SAMPLE_FMT_COUNT:
case SAMPLE_FMT_INVALID:
abort();
input_device_ = device;
}
void AudioManager::HardReset()
{
CloseOutputStream();
Pa_Terminate();
Pa_Initialize();
}
PaDeviceIndex AudioManager::FindConfigDeviceByName(bool is_output_device)
{
QString entry = is_output_device ? QStringLiteral("AudioOutput") : QStringLiteral("AudioInput");
return FindDeviceByName(Config::Current()[entry].toString(), is_output_device);
}
PaDeviceIndex AudioManager::FindDeviceByName(const QString &s, bool is_output_device)
{
if (!s.isEmpty()) {
for (PaDeviceIndex i=0, end=Pa_GetDeviceCount(); i<end; i++) {
const PaDeviceInfo *device = Pa_GetDeviceInfo(i);
if (((is_output_device && device->maxOutputChannels) || (!is_output_device && device->maxInputChannels))
&& !s.compare(device->name)) {
return i;
}
}
output_ = std::unique_ptr<QAudioOutput>(new QAudioOutput(info, format, this));
connect(output_.get(), SIGNAL(notify()), this, SLOT(OutputNotified()));
}
// Un-comment this to get debug information about what the audio output is doing
//connect(output_.get(), SIGNAL(stateChanged(QAudio::State)), this, SLOT(OutputStateChanged(QAudio::State)));
}
void AudioManager::SetOutputParams(const AudioRenderingParams &params)
{
if (output_params_ != params) {
output_params_ = params;
// Refresh output device
SetOutputDevice(output_device_info_);
}
}
void AudioManager::SetInputDevice(const QAudioDeviceInfo &info)
{
input_ = std::unique_ptr<QAudioInput>(new QAudioInput(info, QAudioFormat(), this));
input_device_info_ = info;
}
const QList<QAudioDeviceInfo> &AudioManager::ListInputDevices()
{
return input_devices_;
}
const QList<QAudioDeviceInfo> &AudioManager::ListOutputDevices()
{
return output_devices_;
return is_output_device ? Pa_GetDefaultOutputDevice() : Pa_GetDefaultInputDevice();
}
AudioManager::AudioManager() :
output_(nullptr),
input_(nullptr),
input_file_(nullptr),
refreshing_devices_(false)
output_stream_(nullptr)
{
connect(&refresh_thread_, SIGNAL(ListsReady()), this, SLOT(RefreshThreadDone()));
#ifdef PA_HAS_JACK
// PortAudio doesn't do a strcpy, so we need a const char that's readily accessible (i.e. not
// a QString converted to UTF-8)
PaJack_SetClientName("Olive");
#endif
RefreshDevices();
Pa_Initialize();
connect(&output_manager_, SIGNAL(HasSamples()), this, SLOT(OutputManagerHasSamples()));
connect(&output_manager_, SIGNAL(SentSamples(QVector<double>)), this, SIGNAL(SentSamples(QVector<double>)));
// Get device from config
PaDeviceIndex output_device = FindConfigDeviceByName(true);
PaDeviceIndex input_device = FindConfigDeviceByName(false);
output_manager_.SetEnableSendingSamples(true);
output_manager_.open(AudioHybridDevice::ReadOnly);
SetOutputDevice(output_device);
SetInputDevice(input_device);
output_buffer_ = new PreviewAudioDevice(this);
output_buffer_->open(PreviewAudioDevice::ReadWrite);
connect(output_buffer_, &PreviewAudioDevice::Notify, this, &AudioManager::OutputNotify);
}
void AudioManager::RefreshThreadDone()
AudioManager::~AudioManager()
{
output_devices_ = refresh_thread_.output_devices();
input_devices_ = refresh_thread_.input_devices();
CloseOutputStream();
refreshing_devices_ = false;
QString preferred_audio_output = Config::Current()["PreferredAudioOutput"].toString();
if (output_ == nullptr
|| (!preferred_audio_output.isEmpty() && output_device_info_.deviceName() != preferred_audio_output)) {
if (preferred_audio_output.isEmpty()) {
SetOutputDevice(QAudioDeviceInfo::defaultOutputDevice());
} else {
foreach (const QAudioDeviceInfo& info, output_devices_) {
if (info.deviceName() == preferred_audio_output) {
SetOutputDevice(info);
break;
}
}
}
}
QString preferred_audio_input = Config::Current()["PreferredAudioInput"].toString();
if (input_ == nullptr
|| (!preferred_audio_input.isEmpty() && input_device_info_.deviceName() != preferred_audio_input)) {
if (preferred_audio_input.isEmpty()) {
SetInputDevice(QAudioDeviceInfo::defaultInputDevice());
} else {
foreach (const QAudioDeviceInfo& info, input_devices_) {
if (info.deviceName() == preferred_audio_input) {
SetInputDevice(info);
break;
}
}
}
}
emit DeviceListReady();
Pa_Terminate();
}
void AudioManager::OutputManagerHasSamples()
{
if (output_ != nullptr && output_->state() != QAudio::ActiveState) {
output_->start(&output_manager_);
}
}
void AudioManager::OutputStateChanged(QAudio::State state)
{
qDebug() << state;
}
void AudioManager::OutputNotified()
{
if (output_manager_.IsIdle()) {
static_cast<QAudioOutput*>(sender())->stop();
}
}
AudioRefreshDevicesThread::AudioRefreshDevicesThread()
{
}
void AudioRefreshDevicesThread::run()
{
output_devices_ = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput);
input_devices_ = QAudioDeviceInfo::availableDevices(QAudio::AudioInput);
emit ListsReady();
}
const QList<QAudioDeviceInfo>& AudioRefreshDevicesThread::input_devices()
{
return input_devices_;
}
const QList<QAudioDeviceInfo>& AudioRefreshDevicesThread::output_devices()
{
return output_devices_;
}
+38 -70
View File
@@ -1,7 +1,7 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
Copyright (C) 2021 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
@@ -22,35 +22,17 @@
#define AUDIOMANAGER_H
#include <memory>
#include <QAudioInput>
#include <QAudioOutput>
#include <QtConcurrent/QtConcurrent>
#include <QThread>
#include <portaudio.h>
#include "audiohybriddevice.h"
#include "audiovisualwaveform.h"
#include "common/define.h"
#include "render/audioparams.h"
#include "render/audioplaybackcache.h"
#include "render/previewaudiodevice.h"
/**
* @brief A thread for refreshing the total list of devices on the system
*
* Refreshing devices causes a noticeable pause in execution. Doing it another thread is intended to avoid this.
*/
class AudioRefreshDevicesThread : public QThread {
Q_OBJECT
public:
AudioRefreshDevicesThread();
virtual void run() override;
const QList<QAudioDeviceInfo>& input_devices();
const QList<QAudioDeviceInfo>& output_devices();
signals:
void ListsReady();
private:
QList<QAudioDeviceInfo> input_devices_;
QList<QAudioDeviceInfo> output_devices_;
};
namespace olive {
/**
* @brief Audio input and output management class
@@ -67,70 +49,56 @@ public:
static AudioManager* instance();
void RefreshDevices();
void SetOutputNotifyInterval(int n);
bool IsRefreshing();
void PushToOutput(const AudioParams &params, const QByteArray& samples);
void PushToOutput(const QByteArray& samples);
void ClearBufferedOutput();
/**
* @brief Start playing audio from QIODevice
*
* This takes ownership of the QIODevice and will delete it when StopOutput() is called
*/
void StartOutput(QIODevice* device);
/**
* @brief Stop audio output immediately
*/
void StopOutput();
void SetOutputDevice(const QAudioDeviceInfo& info);
PaDeviceIndex GetOutputDevice() const
{
return output_device_;
}
void SetOutputParams(const AudioRenderingParams& params);
PaDeviceIndex GetInputDevice() const
{
return input_device_;
}
void SetInputDevice(const QAudioDeviceInfo& info);
void SetOutputDevice(PaDeviceIndex device);
const QList<QAudioDeviceInfo>& ListInputDevices();
const QList<QAudioDeviceInfo>& ListOutputDevices();
void SetInputDevice(PaDeviceIndex device);
void HardReset();
static PaDeviceIndex FindConfigDeviceByName(bool is_output_device);
static PaDeviceIndex FindDeviceByName(const QString &s, bool is_output_device);
signals:
void DeviceListReady();
void SentSamples(QVector<double> averages);
void OutputNotify();
private:
AudioManager();
QList<QAudioDeviceInfo> input_devices_;
virtual ~AudioManager() override;
QList<QAudioDeviceInfo> output_devices_;
static PaSampleFormat GetPortAudioSampleFormat(AudioParams::Format fmt);
void CloseOutputStream();
static AudioManager* instance_;
AudioHybridDevice output_manager_;
PaDeviceIndex output_device_;
PaStream *output_stream_;
AudioParams output_params_;
PreviewAudioDevice *output_buffer_;
std::unique_ptr<QAudioOutput> output_;
QAudioDeviceInfo output_device_info_;
AudioRenderingParams output_params_;
std::unique_ptr<QAudioInput> input_;
QAudioDeviceInfo input_device_info_;
QIODevice* input_file_;
bool refreshing_devices_;
AudioRefreshDevicesThread refresh_thread_;
private slots:
void RefreshThreadDone();
void OutputManagerHasSamples();
void OutputStateChanged(QAudio::State state);
void OutputNotified();
PaDeviceIndex input_device_;
};
}
#endif // AUDIOMANAGER_H
+475
View File
@@ -0,0 +1,475 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#include "audiovisualwaveform.h"
#include <QDebug>
#include <QtGlobal>
#include "config/config.h"
#include "common/cpuoptimize.h"
#include "common/functiontimer.h"
namespace olive {
const rational AudioVisualWaveform::kMinimumSampleRate = rational(1, 8);
const rational AudioVisualWaveform::kMaximumSampleRate = 1024;
AudioVisualWaveform::AudioVisualWaveform() :
channels_(0)
{
for (rational i=kMinimumSampleRate; i<=kMaximumSampleRate; i*=2) {
mipmapped_data_.insert({i, Sample()});
}
}
void AudioVisualWaveform::OverwriteSamplesFromBuffer(SampleBufferPtr samples, int sample_rate, const rational &start, double target_rate, Sample& data, int &start_index, int &samples_length)
{
start_index = time_to_samples(start, target_rate);
samples_length = time_to_samples(static_cast<double>(samples->sample_count()) / static_cast<double>(sample_rate), target_rate);
int end_index = start_index + samples_length;
if (data.size() < end_index) {
data.resize(end_index);
}
double chunk_size = double(sample_rate) / double(target_rate);
for (int i=0; i<samples_length; i+=channels_) {
int src_start = qRound((double(i) * chunk_size)) / channels_;
int src_end = qMin(qRound((double(i + channels_) * chunk_size)) / channels_, samples->sample_count());
Sample summary = SumSamples(samples,
src_start,
src_end - src_start);
memcpy(&data.data()[i + start_index],
summary.constData(),
summary.size() * sizeof(SamplePerChannel));
}
}
void AudioVisualWaveform::OverwriteSamplesFromMipmap(const AudioVisualWaveform::Sample &input, double input_sample_rate, int &input_start, int &input_length, const rational &start, double output_rate, AudioVisualWaveform::Sample &output_data)
{
int start_index = time_to_samples(start, output_rate);
int samples_length = time_to_samples(static_cast<double>(input_length / channels_) / input_sample_rate, output_rate);
int end_index = start_index + samples_length;
if (output_data.size() < end_index) {
output_data.resize(end_index);
}
// We guarantee mipmaps are powers of two so integer division should be perfectly accurate here
int chunk_size = input_sample_rate / output_rate;
for (int i=0; i<samples_length; i+=channels_) {
Sample summary = ReSumSamples(&input.constData()[input_start + (i*chunk_size)], chunk_size * channels_, channels_);
memcpy(&output_data.data()[i + start_index],
summary.constData(),
summary.size() * sizeof(SamplePerChannel));
}
input_start = start_index;
input_length = samples_length;
}
void AudioVisualWaveform::OverwriteSamples(SampleBufferPtr samples, int sample_rate, const rational &start)
{
if (!channels_) {
qWarning() << "Failed to write samples - channel count is zero";
return;
}
// Old less optimized code. Keeping this around as a reference, but the below code is at least
// 10x faster so this shouldn't be used in production.
//
// int input_start, input_length;
// for (auto it=mipmapped_data_.begin(); it!=mipmapped_data_.end(); it++) {
// OverwriteSamplesFromBuffer(samples, sample_rate, start, it->first.toDouble(), it->second, input_start, input_length);
// }
// Process the largest mipmap directly for the samples
auto current_mipmap = mipmapped_data_.rbegin();
int input_start, input_length;
OverwriteSamplesFromBuffer(samples, sample_rate, start, current_mipmap->first.toDouble(), current_mipmap->second, input_start, input_length);
while (true) {
// For each smaller mipmap, we just process from the mipmap before it, making each one
// exponentially faster to create
auto previous_mipmap = current_mipmap;
current_mipmap++;
if (current_mipmap == mipmapped_data_.rend()) {
break;
}
OverwriteSamplesFromMipmap(previous_mipmap->second, previous_mipmap->first.toDouble(),
input_start, input_length, start, current_mipmap->first.toDouble(),
current_mipmap->second);
}
rational sample_length(samples->sample_count(), sample_rate);
length_ = qMax(length_, start + sample_length);
}
void AudioVisualWaveform::OverwriteSums(const AudioVisualWaveform &sums, const rational &dest, const rational& offset, const rational& length)
{
for (auto it=mipmapped_data_.begin(); it!=mipmapped_data_.end(); it++) {
rational rate = it->first;
Sample& our_arr = it->second;
const Sample& their_arr = sums.mipmapped_data_.at(rate);
double rate_dbl = rate.toDouble();
// Get our destination sample
int our_start_index = time_to_samples(dest, rate_dbl);
// Get our source sample
int their_start_index = time_to_samples(offset, rate_dbl);
if (their_start_index >= their_arr.size()) {
continue;
}
// Determine how much we're copying
int copy_len = their_arr.size() - their_start_index;
if (!length.isNull()) {
copy_len = qMin(copy_len, time_to_samples(length, rate_dbl));
}
// Determine end index of our array
int end_index = our_start_index + copy_len;
if (our_arr.size() < end_index) {
our_arr.resize(end_index);
}
memcpy(reinterpret_cast<char*>(our_arr.data()) + our_start_index * sizeof(SamplePerChannel),
reinterpret_cast<const char*>(their_arr.constData()) + their_start_index * sizeof(SamplePerChannel),
copy_len * sizeof(SamplePerChannel));
}
length_ = qMax(length_, dest + ((length.isNull()) ? sums.length() - offset : length));
}
void AudioVisualWaveform::OverwriteSilence(const rational &start, const rational &length)
{
for (auto it=mipmapped_data_.begin(); it!=mipmapped_data_.end(); it++) {
rational rate = it->first;
Sample& our_arr = it->second;
double rate_dbl = rate.toDouble();
// Get our destination sample
int our_start_index = time_to_samples(start, rate_dbl);
int our_length_index = time_to_samples(length, rate_dbl);
int our_end_index = our_start_index + our_length_index;
if (our_arr.size() < our_end_index) {
our_arr.resize(our_end_index);
}
memset(reinterpret_cast<char*>(our_arr.data()) + our_start_index * sizeof(SamplePerChannel), 0, our_length_index * sizeof(SamplePerChannel));
}
}
void AudioVisualWaveform::Shift(const rational &from, const rational &to)
{
for (auto it=mipmapped_data_.begin(); it!=mipmapped_data_.end(); it++) {
rational rate = it->first;
double rate_dbl = rate.toDouble();
Sample& data = it->second;
int from_index = time_to_samples(from, rate_dbl);
int to_index = time_to_samples(to, rate_dbl);
if (from_index == to_index) {
continue;
}
if (from_index >= data.size()) {
continue;
}
if (from_index > to_index) {
// Shifting backwards <-
data.remove(to_index, from_index - to_index);
} else {
// Shifting forwards ->
data.insert(from_index, to_index - from_index, {0, 0});
}
}
length_ = qMax(rational(0), length_ + (to-from));
}
void AudioVisualWaveform::TrimIn(const rational &length)
{
for (auto it=mipmapped_data_.begin(); it!=mipmapped_data_.end(); it++) {
rational rate = it->first;
double rate_dbl = rate.toDouble();
Sample& data = it->second;
int chop_length = time_to_samples(length, rate_dbl);
if (chop_length == 0) {
continue;
}
if (chop_length > 0) {
data = data.mid(chop_length);
} else {
data.insert(0, -chop_length, SamplePerChannel());
}
}
length_ = qMax(rational(0), length_ - length);
}
AudioVisualWaveform AudioVisualWaveform::Mid(const rational &offset) const
{
AudioVisualWaveform mid = *this;
mid.TrimIn(offset);
return mid;
}
AudioVisualWaveform::Sample AudioVisualWaveform::GetSummaryFromTime(const rational &start, const rational &length) const
{
// Find mipmap that requires
auto using_mipmap = GetMipmapForScale(length.flipped().toDouble());
double rate_dbl = using_mipmap->first.toDouble();
int start_sample = time_to_samples(start, rate_dbl);
int sample_length = time_to_samples(length, rate_dbl);
const QVector<AudioVisualWaveform::SamplePerChannel> &mipmap_data = using_mipmap->second;
// Determine if the array actually has this sample
sample_length = qMin(sample_length, mipmap_data.size() - start_sample);
// Based on the above `min`, if sample length <= 0, that means start_sample >= the size of the
// array and nothing can be returned.
if (sample_length > 0) {
return ReSumSamples(&mipmap_data.constData()[start_sample], sample_length, channels_);
}
// Return null samples
return AudioVisualWaveform::Sample(channel_count(), {0, 0});
}
void ExpandMinMaxChannel(float *a, int start, int length, float &min_val, float &max_val)
{
#if defined(Q_PROCESSOR_X86) || defined(Q_PROCESSOR_ARM)
// SSE optimized
// load the first 4 elements of 'a' into min and max (they are 4 * 32 = 128 bits)
__m128 max = _mm_loadu_ps(a + start);
__m128 min = _mm_loadu_ps(a + start);
// loop over 'a' and compare current elements with min and max 4 by 4.
// we need to make sure we don't read out of boundaries should 'a' lenght be not mod. 4
for(int i = 4; i < length-4; i+=4) {
__m128 cur = _mm_loadu_ps(a + start + i);
max = _mm_max_ps(max, cur);
min = _mm_min_ps(min, cur);
}
// so we read the last 4 (or less) elements in a safe manner.
__m128 cur = _mm_loadu_ps(a + length - 4);
max = _mm_max_ps(max, cur);
min = _mm_min_ps(min, cur);
// this potentially overlaps up to the last 3 elements but it's not an issue.
// min and max will contain 4 min and max. To get the absolute min and max
// we need to compare the 4 values over themselves by shuffling each time.
for (int i = 0; i < 3; i++) {
max = _mm_max_ps(max, _mm_shuffle_ps(max, max, 0x93));
min = _mm_min_ps(min, _mm_shuffle_ps(min, min, 0x93));
}
// now min and max contain 4 identical items each representing min and max value respectively.
// and we store the first one into a float variable.
_mm_store_ss(&max_val, max);
_mm_store_ss(&min_val, min);
// I bet you don't find annotated low level code very often.
#else
// Standard unoptimized function
int end = start + length;
for (int i=start; i<end; i++) {
min_val = std::min(min_val, a[i]);
max_val = std::max(max_val, a[i]);
}
#endif
}
AudioVisualWaveform::Sample AudioVisualWaveform::SumSamples(SampleBufferPtr samples, int start_index, int length)
{
int channels = samples->audio_params().channel_count();
AudioVisualWaveform::Sample summed_samples(channels);
for (int channel=0; channel<samples->audio_params().channel_count(); channel++) {
ExpandMinMaxChannel(samples->data(channel), start_index, length, summed_samples[channel].min, summed_samples[channel].max);
}
// for reference: this approximation is n x faster (and less accurate) for a n-tracks clip
// for (int i=start_index; i<end_index; i++) {
// ExpandMinMax(summed_samples[i%channels], samples->data(i%channels)[i]);
// }
return summed_samples;
}
AudioVisualWaveform::Sample AudioVisualWaveform::ReSumSamples(const SamplePerChannel* samples,
int nb_samples,
int nb_channels)
{
AudioVisualWaveform::Sample summed_samples(nb_channels);
for (int i=0;i<nb_samples;i+=nb_channels) {
for (int j=0;j<nb_channels;j++) {
const AudioVisualWaveform::SamplePerChannel& sample = samples[i + j];
if (sample.min < summed_samples[j].min) {
summed_samples[j].min = sample.min;
}
if (sample.max > summed_samples[j].max) {
summed_samples[j].max = sample.max;
}
}
}
return summed_samples;
}
void AudioVisualWaveform::DrawSample(QPainter *painter, const Sample& sample, int x, int y, int height, bool rectified)
{
if (sample.isEmpty()) {
return;
}
int channel_height = height / sample.size();
int channel_half_height = channel_height / 2;
for (int i=0;i<sample.size();i++) {
float max = qMin(sample.at(i).max, 1.0f);
float min = qMax(sample.at(i).min, -1.0f);
if (rectified) {
int channel_bottom = y + channel_height * (i + 1);
int diff = qRound((max - min) * channel_half_height);
painter->drawLine(x,
channel_bottom - diff,
x,
channel_bottom);
} else {
int channel_mid = y + channel_height * i + channel_half_height;
// We subtract the sample so that positive Y values go up on the screen rather than down,
// which is how waveforms are usually rendered
painter->drawLine(x,
channel_mid - qRound(min * static_cast<float>(channel_half_height)),
x,
channel_mid - qRound(max * static_cast<float>(channel_half_height)));
}
}
}
void AudioVisualWaveform::DrawWaveform(QPainter *painter, const QRect& rect, const double& scale, const AudioVisualWaveform &samples, const rational& start_time)
{
if (samples.mipmapped_data_.empty()) {
return;
}
auto using_mipmap = samples.GetMipmapForScale(scale);
rational rate = using_mipmap->first;
double rate_dbl = rate.toDouble();
const Sample& arr = using_mipmap->second;
int start_sample_index = samples.time_to_samples(start_time, rate_dbl);
if (start_sample_index >= arr.size()) {
return;
}
int next_sample_index = start_sample_index;
int sample_index;
Sample summary;
int summary_index = -1;
const QRect& viewport = painter->viewport();
QPoint top_left = painter->transform().map(viewport.topLeft());
int start = qMax(rect.x(), -top_left.x());
int end = qMin(rect.right(), -top_left.x() + viewport.width());
bool rectified = Config::Current()[QStringLiteral("RectifiedWaveforms")].toBool();
for (int i=start;i<end;i++) {
sample_index = next_sample_index;
if (sample_index == arr.size()) {
break;
}
next_sample_index = qMin(arr.size(),
start_sample_index + qFloor(rate_dbl * static_cast<double>(i - rect.x() + 1) / scale) * samples.channel_count());
if (summary_index != sample_index) {
summary = AudioVisualWaveform::ReSumSamples(&arr.at(sample_index),
qMax(samples.channel_count(), next_sample_index - sample_index),
samples.channel_count());
summary_index = sample_index;
}
DrawSample(painter, summary, i, rect.y(), rect.height(), rectified);
}
}
int AudioVisualWaveform::time_to_samples(const rational &time, double sample_rate) const
{
return time_to_samples(time.toDouble(), sample_rate);
}
int AudioVisualWaveform::time_to_samples(const double &time, double sample_rate) const
{
return qFloor(time * sample_rate) * channels_;
}
std::map<rational, AudioVisualWaveform::Sample>::const_iterator AudioVisualWaveform::GetMipmapForScale(double scale) const
{
// Find largest mipmap for this scale (or the largest if we don't find one sufficient)
for (auto it=mipmapped_data_.cbegin(); it!=mipmapped_data_.cend(); it++) {
if (it->first.toDouble() >= scale) {
return it;
}
}
// We don't have a mipmap large enough for this scale, so just return the largest we have
return std::prev(mipmapped_data_.cend());
}
}
+135
View File
@@ -0,0 +1,135 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#ifndef SUMSAMPLES_H
#define SUMSAMPLES_H
#include <QPainter>
#include <QVector>
#include "codec/samplebuffer.h"
namespace olive {
/**
* @brief A buffer of data used to store a visual representation of audio
*
* This differs from a SampleBuffer as the data in an AudioVisualWaveform has been reduced
* significantly and optimized for visual display.
*/
class AudioVisualWaveform {
public:
AudioVisualWaveform();
struct SamplePerChannel {
float min;
float max;
};
using Sample = QVector<SamplePerChannel>;
int channel_count() const
{
return channels_;
}
void set_channel_count(int channels)
{
channels_ = channels;
}
const rational& length() const
{
return length_;
}
/**
* @brief Writes samples into the visual waveform buffer
*
* Starting at `start`, writes samples over anything in the buffer, expanding it if necessary.
*/
void OverwriteSamples(SampleBufferPtr samples, int sample_rate, const rational& start = 0);
/**
* @brief Replaces sums at a certain range in this visual waveform
*
* @param sums
*
* The sums to write over our current ones with.
*
* @param dest
*
* Where in this visual waveform these sums should START being written to.
*
* @param offset
*
* Where in the `sums` parameter this should start reading from. Defaults to 0.
*
* @param length
*
* Maximum length of `sums` to overwrite with.
*/
void OverwriteSums(const AudioVisualWaveform& sums, const rational& dest, const rational& offset = 0, const rational &length = 0);
void OverwriteSilence(const rational &start, const rational &length);
void Shift(const rational& from, const rational& to);
void TrimIn(const rational &length);
AudioVisualWaveform Mid(const rational &offset) const;
Sample GetSummaryFromTime(const rational& start, const rational& length) const;
static Sample SumSamples(SampleBufferPtr samples, int start_index, int length);
static Sample ReSumSamples(const SamplePerChannel *samples, int nb_samples, int nb_channels);
static void DrawSample(QPainter* painter, const Sample &sample, int x, int y, int height, bool rectified);
static void DrawWaveform(QPainter* painter, const QRect &rect, const double &scale, const AudioVisualWaveform& samples, const rational &start_time);
// Must be a power of 2
static const rational kMinimumSampleRate;
static const rational kMaximumSampleRate;
private:
void OverwriteSamplesFromBuffer(SampleBufferPtr samples, int sample_rate, const rational& start, double target_rate, Sample &data, int &start_index, int &samples_length);
void OverwriteSamplesFromMipmap(const Sample& input, double input_sample_rate, int &input_start, int &input_length, const rational& start, double output_rate, Sample &output_data);
int time_to_samples(const rational& time, double sample_rate) const;
int time_to_samples(const double& time, double sample_rate) const;
std::map<rational, Sample>::const_iterator GetMipmapForScale(double scale) const;
int channels_;
std::map<rational, Sample> mipmapped_data_;
rational length_;
};
}
Q_DECLARE_METATYPE(olive::AudioVisualWaveform)
#endif // SUMSAMPLES_H
+101
View File
@@ -0,0 +1,101 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#include "packedprocessor.h"
#include "common/ffmpegutils.h"
namespace olive {
PackedProcessor::PackedProcessor() :
swr_ctx_(nullptr)
{
}
PackedProcessor::~PackedProcessor()
{
Close();
}
bool PackedProcessor::Open(const AudioParams &params)
{
if (IsOpen()) {
return true;
}
swr_ctx_ = swr_alloc_set_opts(nullptr,
params.channel_layout(),
FFmpegUtils::GetFFmpegSampleFormat(params.format(), false),
params.sample_rate(),
params.channel_layout(),
FFmpegUtils::GetFFmpegSampleFormat(params.format(), true),
params.sample_rate(),
0,
nullptr);
if (!swr_ctx_) {
qCritical() << "Failed to allocate resample context";
return false;
}
if (swr_init(swr_ctx_) < 0) {
qCritical() << "Failed to init resample context";
swr_free(&swr_ctx_);
return false;
}
return true;
}
QByteArray PackedProcessor::Convert(SampleBufferPtr planar)
{
if (!IsOpen()) {
qCritical() << "Tried to convert while closed";
return QByteArray();
}
int nb_samples = planar->sample_count();
if (nb_samples == 0) {
return QByteArray();
}
QByteArray output(planar->audio_params().samples_to_bytes(nb_samples), Qt::Uninitialized);
uint8_t *output_data = reinterpret_cast<uint8_t*>(output.data());
int ret = swr_convert(swr_ctx_, &output_data, nb_samples,
const_cast<const uint8_t**>(reinterpret_cast<uint8_t**>(planar->to_raw_ptrs())),
nb_samples);
if (ret < 0) {
char buf[200];
av_strerror(ret, buf, 200);
qDebug() << "Packed processor failed with error:" << buf << ret;
}
return output;
}
void PackedProcessor::Close()
{
if (swr_ctx_) {
swr_free(&swr_ctx_);
}
}
}
+60
View File
@@ -0,0 +1,60 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#ifndef PACKEDPROCESSOR_H
#define PACKEDPROCESSOR_H
extern "C" {
#include <libswresample/swresample.h>
}
#include "codec/samplebuffer.h"
#include "render/audioparams.h"
namespace olive {
class PackedProcessor
{
public:
PackedProcessor();
~PackedProcessor();
DISABLE_COPY_MOVE(PackedProcessor)
bool Open(const AudioParams &params);
QByteArray Convert(SampleBufferPtr planar);
void Close();
bool IsOpen() const
{
return swr_ctx_;
}
private:
SwrContext *swr_ctx_;
};
}
#endif // PACKEDPROCESSOR_H
+104
View File
@@ -0,0 +1,104 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#include "planarprocessor.h"
#include "common/ffmpegutils.h"
namespace olive {
PlanarProcessor::PlanarProcessor() :
swr_ctx_(nullptr)
{
}
PlanarProcessor::~PlanarProcessor()
{
Close();
}
bool PlanarProcessor::Open(const AudioParams &params)
{
if (IsOpen()) {
return true;
}
swr_ctx_ = swr_alloc_set_opts(nullptr,
params.channel_layout(),
FFmpegUtils::GetFFmpegSampleFormat(params.format(), true),
params.sample_rate(),
params.channel_layout(),
FFmpegUtils::GetFFmpegSampleFormat(params.format(), false),
params.sample_rate(),
0,
nullptr);
if (!swr_ctx_) {
qCritical() << "Failed to allocate resample context";
return false;
}
if (swr_init(swr_ctx_) < 0) {
qCritical() << "Failed to init resample context";
swr_free(&swr_ctx_);
return false;
}
params_ = params;
return true;
}
SampleBufferPtr PlanarProcessor::Convert(const QByteArray &packed)
{
if (!IsOpen()) {
qCritical() << "Tried to convert while closed";
return nullptr;
}
if (packed.isEmpty()) {
return nullptr;
}
int nb_samples_per_channel = params_.bytes_to_samples(packed.size());
SampleBufferPtr output = SampleBuffer::CreateAllocated(params_, nb_samples_per_channel);
const uint8_t *input = reinterpret_cast<const uint8_t*>(packed.constData());
int ret = swr_convert(swr_ctx_,
reinterpret_cast<uint8_t**>(output->to_raw_ptrs()), nb_samples_per_channel,
&input, nb_samples_per_channel);
if (ret < 0) {
char buf[200];
av_strerror(ret, buf, 200);
qDebug() << "Planar processor failed with error:" << buf << ret;
}
return output;
}
void PlanarProcessor::Close()
{
if (swr_ctx_) {
swr_free(&swr_ctx_);
}
}
}
+62
View File
@@ -0,0 +1,62 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#ifndef PLANARPROCESSOR_H
#define PLANARPROCESSOR_H
extern "C" {
#include <libswresample/swresample.h>
}
#include "codec/samplebuffer.h"
#include "render/audioparams.h"
namespace olive {
class PlanarProcessor
{
public:
PlanarProcessor();
~PlanarProcessor();
DISABLE_COPY_MOVE(PlanarProcessor)
bool Open(const AudioParams &params);
SampleBufferPtr Convert(const QByteArray &packed);
void Close();
bool IsOpen() const
{
return swr_ctx_;
}
private:
SwrContext *swr_ctx_;
AudioParams params_;
};
}
#endif // PLANARPROCESSOR_H
-1
View File
@@ -1 +0,0 @@
#include "sampleformat.h"
+269
View File
@@ -0,0 +1,269 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#include "tempoprocessor.h"
extern "C" {
#include <libavfilter/buffersink.h>
#include <libavfilter/buffersrc.h>
#include <libavutil/opt.h>
}
#include <QDebug>
#include "common/ffmpegutils.h"
namespace olive {
TempoProcessor::TempoProcessor() :
filter_graph_(nullptr),
buffersrc_ctx_(nullptr),
buffersink_ctx_(nullptr),
open_(false)
{
}
TempoProcessor::~TempoProcessor()
{
Close();
}
bool TempoProcessor::IsOpen() const
{
return open_;
}
const double &TempoProcessor::GetSpeed() const
{
return speed_;
}
bool TempoProcessor::Open(const AudioParams &params, const double& speed)
{
if (open_) {
return true;
}
params_ = params;
speed_ = speed;
// Create AVFilterGraph instance
filter_graph_ = avfilter_graph_alloc();
if (!filter_graph_) {
qCritical() << "Failed to create AVFilterGraph";
Close();
return false;
}
// Set up audio buffer args
char filter_args[200];
snprintf(filter_args, 200, "time_base=%d/%d:sample_rate=%d:sample_fmt=%d:channel_layout=0x%" PRIx64,
1,
params_.sample_rate(),
params_.sample_rate(),
FFmpegUtils::GetFFmpegSampleFormat(params_.format()),
params.channel_layout());
// Create buffer and buffersink
if (avfilter_graph_create_filter(&buffersrc_ctx_, avfilter_get_by_name("abuffer"), "in", filter_args, nullptr, filter_graph_) < 0) {
qCritical() << "Failed to create audio buffer source";
Close();
return false;
}
if (avfilter_graph_create_filter(&buffersink_ctx_, avfilter_get_by_name("abuffersink"), "out", nullptr, nullptr, filter_graph_) < 0) {
qCritical() << "Failed to create audio buffer sink";
Close();
return false;
}
// Create audio tempo filters: FFmpeg's atempo can only be set between 0.5 and 2.0. If the requested speed is outside
// those boundaries, we need to daisychain more than one together.
double base = (speed_ > 1.0) ? 2.0 : 0.5;
double speed_log = log(speed_) / log(base);
// This is the number of how many 0.5 or 2.0 tempos we need to daisychain
int whole = qFloor(speed_log);
// Set speed_log to the remainder
speed_log -= whole;
AVFilterContext* previous_filter = buffersrc_ctx_;
for (int i=0;i<=whole;i++) {
double filter_tempo = (i == whole) ? qPow(base, speed_log) : base;
if (qFuzzyCompare(filter_tempo, 1.0)) {
// This filter would do nothing
continue;
}
previous_filter = CreateTempoFilter(filter_graph_,
previous_filter,
filter_tempo);
if (!previous_filter) {
qCritical() << "Failed to create audio tempo filter";
Close();
return false;
}
}
// Link the last filter to the buffersink
if (avfilter_link(previous_filter, 0, buffersink_ctx_, 0) != 0) {
qCritical() << "Failed to link final filter and buffer sink";
Close();
return false;
}
// Config graph
if (avfilter_graph_config(filter_graph_, nullptr) < 0) {
qCritical() << "Failed to configure filter graph";
Close();
return false;
}
timestamp_ = 0;
open_ = true;
flushed_ = false;
return true;
}
void TempoProcessor::Push(const QByteArray &packed)
{
if (!IsOpen()) {
qWarning() << "Tried to push to closed TempoProcessor";
return;
}
if (flushed_) {
qWarning() << "Tried to push to flushed TempoProcessor";
return;
}
AVFrame* src_frame = av_frame_alloc();
if (!src_frame) {
qCritical() << "Failed to allocate source frame";
return;
}
// Allocate a buffer for the number of samples we got
src_frame->sample_rate = params_.sample_rate();
src_frame->format = FFmpegUtils::GetFFmpegSampleFormat(params_.format());
src_frame->channel_layout = params_.channel_layout();
src_frame->nb_samples = params_.bytes_to_samples(packed.size());
src_frame->pts = timestamp_;
timestamp_ += src_frame->nb_samples;
if (av_frame_get_buffer(src_frame, 0) < 0) {
qCritical() << "Failed to allocate buffer for source frame";
av_frame_free(&src_frame);
return;
}
// Copy buffer from data array to frame
memcpy(src_frame->data[0], packed, packed.size());
int ret = av_buffersrc_add_frame_flags(buffersrc_ctx_, src_frame, AV_BUFFERSRC_FLAG_KEEP_REF);
if (ret < 0) {
qCritical() << "Failed to feed buffer source" << ret;
}
if (src_frame) {
av_frame_free(&src_frame);
}
}
void TempoProcessor::Flush()
{
if (!flushed_) {
int ret = av_buffersrc_add_frame_flags(buffersrc_ctx_, nullptr, AV_BUFFERSRC_FLAG_KEEP_REF);
if (ret < 0) {
qCritical() << "Failed to feed buffer source" << ret;
}
flushed_ = true;
}
}
QByteArray TempoProcessor::Pull()
{
QByteArray b;
AVFrame *processed_frame = av_frame_alloc();
// Try to pull samples from the buffersink
int ret = av_buffersink_get_frame(buffersink_ctx_, processed_frame);
if (ret < 0) {
// We couldn't pull for some reason, if the error was EAGAIN, we just need to send more samples. Otherwise the
// error might be fatal...
if (ret != AVERROR(EAGAIN)) {
qCritical() << "Failed to pull from buffersink" << ret;
}
av_frame_free(&processed_frame);
return b;
}
b.resize(params_.samples_to_bytes(processed_frame->nb_samples));
// Copy the bytes
memcpy(b.data(), processed_frame->data[0], b.size());
// If the index has reached the limit of this processed frame, we can dispose of the frame now
av_frame_free(&processed_frame);
return b;
}
void TempoProcessor::Close()
{
open_ = false;
if (filter_graph_) {
avfilter_graph_free(&filter_graph_);
filter_graph_ = nullptr;
}
buffersrc_ctx_ = nullptr;
buffersink_ctx_ = nullptr;
}
AVFilterContext *TempoProcessor::CreateTempoFilter(AVFilterGraph* graph, AVFilterContext* link, const double &tempo)
{
// Set up tempo param, which is taken as a C string
char speed_param[20];
snprintf(speed_param, 20, "%f", tempo);
AVFilterContext* tempo_ctx = nullptr;
if (avfilter_graph_create_filter(&tempo_ctx, avfilter_get_by_name("atempo"), "atempo", speed_param, nullptr, graph) >= 0
&& avfilter_link(link, 0, tempo_ctx, 0) == 0) {
return tempo_ctx;
}
return nullptr;
}
}
+85
View File
@@ -0,0 +1,85 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#ifndef TEMPOPROCESSOR_H
#define TEMPOPROCESSOR_H
#ifdef __MINGW32__
#ifndef __USE_MINGW_ANSI_STDIO
#define __USE_MINGW_ANSI_STDIO
#endif
#endif
#include <inttypes.h>
extern "C" {
#include <libavfilter/avfilter.h>
}
#include "render/audioparams.h"
namespace olive {
class TempoProcessor
{
public:
TempoProcessor();
~TempoProcessor();
DISABLE_COPY_MOVE(TempoProcessor)
bool IsOpen() const;
const double& GetSpeed() const;
bool Open(const AudioParams& params, const double &speed);
void Push(const QByteArray &packed);
void Flush();
QByteArray Pull();
void Close();
private:
static AVFilterContext* CreateTempoFilter(AVFilterGraph *graph, AVFilterContext *link, const double& tempo);
AVFilterGraph* filter_graph_;
AVFilterContext* buffersrc_ctx_;
AVFilterContext* buffersink_ctx_;
AudioParams params_;
int64_t timestamp_;
double speed_;
bool open_;
bool flushed_;
};
}
#endif // TEMPOPROCESSOR_H
@@ -1,5 +1,5 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2019 Olive Team
# Copyright (C) 2021 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
@@ -14,9 +14,10 @@
# 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(cliprogress)
add_subdirectory(clitask)
set(OLIVE_SOURCES
${OLIVE_SOURCES}
decoder/oiio/oiiodecoder.h
decoder/oiio/oiiodecoder.cpp
PARENT_SCOPE
)
@@ -1,7 +1,7 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
Copyright (C) 2021 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
@@ -18,11 +18,13 @@
***/
#include "menu.h"
#include "cliexportmanager.h"
#include "blend/alphaover/alphaover.h"
namespace olive {
NodeMenu::NodeMenu(QWidget *parent) :
QMenu(parent)
CLIExportManager::CLIExportManager()
{
}
}
+36
View File
@@ -0,0 +1,36 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#ifndef CLIEXPORTMANAGER_H
#define CLIEXPORTMANAGER_H
#include "task/export/export.h"
namespace olive {
class CLIExportManager : public QObject
{
public:
CLIExportManager();
};
}
#endif // CLIEXPORTMANAGER_H
+22
View File
@@ -0,0 +1,22 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2021 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}
cli/cliprogress/cliprogressdialog.h
cli/cliprogress/cliprogressdialog.cpp
PARENT_SCOPE
)
+106
View File
@@ -0,0 +1,106 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#include "cliprogressdialog.h"
#include <iostream>
namespace olive {
CLIProgressDialog::CLIProgressDialog(const QString& title, QObject *parent) :
QObject(parent),
title_(title),
progress_(-1),
drawn_(false)
{
SetProgress(0);
}
void CLIProgressDialog::Update()
{
if (drawn_) {
// We've been here before, do a carriage return back to the start of the terminal line
std::cout << "\r";
} else {
drawn_ = true;
}
// FIXME: Get real column count
int columns = 80;
int title_columns = columns / 2 - 1;
// Print "title" text
QString sized_title = title_;
if (title_.size() > title_columns) {
sized_title = title_.left(title_columns - 3).append(QStringLiteral("..."));
} else {
sized_title = title_;
}
std::cout << sized_title.toUtf8().constData();
// Pad out the rest of the title area if necessary
for (int i=sized_title.size(); i<title_columns; i++) {
std::cout << " ";
}
// Percentage counter " 100% " is 5 characters + the enclosing brackets [] are 2 characters
int progress_bar_columns = columns / 2 - 7;
std::cout << "[";
// Get UI bar progress
int bar_prog = qRound(progress_ * progress_bar_columns);
// Draw filled in bar
for (int i=0;i<bar_prog;i++) {
std::cout << "=";
}
// Draw empty space
for (int i=bar_prog;i<progress_bar_columns;i++) {
std::cout << " ";
}
std::cout << "] ";
if (progress_ < 100) {
std::cout << " ";
}
if (progress_ < 10) {
std::cout << " ";
}
std::cout << qRound(progress_ * 100.0) << "% " << std::endl << std::flush;
}
void CLIProgressDialog::SetProgress(double p)
{
if (progress_ != p) {
progress_ = p;
Update();
}
}
}
@@ -1,7 +1,7 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
Copyright (C) 2021 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
@@ -18,28 +18,35 @@
***/
#ifndef TRACKREFERENCE_H
#define TRACKREFERENCE_H
#ifndef CLIPROGRESSDIALOG_H
#define CLIPROGRESSDIALOG_H
#include "timeline/tracktypes.h"
#include <QObject>
#include <QTimer>
class TrackReference
#include "common/define.h"
namespace olive {
class CLIProgressDialog : public QObject
{
public:
TrackReference();
CLIProgressDialog(const QString &title, QObject* parent = nullptr);
TrackReference(const TrackType& type, const int& index);
const TrackType& type() const;
const int& index() const;
bool operator==(const TrackReference& ref) const;
public slots:
void SetProgress(double p);
private:
TrackType type_;
void Update();
QString title_;
double progress_;
bool drawn_;
int index_;
};
#endif // TRACKREFERENCE_H
}
#endif // CLIPROGRESSDIALOG_H
@@ -1,5 +1,5 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2019 Olive Team
# Copyright (C) 2021 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
@@ -16,7 +16,7 @@
set(OLIVE_SOURCES
${OLIVE_SOURCES}
node/color/opacity/opacity.h
node/color/opacity/opacity.cpp
cli/clitask/clitaskdialog.h
cli/clitask/clitaskdialog.cpp
PARENT_SCOPE
)
+37
View File
@@ -0,0 +1,37 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#include "clitaskdialog.h"
namespace olive {
CLITaskDialog::CLITaskDialog(Task *task, QObject* parent) :
CLIProgressDialog(task->GetTitle(), parent),
task_(task)
{
connect(task_, &Task::ProgressChanged, this, &CLITaskDialog::SetProgress);
}
bool CLITaskDialog::Run()
{
return task_->Start();
}
}
+44
View File
@@ -0,0 +1,44 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#ifndef CLITASKDIALOG_H
#define CLITASKDIALOG_H
#include "cli/cliprogress/cliprogressdialog.h"
#include "task/task.h"
namespace olive {
class CLITaskDialog : public CLIProgressDialog
{
Q_OBJECT
public:
CLITaskDialog(Task *task, QObject* parent = nullptr);
bool Run();
private:
Task* task_;
};
}
#endif // CLITASKDIALOG_H
+39
View File
@@ -0,0 +1,39 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2021 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(ffmpeg)
add_subdirectory(oiio)
set(OLIVE_SOURCES
${OLIVE_SOURCES}
codec/conformmanager.cpp
codec/conformmanager.h
codec/decoder.cpp
codec/decoder.h
codec/encoder.cpp
codec/encoder.h
codec/exportcodec.cpp
codec/exportcodec.h
codec/exportformat.cpp
codec/exportformat.h
codec/frame.cpp
codec/frame.h
codec/planarfiledevice.cpp
codec/planarfiledevice.h
codec/samplebuffer.cpp
codec/samplebuffer.h
PARENT_SCOPE
)
+126
View File
@@ -0,0 +1,126 @@
#include "conformmanager.h"
#include <QDir>
#include "task/taskmanager.h"
namespace olive {
ConformManager *ConformManager::instance_ = nullptr;
ConformManager::Conform ConformManager::GetConformState(const QString &decoder_id, const QString &cache_path, const Decoder::CodecStream &stream, const AudioParams &params, bool wait)
{
// Mutex because we'll need to check the status of a conform task
QMutexLocker locker(&mutex_);
// Return existing conform if exists
QVector<QString> filenames = GetConformedFilename(cache_path, stream, params);
if (AllConformsExist(filenames)) {
return {kConformExists, filenames, nullptr};
}
ConformTask *conforming_task = nullptr;
foreach (const ConformData &data, conforming_) {
if (data.stream == stream && data.params == params) {
// Already creating conform in a task
conforming_task = data.task;
break;
}
}
if (!conforming_task) {
// Not conforming yet, create a task to do so
// We conform to a different filename until it's done to make it clear even across sessions
// whether this conform is ready or not
QVector<QString> working_filenames = filenames;
for (int i=0; i<working_filenames.size(); i++) {
working_filenames[i].append(QStringLiteral(".working"));
}
conforming_task = new ConformTask(decoder_id, stream, params, working_filenames);
connect(conforming_task, &ConformTask::Finished, this, &ConformManager::ConformTaskFinished);
conforming_task->moveToThread(TaskManager::instance()->thread());
QMetaObject::invokeMethod(TaskManager::instance(), "AddTask", Qt::QueuedConnection, Q_ARG(Task *, conforming_task));
conforming_.append({stream, params, conforming_task, working_filenames, filenames});
}
if (wait) {
do {
conform_done_condition_.wait(&mutex_);
} while (!AllConformsExist(filenames));
return {kConformExists, filenames, nullptr};
}
return {kConformGenerating, QVector<QString>(), conforming_task};
}
QVector<QString> ConformManager::GetConformedFilename(const QString &cache_path, const Decoder::CodecStream &stream, const AudioParams &params)
{
QVector<QString> filenames(params.channel_count());
for (int i=0; i<filenames.size(); i++) {
QString index_fn = QStringLiteral("%1-%2.%3.%4.%5.%6.pcm").arg(FileFunctions::GetUniqueFileIdentifier(stream.filename()),
QString::number(stream.stream()),
QString::number(params.sample_rate()),
QString::number(params.format()),
QString::number(params.channel_layout()),
QString::number(i));
filenames[i] = QDir(cache_path).filePath(index_fn);
}
return filenames;
}
bool ConformManager::AllConformsExist(const QVector<QString> &filenames)
{
foreach (const QString &fn, filenames) {
if (!QFileInfo::exists(fn)) {
return false;
}
}
return true;
}
void ConformManager::ConformTaskFinished(Task *task, bool succeeded)
{
QMutexLocker locker(&mutex_);
ConformData data;
// Remove conform data from list
for (int i=0; i<conforming_.size(); i++) {
const ConformData &c = conforming_.at(i);
if (c.task == task) {
data = c;
conforming_.removeAt(i);
break;
}
}
if (succeeded) {
// Move file to standard conform name, making it clear this conform is ready for use
for (int i=0; i<data.finished_filename.size(); i++) {
const QString &finished = data.finished_filename.at(i);
const QString &working = data.working_filename.at(i);
QFile::remove(finished);
QFile::rename(working, finished);
}
conform_done_condition_.wakeAll();
locker.unlock();
emit ConformReady();
} else {
// Failed, just delete the working filename if exists
for (int i=0; i<data.working_filename.size(); i++) {
QFile::remove(data.working_filename.at(i));
}
}
}
}
+88
View File
@@ -0,0 +1,88 @@
#ifndef CONFORMMANAGER_H
#define CONFORMMANAGER_H
#include <QMutex>
#include <QObject>
#include "decoder.h"
#include "task/conform/conform.h"
namespace olive {
class ConformManager : public QObject
{
Q_OBJECT
public:
static void CreateInstance()
{
if (!instance_) {
instance_ = new ConformManager();
}
}
static void DestroyInstance()
{
delete instance_;
instance_ = nullptr;
}
static ConformManager *instance()
{
return instance_;
}
enum ConformState {
kConformExists,
kConformGenerating
};
struct Conform {
ConformState state;
QVector<QString> filenames;
ConformTask *task;
};
/**
* @brief Get conform state, and start conforming if no conform exists
*
* Thread-safe.
*/
Conform GetConformState(const QString &decoder_id, const QString &cache_path, const Decoder::CodecStream &stream, const AudioParams &params, bool wait);
signals:
void ConformReady();
private:
ConformManager() = default;
static ConformManager *instance_;
QMutex mutex_;
QWaitCondition conform_done_condition_;
struct ConformData {
Decoder::CodecStream stream;
AudioParams params;
ConformTask *task;
QVector<QString> working_filename;
QVector<QString> finished_filename;
};
QVector<ConformData> conforming_;
/**
* @brief Get the destination filename of an audio stream conformed to a set of parameters
*/
static QVector<QString> GetConformedFilename(const QString &cache_path, const Decoder::CodecStream &stream, const AudioParams &params);
static bool AllConformsExist(const QVector<QString> &filenames);
private slots:
void ConformTaskFinished(Task *task, bool succeeded);
};
}
#endif // CONFORMMANAGER_H
+332
View File
@@ -0,0 +1,332 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#include "decoder.h"
#include <QCoreApplication>
#include <QDebug>
#include "codec/ffmpeg/ffmpegdecoder.h"
#include "codec/planarfiledevice.h"
#include "codec/oiio/oiiodecoder.h"
#include "common/ffmpegutils.h"
#include "common/filefunctions.h"
#include "common/timecodefunctions.h"
#include "conformmanager.h"
#include "node/project/project.h"
#include "task/taskmanager.h"
namespace olive {
const rational Decoder::kAnyTimecode = RATIONAL_MIN;
Decoder::Decoder()
{
UpdateLastAccessed();
}
bool Decoder::Open(const CodecStream &stream)
{
QMutexLocker locker(&mutex_);
UpdateLastAccessed();
if (stream_.IsValid()) {
// Decoder is already open. Return TRUE if the stream is the stream we have, or FALSE if not.
if (stream_ == stream) {
return true;
} else {
qWarning() << "Tried to open a decoder that was already open with another stream";
return false;
}
} else {
// Stream was not open, try opening it now
if (!stream.IsValid()) {
// Cannot open null stream
qCritical() << "Decoder attempted to open null stream";
return false;
}
if (!stream.Exists()) {
// Cannot open file that doesn't exist
qCritical() << "Decoder attempted to open file that doesn't exist";
return false;
}
// Set stream
stream_ = stream;
// Try open internal
if (OpenInternal()) {
return true;
} else {
// Unset stream
qCritical() << "Failed to open" << stream_.filename() << "stream" << stream_.stream();
CloseInternal();
stream_.Reset();
return false;
}
}
}
FramePtr Decoder::RetrieveVideo(const rational &timecode, const RetrieveVideoParams &divider, const QAtomicInt *cancelled)
{
QMutexLocker locker(&mutex_);
UpdateLastAccessed();
if (!stream_.IsValid()) {
qCritical() << "Can't retrieve video on a closed decoder";
return nullptr;
}
if (!SupportsVideo()) {
qCritical() << "Decoder doesn't support video";
return nullptr;
}
if (cancelled && *cancelled) {
return nullptr;
}
return RetrieveVideoInternal(timecode, divider, cancelled);
}
Decoder::RetrieveAudioData Decoder::RetrieveAudio(const TimeRange &range, const AudioParams &params, const QString& cache_path, Footage::LoopMode loop_mode, RenderMode::Mode mode)
{
QMutexLocker locker(&mutex_);
UpdateLastAccessed();
if (!stream_.IsValid()) {
qCritical() << "Can't retrieve audio on a closed decoder";
return {kInvalid, nullptr, nullptr};
}
if (!SupportsAudio()) {
qCritical() << "Decoder doesn't support audio";
return {kInvalid, nullptr, nullptr};
}
// Get conform state from ConformManager
ConformManager::Conform conform = ConformManager::instance()->GetConformState(id(), cache_path, stream_, params, (mode == RenderMode::kOnline));
if (conform.state == ConformManager::kConformGenerating) {
return {kWaitingForConform, nullptr, conform.task};
}
// See if we got the conform
SampleBufferPtr out_buffer = RetrieveAudioFromConform(conform.filenames, range, loop_mode, params);
return {kOK, out_buffer, nullptr};
}
qint64 Decoder::GetLastAccessedTime()
{
QMutexLocker locker(&mutex_);
return last_accessed_;
}
void Decoder::Close()
{
QMutexLocker locker(&mutex_);
UpdateLastAccessed();
if (stream_.IsValid()) {
CloseInternal();
stream_.Reset();
} else {
qWarning() << "Tried to close a decoder that wasn't open";
}
}
bool Decoder::ConformAudio(const QVector<QString> &output_filenames, const AudioParams &params, const QAtomicInt *cancelled)
{
return ConformAudioInternal(output_filenames, params, cancelled);
}
/*
* DECODER STATIC PUBLIC MEMBERS
*/
QVector<DecoderPtr> Decoder::ReceiveListOfAllDecoders()
{
QVector<DecoderPtr> decoders;
// The order in which these decoders are added is their priority when probing. Hence FFmpeg should usually be last,
// since it supports so many formats and we presumably want to override those formats with a more specific decoder.
decoders.append(std::make_shared<OIIODecoder>());
decoders.append(std::make_shared<FFmpegDecoder>());
return decoders;
}
DecoderPtr Decoder::CreateFromID(const QString &id)
{
if (id.isEmpty()) {
return nullptr;
}
// Create list to iterate through
QVector<DecoderPtr> decoder_list = ReceiveListOfAllDecoders();
foreach (DecoderPtr d, decoder_list) {
if (d->id() == id) {
return d;
}
}
return nullptr;
}
int64_t Decoder::GetTimeInTimebaseUnits(const rational &time, const rational &timebase, int64_t start_time)
{
return Timecode::time_to_timestamp(time, timebase) + start_time;
}
void Decoder::SignalProcessingProgress(int64_t ts, int64_t duration)
{
if (duration != AV_NOPTS_VALUE && duration != 0) {
emit IndexProgress(static_cast<double>(ts) / static_cast<double>(duration));
}
}
QString Decoder::TransformImageSequenceFileName(const QString &filename, const int64_t& number)
{
int digit_count = GetImageSequenceDigitCount(filename);
QFileInfo file_info(filename);
QString original_basename = file_info.completeBaseName();
QString new_basename = original_basename.left(original_basename.size() - digit_count)
.append(QStringLiteral("%1").arg(number, digit_count, 10, QChar('0')));
return file_info.dir().filePath(file_info.fileName().replace(original_basename, new_basename));
}
int Decoder::GetImageSequenceDigitCount(const QString &filename)
{
QString basename = QFileInfo(filename).completeBaseName();
// See if basename contains a number at the end
int digit_count = 0;
for (int i=basename.size()-1;i>=0;i--) {
if (basename.at(i).isDigit()) {
digit_count++;
} else {
break;
}
}
return digit_count;
}
int64_t Decoder::GetImageSequenceIndex(const QString &filename)
{
int digit_count = GetImageSequenceDigitCount(filename);
QFileInfo file_info(filename);
QString original_basename = file_info.completeBaseName();
QString number_only = original_basename.mid(original_basename.size() - digit_count);
return number_only.toLongLong();
}
FramePtr Decoder::RetrieveVideoInternal(const rational &timecode, const RetrieveVideoParams &divider, const QAtomicInt *cancelled)
{
Q_UNUSED(timecode)
Q_UNUSED(divider)
Q_UNUSED(cancelled)
return nullptr;
}
bool Decoder::ConformAudioInternal(const QVector<QString> &filenames, const AudioParams &params, const QAtomicInt* cancelled)
{
Q_UNUSED(filenames)
Q_UNUSED(cancelled)
Q_UNUSED(params)
return false;
}
SampleBufferPtr Decoder::RetrieveAudioFromConform(const QVector<QString> &conform_filenames, const TimeRange& range, Footage::LoopMode loop_mode, const AudioParams &input_params)
{
PlanarFileDevice input;
if (input.open(conform_filenames, QFile::ReadOnly)) {
SampleBufferPtr sample_buffer = SampleBuffer::CreateAllocated(input_params, range.length());
qint64 read_index = input_params.time_to_bytes(range.in()) / input_params.channel_count();
qint64 write_index = 0;
const qint64 buffer_length_in_bytes = sample_buffer->sample_count() * input_params.bytes_per_sample_per_channel();
while (write_index < buffer_length_in_bytes) {
if (loop_mode == Footage::kLoopModeLoop) {
while (read_index >= input.size()) {
read_index -= input.size();
}
while (read_index < 0) {
read_index += input.size();
}
}
qint64 write_count = 0;
if (read_index < 0) {
// Reading before 0, write silence here until audio data would actually start
write_count = qMin(-read_index, buffer_length_in_bytes);
sample_buffer->silence_bytes(write_index, write_index + write_count);
} else if (read_index >= input.size()) {
// Reading after data length, write silence until the end of the buffer
write_count = buffer_length_in_bytes - write_index;
sample_buffer->silence_bytes(write_index, write_index + write_count);
} else {
write_count = qMin(input.size() - read_index, buffer_length_in_bytes - write_index);
input.seek(read_index);
input.read(reinterpret_cast<char**>(sample_buffer->to_raw_ptrs()), write_count, write_index);
}
read_index += write_count;
write_index += write_count;
}
input.close();
return sample_buffer;
}
return nullptr;
}
void Decoder::UpdateLastAccessed()
{
last_accessed_ = QDateTime::currentMSecsSinceEpoch();
}
uint qHash(Decoder::CodecStream stream, uint seed)
{
return qHash(stream.filename(), seed) ^ qHash(stream.stream(), seed);
}
}
+331
View File
@@ -0,0 +1,331 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#ifndef DECODER_H
#define DECODER_H
extern "C" {
#include <libswresample/swresample.h>
}
#include <QFileInfo>
#include <QMutex>
#include <QObject>
#include <QWaitCondition>
#include <stdint.h>
#include "codec/frame.h"
#include "codec/samplebuffer.h"
#include "common/rational.h"
#include "node/project/footage/footage.h"
#include "node/project/footage/footagedescription.h"
#include "task/task.h"
namespace olive {
class Decoder;
using DecoderPtr = std::shared_ptr<Decoder>;
#define DECODER_DEFAULT_DESTRUCTOR(x) virtual ~x() override {CloseInternal();}
/**
* @brief A decoder's is the main class for bringing external media into Olive
*
* Its responsibilities are to serve as
* abstraction from codecs/decoders and provide complete frames. These frames can be video or audio data and are
* provided as Frame objects in shared pointers to alleviate the responsibility of memory handling.
*
* The main function in a decoder is Retrieve() which should return complete image/audio data. A decoder should
* alleviate all the complexities of codec compression from the rest of the application (i.e. a decoder should never
* return a partial frame or require other parts of the system to interface directly with the codec). Often this will
* necessitate pre-emptively caching, indexing, or even fully transcoding media before using it which can be implemented
* through the Analyze() function.
*
* A decoder does NOT perform any pixel/sample format conversion. Frames should pass through the PixelService
* to be utilized in the rest of the rendering pipeline.
*/
class Decoder : public QObject
{
Q_OBJECT
public:
enum RetrieveState {
kReady,
kFailedToOpen,
kIndexUnavailable
};
Decoder();
/**
* @brief Unique decoder ID
*/
virtual QString id() const = 0;
virtual bool SupportsVideo(){return false;}
virtual bool SupportsAudio(){return false;}
class CodecStream
{
public:
CodecStream() :
stream_(-1)
{
}
CodecStream(const QString& filename, int stream) :
filename_(filename),
stream_(stream)
{
}
bool IsValid() const
{
return !filename_.isEmpty() && stream_ >= 0;
}
bool Exists() const
{
return QFileInfo::exists(filename_);
}
void Reset()
{
*this = CodecStream();
}
bool operator==(const CodecStream& rhs) const
{
return filename_ == rhs.filename_ && stream_ == rhs.stream_;
}
const QString& filename() const
{
return filename_;
}
int stream() const
{
return stream_;
}
private:
QString filename_;
int stream_;
};
/**
* @brief Open stream for decoding
*
* This function is thread safe.
*
* Returns TRUE if stream could be opened successfully. Also returns TRUE if the decoder is
* already open and the stream == the stream provided. Returns FALSE if the stream couldn't
* be opened OR if already open and the stream is NOT the same.
*/
bool Open(const CodecStream& stream);
static const rational kAnyTimecode;
struct RetrieveVideoParams
{
RetrieveVideoParams()
{
divider = 1;
src_interlacing = VideoParams::kInterlaceNone;
dst_interlacing = VideoParams::kInterlaceNone;
}
int divider;
VideoParams::Interlacing src_interlacing;
VideoParams::Interlacing dst_interlacing;
void reset()
{
*this = RetrieveVideoParams();
}
bool operator==(const RetrieveVideoParams& rhs) const
{
return divider == rhs.divider && src_interlacing == rhs.src_interlacing && dst_interlacing == rhs.dst_interlacing;
}
bool operator!=(const RetrieveVideoParams& rhs) const
{
return !(*this == rhs);
}
};
/**
* @brief Retrieves a video frame from footage
*
* This function will always return a valid frame unless a fatal error occurs (in such case,
* nullptr will return). If the timecode is before the start of the footage, this function should
* return the first frame. Likewise, if it is after the timecode, this function should return the
* last frame.
*
* This function is thread safe and can only run while the decoder is open. \see Open()
*/
FramePtr RetrieveVideo(const rational& timecode, const RetrieveVideoParams& divider, const QAtomicInt *cancelled = nullptr);
enum RetrieveAudioStatus {
kInvalid = -1,
kOK,
kWaitingForConform
};
struct RetrieveAudioData {
RetrieveAudioStatus status;
SampleBufferPtr samples;
Task *task;
};
/**
* @brief Retrieve audio data from footage
*
* This function will always return a sample buffer unless a fatal error occurs (in such case,
* nullptr will return). The SampleBuffer should always have enough audio for the range provided.
*
* This function is thread safe and can only run while the decoder is open. \see Open()
*/
RetrieveAudioData RetrieveAudio(const TimeRange& range, const AudioParams& params, const QString &cache_path, Footage::LoopMode loop_mode, RenderMode::Mode mode);
/**
* @brief Determine the last time this decoder instance was used in any way
*/
qint64 GetLastAccessedTime();
/**
* @brief Generate a Footage object from a file
*
* If this decoder is able to parse this file, it will return a valid FootagePtr. Otherwise, it
* will return nullptr.
*
* For sub-classes, this function should be effectively static. We can't do virtual static
* functions in C++, but it should hold and access no state during its run.
*
* This function is re-entrant.
*/
virtual FootageDescription Probe(const QString& filename, const QAtomicInt* cancelled) const = 0;
/**
* @brief Closes media/deallocates memory
*
* This function is thread safe and can only run while the decoder is open. \see Open()
*/
void Close();
/**
* @brief Conform audio stream
*/
bool ConformAudio(const QVector<QString> &output_filenames, const AudioParams &params, const QAtomicInt *cancelled = nullptr);
/**
* @brief Create a Decoder instance using a Decoder ID
*
* @return
*
* A Decoder instance or nullptr if a Decoder with this ID does not exist
*/
static DecoderPtr CreateFromID(const QString& id);
static QString TransformImageSequenceFileName(const QString& filename, const int64_t& number);
static int GetImageSequenceDigitCount(const QString& filename);
static int64_t GetImageSequenceIndex(const QString& filename);
static QVector<DecoderPtr> ReceiveListOfAllDecoders();
protected:
/**
* @brief Internal open function
*
* Sub-classes must override this function. Function will already be mutexed, so there is no need
* to worry about thread safety. Also many other sanity checks will be done before this, so
* sub-classes only need to worry about their own opening functions. It is guaranteed that the
* decoder is not open yet and that the footage stream was from that sub-classes probe function.
*
* Return TRUE if everything opened successfully and the decoder is ready to work. Otherwise,
* return FALSE. If this function returns false, Decoder will call CloseInternal to clean any
* memory allocated during OpenInternal.
*/
virtual bool OpenInternal() = 0;
/**
* @brief Internal close function
*
* Sub-classes must override this function. Function should be able to safely clear all allocated
* memory. It may be called even if Open() didn't complete or RetrieveVideo() was never called.
*/
virtual void CloseInternal() = 0;
/**
* @brief Internal frame retrieval function
*
* Sub-classes must override this function IF they support video. Function is already mutexed
* so sub-classes don't need to worry about thread safety.
*/
virtual FramePtr RetrieveVideoInternal(const rational& timecode, const RetrieveVideoParams& divider, const QAtomicInt *cancelled);
virtual bool ConformAudioInternal(const QVector<QString>& filenames, const AudioParams &params, const QAtomicInt* cancelled);
void SignalProcessingProgress(int64_t ts, int64_t duration);
/**
* @brief Return currently open stream
*
* This function is NOT thread safe and should therefore only be called by thread safe functions.
*/
const CodecStream& stream() const
{
return stream_;
}
static int64_t GetTimeInTimebaseUnits(const rational& time, const rational& timebase, int64_t start_time);
signals:
/**
* @brief While indexing, this signal will provide progress as a percentage (0-100 inclusive) if
* available
*/
void IndexProgress(double);
private:
void UpdateLastAccessed();
SampleBufferPtr RetrieveAudioFromConform(const QVector<QString> &conform_filenames, const TimeRange &range, Footage::LoopMode loop_mode, const AudioParams &params);
CodecStream stream_;
QMutex mutex_;
qint64 last_accessed_;
};
uint qHash(Decoder::CodecStream stream, uint seed = 0);
}
Q_DECLARE_METATYPE(olive::Decoder::RetrieveState)
#endif // DECODER_H
+353
View File
@@ -0,0 +1,353 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#include "encoder.h"
#include <QFile>
#include "common/timecodefunctions.h"
#include "ffmpeg/ffmpegencoder.h"
#include "oiio/oiioencoder.h"
namespace olive {
const QRegularExpression Encoder::kImageSequenceContainsDigits = QRegularExpression(QStringLiteral("\\[[#]+\\]"));
const QRegularExpression Encoder::kImageSequenceRemoveDigits = QRegularExpression(QStringLiteral("[\\-\\.\\ \\_]?\\[[#]+\\]"));
Encoder::Encoder(const EncodingParams &params) :
params_(params)
{
}
const EncodingParams &Encoder::params() const
{
return params_;
}
QString Encoder::GetFilenameForFrame(const rational &frame)
{
if (params().video_is_image_sequence()) {
// Transform!
int64_t frame_index = Timecode::time_to_timestamp(frame, params().video_params().frame_rate_as_time_base());
int digits = GetImageSequencePlaceholderDigitCount(params().filename());
QString frame_index_str = QStringLiteral("%1").arg(frame_index, digits, 10, QChar('0'));
QString f = params_.filename();
f.replace(kImageSequenceContainsDigits, frame_index_str);
return f;
} else {
// Keep filename
return params_.filename();
}
}
int Encoder::GetImageSequencePlaceholderDigitCount(const QString &filename)
{
int start = filename.indexOf(kImageSequenceContainsDigits);
int digit_count = 0;
for (int i=start+1; i<filename.size(); i++) {
if (filename.at(i) == '#') {
digit_count++;
} else {
break;
}
}
return digit_count;
}
bool Encoder::FilenameContainsDigitPlaceholder(const QString& filename)
{
return filename.contains(kImageSequenceContainsDigits);
}
QString Encoder::FilenameRemoveDigitPlaceholder(QString filename)
{
return filename.remove(kImageSequenceRemoveDigits);
}
EncodingParams::EncodingParams() :
video_enabled_(false),
video_bit_rate_(0),
video_min_bit_rate_(0),
video_max_bit_rate_(0),
video_buffer_size_(0),
video_threads_(0),
video_is_image_sequence_(false),
audio_enabled_(false),
audio_bit_rate_(0),
subtitles_enabled_(false)
{
}
void EncodingParams::SetFilename(const QString &filename)
{
filename_ = filename;
}
void EncodingParams::EnableVideo(const VideoParams &video_params, const ExportCodec::Codec &vcodec)
{
video_enabled_ = true;
video_params_ = video_params;
video_codec_ = vcodec;
}
void EncodingParams::EnableAudio(const AudioParams &audio_params, const ExportCodec::Codec &acodec)
{
audio_enabled_ = true;
audio_params_ = audio_params;
audio_codec_ = acodec;
}
void EncodingParams::EnableSubtitles(const ExportCodec::Codec &scodec)
{
subtitles_enabled_ = true;
subtitles_codec_ = scodec;
}
void EncodingParams::set_video_option(const QString &key, const QString &value)
{
video_opts_.insert(key, value);
}
void EncodingParams::set_video_bit_rate(const int64_t &rate)
{
video_bit_rate_ = rate;
}
void EncodingParams::set_video_min_bit_rate(const int64_t &rate)
{
video_min_bit_rate_ = rate;
}
void EncodingParams::set_video_max_bit_rate(const int64_t &rate)
{
video_max_bit_rate_ = rate;
}
void EncodingParams::set_video_buffer_size(const int64_t &sz)
{
video_buffer_size_ = sz;
}
void EncodingParams::set_video_threads(const int &threads)
{
video_threads_ = threads;
}
void EncodingParams::set_video_pix_fmt(const QString &s)
{
video_pix_fmt_ = s;
}
const QString &EncodingParams::filename() const
{
return filename_;
}
bool EncodingParams::video_enabled() const
{
return video_enabled_;
}
const ExportCodec::Codec &EncodingParams::video_codec() const
{
return video_codec_;
}
const VideoParams &EncodingParams::video_params() const
{
return video_params_;
}
const QHash<QString, QString> &EncodingParams::video_opts() const
{
return video_opts_;
}
const int64_t &EncodingParams::video_bit_rate() const
{
return video_bit_rate_;
}
const int64_t &EncodingParams::video_min_bit_rate() const
{
return video_min_bit_rate_;
}
const int64_t &EncodingParams::video_max_bit_rate() const
{
return video_max_bit_rate_;
}
const int64_t &EncodingParams::video_buffer_size() const
{
return video_buffer_size_;
}
const int &EncodingParams::video_threads() const
{
return video_threads_;
}
const QString &EncodingParams::video_pix_fmt() const
{
return video_pix_fmt_;
}
bool EncodingParams::audio_enabled() const
{
return audio_enabled_;
}
const ExportCodec::Codec &EncodingParams::audio_codec() const
{
return audio_codec_;
}
const AudioParams &EncodingParams::audio_params() const
{
return audio_params_;
}
bool EncodingParams::subtitles_enabled() const
{
return subtitles_enabled_;
}
ExportCodec::Codec EncodingParams::subtitles_codec() const
{
return subtitles_codec_;
}
const rational &EncodingParams::GetExportLength() const
{
return export_length_;
}
void EncodingParams::SetExportLength(const rational &export_length)
{
export_length_ = export_length;
}
void EncodingParams::Save(QXmlStreamWriter *writer) const
{
writer->writeTextElement(QStringLiteral("filename"), filename_);
writer->writeStartElement(QStringLiteral("video"));
writer->writeAttribute(QStringLiteral("enabled"), QString::number(video_enabled_));
if (video_enabled_) {
writer->writeTextElement(QStringLiteral("codec"), QString::number(video_codec_));
writer->writeTextElement(QStringLiteral("width"), QString::number(video_params_.width()));
writer->writeTextElement(QStringLiteral("height"), QString::number(video_params_.height()));
writer->writeTextElement(QStringLiteral("format"), QString::number(video_params_.format()));
writer->writeTextElement(QStringLiteral("timebase"), video_params_.time_base().toString());
writer->writeTextElement(QStringLiteral("divider"), QString::number(video_params_.divider()));
writer->writeTextElement(QStringLiteral("bitrate"), QString::number(video_bit_rate_));
writer->writeTextElement(QStringLiteral("minbitrate"), QString::number(video_max_bit_rate_));
writer->writeTextElement(QStringLiteral("maxbitrate"), QString::number(video_max_bit_rate_));
writer->writeTextElement(QStringLiteral("bufsize"), QString::number(video_buffer_size_));
writer->writeTextElement(QStringLiteral("threads"), QString::number(video_threads_));
if (!video_opts_.isEmpty()) {
writer->writeStartElement(QStringLiteral("opts"));
QHash<QString, QString>::const_iterator i;
for (i=video_opts_.constBegin(); i!=video_opts_.constEnd(); i++) {
writer->writeStartElement(QStringLiteral("entry"));
writer->writeTextElement(QStringLiteral("key"), i.key());
writer->writeTextElement(QStringLiteral("value"), i.value());
writer->writeEndElement(); // entry
}
writer->writeEndElement(); // opts
}
}
writer->writeEndElement(); // video
writer->writeStartElement(QStringLiteral("audio"));
writer->writeAttribute(QStringLiteral("enabled"), QString::number(audio_enabled_));
if (audio_enabled_) {
writer->writeTextElement(QStringLiteral("codec"), QString::number(audio_codec_));
writer->writeTextElement(QStringLiteral("samplerate"), QString::number(audio_params_.sample_rate()));
writer->writeTextElement(QStringLiteral("channellayout"), QString::number(audio_params_.channel_layout()));
writer->writeTextElement(QStringLiteral("format"), QString::number(audio_params_.format()));
}
writer->writeEndElement(); // audio
}
Encoder* Encoder::CreateFromID(Type id, const EncodingParams& params)
{
switch (id) {
case kEncoderTypeNone:
break;
case kEncoderTypeFFmpeg:
return new FFmpegEncoder(params);
case kEncoderTypeOIIO:
return new OIIOEncoder(params);
}
return nullptr;
}
Encoder::Type Encoder::GetTypeFromFormat(ExportFormat::Format f)
{
switch (f) {
case ExportFormat::kFormatDNxHD:
case ExportFormat::kFormatMatroska:
case ExportFormat::kFormatQuickTime:
case ExportFormat::kFormatMPEG4:
case ExportFormat::kFormatWAV:
case ExportFormat::kFormatAIFF:
case ExportFormat::kFormatMP3:
case ExportFormat::kFormatFLAC:
case ExportFormat::kFormatOgg:
case ExportFormat::kFormatWebM:
case ExportFormat::kFormatSRT:
return kEncoderTypeFFmpeg;
case ExportFormat::kFormatOpenEXR:
case ExportFormat::kFormatPNG:
case ExportFormat::kFormatTIFF:
return kEncoderTypeOIIO;
case ExportFormat::kFormatCount:
break;
}
return kEncoderTypeNone;
}
Encoder *Encoder::CreateFromFormat(ExportFormat::Format f, const EncodingParams &params)
{
return CreateFromID(GetTypeFromFormat(f), params);
}
QStringList Encoder::GetPixelFormatsForCodec(ExportCodec::Codec c) const
{
return QStringList();
}
}
+205
View File
@@ -0,0 +1,205 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#ifndef ENCODER_H
#define ENCODER_H
#include <memory>
#include <QRegularExpression>
#include <QString>
#include <QXmlStreamWriter>
#include "codec/exportcodec.h"
#include "codec/exportformat.h"
#include "codec/frame.h"
#include "codec/samplebuffer.h"
#include "common/timerange.h"
#include "node/block/subtitle/subtitle.h"
#include "render/audioparams.h"
#include "render/subtitleparams.h"
#include "render/videoparams.h"
namespace olive {
class Encoder;
using EncoderPtr = std::shared_ptr<Encoder>;
class EncodingParams {
public:
EncodingParams();
void SetFilename(const QString& filename);
void EnableVideo(const VideoParams& video_params, const ExportCodec::Codec& vcodec);
void EnableAudio(const AudioParams& audio_params, const ExportCodec::Codec &acodec);
void EnableSubtitles(const ExportCodec::Codec &scodec);
void set_video_option(const QString& key, const QString& value);
void set_video_bit_rate(const int64_t& rate);
void set_video_min_bit_rate(const int64_t& rate);
void set_video_max_bit_rate(const int64_t& rate);
void set_video_buffer_size(const int64_t& sz);
void set_video_threads(const int& threads);
void set_video_pix_fmt(const QString& s);
void set_video_is_image_sequence(bool s)
{
video_is_image_sequence_ = s;
}
const QString& filename() const;
bool video_enabled() const;
const ExportCodec::Codec& video_codec() const;
const VideoParams& video_params() const;
const QHash<QString, QString>& video_opts() const;
const int64_t& video_bit_rate() const;
const int64_t& video_min_bit_rate() const;
const int64_t& video_max_bit_rate() const;
const int64_t& video_buffer_size() const;
const int& video_threads() const;
const QString& video_pix_fmt() const;
bool video_is_image_sequence() const
{
return video_is_image_sequence_;
}
bool audio_enabled() const;
const ExportCodec::Codec &audio_codec() const;
const AudioParams& audio_params() const;
const int64_t& audio_bit_rate() const
{
return audio_bit_rate_;
}
void set_audio_bit_rate(const int64_t& b)
{
audio_bit_rate_ = b;
}
bool subtitles_enabled() const;
ExportCodec::Codec subtitles_codec() const;
const rational& GetExportLength() const;
void SetExportLength(const rational& GetExportLength);
virtual void Save(QXmlStreamWriter* writer) const;
private:
QString filename_;
bool video_enabled_;
ExportCodec::Codec video_codec_;
VideoParams video_params_;
QHash<QString, QString> video_opts_;
int64_t video_bit_rate_;
int64_t video_min_bit_rate_;
int64_t video_max_bit_rate_;
int64_t video_buffer_size_;
int video_threads_;
QString video_pix_fmt_;
bool video_is_image_sequence_;
bool audio_enabled_;
ExportCodec::Codec audio_codec_;
AudioParams audio_params_;
int64_t audio_bit_rate_;
bool subtitles_enabled_;
ExportCodec::Codec subtitles_codec_;
rational export_length_;
};
class Encoder : public QObject
{
Q_OBJECT
public:
Encoder(const EncodingParams& params);
enum Type {
kEncoderTypeNone = -1,
kEncoderTypeFFmpeg,
kEncoderTypeOIIO
};
/**
* @brief Create a Encoder instance using a Encoder ID
*
* @return
*
* A Encoder instance or nullptr if a Decoder with this ID does not exist
*/
static Encoder *CreateFromID(Type id, const EncodingParams &params);
static Type GetTypeFromFormat(ExportFormat::Format f);
static Encoder *CreateFromFormat(ExportFormat::Format f, const EncodingParams &params);
virtual QStringList GetPixelFormatsForCodec(ExportCodec::Codec c) const;
const EncodingParams& params() const;
virtual VideoParams::Format GetDesiredPixelFormat() const
{
return VideoParams::kFormatInvalid;
}
const QString& GetError() const
{
return error_;
}
QString GetFilenameForFrame(const rational& frame);
static int GetImageSequencePlaceholderDigitCount(const QString& filename);
static bool FilenameContainsDigitPlaceholder(const QString &filename);
static QString FilenameRemoveDigitPlaceholder(QString filename);
static const QRegularExpression kImageSequenceContainsDigits;
static const QRegularExpression kImageSequenceRemoveDigits;
public slots:
virtual bool Open() = 0;
virtual bool WriteFrame(olive::FramePtr frame, olive::rational time) = 0;
virtual bool WriteAudio(olive::SampleBufferPtr audio) = 0;
virtual bool WriteSubtitle(const SubtitleBlock *sub_block) = 0;
virtual void Close() = 0;
protected:
void SetError(const QString& err)
{
error_ = err;
}
private:
EncodingParams params_;
QString error_;
};
}
#endif // ENCODER_H
+106
View File
@@ -0,0 +1,106 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#include "exportcodec.h"
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavutil/pixdesc.h>
}
namespace olive {
QString ExportCodec::GetCodecName(ExportCodec::Codec c)
{
switch (c) {
case kCodecDNxHD:
return tr("DNxHD");
case kCodecH264:
return tr("H.264");
case kCodecH264rgb:
return tr("H.264 RGB");
case kCodecH265:
return tr("H.265");
case kCodecOpenEXR:
return tr("OpenEXR");
case kCodecPNG:
return tr("PNG");
case kCodecProRes:
return tr("ProRes");
case kCodecCineform:
return tr("Cineform");
case kCodecTIFF:
return tr("TIFF");
case kCodecMP2:
return tr("MP2");
case kCodecMP3:
return tr("MP3");
case kCodecAAC:
return tr("AAC");
case kCodecPCM:
return tr("PCM (Uncompressed)");
case kCodecFLAC:
return tr("FLAC");
case kCodecOpus:
return tr("Opus");
case kCodecVorbis:
return tr("Vorbis");
case kCodecVP9:
return tr("VP9");
case kCodecSRT:
return tr("SubRip SRT");
case kCodecCount:
break;
}
return tr("Unknown");
}
bool ExportCodec::IsCodecAStillImage(ExportCodec::Codec c)
{
switch (c) {
case kCodecDNxHD:
case kCodecH264:
case kCodecH264rgb:
case kCodecH265:
case kCodecProRes:
case kCodecCineform:
case kCodecMP2:
case kCodecMP3:
case kCodecAAC:
case kCodecPCM:
case kCodecVorbis:
case kCodecOpus:
case kCodecFLAC:
case kCodecVP9:
case kCodecSRT:
return false;
case kCodecOpenEXR:
case kCodecPNG:
case kCodecTIFF:
return true;
case kCodecCount:
break;
}
return false;
}
}
+72
View File
@@ -0,0 +1,72 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#ifndef EXPORTCODEC_H
#define EXPORTCODEC_H
#include <QObject>
#include <QString>
#include "common/define.h"
#include "render/subtitleparams.h"
namespace olive {
class ExportCodec : public QObject
{
Q_OBJECT
public:
enum Codec {
// Video codecs
kCodecDNxHD,
kCodecH264,
kCodecH264rgb,
kCodecH265,
kCodecOpenEXR,
kCodecPNG,
kCodecProRes,
kCodecCineform,
kCodecTIFF,
kCodecVP9,
// Audio codecs
kCodecMP2,
kCodecMP3,
kCodecAAC,
kCodecPCM,
kCodecOpus,
kCodecVorbis,
kCodecFLAC,
// Subtitle codecs
kCodecSRT,
kCodecCount
};
static QString GetCodecName(Codec c);
static bool IsCodecAStillImage(Codec c);
};
}
#endif // EXPORTCODEC_H
+214
View File
@@ -0,0 +1,214 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#include "exportformat.h"
#include "encoder.h"
namespace olive {
QString ExportFormat::GetName(olive::ExportFormat::Format f)
{
switch (f) {
case kFormatDNxHD:
return tr("DNxHD");
case kFormatMatroska:
return tr("Matroska Video");
case kFormatMPEG4:
return tr("MPEG-4 Video");
case kFormatOpenEXR:
return tr("OpenEXR");
case kFormatPNG:
return tr("PNG");
case kFormatTIFF:
return tr("TIFF");
case kFormatQuickTime:
return tr("QuickTime");
case kFormatWAV:
return tr("Wave Audio");
case kFormatAIFF:
return tr("AIFF");
case kFormatMP3:
return tr("MP3");
case kFormatFLAC:
return tr("FLAC");
case kFormatOgg:
return tr("Ogg");
case kFormatWebM:
return tr("WebM");
case kFormatSRT:
return tr("SubRip SRT");
case kFormatCount:
break;
}
return tr("Unknown");
}
QString ExportFormat::GetExtension(ExportFormat::Format f)
{
switch (f) {
case kFormatDNxHD:
return QStringLiteral("mxf");
case kFormatMatroska:
return QStringLiteral("mkv");
case kFormatMPEG4:
return QStringLiteral("mp4");
case kFormatOpenEXR:
return QStringLiteral("exr");
case kFormatPNG:
return QStringLiteral("png");
case kFormatTIFF:
return QStringLiteral("tiff");
case kFormatQuickTime:
return QStringLiteral("mov");
case kFormatWAV:
return QStringLiteral("wav");
case kFormatAIFF:
return QStringLiteral("aiff");
case kFormatMP3:
return QStringLiteral("mp3");
case kFormatFLAC:
return QStringLiteral("flac");
case kFormatOgg:
return QStringLiteral("ogg");
case kFormatWebM:
return QStringLiteral("webm");
case kFormatSRT:
return QStringLiteral("srt");
case kFormatCount:
break;
}
return QString();
}
QList<ExportCodec::Codec> ExportFormat::GetVideoCodecs(ExportFormat::Format f)
{
switch (f) {
case kFormatDNxHD:
return {ExportCodec::kCodecDNxHD};
case kFormatMatroska:
return {ExportCodec::kCodecH264, ExportCodec::kCodecH264rgb, ExportCodec::kCodecH265, ExportCodec::kCodecVP9};
case kFormatMPEG4:
return {ExportCodec::kCodecH264, ExportCodec::kCodecH264rgb, ExportCodec::kCodecH265};
case kFormatOpenEXR:
return {ExportCodec::kCodecOpenEXR};
case kFormatPNG:
return {ExportCodec::kCodecPNG};
case kFormatTIFF:
return {ExportCodec::kCodecTIFF};
case kFormatQuickTime:
return {ExportCodec::kCodecH264, ExportCodec::kCodecH264rgb, ExportCodec::kCodecH265, ExportCodec::kCodecProRes, ExportCodec::kCodecCineform};
case kFormatWebM:
return {ExportCodec::kCodecVP9};
case kFormatOgg:
case kFormatWAV:
case kFormatAIFF:
case kFormatMP3:
case kFormatFLAC:
case kFormatSRT:
case kFormatCount:
break;
}
return {};
}
QList<ExportCodec::Codec> ExportFormat::GetAudioCodecs(ExportFormat::Format f)
{
switch (f) {
// Video/audio formats
case kFormatDNxHD:
return {ExportCodec::kCodecPCM};
case kFormatMatroska:
return {ExportCodec::kCodecAAC, ExportCodec::kCodecMP2, ExportCodec::kCodecMP3, ExportCodec::kCodecPCM, ExportCodec::kCodecVorbis, ExportCodec::kCodecOpus, ExportCodec::kCodecFLAC};
case kFormatMPEG4:
return {ExportCodec::kCodecAAC, ExportCodec::kCodecMP2, ExportCodec::kCodecMP3};
case kFormatQuickTime:
return {ExportCodec::kCodecAAC, ExportCodec::kCodecMP2, ExportCodec::kCodecMP3, ExportCodec::kCodecPCM};
case kFormatWebM:
return {ExportCodec::kCodecAAC, ExportCodec::kCodecMP2, ExportCodec::kCodecMP3, ExportCodec::kCodecPCM, ExportCodec::kCodecVorbis, ExportCodec::kCodecOpus};
// Audio only formats
case kFormatWAV:
return {ExportCodec::kCodecPCM};
case kFormatAIFF:
return {ExportCodec::kCodecPCM};
case kFormatMP3:
return {ExportCodec::kCodecMP3};
case kFormatFLAC:
return {ExportCodec::kCodecFLAC};
case kFormatOgg:
return {ExportCodec::kCodecOpus, ExportCodec::kCodecVorbis, ExportCodec::kCodecPCM};
// Video only formats
case kFormatOpenEXR:
case kFormatPNG:
case kFormatTIFF:
case kFormatSRT:
case kFormatCount:
break;
}
return {};
}
QList<ExportCodec::Codec> ExportFormat::GetSubtitleCodecs(Format f)
{
switch (f) {
case kFormatDNxHD:
case kFormatMPEG4:
case kFormatOpenEXR:
case kFormatQuickTime:
case kFormatPNG:
case kFormatTIFF:
case kFormatWAV:
case kFormatAIFF:
case kFormatMP3:
case kFormatFLAC:
case kFormatOgg:
case kFormatWebM:
case kFormatCount:
break;
case kFormatMatroska:
case kFormatSRT:
return {ExportCodec::kCodecSRT};
}
return {};
}
QStringList ExportFormat::GetPixelFormatsForCodec(ExportFormat::Format f, ExportCodec::Codec c)
{
Encoder* e = Encoder::CreateFromFormat(f, EncodingParams());
QStringList list;
if (e) {
list = e->GetPixelFormatsForCodec(c);
delete e;
}
return list;
}
}
+67
View File
@@ -0,0 +1,67 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#ifndef EXPORTFORMAT_H
#define EXPORTFORMAT_H
#include <QList>
#include <QString>
#include "common/define.h"
#include "exportcodec.h"
namespace olive {
class ExportFormat : public QObject
{
Q_OBJECT
public:
enum Format {
kFormatDNxHD,
kFormatMatroska,
kFormatMPEG4,
kFormatOpenEXR,
kFormatQuickTime,
kFormatPNG,
kFormatTIFF,
kFormatWAV,
kFormatAIFF,
kFormatMP3,
kFormatFLAC,
kFormatOgg,
kFormatWebM,
kFormatSRT,
kFormatCount
};
static QString GetName(Format f);
static QString GetExtension(Format f);
static QList<ExportCodec::Codec> GetVideoCodecs(ExportFormat::Format f);
static QList<ExportCodec::Codec> GetAudioCodecs(ExportFormat::Format f);
static QList<ExportCodec::Codec> GetSubtitleCodecs(ExportFormat::Format f);
static QStringList GetPixelFormatsForCodec(Format f, ExportCodec::Codec c);
};
}
#endif // EXPORTFORMAT_H
+26
View File
@@ -0,0 +1,26 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2021 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}
codec/ffmpeg/ffmpegdecoder.h
codec/ffmpeg/ffmpegdecoder.cpp
codec/ffmpeg/ffmpegencoder.h
codec/ffmpeg/ffmpegencoder.cpp
codec/ffmpeg/ffmpegframepool.h
codec/ffmpeg/ffmpegframepool.cpp
PARENT_SCOPE
)
File diff suppressed because it is too large Load Diff
+170
View File
@@ -0,0 +1,170 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#ifndef FFMPEGDECODER_H
#define FFMPEGDECODER_H
// Fixes weird define issue when including <avfilter.h>
#include <inttypes.h>
extern "C" {
#include <libavfilter/avfilter.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
}
#include <QAtomicInt>
#include <QTimer>
#include <QVector>
#include <QWaitCondition>
#include "codec/decoder.h"
#include "ffmpegframepool.h"
namespace olive {
/**
* @brief A Decoder derivative that wraps FFmpeg functions as on Olive decoder
*/
class FFmpegDecoder : public Decoder
{
Q_OBJECT
public:
// Constructor
FFmpegDecoder();
// Destructor
DECODER_DEFAULT_DESTRUCTOR(FFmpegDecoder)
virtual QString id() const override;
virtual bool SupportsVideo() override{return true;}
virtual bool SupportsAudio() override{return true;}
virtual FootageDescription Probe(const QString &filename, const QAtomicInt *cancelled) const override;
protected:
virtual bool OpenInternal() override;
virtual FramePtr RetrieveVideoInternal(const rational &timecode, const RetrieveVideoParams& params, const QAtomicInt *cancelled) override;
virtual bool ConformAudioInternal(const QVector<QString>& filenames, const AudioParams &params, const QAtomicInt* cancelled) override;
virtual void CloseInternal() override;
private:
class Instance
{
public:
Instance();
~Instance()
{
Close();
}
bool Open(const char* filename, int stream_index);
void Close();
/**
* @brief Uses the FFmpeg API to retrieve a packet (stored in pkt_) and decode it (stored in frame_)
*
* @return
*
* An FFmpeg error code, or >= 0 on success
*/
int GetFrame(AVPacket* pkt, AVFrame* frame);
void Seek(int64_t timestamp);
AVFormatContext* fmt_ctx() const
{
return fmt_ctx_;
}
AVStream* avstream() const
{
return avstream_;
}
private:
AVFormatContext* fmt_ctx_;
AVCodecContext* codec_ctx_;
AVStream* avstream_;
AVDictionary* opts_;
};
int GetFilteredFrame(AVPacket *packet, AVFrame *frame);
/**
* @brief Handle an FFmpeg error code
*
* Uses the FFmpeg API to retrieve a descriptive string for this error code and sends it to Error(). As such, this
* function also automatically closes the Decoder.
*
* @param error_code
*/
static QString FFmpegError(int error_code);
bool InitScaler(const RetrieveVideoParams &params);
void FreeScaler();
static VideoParams::Format GetNativePixelFormat(AVPixelFormat pix_fmt);
static int GetNativeChannelCount(AVPixelFormat pix_fmt);
static uint64_t ValidateChannelLayout(AVStream *stream);
static const char* GetInterlacingModeInFFmpeg(VideoParams::Interlacing interlacing);
FFmpegFramePool::ElementPtr GetFrameFromCache(const int64_t& t) const;
void ClearFrameCache();
FFmpegFramePool::ElementPtr RetrieveFrame(const rational &time, const QAtomicInt *cancelled);
void RemoveFirstFrame();
RetrieveVideoParams filter_params_;
AVFilterGraph* filter_graph_;
AVFilterContext* buffersrc_ctx_;
AVFilterContext* buffersink_ctx_;
AVPixelFormat ideal_pix_fmt_;
VideoParams::Format native_pix_fmt_;
int native_channel_count_;
FFmpegFramePool pool_;
int64_t second_ts_;
QList<FFmpegFramePool::ElementPtr> cached_frames_;
bool is_working_;
QMutex is_working_mutex_;
bool cache_at_zero_;
bool cache_at_eof_;
Instance instance_;
};
}
#endif // FFMPEGDECODER_H
+883
View File
@@ -0,0 +1,883 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#include "ffmpegencoder.h"
extern "C" {
#include <libavutil/pixdesc.h>
}
#include <QFile>
#include "common/ffmpegutils.h"
#include "common/timecodefunctions.h"
namespace olive {
FFmpegEncoder::FFmpegEncoder(const EncodingParams &params) :
Encoder(params),
fmt_ctx_(nullptr),
video_stream_(nullptr),
video_codec_ctx_(nullptr),
video_alpha_scale_ctx_(nullptr),
video_noalpha_scale_ctx_(nullptr),
audio_stream_(nullptr),
audio_codec_ctx_(nullptr),
audio_resample_ctx_(nullptr),
audio_frame_(nullptr),
open_(false)
{
}
QStringList FFmpegEncoder::GetPixelFormatsForCodec(ExportCodec::Codec c) const
{
QStringList pix_fmts;
const AVCodec* codec_info = GetEncoder(c);
if (codec_info) {
for (int i=0; codec_info->pix_fmts[i]!=-1; i++) {
const char* pix_fmt_name = av_get_pix_fmt_name(codec_info->pix_fmts[i]);
pix_fmts.append(pix_fmt_name);
}
}
return pix_fmts;
}
bool FFmpegEncoder::Open()
{
if (open_) {
return true;
}
int error_code;
// Convert QString to C string that FFmpeg expects
QByteArray filename_bytes = params().filename().toUtf8();
const char* filename_c_str = filename_bytes.constData();
// Create output format context
error_code = avformat_alloc_output_context2(&fmt_ctx_, nullptr, nullptr, filename_c_str);
// Check error code
if (error_code < 0) {
FFmpegError(tr("Failed to allocate output context"), error_code);
return false;
}
// Initialize a video stream if it's enabled
if (params().video_enabled()) {
if (!InitializeStream(AVMEDIA_TYPE_VIDEO, &video_stream_, &video_codec_ctx_, params().video_codec())) {
return false;
}
// This is the format we will expect frames received in Write() to be in
VideoParams::Format native_pixel_fmt = params().video_params().format();
// This is the format we will need to convert the frame to for swscale to understand it
video_conversion_fmt_ = FFmpegUtils::GetCompatiblePixelFormat(native_pixel_fmt);
// This is the equivalent pixel format above as an AVPixelFormat that swscale can understand
AVPixelFormat src_alpha_pix_fmt = FFmpegUtils::GetFFmpegPixelFormat(video_conversion_fmt_,
VideoParams::kRGBAChannelCount);
AVPixelFormat src_noalpha_pix_fmt = FFmpegUtils::GetFFmpegPixelFormat(video_conversion_fmt_,
VideoParams::kRGBChannelCount);
if (src_alpha_pix_fmt == AV_PIX_FMT_NONE || src_noalpha_pix_fmt == AV_PIX_FMT_NONE) {
SetError(tr("Failed to find suitable pixel format for this buffer"));
return false;
}
// This is the pixel format the encoder wants to encode to
AVPixelFormat encoder_pix_fmt = video_codec_ctx_->pix_fmt;
// Set up a scaling context - if the native pixel format is not equal to the encoder's, we'll need to convert it
// before encoding. Even if we don't, this may be useful for converting between linesizes, etc.
video_alpha_scale_ctx_ = sws_getContext(params().video_params().width(),
params().video_params().height(),
src_alpha_pix_fmt,
params().video_params().width(),
params().video_params().height(),
encoder_pix_fmt,
0,
nullptr,
nullptr,
nullptr);
video_noalpha_scale_ctx_ = sws_getContext(params().video_params().width(),
params().video_params().height(),
src_noalpha_pix_fmt,
params().video_params().width(),
params().video_params().height(),
encoder_pix_fmt,
0,
nullptr,
nullptr,
nullptr);
}
// Initialize an audio stream if it's enabled
if (params().audio_enabled()) {
if (!InitializeStream(AVMEDIA_TYPE_AUDIO, &audio_stream_, &audio_codec_ctx_, params().audio_codec())) {
return false;
}
}
// Initialize a subtitle stream if it's enabled
if (params().subtitles_enabled()) {
if (!InitializeStream(AVMEDIA_TYPE_SUBTITLE, &subtitle_stream_, &subtitle_codec_ctx_, params().subtitles_codec())) {
return false;
}
}
av_dump_format(fmt_ctx_, 0, filename_c_str, 1);
// Open output file for writing
error_code = avio_open(&fmt_ctx_->pb, filename_c_str, AVIO_FLAG_WRITE);
if (error_code < 0) {
FFmpegError(tr("Failed to open IO context"), error_code);
return false;
}
// Write header
error_code = avformat_write_header(fmt_ctx_, nullptr);
if (error_code < 0) {
FFmpegError(tr("Failed to write format header"), error_code);
return false;
}
open_ = true;
return true;
}
bool FFmpegEncoder::WriteFrame(FramePtr frame, rational time)
{
bool success = false;
AVFrame* encoded_frame = av_frame_alloc();
int error_code;
const char* input_data;
int input_linesize;
// Frame must be video
encoded_frame->width = frame->width();
encoded_frame->height = frame->height();
encoded_frame->format = video_codec_ctx_->pix_fmt;
// Set interlacing
if (frame->video_params().interlacing() != VideoParams::kInterlaceNone) {
encoded_frame->interlaced_frame = 1;
if (frame->video_params().interlacing() == VideoParams::kInterlacedTopFirst) {
encoded_frame->top_field_first = 1;
} else {
encoded_frame->top_field_first = 0;
}
}
error_code = av_frame_get_buffer(encoded_frame, 0);
if (error_code < 0) {
FFmpegError(tr("Failed to create AVFrame buffer"), error_code);
goto fail;
}
// We may need to convert this frame to a frame that swscale will understand
if (frame->format() != video_conversion_fmt_) {
frame = frame->convert(video_conversion_fmt_);
}
// Use swscale context to convert formats/linesizes
input_data = frame->const_data();
input_linesize = frame->linesize_bytes();
error_code = sws_scale((frame->channel_count() == VideoParams::kRGBAChannelCount) ? video_alpha_scale_ctx_ : video_noalpha_scale_ctx_,
reinterpret_cast<const uint8_t**>(&input_data),
&input_linesize,
0,
frame->height(),
encoded_frame->data,
encoded_frame->linesize);
if (error_code < 0) {
FFmpegError(tr("Failed to scale frame"), error_code);
goto fail;
}
encoded_frame->pts = qRound64(time.toDouble() / av_q2d(video_codec_ctx_->time_base));
success = WriteAVFrame(encoded_frame, video_codec_ctx_, video_stream_);
fail:
av_frame_free(&encoded_frame);
return success;
}
bool FFmpegEncoder::WriteAudio(SampleBufferPtr audio)
{
if (!InitializeResampleContext(audio)) {
qCritical() << "Failed to initialize resample context";
return false;
}
bool result = true;
// Create input buffer
int input_sample_count = 0;
uint8_t** input_data = nullptr;
if (audio) {
input_sample_count = audio->sample_count();
int input_linesize;
av_samples_alloc_array_and_samples(&input_data, &input_linesize, audio->audio_params().channel_count(),
input_sample_count, FFmpegUtils::GetFFmpegSampleFormat(audio->audio_params().format(), true), 0);
for (int i=0; i<audio->audio_params().channel_count(); i++) {
memcpy(input_data[i], audio->data(i), input_sample_count * audio->audio_params().bytes_per_sample_per_channel());
}
}
// Create output buffer
int output_sample_count = input_sample_count ? swr_get_out_samples(audio_resample_ctx_, input_sample_count) : 102400;
uint8_t** output_data = nullptr;
int output_linesize;
av_samples_alloc_array_and_samples(&output_data, &output_linesize, audio_stream_->codecpar->channels,
output_sample_count, static_cast<AVSampleFormat>(audio_stream_->codecpar->format), 0);
// Perform conversion
int converted = swr_convert(audio_resample_ctx_, output_data, output_sample_count, const_cast<const uint8_t**>(input_data), input_sample_count);
if (converted > 0) {
// Split sample buffer into frames
for (int i=0; i<converted; ) {
int frame_remaining_samples = audio_max_samples_ - audio_frame_offset_;
int converted_remaining_samples = converted - i;
int copy_length = qMin(frame_remaining_samples, converted_remaining_samples);
av_samples_copy(audio_frame_->data, output_data, audio_frame_offset_, i,
copy_length,
audio_frame_->channels, static_cast<AVSampleFormat>(audio_frame_->format));
audio_frame_offset_ += copy_length;
i += copy_length;
if (audio_frame_offset_ == audio_max_samples_ || (i == converted && !input_data)) {
// Got all the samples we needed, write the frame
audio_frame_->pts = av_rescale_q(audio_write_count_, {1, audio_codec_ctx_->sample_rate}, audio_codec_ctx_->time_base);
WriteAVFrame(audio_frame_, audio_codec_ctx_, audio_stream_);
audio_write_count_ += audio_frame_offset_;
audio_frame_offset_ = 0;
}
}
} else if (converted < 0) {
FFmpegError(tr("Failed to resample audio"), converted);
result = false;
}
if (!input_data && audio_frame_offset_ > 0) {
audio_frame_->nb_samples = audio_frame_offset_;
audio_frame_->pts = av_rescale_q(audio_write_count_, {1, audio_codec_ctx_->sample_rate}, audio_codec_ctx_->time_base);
WriteAVFrame(audio_frame_, audio_codec_ctx_, audio_stream_);
}
// Free buffers created
if (output_data) {
av_freep(&output_data[0]);
av_freep(&output_data);
}
if (input_data) {
av_freep(&input_data[0]);
av_freep(&input_data);
}
return result;
}
QString GetAssTime(const rational &time)
{
int64_t total_centiseconds = qRound64(time.toDouble() * 100);
int64_t cs = total_centiseconds % 100;
int64_t ss = (total_centiseconds / 100) % 60;
int64_t mm = (total_centiseconds / 6000) % 60;
int64_t hh = total_centiseconds / 360000;
return QStringLiteral("%1:%2:%3.%4").arg(
QString::number(hh),
QStringLiteral("%1").arg(mm, 2, 10, QLatin1Char('0')),
QStringLiteral("%1").arg(ss, 2, 10, QLatin1Char('0')),
QStringLiteral("%1").arg(cs, 2, 10, QLatin1Char('0'))
);
}
bool FFmpegEncoder::WriteSubtitle(const SubtitleBlock *sub_block)
{
AVSubtitle subtitle;
memset(&subtitle, 0, sizeof(subtitle));
AVSubtitleRect rect;
memset(&rect, 0, sizeof(rect));
QString ass_line = QStringLiteral("Dialogue: 0,%1,%2,Default,,0,0,0,,%3").arg(
GetAssTime(sub_block->in()),
GetAssTime(sub_block->out()),
sub_block->GetText()
);
QByteArray utf8_sub = sub_block->GetText().toUtf8();
QByteArray utf8_ass = ass_line.toUtf8();
rect.type = SUBTITLE_ASS;
rect.text = utf8_sub.data();
rect.ass = utf8_ass.data();
AVSubtitleRect *rect_array = &rect;
subtitle.num_rects = 1;
subtitle.rects = &rect_array;
subtitle.pts = Timecode::time_to_timestamp(sub_block->in(), subtitle_codec_ctx_->time_base, Timecode::kFloor);
subtitle.end_display_time = qRound64(sub_block->length().toDouble() * 1000);
QVector<uint8_t> out_buf(1024 * 1024);
int sub_sz = avcodec_encode_subtitle(subtitle_codec_ctx_, out_buf.data(), out_buf.size(), &subtitle);
if (sub_sz < 0) {
return false;
}
AVPacket *pkt = av_packet_alloc();
pkt->stream_index = subtitle_stream_->index;
pkt->data = out_buf.data();
pkt->size = sub_sz;
pkt->pts = subtitle.pts;
pkt->duration = av_rescale_q(subtitle.end_display_time, {1, 1000}, subtitle_codec_ctx_->time_base);
pkt->dts = pkt->pts;
av_packet_rescale_ts(pkt, subtitle_codec_ctx_->time_base, subtitle_stream_->time_base);
int err = av_interleaved_write_frame(fmt_ctx_, pkt);
bool ret = true;
if (err < 0) {
FFmpegError(tr("Failed to write interleaved packet"), err);
ret = false;
}
av_packet_free(&pkt);
return ret;
}
/*
void FFmpegEncoder::WriteAudio(AudioParams pcm_info, QIODevice* file)
{
// Keep track of sample count to use as each frame's timebase
int sample_counter = 0;
while (true) {
// Calculate how many samples should input this frame
int64_t samples_needed = av_rescale_rnd(maximum_frame_samples + swr_get_delay(swr_ctx, pcm_info.sample_rate()),
audio_codec_ctx_->sample_rate,
pcm_info.sample_rate(),
AV_ROUND_UP);
// Calculate how many bytes this is
int max_read = pcm_info.samples_to_bytes(samples_needed);
// Read bytes from PCM
QByteArray input_data = file->read(max_read);
// Use swresample to convert the data into the correct format
const char* input_data_array = input_data.constData();
int converted = swr_convert(swr_ctx,
// output data
frame->data,
// output sample count (maximum amount of samples in output)
maximum_frame_samples,
// input data
reinterpret_cast<const uint8_t**>(&input_data_array),
// input sample count (maximum amount of samples we read from pcm file)
pcm_info.bytes_to_samples(input_data.size()));
// Update the frame's number of samples to the amount we actually received
frame->nb_samples = converted;
// Update frame timestamp
frame->pts = sample_counter;
// Increment timestamp for the next frame by the amount of samples in this one
sample_counter += converted;
// Write the frame
if (!WriteAVFrame(frame, audio_codec_ctx_, audio_stream_)) {
qCritical() << "Failed to write audio AVFrame";
break;
}
// Break if we've reached the end point
if (file->atEnd()) {
break;
}
}
}
*/
void FFmpegEncoder::Close()
{
if (open_) {
// Flush encoders
FlushEncoders();
// We've written a header, so we'll write a trailer
av_write_trailer(fmt_ctx_);
avio_closep(&fmt_ctx_->pb);
open_ = false;
}
if (audio_resample_ctx_) {
swr_init(audio_resample_ctx_);
audio_resample_ctx_ = nullptr;
}
if (audio_frame_) {
av_frame_free(&audio_frame_);
audio_frame_ = nullptr;
}
if (video_alpha_scale_ctx_) {
sws_freeContext(video_alpha_scale_ctx_);
video_alpha_scale_ctx_ = nullptr;
}
if (video_noalpha_scale_ctx_) {
sws_freeContext(video_noalpha_scale_ctx_);
video_noalpha_scale_ctx_ = nullptr;
}
if (video_codec_ctx_) {
avcodec_free_context(&video_codec_ctx_);
video_codec_ctx_ = nullptr;
}
if (audio_codec_ctx_) {
avcodec_free_context(&audio_codec_ctx_);
audio_codec_ctx_ = nullptr;
}
if (fmt_ctx_) {
// NOTE: This also frees video_stream_ and audio_stream_
avformat_free_context(fmt_ctx_);
fmt_ctx_ = nullptr;
video_stream_ = nullptr;
audio_stream_ = nullptr;
}
}
void FFmpegEncoder::FFmpegError(const QString& context, int error_code)
{
char err[1024];
av_strerror(error_code, err, 1024);
QString formatted_err = tr("%1: %2 %3").arg(context, err, QString::number(error_code));
qDebug() << formatted_err;
SetError(formatted_err);
}
bool FFmpegEncoder::WriteAVFrame(AVFrame *frame, AVCodecContext* codec_ctx, AVStream* stream)
{
// Send raw frame to the encoder
int error_code = avcodec_send_frame(codec_ctx, frame);
if (error_code < 0) {
FFmpegError(tr("Failed to send frame to encoder"), error_code);
return false;
}
bool succeeded = false;
AVPacket* pkt = av_packet_alloc();
// Retrieve packets from encoder
while (error_code >= 0) {
error_code = avcodec_receive_packet(codec_ctx, pkt);
// EAGAIN just means the encoder wants another frame before encoding
if (error_code == AVERROR(EAGAIN)) {
break;
} else if (error_code < 0) {
FFmpegError(tr("Failed to receive packet from decoder"), error_code);
goto fail;
}
// Set packet stream index
pkt->stream_index = stream->index;
av_packet_rescale_ts(pkt, codec_ctx->time_base, stream->time_base);
// Write packet to file
error_code = av_interleaved_write_frame(fmt_ctx_, pkt);
if (error_code < 0) {
FFmpegError(tr("Failed to write interleaved packet"), error_code);
goto fail;
}
// Unref packet in case we're getting another
av_packet_unref(pkt);
}
succeeded = true;
fail:
av_packet_free(&pkt);
return succeeded;
}
bool FFmpegEncoder::InitializeStream(AVMediaType type, AVStream** stream_ptr, AVCodecContext** codec_ctx_ptr, const ExportCodec::Codec& codec)
{
if (type != AVMEDIA_TYPE_VIDEO && type != AVMEDIA_TYPE_AUDIO && type != AVMEDIA_TYPE_SUBTITLE) {
SetError(tr("Cannot initialize a stream that is not a video, audio, or subtitle type"));
return false;
}
// Find encoder
const AVCodec* encoder = GetEncoder(codec);
if (!encoder) {
SetError(tr("Failed to find codec for 0x%1").arg(codec, 16));
return false;
}
if (encoder->type != type) {
SetError(tr("Retrieved unexpected codec type %1 for codec %2").arg(QString::number(encoder->type), codec));
return false;
}
if (!InitializeCodecContext(stream_ptr, codec_ctx_ptr, encoder)) {
return false;
}
// Set codec parameters
AVCodecContext* codec_ctx = *codec_ctx_ptr;
AVStream* stream = *stream_ptr;
if (type == AVMEDIA_TYPE_VIDEO) {
codec_ctx->width = params().video_params().width();
codec_ctx->height = params().video_params().height();
codec_ctx->sample_aspect_ratio = params().video_params().pixel_aspect_ratio().toAVRational();
codec_ctx->time_base = params().video_params().frame_rate_as_time_base().toAVRational();
codec_ctx->framerate = params().video_params().frame_rate().toAVRational();
codec_ctx->pix_fmt = av_get_pix_fmt(params().video_pix_fmt().toUtf8());
if (params().video_params().interlacing() != VideoParams::kInterlaceNone) {
// FIXME: I actually don't know what these flags do, the documentation helpfully doesn't
// explain them at all. I hope using both of them is the right thing to do.
codec_ctx->flags |= AV_CODEC_FLAG_INTERLACED_DCT | AV_CODEC_FLAG_INTERLACED_ME;
if (params().video_params().interlacing() == VideoParams::kInterlacedTopFirst) {
codec_ctx->field_order = AV_FIELD_TT;
} else {
codec_ctx->field_order = AV_FIELD_BB;
if (codec == ExportCodec::kCodecH264 || codec == ExportCodec::kCodecH264rgb) {
// For some reason, FFmpeg doesn't set libx264's bff flag so we have to do it ourselves
av_opt_set(codec_ctx->priv_data, "x264opts", "bff=1", AV_OPT_SEARCH_CHILDREN);
}
}
}
// Set custom options
{
for (auto i=params().video_opts().begin();i!=params().video_opts().end();i++) {
av_opt_set(codec_ctx->priv_data, i.key().toUtf8(), i.value().toUtf8(), AV_OPT_SEARCH_CHILDREN);
}
if (params().video_bit_rate() > 0) {
codec_ctx->bit_rate = params().video_bit_rate();
}
if (params().video_min_bit_rate() > 0) {
codec_ctx->rc_min_rate = params().video_min_bit_rate();
}
if (params().video_max_bit_rate() > 0) {
codec_ctx->rc_max_rate = params().video_max_bit_rate();
}
if (params().video_buffer_size() > 0) {
codec_ctx->rc_buffer_size = static_cast<int>(params().video_buffer_size());
}
}
} else if (type == AVMEDIA_TYPE_AUDIO) {
// Assume audio stream
codec_ctx->sample_rate = params().audio_params().sample_rate();
codec_ctx->channel_layout = params().audio_params().channel_layout();
codec_ctx->channels = av_get_channel_layout_nb_channels(codec_ctx->channel_layout);
codec_ctx->sample_fmt = encoder->sample_fmts[0];
codec_ctx->time_base = {1, codec_ctx->sample_rate};
if (params().audio_bit_rate() > 0) {
codec_ctx->bit_rate = params().audio_bit_rate();
}
} else if (type == AVMEDIA_TYPE_SUBTITLE) {
codec_ctx->time_base = av_get_time_base_q();
QByteArray ass_header = SubtitleParams::GenerateASSHeader().toUtf8();
codec_ctx->subtitle_header = new uint8_t[ass_header.size()];
memcpy(codec_ctx->subtitle_header, ass_header.constData(), ass_header.size());
codec_ctx->subtitle_header_size = ass_header.size();
}
if (!SetupCodecContext(stream, codec_ctx, encoder)) {
return false;
}
return true;
}
bool FFmpegEncoder::InitializeCodecContext(AVStream **stream, AVCodecContext **codec_ctx, const AVCodec* codec)
{
*stream = avformat_new_stream(fmt_ctx_, nullptr);
if (!(*stream)) {
SetError(tr("Failed to allocate AVStream"));
return false;
}
// Allocate a codec context
*codec_ctx = avcodec_alloc_context3(codec);
if (!(*codec_ctx)) {
SetError(tr("Failed to allocate AVCodecContext"));
return false;
}
return true;
}
bool FFmpegEncoder::SetupCodecContext(AVStream* stream, AVCodecContext* codec_ctx, const AVCodec* codec)
{
int error_code;
if (fmt_ctx_->oformat->flags & AVFMT_GLOBALHEADER) {
codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
AVDictionary* codec_opts = nullptr;
// Set thread count
if (params().video_threads() == 0) {
av_dict_set(&codec_opts, "threads", "auto", 0);
} else {
QString thread_val = QString::number(params().video_threads());
av_dict_set(&codec_opts, "threads", thread_val.toUtf8(), 0);
}
// Try to open encoder
error_code = avcodec_open2(codec_ctx, codec, &codec_opts);
if (error_code < 0) {
FFmpegError(tr("Failed to open encoder"), error_code);
return false;
}
// Copy context settings to codecpar object
error_code = avcodec_parameters_from_context(stream->codecpar, codec_ctx);
if (error_code < 0) {
FFmpegError(tr("Failed to copy codec parameters to stream"), error_code);
return false;
}
if (codec->type == AVMEDIA_TYPE_VIDEO) {
stream->avg_frame_rate = codec_ctx->framerate;
}
return true;
}
void FFmpegEncoder::FlushEncoders()
{
if (video_codec_ctx_) {
FlushCodecCtx(video_codec_ctx_, video_stream_);
}
if (audio_codec_ctx_) {
WriteAudio(nullptr);
FlushCodecCtx(audio_codec_ctx_, audio_stream_);
}
if (fmt_ctx_) {
if (fmt_ctx_->oformat->flags & AVFMT_ALLOW_FLUSH) {
int r = av_interleaved_write_frame(fmt_ctx_, nullptr);
if (r < 0) {
FFmpegError(tr("Failed to write interleaved packet"), r);
}
}
}
}
void FFmpegEncoder::FlushCodecCtx(AVCodecContext *codec_ctx, AVStream* stream)
{
avcodec_send_frame(codec_ctx, nullptr);
AVPacket* pkt = av_packet_alloc();
int error_code;
do {
error_code = avcodec_receive_packet(codec_ctx, pkt);
if (error_code < 0) {
break;
}
pkt->stream_index = stream->index;
av_packet_rescale_ts(pkt, codec_ctx->time_base, stream->time_base);
int r = av_interleaved_write_frame(fmt_ctx_, pkt);
if (r < 0) {
FFmpegError(tr("Failed to write interleaved packet"), r);
break;
}
av_packet_unref(pkt);
} while (error_code >= 0);
av_packet_free(&pkt);
}
bool FFmpegEncoder::InitializeResampleContext(SampleBufferPtr audio)
{
if (audio_resample_ctx_) {
return true;
}
// Create resample context
audio_resample_ctx_ = swr_alloc_set_opts(nullptr,
static_cast<int64_t>(audio_codec_ctx_->channel_layout),
audio_codec_ctx_->sample_fmt,
audio_codec_ctx_->sample_rate,
static_cast<int64_t>(audio->audio_params().channel_layout()),
FFmpegUtils::GetFFmpegSampleFormat(audio->audio_params().format(), true),
audio->audio_params().sample_rate(),
0,
nullptr);
if (!audio_resample_ctx_) {
return false;
}
int err = swr_init(audio_resample_ctx_);
if (err < 0) {
FFmpegError(tr("Failed to create resampling context"), err);
return false;
}
audio_max_samples_ = audio_codec_ctx_->frame_size;
if (!audio_max_samples_) {
// If not, use another frame size
if (params().video_enabled()) {
// If we're encoding video, use enough samples to cover roughly one frame of video
audio_max_samples_ = params().audio_params().time_to_samples(params().video_params().frame_rate_as_time_base());
} else {
// If no video, just use an arbitrary number
audio_max_samples_ = 256;
}
}
audio_frame_ = av_frame_alloc();
if (!audio_frame_) {
return false;
}
audio_frame_->channel_layout = audio_codec_ctx_->channel_layout;
audio_frame_->format = audio_codec_ctx_->sample_fmt;
audio_frame_->nb_samples = audio_max_samples_;
err = av_frame_get_buffer(audio_frame_, 0);
if (err < 0) {
FFmpegError(tr("Failed to create audio frame"), err);
return false;
}
audio_frame_offset_ = 0;
audio_write_count_ = 0;
return true;
}
const AVCodec *FFmpegEncoder::GetEncoder(ExportCodec::Codec c)
{
switch (c) {
case ExportCodec::kCodecH264:
return avcodec_find_encoder_by_name("libx264");
case ExportCodec::kCodecH264rgb:
return avcodec_find_encoder_by_name("libx264rgb");
case ExportCodec::kCodecDNxHD:
return avcodec_find_encoder(AV_CODEC_ID_DNXHD);
case ExportCodec::kCodecProRes:
return avcodec_find_encoder(AV_CODEC_ID_PRORES);
case ExportCodec::kCodecCineform:
return avcodec_find_encoder(AV_CODEC_ID_CFHD);
case ExportCodec::kCodecH265:
return avcodec_find_encoder(AV_CODEC_ID_HEVC);
case ExportCodec::kCodecVP9:
return avcodec_find_encoder(AV_CODEC_ID_VP9);
case ExportCodec::kCodecOpenEXR:
return avcodec_find_encoder(AV_CODEC_ID_EXR);
case ExportCodec::kCodecPNG:
return avcodec_find_encoder(AV_CODEC_ID_PNG);
case ExportCodec::kCodecTIFF:
return avcodec_find_encoder(AV_CODEC_ID_TIFF);
case ExportCodec::kCodecMP2:
return avcodec_find_encoder(AV_CODEC_ID_MP2);
case ExportCodec::kCodecMP3:
return avcodec_find_encoder(AV_CODEC_ID_MP3);
case ExportCodec::kCodecAAC:
return avcodec_find_encoder(AV_CODEC_ID_AAC);
case ExportCodec::kCodecPCM:
return avcodec_find_encoder(AV_CODEC_ID_PCM_S16LE);
case ExportCodec::kCodecFLAC:
return avcodec_find_encoder(AV_CODEC_ID_FLAC);
case ExportCodec::kCodecOpus:
return avcodec_find_encoder(AV_CODEC_ID_OPUS);
case ExportCodec::kCodecVorbis:
return avcodec_find_encoder(AV_CODEC_ID_VORBIS);
case ExportCodec::kCodecSRT:
return avcodec_find_encoder(AV_CODEC_ID_SUBRIP);
case ExportCodec::kCodecCount:
// These are audio or invalid codecs and therefore have no pixel formats
break;
}
return nullptr;
}
}
+108
View File
@@ -0,0 +1,108 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#ifndef FFMPEGENCODER_H
#define FFMPEGENCODER_H
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
#include <libavutil/opt.h>
}
#include "codec/encoder.h"
namespace olive {
class FFmpegEncoder : public Encoder
{
Q_OBJECT
public:
FFmpegEncoder(const EncodingParams &params);
virtual QStringList GetPixelFormatsForCodec(ExportCodec::Codec c) const override;
virtual bool Open() override;
virtual bool WriteFrame(olive::FramePtr frame, olive::rational time) override;
virtual bool WriteAudio(olive::SampleBufferPtr audio) override;
virtual bool WriteSubtitle(const SubtitleBlock *sub_block) override;
virtual void Close() override;
virtual VideoParams::Format GetDesiredPixelFormat() const override
{
return video_conversion_fmt_;
}
private:
/**
* @brief Handle an FFmpeg error code
*
* Uses the FFmpeg API to retrieve a descriptive string for this error code and sends it to Error(). As such, this
* function also automatically closes the Decoder.
*
* @param error_code
*/
void FFmpegError(const QString &context, int error_code);
bool WriteAVFrame(AVFrame* frame, AVCodecContext *codec_ctx, AVStream *stream);
bool InitializeStream(enum AVMediaType type, AVStream** stream, AVCodecContext** codec_ctx, const ExportCodec::Codec &codec);
bool InitializeCodecContext(AVStream** stream, AVCodecContext** codec_ctx, const AVCodec* codec);
bool SetupCodecContext(AVStream *stream, AVCodecContext *codec_ctx, const AVCodec *codec);
void FlushEncoders();
void FlushCodecCtx(AVCodecContext* codec_ctx, AVStream *stream);
bool InitializeResampleContext(SampleBufferPtr audio);
static const AVCodec *GetEncoder(ExportCodec::Codec c);
AVFormatContext* fmt_ctx_;
AVStream* video_stream_;
AVCodecContext* video_codec_ctx_;
SwsContext* video_alpha_scale_ctx_;
SwsContext* video_noalpha_scale_ctx_;
VideoParams::Format video_conversion_fmt_;
AVStream* audio_stream_;
AVCodecContext* audio_codec_ctx_;
SwrContext* audio_resample_ctx_;
AVFrame* audio_frame_;
int audio_max_samples_;
int audio_frame_offset_;
int audio_write_count_;
AVStream* subtitle_stream_;
AVCodecContext* subtitle_codec_ctx_;
bool open_;
};
}
#endif // FFMPEGENCODER_H
@@ -1,7 +1,7 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
Copyright (C) 2021 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
@@ -18,43 +18,34 @@
***/
#include "dependency.h"
#include "ffmpegframepool.h"
#include "node.h"
#include "codec/frame.h"
NodeDependency::NodeDependency() :
node_(nullptr)
namespace olive {
FFmpegFramePool::FFmpegFramePool(int element_count) :
MemoryPool(element_count),
width_(0),
height_(0),
format_(VideoParams::kFormatInvalid),
channel_count_(0)
{
}
NodeDependency::NodeDependency(Node *node, const TimeRange &range) :
node_(node),
range_(range)
void FFmpegFramePool::SetParameters(int width, int height, VideoParams::Format format, int channel_count)
{
Clear();
width_ = width;
height_ = height;
format_ = format;
channel_count_ = channel_count;
}
NodeDependency::NodeDependency(Node *node, const rational &in, const rational &out) :
node_(node),
range_(in, out)
size_t FFmpegFramePool::GetElementSize()
{
return Frame::generate_linesize_bytes(width_, format_, channel_count_) * height_;
}
Node *NodeDependency::node() const
{
return node_;
}
const rational& NodeDependency::in() const
{
return range_.in();
}
const rational &NodeDependency::out() const
{
return range_.out();
}
const TimeRange &NodeDependency::range() const
{
return range_;
}
+63
View File
@@ -0,0 +1,63 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#ifndef FFMPEGFRAMEPOOL_H
#define FFMPEGFRAMEPOOL_H
#include "common/memorypool.h"
#include "render/videoparams.h"
namespace olive {
class FFmpegFramePool : public MemoryPool
{
Q_OBJECT
public:
FFmpegFramePool(int element_count);
void SetParameters(int width, int height, VideoParams::Format format, int channel_count);
const int& width() const
{
return width_;
}
const int& height() const
{
return height_;
}
protected:
virtual size_t GetElementSize() override;
private:
int width_;
int height_;
VideoParams::Format format_;
int channel_count_;
};
}
#endif // FFMPEGFRAMEPOOL_H
+10
View File
@@ -0,0 +1,10 @@
#ifndef FOOTAGEMETA_H
#define FOOTAGEMETA_H
struct FootageData {
struct StreamData {
};
};
#endif // FOOTAGEMETA_H
+180
View File
@@ -0,0 +1,180 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#include "frame.h"
#include <OpenImageIO/imagebuf.h>
#include <QDebug>
#include <QtGlobal>
#include <QtMath>
#include "common/oiioutils.h"
#include "render/framemanager.h"
namespace olive {
Frame::Frame() :
data_(nullptr),
data_size_(0),
timestamp_(0)
{
}
Frame::~Frame()
{
destroy();
}
FramePtr Frame::Create()
{
return std::make_shared<Frame>();
}
const VideoParams &Frame::video_params() const
{
return params_;
}
void Frame::set_video_params(const VideoParams &params)
{
params_ = params;
linesize_ = generate_linesize_bytes(width(), params_.format(), params_.channel_count());
linesize_pixels_ = linesize_ / params_.GetBytesPerPixel();
}
FramePtr Frame::Interlace(FramePtr top, FramePtr bottom)
{
if (top->video_params() != bottom->video_params()) {
qCritical() << "Tried to interlace two frames that had incompatible parameters";
return nullptr;
}
FramePtr interlaced = Frame::Create();
interlaced->set_video_params(top->video_params());
interlaced->allocate();
int linesize = interlaced->linesize_bytes();
for (int i=0; i<interlaced->height(); i++) {
FramePtr which = (i%2 == 0) ? top : bottom;
memcpy(interlaced->data() + i*linesize,
which->const_data() + i*linesize,
linesize);
}
return interlaced;
}
int Frame::generate_linesize_bytes(int width, VideoParams::Format format, int channel_count)
{
// Align to 32 bytes (not sure if this is necessary?)
return VideoParams::GetBytesPerPixel(format, channel_count) * ((width + 31) & ~31);
}
Color Frame::get_pixel(int x, int y) const
{
if (!contains_pixel(x, y)) {
return Color();
}
int byte_offset = y * linesize_bytes() + x * video_params().GetBytesPerPixel();
return Color(reinterpret_cast<const char*>(data_ + byte_offset), video_params().format(), video_params().channel_count());
}
bool Frame::contains_pixel(int x, int y) const
{
return (is_allocated() && x >= 0 && x < width() && y >= 0 && y < height());
}
void Frame::set_pixel(int x, int y, const Color &c)
{
if (!contains_pixel(x, y)) {
return;
}
int byte_offset = y * linesize_bytes() + x * video_params().GetBytesPerPixel();
c.toData(reinterpret_cast<char*>(data_ + byte_offset), video_params().format(), video_params().channel_count());
}
bool Frame::allocate()
{
// Assume this frame is intended to be a video frame
if (!params_.is_valid()) {
qWarning() << "Tried to allocate a frame with invalid parameters";
return false;
}
if (is_allocated()) {
// Already allocated
return true;
}
data_size_ = linesize_ * height();
data_ = FrameManager::Allocate(data_size_);
return true;
}
void Frame::destroy()
{
if (is_allocated()) {
FrameManager::Deallocate(data_size_, data_);
data_size_ = 0;
data_ = nullptr;
}
}
FramePtr Frame::convert(VideoParams::Format format) const
{
// Create new params with destination format
VideoParams params = params_;
params.set_format(format);
// Create new frame
FramePtr converted = Frame::Create();
converted->set_video_params(params);
converted->set_timestamp(timestamp_);
converted->allocate();
// Do the conversion through OIIO for convenience
OIIO::ImageBuf src(OIIO::ImageSpec(width(), height(),
channel_count(),
OIIOUtils::GetOIIOBaseTypeFromFormat(this->format())));
OIIOUtils::FrameToBuffer(this, &src);
OIIO::ImageBuf dst(OIIO::ImageSpec(converted->width(), converted->height(),
channel_count(),
OIIOUtils::GetOIIOBaseTypeFromFormat(format)));
if (dst.copy_pixels(src)) {
OIIOUtils::BufferToFrame(&dst, converted.get());
return converted;
} else {
return nullptr;
}
}
}
+88 -58
View File
@@ -1,7 +1,7 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
Copyright (C) 2021 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
@@ -24,83 +24,101 @@
#include <memory>
#include <QVector>
#include "common/define.h"
#include "common/rational.h"
#include "render/audioparams.h"
#include "render/pixelformat.h"
#include "render/color.h"
#include "render/videoparams.h"
namespace olive {
class Frame;
using FramePtr = std::shared_ptr<Frame>;
/**
* @brief Video frame data or audio sample data from a Decoder
*
* Abstraction from AVFrame. Currently a simple AVFrame wrapper.
*
* This class does not support copying at this time.
*/
class Frame
{
public:
/// Normal constructor
Frame();
~Frame();
DISABLE_COPY_MOVE(Frame)
static FramePtr Create();
/**
* @brief Get frame's width in pixels
*/
const int& width();
void set_width(const int& width);
const VideoParams& video_params() const;
void set_video_params(const VideoParams& params);
/**
* @brief Get frame's height in pixels
*/
const int& height();
void set_height(const int& height);
static FramePtr Interlace(FramePtr top, FramePtr bottom);
const AudioRenderingParams& audio_params();
void set_audio_params(const AudioRenderingParams& params);
static int generate_linesize_bytes(int width, VideoParams::Format format, int channel_count);
const int &sample_count();
void set_sample_count(const int &sample_count);
int linesize_pixels() const
{
return linesize_pixels_;
}
int linesize_bytes() const
{
return linesize_;
}
int width() const
{
return params_.effective_width();
}
int height() const
{
return params_.effective_height();
}
VideoParams::Format format() const
{
return params_.format();
}
int channel_count() const
{
return params_.channel_count();
}
Color get_pixel(int x, int y) const;
bool contains_pixel(int x, int y) const;
void set_pixel(int x, int y, const Color& c);
/**
* @brief Get frame's timestamp.
*
* This timestamp is always a rational that will equate to the time in seconds.
*/
const rational& timestamp();
void set_timestamp(const rational& timestamp);
const rational& timestamp() const
{
return timestamp_;
}
/*const int64_t& native_timestamp();
void set_native_timestamp(const int64_t& timestamp);*/
/**
* @brief Get frame's format
*
* @return
*
* Currently this will either be an olive::PixelFormat (video) or an olive::SampleFormat (audio).
*/
const olive::PixelFormat& format();
void set_format(const olive::PixelFormat& format);
/**
* @brief Returns a copy of the data in this frame as a QByteArray
*
* Will always do a deep copy. If you want to affect the data directly, use data() instead.
*/
QByteArray ToByteArray();
void set_timestamp(const rational& timestamp)
{
timestamp_ = timestamp;
}
/**
* @brief Get the data buffer of this frame
*/
char* data();
char* data()
{
return data_;
}
/**
* @brief Get the const data buffer of this frame
*/
const char* const_data();
const char* const_data() const
{
return data_;
}
/**
* @brief Allocate memory buffer to store data based on parameters
@@ -109,7 +127,15 @@ public:
*
* If a memory buffer has been previously allocated without destroying, this function will destroy it.
*/
void allocate();
bool allocate();
/**
* @brief Return whether the frame is allocated or not
*/
bool is_allocated() const
{
return data_;
}
/**
* @brief Destroy a memory buffer allocated with allocate()
@@ -121,25 +147,29 @@ public:
*
* Returns 0 if nothing is allocated.
*/
int allocated_size() const;
int allocated_size() const
{
return data_size_;
}
FramePtr convert(VideoParams::Format format) const;
private:
int width_;
VideoParams params_;
int height_;
olive::PixelFormat format_;
AudioRenderingParams audio_params_;
int sample_count_;
QByteArray data_;
char* data_;
int data_size_;
rational timestamp_;
int64_t native_timestamp_;
int linesize_;
int linesize_pixels_;
};
}
Q_DECLARE_METATYPE(olive::FramePtr)
#endif // FRAME_H
+24
View File
@@ -0,0 +1,24 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2021 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}
codec/oiio/oiiodecoder.cpp
codec/oiio/oiiodecoder.h
codec/oiio/oiioencoder.cpp
codec/oiio/oiioencoder.h
PARENT_SCOPE
)
+220
View File
@@ -0,0 +1,220 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#include "oiiodecoder.h"
#include <OpenImageIO/imagebufalgo.h>
#include <QDebug>
#include <QDir>
#include <QFileInfo>
#include <QMessageBox>
#include "common/define.h"
#include "common/oiioutils.h"
#include "config/config.h"
#include "core.h"
namespace olive {
QStringList OIIODecoder::supported_formats_;
OIIODecoder::OIIODecoder() :
image_(nullptr),
buffer_(nullptr)
{
}
QString OIIODecoder::id() const
{
return QStringLiteral("oiio");
}
FootageDescription OIIODecoder::Probe(const QString &filename, const QAtomicInt* cancelled) const
{
Q_UNUSED(cancelled)
FootageDescription desc(id());
// Filter out any file extensions that aren't expected to work - sometimes OIIO will crash trying
// to open a file that it can't if it's given one
if (!FileTypeIsSupported(filename)) {
return desc;
}
std::string std_filename = filename.toStdString();
auto in = OIIO::ImageInput::open(std_filename);
if (!in) {
return desc;
}
// Filter out OIIO detecting an "FFmpeg movie", we have a native FFmpeg decoder that can handle
// it better
if (!strcmp(in->format_name(), "FFmpeg movie")) {
return desc;
}
VideoParams video_params;
video_params.set_stream_index(0);
video_params.set_width(in->spec().width);
video_params.set_height(in->spec().height);
video_params.set_format(OIIOUtils::GetFormatFromOIIOBasetype(static_cast<OIIO::TypeDesc::BASETYPE>(in->spec().format.basetype)));
video_params.set_channel_count(in->spec().nchannels);
video_params.set_pixel_aspect_ratio(OIIOUtils::GetPixelAspectRatioFromOIIO(in->spec()));
video_params.set_video_type(VideoParams::kVideoTypeStill);
// OIIO automatically premultiplies alpha
// FIXME: We usually disassociate the alpha for the color management later, for 8-bit images this
// likely reduces the fidelity?
video_params.set_premultiplied_alpha(true);
desc.AddVideoStream(video_params);
// If we're here, we have a successful image open
in->close();
return desc;
}
bool OIIODecoder::OpenInternal()
{
// If we can open the filename provided, assume everything is working
return OpenImageHandler(stream().filename());
}
FramePtr OIIODecoder::RetrieveVideoInternal(const rational &timecode, const RetrieveVideoParams &divider, const QAtomicInt *cancelled)
{
Q_UNUSED(timecode)
Q_UNUSED(cancelled)
FramePtr frame = Frame::Create();
frame->set_video_params(VideoParams(buffer_->spec().width,
buffer_->spec().height,
pix_fmt_,
channel_count_,
OIIOUtils::GetPixelAspectRatioFromOIIO(buffer_->spec()),
VideoParams::kInterlaceNone, // FIXME: Does OIIO deinterlace for us?
divider.divider));
frame->allocate();
if (divider.divider == 1) {
OIIOUtils::BufferToFrame(buffer_, frame.get());
} else {
// Will need to resize the image
OIIO::ImageBuf dst(OIIO::ImageSpec(frame->width(), frame->height(), buffer_->spec().nchannels, buffer_->spec().format));
if (!OIIO::ImageBufAlgo::resample(dst, *buffer_)) {
qWarning() << "OIIO resize failed";
}
OIIOUtils::BufferToFrame(&dst, frame.get());
}
return frame;
}
void OIIODecoder::CloseInternal()
{
CloseImageHandle();
}
bool OIIODecoder::FileTypeIsSupported(const QString& fn)
{
// We prioritize OIIO over FFmpeg to pick up still images more effectively, but some OIIO decoders (notably OpenJPEG)
// will segfault entirely if given unexpected data (an MPEG-4 for instance). To workaround this issue, we use OIIO's
// "extension_list" attribute and match it with the extension of the file.
// Check if we've created the supported formats list, create it if not
if (supported_formats_.isEmpty()) {
QStringList extension_list = QString::fromStdString(OIIO::get_string_attribute("extension_list")).split(';');
// The format of "extension_list" is "format:ext", we want to separate it into a simple list of extensions
foreach (const QString& ext, extension_list) {
QStringList format_and_ext = ext.split(':');
supported_formats_.append(format_and_ext.at(1).split(','));
}
}
if (!supported_formats_.contains(QFileInfo(fn).suffix(), Qt::CaseInsensitive)) {
return false;
}
return true;
}
bool OIIODecoder::OpenImageHandler(const QString &fn)
{
image_ = OIIO::ImageInput::open(fn.toStdString());
if (!image_) {
return false;
}
// Check if we can work with this pixel format
const OIIO::ImageSpec& spec = image_->spec();
// Store channel count
channel_count_ = spec.nchannels;
// We use RGBA frames because that tends to be the native format of GPUs
pix_fmt_ = OIIOUtils::GetFormatFromOIIOBasetype(static_cast<OIIO::TypeDesc::BASETYPE>(spec.format.basetype));
if (pix_fmt_ == VideoParams::kFormatInvalid) {
qWarning() << "Failed to convert OIIO::ImageDesc to native pixel format";
return false;
}
OIIO::TypeDesc::BASETYPE type = OIIOUtils::GetOIIOBaseTypeFromFormat(pix_fmt_);
if (type == OIIO::TypeDesc::UNKNOWN) {
qCritical() << "Failed to determine appropriate OIIO basetype from native format";
return false;
}
buffer_ = new OIIO::ImageBuf(OIIO::ImageSpec(spec.width, spec.height, spec.nchannels, type),
OIIO::InitializePixels::No);
image_->read_image(type, buffer_->localpixels());
return true;
}
void OIIODecoder::CloseImageHandle()
{
if (image_) {
image_->close();
image_ = nullptr;
}
if (buffer_) {
delete buffer_;
buffer_ = nullptr;
}
}
}
@@ -1,7 +1,7 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
Copyright (C) 2021 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
@@ -22,44 +22,50 @@
#define OIIODECODER_H
#include <OpenImageIO/imageio.h>
#include <OpenImageIO/imagebuf.h>
#include "decoder/decoder.h"
#include "render/pixelservice.h"
#include "codec/decoder.h"
namespace olive {
class OIIODecoder : public Decoder
{
Q_OBJECT
public:
OIIODecoder();
virtual QString id() override;
DECODER_DEFAULT_DESTRUCTOR(OIIODecoder)
virtual bool Probe(Footage *f) override;
virtual QString id() const override;
virtual bool Open() override;
virtual bool SupportsVideo() override{return true;}
virtual FramePtr RetrieveVideo(const rational &timecode) override;
virtual FootageDescription Probe(const QString& filename, const QAtomicInt* cancelled) const override;
virtual void Close() override;
virtual int64_t GetTimestampFromTime(const rational &time) override;
virtual bool SupportsVideo() override;
protected:
virtual bool OpenInternal() override;
virtual FramePtr RetrieveVideoInternal(const rational &timecode, const RetrieveVideoParams& divider, const QAtomicInt *cancelled) override;
virtual void CloseInternal() override;
private:
std::unique_ptr<OIIO::ImageInput> image_;
int width_;
static bool FileTypeIsSupported(const QString& fn);
int height_;
bool OpenImageHandler(const QString& fn);
olive::PixelFormat pix_fmt_;
void CloseImageHandle();
PixelFormatInfo pix_fmt_info_;
VideoParams::Format pix_fmt_;
bool is_rgba_;
int channel_count_;
FramePtr frame_;
OIIO::ImageBuf* buffer_;
static QStringList supported_formats_;
};
}
#endif // OIIODECODER_H
+81
View File
@@ -0,0 +1,81 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#include "oiioencoder.h"
#include "common/oiioutils.h"
namespace olive {
OIIOEncoder::OIIOEncoder(const EncodingParams &params) :
Encoder(params)
{
}
bool OIIOEncoder::Open()
{
return true;
}
bool OIIOEncoder::WriteFrame(FramePtr frame, rational time)
{
std::string filename = GetFilenameForFrame(time).toStdString();
auto output = OIIO::ImageOutput::create(filename);
if (!output) {
return false;
}
OIIO::TypeDesc type = OIIOUtils::GetOIIOBaseTypeFromFormat(frame->format());
OIIO::ImageSpec spec(frame->width(), frame->height(), frame->channel_count(), type);
if (!output->open(filename, spec)) {
return false;
}
if (!output->write_image(type, frame->data(), OIIO::AutoStride, frame->linesize_bytes())) {
return false;
}
if (!output->close()) {
return false;
}
return true;
}
bool OIIOEncoder::WriteAudio(SampleBufferPtr audio)
{
// Do nothing
return false;
}
bool OIIOEncoder::WriteSubtitle(const SubtitleBlock *sub_block)
{
return false;
}
void OIIOEncoder::Close()
{
// Do nothing
}
}
+47
View File
@@ -0,0 +1,47 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#ifndef OIIOENCODER_H
#define OIIOENCODER_H
#include "codec/encoder.h"
namespace olive {
class OIIOEncoder : public Encoder
{
Q_OBJECT
public:
OIIOEncoder(const EncodingParams &params);
public slots:
virtual bool Open() override;
virtual bool WriteFrame(olive::FramePtr frame, olive::rational time) override;
virtual bool WriteAudio(SampleBufferPtr audio) override;
virtual bool WriteSubtitle(const SubtitleBlock *sub_block) override;
virtual void Close() override;
};
}
#endif // OIIOENCODER_H
+119
View File
@@ -0,0 +1,119 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#include "planarfiledevice.h"
namespace olive {
PlanarFileDevice::PlanarFileDevice(QObject *parent) :
QObject(parent)
{
}
PlanarFileDevice::~PlanarFileDevice()
{
close();
}
bool PlanarFileDevice::open(const QVector<QString> &filenames, QIODevice::OpenMode mode)
{
if (isOpen()) {
// Already open
return false;
}
files_.resize(filenames.size());
files_.fill(nullptr);
for (int i=0; i<files_.size(); i++) {
files_[i] = new QFile(filenames.at(i));
if (!files_[i]->open(mode)) {
close();
return false;
}
}
return true;
}
qint64 PlanarFileDevice::read(char **data, qint64 bytes_per_channel, qint64 offset)
{
qint64 ret = -1;
if (isOpen()) {
for (int i=0; i<files_.size(); i++) {
// Kind of clunky but should be largely fine
ret = files_[i]->read(data[i] + offset, bytes_per_channel);
}
}
return ret;
}
qint64 PlanarFileDevice::write(const char **data, qint64 bytes_per_channel, qint64 offset)
{
qint64 ret = -1;
if (isOpen()) {
for (int i=0; i<files_.size(); i++) {
// Kind of clunky but should be largely fine
ret = files_[i]->write(data[i] + offset, bytes_per_channel);
}
}
return ret;
}
qint64 PlanarFileDevice::size() const
{
if (isOpen()) {
return files_.first()->size();
} else {
return 0;
}
}
bool PlanarFileDevice::seek(qint64 pos)
{
bool ret = true;
for (int i=0; i<files_.size(); i++) {
ret = files_[i]->seek(pos) & ret;
}
return ret;
}
void PlanarFileDevice::close()
{
for (int i=0; i<files_.size(); i++) {
QFile *f = files_.at(i);
if (f) {
if (f->isOpen()) {
f->close();
}
delete f;
}
}
files_.clear();
}
}
+64
View File
@@ -0,0 +1,64 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#ifndef PLANARFILEDEVICE_H
#define PLANARFILEDEVICE_H
#include <QFile>
#include <QObject>
#include "codec/samplebuffer.h"
#include "common/define.h"
namespace olive {
class PlanarFileDevice : public QObject
{
Q_OBJECT
public:
PlanarFileDevice(QObject *parent = nullptr);
virtual ~PlanarFileDevice() override;
bool isOpen() const
{
return !files_.isEmpty();
}
bool open(const QVector<QString> &filenames, QIODevice::OpenMode mode);
qint64 read(char **data, qint64 bytes_per_channel, qint64 offset = 0);
qint64 write(const char **data, qint64 bytes_per_channel, qint64 offset = 0);
qint64 size() const;
bool seek(qint64 pos);
void close();
private:
QVector<QFile*> files_;
};
}
#endif // PLANARFILEDEVICE_H
+231
View File
@@ -0,0 +1,231 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#include "samplebuffer.h"
namespace olive {
SampleBuffer::SampleBuffer() :
sample_count_per_channel_(0)
{
}
SampleBufferPtr SampleBuffer::Create()
{
return std::make_shared<SampleBuffer>();
}
SampleBufferPtr SampleBuffer::CreateAllocated(const AudioParams &audio_params, const rational &length)
{
return CreateAllocated(audio_params, audio_params.time_to_samples(length));
}
SampleBufferPtr SampleBuffer::CreateAllocated(const AudioParams &audio_params, int samples_per_channel)
{
SampleBufferPtr buffer = Create();
buffer->set_audio_params(audio_params);
buffer->set_sample_count(samples_per_channel);
buffer->allocate();
return buffer;
}
const AudioParams &SampleBuffer::audio_params() const
{
return audio_params_;
}
void SampleBuffer::set_audio_params(const AudioParams &params)
{
if (is_allocated()) {
qWarning() << "Tried to set parameters on allocated sample buffer";
return;
}
audio_params_ = params;
}
const int &SampleBuffer::sample_count() const
{
return sample_count_per_channel_;
}
void SampleBuffer::set_sample_count(const int &sample_count)
{
if (is_allocated()) {
qWarning() << "Tried to set sample count on allocated sample buffer";
return;
}
sample_count_per_channel_ = sample_count;
}
bool SampleBuffer::is_allocated() const
{
return !data_.isEmpty();
}
void SampleBuffer::allocate()
{
if (!audio_params_.is_valid()) {
qWarning() << "Tried to allocate sample buffer with invalid audio parameters";
return;
}
if (!sample_count_per_channel_) {
qWarning() << "Tried to allocate sample buffer with zero sample count";
return;
}
if (is_allocated()) {
qWarning() << "Tried to allocate already allocated sample buffer";
return;
}
data_.resize(audio_params_.channel_count());
for (int i=0; i<audio_params_.channel_count(); i++) {
data_[i].resize(sample_count_per_channel_);
}
update_raw();
}
void SampleBuffer::destroy()
{
data_.clear();
raw_ptrs_.clear();
}
void SampleBuffer::reverse()
{
if (!is_allocated()) {
qWarning() << "Tried to reverse an unallocated sample buffer";
return;
}
int half_nb_sample = sample_count_per_channel_ / 2;
for (int i=0;i<half_nb_sample;i++) {
int opposite_ind = sample_count_per_channel_ - i - 1;
for (int j=0;j<audio_params_.channel_count();j++) {
std::swap(data_[j][i], data_[j][opposite_ind]);
}
}
}
void SampleBuffer::speed(double speed)
{
if (!is_allocated()) {
qWarning() << "Tried to speed an unallocated sample buffer";
return;
}
sample_count_per_channel_ = qRound(static_cast<double>(sample_count_per_channel_) / speed);
QVector< QVector<float> > output_data;
output_data.resize(audio_params_.channel_count());
for (int i=0; i<audio_params_.channel_count(); i++) {
output_data[i].resize(sample_count_per_channel_);
}
for (int i=0;i<sample_count_per_channel_;i++) {
int input_index = qFloor(static_cast<double>(i) * speed);
for (int j=0;j<audio_params_.channel_count();j++) {
output_data[j][i] = data_[j][input_index];
}
}
data_ = output_data;
update_raw();
}
void SampleBuffer::transform_volume(float f)
{
for (int i=0;i<audio_params().channel_count();i++) {
for (int j=0;j<sample_count_per_channel_;j++) {
data_[i][j] *= f;
}
}
}
void SampleBuffer::transform_volume_for_channel(int channel, float volume)
{
for (int i=0;i<sample_count_per_channel_;i++) {
data_[channel][i] *= volume;
}
}
void SampleBuffer::transform_volume_for_sample(int sample_index, float volume)
{
for (int i=0;i<audio_params().channel_count();i++) {
data_[i][sample_index] *= volume;
}
}
void SampleBuffer::transform_volume_for_sample_on_channel(int sample_index, int channel, float volume)
{
data_[channel][sample_index] *= volume;
}
void SampleBuffer::silence()
{
silence(0, sample_count_per_channel_);
}
void SampleBuffer::silence(int start_sample, int end_sample)
{
silence_bytes(start_sample * sizeof(float), end_sample * sizeof(float));
}
void SampleBuffer::silence_bytes(int start_byte, int end_byte)
{
if (!is_allocated()) {
qWarning() << "Tried to fill an unallocated sample buffer";
return;
}
for (int i=0;i<audio_params().channel_count();i++) {
memset(reinterpret_cast<char*>(data_[i].data()) + start_byte, 0, end_byte - start_byte);
}
}
void SampleBuffer::set(int channel, const float *data, int sample_offset, int sample_length)
{
if (!is_allocated()) {
qWarning() << "Tried to fill an unallocated sample buffer";
return;
}
memcpy(&data_[channel].data()[sample_offset], data, sizeof(float) * sample_length);
}
void SampleBuffer::update_raw()
{
raw_ptrs_.resize(data_.size());
for (int i=0; i<raw_ptrs_.size(); i++) {
raw_ptrs_[i] = data_[i].data();
}
}
}
+110
View File
@@ -0,0 +1,110 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#ifndef SAMPLEBUFFER_H
#define SAMPLEBUFFER_H
#include <memory>
#include "render/audioparams.h"
namespace olive {
class SampleBuffer;
using SampleBufferPtr = std::shared_ptr<SampleBuffer>;
/**
* @brief A buffer of audio samples
*
* Audio samples in this structure are always stored in PLANAR (separated by channel). This is done to simplify audio
* rendering code. This replaces the old system of using QByteArrays (containing packed audio) and while SampleBuffer
* replaces many of those in the rendering/processing side of things, QByteArrays are currently still in use for
* playback, including reading to and from the cache.
*/
class SampleBuffer
{
public:
SampleBuffer();
static SampleBufferPtr Create();
static SampleBufferPtr CreateAllocated(const AudioParams& audio_params, const rational& length);
static SampleBufferPtr CreateAllocated(const AudioParams& audio_params, int samples_per_channel);
DISABLE_COPY_MOVE(SampleBuffer)
const AudioParams& audio_params() const;
void set_audio_params(const AudioParams& params);
const int &sample_count() const;
void set_sample_count(const int &sample_count);
float* data(int channel)
{
return data_[channel].data();
}
const float* data(int channel) const
{
return data_.at(channel).constData();
}
float **to_raw_ptrs()
{
return raw_ptrs_.data();
}
bool is_allocated() const;
void allocate();
void destroy();
void reverse();
void speed(double speed);
void transform_volume(float f);
void transform_volume_for_channel(int channel, float volume);
void transform_volume_for_sample(int sample_index, float volume);
void transform_volume_for_sample_on_channel(int sample_index, int channel, float volume);
void silence();
void silence(int start_sample, int end_sample);
void silence_bytes(int start_byte, int end_byte);
void set(int channel, const float* data, int sample_offset, int sample_length);
void set(int channel, const float* data, int sample_length)
{
set(channel, data, 0, sample_length);
}
private:
void update_raw();
AudioParams audio_params_;
int sample_count_per_channel_;
QVector< QVector<float> > data_;
QVector<float*> raw_ptrs_;
};
}
Q_DECLARE_METATYPE(olive::SampleBufferPtr)
#endif // SAMPLEBUFFER_H
+37 -10
View File
@@ -1,5 +1,5 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2019 Olive Team
# Copyright (C) 2021 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
@@ -16,25 +16,52 @@
set(OLIVE_SOURCES
${OLIVE_SOURCES}
common/bezier.cpp
common/bezier.h
common/cancelableobject.h
common/channellayout.h
common/clamp.h
common/debug.h
common/commandlineparser.cpp
common/commandlineparser.h
common/crashpadinterface.cpp
common/crashpadinterface.h
common/crashpadutils.h
common/debug.cpp
common/filefunctions.h
common/debug.h
common/decibel.h
common/define.h
common/ffmpegutils.cpp
common/ffmpegutils.h
common/filefunctions.cpp
common/flipmodifiers.h
common/filefunctions.h
common/flipmodifiers.cpp
common/flipmodifiers.h
common/functiontimer.h
common/jobtime.cpp
common/jobtime.h
common/lerp.h
common/memorypool.h
common/ocioutils.cpp
common/ocioutils.h
common/oiioutils.cpp
common/oiioutils.h
common/otioutils.h
common/qtutils.cpp
common/qtutils.h
common/range.h
common/rational.h
common/ratiodialog.cpp
common/ratiodialog.h
common/rational.cpp
common/qtversionabstraction.h
common/qtversionabstraction.cpp
common/threadedobject.h
common/rational.h
common/threadedobject.cpp
common/timecodefunctions.h
common/threadedobject.h
common/threadsafemap.h
common/timecodefunctions.cpp
common/timerange.h
common/timecodefunctions.h
common/timerange.cpp
common/timerange.h
common/tohex.h
common/xmlutils.cpp
common/xmlutils.h
PARENT_SCOPE
)
+39
View File
@@ -0,0 +1,39 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#ifndef AUTOSCROLL_H
#define AUTOSCROLL_H
#include "common/define.h"
namespace olive {
class AutoScroll {
public:
enum Method {
kNone,
kPage,
kSmooth
};
};
}
#endif // AUTOSCROLL_H
+110
View File
@@ -0,0 +1,110 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#include "bezier.h"
#include <QtMath>
#include "common/clamp.h"
namespace olive {
Bezier::Bezier() :
x_(0),
y_(0),
cp1_x_(0),
cp1_y_(0),
cp2_x_(0),
cp2_y_(0)
{
}
Bezier::Bezier(double x, double y) :
x_(x),
y_(y),
cp1_x_(0),
cp1_y_(0),
cp2_x_(0),
cp2_y_(0)
{
}
Bezier::Bezier(double x, double y, double cp1_x, double cp1_y, double cp2_x, double cp2_y) :
x_(x),
y_(y),
cp1_x_(cp1_x),
cp1_y_(cp1_y),
cp2_x_(cp2_x),
cp2_y_(cp2_y)
{
}
double Bezier::QuadraticXtoT(double x, double a, double b, double c)
{
// Clamp to prevent infinite loop
x = clamp(x, a, c);
return CalculateTFromX(false, x, a, b, c, 0);
}
double Bezier::QuadraticTtoY(double a, double b, double c, double t)
{
return qPow(1.0 - t, 2)*a + 2*(1.0 - t)*t*b + qPow(t, 2)*c;
}
double Bezier::CubicXtoT(double x, double a, double b, double c, double d)
{
// Clamp to prevent infinite loop
x = clamp(x, a, d);
return CalculateTFromX(true, x, a, b, c, d);
}
double Bezier::CubicTtoY(double a, double b, double c, double d, double t)
{
return qPow(1.0 - t, 3)*a + 3*qPow(1.0 - t, 2)*t*b + 3*(1.0 - t)*qPow(t, 2)*c + qPow(t, 3)*d;
}
double Bezier::CalculateTFromX(bool cubic, double x, double a, double b, double c, double d)
{
double bottom = 0.0;
double top = 1.0;
while (true) {
if (bottom == top) {
return bottom;
}
double mid = (bottom + top) * 0.5;
double test = cubic ? CubicTtoY(a, b, c, d, mid) : QuadraticTtoY(a, b, c, mid);
if (qAbs(test - x) < 0.000001) {
return mid;
} else if (x > test) {
bottom = mid;
} else {
top = mid;
}
}
return qSNaN();
}
}
+103
View File
@@ -0,0 +1,103 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#ifndef BEZIER_H
#define BEZIER_H
#include <QPointF>
#include <QObject>
#include "common/define.h"
namespace olive {
class Bezier
{
public:
Bezier();
Bezier(double x, double y);
Bezier(double x, double y, double cp1_x, double cp1_y, double cp2_x, double cp2_y);
const double &x() const {return x_; }
const double &y() const {return y_; }
const double &cp1_x() const { return cp1_x_; }
const double &cp1_y() const { return cp1_y_; }
const double &cp2_x() const { return cp2_x_; }
const double &cp2_y() const { return cp2_y_; }
QPointF ToPointF() const
{
return QPointF(x_, y_);
}
QPointF ControlPoint1ToPointF() const
{
return QPointF(cp1_x_, cp1_y_);
}
QPointF ControlPoint2ToPointF() const
{
return QPointF(cp2_x_, cp2_y_);
}
void set_x(const double &x) { x_ = x; }
void set_y(const double &y) { y_ = y; }
void set_cp1_x(const double &cp1_x) { cp1_x_ = cp1_x; }
void set_cp1_y(const double &cp1_y) { cp1_y_ = cp1_y; }
void set_cp2_x(const double &cp2_x) { cp2_x_ = cp2_x; }
void set_cp2_y(const double &cp2_y) { cp2_y_ = cp2_y; }
static double QuadraticXtoT(double x, double a, double b, double c);
static double QuadraticTtoY(double a, double b, double c, double t);
static double QuadraticXtoY(double x, const QPointF &a, const QPointF &b, const QPointF &c)
{
return QuadraticTtoY(a.y(), b.y(), c.y(), QuadraticXtoT(x, a.x(), b.x(), c.x()));
}
static double CubicXtoT(double x, double a, double b, double c, double d);
static double CubicTtoY(double a, double b, double c, double d, double t);
static double CubicXtoY(double x, const QPointF &a, const QPointF &b, const QPointF &c, const QPointF &d)
{
return CubicTtoY(a.y(), b.y(), c.y(), d.y(), CubicXtoT(x, a.x(), b.x(), c.x(), d.x()));
}
private:
static double CalculateTFromX(bool cubic, double x, double a, double b, double c, double d);
double x_;
double y_;
double cp1_x_;
double cp1_y_;
double cp2_x_;
double cp2_y_;
};
}
Q_DECLARE_METATYPE(olive::Bezier)
#endif // BEZIER_H
+58
View File
@@ -0,0 +1,58 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#ifndef CANCELABLEOBJECT_H
#define CANCELABLEOBJECT_H
#include <QAtomicInt>
#include "common/define.h"
namespace olive {
class CancelableObject {
public:
CancelableObject() :
cancelled_(false)
{
}
void Cancel()
{
cancelled_ = true;
CancelEvent();
}
const QAtomicInt& IsCancelled() const
{
return cancelled_;
}
protected:
virtual void CancelEvent(){}
private:
QAtomicInt cancelled_;
};
}
#endif // CANCELABLEOBJECT_H
+1 -1
View File
@@ -1,7 +1,7 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
Copyright (C) 2021 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
+1 -1
View File
@@ -1,7 +1,7 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
Copyright (C) 2021 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
+170
View File
@@ -0,0 +1,170 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#include "commandlineparser.h"
#include <QCoreApplication>
#include <QDebug>
CommandLineParser::~CommandLineParser()
{
foreach (const KnownOption& o, options_) {
delete o.option;
}
foreach (const KnownPositionalArgument& a, positional_args_) {
delete a.option;
}
}
const CommandLineParser::Option *CommandLineParser::AddOption(const QStringList &strings, const QString &description, bool takes_arg, const QString &arg_placeholder, bool hidden)
{
Option* o = new Option();
options_.append({strings, description, o, takes_arg, arg_placeholder, hidden});
return o;
}
const CommandLineParser::PositionalArgument *CommandLineParser::AddPositionalArgument(const QString &name, const QString &description, bool required)
{
PositionalArgument* a = new PositionalArgument();
positional_args_.append({name, description, a, required});
return a;
}
void CommandLineParser::Process(const QVector<QString> &argv)
{
int positional_index = 0;
for (int i=1; i<argv.size(); i++) {
if (argv[i][0] == '-') {
// Must be an option
// Skip past first dashes
QString arg_basename = argv[i].mid(1);
bool matched_known = false;
for (int j=0; j<options_.size(); j++) {
KnownOption& o = options_[j];
foreach (const QString& s, o.args) {
if (!s.compare(arg_basename, Qt::CaseInsensitive)) {
// Flag discovered!
o.option->Set();
if (o.takes_arg && i+1 < argv.size()) {
o.option->SetSetting(argv[i+1]);
i++;
}
matched_known = true;
goto found_flag;
}
}
}
found_flag:
if (!matched_known) {
qWarning() << "Unknown parameter:" << argv[i];
}
} else {
// Must be a positional flag
if (positional_index < positional_args_.size()) {
positional_args_[positional_index].option->SetSetting(argv[i]);
positional_index++;
} else {
qWarning() << "Unknown parameter:" << argv[i];
}
}
}
}
void CommandLineParser::PrintHelp(const char* filename)
{
printf("%s %s\n",
QCoreApplication::applicationName().toUtf8().constData(),
QCoreApplication::applicationVersion().toUtf8().constData());
printf("Copyright (C) 2018-2021 Olive Team\n");
QString positional_args;
for (int i=0; i<positional_args_.size(); i++) {
if (i > 0) {
positional_args.append(' ');
}
positional_args.append('[');
positional_args.append(positional_args_.at(i).name);
positional_args.append(']');
}
const char* basename;
#ifdef Q_OS_WINDOWS
basename = strrchr(filename, '\\');
if (!basename) {
basename = strrchr(filename, '/');
}
#else
basename = strrchr(filename, '/');
#endif
if (basename) {
// Slash found, increment pointer to avoid showing the slash itself
basename++;
} else {
// If no slashes are found, assume string is already a basename
basename = filename;
}
printf("Usage: %s [options] %s\n\n", basename, positional_args.toUtf8().constData());
foreach (const KnownOption& o, options_) {
if (o.hidden) {
continue;
}
QString all_args;
for (int i=0; i<o.args.size(); i++) {
if (i > 0) {
all_args.append(QStringLiteral(", "));
}
const QString& this_arg = o.args.at(i);
all_args.append('-');
all_args.append(this_arg);
}
if (o.arg_placeholder.isEmpty()) {
printf(" %s\n", all_args.toUtf8().constData());
} else {
printf(" %s <%s>\n", all_args.toUtf8().constData(), o.arg_placeholder.toUtf8().constData());
}
printf(" %s\n\n", o.description.toUtf8().constData());
}
printf("\n");
}
+121
View File
@@ -0,0 +1,121 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#ifndef COMMANDLINEPARSER_H
#define COMMANDLINEPARSER_H
#include <QStringList>
#include <QVector>
#include "common/define.h"
/**
* @brief Command-line argument parser
*
* You may be wondering why we don't use QCommandLineParser instead of a custom implementation like
* this. The reason why is because QCommandLineParser requires a QApplication object of some kind
* to already have been created before it can parse anything, but we need to be able to control
* whether a QApplication (GUI-mode) or a QCoreApplication (CLI-mode) is created which is set by
* the user as a command line argument. Therefore we needed a custom implementation that could
* parse arguments without the need for a Q(Core)Application to be present already.
*/
class CommandLineParser
{
public:
~CommandLineParser();
DISABLE_COPY_MOVE(CommandLineParser)
class PositionalArgument
{
public:
PositionalArgument() = default;
const QString& GetSetting() const
{
return setting_;
}
void SetSetting(const QString& s)
{
setting_ = s;
}
private:
QString setting_;
};
class Option : public PositionalArgument {
public:
Option()
{
is_set_ = false;
}
bool IsSet() const
{
return is_set_;
}
void Set()
{
is_set_ = true;
}
private:
bool is_set_;
};
CommandLineParser() = default;
const Option* AddOption(const QStringList& strings, const QString& description, bool takes_arg = false, const QString& arg_placeholder = QString(), bool hidden = false);
const PositionalArgument* AddPositionalArgument(const QString& name, const QString& description, bool required = false);
void Process(const QVector<QString> &argv);
void PrintHelp(const char* filename);
private:
struct KnownOption {
QStringList args;
QString description;
Option* option;
bool takes_arg;
QString arg_placeholder;
bool hidden;
};
struct KnownPositionalArgument {
QString name;
QString description;
PositionalArgument* option;
bool required;
};
QVector<KnownOption> options_;
QVector<KnownPositionalArgument> positional_args_;
};
#endif // COMMANDLINEPARSER_H
-21
View File
@@ -1,21 +0,0 @@
#ifndef CONSTRUCTORS_H
#define CONSTRUCTORS_H
/**
* Copy/move deleters. Similar to Q_DISABLE_COPY_MOVE, et al. but those functions are not present in Qt < 5.13 so we
* use our own functions for portability.
*/
#define DISABLE_COPY(Class) \
Class(const Class &) = delete;\
Class &operator=(const Class &) = delete;
#define DISABLE_MOVE(Class) \
Class(Class &&) = delete; \
Class &operator=(Class &&) = delete;
#define DISABLE_COPY_MOVE(Class) \
DISABLE_COPY(Class) \
DISABLE_MOVE(Class)
#endif // CONSTRUCTORS_H
@@ -1,7 +1,7 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
Copyright (C) 2021 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
@@ -18,12 +18,13 @@
***/
#include "qtversionabstraction.h"
#ifndef CPUOPTIMIZE_H
#define CPUOPTIMIZE_H
int QFontMetricsWidth(QFontMetrics fm, const QString& s) {
#if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
return fm.width(s);
#else
return fm.horizontalAdvance(s);
#if defined(Q_PROCESSOR_X86)
#include <xmmintrin.h>
#elif defined(Q_PROCESSOR_ARM)
#include <sse2neon.h>
#endif
}
#endif // CPUOPTIMIZE_H
+91
View File
@@ -0,0 +1,91 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#include "crashpadinterface.h"
#ifdef USE_CRASHPAD
#include <QCoreApplication>
#include <QDateTime>
#include <QDebug>
#include <QDir>
#include <QMessageBox>
#include <QProcess>
#include "crashpadutils.h"
#include "filefunctions.h"
#ifdef OS_WIN
#include <Windows.h>
#endif
crashpad::CrashpadClient *client;
bool InitializeCrashpad()
{
QString report_path = QDir(olive::FileFunctions::GetTempFilePath()).filePath(QStringLiteral("reports"));
QString handler_fn = olive::FileFunctions::GetFormattedExecutableForPlatform(QStringLiteral("crashpad_handler"));
// Generate absolute path
QString handler_abs_path = QDir(QCoreApplication::applicationDirPath()).filePath(handler_fn);
bool status = false;
if (QFileInfo::exists(handler_abs_path)) {
base::FilePath handler(QSTRING_TO_BASE_STRING(handler_abs_path));
base::FilePath reports_dir(QSTRING_TO_BASE_STRING(report_path));
base::FilePath metrics_dir(QSTRING_TO_BASE_STRING(QDir(olive::FileFunctions::GetTempFilePath()).filePath(QStringLiteral("metrics"))));
// Metadata that will be posted to the server with the crash report map
std::map<std::string, std::string> annotations;
// Disable crashpad rate limiting so that all crashes have dmp files
std::vector<std::string> arguments;
arguments.push_back("--no-rate-limit");
arguments.push_back("--no-upload-gzip");
// Initialize Crashpad database
std::unique_ptr<crashpad::CrashReportDatabase> database = crashpad::CrashReportDatabase::Initialize(reports_dir);
if (database == NULL) return false;
// Disable automated crash uploads
crashpad::Settings *settings = database->GetSettings();
if (settings == NULL) return false;
settings->SetUploadsEnabled(false);
// Start crash handler
client = new crashpad::CrashpadClient();
status = client->StartHandler(handler, reports_dir, metrics_dir,
"https://olivevideoeditor.org/crashpad/report.php",
annotations, arguments, true, true);
}
// Override Crashpad exception filter with our own
if (!status) {
qWarning() << "Failed to start Crashpad, automatic crash reporting will be disabled";
}
return status;
}
#endif // USE_CRASHPAD
@@ -1,7 +1,7 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
Copyright (C) 2021 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
@@ -18,22 +18,16 @@
***/
#include "taskthread.h"
#ifndef CRASHPAD_INTERFACE_H
#define CRASHPAD_INTERFACE_H
#include "task/task.h"
#ifdef USE_CRASHPAD
TaskThread::TaskThread(Task *parent) :
parent_(parent),
result_(false)
{
}
#include <client/crash_report_database.h>
#include <client/settings.h>
void TaskThread::run()
{
result_ = parent_->Action();
}
bool InitializeCrashpad();
bool TaskThread::result()
{
return result_;
}
#endif // USE_CRASHPAD
#endif // CRASHPAD_INTERFACE_H
+40
View File
@@ -0,0 +1,40 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#ifndef CRASHPADUTILS_H
#define CRASHPADUTILS_H
#include <client/crashpad_client.h>
// Copied from base::FilePath to match its macro
#if defined(OS_POSIX)
// On most platforms, native pathnames are char arrays, and the encoding
// may or may not be specified. On Mac OS X, native pathnames are encoded
// in UTF-8.
#define QSTRING_TO_BASE_STRING(x) x.toStdString()
#define BASE_STRING_TO_QSTRING(x) QString::fromStdString(x)
#elif defined(OS_WIN)
// On Windows, for Unicode-aware applications, native pathnames are wchar_t
// arrays encoded in UTF-16.
#define QSTRING_TO_BASE_STRING(x) x.toStdWString()
#define BASE_STRING_TO_QSTRING(x) QString::fromStdWString(x)
#endif // OS_WIN
#endif // CRASHPADUTILS_H
+23
View File
@@ -1,5 +1,26 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#include "debug.h"
namespace olive {
void DebugHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
@@ -32,3 +53,5 @@ void DebugHandler(QtMsgType type, const QMessageLogContext &context, const QStri
fflush(stderr);
#endif
}
}
+26
View File
@@ -1,8 +1,34 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2021 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/>.
***/
#ifndef DEBUG_H
#define DEBUG_H
#include <QDebug>
#include "common/define.h"
namespace olive {
void DebugHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg);
}
#endif // DEBUG_H
-11
View File
@@ -1,11 +0,0 @@
#include "decibel.h"
#include <QtMath>
double amplitude_to_db(double amplitude) {
return (20.0*(qLn(amplitude)/qLn(10.0)));
}
double db_to_amplitude(double db) {
return qPow(M_E, (db*qLn(10.0))/20.0);
}

Some files were not shown because too many files have changed in this diff Show More