Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package libopenshot-audio for
openSUSE:Factory checked in at 2026-11-01 22:24:15
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/libopenshot-audio (Old)
and /work/SRC/openSUSE:Factory/.libopenshot-audio.new.1255 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "libopenshot-audio"
Sun Nov 1 22:24:15 2026 rev:15 rq:1394233 version:1.0.2
Changes:
--------
--- /work/SRC/openSUSE:Factory/libopenshot-audio/libopenshot-audio.changes
2026-09-29 17:56:22.950711148 +0200
+++
/work/SRC/openSUSE:Factory/.libopenshot-audio.new.1255/libopenshot-audio.changes
2026-11-01 22:25:36.876466878 +0100
@@ -1,0 +2,11 @@
+Sun Nov 1 01:40:30 UTC 2026 - Bjørn Lie <[email protected]>
+
+- Update to version 1.0.2:
+ * Windows ARM64 support: Add native audio-library build readiness and
+ compatible alignment queries for the Windows ARM toolchain.
+ * Native ARM64 validation: Automatically check Linux and Windows ARM64
+ builds, with improved dependency validation and CI compatibility.
+ * Stable ABI: Version 1.0.2 keeps SO 10 unchanged and provides the audio
+ dependency required by libopenshot 1.1.0.
+
+-------------------------------------------------------------------
Old:
----
libopenshot-audio-1.0.1.tar.gz
New:
----
libopenshot-audio-1.0.2.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ libopenshot-audio.spec ++++++
--- /var/tmp/diff_new_pack.L7spml/_old 2026-11-01 22:25:37.849507321 +0100
+++ /var/tmp/diff_new_pack.L7spml/_new 2026-11-01 22:25:37.853507488 +0100
@@ -19,7 +19,7 @@
%define sover 10
Name: libopenshot-audio
-Version: 1.0.1
+Version: 1.0.2
Release: 0
Summary: Audio library for the OpenShot video editor
License: GPL-3.0-or-later
++++++ libopenshot-audio-1.0.1.tar.gz -> libopenshot-audio-1.0.2.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/libopenshot-audio-1.0.1/.github/workflows/ci.yml
new/libopenshot-audio-1.0.2/.github/workflows/ci.yml
--- old/libopenshot-audio-1.0.1/.github/workflows/ci.yml 2026-09-20
01:23:51.000000000 +0200
+++ new/libopenshot-audio-1.0.2/.github/workflows/ci.yml 2026-10-25
22:20:01.000000000 +0100
@@ -56,3 +56,73 @@
pushd build
cmake --build . --target install -- VERBOSE=1
popd
+
+ windows-arm64:
+ name: Windows ARM64 build and tests
+ runs-on: windows-11-arm
+ steps:
+ - name: Preserve repository line endings
+ run: git config --global core.autocrlf input
+
+ - uses: actions/checkout@v4
+
+ - uses: msys2/setup-msys2@v2
+ with:
+ msystem: CLANGARM64
+ # Use available rolling packages; record versions against the baseline.
+ # An archived package snapshot is required for reproducible builds.
+ update: true
+
+ - name: Install CLANGARM64 packages
+ shell: msys2 {0}
+ run: |
+ mapfile -t packages < <(sed -n '/^[^#[:space:]][^=]*=/s/=.*//p'
ci/windows-arm64-packages.lock)
+ pacman --noconfirm -S --needed -- "${packages[@]}"
+
+ - name: Build libopenshot-audio (CLANGARM64, ARM64)
+ shell: msys2 {0}
+ run: |
+ cmake -B build -S . -G Ninja \
+ -DCMAKE_C_COMPILER=clang \
+ -DCMAKE_CXX_COMPILER=clang++ \
+ -DCMAKE_INSTALL_PREFIX="$PWD/dist-arm64" \
+ -DCMAKE_BUILD_TYPE=Debug \
+ -DENABLE_AUDIO_DOCS=OFF
+ cmake --build build
+ cmake --install build
+ cmake -S ci/audio-smoke -B smoke-build -G Ninja \
+ -DCMAKE_PREFIX_PATH="$PWD/dist-arm64" -DREQUIRE_ARM64=ON
+ cmake --build smoke-build
+ PATH="$PWD/dist-arm64/bin:$PATH" ctest --test-dir smoke-build
--output-on-failure --no-tests=error
+ python -m unittest discover -s ci -p "test_*.py" -v
+ python ci/validate_arm64_architecture.py \
+ --require-native-arm64 \
+ --package-baseline ci/windows-arm64-packages.lock \
+ --payload-root dist-arm64 \
+ --require-payload \
+ --json-report build/arm64-report.json
+
+ linux-arm64:
+ name: Linux ARM64 build and tests
+ runs-on: ubuntu-24.04-arm
+ steps:
+ - uses: actions/checkout@v4
+ - name: Install dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y cmake ninja-build libasound2-dev python3
+ - name: Build and install audio library
+ run: |
+ test "$(uname -m)" = aarch64
+ cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release \
+ -DCMAKE_INSTALL_PREFIX="$PWD/install-arm64" -DENABLE_AUDIO_DOCS=OFF
+ cmake --build build --parallel 2
+ cmake --install build
+ readelf -h install-arm64/lib/libopenshot-audio.so | grep -Eq
'Machine:.*AArch64'
+ - name: Test installed audio library and validator
+ run: |
+ cmake -S ci/audio-smoke -B smoke-build -G Ninja \
+ -DCMAKE_PREFIX_PATH="$PWD/install-arm64" -DREQUIRE_ARM64=ON
+ cmake --build smoke-build
+ ctest --test-dir smoke-build --output-on-failure --no-tests=error
+ python3 -m unittest discover -s ci -p 'test_*.py' -v
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/libopenshot-audio-1.0.1/.gitignore
new/libopenshot-audio-1.0.2/.gitignore
--- old/libopenshot-audio-1.0.1/.gitignore 2026-09-20 01:23:51.000000000
+0200
+++ new/libopenshot-audio-1.0.2/.gitignore 2026-10-25 22:20:01.000000000
+0100
@@ -3,3 +3,6 @@
.settings
.idea/*
cmake-build-debug/*
+
+# Python validator bytecode
+__pycache__/
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/libopenshot-audio-1.0.1/.gitlab-ci.yml
new/libopenshot-audio-1.0.2/.gitlab-ci.yml
--- old/libopenshot-audio-1.0.1/.gitlab-ci.yml 2026-09-20 01:23:51.000000000
+0200
+++ new/libopenshot-audio-1.0.2/.gitlab-ci.yml 2026-10-25 22:20:01.000000000
+0100
@@ -79,6 +79,48 @@
- tags
tags: [windows]
+# WINDOWS ARM64 (native, additive lane; existing x64/x86 jobs are unchanged).
+# Contract identity: windows-arm64-clangarm64-v1 (design-spec.md).
+# Verifies the shared ci/windows-arm64-packages.lock and validates every
+# produced .exe/.dll/.pyd, plus the native-process oracle of the build host,
+# using ci/validate_arm64_architecture.py (design-amendment-A1 semantics:
+# native execution requires pProcessMachine == IMAGE_FILE_MACHINE_UNKNOWN
+# and pNativeMachine == IMAGE_FILE_MACHINE_ARM64; every payload PE must be
+# machine 0xAA64 independently).
+windows-builder-arm64:
+ stage: build-libopenshot-audio
+ artifacts:
+ expire_in: 6 months
+ paths:
+ - build/install-arm64/
+ - build\arm64-architecture-report.json
+ script:
+ - $env:Path = "C:\msys64\clangarm64\bin;C:\msys64\usr\bin;" + $env:Path;
+ - $env:MSYSTEM = "CLANGARM64"
+ - New-Item -ItemType Directory -Force -Path build
+ - cmake -B build -S . -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON
-D"CMAKE_INSTALL_PREFIX:PATH=build/install-arm64" -DCMAKE_C_COMPILER=clang
-DCMAKE_CXX_COMPILER=clang++ -G Ninja -D"CMAKE_BUILD_TYPE:STRING=Release"
+ - cmake --build build
+ - ctest --test-dir build --output-on-failure -VV
+ - cmake --install build
+ - python -m unittest discover -s ci -p "test_*.py" -v
+ - python ci\validate_arm64_architecture.py --require-native-arm64
--package-baseline ci\windows-arm64-packages.lock --payload-root
build\install-arm64 --require-payload --json-report
build\arm64-architecture-report.json
+ - $PROJECT_VERSION = (Select-String -Path "CMakeLists.txt" -Pattern
'^set\(PROJECT_VERSION_FULL "(.*)\"' | %{$_.Matches.Groups[1].value})
+ - $PROJECT_SO = (Select-String -Path "CMakeLists.txt" -Pattern
'^set\(PROJECT_SO_VERSION (.*)\)' | %{$_.Matches.Groups[1].value})
+ - New-Item -path "build/install-arm64/share/" -Name "$CI_PROJECT_NAME.env"
-Value
"CI_PROJECT_NAME:$CI_PROJECT_NAME`nCI_COMMIT_REF_NAME:$CI_COMMIT_REF_NAME`nCI_COMMIT_SHA:$CI_COMMIT_SHA`nCI_JOB_ID:$CI_JOB_ID`nCI_PIPELINE_ID:$CI_PIPELINE_ID`nVERSION:$PROJECT_VERSION`nSO:$PROJECT_SO`nTARGET_TRIPLET:aarch64-w64-mingw32`nPE_MACHINE:0xAA64"
-ItemType file -force
+ - $PREV_GIT_LABEL=(git describe --tags --abbrev=0 '@^')
+ - git log "$PREV_GIT_LABEL..@" --oneline --pretty=format:"-
%C(auto,yellow)%h%C(auto,magenta)% %C(auto,blue)%>(12,trunc)%ad
%C(auto,green)%<(25,trunc)%aN%C(auto,reset)%s%C(auto,red)% gD% D" --date=short
> "build/install-arm64/share/$CI_PROJECT_NAME.log"
+ only:
+ variables:
+ - $ENABLE_WINDOWS_ARM64 == "1"
+ except:
+ - tags
+ tags: [windows-arm64]
+ # Requires release infrastructure's native/virtual Windows Arm64 GitLab
+ # runner tagged windows-arm64 (design-spec.md "Release-infrastructure
+ # surface"). Until that runner exists, this job cannot execute; it does
+ # not weaken or replace the existing windows-builder-x64/x86 jobs.
+ allow_failure: true
+
windows-builder-x86:
stage: build-libopenshot-audio
artifacts:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/libopenshot-audio-1.0.1/CMakeLists.txt
new/libopenshot-audio-1.0.2/CMakeLists.txt
--- old/libopenshot-audio-1.0.1/CMakeLists.txt 2026-09-20 01:23:51.000000000
+0200
+++ new/libopenshot-audio-1.0.2/CMakeLists.txt 2026-10-25 22:20:01.000000000
+0100
@@ -40,7 +40,7 @@
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake/Modules")
################ PROJECT VERSION ####################
-set(PROJECT_VERSION_FULL "1.0.1")
+set(PROJECT_VERSION_FULL "1.0.2")
set(PROJECT_SO_VERSION 10)
#### Set C++ standard level
@@ -215,6 +215,8 @@
if(TARGET ASIO::SDK)
target_link_libraries(openshot-audio PRIVATE ASIO::SDK)
set(NEED_ASIO TRUE)
+ else()
+ target_compile_definitions(openshot-audio PUBLIC JUCE_ASIO=0)
endif()
# Order here can be important!
# For example, winmm.lib must come before kernel32.lib (if linked)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/libopenshot-audio-1.0.1/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h
new/libopenshot-audio-1.0.2/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h
---
old/libopenshot-audio-1.0.1/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h
2026-09-20 01:23:51.000000000 +0200
+++
new/libopenshot-audio-1.0.2/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h
2026-10-25 22:20:01.000000000 +0100
@@ -1193,7 +1193,7 @@
jassert (size >= 0);
auto channelListSize = (size_t) (numChannels + 1) * sizeof (Type*);
- auto requiredSampleAlignment = std::alignment_of_v<Type>;
+ auto requiredSampleAlignment = alignof (Type);
size_t alignmentOverflow = channelListSize % requiredSampleAlignment;
if (alignmentOverflow != 0)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/libopenshot-audio-1.0.1/ci/README-arm64.md
new/libopenshot-audio-1.0.2/ci/README-arm64.md
--- old/libopenshot-audio-1.0.1/ci/README-arm64.md 1970-01-01
01:00:00.000000000 +0100
+++ new/libopenshot-audio-1.0.2/ci/README-arm64.md 2026-10-25
22:20:01.000000000 +0100
@@ -0,0 +1,34 @@
+<!--
+SPDX-FileCopyrightText: 2026 OpenShot Studios, LLC
+SPDX-License-Identifier: LGPL-3.0-or-later
+-->
+
+# ARM64 build checks
+
+The `arm64-support` branch collects build support before integration into
+`develop`. Existing Linux, Intel Mac, and Windows x86/x64 jobs remain enabled.
+
+GitHub automatically runs **Linux ARM64 build and tests** on
+`ubuntu-24.04-arm` and **Windows ARM64 build and tests** on `windows-11-arm`
+for pushes and pull requests. No repository enable variables are needed, and
+failures are reported normally. These checks do not publish releases.
+
+The production GitLab configuration is unchanged: its optional Windows ARM64
+job still requires `ENABLE_WINDOWS_ARM64=1`, a runner tagged `windows-arm64`,
+and MSYS2 CLANGARM64 installed at `C:\msys64`.
+
+`windows-arm64-packages.lock` is a reference package list. GitHub installs
+currently available packages by name, and the validator records actual
+versions and reports deviations using `--package-baseline`. GitLab expects
+the packages to be provisioned on the runner. Reproducible builds require a
+separately archived package snapshot; `--package-lock` retains strict version
+checking for such environments. Archive checksums are not verified.
+
+Native validation requires both the Windows host API result and an ARM64 PE
+header on the running Python interpreter. Every produced executable, DLL,
+and Python extension is also checked independently. A Linux validator test
+pass is not evidence of native Windows execution.
+
+The audio checks build a consumer of the installed library, apply gain to
+stereo samples, and verify a WAV encode/decode round trip without requiring
+an audio device. They also run the architecture-validator unit tests.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/libopenshot-audio-1.0.1/ci/audio-smoke/CMakeLists.txt
new/libopenshot-audio-1.0.2/ci/audio-smoke/CMakeLists.txt
--- old/libopenshot-audio-1.0.1/ci/audio-smoke/CMakeLists.txt 1970-01-01
01:00:00.000000000 +0100
+++ new/libopenshot-audio-1.0.2/ci/audio-smoke/CMakeLists.txt 2026-10-25
22:20:01.000000000 +0100
@@ -0,0 +1,15 @@
+# SPDX-FileCopyrightText: 2026 OpenShot Studios, LLC
+# SPDX-License-Identifier: LGPL-3.0-or-later
+
+cmake_minimum_required(VERSION 3.16)
+project(AudioSmokeTest LANGUAGES CXX)
+find_package(OpenShotAudio REQUIRED)
+option(REQUIRE_ARM64 "Require a native ARM64 test executable" OFF)
+add_executable(audio-smoke main.cpp)
+target_compile_features(audio-smoke PRIVATE cxx_std_17)
+target_link_libraries(audio-smoke PRIVATE OpenShot::Audio)
+if(REQUIRE_ARM64)
+ target_compile_definitions(audio-smoke PRIVATE REQUIRE_ARM64=1)
+endif()
+enable_testing()
+add_test(NAME audio-buffer-roundtrip COMMAND audio-smoke)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/libopenshot-audio-1.0.1/ci/audio-smoke/main.cpp
new/libopenshot-audio-1.0.2/ci/audio-smoke/main.cpp
--- old/libopenshot-audio-1.0.1/ci/audio-smoke/main.cpp 1970-01-01
01:00:00.000000000 +0100
+++ new/libopenshot-audio-1.0.2/ci/audio-smoke/main.cpp 2026-10-25
22:20:01.000000000 +0100
@@ -0,0 +1,43 @@
+// SPDX-FileCopyrightText: 2026 OpenShot Studios, LLC
+// SPDX-License-Identifier: LGPL-3.0-or-later
+
+#include <cmath>
+#include <iostream>
+#include <memory>
+#include "OpenShotAudio.h"
+
+#if defined(REQUIRE_ARM64) && !defined(__aarch64__) && !defined(_M_ARM64)
+#error The ARM64 smoke test must be compiled for ARM64
+#endif
+
+int main()
+{
+ // Exercise the installed library without requiring an audio device.
+ juce::AudioBuffer<float> samples(2, 128);
+ for (int i = 0; i < samples.getNumSamples(); ++i) {
+ samples.setSample(0, i, 0.5f);
+ samples.setSample(1, i, -0.25f);
+ }
+ samples.applyGain(0.5f);
+ juce::MemoryBlock encoded;
+ juce::WavAudioFormat format;
+ auto output = std::make_unique<juce::MemoryOutputStream>(encoded, false);
+ std::unique_ptr<juce::AudioFormatWriter> writer(
+ format.createWriterFor(output.get(), 48000, 2, 16, {}, 0));
+ if (!writer) return 1;
+ output.release(); // The writer owns the stream after successful creation.
+ if (!writer->writeFromAudioSampleBuffer(samples, 0,
samples.getNumSamples())) return 1;
+ writer.reset(); // Finalize the WAV header before reading it.
+ std::unique_ptr<juce::AudioFormatReader> reader(format.createReaderFor(
+ new juce::MemoryInputStream(encoded, false), true));
+ if (!reader || reader->numChannels != 2 || reader->sampleRate != 48000
+ || reader->lengthInSamples != 128) return 1;
+ juce::AudioBuffer<float> decoded(2, 128);
+ if (!reader->read(&decoded, 0, 128, 0, true, true)) return 1;
+ for (int i = 0; i < decoded.getNumSamples(); ++i) {
+ if (std::abs(decoded.getSample(0, i) - 0.25f) > 0.0001f
+ || std::abs(decoded.getSample(1, i) + 0.125f) > 0.0001f) return 1;
+ }
+ std::cout << "Installed audio library: buffer gain and WAV round trip
passed\n";
+ return 0;
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/libopenshot-audio-1.0.1/ci/test_validate_arm64_architecture.py
new/libopenshot-audio-1.0.2/ci/test_validate_arm64_architecture.py
--- old/libopenshot-audio-1.0.1/ci/test_validate_arm64_architecture.py
1970-01-01 01:00:00.000000000 +0100
+++ new/libopenshot-audio-1.0.2/ci/test_validate_arm64_architecture.py
2026-10-25 22:20:01.000000000 +0100
@@ -0,0 +1,197 @@
+# SPDX-FileCopyrightText: 2026 OpenShot Studios, LLC
+# SPDX-License-Identifier: LGPL-3.0-or-later
+
+import contextlib
+import importlib.util
+import io
+import os
+import struct
+import sys
+import tempfile
+import unittest
+from unittest import mock
+
+VALIDATOR_PATH = os.path.join(os.path.dirname(__file__),
"validate_arm64_architecture.py")
+SPEC = importlib.util.spec_from_file_location("validate_arm64_architecture",
VALIDATOR_PATH)
+if SPEC is None or SPEC.loader is None:
+ raise RuntimeError("Unable to load validator from %s" % VALIDATOR_PATH)
+validator = importlib.util.module_from_spec(SPEC)
+SPEC.loader.exec_module(validator)
+
+
+def write_pe(path, machine):
+ data = bytearray(0x80)
+ data[:2] = b"MZ"
+ struct.pack_into("<I", data, 0x3C, 0x40)
+ data[0x40:0x44] = b"PE\0\0"
+ struct.pack_into("<H", data, 0x44, machine)
+ with open(path, "wb") as stream:
+ stream.write(data)
+
+
+class Arm64ArchitectureValidatorTests(unittest.TestCase):
+ def test_host_api_requires_independent_arm64_interpreter(self):
+ kernel32 = mock.Mock()
+ def query(_handle, process, native):
+ process._obj.value = validator.IMAGE_FILE_MACHINE_UNKNOWN
+ native._obj.value = validator.IMAGE_FILE_MACHINE_ARM64
+ return 1
+ kernel32.IsWow64Process2.side_effect = query
+ with tempfile.TemporaryDirectory() as root:
+ executable = os.path.join(root, "python.exe")
+ for machine, expected in ((validator.IMAGE_FILE_MACHINE_ARM64,
True),
+ (validator.IMAGE_FILE_MACHINE_AMD64,
False),
+ (validator.IMAGE_FILE_MACHINE_I386,
False)):
+ with self.subTest(machine=machine):
+ write_pe(executable, machine)
+ with mock.patch.object(sys, "platform", "win32"), \
+ mock.patch.object(sys, "executable", executable), \
+ mock.patch.object(validator.ctypes, "WinDLL",
return_value=kernel32, create=True):
+ result = validator.read_native_process_oracle()
+ self.assertEqual(result["native_arm64_ok"], expected)
+ self.assertEqual(result["executable_machine"], machine)
+ with open(executable, "w") as stream:
+ stream.write("invalid executable")
+ with mock.patch.object(sys, "platform", "win32"), \
+ mock.patch.object(sys, "executable", executable), \
+ mock.patch.object(validator.ctypes, "WinDLL",
return_value=kernel32, create=True):
+
self.assertFalse(validator.read_native_process_oracle()["native_arm64_ok"])
+
+ def test_baseline_records_drift_but_still_requires_packages(self):
+ with tempfile.TemporaryDirectory() as root:
+ lock_path = os.path.join(root, "packages.lock")
+ with open(lock_path, "w") as stream:
+
stream.write("example-package=1.2.3,UNVERIFIED-NO-SIGNED-SNAPSHOT\n")
+ completed = mock.Mock(returncode=0, stdout="example-package
1.2.4\n")
+ with mock.patch.object(validator.subprocess, "run",
return_value=completed):
+ verified, failures = validator.verify_package_lock(lock_path,
require_exact_versions=False)
+ self.assertEqual(failures, [])
+ self.assertFalse(verified[0]["version_matches"])
+ completed.returncode = 1
+ with mock.patch.object(validator.subprocess, "run",
return_value=completed):
+ _, failures = validator.verify_package_lock(lock_path,
require_exact_versions=False)
+ self.assertIn("Package not installed", failures[0])
+
+ def test_package_lock_reports_missing_file(self):
+ verified, failures =
validator.verify_package_lock("missing-package-lock.txt")
+ self.assertEqual(verified, [])
+ self.assertIn("Unable to read package lock", failures[0])
+
+ def test_payload_scan_accepts_arm64_and_rejects_amd64(self):
+ with tempfile.TemporaryDirectory() as root:
+ write_pe(os.path.join(root, "native.dll"),
validator.IMAGE_FILE_MACHINE_ARM64)
+ results, failures = validator.scan_payload_architecture(root)
+ self.assertEqual(len(results), 1)
+ self.assertEqual(failures, [])
+
+ write_pe(os.path.join(root, "foreign.pyd"),
validator.IMAGE_FILE_MACHINE_AMD64)
+ results, failures = validator.scan_payload_architecture(root)
+ self.assertEqual(len(results), 2)
+ self.assertEqual(len(failures), 1)
+
+ def test_required_native_host_fails_closed(self):
+ oracle = {
+ "checked": True,
+ "process_machine": validator.IMAGE_FILE_MACHINE_UNKNOWN,
+ "native_machine": validator.IMAGE_FILE_MACHINE_AMD64,
+ "is_wow_or_emulated": False,
+ "native_arm64_ok": False,
+ "reason": None,
+ }
+ with mock.patch.object(validator, "read_native_process_oracle",
return_value=oracle):
+ with mock.patch.object(sys, "argv", ["validator",
"--require-native-arm64"]):
+ with contextlib.redirect_stdout(io.StringIO()):
+ self.assertEqual(validator.main(), 1)
+
+ def test_package_lock_detects_version_drift(self):
+ with tempfile.NamedTemporaryFile("w", delete=False) as lock:
+ lock.write("example-package=1.2.3,UNVERIFIED-NO-SIGNED-SNAPSHOT\n")
+ lock_path = lock.name
+ try:
+ completed = mock.Mock(returncode=0, stdout="example-package
1.2.4\n")
+ with mock.patch.object(validator.subprocess, "run",
return_value=completed):
+ verified, failures = validator.verify_package_lock(lock_path)
+ self.assertEqual(verified[0]["version"], "1.2.4")
+ self.assertEqual(len(failures), 1)
+ finally:
+ os.unlink(lock_path)
+
+ def test_package_lock_reports_missing_pacman(self):
+ with tempfile.NamedTemporaryFile("w", delete=False) as lock:
+ lock.write("example-package=1.2.3,UNVERIFIED-NO-SIGNED-SNAPSHOT\n")
+ lock_path = lock.name
+ try:
+ with mock.patch.object(
+ validator.subprocess, "run",
side_effect=FileNotFoundError("pacman")
+ ):
+ verified, failures = validator.verify_package_lock(lock_path)
+ self.assertEqual(verified, [])
+ self.assertIn("Unable to run pacman", failures[0])
+ finally:
+ os.unlink(lock_path)
+
+ def test_package_lock_reports_malformed_entry(self):
+ with tempfile.NamedTemporaryFile("w", delete=False) as lock:
+ lock.write("malformed-entry\n")
+ lock_path = lock.name
+ try:
+ verified, failures = validator.verify_package_lock(lock_path)
+ self.assertEqual(verified, [])
+ self.assertEqual(failures, ["Malformed package lock entry:
malformed-entry"])
+ finally:
+ os.unlink(lock_path)
+
+ def test_package_lock_rejects_unsafe_package_name(self):
+ with tempfile.NamedTemporaryFile("w", delete=False) as lock:
+ lock.write("-unsafe=1.2.3,UNVERIFIED-NO-SIGNED-SNAPSHOT\n")
+ lock_path = lock.name
+ try:
+ verified, failures = validator.verify_package_lock(lock_path)
+ self.assertEqual(verified, [])
+ self.assertEqual(failures, ["Invalid package name in lock:
'-unsafe'"])
+ finally:
+ os.unlink(lock_path)
+
+ def test_package_lock_rejects_unexpected_pacman_output(self):
+ with tempfile.NamedTemporaryFile("w", delete=False) as lock:
+ lock.write("example-package=1.2.3,UNVERIFIED-NO-SIGNED-SNAPSHOT\n")
+ lock_path = lock.name
+ try:
+ completed = mock.Mock(returncode=0, stdout="warning only\n")
+ with mock.patch.object(validator.subprocess, "run",
return_value=completed):
+ verified, failures = validator.verify_package_lock(lock_path)
+ self.assertEqual(verified, [])
+ self.assertIn("Unexpected pacman output", failures[0])
+ finally:
+ os.unlink(lock_path)
+
+ def test_package_lock_rejects_unverified_real_hash(self):
+ with tempfile.NamedTemporaryFile("w", delete=False) as lock:
+ lock.write("example-package=1.2.3,abc123\n")
+ lock_path = lock.name
+ try:
+ completed = mock.Mock(returncode=0, stdout="example-package
1.2.3\n")
+ with mock.patch.object(validator.subprocess, "run",
return_value=completed):
+ _verified, failures = validator.verify_package_lock(lock_path)
+ self.assertIn("hash verification is not implemented", failures[0])
+ finally:
+ os.unlink(lock_path)
+
+ def test_require_payload_rejects_missing_root(self):
+ with mock.patch.object(sys, "argv", ["validator",
"--require-payload"]):
+ with contextlib.redirect_stdout(io.StringIO()):
+ self.assertEqual(validator.main(), 1)
+
+ def test_require_payload_rejects_directory_without_valid_pe(self):
+ with tempfile.TemporaryDirectory() as root:
+ with open(os.path.join(root, "stub.exe"), "w", encoding="utf-8")
as stream:
+ stream.write("not a PE")
+ with mock.patch.object(
+ sys, "argv", ["validator", "--payload-root", root,
"--require-payload"]
+ ):
+ with contextlib.redirect_stdout(io.StringIO()):
+ self.assertEqual(validator.main(), 1)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/libopenshot-audio-1.0.1/ci/validate_arm64_architecture.py
new/libopenshot-audio-1.0.2/ci/validate_arm64_architecture.py
--- old/libopenshot-audio-1.0.1/ci/validate_arm64_architecture.py
1970-01-01 01:00:00.000000000 +0100
+++ new/libopenshot-audio-1.0.2/ci/validate_arm64_architecture.py
2026-10-25 22:20:01.000000000 +0100
@@ -0,0 +1,365 @@
+#!/usr/bin/env python3
+# SPDX-FileCopyrightText: 2026 OpenShot Studios, LLC
+# SPDX-License-Identifier: LGPL-3.0-or-later
+
+"""Validate Windows ARM64 host, payload, and dependency architectures."""
+
+# The Windows host API and interpreter PE header must both identify native
+# ARM64 execution. A separate recursive scan checks every payload PE.
+# Package manifests can enforce exact versions or report baseline drift.
+
+import argparse
+import ctypes
+import json
+import os
+import re
+import subprocess # nosec B404 - Query pacman with an argument list, never a
shell.
+import struct
+import sys
+
+IMAGE_FILE_MACHINE_UNKNOWN = 0x0
+IMAGE_FILE_MACHINE_ARM64 = 0xAA64
+IMAGE_FILE_MACHINE_AMD64 = 0x8664
+IMAGE_FILE_MACHINE_I386 = 0x14C
+
+MACHINE_NAMES = {
+ IMAGE_FILE_MACHINE_UNKNOWN: "UNKNOWN (native, non-WOW64)",
+ IMAGE_FILE_MACHINE_ARM64: "ARM64",
+ IMAGE_FILE_MACHINE_AMD64: "AMD64",
+ IMAGE_FILE_MACHINE_I386: "I386",
+}
+
+
+def machine_name(value):
+ return MACHINE_NAMES.get(value, "0x%04X" % value)
+
+
+def read_native_process_oracle():
+ """Report host API values, interpreter architecture, and native ARM64
status."""
+ result = {
+ "checked": False,
+ "process_machine": None,
+ "native_machine": None,
+ "is_wow_or_emulated": None,
+ "native_arm64_ok": False,
+ "executable_machine": None,
+ "reason": None,
+ }
+
+ if not sys.platform.startswith("win"):
+ result["reason"] = "IsWow64Process2 is a Windows-only API; not running
on Windows."
+ return result
+
+ try:
+ kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
+ kernel32.GetCurrentProcess.restype = ctypes.c_void_p
+ is_wow64_process2 = getattr(kernel32, "IsWow64Process2", None)
+ if is_wow64_process2 is None:
+ result["reason"] = "IsWow64Process2 is unavailable on this Windows
system."
+ return result
+ is_wow64_process2.argtypes = [
+ ctypes.c_void_p,
+ ctypes.POINTER(ctypes.c_ushort),
+ ctypes.POINTER(ctypes.c_ushort),
+ ]
+ is_wow64_process2.restype = ctypes.c_int
+ process_machine = ctypes.c_ushort(0)
+ native_machine = ctypes.c_ushort(0)
+ handle = kernel32.GetCurrentProcess()
+ ok = is_wow64_process2(
+ handle,
+ ctypes.byref(process_machine),
+ ctypes.byref(native_machine),
+ )
+ if not ok:
+ result["reason"] = (
+ "IsWow64Process2 returned failure (GetLastError=%s)."
+ % ctypes.get_last_error()
+ )
+ return result
+
+ result["checked"] = True
+ result["process_machine"] = process_machine.value
+ result["native_machine"] = native_machine.value
+ result["is_wow_or_emulated"] = process_machine.value !=
IMAGE_FILE_MACHINE_UNKNOWN
+ # The host API alone can be ambiguous under x64 emulation. Inspect
+ # the interpreter that is actually executing this validator as well.
+ result["executable_machine"] = read_pe_machine(sys.executable)
+ result["native_arm64_ok"] = (
+ native_machine.value == IMAGE_FILE_MACHINE_ARM64
+ and process_machine.value == IMAGE_FILE_MACHINE_UNKNOWN
+ and result["executable_machine"] == IMAGE_FILE_MACHINE_ARM64
+ )
+ return result
+ except Exception as exc: # noqa: BLE001 - report, don't hide
+ result["reason"] = "IsWow64Process2 call failed: %r" % exc
+ return result
+
+
+def read_pe_machine(path):
+ """Read the COFF machine field directly, returning None for invalid PE
files."""
+ with open(path, "rb") as f:
+ dos_header = f.read(64)
+ if len(dos_header) < 64 or dos_header[0:2] != b"MZ":
+ return None
+ pe_offset = struct.unpack_from("<I", dos_header, 0x3C)[0]
+ f.seek(pe_offset)
+ pe_sig = f.read(4)
+ if pe_sig != b"PE\x00\x00":
+ return None
+ coff_header = f.read(20)
+ if len(coff_header) < 20:
+ return None
+ machine = struct.unpack_from("<H", coff_header, 0)[0]
+ return machine
+
+
+def scan_payload_architecture(root):
+ """Recursively return PE architecture results and failures for a
payload."""
+ results = []
+ failures = []
+ if not os.path.isdir(root):
+ failures.append("Payload root does not exist: %s" % root)
+ return results, failures
+
+ for dirpath, dirnames, filenames in os.walk(root):
+ dirnames.sort()
+ for filename in sorted(filenames):
+ ext = os.path.splitext(filename)[1].lower()
+ if ext not in (".exe", ".dll", ".pyd"):
+ continue
+ full_path = os.path.join(dirpath, filename)
+ try:
+ machine = read_pe_machine(full_path)
+ except OSError as exc:
+ failures.append("Could not read %s: %s" % (full_path, exc))
+ continue
+
+ if machine is None:
+ failures.append("Not a valid PE file (no MZ/PE header): %s" %
full_path)
+ continue
+
+ results.append({
+ "path": full_path,
+ "machine": machine,
+ "machine_name": machine_name(machine),
+ })
+ if machine != IMAGE_FILE_MACHINE_ARM64:
+ failures.append(
+ "Wrong architecture %s (expected ARM64/0xAA64): %s"
+ % (machine_name(machine), full_path)
+ )
+
+ return results, failures
+
+
+def verify_package_lock(path, require_exact_versions=True):
+ failures = []
+ verified = []
+ try:
+ with open(path, encoding="utf-8") as stream:
+ entries = [
+ line.strip()
+ for line in stream
+ if line.strip() and not line.lstrip().startswith("#")
+ ]
+ except OSError as exc:
+ return verified, ["Unable to read package lock %s: %s" % (path, exc)]
+ for entry in entries:
+ if "=" not in entry or "," not in entry.split("=", 1)[1]:
+ failures.append("Malformed package lock entry: %s" % entry)
+ continue
+ package, remainder = entry.split("=", 1)
+ if not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9@._+:-]*", package):
+ failures.append("Invalid package name in lock: %r" % package)
+ continue
+ expected_version, expected_sha256 = (
+ value.strip() for value in remainder.split(",", 1)
+ )
+ if expected_sha256 != "UNVERIFIED-NO-SIGNED-SNAPSHOT":
+ failures.append(
+ "Package archive hash verification is not implemented for %s:
%s"
+ % (package, expected_sha256)
+ )
+ try:
+ completed = subprocess.run( # nosec B603 B607 - Fixed pacman
command and validated package name; no shell.
+ ["pacman", "-Q", "--", package],
+ capture_output=True,
+ text=True,
+ check=False,
+ )
+ except OSError as exc:
+ failures.append("Unable to run pacman for %s: %s" % (package, exc))
+ continue
+ if completed.returncode != 0:
+ failures.append("Package not installed: %s" % package)
+ continue
+ lines = [line.strip() for line in completed.stdout.splitlines() if
line.strip()]
+ fields = lines[0].split() if lines else []
+ if len(fields) != 2 or fields[0] != package:
+ failures.append(
+ "Unexpected pacman output for %s: %r" % (package,
completed.stdout)
+ )
+ continue
+ actual_version = fields[1]
+ verified.append({
+ "package": package,
+ "version": actual_version,
+ "expected_version": expected_version,
+ "version_matches": actual_version == expected_version,
+ "expected_sha256": expected_sha256,
+ })
+ if require_exact_versions and actual_version != expected_version:
+ failures.append(
+ "Wrong package version for %s: %s (expected %s)"
+ % (package, actual_version, expected_version)
+ )
+ return verified, failures
+
+
+def print_native_report(oracle):
+ """Print the native-process validation result."""
+ print("== Native Arm64 process oracle (design-amendment-A1) ==")
+ if oracle["checked"]:
+ print(" process_machine = %s" %
machine_name(oracle["process_machine"]))
+ print(" native_machine = %s" %
machine_name(oracle["native_machine"]))
+ print(" wow/emulated = %s" % oracle["is_wow_or_emulated"])
+ print(" native ARM64 ok = %s" % oracle["native_arm64_ok"])
+ else:
+ print(" NOT AVAILABLE: %s" % oracle["reason"])
+
+
+def print_payload_report(root, results, failures):
+ """Print the payload scan result."""
+ print("== Recursive payload PE architecture scan ==")
+ if root:
+ print(" root = %s" % root)
+ print(" files scanned = %d" % len(results))
+ for failure in failures:
+ print(" FAIL: %s" % failure)
+ else:
+ print(" SKIPPED: no --payload-root supplied")
+
+
+def print_package_report(manifest, baseline, results, failures):
+ """Print package versions without claiming baseline drift is a lock
match."""
+ print("== MSYS2 package lock ==")
+ if manifest:
+ print(" manifest = %s" % manifest)
+ if baseline:
+ print(" BASELINE ONLY: exact package versions are not required")
+ for package in results:
+ if not package["version_matches"]:
+ print(" VERSION DRIFT: %s = %s (baseline %s)" % (
+ package["package"], package["version"],
package["expected_version"]))
+ print(" packages verified = %d" % len(results))
+ for failure in failures:
+ print(" FAIL: %s" % failure)
+ else:
+ print(" SKIPPED: no package manifest supplied")
+
+
+def main():
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument(
+ "--payload-root",
+ default=None,
+ help="Directory to recursively scan for .exe/.dll/.pyd PE
architecture. "
+ "If omitted, only the native-process oracle check runs.",
+ )
+ parser.add_argument(
+ "--require-payload",
+ action="store_true",
+ help="Fail if --payload-root is omitted or contains no valid PE
candidate files.",
+ )
+ parser.add_argument(
+ "--require-native-arm64",
+ action="store_true",
+ help="Fail unless the current process is running natively on Windows
Arm64.",
+ )
+ parser.add_argument(
+ "--json-report",
+ default=None,
+ help="Optional path to write the full machine-readable report.",
+ )
+ packages = parser.add_mutually_exclusive_group()
+ packages.add_argument(
+ "--package-lock",
+ default=None,
+ help="Verify installed pacman package versions against this lock
file.",
+ )
+ packages.add_argument(
+ "--package-baseline",
+ help="Record installed versions and report drift from a reference
lock; "
+ "missing packages still fail. This does not verify exact
versions.",
+ )
+ args = parser.parse_args()
+
+ oracle = read_native_process_oracle()
+
+ payload_results = []
+ payload_failures = []
+ if args.payload_root:
+ payload_results, payload_failures =
scan_payload_architecture(args.payload_root)
+ if args.require_payload and not payload_results:
+ payload_failures.append(
+ "No valid PE .exe/.dll/.pyd files were scanned under: %s" %
args.payload_root
+ )
+ elif args.require_payload:
+ payload_failures.append("--require-payload was set but --payload-root
was not provided.")
+
+ package_results = []
+ package_failures = []
+ package_manifest = args.package_lock or args.package_baseline
+ if package_manifest:
+ package_results, package_failures = verify_package_lock(
+ package_manifest, require_exact_versions=bool(args.package_lock))
+
+ report = {
+ "contract": "windows-arm64-clangarm64-v1",
+ "amendment": "A1",
+ "native_process_oracle": oracle,
+ "native_process_required": args.require_native_arm64,
+ "payload_root": args.payload_root,
+ "payload_files_scanned": len(payload_results),
+ "payload_failures": payload_failures,
+ "payload_results": payload_results,
+ "package_lock": args.package_lock,
+ "package_baseline": args.package_baseline,
+ "exact_package_versions_required": bool(args.package_lock),
+ "package_results": package_results,
+ "package_failures": package_failures,
+ }
+
+ print_native_report(oracle)
+ print_payload_report(args.payload_root, payload_results, payload_failures)
+ print_package_report(package_manifest, args.package_baseline,
package_results, package_failures)
+
+ if args.json_report:
+ with open(args.json_report, "w", encoding="utf-8") as f:
+ json.dump(report, f, indent=2)
+
+ if args.require_native_arm64 and not oracle["native_arm64_ok"]:
+ reason = oracle["reason"] or (
+ "observed process_machine=%s, native_machine=%s"
+ % (
+ machine_name(oracle["process_machine"]),
+ machine_name(oracle["native_machine"]),
+ )
+ )
+ print("RESULT: FAIL (native Windows Arm64 process required: %s)" %
reason)
+ return 1
+
+ if payload_failures:
+ print("RESULT: FAIL (%d payload architecture problem(s))" %
len(payload_failures))
+ return 1
+ if package_failures:
+ print("RESULT: FAIL (%d package lock problem(s))" %
len(package_failures))
+ return 1
+
+ print("RESULT: PASS")
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/libopenshot-audio-1.0.1/ci/windows-arm64-packages.lock
new/libopenshot-audio-1.0.2/ci/windows-arm64-packages.lock
--- old/libopenshot-audio-1.0.1/ci/windows-arm64-packages.lock 1970-01-01
01:00:00.000000000 +0100
+++ new/libopenshot-audio-1.0.2/ci/windows-arm64-packages.lock 2026-10-25
22:20:01.000000000 +0100
@@ -0,0 +1,20 @@
+# Windows ARM64 MSYS2 CLANGARM64 reference package versions
+# SPDX-FileCopyrightText: 2026 OpenShot Studios, LLC
+# SPDX-License-Identifier: LGPL-3.0-or-later
+#
+# Integration builds install available packages from the rolling MSYS2 repo.
+# --package-baseline records installed versions and reports drift from this
+# reference. It still rejects missing packages. --package-lock instead
+# requires an exact version match, for a separately provisioned environment.
+# This file does not provide archived packages or verify archive checksums.
+# This list contains the audio build and validation dependencies.
+#
+# name=version-release,sha256
+mingw-w64-clang-aarch64-clang=22.1.8-2,UNVERIFIED-NO-SIGNED-SNAPSHOT
+mingw-w64-clang-aarch64-libc++=22.1.8-1,UNVERIFIED-NO-SIGNED-SNAPSHOT
+mingw-w64-clang-aarch64-compiler-rt=22.1.8-2,UNVERIFIED-NO-SIGNED-SNAPSHOT
+mingw-w64-clang-aarch64-llvm-openmp=22.1.8-1,UNVERIFIED-NO-SIGNED-SNAPSHOT
+mingw-w64-clang-aarch64-libwinpthread=14.0.0.r302.gd7f3c5201-1,UNVERIFIED-NO-SIGNED-SNAPSHOT
+mingw-w64-clang-aarch64-cmake=4.4.2-2,UNVERIFIED-NO-SIGNED-SNAPSHOT
+mingw-w64-clang-aarch64-ninja=1.13.2-1,UNVERIFIED-NO-SIGNED-SNAPSHOT
+mingw-w64-clang-aarch64-python=3.14.7-1,UNVERIFIED-NO-SIGNED-SNAPSHOT