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

Reply via email to