diff --git a/.github/ISSUE_TEMPLATE/01-crash_issue.md b/.github/ISSUE_TEMPLATE/01-crash_issue.md index c05544838..0b68b7202 100644 --- a/.github/ISSUE_TEMPLATE/01-crash_issue.md +++ b/.github/ISSUE_TEMPLATE/01-crash_issue.md @@ -6,7 +6,7 @@ labels: "Crash, Triage" assignees: '' --- -**Commit Hash** +**Commit Hash (8 character string of letters/numbers in title bar)** **Platform** diff --git a/.github/ISSUE_TEMPLATE/50-build_issue.md b/.github/ISSUE_TEMPLATE/50-build_issue.md index f14b6c914..8d93ad06d 100644 --- a/.github/ISSUE_TEMPLATE/50-build_issue.md +++ b/.github/ISSUE_TEMPLATE/50-build_issue.md @@ -6,7 +6,7 @@ labels: "Building/Packaging, Triage" assignees: '' --- -**Commit Hash** +**Commit Hash (8 character string of letters/numbers in title bar)** **Platform** diff --git a/.github/ISSUE_TEMPLATE/50-cache_issue.md b/.github/ISSUE_TEMPLATE/50-cache_issue.md index 13b8bc14b..0d088aa8c 100644 --- a/.github/ISSUE_TEMPLATE/50-cache_issue.md +++ b/.github/ISSUE_TEMPLATE/50-cache_issue.md @@ -6,7 +6,7 @@ labels: "Disk Cache, Triage" assignees: '' --- -**Commit Hash** +**Commit Hash (8 character string of letters/numbers in title bar)** **Platform** diff --git a/.github/ISSUE_TEMPLATE/50-codec_issue.md b/.github/ISSUE_TEMPLATE/50-codec_issue.md index a3a936915..04082833f 100644 --- a/.github/ISSUE_TEMPLATE/50-codec_issue.md +++ b/.github/ISSUE_TEMPLATE/50-codec_issue.md @@ -6,7 +6,7 @@ labels: "Codec, Triage" assignees: '' --- -**Commit Hash** +**Commit Hash (8 character string of letters/numbers in title bar)** **Platform** diff --git a/.github/ISSUE_TEMPLATE/50-color_issue.md b/.github/ISSUE_TEMPLATE/50-color_issue.md index 6a7464ea3..99889a56b 100644 --- a/.github/ISSUE_TEMPLATE/50-color_issue.md +++ b/.github/ISSUE_TEMPLATE/50-color_issue.md @@ -6,7 +6,7 @@ labels: "Color Management, Triage" assignees: '' --- -**Commit Hash** +**Commit Hash (8 character string of letters/numbers in title bar)** **Platform** diff --git a/.github/ISSUE_TEMPLATE/50-editing_issue.md b/.github/ISSUE_TEMPLATE/50-editing_issue.md index c731e221f..0465e88a5 100644 --- a/.github/ISSUE_TEMPLATE/50-editing_issue.md +++ b/.github/ISSUE_TEMPLATE/50-editing_issue.md @@ -6,7 +6,7 @@ labels: "Timeline/Editing, Triage" assignees: '' --- -**Commit Hash** +**Commit Hash (8 character string of letters/numbers in title bar)** **Platform** diff --git a/.github/ISSUE_TEMPLATE/50-export_issue.md b/.github/ISSUE_TEMPLATE/50-export_issue.md index e21a79294..7ccef22fc 100644 --- a/.github/ISSUE_TEMPLATE/50-export_issue.md +++ b/.github/ISSUE_TEMPLATE/50-export_issue.md @@ -6,7 +6,7 @@ labels: "Export, Triage" assignees: '' --- -**Commit Hash** +**Commit Hash (8 character string of letters/numbers in title bar)** **Platform** diff --git a/.github/ISSUE_TEMPLATE/50-node_issue.md b/.github/ISSUE_TEMPLATE/50-node_issue.md index 7749521ba..4d15ecebe 100644 --- a/.github/ISSUE_TEMPLATE/50-node_issue.md +++ b/.github/ISSUE_TEMPLATE/50-node_issue.md @@ -6,7 +6,7 @@ labels: "Nodes/Compositing, Triage" assignees: '' --- -**Commit Hash** +**Commit Hash (8 character string of letters/numbers in title bar)** **Platform** diff --git a/.github/ISSUE_TEMPLATE/50-playback_issue.md b/.github/ISSUE_TEMPLATE/50-playback_issue.md index 00aa98149..5e35446f5 100644 --- a/.github/ISSUE_TEMPLATE/50-playback_issue.md +++ b/.github/ISSUE_TEMPLATE/50-playback_issue.md @@ -6,7 +6,7 @@ labels: "Playback, Triage" assignees: '' --- -**Commit Hash** +**Commit Hash (8 character string of letters/numbers in title bar)** **Platform** diff --git a/.github/ISSUE_TEMPLATE/50-project_issue.md b/.github/ISSUE_TEMPLATE/50-project_issue.md index 9e151539c..fa65f393e 100644 --- a/.github/ISSUE_TEMPLATE/50-project_issue.md +++ b/.github/ISSUE_TEMPLATE/50-project_issue.md @@ -6,7 +6,7 @@ labels: "Project, Triage" assignees: '' --- -**Commit Hash** +**Commit Hash (8 character string of letters/numbers in title bar)** **Platform** diff --git a/.github/ISSUE_TEMPLATE/50-renderer_issue.md b/.github/ISSUE_TEMPLATE/50-renderer_issue.md index 3d6812004..f97a22079 100644 --- a/.github/ISSUE_TEMPLATE/50-renderer_issue.md +++ b/.github/ISSUE_TEMPLATE/50-renderer_issue.md @@ -6,7 +6,7 @@ labels: "Renderer, Triage" assignees: '' --- -**Commit Hash** +**Commit Hash (8 character string of letters/numbers in title bar)** **Platform** diff --git a/.github/ISSUE_TEMPLATE/50-ui_issue.md b/.github/ISSUE_TEMPLATE/50-ui_issue.md index 12af9f863..a0cfa3c02 100644 --- a/.github/ISSUE_TEMPLATE/50-ui_issue.md +++ b/.github/ISSUE_TEMPLATE/50-ui_issue.md @@ -6,7 +6,7 @@ labels: "User Interface, Triage" assignees: '' --- -**Commit Hash** +**Commit Hash (8 character string of letters/numbers in title bar)** **Platform** diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 310a45902..07ebcbdd7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,39 +1,62 @@ -name: CMake +name: CI on: push: branches: - master -env: - # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) - BUILD_TYPE: RelWithDebInfo - jobs: - build: - # The CMake configure and build commands are platform agnostic and should work equally - # well on Windows or Mac. You can convert this to a matrix build if you need - # cross-platform coverage. - # See: https://docs.github.com/en/actions/configuring-and-managing-workflows/configuring-a-workflow#configuring-a-build-matrix - runs-on: windows-latest + windows: + strategy: + matrix: + build: [1] + include: + - build: 1 + build-type: RelWithDebInfo + compiler-name: MSVC 16.x + os-name: Windows + os-arch: x86_64 + os: windows-latest + cmake-gen: Ninja + dep-suffix: win.7z + extract-tool: 7z x + - build: 2 + build-type: RelWithDebInfo + compiler-name: Clang LLVM + os-name: macOS + os-arch: x86_64 + os: macos-latest + cmake-gen: Unix Makefiles + dep-suffix: mac.zip + extract-tool: sudo unzip -d /usr/local -o + name: | + ${{ matrix.os-name }} + <${{ matrix.compiler-name }}, + ${{ matrix.build-type }}> + runs-on: ${{ matrix.os }} env: - PKGNAME: Olive-${GITHUB_SHA:8}-Windows-x86_64 + DOWNLOAD_TOOL: curl -fLOSs --retry 3 steps: - - name: Set package name + - name: Checkout Source Code + uses: actions/checkout@v2 + + - name: Automatically Generate Package Name shell: bash - run: echo "::set-env name=PKGNAME::$(echo Olive-${GITHUB_SHA::8}-Windows-x86_64)" + env: + PLATFORM: ${{ matrix.os-name }} + ARCH: ${{ matrix.os-arch }} + run: | + echo "::set-env name=PKGNAME::$(echo Olive-${GITHUB_SHA::8}-$PLATFORM-$ARCH)" - - uses: actions/checkout@v2 + - name: Create Build Folder + run: | + cmake -E make_directory ${{ runner.workspace }}/build - - name: Create Build Environment - # Some projects don't allow in-source building, so create a separate build directory - # We'll use this as our working directory for all subsequent commands - run: cmake -E make_directory ${{runner.workspace}}/build - - - name: Enable Developer Command Prompt + - name: Enable Developer Command Prompt (Windows) uses: ilammy/msvc-dev-cmd@v1.3.0 + if: matrix.os == 'windows-latest' - name: Acquire Qt uses: jurplel/install-qt-action@v2.8.0 @@ -41,102 +64,183 @@ jobs: version: 5.15.1 - name: Acquire FFmpeg - run: curl -fLOSs --retry 3 http://olivevideoeditor.org/deps/ffmpeg-win.7z && 7z x ffmpeg-win.7z + shell: bash + env: + DEP_SUFFIX: ${{ matrix.dep-suffix }} + EXTRACT_TOOL: ${{ matrix.extract-tool }} + run: | + $DOWNLOAD_TOOL http://olivevideoeditor.org/deps/ffmpeg-$DEP_SUFFIX + $EXTRACT_TOOL ffmpeg-$DEP_SUFFIX - name: Acquire OpenColorIO - run: curl -fLOSs --retry 3 http://olivevideoeditor.org/deps/ocio-win.7z && 7z x ocio-win.7z + shell: bash + env: + DEP_SUFFIX: ${{ matrix.dep-suffix }} + EXTRACT_TOOL: ${{ matrix.extract-tool }} + run: | + $DOWNLOAD_TOOL http://olivevideoeditor.org/deps/ocio-$DEP_SUFFIX + $EXTRACT_TOOL ocio-$DEP_SUFFIX - name: Acquire OpenEXR - run: curl -fLOSs --retry 3 http://olivevideoeditor.org/deps/openexr-win.7z && 7z x openexr-win.7z + shell: bash + env: + DEP_SUFFIX: ${{ matrix.dep-suffix }} + EXTRACT_TOOL: ${{ matrix.extract-tool }} + run: | + $DOWNLOAD_TOOL http://olivevideoeditor.org/deps/openexr-$DEP_SUFFIX + $EXTRACT_TOOL openexr-$DEP_SUFFIX - name: Acquire OpenImageIO - run: curl -fLOSs --retry 3 http://olivevideoeditor.org/deps/oiio-win.7z && 7z x oiio-win.7z - - - name: Acquire libpng - run: curl -fLOSs --retry 3 http://olivevideoeditor.org/deps/libpng-win.7z && 7z x libpng-win.7z - - - name: Acquire libtiff - run: curl -fLOSs --retry 3 http://olivevideoeditor.org/deps/libtiff-win.7z && 7z x libtiff-win.7z - - - name: Acquire turbojpeg - run: curl -fLOSs --retry 3 http://olivevideoeditor.org/deps/turbojpeg-win.7z && 7z x turbojpeg-win.7z - - - name: Acquire zlib - run: curl -fLOSs --retry 3 http://olivevideoeditor.org/deps/zlib-win.7z && 7z x zlib-win.7z - - - name: Acquire Boost - run: curl -fLOSs --retry 3 http://olivevideoeditor.org/deps/boost-win.7z && 7z x boost-win.7z + shell: bash + env: + DEP_SUFFIX: ${{ matrix.dep-suffix }} + EXTRACT_TOOL: ${{ matrix.extract-tool }} + run: | + $DOWNLOAD_TOOL http://olivevideoeditor.org/deps/oiio-$DEP_SUFFIX + $EXTRACT_TOOL oiio-$DEP_SUFFIX - name: Acquire Crashpad - run: curl -fLOSs --retry 3 http://olivevideoeditor.org/deps/crashpad-win.7z && 7z x crashpad-win.7z + shell: bash + env: + DEP_SUFFIX: ${{ matrix.dep-suffix }} + EXTRACT_TOOL: ${{ matrix.extract-tool }} + run: | + $DOWNLOAD_TOOL http://olivevideoeditor.org/deps/crashpad-$DEP_SUFFIX + $EXTRACT_TOOL crashpad-$DEP_SUFFIX - name: Configure CMake - # Use a bash shell so we can use the same syntax for environment variable - # access regardless of the host operating system shell: bash - working-directory: ${{runner.workspace}}/build - # Note the current convention is to use the -S and -B options here to specify source - # and build directories, but this is only available with CMake 3.13 and higher. - # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 - run: PATH=$GITHUB_WORKSPACE:$PATH cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -G "Ninja" + working-directory: ${{ runner.workspace }}/build + env: + CMAKE_GEN: ${{ matrix.cmake-gen }} + run: | + PATH=$GITHUB_WORKSPACE:$PATH \ + cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} -G "$CMAKE_GEN" - name: Build - working-directory: ${{runner.workspace}}/build + working-directory: ${{ runner.workspace }}/build shell: bash - # Execute the build. You can specify a specific target with "--target " - run: cmake --build . --config $BUILD_TYPE + run: | + cmake --build . - name: Test - working-directory: ${{runner.workspace}}/build + working-directory: ${{ runner.workspace }}/build shell: bash - # Execute tests defined by the CMake configuration. - # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest -C $BUILD_TYPE + run: | + ctest -C ${{ matrix.build-type }} - name: Create Package - working-directory: ${{runner.workspace}}/build - shell: cmd - run: mkdir olive-editor && copy app\olive-editor.exe olive-editor && copy app\olive-editor.pdb olive-editor && windeployqt olive-editor\olive-editor.exe && copy %GITHUB_WORKSPACE%\bin\*.dll olive-editor && copy %GITHUB_WORKSPACE%\out\Default\crashpad_handler.exe olive-editor + working-directory: ${{ runner.workspace }}/build + shell: bash + env: + MATRIX_OS: ${{ matrix.os }} + run: | + if [ "$MATRIX_OS" == "windows-latest" ] + then + mkdir olive-editor + cp app/olive-editor.exe olive-editor + cp app/olive-editor.pdb olive-editor + windeployqt olive-editor/olive-editor.exe + cp $(cygpath $GITHUB_WORKSPACE)/bin/*.dll olive-editor + cp $(cygpath $GITHUB_WORKSPACE)/out/Default/crashpad_handler.exe olive-editor + elif [ "$MATRIX_OS" == "macos-latest" ] + then + export BUNDLE_NAME=Olive.app + mv app/$BUNDLE_NAME . + macdeployqt $BUNDLE_NAME + $DOWNLOAD_TOOL https://github.com/arl/macdeployqtfix/raw/master/macdeployqtfix.py + python2 macdeployqtfix.py $BUNDLE_NAME/Contents/MacOS/Olive /usr/local/Cellar/qt5/5.*/ + fi - - name: Deploy Installer - working-directory: ${{runner.workspace}}/build - shell: cmd - run: curl -fLOSs https://download.microsoft.com/download/3/2/2/3224B87F-CFA0-4E70-BDA3-3DE650EFEBA5/vcredist_x64.exe && copy %GITHUB_WORKSPACE%\app\packaging\windows\nsis\* . && copy %GITHUB_WORKSPACE%\LICENSE . && "C:/Program Files (x86)/NSIS/makensis.exe" -V4 -DX64 "-XOutFile %PKGNAME%.exe" olive.nsi + - name: Deploy Packages + working-directory: ${{ runner.workspace }}/build + shell: bash + env: + MATRIX_OS: ${{ matrix.os }} + run: | + if [ "$MATRIX_OS" == "windows-latest" ] + then + # Create Installer Executable + curl -fLOSs https://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 . + makensis -V4 -DX64 "-XOutFile $PKGNAME.exe" olive.nsi - - name: Deploy Portable - working-directory: ${{runner.workspace}}/build - shell: cmd - run: type NUL > olive-editor\portable && 7z a %PKGNAME%.zip olive-editor + # Create Portable ZIP + echo -n > olive-editor/portable + 7z a $PKGNAME.zip olive-editor + elif [ "$MATRIX_OS" == "macos-latest" ] + then + zip -r $PKGNAME.zip Olive.app + fi + + - name: Upload Packages to Nightly Page + shell: bash + working-directory: ${{ runner.workspace }}/build + env: + GH_AUTH_KEY: ${{ secrets.GH_AUTH_KEY }} + MATRIX_OS: ${{ matrix.os }} + run: | + if [ "$MATRIX_OS" == "windows-latest" ] + then + curl -X POST \ + -F file=@$PKGNAME.exe \ + -F key="$GH_AUTH_KEY" \ + -F hash="$GITHUB_SHA" \ + -F time="$(git -C $GITHUB_WORKSPACE show -s --format=%ct)" \ + -F comment="$(git -C $GITHUB_WORKSPACE show -s --format=%B)" \ + https://olivevideoeditor.org/dl/push.php + + curl -X POST \ + -F file=@$PKGNAME.zip \ + -F key="$GH_AUTH_KEY" \ + -F hash="$GITHUB_SHA" \ + -F time="$(git -C $GITHUB_WORKSPACE show -s --format=%ct)" \ + -F comment="$(git -C $GITHUB_WORKSPACE show -s --format=%B)" \ + https://olivevideoeditor.org/dl/push.php + elif [ "$MATRIX_OS" == "macos-latest" ] + then + curl -X POST \ + -F file=@$PKGNAME.zip \ + -F key="$GH_AUTH_KEY" \ + -F hash="$GITHUB_SHA" \ + -F time="$(git -C $GITHUB_WORKSPACE show -s --format=%ct)" \ + -F comment="$(git -C $GITHUB_WORKSPACE show -s --format=%B)" \ + https://olivevideoeditor.org/dl/push.php + fi + if: github.event_name != 'pull_request' + + - name: Upload Installer Artifact to GitHub (Windows) + uses: actions/upload-artifact@v2 + with: + name: ${{ env.PKGNAME }}.exe + path: ${{ runner.workspace }}/build/${{ env.PKGNAME }}.exe + if: matrix.os == 'windows-latest' + + - name: Upload Portable Artifact to GitHub (Windows) + uses: actions/upload-artifact@v2 + with: + name: ${{ env.PKGNAME }}.zip + path: ${{ runner.workspace }}/build/${{ env.PKGNAME }}.zip + if: matrix.os == 'windows-latest' + + - name: Upload Artifact to GitHub (macOS) + uses: actions/upload-artifact@v2 + with: + name: ${{ env.PKGNAME }}.zip + path: ${{ runner.workspace }}/build/${{ env.PKGNAME }}.zip + if: matrix.os == 'macos-latest' - name: Export Crashpad Symbols and Upload To Server - working-directory: ${{runner.workspace}}/build - shell: cmd - env: - GH_AUTH_KEY: ${{secrets.GH_AUTH_KEY}} - run: curl -fLSs --retry 3 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 && curl -X POST -F symfile=@olive-editor.sym -F key="%GH_AUTH_KEY%" https://olivevideoeditor.org/crashpad/symbols.php - - - name: Upload Installer to Nightly Page + working-directory: ${{ runner.workspace }}/build shell: bash - working-directory: ${{runner.workspace}}/build env: - GH_AUTH_KEY: ${{secrets.GH_AUTH_KEY}} - run: curl -X POST -F file=@$PKGNAME.exe -F key="$GH_AUTH_KEY" -F hash="$GITHUB_SHA" -F time="$(git -C $GITHUB_WORKSPACE show -s --format=%ct)" -F comment="$(git -C $GITHUB_WORKSPACE show -s --format=%B)" https://olivevideoeditor.org/dl/push.php - - - name: Upload Portable to Nightly Page - shell: bash - working-directory: ${{runner.workspace}}/build - env: - GH_AUTH_KEY: ${{secrets.GH_AUTH_KEY}} - run: curl -X POST -F file=@$PKGNAME.zip -F key="$GH_AUTH_KEY" -F hash="$GITHUB_SHA" -F time="$(git -C $GITHUB_WORKSPACE show -s --format=%ct)" -F comment="$(git -C $GITHUB_WORKSPACE show -s --format=%B)" https://olivevideoeditor.org/dl/push.php - - - name: Upload Installer Artifact - uses: actions/upload-artifact@v2 - with: - name: ${{env.PKGNAME}}.exe - path: ${{runner.workspace}}/build/${{env.PKGNAME}}.exe - - - name: Upload Portable Artifact - uses: actions/upload-artifact@v2 - with: - name: ${{env.PKGNAME}}.zip - path: ${{runner.workspace}}/build/${{env.PKGNAME}}.zip + GH_AUTH_KEY: ${{ secrets.GH_AUTH_KEY }} + run: | + curl -fLSs --retry 3 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 + curl -X POST \ + -F symfile=@olive-editor.sym \ + -F key="$GH_AUTH_KEY" \ + https://olivevideoeditor.org/crashpad/symbols.php + if: matrix.os == 'windows-latest' diff --git a/app/audio/audiovisualwaveform.cpp b/app/audio/audiovisualwaveform.cpp index 08c6c27b0..9cba6f755 100644 --- a/app/audio/audiovisualwaveform.cpp +++ b/app/audio/audiovisualwaveform.cpp @@ -37,6 +37,7 @@ void AudioVisualWaveform::OverwriteSamples(SampleBufferPtr samples, int sample_r { if (!channels_) { qWarning() << "Failed to write samples - channel count is zero"; + return; } int start_index = time_to_samples(start); diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 096cfcd07..46cac10fc 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -477,14 +477,19 @@ bool FFmpegDecoder::Probe(Footage *f, const QAtomicInt* cancelled) // Retrieve metadata about the media avformat_find_stream_info(fmt_ctx, nullptr); + QVector streams(fmt_ctx->nb_streams); + // Dump it into the Footage object for (unsigned int i=0;inb_streams;i++) { AVStream* avstream = fmt_ctx->streams[i]; + // Find decoder for this stream, if it exists we can proceed + AVCodec* decoder = avcodec_find_decoder(avstream->codecpar->codec_id); + StreamPtr str; - if (avstream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + if (avstream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && decoder) { bool image_is_still = false; rational pixel_aspect_ratio; @@ -564,7 +569,7 @@ bool FFmpegDecoder::Probe(Footage *f, const QAtomicInt* cancelled) str = image_stream; - } else if (avstream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { + } else if (avstream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && decoder) { // Create an audio stream object AudioStreamPtr audio_stream = std::make_shared(); @@ -600,7 +605,7 @@ bool FFmpegDecoder::Probe(Footage *f, const QAtomicInt* cancelled) str->set_type(Stream::kAttachment); break; default: - // We should never realistically get here, but we make an "invalid" stream just in case + // Fallback to an unknown stream str->set_type(Stream::kUnknown); break; } @@ -611,11 +616,27 @@ bool FFmpegDecoder::Probe(Footage *f, const QAtomicInt* cancelled) str->set_timebase(avstream->time_base); str->set_duration(avstream->duration); - f->add_stream(str); + streams[i] = str; } - // As long as we can open the container and retrieve information, this was a successful probe - result = true; + // Check if we could pick up any streams in this file + bool found_valid_streams = false; + + foreach (StreamPtr stream, streams) { + if (stream->type() != Stream::kUnknown) { + found_valid_streams = true; + break; + } + } + + if (found_valid_streams) { + // Copy streams over + foreach (StreamPtr stream, streams) { + f->add_stream(stream); + } + + result = true; + } } // Free all memory @@ -1330,6 +1351,7 @@ void FFmpegDecoderInstance::RemoveFirstFrame() FFmpegDecoderInstance::FFmpegDecoderInstance(const char *filename, int stream_index) : fmt_ctx_(nullptr), + codec_ctx_(nullptr), opts_(nullptr), scale_ctx_(nullptr), scale_divider_(0), diff --git a/app/common/crashpadinterface.cpp b/app/common/crashpadinterface.cpp index 6c55ab45e..770e8a84a 100644 --- a/app/common/crashpadinterface.cpp +++ b/app/common/crashpadinterface.cpp @@ -81,7 +81,11 @@ bool InitializeCrashpad() // FIXME: On Linux, probably should put this in a subdir so that it doesn't conflict with // anything else in /usr/bin +#ifdef Q_OS_WINDOWS base::FilePath handler(TO_BASE_STRING_TYPE(QDir(exe_dir).filePath(QStringLiteral("crashpad_handler.exe")))); +#else + base::FilePath handler(TO_BASE_STRING_TYPE(QDir(exe_dir).filePath(QStringLiteral("crashpad_handler")))); +#endif base::FilePath reports_dir(TO_BASE_STRING_TYPE(QDir(OLIVE_NAMESPACE::FileFunctions::GetTempFilePath()).filePath("reports"))); diff --git a/app/core.cpp b/app/core.cpp index 9790246ed..5ba51ee45 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -51,6 +51,7 @@ #include "render/pixelformat.h" #include "render/shaderinfo.h" #include "task/project/import/import.h" +#include "task/project/import/importerrordialog.h" #include "task/project/load/load.h" #include "task/project/save/save.h" #include "task/taskmanager.h" @@ -473,7 +474,14 @@ void Core::AddOpenProjectFromTask(Task *task) void Core::ImportTaskComplete(Task* task) { - QUndoCommand *command = static_cast(task)->GetCommand(); + ProjectImportTask* import_task = static_cast(task); + + QUndoCommand *command = import_task->GetCommand(); + + if (import_task->HasInvalidFiles()) { + ProjectImportErrorDialog d(import_task->GetInvalidFiles(), main_window_); + d.exec(); + } undo_stack_.pushIfHasChildren(command); } diff --git a/app/node/math/math/mathbase.cpp b/app/node/math/math/mathbase.cpp index 4a793af93..731a750da 100644 --- a/app/node/math/math/mathbase.cpp +++ b/app/node/math/math/mathbase.cpp @@ -248,8 +248,8 @@ NodeValueTable MathNodeBase::ValueInternal(NodeValueDatabase &value, Operation o SampleBufferPtr mixed_samples = SampleBuffer::CreateAllocated(samples_a->audio_params(), max_samples); - // Mix samples that are in both buffers for (int i=0;iaudio_params().channel_count();i++) { + // Mix samples that are in both buffers for (int j=0;jdata()[i][j] = PerformAll(operation, samples_a->data()[i][j], samples_b->data()[i][j]); } @@ -259,9 +259,11 @@ NodeValueTable MathNodeBase::ValueInternal(NodeValueDatabase &value, Operation o // Fill in remainder space with 0s int remainder = max_samples - min_samples; + SampleBufferPtr larger_buffer = (max_samples == samples_a->sample_count()) ? samples_a : samples_b; + for (int i=0;iaudio_params().channel_count();i++) { - memset(mixed_samples->data()[i] + min_samples * sizeof(float), - 0, + memcpy(&mixed_samples->data()[i][min_samples], + &larger_buffer->data()[i][min_samples], remainder * sizeof(float)); } } diff --git a/app/project/item/sequence/sequence.cpp b/app/project/item/sequence/sequence.cpp index fcf76c8dd..ede8f6f88 100644 --- a/app/project/item/sequence/sequence.cpp +++ b/app/project/item/sequence/sequence.cpp @@ -268,6 +268,10 @@ void Sequence::set_parameters_from_footage(const QList footage) foreach (Footage* f, footage) { foreach (StreamPtr s, f->streams()) { + if (!s->enabled()) { + continue; + } + switch (s->type()) { case Stream::kVideo: { diff --git a/app/render/backend/renderbackend.cpp b/app/render/backend/renderbackend.cpp index 9c72f47a9..8e5c0110b 100644 --- a/app/render/backend/renderbackend.cpp +++ b/app/render/backend/renderbackend.cpp @@ -375,13 +375,6 @@ void RenderBackend::RunNextJob() return; } - // Check if params are valid - if (!video_params_.is_valid() - || !audio_params_.is_valid()) { - qDebug() << "Failed to run job, parameters are invalid"; - return; - } - // If we have a value update queued, check if all workers are available and proceed from there if (autocache_enabled_ && !graph_update_queue_.isEmpty()) { bool all_workers_available = true; @@ -449,6 +442,8 @@ void RenderBackend::RunNextJob() switch (ticket->GetType()) { case RenderTicket::kTypeHash: + Q_ASSERT(video_params_.is_valid()); + QtConcurrent::run(&thread_pool_, worker, &RenderWorker::Hash, @@ -458,6 +453,8 @@ void RenderBackend::RunNextJob() break; case RenderTicket::kTypeVideo: { + Q_ASSERT(video_params_.is_valid()); + rational frame = ticket->GetTime().value(); QtConcurrent::run(&thread_pool_, @@ -474,6 +471,8 @@ void RenderBackend::RunNextJob() break; } case RenderTicket::kTypeAudio: + Q_ASSERT(audio_params_.is_valid()); + QtConcurrent::run(&thread_pool_, worker, &RenderWorker::RenderAudio, diff --git a/app/render/backend/renderworker.cpp b/app/render/backend/renderworker.cpp index 28e8a50f5..2db075c25 100644 --- a/app/render/backend/renderworker.cpp +++ b/app/render/backend/renderworker.cpp @@ -126,7 +126,7 @@ void RenderWorker::RenderFrame(RenderTicketPtr ticket, ViewerOutput* viewer, con FramePtr frame = Frame::Create(); frame->set_timestamp(time); - if (video_force_download_resolution_ || texture.isNull()) { + if (video_force_download_resolution_) { // If we're setting the resolution ourselves or we're zeroing it out, allocate the frame now frame->set_video_params(VideoParams(video_params_.width(), video_params_.height(), diff --git a/app/task/project/import/CMakeLists.txt b/app/task/project/import/CMakeLists.txt index 77b4f31a6..4a25a6b03 100644 --- a/app/task/project/import/CMakeLists.txt +++ b/app/task/project/import/CMakeLists.txt @@ -18,5 +18,7 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} task/project/import/import.h task/project/import/import.cpp + task/project/import/importerrordialog.h + task/project/import/importerrordialog.cpp PARENT_SCOPE ) diff --git a/app/task/project/import/import.cpp b/app/task/project/import/import.cpp index 270e3a7d4..c8cd0a6c9 100644 --- a/app/task/project/import/import.cpp +++ b/app/task/project/import/import.cpp @@ -122,7 +122,10 @@ void ProjectImportTask::Import(Folder *folder, const QFileInfoList &import, int f->set_project(nullptr); - if (f->status() != Footage::kInvalid) { + if (f->status() == Footage::kInvalid) { + // Add to list so we can tell the user about it later + invalid_files_.append(file_info.absoluteFilePath()); + } else { // Create undoable command that adds the items to the model new ProjectViewModel::AddItemCommand(model_, folder, @@ -132,7 +135,7 @@ void ProjectImportTask::Import(Folder *folder, const QFileInfoList &import, int counter++; - emit ProgressChanged((counter * 100) / file_count_); + emit ProgressChanged(static_cast(counter) / static_cast(file_count_)); } } diff --git a/app/task/project/import/import.h b/app/task/project/import/import.h index d092fa192..2257fc8ea 100644 --- a/app/task/project/import/import.h +++ b/app/task/project/import/import.h @@ -42,6 +42,16 @@ public: return command_; } + const QStringList& GetInvalidFiles() const + { + return invalid_files_; + } + + bool HasInvalidFiles() const + { + return !invalid_files_.isEmpty(); + } + protected: virtual bool Run() override; @@ -58,6 +68,8 @@ private: int file_count_; + QStringList invalid_files_; + }; OLIVE_NAMESPACE_EXIT diff --git a/app/task/project/import/importerrordialog.cpp b/app/task/project/import/importerrordialog.cpp new file mode 100644 index 000000000..c6b5e4d8c --- /dev/null +++ b/app/task/project/import/importerrordialog.cpp @@ -0,0 +1,53 @@ +/*** + + 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 . + +***/ + +#include "importerrordialog.h" + +#include +#include +#include +#include + +OLIVE_NAMESPACE_ENTER + +ProjectImportErrorDialog::ProjectImportErrorDialog(const QStringList& filenames, QWidget* parent) : + QDialog(parent) +{ + QVBoxLayout* layout = new QVBoxLayout(this); + + setWindowTitle(tr("Import Error")); + + layout->addWidget(new QLabel(tr("The following files failed to import. Olive likely does not " + "support their formats."))); + + QListWidget* list_widget = new QListWidget(); + foreach (const QString& s, filenames) { + list_widget->addItem(s); + } + layout->addWidget(list_widget); + + QDialogButtonBox* buttons = new QDialogButtonBox(); + buttons->setStandardButtons(QDialogButtonBox::Ok); + buttons->setCenterButtons(true); + connect(buttons, &QDialogButtonBox::accepted, this, &ProjectImportErrorDialog::accept); + layout->addWidget(buttons); +} + +OLIVE_NAMESPACE_EXIT diff --git a/app/task/project/import/importerrordialog.h b/app/task/project/import/importerrordialog.h new file mode 100644 index 000000000..8ee6bf740 --- /dev/null +++ b/app/task/project/import/importerrordialog.h @@ -0,0 +1,40 @@ +/*** + + 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 . + +***/ + +#ifndef PROJECTIMPORTERRORDIALOG_H +#define PROJECTIMPORTERRORDIALOG_H + +#include + +#include "common/define.h" + +OLIVE_NAMESPACE_ENTER + +class ProjectImportErrorDialog : public QDialog +{ + Q_OBJECT +public: + ProjectImportErrorDialog(const QStringList& filenames, QWidget* parent = nullptr); + +}; + +OLIVE_NAMESPACE_EXIT + +#endif // PROJECTIMPORTERRORDIALOG_H diff --git a/app/widget/slider/sliderlabel.cpp b/app/widget/slider/sliderlabel.cpp index d100379a5..ffc07fb2c 100644 --- a/app/widget/slider/sliderlabel.cpp +++ b/app/widget/slider/sliderlabel.cpp @@ -51,10 +51,12 @@ SliderLabel::SliderLabel(QWidget *parent) : void SliderLabel::mousePressEvent(QMouseEvent *e) { - if (e->modifiers() & Qt::AltModifier) { - emit RequestReset(); - } else { - emit LabelPressed(); + if (e->button() == Qt::LeftButton) { + if (e->modifiers() & Qt::AltModifier) { + emit RequestReset(); + } else { + emit LabelPressed(); + } } } diff --git a/app/widget/slider/sliderladder.cpp b/app/widget/slider/sliderladder.cpp index 5a9c4d235..c5c0054ae 100644 --- a/app/widget/slider/sliderladder.cpp +++ b/app/widget/slider/sliderladder.cpp @@ -103,9 +103,7 @@ void SliderLadder::mouseReleaseEvent(QMouseEvent *event) { Q_UNUSED(event) - drag_timer_.stop(); - - emit Released(); + this->close(); } void SliderLadder::showEvent(QShowEvent *event) @@ -115,6 +113,15 @@ void SliderLadder::showEvent(QShowEvent *event) drag_timer_.start(); } +void SliderLadder::closeEvent(QCloseEvent *event) +{ + Q_UNUSED(event) + + drag_timer_.stop(); + + emit Released(); +} + void SliderLadder::TimerUpdate() { int32_t x_mvmt, y_mvmt; diff --git a/app/widget/slider/sliderladder.h b/app/widget/slider/sliderladder.h index ce0ae6193..9fc30e511 100644 --- a/app/widget/slider/sliderladder.h +++ b/app/widget/slider/sliderladder.h @@ -75,6 +75,8 @@ protected: virtual void showEvent(QShowEvent *event) override; + virtual void closeEvent(QCloseEvent* event) override; + signals: void DraggedByValue(int value, double multiplier); diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index 4b60ec0b7..99fd892a0 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -46,6 +46,7 @@ TimelineView::TimelineView(Qt::Alignment vertical_alignment, QWidget *parent) : setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); setBackgroundRole(QPalette::Window); setContextMenuPolicy(Qt::CustomContextMenu); + setViewportUpdateMode(QGraphicsView::FullViewportUpdate); viewport()->setMouseTracking(true); } diff --git a/app/widget/viewer/footageviewer.cpp b/app/widget/viewer/footageviewer.cpp index c4df185ce..5ca81ae1e 100644 --- a/app/widget/viewer/footageviewer.cpp +++ b/app/widget/viewer/footageviewer.cpp @@ -33,8 +33,10 @@ FootageViewerWidget::FootageViewerWidget(QWidget *parent) : footage_(nullptr) { video_node_ = new VideoInput(); + sequence_.AddNode(video_node_); + audio_node_ = new AudioInput(); - viewer_node_ = new ViewerOutput(); + sequence_.AddNode(audio_node_); connect(display_widget(), &ViewerDisplayWidget::DragStarted, this, &FootageViewerWidget::StartFootageDrag); @@ -55,17 +57,29 @@ void FootageViewerWidget::SetFootage(Footage *footage) ConnectViewerNode(nullptr); - NodeParam::DisconnectEdge(video_node_->output(), viewer_node_->texture_input()); - NodeParam::DisconnectEdge(audio_node_->output(), viewer_node_->samples_input()); + NodeParam::DisconnectEdge(video_node_->output(), sequence_.viewer_output()->texture_input()); + NodeParam::DisconnectEdge(audio_node_->output(), sequence_.viewer_output()->samples_input()); } footage_ = footage; if (footage_) { + // Update sequence media name + sequence_.viewer_output()->set_media_name(footage_->name()); + + // Reset parameters and then attempt to set from footage + sequence_.set_default_parameters(); + sequence_.set_parameters_from_footage({footage_}); + + // Use first of each stream VideoStreamPtr video_stream = nullptr; AudioStreamPtr audio_stream = nullptr; foreach (StreamPtr s, footage_->streams()) { + if (!s->enabled()) { + continue; + } + if (!audio_stream && s->type() == Stream::kAudio) { audio_stream = std::static_pointer_cast(s); } @@ -80,42 +94,17 @@ void FootageViewerWidget::SetFootage(Footage *footage) } } - viewer_node_->set_media_name(footage_->name()); - if (video_stream) { video_node_->SetFootage(video_stream); - viewer_node_->set_video_params(VideoParams(video_stream->width(), - video_stream->height(), - video_stream->frame_rate().flipped(), - static_cast(Config::Current()["DefaultSequencePreviewFormat"].toInt()), - video_stream->pixel_aspect_ratio(), - video_stream->interlacing(), - VideoParams::generate_auto_divider(video_stream->width(), video_stream->height()))); - NodeParam::ConnectEdge(video_node_->output(), viewer_node_->texture_input()); - } else { - int width = Config::Current()["DefaultSequenceWidth"].toInt(); - int height = Config::Current()["DefaultSequenceHeight"].toInt(); - - viewer_node_->set_video_params(VideoParams(width, - height, - Config::Current()["DefaultSequenceFrameRate"].value(), - static_cast(Config::Current()["DefaultSequencePreviewFormat"].toInt()), - Config::Current()["DefaultSequencePixelAspect"].value(), - Config::Current()["DefaultSequenceInterlacing"].value(), - VideoParams::generate_auto_divider(width, height))); + NodeParam::ConnectEdge(video_node_->output(), sequence_.viewer_output()->texture_input()); } if (audio_stream) { audio_node_->SetFootage(audio_stream); - viewer_node_->set_audio_params(AudioParams(audio_stream->sample_rate(), audio_stream->channel_layout(), SampleFormat::kInternalFormat)); - NodeParam::ConnectEdge(audio_node_->output(), viewer_node_->samples_input()); - } else { - viewer_node_->set_audio_params(AudioParams(Config::Current()["DefaultSequenceAudioFrequency"].toInt(), - Config::Current()["DefaultSequenceAudioLayout"].toULongLong(), - SampleFormat::kInternalFormat)); + NodeParam::ConnectEdge(audio_node_->output(), sequence_.viewer_output()->samples_input()); } - ConnectViewerNode(viewer_node_, footage_->project()->color_manager()); + ConnectViewerNode(sequence_.viewer_output(), footage_->project()->color_manager()); SetTimestamp(cached_timestamps_.value(footage_, 0)); } diff --git a/app/widget/viewer/footageviewer.h b/app/widget/viewer/footageviewer.h index 36f688fa9..af093eb8f 100644 --- a/app/widget/viewer/footageviewer.h +++ b/app/widget/viewer/footageviewer.h @@ -47,12 +47,12 @@ private: Footage* footage_; + Sequence sequence_; + VideoInput* video_node_; AudioInput* audio_node_; - ViewerOutput* viewer_node_; - QHash cached_timestamps_; private slots: diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 4af7869b6..b3b52231a 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -566,7 +566,9 @@ QString ViewerWidget::GetCachedFilenameFromTime(const rational &time) bool ViewerWidget::FrameExistsAtTime(const rational &time) { - return GetConnectedNode() && time >= 0 && time < GetConnectedNode()->video_frame_cache()->GetLength(); + return GetConnectedNode() + && ((time >= 0 && time < GetConnectedNode()->video_frame_cache()->GetLength()) + || GetConnectedNode()->video_frame_cache()->GetLength().isNull()); } void ViewerWidget::SetDisplayImage(FramePtr frame, bool main_only) diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 983f422e1..25f13ac58 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -99,7 +99,11 @@ void ViewerDisplayWidget::SetSignalCursorColorEnabled(bool e) void ViewerDisplayWidget::SetImage(FramePtr in_buffer) { - last_loaded_buffer_ = in_buffer; + if (in_buffer && in_buffer->is_allocated()) { + last_loaded_buffer_ = in_buffer; + } else { + last_loaded_buffer_ = nullptr; + } if (last_loaded_buffer_) { makeCurrent(); diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 4c010e963..cbb4b48da 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -349,6 +349,22 @@ void MainWindow::ProjectClose(Project *p) CloseSequence(seq); } } + + // Close any open footage in footage viewer + QList footage = p->get_items_of_type(Item::kFootage); + QList footage_in_viewer = footage_viewer_panel_->GetSelectedFootage(); + + if (!footage_in_viewer.isEmpty()) { + // FootageViewer only has the one footage item + Footage* f = footage_in_viewer.first(); + + foreach (ItemPtr i, footage) { + if (f == i.get()) { + footage_viewer_panel_->SetFootage(nullptr); + break; + } + } + } } void MainWindow::SetApplicationProgressStatus(ProgressStatus status)