diff --git a/CLAUDE.md b/.claude/CLAUDE.md similarity index 93% rename from CLAUDE.md rename to .claude/CLAUDE.md index 9992958..6319664 100644 --- a/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -1,9 +1,13 @@ # MLC Player çalışma kuralları -Bu dosya yalnız kalıcı kuralları içerir. Güncel durum ve sıradaki iş için -`docs/CONTINUITY.md`, doğrulanmış sonuç için -`docs/VERIFICATION_LEDGER.json` dosyasını oku. Ortak agent başlangıç -sözleşmesi kökteki `AGENTS.md` dosyasıdır. +Bu dosya yalnız Claude'a özgü kalıcı kuralları içerir. Güncel durum ve +sıradaki iş için `docs/CONTINUITY.md`, doğrulanmış sonuç için +`docs/VERIFICATION_LEDGER.json` dosyasını oku. Belge haritası +`docs/README.md` içindedir. Ortak agent başlangıç sözleşmesi +`docs/process/AGENTS.md` dosyasıdır ve aşağıdaki satırla her oturumda +otomatik yüklenir; orada yazan kural burada tekrarlanmaz: + +@../docs/process/AGENTS.md Aşağıdaki kuralların üçü `.claude/settings.json` içindeki hook'larla mekanik olarak da uygulanır: `git stash/reset/checkout/restore/clean` ile @@ -14,14 +18,13 @@ kapatır. ## Başlangıç -1. `AGENTS.md`, `CLAUDE.md`, `docs/CONTINUITY.md` ve ilgili ledger kaydını oku. +1. `docs/process/AGENTS.md`, `.claude/CLAUDE.md`, `docs/CONTINUITY.md` ve ilgili ledger kaydını oku. 2. `git status --short --branch` çalıştır (oturum hook'u bunu zaten sunar). 3. Yalnız görevle ilgili kaynak, test ve diff bölümlerini incele. Tüm depoyu veya eski raporları baştan okuma. 4. Kaynak ile rapor çelişirse kaynak ve yeniden üretilen davranış esas alınır. ## Çalışma yöntemi -- Kullanıcıyla Türkçe ve açık konuş. - Hata düzeltmesinde önce gerçek davranışı ölçen başarısız test yaz; sonra minimum ürün değişikliğini yap. - Tek turda tek bağımsız sorunu çöz. İlgisiz refaktör veya görsel değişiklik yapma. - Kirli çalışma ağacını koru; `stash`, `reset`, `checkout`, `restore`, `clean` @@ -69,7 +72,7 @@ imzası olmayan release REDDEDİLİR. Bu yüzden: Manifest `ready` değilse veya blocker listesi doluysa yayın kapısı kapalıdır. Dinamik listeyi `packaging/fetch_sources.py`, bütünlüğü `python packaging/prepublish.py --tag vX.Y` denetler. Kesin varlık ve komut - sözleşmesi yalnız `docs/RELEASE_PROCESS.md` içindedir. + sözleşmesi yalnız `docs/release/RELEASE_PROCESS.md` içindedir. - Installer imzaları `packaging/sign_release.py` ve `.sig` zinciriyle üretilir; bu uygulama imzası Windows Authenticode yerine geçmez. - **Özel anahtar depoya GİRMEZ.** Konum: `%USERPROFILE%\.mlcplayer\release_ed25519.key` @@ -85,7 +88,7 @@ imzası olmayan release REDDEDİLİR. Bu yüzden: `v0.31` varken `v0.4` yayımlanamaz (`31 > 4`, istemciler göremez); büyük adım için `v0.40`. `packaging/check_publishable.py` bunu zincirde durdurur. -- **YAYIN SÜRECİNİN TEK RESMÎ KAYNAĞI `docs/RELEASE_PROCESS.md`'DİR.** +- **YAYIN SÜRECİNİN TEK RESMÎ KAYNAĞI `docs/release/RELEASE_PROCESS.md`'DİR.** Kesin sıra, her adımın giriş/çıkış şartı, dinamik varlık sözleşmesi ve hata hâlinde nerede durulacağı ORADADIR; burada tekrarlanmaz. @@ -171,7 +174,7 @@ Test kapsamı etki alanına göre nokta atışıdır. - Native testler yalnız kendi başlattığı kesin PID'i `try/finally` ile temizlemeli; Notepad/Explorer veya kullanıcının Python/Qt süreçlerini hedeflememeli. - Native crash assertion geçse bile yok sayılmaz; son marker ve gerçek exit code raporlanır. - Görsel değişiklik yalnız offscreen testle kabul edilmez; gerçek Windows penceresi ve mümkünse gerçek video gerekir. -- **Kayıt defteri ölçümü ve düzeltmesi yalnız `python -c "import winreg..."` ile yapılır.** `Get-ItemProperty`, `Set-ItemProperty` ve `reg.exe` bu ortamda GÜVENİLİR DEĞİLDİR: ajanın PowerShell'inden yapılan yazmalar sanal katmanda kalır, gerçek hive'a ulaşmaz. Çapraz test: `reg.exe`'nin yazdığını python GÖREMEZ, python'un yazdığını `reg.exe` GÖRÜR. Bu yüzden "düzelttim" raporları üç tur boyunca yanlıştı (bkz. docs/PROJECT_STATUS.md, ölçüm aracı tuzağı). +- **Kayıt defteri ölçümü ve düzeltmesi yalnız `python -c "import winreg..."` ile yapılır.** `Get-ItemProperty`, `Set-ItemProperty` ve `reg.exe` bu ortamda GÜVENİLİR DEĞİLDİR: ajanın PowerShell'inden yapılan yazmalar sanal katmanda kalır, gerçek hive'a ulaşmaz. Çapraz test: `reg.exe`'nin yazdığını python GÖREMEZ, python'un yazdığını `reg.exe` GÖRÜR. Bu yüzden "düzelttim" raporları üç tur boyunca yanlıştı (bkz. docs/history/PROJECT_STATUS.md, ölçüm aracı tuzağı). - Windows kabuk davranışı (ör. "Birlikte aç" adı) yalnız dosya meta verisiyle doğrulanmaz; Explorer adı çıkarımla bulup ÖNBELLEĞE alır. Kabul, kullanıcının gerçek menüsünde görülmesidir. ## Tur sonu @@ -179,7 +182,7 @@ Test kapsamı etki alanına göre nokta atışıdır. İlgili testlerden sonra çalıştır: ```powershell -python -m compileall -q main.py app tests +python -m compileall -q app tests git diff --check ``` diff --git a/.claude/hooks/compile_check.py b/.claude/hooks/compile_check.py index 1f8e278..b3fd469 100644 --- a/.claude/hooks/compile_check.py +++ b/.claude/hooks/compile_check.py @@ -1,6 +1,6 @@ """Duzenlenen Python dosyasini ANINDA derler. -CLAUDE.md tur sonunda `python -m compileall -q main.py app tests` istiyor. +.claude/CLAUDE.md tur sonunda `python -m compileall -q app tests` istiyor. Tur sonu cok gec: sozdizimi hatasi o ana kadar yapilan butun koşumları bozabiliyor. Bu hook her Edit/Write sonrasi YALNIZ degisen dosyayi derler, bu yuzden maliyeti milisaniyedir. diff --git a/.claude/hooks/guard_git.py b/.claude/hooks/guard_git.py index bb53e21..3759734 100644 --- a/.claude/hooks/guard_git.py +++ b/.claude/hooks/guard_git.py @@ -1,4 +1,4 @@ -"""CLAUDE.md kuralini mekanik bariyere cevirir. +""".claude/CLAUDE.md kuralini mekanik bariyere cevirir. Kural: "Kirli calisma agacini koru; stash, reset, checkout veya kullanici degisikliklerini geri alan komutlar kullanma." @@ -58,7 +58,7 @@ def main(): reason = ( f"ENGELLENDI: `git {verb}` calisma agacindaki degisiklikleri geri " "alabilir.\n" - "CLAUDE.md kurali: kirli calisma agacini koru; stash/reset/checkout/" + ".claude/CLAUDE.md kurali: kirli calisma agacini koru; stash/reset/checkout/" "restore kullanma.\n" "Gercekten gerekiyorsa kullaniciya sor ve komutu KULLANICI calistirsin." ) diff --git a/.claude/hooks/session_start.py b/.claude/hooks/session_start.py index 204b201..dfab2ce 100644 --- a/.claude/hooks/session_start.py +++ b/.claude/hooks/session_start.py @@ -68,7 +68,7 @@ def next_step(): def main(): context = ( - "MLC Player oturum acilisi (AGENTS.md sozlesmesi, otomatik):\n\n" + "MLC Player oturum acilisi (docs/process/AGENTS.md sozlesmesi, otomatik):\n\n" "### git rev-parse --show-toplevel\n" f"{git_query(['rev-parse', '--show-toplevel'])}\n\n" "### git status --short --branch\n" diff --git a/README.md b/.github/README.md similarity index 86% rename from README.md rename to .github/README.md index 6016a24..d953e08 100644 --- a/README.md +++ b/.github/README.md @@ -3,7 +3,7 @@ **A cinematic media player for Windows, built on libmpv.** [![Latest release](https://img.shields.io/github/v/release/IzzmooPro/MLCPlayer)](https://github.com/IzzmooPro/MLCPlayer/releases/latest) -[![Licence: GPL v3](https://img.shields.io/badge/licence-GPLv3-blue)](LICENSE) +[![Licence: GPL v3](https://img.shields.io/badge/licence-GPLv3-blue)](../LICENSE) [![Platform](https://img.shields.io/badge/platform-Windows%2010%2F11-informational)](https://github.com/IzzmooPro/MLCPlayer/releases/latest) MLC Player has one interface and commits to it: a frameless window, an @@ -100,9 +100,9 @@ does not match is deleted. You do not run the `.sig` file yourself. The installer is not yet code-signed, so Windows SmartScreen will warn about an unknown publisher on first run. -See the project's [Code signing policy](CODE_SIGNING_POLICY.md), -[Privacy policy](PRIVACY.md), [SignPath readiness record](docs/SIGNPATH_READINESS.md) -and [application record](docs/SIGNPATH_FOUNDATION_APPLICATION.md). An +See the project's [Code signing policy](../docs/policies/CODE_SIGNING_POLICY.md), +[Privacy policy](../docs/policies/PRIVACY.md) and +[SignPath application and readiness record](../docs/release/SIGNPATH.md). An application was submitted on 23 August 2026 and was not approved by the Foundation on 2 September 2026 because public visibility/trust signals were not yet sufficient; the current installer remains unsigned. @@ -114,26 +114,34 @@ release as a separate step. ### Uninstall and user data -Uninstalling either package keeps your settings and logs so an upgrade or -reinstall does not erase your preferences. Logs are stored under -`%APPDATA%\MLCPlayer\logs` and can be inspected or deleted safely from -`Tools → Log Management`. The add-on has its own uninstaller; removing it -disables website extraction without removing the main player. +By default, uninstalling either package keeps your settings and logs so an +upgrade or reinstall does not erase your preferences. The main player's +interactive uninstaller offers separate unchecked choices to remove settings, +history and saved subtitle credentials, and to remove logs, thumbnail cache +and temporary update files. Selecting both choices removes the MLC Player data +owned by the Windows account registered when the installed player was opened; +the cleanup stops and reports a failure if that account cannot be identified +exactly. Unrelated files and shared legacy cache locations are never removed. +Logs are stored under `%APPDATA%\MLCPlayer\logs` +and can be inspected or deleted safely from `Tools → Log Management`. The +main uninstaller also offers to remove the installed Internet Video add-on; +the add-on's own uninstaller can instead remove it without removing the main +player. --- ## Build and run from source The quickest path is `Start.bat`: it locates Python 3.12-3.14, installs it only -if missing, installs the packages from `requirements.txt` only if they are +if missing, installs the packages from `requirements/runtime.txt` only if they are absent, verifies all three runtime binaries and starts the player. `Start.bat -CheckOnly` verifies everything without launching. Manually: ```bash -pip install -r requirements.txt -python main.py +pip install -r requirements/runtime.txt +python app/main.py ``` Manual local media playback requires Windows, Python 3.12-3.14 and @@ -160,7 +168,7 @@ The release chain lives in `packaging/`: `build_release.bat` drives PyInstaller, Inno Setup, the publisher signature and the verification steps in `verify_build.py`. It refuses to build a version that installed clients could not see as an update, and it stops if the installer cannot be signed. The -reasoning behind the packaging decisions is in `docs/PACKAGING_PLAN.md`. +reasoning behind the packaging decisions is in `docs/release/PACKAGING_PLAN.md`. --- @@ -169,7 +177,7 @@ reasoning behind the packaging decisions is in `docs/PACKAGING_PLAN.md`. Issues and pull requests are welcome at [github.com/IzzmooPro/MLCPlayer](https://github.com/IzzmooPro/MLCPlayer). The maintainer workflow, required CI check and evidence-preserving merge policy -are documented in [`docs/CHANGE_WORKFLOW.md`](docs/CHANGE_WORKFLOW.md). +are documented in [`docs/process/CHANGE_WORKFLOW.md`](../docs/process/CHANGE_WORKFLOW.md). Two things are worth knowing before you send a change: @@ -179,19 +187,19 @@ Two things are worth knowing before you send a change: interface, the playlist stays in an owned window beside the main window, mpv and the subtitle workers are shut down cooperatively rather than terminated, and no new always-on-top flags or timers are introduced. - `CLAUDE.md` records these invariants and the reasons behind them. + `.claude/CLAUDE.md` records these invariants and the reasons behind them. ### Developer setup -`requirements.txt` is enough to run the player. To run the tests or build a +`requirements/runtime.txt` is enough to run the player. To run the tests or build a release you also need the tooling: ``` -pip install -r requirements-lock.txt +pip install -r requirements/lock.txt ``` The lock file is the reproducible Windows test/release environment. -`requirements.txt` and `requirements-dev.txt` document the smaller direct +`requirements/runtime.txt` and `requirements/dev.txt` document the smaller direct runtime and developer dependency sets; their versions must match the lock. The developer set brings pytest, Pillow, PyInstaller and PySide6. PySide6 is @@ -233,7 +241,7 @@ Copyright (C) 2026 MLC Player contributors. MLC Player is licensed under the **GNU General Public License v3.0**, SPDX identifier **`GPL-3.0-only`**. Every source file carries that identifier in a -two-line SPDX header, and the full licence text is in [`LICENSE`](LICENSE) — +two-line SPDX header, and the full licence text is in [`LICENSE`](../LICENSE) — the canonical gnu.org text, byte for byte, which a test keeps pinned by its SHA-256. @@ -262,20 +270,20 @@ a binary it ships are not the same thing. For MLC Player itself the corresponding source is this repository. For the third-party binaries we redistribute, every component is recorded in -[`bin/RUNTIME_MANIFEST.txt`](bin/RUNTIME_MANIFEST.txt) with its exact version, +[`bin/RUNTIME_MANIFEST.txt`](../bin/RUNTIME_MANIFEST.txt) with its exact version, the upstream URL it came from and its SHA-256, and -[`licenses/mpv-NOTICE.txt`](licenses/mpv-NOTICE.txt) names the upstream +[`licenses/mpv-NOTICE.txt`](../licenses/mpv-NOTICE.txt) names the upstream repositories for mpv, FFmpeg and the build recipe. Both files ship inside the installed package, not only here. We do not modify any of those sources. The runtime manifest records binary provenance; a binary, installer archive, or development archive is **not** corresponding source. The separate -[`packaging/corresponding_sources.json`](packaging/corresponding_sources.json) +[`packaging/corresponding_sources.json`](../packaging/corresponding_sources.json) contract lists rebuildable source archives. The release gate stays closed while that contract has any blocker. Once the exact build inventory and source bundles are verified, `python packaging/fetch_sources.py` will fetch only those source archives, verify their size and SHA-256, and place them in -`source_mirror/` for publication beside the installers. +`output/source_mirror/` for publication beside the installers. The verified subset can be collected during remediation with `python packaging/fetch_sources.py --allow-incomplete`; this does not open the diff --git a/README.tr.md b/.github/README.tr.md similarity index 88% rename from README.tr.md rename to .github/README.tr.md index 234d8c5..6ecb7be 100644 --- a/README.tr.md +++ b/.github/README.tr.md @@ -80,9 +80,9 @@ silinir. `.sig` dosyasını kendiniz çalıştırmazsınız. Kurulum henüz kod imzalı değildir; Windows SmartScreen ilk çalıştırmada “bilinmeyen yayıncı” uyarısı gösterebilir. -Projenin [Kod imzalama politikası](CODE_SIGNING_POLICY.md), -[Gizlilik politikası](PRIVACY.md), [SignPath hazırlık kaydı](docs/SIGNPATH_READINESS.md) -ve [başvuru kaydı](docs/SIGNPATH_FOUNDATION_APPLICATION.md) herkese açıktır. +Projenin [Kod imzalama politikası](../docs/policies/CODE_SIGNING_POLICY.md), +[Gizlilik politikası](../docs/policies/PRIVACY.md) ve +[SignPath başvuru ve hazırlık kaydı](../docs/release/SIGNPATH.md) herkese açıktır. Başvuru 23 Ağustos 2026 tarihinde gönderildi; Foundation başvuruyu 2 Eylül 2026'da projenin henüz yeterli kamu görünürlüğü/güven sinyali olmadığı gerekçesiyle onaylamadı. Mevcut kurulum imzasızdır. @@ -94,16 +94,24 @@ sayfasından ayrıca indirip çalıştırın. ### Kaldırma ve kullanıcı verileri -Paketlerden birini kaldırmak ayarlarınızı ve günlüklerinizi korur; böylece -yükseltme veya yeniden kurulum tercihlerinizi silmez. Günlükler -`%APPDATA%\MLCPlayer\logs` altında tutulur ve `Araçlar → Günlük Yönetimi` -üzerinden güvenle incelenebilir veya silinebilir. Ek paketin ayrı kaldırıcısı -vardır; onu kaldırmak ana oynatıcıyı silmeden site çıkarımını kapatır. +Varsayılan olarak paketlerden birini kaldırmak ayarlarınızı ve +günlüklerinizi korur; böylece yükseltme veya yeniden kurulum tercihlerinizi +silmez. Ana oynatıcının etkileşimli kaldırıcısında, ayarları/geçmişi/kayıtlı +altyazı kimliklerini ve günlükleri/küçük resim önbelleğini/geçici +güncelleme dosyalarını silmek için ayrı, varsayılan boş seçenekler vardır. +İki veri seçeneği de işaretlenirse kurulu oynatıcı açıldığında kaydedilmiş +Windows hesabına ait MLC Player verileri silinir; bu hesap kesin olarak +belirlenemezse temizlik durur ve hata bildirilir. İlgisiz dosyalar ve +paylaşılan eski önbellek konumlarına dokunulmaz. Günlükler +`%APPDATA%\MLCPlayer\logs` altında tutulur ve +`Araçlar → Günlük Yönetimi` üzerinden güvenle incelenebilir veya silinebilir. +Ana kaldırıcı ayrıca kurulu Internet Video eklentisini de kaldırmayı sunar; +eklenti kendi kaldırıcısıyla ana oynatıcıyı silmeden de kaldırılabilir. ## Çalıştırma (kaynaktan) En kolay yol `Start.bat` dosyasına çift tıklamaktır: Python 3.12-3.14 yoksa -kurar, eksik paketleri `requirements.txt` üzerinden yükler ve programı açar. +kurar, eksik paketleri `requirements/runtime.txt` üzerinden yükler ve programı açar. Başlatmadan önce üç çalışma zamanı ikilisini de doğrular. Her şey hazırsa hiçbir kurulum yapmadan doğrudan başlatır. Yalnızca kontrol için `Start.bat -CheckOnly` kullanın; program açılmaz. @@ -114,11 +122,11 @@ gerekir. İnternet videosu site çıkarımı ayrıca `bin/yt-dlp.exe` ve Mevcut `Start.bat` üç ikiliyi de zorunlu tutar. ```bash -pip install -r requirements.txt +pip install -r requirements/runtime.txt ``` ```bash -python main.py +python app/main.py ``` Bu çalışma zamanı ikilileri boyutları nedeniyle depoda tutulmaz; sağlamaları @@ -139,14 +147,14 @@ pakete dahil değildir. Release zinciri `packaging/` altındadır: `build_release.bat`, `MLCPlayer.iss` ve ölçümlü doğrulama adımlarıyla `verify_build.py`. -Kararların gerekçesi `docs/PACKAGING_PLAN.md` içindedir. +Kararların gerekçesi `docs/release/PACKAGING_PLAN.md` içindedir. ## Lisans Copyright (C) 2026 MLC Player katkıcıları. MLC Player **GNU General Public License v3.0** ile lisanslanmıştır; tam metin -[`LICENSE`](LICENSE) dosyasındadır (gnu.org kanonik metni, 35 149 bayt, +[`LICENSE`](../LICENSE) dosyasındadır (gnu.org kanonik metni, 35 149 bayt, değiştirilmeden). Bu program özgür yazılımdır: GNU GPL sürüm 3 şartları altında @@ -172,7 +180,7 @@ lisansı aynı şey değildir. ### Değişiklik katkısı Bakımcı dal/PR sırası, zorunlu CI kontrolü ve kanıt commit'lerini koruyan -birleştirme yöntemi [`docs/CHANGE_WORKFLOW.md`](docs/CHANGE_WORKFLOW.md) +birleştirme yöntemi [`docs/process/CHANGE_WORKFLOW.md`](../docs/process/CHANGE_WORKFLOW.md) dosyasındadır. ### Yayın öncesi açık maddeler diff --git a/.github/workflows/build-unsigned-main.yml b/.github/workflows/build-unsigned-main.yml index 115d958..2a489b0 100644 --- a/.github/workflows/build-unsigned-main.yml +++ b/.github/workflows/build-unsigned-main.yml @@ -48,7 +48,7 @@ jobs: with: python-version: '3.13.15' cache: pip - cache-dependency-path: requirements-build-windows.txt + cache-dependency-path: requirements/build-windows.txt - name: Install pinned ORAS CLI uses: oras-project/setup-oras@1d808f7d7f6995cc68b7bf507bfe5c5446e1dc9d # v2.0.1 @@ -111,12 +111,12 @@ jobs: shell: pwsh run: | python -m pip install --require-hashes --only-binary=:all: ` - --no-deps -r requirements-build-windows.txt ` + --no-deps -r requirements/build-windows.txt ` --disable-pip-version-check if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } python -m pip check if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - python packaging/verify_dependencies.py requirements-lock.txt + python packaging/verify_dependencies.py requirements/lock.txt - name: Pull and verify the immutable libmpv runtime shell: pwsh @@ -174,7 +174,7 @@ jobs: run: | $ErrorActionPreference = "Stop" $version = python -c "from app.config import APP_VERSION; print(APP_VERSION)" - $path = "installer_output/MLCPlayer_Setup_$version.exe" + $path = "output/installer/MLCPlayer_Setup_$version.exe" if (-not (Test-Path -LiteralPath $path -PathType Leaf)) { throw "Expected installer is missing: $path" } @@ -196,7 +196,7 @@ jobs: runner_image_version = $env:ImageVersion python_version = (python --version) inno_setup_version = $env:INNO_VERSION - wheel_lock_sha256 = (Get-FileHash requirements-build-windows.txt -Algorithm SHA256).Hash.ToLowerInvariant() + wheel_lock_sha256 = (Get-FileHash requirements/build-windows.txt -Algorithm SHA256).Hash.ToLowerInvariant() runtime_manifest_digest = $lock.digest runtime_sha256 = $lock.runtime_sha256 installer_name = $installer.Name @@ -205,7 +205,7 @@ jobs: authenticode_status = [string]$signature.Status } $provenance | ConvertTo-Json | Set-Content ` - -LiteralPath installer_output/build-provenance.json -Encoding utf8 + -LiteralPath output/installer/build-provenance.json -Encoding utf8 "path=$path" >> $env:GITHUB_OUTPUT "name=$($installer.Name)" >> $env:GITHUB_OUTPUT @@ -221,6 +221,6 @@ jobs: uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: mlcplayer-unsigned-main-provenance-${{ github.sha }} - path: installer_output/build-provenance.json + path: output/installer/build-provenance.json if-no-files-found: error retention-days: 90 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 096ebbd..20f316c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -92,7 +92,7 @@ jobs: with: python-version: '3.13.15' cache: pip - cache-dependency-path: requirements-lock.txt + cache-dependency-path: requirements/lock.txt - name: Install documentation-check dependencies if: steps.classify.outputs.docs_only == 'true' @@ -100,12 +100,12 @@ jobs: env: MLC_CI: '0' PYTHONPATH: '' - run: python -m pip install -r requirements-ci-docs.txt --disable-pip-version-check + run: python -m pip install -r requirements/ci-docs.txt --disable-pip-version-check - name: Install locked dependencies if: steps.classify.outputs.docs_only != 'true' shell: pwsh - run: python -m pip install -r requirements-lock.txt --disable-pip-version-check + run: python -m pip install -r requirements/lock.txt --disable-pip-version-check - name: Verify ledger append-only history if: github.event_name == 'pull_request' @@ -117,12 +117,12 @@ jobs: - name: Verify locked environment if: steps.classify.outputs.docs_only != 'true' shell: pwsh - run: python packaging/verify_dependencies.py requirements-lock.txt + run: python packaging/verify_dependencies.py requirements/lock.txt - name: Compile Python sources if: steps.classify.outputs.docs_only != 'true' shell: pwsh - run: python -m compileall -q main.py app tests packaging + run: python -m compileall -q app tests packaging - name: Verify translations and whitespace if: steps.classify.outputs.docs_only != 'true' @@ -147,7 +147,9 @@ jobs: tests/test_readme_user_guidance_regressions.py ` tests/test_release_documentation_regressions.py ` tests/test_signpath_readiness_regressions.py ` - tests/test_video_format_acceptance_plan_regressions.py + tests/test_video_format_acceptance_plan_regressions.py ` + tests/test_docs_index_regressions.py ` + tests/test_repository_privacy_regressions.py if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } python -m json.tool docs/VERIFICATION_LEDGER.json > $null if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } diff --git a/.gitignore b/.gitignore index 7ac80ec..1a058bc 100644 --- a/.gitignore +++ b/.gitignore @@ -7,10 +7,14 @@ __pycache__/ .venv/ venv/ -# Build outputs +# Build outputs: everything generated lives under output/ (build, dist, +# installer, source_mirror). The pre-3-October-2026 root locations stay +# ignored so an old tree left on disk can never be added by accident. +output/ build/ dist/ installer_output/ +source_mirror/ # Temporary test/runtime folders tmp/ @@ -59,4 +63,4 @@ assets/icon-concepts/ # Mirrored upstream source archives (GPLv3 corresponding source). Build # output: fetched by packaging/fetch_sources.py and uploaded to the # release, never tracked here. -source_mirror/ +output/source_mirror/ diff --git a/app/config.py b/app/config.py index 10e1a7a..bc64150 100644 --- a/app/config.py +++ b/app/config.py @@ -14,6 +14,8 @@ WINDOW_WIDTH = 800 WINDOW_HEIGHT = 450 DEFAULT_VOLUME = 70 +# Menü çubuğu ve video sağ-tık menüsü bu tek listeyi kullanır. +PLAYBACK_SPEEDS = (0.5, 0.75, 1.0, 1.25, 1.5, 2.0) # 100 üstü mpv amplifikasyonu; ses çubuğu 0-175 arası değer alır MAX_VOLUME = 175 diff --git a/app/empty_state.py b/app/empty_state.py index 3a331bc..93204ed 100644 --- a/app/empty_state.py +++ b/app/empty_state.py @@ -5,7 +5,6 @@ from PyQt6.QtCore import Qt from PyQt6.QtWidgets import QLabel, QPushButton, QVBoxLayout, QWidget -from app.app_icon import application_icon from app.config import (UI_ACCENT, UI_ACCENT_HOVER, UI_ACCENT_PRESSED, UI_FONT_FAMILY) from app.i18n import tr @@ -41,16 +40,6 @@ def __init__(self, video_frame): root.setSpacing(0) root.addStretch(2) - self.logo_label = QLabel(self) - self.logo_label.setObjectName("emptyStateLogo") - self.logo_label.setAlignment(Qt.AlignmentFlag.AlignCenter) - self.logo_label.setStyleSheet("background: transparent;") - icon = application_icon() - if not icon.isNull(): - self.logo_label.setPixmap(icon.pixmap(94, 94)) - root.addWidget(self.logo_label) - root.addSpacing(18) - self.title_label = QLabel(tr("İzlemeye hazır"), self) self.title_label.setObjectName("emptyStateTitle") self.title_label.setAlignment(Qt.AlignmentFlag.AlignCenter) @@ -126,7 +115,7 @@ def _run_player_action(self, name): def set_placeholder_text(self, text, default_text): """Normal başlangıç ve bağlantı-yükleniyor görünümleri arasında geç.""" loading = bool(text and text != default_text) - for widget in (self.logo_label, self.title_label, self.hint_label, + for widget in (self.title_label, self.hint_label, self.open_file_button, self.open_folder_button): widget.setVisible(not loading) self.loading_label.setText(text if loading else "") diff --git a/app/errors.py b/app/errors.py index 4b50c53..97a419b 100644 --- a/app/errors.py +++ b/app/errors.py @@ -40,6 +40,7 @@ # aktarilir ve i18n `app.settings_store` uzerinden ek bir zincir getirir. # Cekirdek Qt'yi cagri aninda yukler (bkz. o modulun gerekcesi). from app.translate import tr, tr_mark, translate_marked +from app.number_text import byte_count_text, localize_decimal # Maskeleme işaretleri ve kayıt numarası biçimi. MASK = "" @@ -485,13 +486,13 @@ def format_bytes(size): try: value = float(size) except (TypeError, ValueError): - return "0 bayt" + return byte_count_text(0) if value < 1024: - return f"{int(value)} bayt" + return byte_count_text(value) for unit in ("KB", "MB", "GB"): value /= 1024.0 if value < 1024 or unit == "GB": - return f"{value:.1f}".replace(".", ",") + f" {unit}" + return localize_decimal(f"{value:.1f}") + f" {unit}" return f"{value:.1f} GB" @@ -695,7 +696,7 @@ def log_error_event(event): def safe_console(message): """Üretim konsol çıktısının TEK güvenli sınırı. - `main.py` ve `app/**/*.py` içindeki bütün konsol yazımları buradan + `app/main.py` ve `app/**/*.py` içindeki bütün konsol yazımları buradan geçer; stdout'a yazmadan hemen önce merkezi `redact()` uygulanır. Böylece ham yol, URL token'ı, `Authorization` değeri veya ham `str(exception)` konsola ULAŞMAZ ve yeni bir çağıran güvenlik diff --git a/app/local_subtitle.py b/app/local_subtitle.py index 8321678..bd40b2e 100644 --- a/app/local_subtitle.py +++ b/app/local_subtitle.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-only """Video açılışında aynı klasördeki eşleşen SRT'yi SESSİZ etkinleştirme. -Sözleşme `docs/PROJECT_STATUS.md` içindeki 14 Ağustos 2026 araştırmasıdır ve +Sözleşme `docs/history/PROJECT_STATUS.md` içindeki 14 Ağustos 2026 araştırmasıdır ve teknik kaynağı mpv'nin `sub-auto=exact` davranışıdır. Bu modül `SubtitleTrackWatcher`'dan AYRIDIR: watcher yalnız altyazının güvenli diff --git a/main.py b/app/main.py similarity index 84% rename from main.py rename to app/main.py index 5390cbc..2b82f9c 100644 --- a/main.py +++ b/app/main.py @@ -2,6 +2,18 @@ # SPDX-License-Identifier: GPL-3.0-only import os import sys + +# `python app/main.py` puts app/ itself first on sys.path; the package +# imports below need the project root instead. A frozen build and +# `python -m app.main` already resolve `app` correctly. +_PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +if not getattr(sys, 'frozen', False) and not __package__: + _APP_DIR = os.path.dirname(os.path.abspath(__file__)) + sys.path[:] = [entry for entry in sys.path + if os.path.abspath(entry or os.curdir) != _APP_DIR] + if _PROJECT_ROOT not in sys.path: + sys.path.insert(0, _PROJECT_ROOT) + from PyQt6.QtWidgets import QApplication, QMessageBox from PyQt6.QtCore import QTimer @@ -17,7 +29,7 @@ def get_bin_dir(): Adım adım arar (ilk bulunan geçerli): 1. PyInstaller paketlemesi -> sys._MEIPASS/bin (--add-data ile gömülü) 2. EXE'nin yanındaki klasör -> /bin (exe ile birlikte taşınan) - 3. Geliştirme dizini -> /bin (kaynak koddan çalıştırma) + 3. Geliştirme dizini -> /bin (kaynak koddan çalıştırma) """ candidates = [] @@ -30,8 +42,8 @@ def get_bin_dir(): if getattr(sys, 'frozen', False) and sys.executable: candidates.append(os.path.join(os.path.dirname(sys.executable), 'bin')) - # 3. Kaynak koddan çalışırken main.py'nin yanı - candidates.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'bin')) + # 3. Kaynak koddan çalışırken proje kökü (app/ klasörünün bir üstü) + candidates.append(os.path.join(_PROJECT_ROOT, 'bin')) for candidate in candidates: if os.path.isdir(candidate): @@ -148,7 +160,17 @@ def check_dependencies(): if check_dependencies(): from app.player import MPVPlayer player = MPVPlayer() + player.center_on_active_screen() player.show() + # Kurulu kopyada kaldırma temizliği gerçek kullanıcı oturumuna + # bağlanır. Kayıt alt süreci 30 sn'ye kadar sürebildiği için pencere + # açıldıktan SONRA arka planda çalışır; başarısızlığı oynatıcıyı + # bozmaz, kaldırıcı daha sonra kullanıcı verisini silmiş gibi + # davranmaz. + from app.user_cleanup_helper import start_user_cleanup_registration + start_user_cleanup_registration( + report=lambda result: safe_console( + f"Uninstall cleanup task: {result}")) # Başka bir başlatma isteği: pencere öne gelir, dosya BURAYA yüklenir. instance_guard.activation_requested.connect( lambda payload: activate_window(player, payload)) @@ -161,7 +183,7 @@ def check_dependencies(): from app.updater import start_startup_check # Referans çöp toplayıcı tarafından alınmasın diye tutulur. player._update_checker = start_startup_check(player) - # Komut satırından dosya/URL argümanı: python main.py video.mp4 + # Komut satırından dosya/URL argümanı: python app/main.py video.mp4 if len(sys.argv) > 1: QTimer.singleShot( 0, lambda: player.open_external_target(sys.argv[1])) diff --git a/app/media_controls.py b/app/media_controls.py index 85838e6..4bf54c5 100644 --- a/app/media_controls.py +++ b/app/media_controls.py @@ -12,8 +12,9 @@ import mpv from app.config import MEDIA_EXTENSIONS, SUBTITLE_EXTENSIONS, DEFAULT_VOLUME, MAX_VOLUME +from app.number_text import percent_text from app.utils import format_time, time_to_seconds -from app.errors import show_user_error, safe_console +from app.errors import show_user_error, safe_console, log from app.media_info import sanitize_media_url from app.runtime_binaries import (INTERNET_VIDEO_MISSING_MESSAGE, INTERNET_VIDEO_MISSING_TITLE) @@ -50,8 +51,8 @@ def _reset_subtitle_timing_for_new_media(player): """Dosyaya özel altyazı gecikmesini yeni medyaya taşımayı önler.""" try: player.mpv_player.sub_delay = 0.0 - except Exception: - pass + except Exception as exc: + log(f"Subtitle delay reset failed: {type(exc).__name__}", "WARNING") settings = getattr(player, "settings", None) if settings is not None: settings.setValue("subtitle/sub_delay", 0.0) @@ -94,8 +95,9 @@ def _hide_subtitles_for_new_media(player): """Yeni medya otomatik bulunan altyazıyla başlasa bile görünürlüğü kapatır.""" try: player.mpv_player.sub_visibility = False - except Exception: - pass + except Exception as exc: + log(f"Subtitle hide for new media failed: {type(exc).__name__}", + "WARNING") def reset_picture_in_picture_media_availability(player): @@ -793,24 +795,33 @@ def open_subtitle(player): player.video_frame.show_osd(tr("Altyazı yükleniyor...")) return - try: - player.mpv_player.sub_add(subtitle_path) - _refresh_overlay_subtitle_state(player) - safe_console(f"Subtitle added: {subtitle_path}") - player.video_frame.show_osd(tr("Altyazı eklendi")) - except Exception as e: - safe_console(f"Open subtitle error: {e}") - show_user_error(player, tr("Altyazı Eklenemedi"), - tr("Altyazı eklenemedi. Dosyanın hasarlı veya " - "desteklenmeyen bir format olması mümkün."), - exc=e) + # Sürükle-bırakla AYNI yaşam döngüsü: iz doğrulanır, seçilir ve görünür + # yapılır. Ham `sub_add` görünürlüğü açmaz (gerçek libmpv ölçümü); yeni + # medya açılışında görünürlük kapatıldığı için altyazı görünmüyordu. + # Başarı OSD'si ve hata penceresi o yolun içindedir. + player._activate_dropped_subtitle(subtitle_path) + _refresh_overlay_subtitle_state(player) def select_subtitle_language(player, sid): + previous_sid = getattr(player.mpv_player, "sid", None) + sid_written = False try: player.mpv_player.sid = sid + sid_written = True + # Açıkça seçilen parça gösterilir. `sid` yazımı görünürlüğü + # değiştirmez (gerçek libmpv ölçümü) ve yeni medya açılışında + # görünürlük kapatıldığı için seçim ekranda hiçbir şey göstermiyordu. + player.mpv_player.sub_visibility = True _refresh_overlay_subtitle_state(player) safe_console(f"Selected subtitle ID: {sid}") except Exception as e: + if sid_written: + # Görünürlük yazılamadıysa seçim yarım kalmasın. + try: + player.mpv_player.sid = previous_sid + except Exception: + pass + _refresh_overlay_subtitle_state(player) safe_console(f"Subtitle selection error: {e}") show_user_error(player, tr("Altyazı Seçilemedi"), tr("Altyazı seçilemedi. Lütfen başka bir altyazı " @@ -1077,12 +1088,20 @@ def natural_end_should_rewind(player): def handle_natural_end(player): """Doğal bitişte sıradaki öğeye geç, sar veya mevcut öğeyi başta tut.""" if (getattr(player, "_eof_rewound", False) + or getattr(player, "_natural_end_active", False) or not natural_end_should_rewind(player)): return False - if getattr(player, "playlist", None): - if _play_playlist_step(player, 1, notify=False): - return True - return finish_media_at_start(player) + # Yükleme hatası modal pencere açar ve bu timer onun döngüsünde de + # çalışır; durum "sonda" geri alındığı için koruma olmadan her tik aynı + # yüklemeyi yeniden deneyip yeni bir hata penceresi açardı. + player._natural_end_active = True + try: + if getattr(player, "playlist", None): + if _play_playlist_step(player, 1, notify=False): + return True + return finish_media_at_start(player) + finally: + player._natural_end_active = False def _restore_slider(slider, value): """Kontrolü yeni ürün işlemi üretmeden ölçülen değere döndürür.""" @@ -1136,16 +1155,16 @@ def set_volume(player, volume): _restore_volume_sliders(player, volume) try: - player.volume_label.setText(f"%{int(volume)}") + player.volume_label.setText(percent_text(volume)) if volume == 0: player.volume_icon.setIcon(player.style().standardIcon(QStyle.StandardPixmap.SP_MediaVolumeMuted)) player.is_muted = True - osd_text = "Sessiz" + osd_text = tr("Sessiz") else: player.volume_icon.setIcon(player.style().standardIcon(QStyle.StandardPixmap.SP_MediaVolume)) player.is_muted = False player.last_volume = volume - osd_text = f"Ses: %{int(volume)}" + osd_text = tr("Ses: %{volume}").format(volume=int(volume)) if getattr(player, '_ui_ready', False): player.video_frame.show_osd(osd_text) except Exception as e: diff --git a/app/media_info.py b/app/media_info.py index 2ea84c3..fab50e3 100644 --- a/app/media_info.py +++ b/app/media_info.py @@ -47,6 +47,7 @@ # düzeyinde PyQt6 yükler ve bu dosyanın Qt'siz import sözleşmesini bozardı # (tests/test_media_info_builder_regressions.py). from app.translate import tr, tr_mark, translate_marked +from app.number_text import byte_count_text, localize_decimal from app.track_labels import (bitrate_label, channel_name, codec_name, language_name, sample_rate_label) @@ -151,13 +152,13 @@ def format_size_text(size): try: value = float(size) except (TypeError, ValueError): - return "0 bayt" + return byte_count_text(0) if value < 1024: - return f"{int(value)} bayt" + return byte_count_text(value) for unit in ("KB", "MB", "GB"): value /= 1024.0 if value < 1024 or unit == "GB": - return f"{value:.1f}".replace(".", ",") + f" {unit}" + return localize_decimal(f"{value:.1f}") + f" {unit}" return f"{value:.1f} GB" @@ -391,7 +392,7 @@ def _aspect_text(value): for known, label in _COMMON_ASPECTS: if abs(ratio - known) <= _ASPECT_TOLERANCE: return label - return f"{ratio:.2f}".replace(".", ",") + ":1" + return localize_decimal(f"{ratio:.2f}") + ":1" def _pixel_format_text(track, params): @@ -473,7 +474,7 @@ def _fps_text(track): if value <= 0: return "" text = f"{value:.3f}".rstrip("0").rstrip(".") - return text.replace(".", ",") + " fps" + return localize_decimal(text) + " fps" def _video_aspect_text(track, params): diff --git a/app/menu_actions.py b/app/menu_actions.py index 1ddf5be..fcf2472 100644 --- a/app/menu_actions.py +++ b/app/menu_actions.py @@ -12,7 +12,8 @@ from app.errors import show_user_error, safe_console from app import i18n from app.i18n import tr -from app.config import APP_VERSION, COPYRIGHT_YEAR, SUBTITLE_DEFAULTS +from app.config import (APP_VERSION, COPYRIGHT_YEAR, PLAYBACK_SPEEDS, + SUBTITLE_DEFAULTS) from app.updater import check_for_updates from app.media_info import build_media_info, media_info_refresh_key from app.media_controls import is_remote_media_url, safe_media_host @@ -211,7 +212,7 @@ def setup_menu(player): speed_actions = {} speed_group = QActionGroup(player) speed_group.setExclusive(True) - for speed in [0.5, 0.75, 1.0, 1.25, 1.5, 2.0]: + for speed in PLAYBACK_SPEEDS: speed_action = QAction(f"{speed}x", player) speed_action.setCheckable(True) speed_group.addAction(speed_action) diff --git a/app/number_text.py b/app/number_text.py new file mode 100644 index 0000000..973e180 --- /dev/null +++ b/app/number_text.py @@ -0,0 +1,27 @@ +# SPDX-FileCopyrightText: 2026 MLC Player contributors +# SPDX-License-Identifier: GPL-3.0-only +"""Dile göre sayı metinleri (ondalık ayırıcı, yüzde, bayt). + +Qt İÇERMEZ: `media_info` gibi Qt'siz modüller de kullanır. Biçim +`tr()` ile çevrilir; testler makinenin sistem dilinden bağımsızdır. +""" + +from app.translate import tr + + +def localize_decimal(text): + """`23.976` gibi noktalı sayıyı dilin ondalık ayırıcısıyla yazar.""" + whole, dot, fraction = str(text).partition(".") + if not dot: + return whole + return tr("{whole},{fraction}").format(whole=whole, fraction=fraction) + + +def byte_count_text(count): + """1024'ten küçük boyut: Türkçe `512 bayt`, İngilizce `512 bytes`.""" + return tr("{count} bayt").format(count=int(count)) + + +def percent_text(value): + """Yüzde değerini dilin yazımıyla verir (Türkçe `%70`, İngilizce `70%`).""" + return tr("%{value}").format(value=int(round(float(value)))) diff --git a/app/player.py b/app/player.py index 7354ec5..00140c2 100644 --- a/app/player.py +++ b/app/player.py @@ -7,7 +7,9 @@ import mpv from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QMessageBox, QSizePolicy) -from PyQt6.QtCore import Qt, QTimer, QSettings, QObject, QEventLoop, QRect +from PyQt6.QtCore import (Qt, QTimer, QSettings, QObject, QEventLoop, QPoint, + QRect) +from PyQt6.QtGui import QCursor from app.config import cinematic_ui_enabled, APP_NAME, WINDOW_WIDTH, WINDOW_HEIGHT, APP_STYLE, DEFAULT_VOLUME, MAX_VOLUME, MPV_CONFIG, MEDIA_EXTENSIONS, SUBTITLE_EXTENSIONS, SUBTITLE_DEFAULTS from app.settings_store import user_settings @@ -24,7 +26,7 @@ from app.app_icon import apply_window_icon from app.runtime_binaries import (YOUTUBE_YTDL_RAW_OPTIONS, internet_video_ready, ytdl_script_opt) -from main import get_bin_dir +from app.main import get_bin_dir from app.subtitle_service import (SubtitleSession, TRACK_WAIT_ATTEMPTS, TRACK_WAIT_INTERVAL_S) from app.subtitle_center_composition import ( @@ -66,6 +68,34 @@ _MPV_OBSERVED_MISSING = object() +def centered_startup_position(available, window_size): + """Mevcut pencere boyutunu değiştirmeden çalışma alanında ortala. + + Pencere çalışma alanından büyükse sol üst köşe alanın içinde tutulur; + aksi hâlde başlık çubuğu ekran dışına düşer ve pencere tutulamaz. + """ + return QPoint( + available.x() + max(0, (available.width() - window_size.width()) // 2), + available.y() + max(0, (available.height() - window_size.height()) // 2), + ) + + +def restore_saved_window_geometry(window, saved_geometry): + """Kayıtlı boyut/konum ve büyütülmüş durumu geri yükler. + + Qt tam ekran bayrağını da geri getirir; ürünün tam ekran durumu ise + kalıcı değildir. Tam ekranda kapatılan pencere ürün habersizken tam + ekran açılmasın diye yalnız o bayrak temizlenir. + """ + if not saved_geometry: + return False + restored = window.restoreGeometry(saved_geometry) + state = window.windowState() + if state & Qt.WindowState.WindowFullScreen: + window.setWindowState(state & ~Qt.WindowState.WindowFullScreen) + return restored + + def build_ytdl_config(bin_dir): """MPV'ye verilecek ytdl karari. SISTEM FALLBACK YOKTUR. @@ -251,8 +281,7 @@ def __init__(self): # Kaydedilmiş pencere boyutu/konumu ve ses seviyesini geri yükle saved_geometry = self.settings.value("geometry") - if saved_geometry: - self.restoreGeometry(saved_geometry) + restore_saved_window_geometry(self, saved_geometry) saved_volume = self.settings.value("volume") try: volume = int(float(saved_volume)) if saved_volume is not None else DEFAULT_VOLUME @@ -286,6 +315,15 @@ def __init__(self): self._ui_ready = True + def center_on_active_screen(self): + """Yalnız süreç açılışında pencereyi aktif ekranın ortasına taşı.""" + screen = (QApplication.screenAt(QCursor.pos()) + or self.screen() + or QApplication.primaryScreen()) + if screen is not None: + self.move(centered_startup_position( + screen.availableGeometry(), self.size())) + def clear_title_bar_raise_pending(self): """Yeni bir medya yükleme girişimi başladığında eski işareti siler. @@ -531,6 +569,10 @@ def update_ui(self): # NOT: Kullanıcı penceresine geliştirici talimatı # (`pip install ...`) YAZILMAZ. msg = f"{tr('Dosya açılamadı:')}\n{self.current_file}" + # Pencere modal `exec()` ile açılır ve bu timer onun + # döngüsünde de çalışır; işaret ÖNCE sıfırlanmazsa her tik + # yeni bir hata penceresi daha açar. + self._load_started_at = 0 show_user_error(self, tr("Dosya Açılamadı"), tr("Dosya açılamadı. Dosyanın mevcut ve " "desteklenen bir medya dosyası olduğunu " @@ -653,8 +695,14 @@ def _activate_dropped_subtitle(self, path): "kontrol edin."), details=f"{tr('Altyazı yolu:')} {path}") return False - if self._drop_subtitle_session is None: + # Oturum önceki MLC track'inin `sid`'ini hatırlar ve yeni eklemede + # kaldırır; o numara başka medyada BAŞKA bir track'i gösterir. Bu + # yüzden oturum medyaya bağlıdır. + if (self._drop_subtitle_session is None + or self.__dict__.get("_drop_subtitle_media") + != self.current_file): self._drop_subtitle_session = SubtitleSession() + self._drop_subtitle_media = self.current_file try: applied = bool(self._drop_subtitle_session.apply( self, path, wait=self._subtitle_track_wait, @@ -1318,7 +1366,10 @@ def closeEvent(self, event): # Ayarları kaydet (pencere boyutu, ses seviyesi, son klasör) try: - self.settings.setValue("geometry", self.saveGeometry()) + # PiP geçici küçük bir moddur; o anki geometri kaydedilirse + # sonraki açılış küçük pencereyle başlar. Önceki kayıt korunur. + if not self.__dict__.get("picture_in_picture_enabled", False): + self.settings.setValue("geometry", self.saveGeometry()) if hasattr(self, 'volume_slider'): self.settings.setValue("volume", self.volume_slider.value()) if self.last_dir: diff --git a/app/playlist_panel.py b/app/playlist_panel.py index 6307c30..3362ee7 100644 --- a/app/playlist_panel.py +++ b/app/playlist_panel.py @@ -1023,8 +1023,11 @@ def _add_files(self): self.refresh() def _remove_selected(self): + # Arama satırı gizler ama seçimi bırakmaz; kullanıcının göremediği + # seçili öğe silinmez. rows = sorted({self.playlist_view.row(item) - for item in self.playlist_view.selectedItems()}) + for item in self.playlist_view.selectedItems() + if not item.isHidden()}) if rows: self.player.remove_many_from_playlist(rows) self.refresh() diff --git a/app/single_instance.py b/app/single_instance.py index e9c814a..58eccd3 100644 --- a/app/single_instance.py +++ b/app/single_instance.py @@ -17,7 +17,7 @@ import struct import time -from PyQt6.QtCore import QObject, pyqtSignal +from PyQt6.QtCore import QObject, Qt, pyqtSignal from PyQt6.QtNetwork import QLocalServer, QLocalSocket from app.errors import log @@ -301,7 +301,11 @@ def _hand_over(self, socket, payload): def activate_window(window, payload=""): """Açık pencereyi standart çağrılarla öne getirir ve hedefi yükler.""" if window.isMinimized(): - window.showNormal() + # `showNormal()` büyütülmüş/tam ekran bayrağını da siler; yalnız + # simge durumu kaldırılır, önceki pencere durumu korunur. + window.setWindowState( + window.windowState() & ~Qt.WindowState.WindowMinimized) + window.show() window.raise_() window.activateWindow() if payload: diff --git a/app/subtitle_appearance_dialog.py b/app/subtitle_appearance_dialog.py index 3597887..3a18519 100644 --- a/app/subtitle_appearance_dialog.py +++ b/app/subtitle_appearance_dialog.py @@ -45,6 +45,7 @@ from app.config import SUBTITLE_DEFAULTS, UI_ACCENT from app.i18n import tr, tr_mark, translate_marked +from app.number_text import localize_decimal, percent_text from app.subtitle_style import (BORDER_PRESETS, COLOR_KEYS, DELAY_PRESETS, SCALE_PRESETS, mpv_argb_to_qcolor, normalise_subtitle_numeric, @@ -98,7 +99,7 @@ def _turkish_number(value, decimals): - return f"{value:.{decimals}f}".replace(".", ",") + return localize_decimal(f"{value:.{decimals}f}") def delay_label(value): @@ -734,7 +735,7 @@ def set_color(self, color): state = tr("tamamen saydam") elif alpha < 255: state = (f"{tr('kısmen saydam')} " - f"(%{round(alpha / 255 * 100)} {tr('opak')})") + f"({percent_text(alpha / 255 * 100)} {tr('opak')})") else: state = tr("opak") marked = COLOR_LABELS.get(self.key, "") @@ -871,11 +872,11 @@ def initial(name): # pencere AÇILIRKEN düşüyordu (TypeError/ValueError/OverflowError). # `sub_pos` da diğer sayılar gibi merkezî sınırdan geçer. self.position_slider.setValue(int(round(initial("sub_pos")))) - self.position_value = QLabel(f"%{self.position_slider.value()}") + self.position_value = QLabel(percent_text(self.position_slider.value())) self.position_value.setObjectName("subtitlePositionValue") self.position_value.setMinimumWidth(40) self.position_slider.valueChanged.connect( - lambda value: (self.position_value.setText(f"%{value}"), + lambda value: (self.position_value.setText(percent_text(value)), self._refresh_preview())) position_row.addWidget(self.position_slider, 1) position_row.addWidget(self.position_value, 0) diff --git a/app/subtitle_service.py b/app/subtitle_service.py index 3547103..e2a0847 100644 --- a/app/subtitle_service.py +++ b/app/subtitle_service.py @@ -21,6 +21,8 @@ import tempfile import time +from app.errors import log + SUBTITLE_EXTENSION = ".srt" # `apply()` track_list'in güncellenmesini beklerken kullanılan VARSAYILAN @@ -362,8 +364,9 @@ def _remove_previous(self, mpv, target, keep=None): continue try: remover(sid) - except Exception: - pass + except Exception as exc: + log("Previous subtitle track removal failed: " + f"{type(exc).__name__}", "WARNING") self._sid = None def _add_subtitle(self, mpv, target, language=None, title=None): diff --git a/app/thumbnail_service.py b/app/thumbnail_service.py index 66514aa..89b0136 100644 --- a/app/thumbnail_service.py +++ b/app/thumbnail_service.py @@ -87,11 +87,11 @@ def thumbnail_cache_path(media_path, cache_dir=None): def build_worker_command(media_path, output_path): - """Kaynak çalıştırmada main.py'yi, frozen pakette aynı EXE'yi kullanır.""" + """Kaynak çalıştırmada app/main.py'yi, frozen pakette aynı EXE'yi kullanır.""" args = ["--thumbnail-worker", media_path, output_path] if getattr(sys, "frozen", False): return sys.executable, args - main_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "main.py") + main_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "app/main.py") return sys.executable, [main_path, *args] @@ -112,6 +112,9 @@ def __init__(self, parent=None, cache_dir=None): self._current = None self._process = QProcess(self) self._process.finished.connect(self._finished) + # Başlatılamayan süreç `finished` GÖNDERMEZ; kuyruk takılmasın diye + # başarısız bitiş olarak işlenir. + self._process.errorOccurred.connect(self._process_error) self._timeout = QTimer(self) self._timeout.setSingleShot(True) self._timeout.setInterval(THUMBNAIL_TIMEOUT_MS) @@ -183,10 +186,19 @@ def _start_next(self): return self._current = self._queue.pop(0) media_path, output, _ = self._current - os.makedirs(os.path.dirname(output), exist_ok=True) + try: + os.makedirs(os.path.dirname(output), exist_ok=True) + except OSError: + self._finished(-1, None) + return program, args = build_worker_command(media_path, output) - self._process.start(program, args) self._timeout.start() + self._process.start(program, args) + + def _process_error(self, error): + if (error == QProcess.ProcessError.FailedToStart + and self._current is not None): + self._finished(-1, None) def _finished(self, exit_code, _exit_status): self._timeout.stop() diff --git a/app/title_bar.py b/app/title_bar.py index 9b04b75..4033cf5 100644 --- a/app/title_bar.py +++ b/app/title_bar.py @@ -16,6 +16,7 @@ from app.config import UI_ACCENT, UI_FONT_FAMILY from app.ui_icons import make_media_icon from app.i18n import tr +from app.number_text import percent_text TITLE_BAR_HEIGHT = 40 # Baslik cubugu yuksekligini BUYUTMEYEN logo olcusu. @@ -277,7 +278,7 @@ def _build_transparency_control(self): def _transparency_value_changed(self, value): value = int(value) - self.transparency_value_label.setText(f"%{value}") + self.transparency_value_label.setText(percent_text(value)) self.player.set_window_opacity_percent(value) def show_transparency_control(self): @@ -289,7 +290,7 @@ def show_transparency_control(self): self.transparency_slider.blockSignals(True) self.transparency_slider.setValue(value) self.transparency_slider.blockSignals(False) - self.transparency_value_label.setText(f"%{value}") + self.transparency_value_label.setText(percent_text(value)) popup.adjustSize() origin_global = self.transparency_button.mapToGlobal( QPoint(self.transparency_button.width() - popup.width(), @@ -478,7 +479,7 @@ def update_maximize_state(self): def update_window_mode_state(self): """Şeffaflık/PiP erişilebilir adlarını gerçek durumla eşitler.""" percent = int(getattr(self.player, "window_opacity_percent", 100)) - transparency_label = f"{tr('Şeffaflık')}: %{percent}" + transparency_label = f"{tr('Şeffaflık')}: {percent_text(percent)}" self.transparency_button.setAccessibleName(transparency_label) self.transparency_button.setToolTip(transparency_label) diff --git a/app/track_labels.py b/app/track_labels.py index 4ca265d..34437b7 100644 --- a/app/track_labels.py +++ b/app/track_labels.py @@ -31,6 +31,7 @@ # SAF KATMAN: `app.i18n` DEĞİL `app.translate` (Qt'yi import anında # yüklemez; bkz. o modülün gerekçesi). from app.translate import tr, tr_mark, translate_marked +from app.number_text import localize_decimal # --- Dil kodları ----------------------------------------------------- @@ -150,7 +151,7 @@ def sample_rate_label(hertz): khz = value / 1000.0 if abs(khz - round(khz)) < 0.05: return f"{int(round(khz))} kHz" - return f"{khz:.1f}".replace(".", ",") + " kHz" + return localize_decimal(f"{khz:.1f}") + " kHz" def bitrate_label(bits_per_second): diff --git a/app/ui_components.py b/app/ui_components.py index 909f56c..5f236db 100644 --- a/app/ui_components.py +++ b/app/ui_components.py @@ -5,6 +5,7 @@ from PyQt6.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QPushButton, QLabel, QSizePolicy, QSlider, QStyle from PyQt6.QtCore import Qt, QSize, QRectF from PyQt6.QtGui import QColor, QPainter +from app.number_text import percent_text from app.utils import create_colored_icon, format_time from app.config import (UI_ACCENT, cinematic_ui_enabled, DEFAULT_VOLUME, MAX_VOLUME) @@ -244,7 +245,7 @@ def setup_controls(player): # Ses seviyesi etiketi - volume_slider'dan ÖNCE oluşturulmalı ki # setValue yoluyla tetiklenen set_volume etikete erişebilsin - player.volume_label = QLabel(f"%{int(DEFAULT_VOLUME)}") + player.volume_label = QLabel(percent_text(DEFAULT_VOLUME)) player.volume_label.setObjectName("volumeLabel") player.volume_label.setStyleSheet("font-size: 12px; color: #9AA7B3;") # Sabit genişlik/yükseklik: metin boyutu değişince etiket yukarı/aşağı kaymasın diff --git a/app/updater.py b/app/updater.py index b0020d4..77c9e77 100644 --- a/app/updater.py +++ b/app/updater.py @@ -196,10 +196,24 @@ class VerificationError(Exception): # ── Sürüm karşılaştırma ────────────────────────────────────────────────── def _version_parts(value): - numbers = [int(part) for part in re.findall(r"\d+", value or "")] - while len(numbers) < 3: - numbers.append(0) - return tuple(numbers) + # Sürüm çekirdeği ilk noktalı sayı grubudur; sondaki sıfırlar anlam + # taşımaz (`v0.41.0.0` == `v0.41`). `+` sonrası derleme üst verisidir + # ve yok sayılır (semver). Kalan son ek (`-rc1`, `-beta`) ön sürümdür ve + # aynı çekirdeğin kararlısından ESKİDİR; son ekteki sayılar yalnız ön + # sürümleri kendi arasında sıralar. Çekirdek ayrı bir demet olduğu için + # kararlılık işaretçisi dördüncü sürüm sayısıyla karışmaz. + text = (value or "").split("+", 1)[0] + match = re.search(r"\d+(?:\.\d+)*", text) + if match is None: + return ((), 1, ()) + numbers = [int(part) for part in match.group().split(".")] + while numbers and numbers[-1] == 0: + numbers.pop() + suffix = text[match.end():].strip().strip(".") + if not suffix: + return (tuple(numbers), 1, ()) + return (tuple(numbers), 0, + tuple(int(part) for part in re.findall(r"\d+", suffix))) def is_newer_version(latest, current=None): diff --git a/app/user_cleanup_helper.py b/app/user_cleanup_helper.py new file mode 100644 index 0000000..f60cb6d --- /dev/null +++ b/app/user_cleanup_helper.py @@ -0,0 +1,777 @@ +# SPDX-FileCopyrightText: 2026 MLC Player contributors +# SPDX-License-Identifier: GPL-3.0-only +"""User-session half of MLC Player's optional uninstall cleanup.""" + +from __future__ import annotations + +import argparse +import ctypes +from ctypes import wintypes +import hashlib +import json +import os +from pathlib import Path +import re +import shutil +import stat +import subprocess +import sys +import tempfile +import threading +from xml.sax.saxutils import escape + + +ERROR_INSUFFICIENT_BUFFER = 122 +ERROR_NOT_FOUND = 1168 +CRED_TYPE_GENERIC = 1 +TOKEN_QUERY = 0x0008 +_SID_RE = re.compile(r"S-\d+(?:-\d+)+\Z") +_NONCE_RE = re.compile(r"[0-9a-f]{32}\Z") +_THUMBNAIL_RE = re.compile(r"[0-9a-fA-F]{64}\.jpg\Z") +_UPDATE_RE = re.compile(r"MLCPlayer_Setup_v[^\\/]+\.(?:exe|exe\.sig)\Z", re.I) +_IPC_RE = re.compile(r"[0-9a-f]{32}\.(?:request|result)\.json\Z", re.I) +_HELPER_PARTS = ("Programs", "MLC Player", "UninstallCleanup", "v1", + "MLCUserCleanup.exe") +_LEGACY_HELPER_PARTS = ("MLCPlayer", "UninstallCleanup", "v1", + "MLCUserCleanup.exe") + + +class FILETIME(ctypes.Structure): + _fields_ = [("dwLowDateTime", wintypes.DWORD), + ("dwHighDateTime", wintypes.DWORD)] + + +class CREDENTIALW(ctypes.Structure): + _fields_ = [ + ("Flags", wintypes.DWORD), ("Type", wintypes.DWORD), + ("TargetName", wintypes.LPWSTR), ("Comment", wintypes.LPWSTR), + ("LastWritten", FILETIME), ("CredentialBlobSize", wintypes.DWORD), + ("CredentialBlob", ctypes.POINTER(ctypes.c_byte)), + ("Persist", wintypes.DWORD), ("AttributeCount", wintypes.DWORD), + ("Attributes", ctypes.c_void_p), ("TargetAlias", wintypes.LPWSTR), + ("UserName", wintypes.LPWSTR), + ] + + +def _windows_libraries(): + advapi32 = ctypes.WinDLL("advapi32", use_last_error=True) + kernel32 = ctypes.WinDLL("kernel32", use_last_error=True) + advapi32.OpenProcessToken.argtypes = [wintypes.HANDLE, wintypes.DWORD, + ctypes.POINTER(wintypes.HANDLE)] + advapi32.OpenProcessToken.restype = wintypes.BOOL + advapi32.GetTokenInformation.argtypes = [ + wintypes.HANDLE, ctypes.c_int, ctypes.c_void_p, wintypes.DWORD, + ctypes.POINTER(wintypes.DWORD)] + advapi32.GetTokenInformation.restype = wintypes.BOOL + advapi32.ConvertSidToStringSidW.argtypes = [ctypes.c_void_p, + ctypes.POINTER(wintypes.LPWSTR)] + advapi32.ConvertSidToStringSidW.restype = wintypes.BOOL + advapi32.CredEnumerateW.argtypes = [ + wintypes.LPCWSTR, wintypes.DWORD, ctypes.POINTER(wintypes.DWORD), + ctypes.POINTER(ctypes.POINTER(ctypes.POINTER(CREDENTIALW)))] + advapi32.CredEnumerateW.restype = wintypes.BOOL + advapi32.CredDeleteW.argtypes = [wintypes.LPCWSTR, wintypes.DWORD, + wintypes.DWORD] + advapi32.CredDeleteW.restype = wintypes.BOOL + advapi32.CredFree.argtypes = [ctypes.c_void_p] + kernel32.GetCurrentProcess.restype = wintypes.HANDLE + kernel32.CloseHandle.argtypes = [wintypes.HANDLE] + kernel32.LocalFree.argtypes = [ctypes.c_void_p] + return advapi32, kernel32 + + +def current_user_sid(): + """Return the exact SID from this process token; never infer from paths.""" + if os.name != "nt": + return None + advapi32, kernel32 = _windows_libraries() + token = wintypes.HANDLE() + if not advapi32.OpenProcessToken(kernel32.GetCurrentProcess(), TOKEN_QUERY, + ctypes.byref(token)): + return None + try: + size = wintypes.DWORD() + advapi32.GetTokenInformation(token, 1, None, 0, ctypes.byref(size)) + if ctypes.get_last_error() != ERROR_INSUFFICIENT_BUFFER or not size.value: + return None + buffer = ctypes.create_string_buffer(size.value) + if not advapi32.GetTokenInformation(token, 1, buffer, size.value, + ctypes.byref(size)): + return None + sid_pointer = ctypes.c_void_p.from_buffer(buffer).value + text = wintypes.LPWSTR() + if not advapi32.ConvertSidToStringSidW(sid_pointer, ctypes.byref(text)): + return None + try: + value = text.value + return value if value and _SID_RE.fullmatch(value) else None + finally: + kernel32.LocalFree(text) + finally: + kernel32.CloseHandle(token) + + +def task_name_for_sid(sid): + if not isinstance(sid, str) or not _SID_RE.fullmatch(sid): + return None + return "\\MLCPlayer_UninstallCleanup_" + sid + + +def request_matches_current_user(request_sid): + sid = current_user_sid() + return bool(sid and request_sid and sid == request_sid) + + +def helper_location(local_appdata): + # This host's Task Scheduler deterministically returns 0x80070002 for + # executables directly below %LOCALAPPDATA%, while the per-user Programs + # tree launches the exact same helper. Keep the helper durable and scoped + # to this product without relying on the purgeable Temp directory. + return Path(os.path.abspath(str(local_appdata))).joinpath(*_HELPER_PARTS) + + +def legacy_helper_location(local_appdata): + return Path(os.path.abspath(str(local_appdata))).joinpath( + *_LEGACY_HELPER_PARTS) + + +def profile_local_appdata(environ=None): + env = os.environ if environ is None else environ + profile = env.get("USERPROFILE") + if not profile: + return None + profile = Path(os.path.abspath(profile)) + local = safe_owned_path(profile, "AppData", "Local") + return local if local is not None and local.is_dir() else None + + +def ipc_root(local_appdata): + return helper_location(local_appdata).parent / "ipc" + + +def request_file(local_appdata, nonce): + if not isinstance(nonce, str) or not _NONCE_RE.fullmatch(nonce): + return None + return ipc_root(local_appdata) / (nonce + ".request.json") + + +def result_file(local_appdata, nonce): + if not isinstance(nonce, str) or not _NONCE_RE.fullmatch(nonce): + return None + return ipc_root(local_appdata) / (nonce + ".result.json") + + +def _is_reparse(path): + try: + info = os.lstat(path) + except FileNotFoundError: + return False + return stat.S_ISLNK(info.st_mode) or bool( + getattr(info, "st_file_attributes", 0) & + getattr(stat, "FILE_ATTRIBUTE_REPARSE_POINT", 0)) + + +def safe_owned_path(root, *parts): + """Reject traversal and every existing reparse component, including leaf.""" + current = Path(root) + try: + if _is_reparse(current): + return None + for part in parts: + if part in ("", ".", "..") or Path(part).name != part: + return None + current = current / part + if _is_reparse(current): + return None + return current + except OSError: + return None + + +def _same_path(left, right): + return os.path.normcase(os.path.abspath(str(left))) == os.path.normcase( + os.path.abspath(str(right))) + + +def _file_sha256(path): + try: + return hashlib.sha256(Path(path).read_bytes()).hexdigest() + except OSError: + return None + + +def _ensure_safe_directories(root, parts): + """Create one component at a time and recheck every ancestor.""" + root = Path(root) + if not root.is_dir() or _is_reparse(root): + return None + current_parts = [] + for part in parts: + current_parts.append(part) + current = safe_owned_path(root, *current_parts) + if current is None: + return None + try: + current.mkdir(exist_ok=True) + except OSError: + return None + current = safe_owned_path(root, *current_parts) + if current is None or not current.is_dir(): + return None + return current + + +def _safe_profile_owned_path(local_appdata, *parts): + """Validate Profile/AppData/Local plus the requested product components.""" + local = Path(os.path.abspath(str(local_appdata))) + appdata = local.parent + profile = appdata.parent + if local.name.casefold() != "local" or \ + appdata.name.casefold() != "appdata": + return None + return safe_owned_path(profile, appdata.name, local.name, *parts) + + +def read_owned_request(path): + try: + target = Path(path) + data = json.loads(target.read_text(encoding="utf-8")) + except (OSError, ValueError): + return None + if set(data) != {"nonce", "sid", "delete_settings", "delete_cache"}: + return None + if target.name != str(data.get("nonce")) + ".request.json": + return None + if not _NONCE_RE.fullmatch(str(data.get("nonce", ""))): + return None + if not request_matches_current_user(data.get("sid")): + return None + if not isinstance(data.get("delete_settings"), bool): + return None + if not isinstance(data.get("delete_cache"), bool): + return None + return data + + +def write_result_atomically(path, nonce, succeeded, failures=()): + target = Path(path) + failures = tuple(failures) + if target.name != str(nonce) + ".result.json" or not _NONCE_RE.fullmatch( + str(nonce)) or not isinstance(succeeded, bool): + return False + target.parent.mkdir(parents=True, exist_ok=True) + payload = {"nonce": nonce, "succeeded": succeeded, + "failure_count": len(failures)} + handle = None + try: + handle = tempfile.NamedTemporaryFile( + "w", encoding="utf-8", delete=False, dir=target.parent, + suffix=".tmp") + json.dump(payload, handle, separators=(",", ":"), sort_keys=True) + handle.flush() + os.fsync(handle.fileno()) + handle.close() + os.replace(handle.name, target) + return True + except OSError: + if handle is not None: + try: + handle.close() + os.unlink(handle.name) + except OSError: + pass + return False + + +def task_xml(sid, helper_executable): + task = task_name_for_sid(sid) + helper = Path(helper_executable) + if task is None or helper.name != "MLCUserCleanup.exe" or not helper.is_absolute(): + return None + command = escape(str(helper)) + user_sid = escape(sid) + return f''' + + MLC Player optional uninstall cleanup + {user_sid}InteractiveTokenLeastPrivilege + IgnoreNewfalsefalsetruefalsefalsefalsefalsetruetruetruefalsefalsePT5M7 + {command}--process-request +''' + + +def create_task_command(sid, helper_executable, xml_path=None): + if task_xml(sid, helper_executable) is None: + return None + xml_path = Path(xml_path) if xml_path else Path(helper_executable).with_suffix(".task.xml") + return ["schtasks.exe", "/Create", "/TN", task_name_for_sid(sid), + "/XML", str(xml_path), "/F"] + + +def run_task_command(sid): + task = task_name_for_sid(sid) + return None if task is None else ["schtasks.exe", "/Run", "/TN", task] + + +def _remove_file(path, failures): + if path is None: + failures.append("unsafe-path") + return + try: + if not path.exists(): + return + if _is_reparse(path) or not path.is_file(): + failures.append(path.name) + return + path.unlink() + except OSError: + failures.append(path.name) + + +def _remove_allowed_directory(path, allowed, failures): + if path is None: + failures.append("unsafe-directory") + return + try: + if not path.exists(): + return + if _is_reparse(path) or not path.is_dir(): + failures.append(path.name) + return + for entry in path.iterdir(): + if _is_reparse(entry) or not entry.is_file() or not allowed(entry.name): + failures.append(entry.name) + continue + _remove_file(entry, failures) + try: + path.rmdir() + except OSError: + failures.append(path.name) + except OSError: + failures.append(path.name) + + +def _remove_empty(path, failures): + if path is None: + return + try: + if not path.exists(): + return + if _is_reparse(path) or not path.is_dir(): + failures.append(path.name) + return + path.rmdir() + except OSError: + failures.append(path.name) + + +def _delete_registry_tree(winreg_module, root, subkey): + """Delete one owned registry tree and prove that it is gone.""" + try: + with winreg_module.OpenKey( + root, subkey, 0, + winreg_module.KEY_READ | winreg_module.KEY_WRITE) as key: + children = [] + index = 0 + while True: + try: + children.append(winreg_module.EnumKey(key, index)) + index += 1 + except OSError: + break + for child in children: + if not _delete_registry_tree( + winreg_module, root, subkey + "\\" + child): + return False + winreg_module.DeleteKey(root, subkey) + except FileNotFoundError: + return True + except OSError: + return False + + try: + with winreg_module.OpenKey(root, subkey): + return False + except FileNotFoundError: + return True + except OSError: + return False + + +def _delete_settings_registry(failures): + if os.name != "nt": + return + try: + import winreg + deleted = _delete_registry_tree( + winreg, winreg.HKEY_CURRENT_USER, "Software\\MLCPlayer") + except (ImportError, OSError, ValueError): + deleted = False + if not deleted: + failures.append("settings-registry") + + +def _delete_credentials(failures): + if os.name != "nt": + return + try: + library, _ = _windows_libraries() + for prefix in ("MLCPlayer/OpenSubtitles/", "MLCPlayer/OpenSubtitles.ApiKey/"): + count = wintypes.DWORD() + items = ctypes.POINTER(ctypes.POINTER(CREDENTIALW))() + if not library.CredEnumerateW(prefix + "*", 0, ctypes.byref(count), + ctypes.byref(items)): + if ctypes.get_last_error() != ERROR_NOT_FOUND: + failures.append("credentials") + continue + try: + targets = [items[index].contents.TargetName + for index in range(count.value)] + finally: + library.CredFree(items) + for target in targets: + if not target.startswith(prefix): + failures.append("credential-namespace") + elif not library.CredDeleteW(target, CRED_TYPE_GENERIC, 0) and \ + ctypes.get_last_error() != ERROR_NOT_FOUND: + failures.append("credential-delete") + except (OSError, ValueError, ctypes.ArgumentError): + failures.append("credentials") + + +def cleanup_current_user(delete_settings, delete_cache, environ=None): + env = dict(os.environ if environ is None else environ) + failures = [] + roaming = Path(env["APPDATA"]) if env.get("APPDATA") else None + local = Path(env["LOCALAPPDATA"]) if env.get("LOCALAPPDATA") else None + if delete_settings: + _delete_settings_registry(failures) + _delete_credentials(failures) + if roaming is None: + failures.append("appdata-missing") + else: + _remove_file(safe_owned_path(roaming, "MLCPlayer", "MLCPlayer.ini"), failures) + if delete_cache: + if roaming is None: + failures.append("appdata-missing") + else: + logs = safe_owned_path(roaming, "MLCPlayer", "logs") + _remove_allowed_directory( + logs, lambda name: name in {"uygulama.log", "uygulama.log.1"}, failures) + if local is None: + failures.append("localappdata-missing") + else: + thumbs = safe_owned_path(local, "MLCPlayer", "cache", "thumbnails") + _remove_allowed_directory(thumbs, lambda name: bool(_THUMBNAIL_RE.fullmatch(name)), failures) + try: + if not _is_reparse(local): + for entry in local.iterdir(): + if (entry.name.startswith("MLCPlayerUpdate_") and entry.is_dir() and + not _is_reparse(entry)): + _remove_allowed_directory(entry, lambda name: bool(_UPDATE_RE.fullmatch(name)), failures) + except OSError: + failures.append("update-scan") + if roaming is not None and (delete_settings or delete_cache): + _remove_empty(safe_owned_path(roaming, "MLCPlayer"), failures) + if local is not None and delete_cache: + _remove_empty(safe_owned_path(local, "MLCPlayer", "cache"), failures) + return {"succeeded": not failures, "failures": tuple(failures)} + + +def process_requests(local_appdata=None): + local = (Path(local_appdata) if local_appdata is not None else + profile_local_appdata()) + root = (None if local is None else _safe_profile_owned_path( + local, *_HELPER_PARTS[:-1], "ipc")) + sid = current_user_sid() + if root is None or not sid or not root.is_dir(): + return 2 + candidates = [] + try: + for path in root.glob("*.request.json"): + request = read_owned_request(path) + if request is not None: + candidates.append((path, request)) + except OSError: + return 2 + if not candidates: + return 3 + # Requests live in this user's private profile. Prefer the newest valid + # request and discard older valid nonce files left by an interrupted run. + candidates.sort(key=lambda item: item[0].stat().st_mtime_ns, reverse=True) + path, request = candidates[0] + for stale_path, _ in candidates[1:]: + try: + stale_path.unlink() + except OSError: + return 3 + outcome = cleanup_current_user(request["delete_settings"], request["delete_cache"]) + target = result_file(local, request["nonce"]) + if target is None or not write_result_atomically( + target, request["nonce"], outcome["succeeded"], outcome["failures"]): + return 4 + try: + path.unlink() + except OSError: + return 5 + return 0 if outcome["succeeded"] else 6 + + +def _read_install_id(): + if os.name != "nt": + return None + try: + import winreg + with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, + r"Software\MLCPlayer\UninstallCleanup") as key: + value, kind = winreg.QueryValueEx(key, "InstallId") + return value if kind == winreg.REG_SZ and _NONCE_RE.fullmatch(value) else None + except OSError: + return None + + +def _write_user_registration(install_id, sid, task_name, target): + import winreg + digest = hashlib.sha256(target.read_bytes()).hexdigest() + with winreg.CreateKeyEx(winreg.HKEY_CURRENT_USER, + r"Software\MLCPlayer\UninstallCleanup", 0, + winreg.KEY_SET_VALUE) as key: + for name, value in (("InstallId", install_id), ("UserSid", sid), + ("TaskName", task_name), ("HelperPath", str(target)), + ("HelperSha256", digest)): + winreg.SetValueEx(key, name, 0, winreg.REG_SZ, value) + + +def _read_user_registration(): + import winreg + with winreg.OpenKey(winreg.HKEY_CURRENT_USER, + r"Software\MLCPlayer\UninstallCleanup") as key: + return { + "install_id": winreg.QueryValueEx(key, "InstallId")[0], + "sid": winreg.QueryValueEx(key, "UserSid")[0], + "task_name": winreg.QueryValueEx(key, "TaskName")[0], + "helper_path": Path(winreg.QueryValueEx(key, "HelperPath")[0]), + "helper_hash": winreg.QueryValueEx(key, "HelperSha256")[0], + } + + +def _registration_is_current(registration, install_id, sid, expected_path, + source_hash): + if not registration or not install_id or not sid or not source_hash: + return False + registered_path = registration.get("helper_path") + registered_hash = registration.get("helper_hash") + return bool( + registration.get("install_id") == install_id and + registration.get("sid") == sid and + registration.get("task_name") == task_name_for_sid(sid) and + registered_path is not None and + _same_path(registered_path, expected_path) and + registered_hash == source_hash and + _file_sha256(registered_path) == source_hash + ) + + +def _remove_legacy_helper(local, registration, install_id, sid): + """Remove only the former exact helper layout after safe reprovisioning.""" + if not registration or registration.get("install_id") != install_id or \ + registration.get("sid") != sid or \ + registration.get("task_name") != task_name_for_sid(sid): + return True + registered_path = registration.get("helper_path") + registered_hash = registration.get("helper_hash") + expected = legacy_helper_location(local) + if registered_path is None or not _same_path(registered_path, expected): + return True + safe_helper = _safe_profile_owned_path(local, *_LEGACY_HELPER_PARTS) + if safe_helper is None or not isinstance(registered_hash, str) or \ + not re.fullmatch(r"[0-9a-fA-F]{64}", registered_hash) or \ + _file_sha256(safe_helper) != registered_hash.lower(): + return False + failures = [] + legacy_ipc = _safe_profile_owned_path( + local, *_LEGACY_HELPER_PARTS[:-1], "ipc") + if legacy_ipc is None: + return False + _remove_allowed_directory( + legacy_ipc, lambda name: bool(_IPC_RE.fullmatch(name)), failures) + _remove_file(safe_helper, failures) + for parts in ( + _LEGACY_HELPER_PARTS[:-1], + _LEGACY_HELPER_PARTS[:-2], + _LEGACY_HELPER_PARTS[:-3]): + _remove_empty(_safe_profile_owned_path(local, *parts), failures) + return not failures + + +ERROR_ALREADY_EXISTS = 183 +PROVISION_BUSY = 7 + + +def _acquire_provision_mutex(sid): + """Return an owned named-mutex handle, or None while another provision runs. + + The OS releases the mutex when its process dies, so a crashed provision + never leaves a permanent lock behind (unlike a lock file). + """ + kernel32 = ctypes.WinDLL("kernel32", use_last_error=True) + kernel32.CreateMutexW.argtypes = [ctypes.c_void_p, wintypes.BOOL, + wintypes.LPCWSTR] + kernel32.CreateMutexW.restype = wintypes.HANDLE + kernel32.CloseHandle.argtypes = [wintypes.HANDLE] + suffix = hashlib.sha256(sid.encode("utf-8")).hexdigest()[:32] + handle = kernel32.CreateMutexW( + None, False, f"Local\\MLCPlayerUserCleanupProvision-{suffix}") + if not handle: + return None + if ctypes.get_last_error() == ERROR_ALREADY_EXISTS: + kernel32.CloseHandle(handle) + return None + return handle + + +def _release_provision_mutex(handle): + kernel32 = ctypes.WinDLL("kernel32", use_last_error=True) + kernel32.CloseHandle.argtypes = [wintypes.HANDLE] + kernel32.CloseHandle(handle) + + +def provision(): + sid = current_user_sid() + local = profile_local_appdata() + install_id = _read_install_id() + if not sid or local is None or not install_id or os.name != "nt": + return 2 + lock = _acquire_provision_mutex(sid) + if not lock: + # Another provision for this user is already copying the helper and + # creating the task; competing here deletes its temporary file. + return PROVISION_BUSY + try: + return _provision_locked(sid, local, install_id) + finally: + _release_provision_mutex(lock) + + +def _provision_locked(sid, local, install_id): + source = Path(sys.executable if getattr(sys, "frozen", False) else __file__).resolve() + source_hash = _file_sha256(source) + if not source_hash: + return 3 + try: + previous_registration = _read_user_registration() + except (OSError, ValueError): + previous_registration = None + profile = local.parent.parent + parent = _ensure_safe_directories( + profile, (local.parent.name, local.name, *_HELPER_PARTS[:-1])) + target = _safe_profile_owned_path(local, *_HELPER_PARTS) + if parent is None or target is None: + return 3 + if source != target: + temporary = target.with_name(target.name + ".new") + try: + if temporary.exists() or _is_reparse(temporary): + return 3 + shutil.copy2(source, temporary) + if _safe_profile_owned_path(local, *_HELPER_PARTS[:-1]) is None: + return 3 + os.replace(temporary, target) + finally: + try: + temporary.unlink() + except OSError: + pass + if _safe_profile_owned_path(local, *_HELPER_PARTS) is None or \ + _file_sha256(target) != source_hash: + return 3 + ipc = _ensure_safe_directories( + profile, (local.parent.name, local.name, *_HELPER_PARTS[:-1], "ipc")) + if ipc is None: + return 3 + xml_path = target.with_suffix(".task.xml") + xml_path.write_text(task_xml(sid, target), encoding="utf-16") + try: + completed = subprocess.run(create_task_command(sid, target, xml_path), + check=False, capture_output=True, timeout=30) + finally: + try: + xml_path.unlink() + except OSError: + pass + if completed.returncode != 0: + return 4 + if not _remove_legacy_helper( + local, previous_registration, install_id, sid): + return 5 + try: + _write_user_registration(install_id, sid, task_name_for_sid(sid), target) + except OSError: + return 6 + return 0 + + +def ensure_user_cleanup_task(): + """Provision from an installed player without showing another window.""" + if os.name != "nt" or not getattr(sys, "frozen", False): + return False + executable = Path(sys.executable).resolve().with_name("MLCUserCleanup.exe") + if not executable.is_file(): + return False + install_id = _read_install_id() + sid = current_user_sid() + local = profile_local_appdata() + source_hash = _file_sha256(executable) + try: + registration = _read_user_registration() + if local is not None and _registration_is_current( + registration, install_id, sid, helper_location(local), + source_hash): + return True + except (OSError, ValueError): + pass + flags = getattr(subprocess, "CREATE_NO_WINDOW", 0) + try: + completed = subprocess.run([str(executable), "--provision"], check=False, + timeout=30, creationflags=flags) + return completed.returncode == 0 + except (OSError, subprocess.SubprocessError): + return False + + +def start_user_cleanup_registration(ensure=None, report=None): + """Run `ensure_user_cleanup_task` without blocking the GUI thread. + + Provisioning may wait up to 30 s for its child process. The result is + only diagnostic, so startup must not wait for it. Exceptions are + reported as a safe type name because a worker thread never reaches + `sys.excepthook`. + """ + ensure = ensure_user_cleanup_task if ensure is None else ensure + + def _run(): + try: + result = ensure() + except Exception as exc: + result = f"error:{type(exc).__name__}" + if report is not None: + report(result) + + worker = threading.Thread(target=_run, name="MLCUserCleanupRegistration", + daemon=True) + worker.start() + return worker + + +def main(argv=None): + parser = argparse.ArgumentParser(add_help=False) + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument("--process-request", action="store_true") + group.add_argument("--provision", action="store_true") + args = parser.parse_args(argv) + if args.process_request: + return process_requests() + return provision() + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/app/user_cleanup_main.py b/app/user_cleanup_main.py new file mode 100644 index 0000000..47b82fd --- /dev/null +++ b/app/user_cleanup_main.py @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: 2026 MLC Player contributors +# SPDX-License-Identifier: GPL-3.0-only +from app.user_cleanup_helper import main + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/app/video_frame.py b/app/video_frame.py index 1051166..8223f30 100644 --- a/app/video_frame.py +++ b/app/video_frame.py @@ -15,17 +15,14 @@ from app.ui_icons import make_media_icon from app.playlist_panel import PlaylistPanel from app.utils import format_time -from app.config import (APP_STYLE, MAX_VOLUME, UI_ACCENT, UI_ACCENT_HOVER, - cinematic_ui_enabled) +from app.config import (APP_STYLE, MAX_VOLUME, PLAYBACK_SPEEDS, UI_ACCENT, + UI_ACCENT_HOVER, cinematic_ui_enabled) from app import track_labels from app.errors import safe_console from app.menu_actions import populate_audio_device_menu, populate_recent_menu from app.i18n import tr, tr_mark, translate_marked from app.empty_state import EmptyStateOverlay -# Ana menüyle AYNI hız seçenekleri. -PLAYBACK_SPEEDS = (0.5, 0.75, 1.0, 1.25, 1.5, 2.0) - # Video sahnesi üzerinde fare tekerleği: standart bir kademe 120 birimdir # ve ses adımı ses çubuğunun kendi adımıyla (`VolumeSlider.wheelEvent`) aynıdır. WHEEL_ANGLE_PER_STEP = 120 @@ -2167,7 +2164,7 @@ def release_overlay_surfaces(self): hatasının kök nedeninin bu yüzeyler olduğu KANITLANMADI; ölçülen tek kesin tetikleyici Qt + libmpv + `audio-device-list` okumasının ardından gelen DOĞAL Python finalizasyonudur ve ürün (bkz. - `main.py`) o faza hiç girmez. + `app/main.py`) o faza hiç girmez. Yalnız yüzen yüzeyler bırakılır; mpv `wid` yüzeyi (bu widget) ve gömülü playlist paneli DOKUNULMAZ. Çağrı idempotenttir ve bırakma diff --git a/docs/CONTINUITY.md b/docs/CONTINUITY.md index ef1552e..2fbda08 100644 --- a/docs/CONTINUITY.md +++ b/docs/CONTINUITY.md @@ -1,29 +1,23 @@ # MLC Player güncel devam noktası -Bu dosya projenin tek canlı devir noktasıdır. Tarihsel continuity kronolojisi `CONTINUITY_HISTORY.md`, makinece doğrulanmış olaylar `VERIFICATION_LEDGER.json`, diğer tarihsel anlatı `PROJECT_STATUS.md`, -`ROADMAP.md` ve `ENGINEERING_AUDIT.md` içindedir. +Bu dosya projenin tek canlı devir noktasıdır. Belge haritası `docs/README.md`, +makinece doğrulanmış olaylar `docs/VERIFICATION_LEDGER.json`, tarihsel +continuity kronolojisi `docs/history/CONTINUITY_HISTORY.md` içindedir. Diğer +tarihsel anlatı `docs/history/` altındadır ve güncel karar kaynağı değildir. -- Güncelleme: 8 Eylül 2026 -- Kayıt hazırlanırken doğrulanan HEAD: `95246d19ebac75e651e4970f5ebbc7473cc0f418` -- Güncel HEAD/origin farkı her oturumda `git rev-list --left-right --count` ile ölçülür; bu belge kendi commit hash'ini tahmin etmez. -- Dal: `codex/v041-release-candidate` (bu devirde kirli ağaç korundu; yerel origin görev ref'iyle eş, origin/master ref'inden 1 commit ileride; bu tur fetch yapılmadı) -- Son kanıt: `EV-20260908-035` -- Yayın kararı: **v0.40 canlı/latest; 87 varlık eş, public ana/add-on indirme hashleri ve Ed25519 imzaları geçti.** +- Güncelleme: 3 Ekim 2026 +- Kayıt hazırlanırken doğrulanan HEAD: `33b19bcc4c28ba3d3a3dbf9c991f1d434ef520db` +- Güncel HEAD/origin farkı her oturumda `git rev-list --left-right --count` + ile ölçülür; bu belge kendi commit hash'ini tahmin etmez. +- Dal: `codex/v041-build-evidence`; `EV-20261003-001` değişiklikleri iki + commit'tir: ürün düzeltmeleri `4964ddc`, ardından klasör/belge düzeni. +- Son kanıt: `EV-20261003-002` (PR #75 ilk hosted koşum: tek test-yarışı + hatası, yalnız test düzeltmesi; değişiklik kanıtı `EV-20261003-001`) +- Yayın kararı: **v0.40 canlı/latest; 87 varlık eş, public ana/add-on indirme + hashleri ve Ed25519 imzaları geçti.** v0.41 henüz yayımlanmadı. ## Canlı ürün ve yayın durumu -- Belge/yönetişim paketi ve semantik sahiplik düzeltmesi PR #48 üzerinden - iki ebeveynli exact `5a94d4e` merge commit'iyle master'a alındı. Yerel ve - uzak master, PR #49 kayıt merge'i sonrasında exact `b7e7cdd` üzerinde temiz, - `0/0` ve aynıdır; iki görev dalı da korundu (`EV-20260826-006`). -- PR #48'in exact head'inde ilk zorunlu hosted run `32899094320`, **4965 - passed / 30 skipped / 1 failed** verdi (`EV-20260826-002`). Tek hata - `PACKAGING_PLAN.md` için stale bütün-belge-tarihsel testiydi; otomatik retry - yapılmadı. -- Semantik sahiplik düzeltmesi `EV-20260826-003` ile dar grupta **13 passed** - ve bağımsız karşıt incelemede bloklayıcı bulgusuzdu. Repaired exact head - `8558b73` üzerindeki yeni run `32934932893`, **4966 passed / 30 skipped / - 0 failed** ve `LEDGER_APPEND_ONLY_OK` verdi; PR merge öncesi MERGEABLE ve - required `test=SUCCESS` olarak geri okundu (`EV-20260826-005`). + - `master` için PR ve GitHub Actions `test` kapısı aktiftir. GitHub approving review sayısı `0` olsa da bağımsız çift-süzgeç süreci uygulanır; force-push, protection bypass ve doğrudan master değişikliği yapılmaz. @@ -31,21 +25,38 @@ Bu dosya projenin tek canlı devir noktasıdır. Tarihsel continuity kronolojisi `e702dd8c` exact-master dispatch'te **5064 passed / 30 skipped** verdi (`EV-20260828-019`). Annotated `v0.40` aynı commit'e peel eder; prepublish 87/87, draft ad/boyut/SHA eşliği ve public/latest readback geçti. Ürünün - updater yolu exact ABBB ana paketi seçti; public ana/add-on dosyaları yeniden - indirilip accepted hash ve Ed25519 imzalarıyla eşleşti (`EV-20260828-020`). + updater yolu exact ABBB ana paketi seçti; public ana/add-on dosyaları + yeniden indirilip accepted hash ve Ed25519 imzalarıyla eşleşti + (`EV-20260828-020`). - Kurulu v0.37 veya başka eski artifact için alınan sonuçlar v0.39'a ya da gelecekteki build'e taşınmaz. +- Son kurulu v0.41 adayı `447E2155...FA48` (`EV-20260910-010`) kaynakla artık + birebir değildir: `EV-20260924-001`–`004` ve `EV-20261003-001` + değişiklikleri hiçbir paketin içinde değildir. ## Kalite kabul özeti -- Windows P0 matrisi: `WIN-P0-01`–`WIN-P0-06` PASSED; tam kabul sözleşmesi için `P0-07`, `P0-08` NOT_RUN. Dar playlist taşıma logu 4/4 PASS olarak geri okundu; IPC devri başarılı raporlandı ancak ham koşum kanıtı bu tur bulunamadı (`EV-20260905-004`–`005`). -- `WIN-P0-06` exact `3451aef` kaynak ağacında manuel Explorer video+SRT bırakmayla doğru oynatma, ilerleyen süre ve görünür/doğru bağlı altyazı verdi. İlk kapanış gecikmesi ayrı onaylı tek dış-SRT native ölçümünde yeniden oluşmadı: apply/track/watcher doğrulandı, `stop→terminate` kapanışı yaklaşık 0,08 sn, exit 0, stderr boş, süreç sızıntısı 0 ve test sözleşmesi **321 passed / 1 skipped** (`EV-20260829-011`). -- Exact `04197ef` tabanındaki uncommitted playlist UX paketi ana-thread senkron MPV tarama/okumalarını gözlemci snapshot'ına taşıdı; yalnız gerçek 14 px tutamaç yatay cursor sahibi, tek/çoklu seçim belirgin, tek tık seçim, çift tık oynatma ve toplu kaldırma atomiktir. Son birleşik etki paketi **606 passed**, ek sınırlar **54 passed**, final geniş paket **243 passed** verdi; kullanıcı final canonical kaynak koşumunu “Oldu” diye kabul etti (`EV-20260829-013`). Aynı working tree'de kullanıcı B yerleşimini seçti: görünür sağ araç grubunda tek tekrar düğmesi kapalı→liste→tek dosya döngüsüne, ayrı karışık düğmesi görünür sırayı bozmayan dahili sıraya bağlandı; doğal bitiş sıradaki öğeye ilerliyor, son öğede kapalı mod başa sarıp duruyor ve liste tekrarı başa dönüyor. Kırmızı 7/7 sınırdan sonra hedef **11 passed**, inventory dahil geniş etki paketi **323 passed** verdi; 400 px dar pencere taşmadan geçti (`EV-20260830-001`). İlk canonical kullanıcı kabulü, native kapatma değeri Python `False` iken ürünün yalnız `"no"` beklemesi nedeniyle dokuz `loop write was ignored` hatasıyla **FAILED** (`EV-20260830-002`). Canonical OSD readback + rollback düzeltmesi ve 40×40 hit alanlarını koruyan 10→4 px sağ-grup aralığı iki kırmızı sınırdan sonra geniş pakette **324 passed** verdi; yeni kaynak henüz fiziksel yeniden açılmadı (`EV-20260830-003`). Formal `WIN-P0-07` tam matris sırası yeniden kaydedilmediği için `NOT_RUN` kalır. -- Aynı uncommitted UX ağacındaki görsel bütünlük paketi ürünün turuncu vurgu, hover/pressed renkleri ve Segoe UI Variable Text→Segoe UI font zincirini `app.config` altında tek kaynağa bağladı; eski mavi seçim/slider vurguları ve ikinci turuncu kaldırıldı. Başlık, playlist, başlangıç, Medya Bilgisi, kısa bilgi, overlay, altyazı ve güncelleyici yüzeyleri ortak kimliği kullanıyor; klavye focus durumları nötr ve görünür, daha önce reddedilen ikincil Klasör düğmesinde turuncu focus geri getirilmedi. Beş kırmızı sözleşme sonrası ana yüzey **192 passed**, ikinci etki halkası **561 passed**, kalite/çeviri **51 passed** ve tam deterministik paket **5210 passed / 11 skipped** verdi (`EV-20260830-004`). Taze Windows insan görsel kabulü ve bağımsız karşıt agent GO'su henüz yoktur. -- Aynı uncommitted UX ağacındaki görsel bütünlük paketi ürünün turuncu vurgu, hover/pressed renkleri ve Segoe UI Variable Text→Segoe UI font zincirini `app.config` altında tek kaynağa bağladı; eski mavi seçim/slider vurguları ve ikinci turuncu kaldırıldı. Başlık, playlist, başlangıç, Medya Bilgisi, kısa bilgi, overlay, altyazı ve güncelleyici yüzeyleri ortak kimliği kullanıyor; klavye focus durumları nötr ve görünür, daha önce reddedilen ikincil Klasör düğmesinde turuncu focus geri getirilmedi. Beş kırmızı sözleşme sonrası ana yüzey **192 passed**, ikinci etki halkası **561 passed**, kalite/çeviri **51 passed** ve tam deterministik paket **5210 passed / 11 skipped** verdi (`EV-20260830-004`). Sonraki dar düzeltmede başlangıç yüzeyinin her 100 ms'de tekrarlanan `raise_()` z-order/input churn'i visibility geçişine indirildi; Kısayollar penceresi 450×520'ye sıkıştırılıp `Tamam` düğmesi `accept()` ile gerçek Accepted kapanışa bağlandı. Etkilenen aileler **53 + 236 passed**, continuity **10 passed** verdi (`EV-20260830-005`). Taze Windows insan görsel kabulü ve bağımsız karşıt agent GO'su henüz yoktur. -- Aynı uncommitted UX ağacındaki görsel bütünlük paketi ürünün turuncu vurgu, hover/pressed renkleri ve Segoe UI Variable Text→Segoe UI font zincirini `app.config` altında tek kaynağa bağladı; eski mavi seçim/slider vurguları ve ikinci turuncu kaldırıldı. Başlık, playlist, başlangıç, Medya Bilgisi, kısa bilgi, overlay, altyazı ve güncelleyici yüzeyleri ortak kimliği kullanıyor; klavye focus durumları nötr ve görünür, daha önce reddedilen ikincil Klasör düğmesinde turuncu focus geri getirilmedi. Beş kırmızı sözleşme sonrası ana yüzey **192 passed**, ikinci etki halkası **561 passed**, kalite/çeviri **51 passed** ve tam deterministik paket **5210 passed / 11 skipped** verdi (`EV-20260830-004`). Sonraki dar düzeltmede başlangıç yüzeyinin her 100 ms'de tekrarlanan `raise_()` z-order/input churn'i visibility geçişine indirildi; Kısayollar penceresi 450×520'ye sıkıştırılıp `Tamam` düğmesi `accept()` ile gerçek Accepted kapanışa bağlandı. Başarılı dosya/klasör açılışından sonra focus video yüzeyine taşındı; böylece açıcı ikonunun oynatma sırasında seçili kalması giderildi. Etkilenen aileler **53 + 236 + 139 passed**, continuity **10 passed** verdi (`EV-20260830-005`–`006`). Taze Windows insan görsel kabulü ve bağımsız karşıt agent GO'su henüz yoktur. -- Aynı uncommitted UX ağacındaki görsel bütünlük paketi ürünün turuncu vurgu, hover/pressed renkleri ve Segoe UI Variable Text→Segoe UI font zincirini `app.config` altında tek kaynağa bağladı; eski mavi seçim/slider vurguları ve ikinci turuncu kaldırıldı. Başlık, playlist, başlangıç, Medya Bilgisi, kısa bilgi, overlay, altyazı ve güncelleyici yüzeyleri ortak kimliği kullanıyor; klavye focus durumları nötr ve görünür, daha önce reddedilen ikincil Klasör düğmesinde turuncu focus geri getirilmedi. Beş kırmızı sözleşme sonrası ana yüzey **192 passed**, ikinci etki halkası **561 passed**, kalite/çeviri **51 passed** ve tam deterministik paket **5210 passed / 11 skipped** verdi (`EV-20260830-004`). Sonraki dar düzeltmede başlangıç yüzeyinin her 100 ms'de tekrarlanan `raise_()` z-order/input churn'i visibility geçişine indirildi; Kısayollar penceresi 450×520'ye sıkıştırılıp `Tamam` düğmesi `accept()` ile gerçek Accepted kapanışa bağlandı. Başarılı dosya/klasör açılışından sonra focus video yüzeyine taşındı; böylece açıcı ikonunun oynatma sırasında seçili kalması giderildi. Tek komutluk ana pencere kabul runner'ı eklendi: 13 fiziksel grubu kanonik sırada varsayılan 3 temiz döngüyle çalıştırıyor, ilk başarısızlıkta fail-closed duruyor ve tek JSON özeti üretiyor. Etkilenen aileler **53 + 236 + 139 + 141 passed**, continuity **10 passed** verdi (`EV-20260830-005`–`007`). Taze Windows insan görsel kabulü ve bağımsız karşıt agent GO'su henüz yoktur. -- Aynı uncommitted UX ağacındaki görsel bütünlük paketi ürünün turuncu vurgu, hover/pressed renkleri ve Segoe UI Variable Text→Segoe UI font zincirini `app.config` altında tek kaynağa bağladı; eski mavi seçim/slider vurguları ve ikinci turuncu kaldırıldı. Başlık, playlist, başlangıç, Medya Bilgisi, kısa bilgi, overlay, altyazı ve güncelleyici yüzeyleri ortak kimliği kullanıyor; klavye focus durumları nötr ve görünür, daha önce reddedilen ikincil Klasör düğmesinde turuncu focus geri getirilmedi. Beş kırmızı sözleşme sonrası ana yüzey **192 passed**, ikinci etki halkası **561 passed**, kalite/çeviri **51 passed** ve tam deterministik paket **5210 passed / 11 skipped** verdi (`EV-20260830-004`). Sonraki dar düzeltmede başlangıç yüzeyinin her 100 ms'de tekrarlanan `raise_()` z-order/input churn'i visibility geçişine indirildi; Kısayollar penceresi 450×520'ye sıkıştırılıp `Tamam` düğmesi `accept()` ile gerçek Accepted kapanışa bağlandı. Başarılı dosya/klasör açılışından sonra focus video yüzeyine taşındı; böylece açıcı ikonunun oynatma sırasında seçili kalması giderildi. Tek komutluk ana pencere kabul runner'ı eklendi: 13 fiziksel grubu kanonik sırada varsayılan 3 temiz döngüyle çalıştırıyor, ilk başarısızlıkta fail-closed duruyor ve tek JSON özeti üretiyor. I: sürücüsündeki iki video ile ilk gerçek ölçümde buttons **21/21**, window_resize **12/12** geçti; birleşik döngü playlist açıkken `overlayFullscreen` için `entered=False` ile başarısız oldu (`EV-20260830-005`–`008`). Taze Windows insan görsel kabulü tamamlanmadı; bağımsız karşıt agent GO'su tek-agent kısıtı nedeniyle yoktur. -- `WIN-P0-08` deterministik offscreen çok-süreç kapsamı gerçek secondary PID, dosya/URL/aktivasyon devri, exit `0` ve terminal kapanış ölçümüyle genişletildi; dar etki paketi **93 passed** verdi. Gerçek ürün penceresi foreground ve hedef-yükleme sonucu ölçülmediği için fiziksel satır hâlâ `NOT_RUN` (`EV-20260829-002`). Aktif oynatma sırasında kuyruğa eklenen yeni video(lar)la birlikte bırakılan altyazının global pending kuyruğuyla yanlışlıkla mevcut videoya uygulanması kırmızı testte doğrulandı; hedef medya başlatılmadığında pending durum artık korunuyor ve kullanıcı açıkça yönlendiriliyor. Subtitle/playlist/atomiklik/çeviri etki paketi **87 passed** verdi (`EV-20260829-003`). PR #72 ilk hosted run `33266269968`, **5151 passed / 22 skipped / 1 failed** ile stale `app/player.py` mimari hash/satır kapısında durdu; diğer beş modül exact eşti ve envanter düzeltildi (`EV-20260829-005`–`007`). Yeni push koşumu `33266596701` yine **5151/22/1** verdi; tek hata ürün bütçesi yerine runner/UI overhead'ini de kapsayan `elapsed <= 0.8` testiydi. Test sonluluğu artık duvar-saati üst sınırı yerine exact 40 Qt wait çağrısıyla ölçülüyor; hedef 5/5 ve dosya paketi **12 passed** (`EV-20260829-008`–`009`). Hiçbir başarısız run retry edilmedi. + +- Windows P0 matrisi: `WIN-P0-01`–`WIN-P0-06` PASSED; tam kabul sözleşmesi + için `P0-07`, `P0-08` NOT_RUN. Dar playlist taşıma logu 4/4 PASS olarak geri + okundu; IPC devri başarılı raporlandı ancak ham koşum kanıtı bulunamadı + (`EV-20260905-004`–`005`). +- `WIN-P0-06` exact `3451aef` kaynak ağacında manuel Explorer video+SRT + bırakmayla doğru oynatma, ilerleyen süre ve görünür altyazı verdi; tek + dış-SRT native ölçümünde `stop→terminate` yaklaşık 0,08 sn, exit 0, süreç + sızıntısı 0 (`EV-20260829-011`). +- Playlist UX paketi ve B yerleşimi (tek tekrar düğmesi, ayrı karışık düğmesi) + deterministik pakette geçti; ilk kullanıcı kabulü `loop` geri okuma + hatasıyla FAILED oldu, düzeltme **324 passed** verdi ancak fiziksel olarak + yeniden açılmadı (`EV-20260829-013`, `EV-20260830-001`–`003`). Formal + `WIN-P0-07` sırası kaydedilmediği için `NOT_RUN` kalır. +- Görsel bütünlük paketi (tek turuncu vurgu, ortak font zinciri, focus + durumları) ve ana pencere kabul runner'ı eklendi; ilk gerçek runner + ölçümünde buttons **21/21**, window_resize **12/12** geçti, playlist açıkken + `overlayFullscreen` `entered=False` ile başarısız oldu + (`EV-20260830-004`–`008`). Taze Windows insan görsel kabulü yoktur. +- `WIN-P0-08` deterministik çok-süreç kapsamı genişletildi; gerçek pencere + foreground ve hedef yükleme ölçülmediği için fiziksel satır `NOT_RUN` + (`EV-20260829-002`–`009`). - Video biçimi matrisi: SDR ekranda `VF-CORE-01` ve HDR ekranda SDR-on-HDR `VF-CORE-02` exact native ve kontrollü insan ramp kabulüyle PASSED. Kalan 14 biçim satırı ve genel `WIN-P2-01` BLOCKED; bu iki PASS genel HDR/format @@ -55,116 +66,39 @@ Bu dosya projenin tek canlı devir noktasıdır. Tarihsel continuity kronolojisi `MARK_DONE`, `stop -> terminate` ve sıfır süreç sızıntısıyla sınırlıdır. - Hosted CI sonucu native Windows, kurulu paket, kullanıcı gözlemi veya skipped senaryoları PASS yapmaz. +- Kapanmış v0.40 installer/B2 zincirleri, PR #48/#49 meta kayıtları ve + yayın sonrası UX denetimi kelimesi kelimesine `CONTINUITY_HISTORY.md` + içindeki 3 Ekim 2026 arşiv bölümündedir. ## Açık çalışma ve korunan kapılar -- Yayın sonrası oynatma/kontrol UX denetimi exact `59f6980` tabanında regression-first yürütüldü. Playlist/EOF ve görünür durum rollback'lerine ek olarak ses, seek exception, hız, loop ve görüntü ayarı başarısızlıkları; klavye yönlendirmeleri; tekrarlı title-bar, updater, panel ve pencere boyutu tıklamaları sertleştirildi. Yapışık playlist sınırındaki çift resize alanı, PiP/dar pencere kontrol-hit çakışması, mod geçişinde takılı resize imleci ve overlay'in yuttuğu çift/sağ tık kapatıldı. Derin kontrol-yolu taraması ayrıca dosya/URL/M3U seçimi, aktif öğe kaldırma, liste temizleme ve üç dosya-ekleme yolunu native ret halinde atomik geri almaya bağladı; klasik ses çubuğunda yatay ve yüksek çözünürlüklü teker davranışı düzeltildi. EV029 ve EV030 uygulamaları kaynak/test ağacından çıkarıldı; ilk görüntülerin eski `9090caf` checkout'undan geldiği ayrıca kaydedildi. Doğru ağaç yeniden açıldıktan sonraki görüntü klasör, playlist/menü ve sağ pencere ikonlarının tümünde hover aktifken dikey okun kaldığını doğruladı ve EV031 child-event teşhisini çürüttü; o uygulama/test de çıkarıldı. Kök neden, ayrı top-level overlay için gerekli `QApplication` override'ının ana pencere başlığında da açılmasıydı: Qt sözleşmesine göre bu cursor bütün uygulama widget'larını ezdiğinden ikonların kendi el cursor'ı görünemiyordu. Ana pencere ağacı artık yalnız widget cursor'ı kullanıyor; global override yalnız farklı top-level overlay ile sınırlı. Etki paketi **302 passed**, son tam deterministik paket **5160 passed / 11 skipped / 0 failed** verdi (`EV-20260828-033`). Değişiklik sonrası kullanıcı görüntülerinde bütün ikonlar doğru el cursor'ını, yalnız boş gerçek üst kenar dikey resize cursor'ını gösterdi; normal kapanıştan sonra player/launcher child süreçleri sıfırdı (`EV-20260828-034`). Kullanıcı tercihiyle başlık şeridi önce 36 px'e, ardından ikon/düğme ölçüleri korunarak güncel 40 px'e çıkarıldı; 40 px hedef paket **278 passed**, tam paket **5160 passed / 11 skipped / 0 failed** verdi (`EV-20260828-036`) ve kullanıcı güncel görünümü açıkça kabul etti (`EV-20260828-037`). Exact paket local `1abd905` commit'ine temiz readback ile bağlandı (`EV-20260828-038`). PR #69 exact `b244a7f` head'i required hosted `test` koşumunda **5149 passed / 22 skipped**, `LEDGER_APPEND_ONLY_OK` verdi ve iki ebeveynli exact `63b7704` merge commit'iyle protected `master`a alındı; canonical klasör clean `0/0` ile aynı commit'e fast-forward edildi (`EV-20260829-001`). Bu görsel source-tree ve hosted CI kabulüdür; captured exit-marker olmadığı için formal native smoke veya kurulu artifact kabulü değildir. İngilizce 456/456 tamam; 0/456 olan diğer altı katalog derlenmez ve menüde sunulmaz. -- Installer UX kararları `PACKAGING_PLAN.md` içinde tutulur. Üç private bitmap - yönü karşılaştırıldı; kullanıcı **C — Dengeli Hibrit** yönünü açıkça seçti - ve seçim kayıt paketi exact `f432c73` commit'iyle bağlandı - (`EV-20260826-008`). A ve B yalnız karşılaştırma referansıdır. Bitmap'ler - Git'e alınmaz ve seçim gerçek Inno Setup davranışı sayılmaz. -- v0.40/Inno, reentry ve Türkçe Ready düzeltme zinciri PR #53–#55 üzerinden - protected master'a alındı; exact hosted koşumlar yeşil, insan kontrollü - ana/add-on ekran ve payload sınırları kabul edildi. Tam tarih ve ara kusurlar - append-only `EV-20260826-077`–`083` kayıtlarındadır. -- İlk resmî v0.40 artifact zinciri hosted **5058 passed / 30 skipped** ve geçerli - Ed25519 verdi; Authenticode `NotSigned`. Active add-on kapanış ve ürün URL - önkoşulları fail-closed durdu (`EV-20260827-011`–`013`). -- Internet Video fallback ve geçici `QMenu` test ömrü regression-first kapatıldı; - protected PR #64 head'i **5059 passed / 30 skipped / 0 failed** verdi. Ara - hosted başarısızlıklar retry edilmedi; kaynak/test/provenance ayrıntıları - `EV-20260827-014`–`023` içindedir. -- PR #64 build kanıtı ve PR #65 path-contract düzeltmesi protected master'a - exact `ebc628b` olarak ulaştı; başarısız run retry edilmedi - (`EV-20260827-024`–`027`). - Exact-master dispatch **5061 passed / 30 skipped / 0 failed** verdi (`EV-20260827-028`); - EV029 exact ebc build'i ana/add-on `07118348...cf6c5` / `d4a4d799...f9e675` üretti. - Son B2 farklı `B8001DAA...fe2fc` ana artifact ile yürüdü fakat çağdaş source-build - transcript'i yoktu. Provenance preflight'ı shared output'taki iki installer EXE'yi - kaldırdı; imzalar ve kurulu v0.40 kaldı, ölçülen ürün/kullanıcı/kayıt durumu exact - aynı ve süreç 0 (`EV-20260827-038`, imza sınırı düzeltmesi `EV-20260828-001`). Final cycle NOT_RUN; tag/release blokludur. -- Exact `994f0e2` artifact'larıyla yürütülen B2 zincirinde silent/interaktif - kurulumlar, gerçek v0.39 yükseltmeleri, iki kaldırma sırası, altı geçersiz - add-on hedefi, payload/ayar/süreç okumaları geçti. Gerçek Blender URL'si - Player-owned exact `yt-dlp` ile 00:01→00:02 / 10:35 ilerledi; çıkarım Deno - gerektirmedi. Bağımsız birlikte çalıştırılan finite `yt-dlp`/Deno engelleyici - denemesi Restart Manager'da fail-closed durdu; kullanıcı Abort seçti, exit 5, - rollback sonrası 103 kurulu ve iki kullanıcı dosyasında fark sıfır, süreç - sızıntısı sıfırdı (`EV-20260827-030`). Karşıt inceleme bunun installer kusuru - değil güvenli negatif davranış olduğunu doğruladı; `force` eklenmedi. -- EV031'deki o tarihteki B2 sözleşmesi regression-first olarak gerçek ürün URL başarısını - yapay engelleyicilerden ayırdı. Product-success yalnız Player'ın başlattığı - exact `yt-dlp`, PID/ebeveyn/komut ve ilerleyen position/duration ile geçer; - Deno yalnız gerçek çıkarım kullanırsa gerekir. Refusing `yt-dlp` ve Deno - diğer hedefler kapalıyken ayrı tek-engelleyici senaryolarında ölçülür; Ignore - ve force yasak, Abort/rollback/hash/AppData/süreç sınırları fail-closed kalır. - Hedef kırmızıdan sonra belge ailesi **206 passed**, karşıt P0/P1/P2=0 verdi; - ürün ve Inno kaynakları değişmedi (`EV-20260827-031`); bu Deno refusal şartı - daha sonra EV035 ile ölçülen cooperative-Deno kuralıyla değiştirildi. -- Exact kurulu v0.39 `yt-dlp` root+child diğer hedefler kapalı tek engelleyici - olarak sınandı. Restart Manager yalnız iki `yt-dlp` sürecini buldu, kapanışın - tamamlanamadığını kaydetti; kullanıcı Ignore/Retry yerine Abort seçti. Setup - exit 5, kopya/başarı marker'ı yok, rollback sonrası 103 kurulu dosya, iki - kullanıcı dosyası, kayıtlar ve hashler exact aynı, hedef süreç sızıntısı - sıfırdı. İlk evaluator şema-kesişimini yanlış yorumlayarak güvenli BLOCKED - verdi; fiziksel retry yapılmadan ayrı düzeltilmiş verdict **16/16 PASS** oldu - (`EV-20260827-032`). Bu PASS yalnız yt-dlp fail-closed negatif kapısına aittir. -- Restart Manager exact Deno'yu kooperatif kapattı; v0.39→v0.40 add-on upgrade - sızıntısız PASS (`EV-20260827-033`), normal fixture refusal BLOCKED (`EV-20260827-034`). - Sözleşme graceful Deno + exact yt-dlp fail-closed oldu; belge **206 passed**, P0/P1/P2=0 (`EV-20260827-035`). -- Askıya alınmış tek exact v0.40 Player kapanmayınca ana setup kopya öncesi durdu; kullanıcı Retry/Ignore yerine Abort seçti, exit 5 ve rollback başarılıydı. Tüm ölçülen 103+2 dosya ile ayar/uninstall/OpenWith/kısayol yüzeyleri exact korundu; resume 0, normal kapanış, watchdog ateşlenmedi, sızıntı 0; evaluator **23/23 PASS**, karşıt P0/P1=0 (`EV-20260827-036`); app-level semantik/normal maintenance/silent/add-on'a aktarılmaz, belge kapanışı P0/P1/P2=0 (`EV-20260827-037`). -- Ayrı onaylı izole build exact `1dafe1f` üzerinde tek çalıştırmada exit 0 ve `DONE` verdi; fast-fail ana setup `ABBB01C3...DC34` / 55.945.176 bayt, değişmeyen add-on `D4A4D799...F9E675` / 48.909.126 bayt, iki detached Ed25519 geçerli, iki EXE Authenticode `NotSigned` ve süreç sızıntısı 0. Bu yalnız `source_build` PASS'tir; 5–10 saniye fiziksel davranış daha sonra EV011 ile geçti fakat tam B2 hâlâ tamamlanmadı (`EV-20260828-009`). -- Exact B7/D4 normal fiziksel döngüsü gerçek v0.39 yükseltmesi, iki kaldırma sırası, clean install, maintenance, payload/ayar koruması ve final restore ile geçti. Askıya alınmış Player yolu kopya öncesi Abort/exit 5, tam rollback ve sıfır sızıntıyla güvenliydi; fakat Restart Manager **66,715 saniye** bekledi. Kullanıcının 5–10 saniye UX sınırı nedeniyle tam B2 **FAILED** (`EV-20260828-006`). -- Regression-first installer-only düzeltme maintenance/upgrade sırasında süreç-ömürlü `MLCPlayer-Running` mutex'ini `PrepareToInstall` içinde önce denetler ve çalışan Player varsa kopya/RM bekleyişinden önce sekiz dilde hızlı fail-closed durur. `CloseApplications` fallback'i korunur; force/taskkill/ürün kodu yoktur. Dil sözleşmesi ve yanlış aynı-sihirbaz talimatı kapatıldı; dar paket **60 passed** (`EV-20260828-007`). -- Onaylı beş-dosya fast-fail/kayıt paketi exact `315e83a`, EV008 iki-belge evidence-commit kaydı exact `1dafe1f` olarak bağlandı; dosya sınırları ve post-commit dar paket **266 passed** (`EV-20260828-008`). -- Exact ABBB tek fiziksel denemesi setup başlamadan runner izolasyon kapısında **BLOCKED** oldu: başlangıç hedef süreç 0 ve başlatılan Player kimliği doğruydu, fakat üç saniye sonra hedef küme tek değildi. UAC/setup/suspend/kopya yok; Player normal kapandı, force/watchdog/sızıntı 0, 103+2 final durum korundu. Eski runner geçici hedef kimliğini kaydetmediğinden guard gevşetilmedi; prepared fresh runner sonraki ayrı onaylı denemede tam kümeyi kaydedecek (`EV-20260828-010`). -- Ayrı onaylı tek fresh-runner denemesinde exact ABBB setup askıya alınmış tek exact Player ve doğrulanmış mutex karşısında **6,983 saniyede** exit 7 ile hızlı fail-closed durdu; setup içi süre yaklaşık 0,131 saniyeydi. Restart Manager, kopya ve başarı marker'ı yok; 103 kurulu dosya, iki kullanıcı dosyası, ayarlar, kayıtlar, OpenWith ve üç kısayol exact korundu. Suspend/resume 0/0, normal kapanış, force/watchdog yok ve süreç sızıntısı 0; karşıt P0/P1=0 (`EV-20260828-011`). Bu PASS yalnız fast-fail senaryosudur; exact ABBB normal install/upgrade/uninstall B2 zincirinin yerine geçmez. -- Resmî B2 metnindeki eski “Player'ı Restart Manager kapatır” şartı committed ABBB davranışıyla çeliştiği için regression-first hizalandı: yalnız bakım/yükseltmede `MLCPlayer-Running` mutex'i `PrepareToInstall` içinde RM'den önce denetlenir; responsive/asılı Player'da 10 saniye içinde kopya öncesi nonzero fail-closed, mesaj, değişmeyen durum ve sıfır sızıntı zorunludur. İlk kurulum etkilenmez; RM mutex dışındaki kilitler için fallback kalır. İlk hedef test kırmızı, son etki paketi **226 passed**, karşıt P0/P1/P2=0 (`EV-20260828-012`). Ürün kodu veya artifact değişmedi. -- Exact `11bb2b0` ve ABBB/D4 artifact'larıyla silent fiziksel B2 zinciri immutable checkpoint resume ile tamamlandı: gerçek v0.39 seed/yükseltme, iki maintenance, iki kaldırma sırası, iki temiz durum, clean install ve final restore geçti. Final exact 103 dosya, iki uninstall kaydı, OpenWith, üç kısayol, korunmuş iki kullanıcı dosyası ve sıfır hedef süreçle `phase_passed` verdi (`EV-20260828-013`). Bu yalnız silent installed-artifact PASS'tir; `InteractiveMain` ve yeni exact Player product URL NOT_RUN kaldı. -- EV013 paketi exact `8ae5fca` commit'ine bağlandı (`EV-20260828-014`). Exact ABBB/D4 `InteractiveMain` zinciri final 103+2 durum ve sıfır süreç verdi (`EV-20260828-015`); kayıt exact `4c5dbd2` commit'ine bağlandı (`EV-20260828-016`). Son exact Player ürün-URL kapısında Player PID 37340 exact kurulu yt-dlp root 54132 ve child 41648'i aynı URL ile başlattı; Deno gerekmedi, kullanıcı oynatmayı “SORUN YOK” diye kabul etti, normal kapanış sonrası 103+2 durum ve sıfır süreç korundu (`EV-20260828-017`). Sayısal position/duration kullanıcı tarafından yazıya geçirilmedi ve bu sınır açıkça kayıtlıdır; Computer Use kullanılmadı. Exact ABBB/D4 B2 ürün kapıları tamamlandı. -- `WIN-P0-03` ilk native koşumu exact `06cebc4` üzerinde fixture kapısını - geçtikten sonra QMenu sınırında TIMEOUT oldu; exit 1 ve eksik final marker - nedeniyle **FAILED** kaydedildi, seçim/ürün bug'ı iddia edilmedi - (`EV-20260827-005`). Test-only nested-loop watchdog paketi 222 dar test ve - karşıt P0/P1=0 sonrası PR #61 exact head `8b1471d` üzerinde **5058 passed / - 30 skipped / 0 failed** verdi ve exact `18a88b8` olarak merge edildi. Ayrı - onaylı tek retry 35.6 saniyede **5 PASS / 0 FAIL / 0 BLOCKED**, exit 0, tam - marker zinciri, ses 1→2, altyazı 1→2→1, checked read-back, - `stop→terminate`, child/parent cursor restore ve ölçülen süreç=0 ile geçti - (`EV-20260827-009`). `WIN-P0-03` artık **PASSED**. -- C yönü seçim, continuity ve protected-master kayıt zinciri PR #50–#52 ile - merge edildi; exact commit/run/parent ayrıntıları append-only - `EV-20260826-009`–`017` ve `EV-20260826-028` kayıtlarındadır. -- Beş ekranlı C sözleşmesi tek kanonik sahibi `PACKAGING_PLAN.md` içinde - hazırlandı. Güncelleme kutusu mevcut ürün ayarına bağlı olmadığı için - çıkarıldı; Internet Video yalnız ayrı paket bilgisi olarak kaldı. İki private - test görseli gösterildi; gerçek Inno, build ve kurulum kabulü değildir. -- Kullanıcı finish seçeneklerinin gerçekten çalışmasını zorunlu tuttu. - Regression-first kırmızıdan sonra exact EXE, Default Apps ve GitHub hedefi, - optional-action sıra/hata/readback'i sözleşmeye bağlandı (`EV-20260826-018`). - Son çift-süzgeç UAC sahipliği, install/reinstall/upgrade metni, downgrade - engeli ve per-screen test boşluklarını buldu; ara test hataları incelenip - sözleşme/test daraltıldı. Son karşıt incelemede finish testinin belge-geneli - araması da fail-open bulundu; test finish ve fiziksel kabul bloklarına ayrı - bağlandı ve nihai paket `EV-20260826-021` ile doğrulandı. -- Kullanıcı gösterilen iki private C akış görselini hedef kompozisyon ve metin - olarak açıkça kabul etti (`EV-20260826-022`). Bu, compiled Inno pikseli, - handler, build, kurulum veya Windows action/readback kabulü değildir. -- C ekran-sözleşmesinin commit/build/reddedilen ara artifact/restore ve exact - cleanup zinciri append-only `EV-20260826-023`–`074` kayıtlarında korunur; - canlı kararlar yukarıdaki v0.40 kabul özetindedir ve meta-kayıt döngüsü - başlatılmaz. +- Installer UX kararlarının tek kanonik sahibi + `docs/release/PACKAGING_PLAN.md` belgesidir; kullanıcı C — Dengeli Hibrit + yönünü seçti (`EV-20260826-008`). - `SUBTITLE_SEARCH_UI_ENABLED=False` korunur. OpenSubtitles masaüstü dağıtım şartları ve güvenli dosya-çakışma davranışı doğrulanmadan çevrimiçi altyazı arayüzü açılmaz. -- SignPath Foundation başvurusu 2 Eylül 2026'da yeterli kamu görünürlüğü/güven sinyali olmadığı gerekçesiyle onaylanmadı; bu teknik kalite reddi, sertifika veya ücretli plan onayı değildir. Ayrı açık onay olmadan GitHub App kurulmaz, imzalama veya yayın yapılmaz; private iletişim bilgisi yayımlanmaz. +- SignPath Foundation başvurusu 2 Eylül 2026'da yeterli kamu görünürlüğü/güven + sinyali olmadığı gerekçesiyle onaylanmadı; bu teknik kalite reddi, sertifika + veya ücretli plan onayı değildir. Ayrı açık onay olmadan GitHub App + kurulmaz, imzalama veya yayın yapılmaz (`docs/release/SIGNPATH.md`). - `app/media_targets.py` ayrıştırması, ilgili P0 başlangıç çizgisi kaydedilmeden uygulanmaz. -- Kullanıcı eşleşen yerel `.srt` dosyaları için mevcut otomatik seçme ve görünür - açma davranışını ürün sözleşmesi olarak seçti. İngilizce/Türkçe README bu - sınırla düzeltildi; eski gizli-başlatma yorumu yenilendi ve etki paketi - **253 passed** verdi (`EV-20260905-019`). +- Kullanıcı eşleşen yerel `.srt` dosyaları için otomatik seçme ve görünür + açma davranışını ürün sözleşmesi olarak seçti (`EV-20260905-019`). +- Kalite/mimari/Windows işi: `docs/quality/QUALITY_EVOLUTION_PLAN.md`, + `docs/quality/ARCHITECTURE_INVENTORY.md`, + `docs/quality/ARCHITECTURE_INVENTORY.json`, + `docs/quality/WINDOWS_ACCEPTANCE_MATRIX.md` ve `docs/video-format/`. +- Ertelenen öneriler (ayrı karar ister): EOF tespitinin 100 ms yoklama yerine + `eof-reached`/`idle-active` gözlemiyle yapılması gerçek libmpv ve native + ölçüm gerektirir; birden çok dosyayla açma tek kopya protokol sürümünü + yükseltir; CI'a ruff eklemek kilitli bağımlılık dosyasını değiştirir; + kaynak-metin (`"x" in source`) testlerinin davranış testine çevrilmesi + kademeli yapılmalıdır. +- Gizlilik: `docs/VERIFICATION_LEDGER.json` içinde 24 kayıt yerel kullanıcı + yolunu taşır; ledger append-only olduğu için yerinde değiştirilmedi + (kullanıcı kararı, 3 Ekim 2026). Yeni kayıtlara kullanıcı yolu yazılmaz. ## Kanıt sınırları @@ -178,13 +112,23 @@ Bu dosya projenin tek canlı devir noktasıdır. Tarihsel continuity kronolojisi Bir katmandaki PASS başka katmana aktarılmaz. `blocked`, `failed`, `skipped` veya eksik marker PASS değildir. + ## Meta-kayıt terminal kuralı Merge-kayıt PR'ı protected master'a ulaştığında yeni meta-PR zinciri başlatma; merge/parent/run/`0/0` readback'ini sonraki gerçek kayıt provenance'ına bağla. + ## Sıradaki tek adım -Exact `98be6f243e7a43a33ae901d61a61d6f7850b7b8a` implementation commit'i kullanıcı onayıyla oluşturuldu ve temiz çalışma ağacından geri okundu (`EV-20260908-039`). Genel denetim ve önceki PiP/altyazı sonuçları `EV-20260905-016`–`019` içindedir. İkinci-Esc native teşhisinde aynı geçerli root HWND'nin 2560×1392 kalmasına yol açan event sıraları `EV-20260908-020`–`032` zincirinde korunur; her başarısızlığın nedeni yeni koşumdan önce incelendi ve otomatik retry yapılmadı. `EV-20260908-033` adayı aynı HWND'yi `SW_SHOWNOACTIVATE=4` ile native restore edip DPI-aware `SetWindowPos` uyguladı. `EV-20260908-034`, owner/title düzelirken ayrı control overlay'in eski global konumda kaldığını gösterdi ve resmî **FAILED** oldu. Son ürün kaynağı, native owner placement True döndüğünde aynı callback içinde `video_frame.update_overlay_geometry()` çağırır; False stabil sayılmaz, desteklenmeyen None Qt yolunda kalır. Kırmızı regresyon sonrası başlık dosyası **48 passed**, etki paketi **207 passed**, compile/çeviri/envanter/diff kontrolleri geçti ve bağımsız kaynak inceleme GO verdi (`EV-20260908-035`). İlk overlay koşumunda ürün doğru görünse de probe tüm lifecycle invariantlarını zorunlu kılmadığından apparent PASS fail-open reddedildi ve resmî sonuç **FAILED** kaydedildi (`EV-20260908-036`). Final özel probe; owner/title/video/overlay handle devamlılığı ve geçerliliğini, title/video root bağını, ürün retry sabitlerinden türetilen bekleme sonunda pending zincirinin bitmesini, 700 ms sonrası final geometriyi ve owner dışındaki ikinci başlık-benzeri yüzeyin yokluğunu zorunlu tutar. Her ek sözleşme kırmızı/yeşil kontrol ve py_compile ile doğrulandı; final hash `ee70446c4d4def4dd2aa2b1f733fee7b1d95992ab6d32168360d298aaae63ebe`, bağımsız kaynak inceleme P0=0/P1=0 ile GO verdi (`EV-20260908-037`). Son onaylı final koşum exit 0, failures 0, tek marker, boş stderr, sıfır süreç sızıntısı ve değişmeyen kaynak/medya ile geçti. Pending zinciri bitip 700 ms settle sonrasında aynı ve geçerli HWND'lerle owner `[800,396,1760,996]`, title `[800,396,1760,436]`, video `[800,436,1760,996]`, control overlay `[800,886,1760,996]` oldu; title/video owner'a bağlı, duplicate title listesi boş, oynatma ve stop→terminate kapanış geçti. Final görselde kopuk başlık veya kontrol yüzeyi yoktu; bağımsız artifact inceleme P0/P1/P2=0 ile resmî **native_smoke PASS** verdi (`EV-20260908-038`). Raporlanan ikinci-Esc sorunu bu exact Windows 11 / Qt 6.10 / DPR 1.0 tek ekran senaryosunda kapandı. Physical keyboard, mixed-DPI, çoklu monitör, build, kurulu artifact ve insan kabulü bu PASS'i devralmaz. `WIN-P0-07/08` ve diğer açık donanım satırları ayrı kalır. Sıradaki tek adım, bu append-only evidence-commit bağlantısını docs-only commit ile kapatmak; sonrasında push, PR, build veya başka açık kabul-matrisi işi ayrı kullanıcı onayı ister. Build, kurulum veya GitHub işlemi yapılmadı. +Ayrı build onayıyla bu kaynaktan yeni bir v0.41 paketi üretmek. Bu build, +3 Ekim 2026'da taşınan spec, requirements ve çıktı yollarını ilk kez gerçek +derlemeden geçirir; başarısız olursa neden incelenmeden tekrarlanmaz. +Ardından ayrı onayla görünür kurulumda şunlar kontrol edilir: ilk açılışın +bekleme yapmaması (temizlik kaydı artık arka planda), ekrandan büyük kayıtlı +pencere, tam ekran/PiP'de kapatıp açma, simge durumundan ikinci başlatma, +menüden altyazı ekleme/seçme, açılamayan dosya ve İngilizce yüzde/ondalık +metinleri. Kaldırma, commit, push/PR, tag ve yayın ayrıca onay ister. + ## Dokunulmayacaklar ve ayrı onaylar - Private görsel/native artifact yolları Git'e eklenmez. @@ -193,8 +137,3 @@ Exact `98be6f243e7a43a33ae901d61a61d6f7850b7b8a` implementation commit'i kullan - Ürün kodu, build, kurulum/kaldırma, commit, push, PR, merge, tag ve release birbirinden ayrı açık kullanıcı onayı ister. - Başarısız native/hosted çalışma neden incelenmeden otomatik tekrarlanmaz. -## Kanonik kayıt kaynakları - -- Canlı durum: `CONTINUITY.md`; kronoloji: `CONTINUITY_HISTORY.md`; kanıt: `VERIFICATION_LEDGER.json`; tarihçe: `PROJECT_STATUS.md`, `ROADMAP.md`, `ENGINEERING_AUDIT.md`. -- Paketleme/akış/yayın: `PACKAGING_PLAN.md`, `CHANGE_WORKFLOW.md`, `RELEASE_PROCESS.md`. -- Kalite/mimari/Windows: `docs/QUALITY_EVOLUTION_PLAN.md`, `docs/ARCHITECTURE_INVENTORY.md`, `docs/ARCHITECTURE_INVENTORY.json`, `docs/WINDOWS_ACCEPTANCE_MATRIX.md` ve video formatı kabul dosyaları. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..842b65c --- /dev/null +++ b/docs/README.md @@ -0,0 +1,83 @@ +# MLC Player belge haritası + +Bu dosya hangi bilginin hangi belgede tutulduğunu gösteren tek haritadır. +Bir bilgi yalnız bir belgede yaşar; başka belgeler onu tekrar etmez, buraya +veya sahibine bağlanır. `tests/test_docs_index_regressions.py` bu haritadaki +her belgenin var olduğunu ve `docs/` altındaki her belgenin burada +listelendiğini denetler. + +## Önce okunacaklar + +1. `docs/process/AGENTS.md` — agent başlangıç sözleşmesi ve kanıt sınırları. +2. `docs/CONTINUITY.md` — güncel durum ve sıradaki tek adım. +3. `docs/VERIFICATION_LEDGER.json` — makinece doğrulanmış sonuçlar. + +Claude'a özgü kurallar `.claude/CLAUDE.md` içindedir ve `AGENTS.md`'yi +otomatik yükler. Kullanıcıya dönük tanıtım `.github/README.md` (İngilizce, +GitHub ana sayfası) ve `.github/README.tr.md` dosyalarındadır. + +## Canlı belgeler + +| Belge | Tek sorumluluğu | Ne zaman güncellenir | +| --- | --- | --- | +| `docs/CONTINUITY.md` | Güncel durum, korunan kapılar, sıradaki tek adım | Her karar/kanıt turunun sonunda | +| `docs/VERIFICATION_LEDGER.json` | Append-only kanıt kaydı | Sonraki kararda kullanılacak her sonuçta | +| `docs/process/AGENTS.md` | Agent başlangıcı, kanıt katmanları, çift-süzgeç | Çalışma sözleşmesi değiştiğinde | +| `docs/process/CHANGE_WORKFLOW.md` | PR, CI ve birleştirme akışı | Dal/PR kuralı değiştiğinde | +| `docs/release/RELEASE_PROCESS.md` | Yayın sırasının tek resmî kaynağı | Yalnız yayın sözleşmesi değiştiğinde | +| `docs/release/PACKAGING_PLAN.md` | Installer UX sözleşmesi, paketleme kararları, açık uyumluluk maddeleri | Installer veya paketleme kararı değiştiğinde | +| `docs/release/SIGNPATH.md` | SignPath başvurusu ve imzalama hazırlığı | Başvuru durumu değiştiğinde | +| `docs/policies/PRIVACY.md` | Kamuya açık gizlilik politikası | Ağ veya yerel veri davranışı değiştiğinde | +| `docs/policies/CODE_SIGNING_POLICY.md` | Kamuya açık kod imzalama politikası | İmzalama durumu değiştiğinde | +| `docs/quality/QUALITY_EVOLUTION_PLAN.md` | Mimari ve gerçek Windows kalite programı | Faz başlarken veya kapanırken | +| `docs/quality/ARCHITECTURE_INVENTORY.md` | Modül sahipliği ve ayrıştırma adayları | Ölçüm veya sahiplik değiştiğinde | +| `docs/quality/ARCHITECTURE_INVENTORY.json` | Altı büyük modülün hash/yapı güncellik kapısı | Bu modüllerden biri değiştiğinde | +| `docs/quality/WINDOWS_ACCEPTANCE_MATRIX.md` | Gerçek Windows kabul senaryoları | Senaryo veya sonuç değiştiğinde | +| `docs/video-format/VIDEO_FORMAT_ACCEPTANCE_PLAN.md` | SDR/HDR/codec kabul planı ve ölçülmüş yetenek envanteri | Format kararı veya ölçüm değiştiğinde | +| `docs/video-format/VIDEO_FORMAT_ACCEPTANCE_MATRIX.json` | Format satırlarının makinece kaynağı | Satır sonucu değiştiğinde | +| `docs/video-format/VIDEO_FORMAT_MEDIA_MANIFEST.json` | Test medyasının kimlik ve köken sözleşmesi | Medya adayı değiştiğinde | + +## Tarihsel arşiv (`docs/history/`) + +Bu dosyalar güncel karar kaynağı değildir; her biri açılışında bunu söyler. +Kapanan iş canlı belgeden silinmez, kelimesi kelimesine buraya taşınır. + +| Belge | İçerik | +| --- | --- | +| `docs/history/CONTINUITY_HISTORY.md` | Eski devir notlarının kronolojisi (özeti testle sabit) | +| `docs/history/PROJECT_STATUS.md` | 20 Ağustos 2026'ya kadarki proje anlatısı | +| `docs/history/ROADMAP.md` | 20 Ağustos 2026 yol haritası snapshot'ı | +| `docs/history/ENGINEERING_AUDIT.md` | Denetim bulguları ve AUD-20260905 devir raporu | +| `docs/history/PACKAGING_HISTORY.md` | Paketleme kayıtları, eski uyumluluk listesi, ilk ikon notları | + +## Depo düzeni + +Kökte yalnız `Start.bat` ve `LICENSE` bulunur (Git'in zorunlu `.gitignore` +ve `.gitattributes` dosyaları dışında). `LICENSE` GitHub'ın lisansı +tanıması için köktedir. + +| Klasör | İçerik | +| --- | --- | +| `app/` | Ürün kaynağı; giriş noktaları `app/main.py` ve `app/user_cleanup_main.py` | +| `assets/` | İkon ve görsel kimlik dosyaları | +| `bin/` | libmpv ve yardımcı çalışma zamanı ikilileri (büyükleri Git'e girmez) | +| `docs/` | Bu harita ve yukarıdaki belgeler | +| `licenses/` | Üçüncü taraf lisans metinleri | +| `packaging/` | PyInstaller spec, Inno Setup ve yayın betikleri | +| `requirements/` | Bağımlılık listeleri ve hash kilidi | +| `scripts/` | Başlatıcı ve bakım betikleri | +| `tests/` | Otomatik testler ve native ölçüm child'ları | +| `translations/` | `.ts` çeviri kaynakları | +| `.github/` | README dosyaları ve GitHub Actions iş akışları | +| `.claude/` | Claude kuralları, ayarları ve hook'ları | +| `output/` | Git'e girmeyen bütün üretim çıktıları (build, dist, installer, kaynak aynası) | + +## Yeni belge kuralları + +- Önce bu haritaya bak: bilginin bir sahibi varsa oraya yaz, yeni dosya açma. +- Yeni belge gerçekten gerekiyorsa uygun alt klasöre koy ve bu tabloya ekle; + test, listelenmemiş belgeyi kabul etmez. +- Canlı belgede kapanan iş birikirse kelimesi kelimesine `docs/history/` + altına taşı ve canlı belgede yalnız kanıt kimliğiyle an. +- Satırlar sarılı yazılır; tek satıra yığılmış paragraf yazılmaz. +- Yerel kullanıcı yolu, kişisel ad veya özel artifact yolu yazılmaz. diff --git a/docs/SIGNPATH_READINESS.md b/docs/SIGNPATH_READINESS.md deleted file mode 100644 index 016bcf2..0000000 --- a/docs/SIGNPATH_READINESS.md +++ /dev/null @@ -1,109 +0,0 @@ -# SignPath Foundation readiness - -This is an engineering checklist, not proof of acceptance and not legal -advice. **Submission status: SUBMITTED - NOT APPROVED (2 September 2026)**. -The application was submitted on 23 August 2026. SignPath Foundation declined -the Foundation application because the project did not yet show sufficient -public visibility/trust signals. This is not a technical-quality verdict and -does not create a SignPath account, certificate or paid subscription. - -The reviewable submission draft is -[`SIGNPATH_FOUNDATION_APPLICATION.md`](SIGNPATH_FOUNDATION_APPLICATION.md). - -## Conditions supported by current evidence - -- The repository is public and licensed under OSI-approved GPL-3.0-only. -- MLC Player is actively maintained, documented and already released as v0.39. -- Windows packages include notices and corresponding-source records for their - Open Source components. -- The product provides uninstallers and does not contain telemetry, analytics - or advertising. -- Upstream Open Source binaries are included as dependencies rather than - represented as MLC Player-owned binaries. -- `CODE_SIGNING_POLICY.md`, `PRIVACY.md` and `docs/RELEASE_PROCESS.md` document - roles, unsigned status, network/local-data behavior and the release order. -- The maintainer confirmed on 23 August 2026 that GitHub MFA is enabled. This - is a user attestation, not independently proven repository evidence. - -These facts make an application plausible; only SignPath Foundation can make -the eligibility decision. - -## Verified GitHub-hosted unsigned build - -`.github/workflows/build-unsigned-main.yml` is manual-only and uses a -GitHub-hosted Windows 2025 runner. Exact master commit -`1f01633bd7b008dba6faec8362ddff66e0d6d009` completed run - successfully. - -The verified boundary includes: - -- Python `3.13.15`, Inno Setup `6.7.1`, 24 exact hash-locked Windows wheels - and the immutable project-owned libmpv OCI digest; -- installer `MLCPlayer_Setup_v0.39.exe`, **57,255,931 bytes**, SHA-256 - `cab8c89ba614dcf3589410d345248b831439beacaecad3db6994af9a0f436066`; -- Authenticode status `NotSigned`, no signer certificate and no detached - `.sig` in the hosted output; -- Actions artifacts - `mlcplayer-unsigned-main-1f01633bd7b008dba6faec8362ddff66e0d6d009` - and - `mlcplayer-unsigned-main-provenance-1f01633bd7b008dba6faec8362ddff66e0d6d009`. - -Both artifact archives were independently downloaded and matched GitHub's -recorded sizes and SHA-256 digests. This is hosted unsigned-build evidence, -not installation, native playback, Authenticode or release acceptance. - -The workflow does not call SignPath, access the local Ed25519 key, create a -tag or change a release. The optional Internet Video add-on was deliberately -outside this build and remains outside the initial signing scope. - -## Submitted application boundary - -The official form at was submitted after separate -user approval. It contained the public project, repository, privacy, hosted -build and early-stage reputation facts recorded in -`SIGNPATH_FOUNDATION_APPLICATION.md`. Contact name and email were supplied -privately to SignPath and are not stored in the repository. The required Code -of Conduct and personal-data processing consents were selected; optional -marketing consent remained off. The page returned **Form submitted** and -**Thank you, we'll be in touch soon.** - -This proves form delivery only. No application identifier, acceptance, -SignPath account, GitHub App, token, organization, project, policy, -certificate, signing request or signature was returned. - -## Recorded Foundation response - -The user provided SignPath's 2 September 2026 support response. It states that -the Foundation program looks for established external trust/visibility signals -such as community adoption, independent references and sustained engagement, -and that MLC Player did not yet show enough of them. It invites a later -reapplication if those signals grow and separately mentions a paid -subscription as an optional route. Neither path is approved, configured or -part of MLC Player's active release chain. - -Do not add an action, token, certificate or acceptance claim without a future -explicit acceptance and separate implementation approval. Private contact -details remain outside the repository. - -## Configuration only after SignPath acceptance - -If accepted, connect only the repository's GitHub-hosted workflow to the -predefined GitHub.com Trusted Build System and install the SignPath GitHub App -with repository-limited access. Define separate CI submitter and interactive -approver roles, require MFA and keep every signing request under manual -approval. Resolve the submission action to an immutable commit before it is -introduced; a floating action tag is not an approved release input. - -The safe future sequence is: - -1. GitHub-hosted runner builds and stores the exact unsigned main installer. -2. After SignPath acceptance, the exact stored artifact is submitted. -3. A human approver checks the commit/version and manually approves signing. -4. SignPath applies Authenticode to the installer bytes. -5. The signed installer is downloaded to the controlled release environment. -6. The existing Ed25519 process signs the final Authenticode-signed bytes. -7. Install, upgrade, uninstall, native-smoke and release-parity gates run - against those final bytes. - -Until these later gates exist and pass, SignPath is not part of the active -release chain. `docs/RELEASE_PROCESS.md` remains authoritative. diff --git a/docs/VERIFICATION_LEDGER.json b/docs/VERIFICATION_LEDGER.json index cd2e613..f18a616 100644 --- a/docs/VERIFICATION_LEDGER.json +++ b/docs/VERIFICATION_LEDGER.json @@ -8914,17 +8914,61 @@ "evaluate_isolated_deno_blocker.ps1 -EvidenceRoot " ], "private_artifact_digests": [ - {"name": "MLCPlayer_Setup_v0.40.exe", "size_bytes": 55914571, "sha256": "B8001DAA3722D99A5E85313CFD417F80E8E405C6990859E81BE8C52D378FE2FC"}, - {"name": "MLCPlayer_InternetVideo_v0.40.exe", "size_bytes": 48909126, "sha256": "D4A4D79995B23228231B8D16091AB85D4805862FDBDDEC415842CE9DFDF9E675"}, - {"name": "run_isolated_deno_blocker.ps1", "size_bytes": 6948, "sha256": "D8CFB236964F62D0DEC10D4461CEAA85A745B9B2F740AB2A226A1DCFA2D521C6"}, - {"name": "evaluate_isolated_deno_blocker.ps1", "size_bytes": 7433, "sha256": "9FA1D94E14187181A658D0DE3A263438F0199F33320341447502C2CC42DE05E0"}, - {"name": "finite_deno_wait.ts", "size_bytes": 62, "sha256": "42DDB4ECFB1F8751D0AAC29A045663D4E4CB2BCF30458150794B069534EBA577"}, - {"name": "50-addon-isolated-deno-pre.json", "size_bytes": 32843, "sha256": "E9B5BD35B98458A78561E7629A3B30158E792A0BEFAF3CC2D07AF1CBEC29F705"}, - {"name": "50-addon-isolated-deno-result.json", "size_bytes": 1540, "sha256": "4CE409331AE3648CEBEC46BAD29CB0EFCE769855205F96C73CB0946FD502D474"}, - {"name": "51-addon-isolated-deno-upgrade.log", "size_bytes": 5793, "sha256": "D740B99FC2ACF36E019CE1224A5277265997307AA4FF001777D5DDC1EC43686F"}, - {"name": "52-addon-isolated-deno-return.json", "size_bytes": 32445, "sha256": "4FF3031B8F6EB25D7EF2AB04A73CEE086F0ED92E32C80446B2C1B3F0A918978D"}, - {"name": "53-addon-isolated-deno-leak.json", "size_bytes": 32445, "sha256": "988B7E9CB83EE18D7DB8D9B6C2E4748FDCE5274411F37A9F51BE858E83A3A679"}, - {"name": "54-addon-isolated-deno-verdict.json", "size_bytes": 1488, "sha256": "8846086604C70268F3DA0A6460CA1A01D09B789B6FA83EF7CB28A79A31CD5A28"} + { + "name": "MLCPlayer_Setup_v0.40.exe", + "size_bytes": 55914571, + "sha256": "B8001DAA3722D99A5E85313CFD417F80E8E405C6990859E81BE8C52D378FE2FC" + }, + { + "name": "MLCPlayer_InternetVideo_v0.40.exe", + "size_bytes": 48909126, + "sha256": "D4A4D79995B23228231B8D16091AB85D4805862FDBDDEC415842CE9DFDF9E675" + }, + { + "name": "run_isolated_deno_blocker.ps1", + "size_bytes": 6948, + "sha256": "D8CFB236964F62D0DEC10D4461CEAA85A745B9B2F740AB2A226A1DCFA2D521C6" + }, + { + "name": "evaluate_isolated_deno_blocker.ps1", + "size_bytes": 7433, + "sha256": "9FA1D94E14187181A658D0DE3A263438F0199F33320341447502C2CC42DE05E0" + }, + { + "name": "finite_deno_wait.ts", + "size_bytes": 62, + "sha256": "42DDB4ECFB1F8751D0AAC29A045663D4E4CB2BCF30458150794B069534EBA577" + }, + { + "name": "50-addon-isolated-deno-pre.json", + "size_bytes": 32843, + "sha256": "E9B5BD35B98458A78561E7629A3B30158E792A0BEFAF3CC2D07AF1CBEC29F705" + }, + { + "name": "50-addon-isolated-deno-result.json", + "size_bytes": 1540, + "sha256": "4CE409331AE3648CEBEC46BAD29CB0EFCE769855205F96C73CB0946FD502D474" + }, + { + "name": "51-addon-isolated-deno-upgrade.log", + "size_bytes": 5793, + "sha256": "D740B99FC2ACF36E019CE1224A5277265997307AA4FF001777D5DDC1EC43686F" + }, + { + "name": "52-addon-isolated-deno-return.json", + "size_bytes": 32445, + "sha256": "4FF3031B8F6EB25D7EF2AB04A73CEE086F0ED92E32C80446B2C1B3F0A918978D" + }, + { + "name": "53-addon-isolated-deno-leak.json", + "size_bytes": 32445, + "sha256": "988B7E9CB83EE18D7DB8D9B6C2E4748FDCE5274411F37A9F51BE858E83A3A679" + }, + { + "name": "54-addon-isolated-deno-verdict.json", + "size_bytes": 1488, + "sha256": "8846086604C70268F3DA0A6460CA1A01D09B789B6FA83EF7CB28A79A31CD5A28" + } ], "subject": "Exact v0.40 add-on upgrade with one active exact installed Deno", "evidence": [ @@ -8965,12 +9009,36 @@ "evaluate_isolated_deno_blocker.ps1 -EvidenceRoot -> 54-addon-isolated-deno-verdict.json" ], "private_artifact_digests": [ - {"name": "50-addon-isolated-deno-pre.json", "size_bytes": 32843, "sha256": "E9B5BD35B98458A78561E7629A3B30158E792A0BEFAF3CC2D07AF1CBEC29F705"}, - {"name": "50-addon-isolated-deno-result.json", "size_bytes": 1540, "sha256": "4CE409331AE3648CEBEC46BAD29CB0EFCE769855205F96C73CB0946FD502D474"}, - {"name": "51-addon-isolated-deno-upgrade.log", "size_bytes": 5793, "sha256": "D740B99FC2ACF36E019CE1224A5277265997307AA4FF001777D5DDC1EC43686F"}, - {"name": "52-addon-isolated-deno-return.json", "size_bytes": 32445, "sha256": "4FF3031B8F6EB25D7EF2AB04A73CEE086F0ED92E32C80446B2C1B3F0A918978D"}, - {"name": "53-addon-isolated-deno-leak.json", "size_bytes": 32445, "sha256": "988B7E9CB83EE18D7DB8D9B6C2E4748FDCE5274411F37A9F51BE858E83A3A679"}, - {"name": "54-addon-isolated-deno-verdict.json", "size_bytes": 1488, "sha256": "8846086604C70268F3DA0A6460CA1A01D09B789B6FA83EF7CB28A79A31CD5A28"} + { + "name": "50-addon-isolated-deno-pre.json", + "size_bytes": 32843, + "sha256": "E9B5BD35B98458A78561E7629A3B30158E792A0BEFAF3CC2D07AF1CBEC29F705" + }, + { + "name": "50-addon-isolated-deno-result.json", + "size_bytes": 1540, + "sha256": "4CE409331AE3648CEBEC46BAD29CB0EFCE769855205F96C73CB0946FD502D474" + }, + { + "name": "51-addon-isolated-deno-upgrade.log", + "size_bytes": 5793, + "sha256": "D740B99FC2ACF36E019CE1224A5277265997307AA4FF001777D5DDC1EC43686F" + }, + { + "name": "52-addon-isolated-deno-return.json", + "size_bytes": 32445, + "sha256": "4FF3031B8F6EB25D7EF2AB04A73CEE086F0ED92E32C80446B2C1B3F0A918978D" + }, + { + "name": "53-addon-isolated-deno-leak.json", + "size_bytes": 32445, + "sha256": "988B7E9CB83EE18D7DB8D9B6C2E4748FDCE5274411F37A9F51BE858E83A3A679" + }, + { + "name": "54-addon-isolated-deno-verdict.json", + "size_bytes": 1488, + "sha256": "8846086604C70268F3DA0A6460CA1A01D09B789B6FA83EF7CB28A79A31CD5A28" + } ], "subject": "Deno isolated refusal gate not produced by normal finite Deno", "evidence": [ @@ -9049,20 +9117,68 @@ "evaluate_suspended_player_fault.ps1 -EvidenceRoot -> 59-main-suspended-player-verdict.json" ], "artifacts": [ - {"name": "MLCPlayer_Setup_v0.40.exe", "size_bytes": 55914571, "sha256": "B8001DAA3722D99A5E85313CFD417F80E8E405C6990859E81BE8C52D378FE2FC"}, - {"name": "MLCPlayer_InternetVideo_v0.40.exe", "size_bytes": 48909126, "sha256": "D4A4D79995B23228231B8D16091AB85D4805862FDBDDEC415842CE9DFDF9E675"}, - {"name": "installed MLC Player.exe", "size_bytes": 3133269, "sha256": "14872D7DA2D924BD627D77A015EB32B1FB1B995D39A4E8BB6D95CF95A3230691"} + { + "name": "MLCPlayer_Setup_v0.40.exe", + "size_bytes": 55914571, + "sha256": "B8001DAA3722D99A5E85313CFD417F80E8E405C6990859E81BE8C52D378FE2FC" + }, + { + "name": "MLCPlayer_InternetVideo_v0.40.exe", + "size_bytes": 48909126, + "sha256": "D4A4D79995B23228231B8D16091AB85D4805862FDBDDEC415842CE9DFDF9E675" + }, + { + "name": "installed MLC Player.exe", + "size_bytes": 3133269, + "sha256": "14872D7DA2D924BD627D77A015EB32B1FB1B995D39A4E8BB6D95CF95A3230691" + } ], "private_artifact_digests": [ - {"name": "55-main-suspended-player-pre.json", "size_bytes": 32704, "sha256": "F3EF242BB8CAAAF5BAC3DD2116557A0875002250229DCA11B64E1D832C413932"}, - {"name": "55-main-suspended-player-result.json", "size_bytes": 2661, "sha256": "131EBC8F3694FE3A50C9E286486F26079484334FF55DACCC8EEF22B0D24D5FF8"}, - {"name": "56-main-suspended-player-upgrade.log", "size_bytes": 2513, "sha256": "AC79839E41B7AE542A398C9AA42B2E48DB0F1EE7BE37A6106B4C5D94BFCB208E"}, - {"name": "57-main-suspended-player-return.json", "size_bytes": 32704, "sha256": "053EF35ADCBB44785F3AB01B0994A5F3DED2F402A273EC66E2D2219862BEB9C7"}, - {"name": "58-main-suspended-player-leak.json", "size_bytes": 32445, "sha256": "BCB7C3C112C58F00E276D2B21A2A228C4C02BD13903CFABFCA89D6B8DB989CA9"}, - {"name": "59-main-suspended-player-verdict.json", "size_bytes": 1381, "sha256": "920E44F9B2A3A622B0E951615319467797BCA18064AC738B7C800C8CF42BF4CD"}, - {"name": "resume_player_watchdog.ps1", "size_bytes": 1966, "sha256": "4D5C8CBDCD8F9E6A22F9E3490C42E501A9652D5F4BB77743A211DC5912E8A191"}, - {"name": "run_suspended_player_fault.ps1", "size_bytes": 10564, "sha256": "3ACD7855F238113FC28A4527C92E747A5F565D6531D2011CC0BCBF89359768E1"}, - {"name": "evaluate_suspended_player_fault.ps1", "size_bytes": 6617, "sha256": "0B8A8FCB01CF94C31BE35DB90C26F16BD913E0F3931CF039CAB59A06951AE4BA"} + { + "name": "55-main-suspended-player-pre.json", + "size_bytes": 32704, + "sha256": "F3EF242BB8CAAAF5BAC3DD2116557A0875002250229DCA11B64E1D832C413932" + }, + { + "name": "55-main-suspended-player-result.json", + "size_bytes": 2661, + "sha256": "131EBC8F3694FE3A50C9E286486F26079484334FF55DACCC8EEF22B0D24D5FF8" + }, + { + "name": "56-main-suspended-player-upgrade.log", + "size_bytes": 2513, + "sha256": "AC79839E41B7AE542A398C9AA42B2E48DB0F1EE7BE37A6106B4C5D94BFCB208E" + }, + { + "name": "57-main-suspended-player-return.json", + "size_bytes": 32704, + "sha256": "053EF35ADCBB44785F3AB01B0994A5F3DED2F402A273EC66E2D2219862BEB9C7" + }, + { + "name": "58-main-suspended-player-leak.json", + "size_bytes": 32445, + "sha256": "BCB7C3C112C58F00E276D2B21A2A228C4C02BD13903CFABFCA89D6B8DB989CA9" + }, + { + "name": "59-main-suspended-player-verdict.json", + "size_bytes": 1381, + "sha256": "920E44F9B2A3A622B0E951615319467797BCA18064AC738B7C800C8CF42BF4CD" + }, + { + "name": "resume_player_watchdog.ps1", + "size_bytes": 1966, + "sha256": "4D5C8CBDCD8F9E6A22F9E3490C42E501A9652D5F4BB77743A211DC5912E8A191" + }, + { + "name": "run_suspended_player_fault.ps1", + "size_bytes": 10564, + "sha256": "3ACD7855F238113FC28A4527C92E747A5F565D6531D2011CC0BCBF89359768E1" + }, + { + "name": "evaluate_suspended_player_fault.ps1", + "size_bytes": 6617, + "sha256": "0B8A8FCB01CF94C31BE35DB90C26F16BD913E0F3931CF039CAB59A06951AE4BA" + } ], "subject": "Suspended installed Player fail-closed main-installer fault boundary", "evidence": [ @@ -9148,16 +9264,40 @@ "b2_capture.ps1 -OutputPath 62-artifact-provenance-failure-post.json -MainArtifact MLCPlayer_Setup_v0.40.exe -AddonArtifact MLCPlayer_InternetVideo_v0.40.exe" ], "artifacts_before_failure": [ - {"name": "MLCPlayer_Setup_v0.40.exe", "size_bytes": 55914571, "sha256": "B8001DAA3722D99A5E85313CFD417F80E8E405C6990859E81BE8C52D378FE2FC"}, - {"name": "MLCPlayer_InternetVideo_v0.40.exe", "size_bytes": 48909126, "sha256": "D4A4D79995B23228231B8D16091AB85D4805862FDBDDEC415842CE9DFDF9E675"} + { + "name": "MLCPlayer_Setup_v0.40.exe", + "size_bytes": 55914571, + "sha256": "B8001DAA3722D99A5E85313CFD417F80E8E405C6990859E81BE8C52D378FE2FC" + }, + { + "name": "MLCPlayer_InternetVideo_v0.40.exe", + "size_bytes": 48909126, + "sha256": "D4A4D79995B23228231B8D16091AB85D4805862FDBDDEC415842CE9DFDF9E675" + } ], "remaining_orphan_signature_digests": [ - {"name": "MLCPlayer_Setup_v0.40.exe.sig", "size_bytes": 88, "sha256": "3C7165A3A70FFB3602436A70B7AED12D6072B77845240B3D9B09FC6E749CFEBA"}, - {"name": "MLCPlayer_InternetVideo_v0.40.exe.sig", "size_bytes": 88, "sha256": "516CA07EE797C06C8B0902CE16EB85E98BCC2713D277FFDC689D6C4D15E2DF73"} + { + "name": "MLCPlayer_Setup_v0.40.exe.sig", + "size_bytes": 88, + "sha256": "3C7165A3A70FFB3602436A70B7AED12D6072B77845240B3D9B09FC6E749CFEBA" + }, + { + "name": "MLCPlayer_InternetVideo_v0.40.exe.sig", + "size_bytes": 88, + "sha256": "516CA07EE797C06C8B0902CE16EB85E98BCC2713D277FFDC689D6C4D15E2DF73" + } ], "private_artifact_digests": [ - {"name": "61-final-clean-pre.json", "size_bytes": 32445, "sha256": "90B2A27EAB20C1973391ACD804DA0BE001D512E19EEE9CE8DE47F824C278A52F"}, - {"name": "62-artifact-provenance-failure-post.json", "size_bytes": 32017, "sha256": "4C06DF3A0C8E49620B7FAF3B59A043502E5669C7CAB4C4B58610C2F02DE76806"} + { + "name": "61-final-clean-pre.json", + "size_bytes": 32445, + "sha256": "90B2A27EAB20C1973391ACD804DA0BE001D512E19EEE9CE8DE47F824C278A52F" + }, + { + "name": "62-artifact-provenance-failure-post.json", + "size_bytes": 32017, + "sha256": "4C06DF3A0C8E49620B7FAF3B59A043502E5669C7CAB4C4B58610C2F02DE76806" + } ], "subject": "Release-candidate provenance split and compile-preflight artifact loss", "evidence": [ @@ -9457,15 +9597,43 @@ "read-only setup-log timestamp comparison for Starting installation process, Shutting down applications, Some applications could not be shut down, user Abort and log close" ], "artifacts": [ - {"name": "MLCPlayer_Setup_v0.40.exe", "size_bytes": 55928017, "sha256": "B7EAA9972B347F2546BDC72B1B198917C6B7DDE4B5FE35D902EDC826C2FC8119"}, - {"name": "MLCPlayer_InternetVideo_v0.40.exe", "size_bytes": 48909126, "sha256": "D4A4D79995B23228231B8D16091AB85D4805862FDBDDEC415842CE9DFDF9E675"} + { + "name": "MLCPlayer_Setup_v0.40.exe", + "size_bytes": 55928017, + "sha256": "B7EAA9972B347F2546BDC72B1B198917C6B7DDE4B5FE35D902EDC826C2FC8119" + }, + { + "name": "MLCPlayer_InternetVideo_v0.40.exe", + "size_bytes": 48909126, + "sha256": "D4A4D79995B23228231B8D16091AB85D4805862FDBDDEC415842CE9DFDF9E675" + } ], "private_artifact_digests": [ - {"name": "29-final-restored.json", "size_bytes": 32444, "sha256": "181092C38ECB17906F824BEA7C275F1D7AC1447C767A952C45BA78DC8B5A8C18"}, - {"name": "55-main-suspended-player-pre.json", "size_bytes": 32704, "sha256": "BF4164DBEEE6C5495BE5D27EAC3DCF2258EF380A401BD92A659F559CB696B97F"}, - {"name": "56-main-suspended-player-upgrade.log", "size_bytes": 2524, "sha256": "64163DA0549E0A75735A3FBE191E5538E5D003AF47FEB68524AF90F7DB65FA84"}, - {"name": "57-main-suspended-player-return.json", "size_bytes": 32704, "sha256": "E90EDB4111E8B0FBD060D023F6DB003CF46B49F66EA6166884FD5E4D65208ECC"}, - {"name": "58-main-suspended-player-leak.json", "size_bytes": 32444, "sha256": "649FFFF5DBC1FD8231E5FF3331C38F18D0F9C09EB7DFD122876043F4E0458176"} + { + "name": "29-final-restored.json", + "size_bytes": 32444, + "sha256": "181092C38ECB17906F824BEA7C275F1D7AC1447C767A952C45BA78DC8B5A8C18" + }, + { + "name": "55-main-suspended-player-pre.json", + "size_bytes": 32704, + "sha256": "BF4164DBEEE6C5495BE5D27EAC3DCF2258EF380A401BD92A659F559CB696B97F" + }, + { + "name": "56-main-suspended-player-upgrade.log", + "size_bytes": 2524, + "sha256": "64163DA0549E0A75735A3FBE191E5538E5D003AF47FEB68524AF90F7DB65FA84" + }, + { + "name": "57-main-suspended-player-return.json", + "size_bytes": 32704, + "sha256": "E90EDB4111E8B0FBD060D023F6DB003CF46B49F66EA6166884FD5E4D65208ECC" + }, + { + "name": "58-main-suspended-player-leak.json", + "size_bytes": 32444, + "sha256": "649FFFF5DBC1FD8231E5FF3331C38F18D0F9C09EB7DFD122876043F4E0458176" + } ], "evidence": [ "the normal exact B7/D4 path passed real v0.39-to-v0.40 main and add-on upgrades, two opposite uninstall orders, clean silent install, same-version maintenance, exact payload readback, preserved two-file user data and zero target-process leakage", @@ -9569,10 +9737,26 @@ "repository prepublish signature_is_valid with string paths and independent app.release_signature.verify readback; Get-Item, Get-FileHash SHA256, Get-AuthenticodeSignature, PE version, exact HEAD/tree/origin and process readback" ], "artifacts": [ - {"name": "MLCPlayer_Setup_v0.40.exe", "size_bytes": 55945176, "sha256": "ABBB01C337535DB989E4A36D61D4362604C448323A2D27506F155C84EA48DC34"}, - {"name": "MLCPlayer_Setup_v0.40.exe.sig", "size_bytes": 88, "sha256": "7D003B8CF705549111D54BDC8543D5E1B8D6C9651A6AA8A590020531E519CC3E"}, - {"name": "MLCPlayer_InternetVideo_v0.40.exe", "size_bytes": 48909126, "sha256": "D4A4D79995B23228231B8D16091AB85D4805862FDBDDEC415842CE9DFDF9E675"}, - {"name": "MLCPlayer_InternetVideo_v0.40.exe.sig", "size_bytes": 88, "sha256": "516CA07EE797C06C8B0902CE16EB85E98BCC2713D277FFDC689D6C4D15E2DF73"} + { + "name": "MLCPlayer_Setup_v0.40.exe", + "size_bytes": 55945176, + "sha256": "ABBB01C337535DB989E4A36D61D4362604C448323A2D27506F155C84EA48DC34" + }, + { + "name": "MLCPlayer_Setup_v0.40.exe.sig", + "size_bytes": 88, + "sha256": "7D003B8CF705549111D54BDC8543D5E1B8D6C9651A6AA8A590020531E519CC3E" + }, + { + "name": "MLCPlayer_InternetVideo_v0.40.exe", + "size_bytes": 48909126, + "sha256": "D4A4D79995B23228231B8D16091AB85D4805862FDBDDEC415842CE9DFDF9E675" + }, + { + "name": "MLCPlayer_InternetVideo_v0.40.exe.sig", + "size_bytes": 88, + "sha256": "516CA07EE797C06C8B0902CE16EB85E98BCC2713D277FFDC689D6C4D15E2DF73" + } ], "evidence": [ "the separately approved build started once from exact clean 1dafe1fc0149200a0f8cbbd81b6713431d785384, completed preflight, translation, PyInstaller, both Inno builds, signing and final verification with exit 0 and DONE", @@ -9610,14 +9794,38 @@ "read-only result, leak snapshot, application log and available Windows process-audit log inspection; no automatic retry" ], "artifacts": [ - {"name": "MLCPlayer_Setup_v0.40.exe", "size_bytes": 55945176, "sha256": "ABBB01C337535DB989E4A36D61D4362604C448323A2D27506F155C84EA48DC34"}, - {"name": "MLCPlayer_InternetVideo_v0.40.exe", "size_bytes": 48909126, "sha256": "D4A4D79995B23228231B8D16091AB85D4805862FDBDDEC415842CE9DFDF9E675"} + { + "name": "MLCPlayer_Setup_v0.40.exe", + "size_bytes": 55945176, + "sha256": "ABBB01C337535DB989E4A36D61D4362604C448323A2D27506F155C84EA48DC34" + }, + { + "name": "MLCPlayer_InternetVideo_v0.40.exe", + "size_bytes": 48909126, + "sha256": "D4A4D79995B23228231B8D16091AB85D4805862FDBDDEC415842CE9DFDF9E675" + } ], "private_artifact_digests": [ - {"name": "run_fast_fail_latency.ps1", "size_bytes": 9079, "sha256": "0DA8BCE26D471C2BD405F1CE96C64F5B56A31B3A87101EB8080F783100D5842B"}, - {"name": "04-fast-fail-leak.json", "size_bytes": 71876, "sha256": "9342A59CF43320FC8AF5C5C5347AA1DBB78D2B4E4FD0D33BDCF7F0BF545880EC"}, - {"name": "05-fast-fail-result.json", "size_bytes": 1072, "sha256": "045B6F81ABF78821BAC66F6B255A9416F49BB89249929C715626787865DB92E0"}, - {"name": "run_fast_fail_latency.ps1.prepared-next", "size_bytes": 9332, "sha256": "A2F5BFA7FF74CE1BB15A6BD88E2D79A15F78E2CE2611CA9E6423233A3CAF799C"} + { + "name": "run_fast_fail_latency.ps1", + "size_bytes": 9079, + "sha256": "0DA8BCE26D471C2BD405F1CE96C64F5B56A31B3A87101EB8080F783100D5842B" + }, + { + "name": "04-fast-fail-leak.json", + "size_bytes": 71876, + "sha256": "9342A59CF43320FC8AF5C5C5347AA1DBB78D2B4E4FD0D33BDCF7F0BF545880EC" + }, + { + "name": "05-fast-fail-result.json", + "size_bytes": 1072, + "sha256": "045B6F81ABF78821BAC66F6B255A9416F49BB89249929C715626787865DB92E0" + }, + { + "name": "run_fast_fail_latency.ps1.prepared-next", + "size_bytes": 9332, + "sha256": "A2F5BFA7FF74CE1BB15A6BD88E2D79A15F78E2CE2611CA9E6423233A3CAF799C" + } ], "evidence": [ "the approved attempt started from exact clean 703c126, exact ABBB/D4 artifacts, registered v0.40 main and add-on, exact installed Player E30 and zero pre-existing target processes", @@ -9655,16 +9863,48 @@ "read-only setup log, pre/return/leak/result, live process and independent adversarial readback; no automatic retry" ], "artifacts": [ - {"name": "MLCPlayer_Setup_v0.40.exe", "size_bytes": 55945176, "sha256": "ABBB01C337535DB989E4A36D61D4362604C448323A2D27506F155C84EA48DC34"}, - {"name": "MLCPlayer_InternetVideo_v0.40.exe", "size_bytes": 48909126, "sha256": "D4A4D79995B23228231B8D16091AB85D4805862FDBDDEC415842CE9DFDF9E675"} + { + "name": "MLCPlayer_Setup_v0.40.exe", + "size_bytes": 55945176, + "sha256": "ABBB01C337535DB989E4A36D61D4362604C448323A2D27506F155C84EA48DC34" + }, + { + "name": "MLCPlayer_InternetVideo_v0.40.exe", + "size_bytes": 48909126, + "sha256": "D4A4D79995B23228231B8D16091AB85D4805862FDBDDEC415842CE9DFDF9E675" + } ], "private_artifact_digests": [ - {"name": "run_fast_fail_latency.ps1", "size_bytes": 9332, "sha256": "A2F5BFA7FF74CE1BB15A6BD88E2D79A15F78E2CE2611CA9E6423233A3CAF799C"}, - {"name": "01-fast-fail-pre.json", "size_bytes": 32704, "sha256": "D64DB9C000720E148342D19A36AF0491EFEBC5134E5D993567434059A7F58EA1"}, - {"name": "02-fast-fail-setup.log", "size_bytes": 1923, "sha256": "52118AD44541FFEF90683BF9B564A3D172C77528F8BC476772A8840CEB25C0F9"}, - {"name": "03-fast-fail-return.json", "size_bytes": 32704, "sha256": "3D7B4AC8B5F74C70589AD442D08FFE77CEF67B822532B46254E9503E2C59E67E"}, - {"name": "04-fast-fail-leak.json", "size_bytes": 32444, "sha256": "7464B8993512CD4BAA775F4D5CC590D15F2E640C5DF7EC5D7415E5F84F21BF74"}, - {"name": "05-fast-fail-result.json", "size_bytes": 1476, "sha256": "6D661C4393851DB3152FBDE2A910D071418135511694C4CC70E8D297FC4F4925"} + { + "name": "run_fast_fail_latency.ps1", + "size_bytes": 9332, + "sha256": "A2F5BFA7FF74CE1BB15A6BD88E2D79A15F78E2CE2611CA9E6423233A3CAF799C" + }, + { + "name": "01-fast-fail-pre.json", + "size_bytes": 32704, + "sha256": "D64DB9C000720E148342D19A36AF0491EFEBC5134E5D993567434059A7F58EA1" + }, + { + "name": "02-fast-fail-setup.log", + "size_bytes": 1923, + "sha256": "52118AD44541FFEF90683BF9B564A3D172C77528F8BC476772A8840CEB25C0F9" + }, + { + "name": "03-fast-fail-return.json", + "size_bytes": 32704, + "sha256": "3D7B4AC8B5F74C70589AD442D08FFE77CEF67B822532B46254E9503E2C59E67E" + }, + { + "name": "04-fast-fail-leak.json", + "size_bytes": 32444, + "sha256": "7464B8993512CD4BAA775F4D5CC590D15F2E640C5DF7EC5D7415E5F84F21BF74" + }, + { + "name": "05-fast-fail-result.json", + "size_bytes": 1476, + "sha256": "6D661C4393851DB3152FBDE2A910D071418135511694C4CC70E8D297FC4F4925" + } ], "evidence": [ "the single approved retry started from exact clean e35ee2a, exact ABBB/D4 artifacts, registered v0.40 main and add-on, exact installed E30 Player and zero pre-existing target processes", @@ -9740,20 +9980,68 @@ "python -m pytest -q tests/test_continuity_regressions.py" ], "artifacts": [ - {"name": "MLCPlayer_Setup_v0.40.exe", "size_bytes": 55945176, "sha256": "ABBB01C337535DB989E4A36D61D4362604C448323A2D27506F155C84EA48DC34"}, - {"name": "MLCPlayer_InternetVideo_v0.40.exe", "size_bytes": 48909126, "sha256": "D4A4D79995B23228231B8D16091AB85D4805862FDBDDEC415842CE9DFDF9E675"}, - {"name": "MLCPlayer_Setup_v0.39.exe", "size_bytes": 56329783, "sha256": "00ED3EF67DA44ADF1F4D997426636BA7642413098C66BF08FADE54DD44584915"}, - {"name": "MLCPlayer_InternetVideo_v0.39.exe", "size_bytes": 48896196, "sha256": "07F69153E498E0537F7C9446B69DB1DC36A43256142C03B91FAA0EFC16C5A491"} + { + "name": "MLCPlayer_Setup_v0.40.exe", + "size_bytes": 55945176, + "sha256": "ABBB01C337535DB989E4A36D61D4362604C448323A2D27506F155C84EA48DC34" + }, + { + "name": "MLCPlayer_InternetVideo_v0.40.exe", + "size_bytes": 48909126, + "sha256": "D4A4D79995B23228231B8D16091AB85D4805862FDBDDEC415842CE9DFDF9E675" + }, + { + "name": "MLCPlayer_Setup_v0.39.exe", + "size_bytes": 56329783, + "sha256": "00ED3EF67DA44ADF1F4D997426636BA7642413098C66BF08FADE54DD44584915" + }, + { + "name": "MLCPlayer_InternetVideo_v0.39.exe", + "size_bytes": 48896196, + "sha256": "07F69153E498E0537F7C9446B69DB1DC36A43256142C03B91FAA0EFC16C5A491" + } ], "private_artifact_digests": [ - {"name": "retry-01-S-result.json", "size_bytes": 1128, "sha256": "5C32D45BE40B8416D72C8091D304E57A99EAC90FB564636F2D52850BCDCF7742"}, - {"name": "resume-01-S-result.json", "size_bytes": 1015, "sha256": "613729C0131A2DE2B843953E0500627DFF340F2FBE9D36C90143302C37A16061"}, - {"name": "resume-02-clean-S-result.json", "size_bytes": 2076, "sha256": "CDA27B191C9596ABD10126678AEC2A9BAE2C4D5DAD61E8A5A94AC47BF1D964E5"}, - {"name": "resume-03-upgraded-S-result.json", "size_bytes": 3282, "sha256": "0E5BF84295A2822DB28D41243E59EC0342FD968366AB35950FF92FC01FD700A4"}, - {"name": "resume-04-tail-S-result.json", "size_bytes": 5495, "sha256": "AD25EF48D2AE6CE2F3D253FF58C6A989C2C32C2D2D82C562A3CFE4911EF60C25"}, - {"name": "resume-04-tail-S-28-final-restored.json", "size_bytes": 32638, "sha256": "6E1459582A81B38D4D4BB8E9C5FF7E1BA82BC9B5553484F3E9B5B6794A531A4F"}, - {"name": "resume-04-tail-run_b2_lifecycle.ps1", "size_bytes": 29727, "sha256": "14A78E2C19B4F143EBFDB99957C7E0DED9678A49EF9656466029C0180D6FAAA0"}, - {"name": "resume-04-tail-test_tail_contract.ps1", "size_bytes": 4237, "sha256": "1FF19CEB9A0A077F67EAB462C66DDF29C4F27DADAC1D5C048160AEF9A2839B21"} + { + "name": "retry-01-S-result.json", + "size_bytes": 1128, + "sha256": "5C32D45BE40B8416D72C8091D304E57A99EAC90FB564636F2D52850BCDCF7742" + }, + { + "name": "resume-01-S-result.json", + "size_bytes": 1015, + "sha256": "613729C0131A2DE2B843953E0500627DFF340F2FBE9D36C90143302C37A16061" + }, + { + "name": "resume-02-clean-S-result.json", + "size_bytes": 2076, + "sha256": "CDA27B191C9596ABD10126678AEC2A9BAE2C4D5DAD61E8A5A94AC47BF1D964E5" + }, + { + "name": "resume-03-upgraded-S-result.json", + "size_bytes": 3282, + "sha256": "0E5BF84295A2822DB28D41243E59EC0342FD968366AB35950FF92FC01FD700A4" + }, + { + "name": "resume-04-tail-S-result.json", + "size_bytes": 5495, + "sha256": "AD25EF48D2AE6CE2F3D253FF58C6A989C2C32C2D2D82C562A3CFE4911EF60C25" + }, + { + "name": "resume-04-tail-S-28-final-restored.json", + "size_bytes": 32638, + "sha256": "6E1459582A81B38D4D4BB8E9C5FF7E1BA82BC9B5553484F3E9B5B6794A531A4F" + }, + { + "name": "resume-04-tail-run_b2_lifecycle.ps1", + "size_bytes": 29727, + "sha256": "14A78E2C19B4F143EBFDB99957C7E0DED9678A49EF9656466029C0180D6FAAA0" + }, + { + "name": "resume-04-tail-test_tail_contract.ps1", + "size_bytes": 4237, + "sha256": "1FF19CEB9A0A077F67EAB462C66DDF29C4F27DADAC1D5C048160AEF9A2839B21" + } ], "evidence": [ "the private runner stale-evidence overwrite P0 was closed with create-only result/snapshot writes, a create-new run lock, prerequisite identity locks and stale preflight outside the catch path", @@ -9825,17 +10113,53 @@ "python -m pytest -q tests/test_continuity_regressions.py" ], "artifacts": [ - {"name": "MLCPlayer_Setup_v0.40.exe", "size_bytes": 55945176, "sha256": "ABBB01C337535DB989E4A36D61D4362604C448323A2D27506F155C84EA48DC34"}, - {"name": "MLCPlayer_InternetVideo_v0.40.exe", "size_bytes": 48909126, "sha256": "D4A4D79995B23228231B8D16091AB85D4805862FDBDDEC415842CE9DFDF9E675"}, - {"name": "MLCPlayer_Setup_v0.39.exe", "size_bytes": 56329783, "sha256": "00ED3EF67DA44ADF1F4D997426636BA7642413098C66BF08FADE54DD44584915"}, - {"name": "MLCPlayer_InternetVideo_v0.39.exe", "size_bytes": 48896196, "sha256": "07F69153E498E0537F7C9446B69DB1DC36A43256142C03B91FAA0EFC16C5A491"} + { + "name": "MLCPlayer_Setup_v0.40.exe", + "size_bytes": 55945176, + "sha256": "ABBB01C337535DB989E4A36D61D4362604C448323A2D27506F155C84EA48DC34" + }, + { + "name": "MLCPlayer_InternetVideo_v0.40.exe", + "size_bytes": 48909126, + "sha256": "D4A4D79995B23228231B8D16091AB85D4805862FDBDDEC415842CE9DFDF9E675" + }, + { + "name": "MLCPlayer_Setup_v0.39.exe", + "size_bytes": 56329783, + "sha256": "00ED3EF67DA44ADF1F4D997426636BA7642413098C66BF08FADE54DD44584915" + }, + { + "name": "MLCPlayer_InternetVideo_v0.39.exe", + "size_bytes": 48896196, + "sha256": "07F69153E498E0537F7C9446B69DB1DC36A43256142C03B91FAA0EFC16C5A491" + } ], "private_artifact_digests": [ - {"name": "I-result.json", "size_bytes": 6503, "sha256": "0ED90D3D9B0B9117C592D71B98BF69A919EDCADDD76B3CC6CAF00A4D702423A4"}, - {"name": "I-19-interactive-final-restored.json", "size_bytes": 32638, "sha256": "684071C0E2E0B509B3F3EF49457203D15785D1C75857D6BAC2F2565D597C41B7"}, - {"name": "run_b2_lifecycle.ps1", "size_bytes": 29727, "sha256": "14A78E2C19B4F143EBFDB99957C7E0DED9678A49EF9656466029C0180D6FAAA0"}, - {"name": "test_interactive_contract.ps1", "size_bytes": 4546, "sha256": "517F28D1C0628B42908CBD58D310A3A97E022FB0FBF4F777024A09B5C5317C63"}, - {"name": "launch_interactive.ps1", "size_bytes": 1458, "sha256": "2AF4CA03745332144824CB0CA54435AB74D53B12E882B22A8034CD1019F46156"} + { + "name": "I-result.json", + "size_bytes": 6503, + "sha256": "0ED90D3D9B0B9117C592D71B98BF69A919EDCADDD76B3CC6CAF00A4D702423A4" + }, + { + "name": "I-19-interactive-final-restored.json", + "size_bytes": 32638, + "sha256": "684071C0E2E0B509B3F3EF49457203D15785D1C75857D6BAC2F2565D597C41B7" + }, + { + "name": "run_b2_lifecycle.ps1", + "size_bytes": 29727, + "sha256": "14A78E2C19B4F143EBFDB99957C7E0DED9678A49EF9656466029C0180D6FAAA0" + }, + { + "name": "test_interactive_contract.ps1", + "size_bytes": 4546, + "sha256": "517F28D1C0628B42908CBD58D310A3A97E022FB0FBF4F777024A09B5C5317C63" + }, + { + "name": "launch_interactive.ps1", + "size_bytes": 1458, + "sha256": "2AF4CA03745332144824CB0CA54435AB74D53B12E882B22A8034CD1019F46156" + } ], "evidence": [ "the exact EV013 silent phase result and final snapshot passed the actual handoff function before any interactive mutation; the scenario checkout was detached at exact clean 11bb2b0 and both ABBB/D4 artifact hashes matched", @@ -9863,7 +10187,10 @@ "evidence_commit": "4c5dbd29d47d35e65eb998976989c8ccc8734dfe", "baseline_commit": "8edd6a91fb58e647a3f1839d02894d27ae35d387", "working_tree_state": "clean after exact commit readback and before this append-only binding record", - "changed_files": ["docs/CONTINUITY.md", "docs/VERIFICATION_LEDGER.json"], + "changed_files": [ + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json" + ], "subject": "Evidence-commit binding for exact InteractiveMain B2 lifecycle", "commands": [ "git add -- docs/CONTINUITY.md docs/VERIFICATION_LEDGER.json; git diff --cached --check; git commit -m \"docs: record interactive B2 lifecycle\"", @@ -9893,7 +10220,10 @@ "baseline_commit": "11bb2b01bf180c61d802717ead6f7a2f72558ca8", "evidence_checkout_commit": "fd7fbdc80cd3ac49d562a54bd5216ed8661b204b", "working_tree_state": "clean detached exact scenario checkout and clean evidence branch before the separately approved physical product-URL run", - "changed_files": ["docs/CONTINUITY.md", "docs/VERIFICATION_LEDGER.json"], + "changed_files": [ + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json" + ], "subject": "Exact installed Player product-URL B2 gate and final restore", "commands": [ "fresh private create-only watcher preflight: 14 assertions, exact clean 11bb2b0 checkout, exact artifact/runtime identities and zero initial target processes", @@ -9902,17 +10232,53 @@ "python -m pytest -q tests/test_continuity_regressions.py" ], "artifacts": [ - {"name": "MLCPlayer_Setup_v0.40.exe", "size_bytes": 55945176, "sha256": "ABBB01C337535DB989E4A36D61D4362604C448323A2D27506F155C84EA48DC34"}, - {"name": "MLCPlayer_InternetVideo_v0.40.exe", "size_bytes": 48909126, "sha256": "D4A4D79995B23228231B8D16091AB85D4805862FDBDDEC415842CE9DFDF9E675"}, - {"name": "MLC Player.exe", "size_bytes": 3133269, "sha256": "82F77C0FBDAC0655B91534E33EED15F60A6D8ADA930854EF96BDB79396BBCA03"}, - {"name": "yt-dlp.exe", "size_bytes": 17840399, "sha256": "66674953FE251B89F4D08C5F0E35E0728679BD67AB3D7D05C0562AF101DD3E7A"}, - {"name": "deno.exe", "size_bytes": 97408288, "sha256": "98F8C2A2D470E4CCB04C935C86FF8050817D877762AEC5EAEEB9E409CCB3B9FD"} + { + "name": "MLCPlayer_Setup_v0.40.exe", + "size_bytes": 55945176, + "sha256": "ABBB01C337535DB989E4A36D61D4362604C448323A2D27506F155C84EA48DC34" + }, + { + "name": "MLCPlayer_InternetVideo_v0.40.exe", + "size_bytes": 48909126, + "sha256": "D4A4D79995B23228231B8D16091AB85D4805862FDBDDEC415842CE9DFDF9E675" + }, + { + "name": "MLC Player.exe", + "size_bytes": 3133269, + "sha256": "82F77C0FBDAC0655B91534E33EED15F60A6D8ADA930854EF96BDB79396BBCA03" + }, + { + "name": "yt-dlp.exe", + "size_bytes": 17840399, + "sha256": "66674953FE251B89F4D08C5F0E35E0728679BD67AB3D7D05C0562AF101DD3E7A" + }, + { + "name": "deno.exe", + "size_bytes": 97408288, + "sha256": "98F8C2A2D470E4CCB04C935C86FF8050817D877762AEC5EAEEB9E409CCB3B9FD" + } ], "private_artifact_digests": [ - {"name": "P-result.json", "size_bytes": 1016, "sha256": "88B38B3B6B88BFF18990315124F89288E46018E61BB36642AF5186A3B8375AFC"}, - {"name": "P-events.jsonl", "size_bytes": 3323, "sha256": "486C0F49F91204BDDB8B2878EE17536A52A139650452429632EEBE76B9A10A68"}, - {"name": "P-00-pre.json", "size_bytes": 32444, "sha256": "73D65386F4B7CCD297E41F77A04E67B05713B78B333D96F2EB9228E8DBD5E2DF"}, - {"name": "P-01-final.json", "size_bytes": 32444, "sha256": "3939A2FCF6A9A513F8CB7BF52729E9C04AF6324B393B2AF4D05810DCB6A20920"} + { + "name": "P-result.json", + "size_bytes": 1016, + "sha256": "88B38B3B6B88BFF18990315124F89288E46018E61BB36642AF5186A3B8375AFC" + }, + { + "name": "P-events.jsonl", + "size_bytes": 3323, + "sha256": "486C0F49F91204BDDB8B2878EE17536A52A139650452429632EEBE76B9A10A68" + }, + { + "name": "P-00-pre.json", + "size_bytes": 32444, + "sha256": "73D65386F4B7CCD297E41F77A04E67B05713B78B333D96F2EB9228E8DBD5E2DF" + }, + { + "name": "P-01-final.json", + "size_bytes": 32444, + "sha256": "3939A2FCF6A9A513F8CB7BF52729E9C04AF6324B393B2AF4D05810DCB6A20920" + } ], "evidence": [ "the exact installed Player PID 37340 started exact installed yt-dlp root PID 54132, which started same-path child PID 41648; both command lines contained the exact requested Blender URL", @@ -9939,7 +10305,10 @@ "evidence_commit": "abdf71c8229eb6f12f9e52b6d982f1073f17641f", "baseline_commit": "fd7fbdc80cd3ac49d562a54bd5216ed8661b204b", "working_tree_state": "clean after exact EV017 commit readback and before this append-only binding record", - "changed_files": ["docs/CONTINUITY.md", "docs/VERIFICATION_LEDGER.json"], + "changed_files": [ + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json" + ], "subject": "Evidence-commit binding for exact product-URL B2 closure", "commands": [ "git add -- docs/CONTINUITY.md docs/VERIFICATION_LEDGER.json; git diff --cached --check; git commit -m \"docs: record product URL B2 closure\"", @@ -9968,10 +10337,13 @@ "commit": "e702dd8c4bd95aedcccd49cfec103a5564be0dca", "baseline_commit": "e702dd8c4bd95aedcccd49cfec103a5564be0dca", "working_tree_state": "clean local master equal to origin/master after protected PR merge and before release mutations", - "changed_files": ["docs/CONTINUITY.md", "docs/VERIFICATION_LEDGER.json"], - "subject": "v0.40 protected merge and exact-master hosted release-candidate CI", - "commands": [ - "GitHub PR #67 exact head 0a4f34cf601961a2d9c8fe361241ed82562f5f1c with required test run 33154530018", + "changed_files": [ + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json" + ], + "subject": "v0.40 protected merge and exact-master hosted release-candidate CI", + "commands": [ + "GitHub PR #67 exact head 0a4f34cf601961a2d9c8fe361241ed82562f5f1c with required test run 33154530018", "separately authorized merge-commit operation and local clean master fast-forward to origin/master", "gh workflow run ci.yml --ref master -f expected_sha=e702dd8c4bd95aedcccd49cfec103a5564be0dca", "single readback of GitHub Actions run 33154878976 after user reported completion" @@ -9998,7 +10370,10 @@ "commit": "e702dd8c4bd95aedcccd49cfec103a5564be0dca", "baseline_commit": "e702dd8c4bd95aedcccd49cfec103a5564be0dca", "working_tree_state": "clean master equal to origin/master throughout tag, draft parity, publication and public-download readback; release record branch created clean afterward", - "changed_files": ["docs/CONTINUITY.md", "docs/VERIFICATION_LEDGER.json"], + "changed_files": [ + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json" + ], "subject": "v0.40 annotated tag, 87-asset public latest release and updater-visible byte acceptance", "commands": [ "python packaging/fetch_sources.py; 83-source presence readback; git tag -a v0.40 -m \"MLC Player v0.40\"; python packaging/prepublish.py --tag v0.40", @@ -10008,13 +10383,33 @@ "product app.updater.fetch_latest_release and select_update_asset path; streamed public main/add-on downloads, final-host allowlist, SHA-256 and embedded-key Ed25519 verification" ], "artifacts": [ - {"name": "MLCPlayer_Setup_v0.40.exe", "size_bytes": 55945176, "sha256": "ABBB01C337535DB989E4A36D61D4362604C448323A2D27506F155C84EA48DC34"}, - {"name": "MLCPlayer_Setup_v0.40.exe.sig", "size_bytes": 88, "sha256": "7D003B8CF705549111D54BDC8543D5E1B8D6C9651A6AA8A590020531E519CC3E"}, - {"name": "MLCPlayer_InternetVideo_v0.40.exe", "size_bytes": 48909126, "sha256": "D4A4D79995B23228231B8D16091AB85D4805862FDBDDEC415842CE9DFDF9E675"}, - {"name": "MLCPlayer_InternetVideo_v0.40.exe.sig", "size_bytes": 88, "sha256": "516CA07EE797C06C8B0902CE16EB85E98BCC2713D277FFDC689D6C4D15E2DF73"} + { + "name": "MLCPlayer_Setup_v0.40.exe", + "size_bytes": 55945176, + "sha256": "ABBB01C337535DB989E4A36D61D4362604C448323A2D27506F155C84EA48DC34" + }, + { + "name": "MLCPlayer_Setup_v0.40.exe.sig", + "size_bytes": 88, + "sha256": "7D003B8CF705549111D54BDC8543D5E1B8D6C9651A6AA8A590020531E519CC3E" + }, + { + "name": "MLCPlayer_InternetVideo_v0.40.exe", + "size_bytes": 48909126, + "sha256": "D4A4D79995B23228231B8D16091AB85D4805862FDBDDEC415842CE9DFDF9E675" + }, + { + "name": "MLCPlayer_InternetVideo_v0.40.exe.sig", + "size_bytes": 88, + "sha256": "516CA07EE797C06C8B0902CE16EB85E98BCC2713D277FFDC689D6C4D15E2DF73" + } ], "private_artifact_digests": [ - {"name": "verify_public_v040.py", "size_bytes": 2614, "sha256": "86D257F3B13B2003CC09CB66D5C33BD305CCD7CC12F53DEFE2ED181F2DA15B99"} + { + "name": "verify_public_v040.py", + "size_bytes": 2614, + "sha256": "86D257F3B13B2003CC09CB66D5C33BD305CCD7CC12F53DEFE2ED181F2DA15B99" + } ], "evidence": [ "product and packaging build inputs under app, packaging, bin, requirements.lock and pyproject.toml were unchanged from accepted source-build commit 1dafe1f through merge e702dd8c, so the exact accepted ABBB/D4 bytes were reused without another costly build or non-transferable B2 rerun", @@ -10041,7 +10436,10 @@ "evidence_commit": "23eca2a5e4983bd80d0d194e26d53dc850d4418a", "baseline_commit": "e702dd8c4bd95aedcccd49cfec103a5564be0dca", "working_tree_state": "clean after exact EV019-EV020 release-record commit readback and before this append-only binding record", - "changed_files": ["docs/CONTINUITY.md", "docs/VERIFICATION_LEDGER.json"], + "changed_files": [ + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json" + ], "subject": "Evidence-commit binding for v0.40 public release record", "commands": [ "git add -- docs/CONTINUITY.md docs/VERIFICATION_LEDGER.json; git diff --cached --check; git commit -m \"docs: record v0.40 release\"", @@ -12694,7 +13092,11 @@ "commit": "95246d19ebac75e651e4970f5ebbc7473cc0f418", "baseline_commit": "95246d19ebac75e651e4970f5ebbc7473cc0f418", "working_tree_state": "Documentation-only audit handoff; product source unchanged", - "changed_files": ["docs/ENGINEERING_AUDIT.md", "docs/CONTINUITY.md", "docs/VERIFICATION_LEDGER.json"], + "changed_files": [ + "docs/ENGINEERING_AUDIT.md", + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json" + ], "subject": "Open PiP and M3U behavior gaps documented for implementation handoff", "commands": [ "git status --short --branch", @@ -13123,7 +13525,9 @@ "commit": "95246d19ebac75e651e4970f5ebbc7473cc0f418", "baseline_commit": "95246d19ebac75e651e4970f5ebbc7473cc0f418", "working_tree_state": "Inherited dirty source preserved; new title lifecycle regressions added before this fix", - "changed_files": ["tests/test_title_bar_lifecycle_regressions.py"], + "changed_files": [ + "tests/test_title_bar_lifecycle_regressions.py" + ], "subject": "Title visibility regressions expose inactive-owner resurrection and stale fullscreen state during Esc restoration", "commands": [ "MLC_CI=1 QT_QPA_PLATFORM=offscreen python -m pytest -q tests/test_title_bar_lifecycle_regressions.py -k 'delayed_title_raise or visible_owner_can_restore'", @@ -13151,8 +13555,12 @@ "baseline_commit": "95246d19ebac75e651e4970f5ebbc7473cc0f418", "working_tree_state": "Inherited dirty source plus uncommitted title visibility guard and fullscreen restoration ordering correction", "changed_files": [ - "app/player.py", "app/video_frame.py", "tests/test_title_bar_lifecycle_regressions.py", - "docs/ARCHITECTURE_INVENTORY.json", "docs/CONTINUITY.md", "docs/VERIFICATION_LEDGER.json" + "app/player.py", + "app/video_frame.py", + "tests/test_title_bar_lifecycle_regressions.py", + "docs/ARCHITECTURE_INVENTORY.json", + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json" ], "subject": "Title restoration lifecycle correction passes targeted tests; screenshot-specific native acceptance remains open", "commands": [ @@ -13181,7 +13589,14 @@ "commit": "95246d19ebac75e651e4970f5ebbc7473cc0f418", "baseline_commit": "95246d19ebac75e651e4970f5ebbc7473cc0f418", "working_tree_state": "User-approved single native run against the inherited dirty canonical tree with EV-20260905-011 title lifecycle correction; no product source edits during this run", - "changed_files": ["app/player.py", "app/video_frame.py", "tests/test_title_bar_lifecycle_regressions.py", "docs/ARCHITECTURE_INVENTORY.json", "docs/CONTINUITY.md", "docs/VERIFICATION_LEDGER.json"], + "changed_files": [ + "app/player.py", + "app/video_frame.py", + "tests/test_title_bar_lifecycle_regressions.py", + "docs/ARCHITECTURE_INVENTORY.json", + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json" + ], "subject": "Six real-video fullscreen to Esc transitions preserve title HWND ownership and geometry without reproducing the detached strip", "commands": [ "python -m py_compile /probe.py", @@ -13189,28 +13604,61 @@ "Get-CimInstance Win32_Process | Where-Object { $_.Name -match 'python|MLC' } | Select-Object ProcessId,ParentProcessId,Name" ], "runtime": { - "python": "3.14.3, MSC v.1944 64 bit AMD64", "qt": "6.10.0", - "windows": "Windows-11-10.0.26200-SP0", "screen": [2560, 1440], "device_pixel_ratio": 1.0, - "audio_output": "null", "mpv_dll_sha256": "de80329f5c019ba2ee48184b5dc1e1d0c2ee9eeba3f1fb7959f20b4b0f684f4e" + "python": "3.14.3, MSC v.1944 64 bit AMD64", + "qt": "6.10.0", + "windows": "Windows-11-10.0.26200-SP0", + "screen": [ + 2560, + 1440 + ], + "device_pixel_ratio": 1.0, + "audio_output": "null", + "mpv_dll_sha256": "de80329f5c019ba2ee48184b5dc1e1d0c2ee9eeba3f1fb7959f20b4b0f684f4e" }, "source_raw_sha256": { "app/player.py": "d77c951046065ee2ab10299e43ec0d203866c8c1170a828bd14dbea6d5a423b7", "app/video_frame.py": "6766ff8b281ab5dede1acc4abc3a991cb7d93559804f80d3f0daba060d7dec0d" }, "media_fingerprint": { - "safe_name": "local-avatar-video.mkv", "size": 31195865553, "hash_mode": "sampled-v1", + "safe_name": "local-avatar-video.mkv", + "size": 31195865553, + "hash_mode": "sampled-v1", "sampled_sha256": "c7956e128179eba69d8ae765c31b3efa63c57a732ecb1edc99925f14a44186a2", "before_after_equal": true }, "private_artifact_digests": [ - {"name": "fullscreen-title-probe.py", "sha256": "afb82f41c8a13e52412c0ae07c97d780ff62526587115b79327e14a76de2a134"}, - {"name": "fullscreen-title-provenance.json", "sha256": "3062d2337122bb88ad4c8e9061edcac97db4275d27ca49e4f7a3fb004efa6313"}, - {"name": "fullscreen-title-parent.json", "sha256": "bde1b6ac41908ca83d757be558700fd5c8aa4fcadb0e7d55fb1394c593594d92"}, - {"name": "fullscreen-title-stdout.log", "sha256": "b212b285801a07da4b9197661623c3530b7125df9ea90e024024887bb0688b05"}, - {"name": "fullscreen-title-stderr.log", "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, - {"name": "fullscreen-title-normal1.png", "sha256": "3bc563f34ed71dfa54822a1649e1d9b3251b296cb4b37763a05f94ac14f2283b"}, - {"name": "fullscreen-title-max1.png", "sha256": "060bab05572997af020695ef8afaade06bb7f84149b1b0b81bae5f7645d9144c"}, - {"name": "fullscreen-title-final-window.png", "sha256": "9cb21ab04e7c1706f40d6bbdb6a01e2a3acffa1fea3240f63d96ae7291091b9b"} + { + "name": "fullscreen-title-probe.py", + "sha256": "afb82f41c8a13e52412c0ae07c97d780ff62526587115b79327e14a76de2a134" + }, + { + "name": "fullscreen-title-provenance.json", + "sha256": "3062d2337122bb88ad4c8e9061edcac97db4275d27ca49e4f7a3fb004efa6313" + }, + { + "name": "fullscreen-title-parent.json", + "sha256": "bde1b6ac41908ca83d757be558700fd5c8aa4fcadb0e7d55fb1394c593594d92" + }, + { + "name": "fullscreen-title-stdout.log", + "sha256": "b212b285801a07da4b9197661623c3530b7125df9ea90e024024887bb0688b05" + }, + { + "name": "fullscreen-title-stderr.log", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "name": "fullscreen-title-normal1.png", + "sha256": "3bc563f34ed71dfa54822a1649e1d9b3251b296cb4b37763a05f94ac14f2283b" + }, + { + "name": "fullscreen-title-max1.png", + "sha256": "060bab05572997af020695ef8afaade06bb7f84149b1b0b81bae5f7645d9144c" + }, + { + "name": "fullscreen-title-final-window.png", + "sha256": "9cb21ab04e7c1706f40d6bbdb6a01e2a3acffa1fea3240f63d96ae7291091b9b" + } ], "evidence": [ "One child, PID 24780, started only after assignment to the existing Windows Job Object guard. User settings/log directories were isolated; real libmpv and a local video were used with ao=null. All app source raw-byte hashes were identical before and after.", @@ -13688,7 +14136,10 @@ "python": "3.14.3, MSC v.1944 64 bit AMD64", "qt": "6.10.0", "windows": "Windows-11-10.0.26200-SP0", - "screen": [2560, 1440], + "screen": [ + 2560, + 1440 + ], "device_pixel_ratio": 1.0, "audio_output": "null", "mpv_dll_sha256": "de80329f5c019ba2ee48184b5dc1e1d0c2ee9eeba3f1fb7959f20b4b0f684f4e" @@ -13704,12 +14155,30 @@ "before_after_equal": true }, "private_artifact_digests": [ - {"name": "second-esc-probe.py", "sha256": "a3c5d805ee36cafbf6723fc68a6b1cca4f1481093662281c5025c0fc57d275b8"}, - {"name": "second-esc-provenance.json", "sha256": "d943de5c1e66c10f151e656338d48dc4ab35411df80175a3ea8fa7fd5c40c109"}, - {"name": "second-esc-parent.json", "sha256": "37ed02573eca2d183c3cc0e8854bf5fe65262e104bdf69a08d25d58a4b017300"}, - {"name": "second-esc-stdout.log", "sha256": "012e7d4bcda34d6201a5a7ec59710e8a24520b82e5f83909eb2ee8101e861b80"}, - {"name": "second-esc-stderr.log", "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, - {"name": "second-esc-final-window.png", "sha256": "4b6797bdcacdc05889e7ea9cd90fe9e46030c0384adebf61cda5f8c7618c9382"} + { + "name": "second-esc-probe.py", + "sha256": "a3c5d805ee36cafbf6723fc68a6b1cca4f1481093662281c5025c0fc57d275b8" + }, + { + "name": "second-esc-provenance.json", + "sha256": "d943de5c1e66c10f151e656338d48dc4ab35411df80175a3ea8fa7fd5c40c109" + }, + { + "name": "second-esc-parent.json", + "sha256": "37ed02573eca2d183c3cc0e8854bf5fe65262e104bdf69a08d25d58a4b017300" + }, + { + "name": "second-esc-stdout.log", + "sha256": "012e7d4bcda34d6201a5a7ec59710e8a24520b82e5f83909eb2ee8101e861b80" + }, + { + "name": "second-esc-stderr.log", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "name": "second-esc-final-window.png", + "sha256": "4b6797bdcacdc05889e7ea9cd90fe9e46030c0384adebf61cda5f8c7618c9382" + } ], "evidence": [ "Video readiness and advancement passed. Three normal and three maximized fullscreen-to-Esc cycles again preserved stable owner/title/video HWND identities, correct title ownership and matching title geometry.", @@ -13801,7 +14270,10 @@ "python": "3.14.3, MSC v.1944 64 bit AMD64", "qt": "6.10.0", "windows": "Windows-11-10.0.26200-SP0", - "screen": [2560, 1440], + "screen": [ + 2560, + 1440 + ], "device_pixel_ratio": 1.0, "audio_output": "null", "mpv_dll_sha256": "de80329f5c019ba2ee48184b5dc1e1d0c2ee9eeba3f1fb7959f20b4b0f684f4e" @@ -13817,12 +14289,30 @@ "before_after_equal": true }, "private_artifact_digests": [ - {"name": "second-esc-fixed-probe.py", "sha256": "a3c5d805ee36cafbf6723fc68a6b1cca4f1481093662281c5025c0fc57d275b8"}, - {"name": "second-esc-fixed-provenance.json", "sha256": "01ff04c49a140556b2dc9f071b1382895f37dfdeda48b0c2ff1d2cf91e800fa3"}, - {"name": "second-esc-fixed-parent.json", "sha256": "e960bde8581288c764af150f0dc413978302bc2d23dc354bd05376cca4aec3ef"}, - {"name": "second-esc-fixed-stdout.log", "sha256": "62971899fafafbbc373cbebecd9b427ce5b926fd69fc8af6db27ba368b13705f"}, - {"name": "second-esc-fixed-stderr.log", "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, - {"name": "second-esc-fixed-final-window.png", "sha256": "5272476dca041802b5d0a80a70a72706a77494b44dae97b4bf110d4eccccb90c"} + { + "name": "second-esc-fixed-probe.py", + "sha256": "a3c5d805ee36cafbf6723fc68a6b1cca4f1481093662281c5025c0fc57d275b8" + }, + { + "name": "second-esc-fixed-provenance.json", + "sha256": "01ff04c49a140556b2dc9f071b1382895f37dfdeda48b0c2ff1d2cf91e800fa3" + }, + { + "name": "second-esc-fixed-parent.json", + "sha256": "e960bde8581288c764af150f0dc413978302bc2d23dc354bd05376cca4aec3ef" + }, + { + "name": "second-esc-fixed-stdout.log", + "sha256": "62971899fafafbbc373cbebecd9b427ce5b926fd69fc8af6db27ba368b13705f" + }, + { + "name": "second-esc-fixed-stderr.log", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "name": "second-esc-fixed-final-window.png", + "sha256": "5272476dca041802b5d0a80a70a72706a77494b44dae97b4bf110d4eccccb90c" + } ], "evidence": [ "The exact same probe SHA and media ran against the EV-20260908-021 player source. Video readiness, advancement and all six fullscreen-to-Esc title ownership/geometry cycles passed.", @@ -13911,7 +14401,10 @@ "python": "3.14.3, MSC v.1944 64 bit AMD64", "qt": "6.10.0", "windows": "Windows-11-10.0.26200-SP0", - "screen": [2560, 1440], + "screen": [ + 2560, + 1440 + ], "device_pixel_ratio": 1.0, "audio_output": "null", "mpv_dll_sha256": "de80329f5c019ba2ee48184b5dc1e1d0c2ee9eeba3f1fb7959f20b4b0f684f4e" @@ -13927,12 +14420,30 @@ "before_after_equal": true }, "private_artifact_digests": [ - {"name": "second-esc-final-probe.py", "sha256": "a3c5d805ee36cafbf6723fc68a6b1cca4f1481093662281c5025c0fc57d275b8"}, - {"name": "second-esc-final-provenance.json", "sha256": "15ff3eb6e6ab97f722b306c23cb392ee969c5bd72a7e259cb1fec7da7fbd6861"}, - {"name": "second-esc-final-parent.json", "sha256": "a87e0e2f2b35b3153d0c562b44e75b37cc4caa00a0ed548342b2eb9ce810715e"}, - {"name": "second-esc-final-stdout.log", "sha256": "057063a36d2c21cdf217ee6aa3fb4f79e6f3e94d3adab54c71f79c76b7617947"}, - {"name": "second-esc-final-stderr.log", "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, - {"name": "second-esc-final-window.png", "sha256": "dbc6a117b67cce2272a7f71a98f4e36a95e20c7d0b1e6d460331bdcff12df409"} + { + "name": "second-esc-final-probe.py", + "sha256": "a3c5d805ee36cafbf6723fc68a6b1cca4f1481093662281c5025c0fc57d275b8" + }, + { + "name": "second-esc-final-provenance.json", + "sha256": "15ff3eb6e6ab97f722b306c23cb392ee969c5bd72a7e259cb1fec7da7fbd6861" + }, + { + "name": "second-esc-final-parent.json", + "sha256": "a87e0e2f2b35b3153d0c562b44e75b37cc4caa00a0ed548342b2eb9ce810715e" + }, + { + "name": "second-esc-final-stdout.log", + "sha256": "057063a36d2c21cdf217ee6aa3fb4f79e6f3e94d3adab54c71f79c76b7617947" + }, + { + "name": "second-esc-final-stderr.log", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "name": "second-esc-final-window.png", + "sha256": "dbc6a117b67cce2272a7f71a98f4e36a95e20c7d0b1e6d460331bdcff12df409" + } ], "evidence": [ "The unchanged probe and media ran against the exact EV-20260908-023 player hash. Video readiness, advancement, three normal and three maximized fullscreen-to-Esc cycles all passed.", @@ -14030,7 +14541,10 @@ "python": "3.14.3, MSC v.1944 64 bit AMD64", "qt": "6.10.0", "windows": "Windows-11-10.0.26200-SP0", - "screen": [2560, 1440], + "screen": [ + 2560, + 1440 + ], "device_pixel_ratio": 1.0, "audio_output": "null", "mpv_dll_sha256": "de80329f5c019ba2ee48184b5dc1e1d0c2ee9eeba3f1fb7959f20b4b0f684f4e" @@ -14046,13 +14560,34 @@ "before_after_equal": true }, "private_artifact_digests": [ - {"name": "second-esc-pass-probe.py", "sha256": "a3c5d805ee36cafbf6723fc68a6b1cca4f1481093662281c5025c0fc57d275b8"}, - {"name": "second-esc-pass-provenance.json", "sha256": "90c54c16cee3a94b23775c04bac7dc50dbf7d6ba56de7cc06d13270872d9bb81"}, - {"name": "second-esc-pass-parent.json", "sha256": "e610d979853d813d27817e6a1db574b0f0f63bc6ecb20e4be5acc750ca5e0b68"}, - {"name": "second-esc-pass-stdout.log", "sha256": "dd311e36118676be1254f8733e15f9f4a23cce87b549fb8d97f0453b3b23b6ef"}, - {"name": "second-esc-pass-stderr.log", "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, - {"name": "second-esc-pass-final-window.png", "sha256": "2de08890b5595632b784d5ca6143f27f3eb33095028d29d19ecc696b1d3b2306"}, - {"name": "second-esc-pass-failure.png", "sha256": "a9d643314fff858e945f06a7157a30fd7800593f0c985a084f0f8ce54a3ffab1"} + { + "name": "second-esc-pass-probe.py", + "sha256": "a3c5d805ee36cafbf6723fc68a6b1cca4f1481093662281c5025c0fc57d275b8" + }, + { + "name": "second-esc-pass-provenance.json", + "sha256": "90c54c16cee3a94b23775c04bac7dc50dbf7d6ba56de7cc06d13270872d9bb81" + }, + { + "name": "second-esc-pass-parent.json", + "sha256": "e610d979853d813d27817e6a1db574b0f0f63bc6ecb20e4be5acc750ca5e0b68" + }, + { + "name": "second-esc-pass-stdout.log", + "sha256": "dd311e36118676be1254f8733e15f9f4a23cce87b549fb8d97f0453b3b23b6ef" + }, + { + "name": "second-esc-pass-stderr.log", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "name": "second-esc-pass-final-window.png", + "sha256": "2de08890b5595632b784d5ca6143f27f3eb33095028d29d19ecc696b1d3b2306" + }, + { + "name": "second-esc-pass-failure.png", + "sha256": "a9d643314fff858e945f06a7157a30fd7800593f0c985a084f0f8ce54a3ffab1" + } ], "evidence": [ "The unchanged probe and media ran against the exact EV-20260908-025 player hash. Video readiness, advancement, three normal and three maximized fullscreen-to-Esc cycles all passed.", @@ -14151,7 +14686,10 @@ "python": "3.14.3, MSC v.1944 64 bit AMD64", "qt": "6.10.0", "windows": "Windows-11-10.0.26200-SP0", - "screen": [2560, 1440], + "screen": [ + 2560, + 1440 + ], "device_pixel_ratio": 1.0, "audio_output": "null", "mpv_dll_sha256": "de80329f5c019ba2ee48184b5dc1e1d0c2ee9eeba3f1fb7959f20b4b0f684f4e" @@ -14167,13 +14705,34 @@ "before_after_equal": true }, "private_artifact_digests": [ - {"name": "second-esc-stable-probe.py", "sha256": "a3c5d805ee36cafbf6723fc68a6b1cca4f1481093662281c5025c0fc57d275b8"}, - {"name": "second-esc-stable-provenance.json", "sha256": "9e2a09755789ef929acb8189454bc73eb9576b206ca6a23b813adb5f8f498c5f"}, - {"name": "second-esc-stable-parent.json", "sha256": "30efbeb0b7bb59c807e939e0bbf6fee5b783bf286d0f7e9e5222089a536f07af"}, - {"name": "second-esc-stable-stdout.log", "sha256": "80090b7ca5e4dbe39ba59d978d112d4f77e8e2ceb9305afa165d264dab832bfa"}, - {"name": "second-esc-stable-stderr.log", "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, - {"name": "second-esc-stable-final-window.png", "sha256": "2549a561f74ff589c132dbbeb3d9e1206c1bf66339206f40a81600a3a06db0e9"}, - {"name": "second-esc-stable-failure.png", "sha256": "4a474c1e9cd9ae9945de91ab7a0792602226dff1011b1882d515bbf85fa9aeea"} + { + "name": "second-esc-stable-probe.py", + "sha256": "a3c5d805ee36cafbf6723fc68a6b1cca4f1481093662281c5025c0fc57d275b8" + }, + { + "name": "second-esc-stable-provenance.json", + "sha256": "9e2a09755789ef929acb8189454bc73eb9576b206ca6a23b813adb5f8f498c5f" + }, + { + "name": "second-esc-stable-parent.json", + "sha256": "30efbeb0b7bb59c807e939e0bbf6fee5b783bf286d0f7e9e5222089a536f07af" + }, + { + "name": "second-esc-stable-stdout.log", + "sha256": "80090b7ca5e4dbe39ba59d978d112d4f77e8e2ceb9305afa165d264dab832bfa" + }, + { + "name": "second-esc-stable-stderr.log", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "name": "second-esc-stable-final-window.png", + "sha256": "2549a561f74ff589c132dbbeb3d9e1206c1bf66339206f40a81600a3a06db0e9" + }, + { + "name": "second-esc-stable-failure.png", + "sha256": "4a474c1e9cd9ae9945de91ab7a0792602226dff1011b1882d515bbf85fa9aeea" + } ], "evidence": [ "The preflight matched exact baseline 95246d1, player hash 4be50d2, unchanged probe hash a3c5d80, unchanged media size and zero matching processes. Video readiness, advancement, three normal and three maximized fullscreen-to-Esc cycles all passed.", @@ -14263,7 +14822,10 @@ "python": "3.14.3, MSC v.1944 64 bit AMD64", "qt": "6.10.0", "windows": "Windows-11-10.0.26200-SP0", - "screen": [2560, 1440], + "screen": [ + 2560, + 1440 + ], "device_pixel_ratio": 1.0, "audio_output": "null", "mpv_dll_sha256": "de80329f5c019ba2ee48184b5dc1e1d0c2ee9eeba3f1fb7959f20b4b0f684f4e" @@ -14279,13 +14841,34 @@ "before_after_equal": true }, "private_artifact_digests": [ - {"name": "second-esc-one-second-probe.py", "sha256": "a3c5d805ee36cafbf6723fc68a6b1cca4f1481093662281c5025c0fc57d275b8"}, - {"name": "second-esc-one-second-provenance.json", "sha256": "606e3b4e701e718b3abbb2675b37e1d15450cdc2a2836ed46bacee45ad0e9a1e"}, - {"name": "second-esc-one-second-parent.json", "sha256": "c8cbc085614f7fd5eeecec5324b737da2001832df7ad95383c4767ac729911ab"}, - {"name": "second-esc-one-second-stdout.log", "sha256": "31e8a87db6305ec5d542c3a2921d22de3fba36acb833e9df385b1056f2838457"}, - {"name": "second-esc-one-second-stderr.log", "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, - {"name": "second-esc-one-second-final-window.png", "sha256": "813338c988934204e474a363137b82da42c07b22ed59df4794acaa540e7e21da"}, - {"name": "second-esc-one-second-failure.png", "sha256": "6f1c13fc9388533ecdfd3a908cb29f4b7016dadb3ed6d02cd179cfa82b9c914e"} + { + "name": "second-esc-one-second-probe.py", + "sha256": "a3c5d805ee36cafbf6723fc68a6b1cca4f1481093662281c5025c0fc57d275b8" + }, + { + "name": "second-esc-one-second-provenance.json", + "sha256": "606e3b4e701e718b3abbb2675b37e1d15450cdc2a2836ed46bacee45ad0e9a1e" + }, + { + "name": "second-esc-one-second-parent.json", + "sha256": "c8cbc085614f7fd5eeecec5324b737da2001832df7ad95383c4767ac729911ab" + }, + { + "name": "second-esc-one-second-stdout.log", + "sha256": "31e8a87db6305ec5d542c3a2921d22de3fba36acb833e9df385b1056f2838457" + }, + { + "name": "second-esc-one-second-stderr.log", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "name": "second-esc-one-second-final-window.png", + "sha256": "813338c988934204e474a363137b82da42c07b22ed59df4794acaa540e7e21da" + }, + { + "name": "second-esc-one-second-failure.png", + "sha256": "6f1c13fc9388533ecdfd3a908cb29f4b7016dadb3ed6d02cd179cfa82b9c914e" + } ], "evidence": [ "The preflight matched exact baseline 95246d1, player hash 495c47f, unchanged probe hash a3c5d80, unchanged media identity and zero matching processes. Video readiness, advancement, three normal and three maximized fullscreen-to-Esc cycles passed.", @@ -14390,7 +14973,10 @@ "python": "3.14.3, MSC v.1944 64 bit AMD64", "qt": "6.10.0", "windows": "Windows-11-10.0.26200-SP0", - "screen": [2560, 1440], + "screen": [ + 2560, + 1440 + ], "device_pixel_ratio": 1.0, "audio_output": "null", "mpv_dll_sha256": "de80329f5c019ba2ee48184b5dc1e1d0c2ee9eeba3f1fb7959f20b4b0f684f4e" @@ -14407,13 +14993,34 @@ "before_after_equal": true }, "private_artifact_digests": [ - {"name": "second-esc-native-placement-probe.py", "sha256": "a3c5d805ee36cafbf6723fc68a6b1cca4f1481093662281c5025c0fc57d275b8"}, - {"name": "second-esc-native-placement-provenance.json", "sha256": "637be5d666064cf205f2616017f2e0c5242aabd90291d727424533a3f92840b6"}, - {"name": "second-esc-native-placement-parent.json", "sha256": "bbf29a2fe2cbff5986efbbf2b4d79ea687215cbffba328cefce78d33171baf5e"}, - {"name": "second-esc-native-placement-stdout.log", "sha256": "6a79c4fb791e311ff65a6bcec2dd4ffb80675a7e375d1c47a2c114e4cf6b7f1e"}, - {"name": "second-esc-native-placement-stderr.log", "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, - {"name": "second-esc-native-placement-final-window.png", "sha256": "31c9541e8b2cb15a16ef5466e959487e0ff9e53e022be27ab1ac5098c6f16bee"}, - {"name": "second-esc-native-placement-failure.png", "sha256": "5010b2c1517893c4055be324738ffbf578e9e5251e9308d334f5ec2f1f41a138"} + { + "name": "second-esc-native-placement-probe.py", + "sha256": "a3c5d805ee36cafbf6723fc68a6b1cca4f1481093662281c5025c0fc57d275b8" + }, + { + "name": "second-esc-native-placement-provenance.json", + "sha256": "637be5d666064cf205f2616017f2e0c5242aabd90291d727424533a3f92840b6" + }, + { + "name": "second-esc-native-placement-parent.json", + "sha256": "bbf29a2fe2cbff5986efbbf2b4d79ea687215cbffba328cefce78d33171baf5e" + }, + { + "name": "second-esc-native-placement-stdout.log", + "sha256": "6a79c4fb791e311ff65a6bcec2dd4ffb80675a7e375d1c47a2c114e4cf6b7f1e" + }, + { + "name": "second-esc-native-placement-stderr.log", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "name": "second-esc-native-placement-final-window.png", + "sha256": "31c9541e8b2cb15a16ef5466e959487e0ff9e53e022be27ab1ac5098c6f16bee" + }, + { + "name": "second-esc-native-placement-failure.png", + "sha256": "5010b2c1517893c4055be324738ffbf578e9e5251e9308d334f5ec2f1f41a138" + } ], "evidence": [ "The preflight matched exact baseline 95246d1, player hash 8cb3044, window_modes hash 641bed2, unchanged probe hash a3c5d80, unchanged media identity and zero matching processes. Video readiness, advancement and all six fullscreen-to-Esc cycles passed.", @@ -14518,7 +15125,10 @@ "python": "3.14.3, MSC v.1944 64 bit AMD64", "qt": "6.10.0", "windows": "Windows-11-10.0.26200-SP0", - "screen": [2560, 1440], + "screen": [ + 2560, + 1440 + ], "device_pixel_ratio": 1.0, "audio_output": "null", "mpv_dll_sha256": "de80329f5c019ba2ee48184b5dc1e1d0c2ee9eeba3f1fb7959f20b4b0f684f4e" @@ -14535,12 +15145,30 @@ "before_after_equal": true }, "private_artifact_digests": [ - {"name": "second-esc-show-noactivate-probe.py", "sha256": "a3c5d805ee36cafbf6723fc68a6b1cca4f1481093662281c5025c0fc57d275b8"}, - {"name": "second-esc-show-noactivate-provenance.json", "sha256": "7cf94c060f8d32ecec306084f03b5c5b738a9a160fd4a772e7539be982d736e4"}, - {"name": "second-esc-show-noactivate-parent.json", "sha256": "df0e337b1f583ef23c1f8cae232a612bc7f6715e61b452e20d00dff6cc0ab41a"}, - {"name": "second-esc-show-noactivate-stdout.log", "sha256": "37d0b45b65e93a906877470c4b3ce54e00a996b4e7af17170f98e55e12ec4339"}, - {"name": "second-esc-show-noactivate-stderr.log", "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, - {"name": "second-esc-show-noactivate-final-window.png", "sha256": "bf98d3392f11e3df9dca682c75967753a9d564f1023223aa5818b99f999db0af"} + { + "name": "second-esc-show-noactivate-probe.py", + "sha256": "a3c5d805ee36cafbf6723fc68a6b1cca4f1481093662281c5025c0fc57d275b8" + }, + { + "name": "second-esc-show-noactivate-provenance.json", + "sha256": "7cf94c060f8d32ecec306084f03b5c5b738a9a160fd4a772e7539be982d736e4" + }, + { + "name": "second-esc-show-noactivate-parent.json", + "sha256": "df0e337b1f583ef23c1f8cae232a612bc7f6715e61b452e20d00dff6cc0ab41a" + }, + { + "name": "second-esc-show-noactivate-stdout.log", + "sha256": "37d0b45b65e93a906877470c4b3ce54e00a996b4e7af17170f98e55e12ec4339" + }, + { + "name": "second-esc-show-noactivate-stderr.log", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "name": "second-esc-show-noactivate-final-window.png", + "sha256": "bf98d3392f11e3df9dca682c75967753a9d564f1023223aa5818b99f999db0af" + } ], "evidence": [ "The probe reported exit 0, failures=0 and MARK_DONE. Owner Qt/native geometry became exact centered [800,396,1760,996], title became [800,396,1760,436], owner/title/video HWNDs were unchanged, playback advanced, stderr was empty, shutdown was stop-to-terminate and Job/process leakage was 0.", @@ -14596,7 +15224,10 @@ "app/player.py": "e8d12b71723a0a5b64db96ebc41851f0e828e248faf4cfce194febd0c6792e52" }, "private_artifact_digests": [ - {"name": "second-esc-overlay-contract-probe.py", "sha256": "2a64541ce75624f7d7441eafe348c03e6c10ab77c5e282c909653bd7554ebb33"} + { + "name": "second-esc-overlay-contract-probe.py", + "sha256": "2a64541ce75624f7d7441eafe348c03e6c10ab77c5e282c909653bd7554ebb33" + } ], "evidence": [ "The product regression attached an overlay geometry spy to the native-success callback. It failed red because owner placement did not trigger any explicit overlay synchronization.", @@ -14641,7 +15272,10 @@ "python": "3.14.3, MSC v.1944 64 bit AMD64", "qt": "6.10.0", "windows": "Windows-11-10.0.26200-SP0", - "screen": [2560, 1440], + "screen": [ + 2560, + 1440 + ], "device_pixel_ratio": 1.0, "audio_output": "null", "mpv_dll_sha256": "de80329f5c019ba2ee48184b5dc1e1d0c2ee9eeba3f1fb7959f20b4b0f684f4e" @@ -14658,12 +15292,30 @@ "before_after_equal": true }, "private_artifact_digests": [ - {"name": "second-esc-overlay-executed-probe.py", "sha256": "2a64541ce75624f7d7441eafe348c03e6c10ab77c5e282c909653bd7554ebb33"}, - {"name": "second-esc-overlay-provenance.json", "sha256": "a0ff5c45c4437a39b37ebbcb08ac1dba8207678c8c7317e9a9c7fbebbee1029c"}, - {"name": "second-esc-overlay-parent.json", "sha256": "25e36c0c621f2fa36378c305a0fe437b35159c4c05b30852747a740ce2e1b9d2"}, - {"name": "second-esc-overlay-stdout.log", "sha256": "834ea664681e6a64a5f29ac4a1fd22978e5983703bc821c53503d861676cd3e5"}, - {"name": "second-esc-overlay-stderr.log", "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, - {"name": "second-esc-overlay-final-window.png", "sha256": "198b65608905c1cbbe16d41eed7a72177992e523bb9dc0aef813660112d37630"} + { + "name": "second-esc-overlay-executed-probe.py", + "sha256": "2a64541ce75624f7d7441eafe348c03e6c10ab77c5e282c909653bd7554ebb33" + }, + { + "name": "second-esc-overlay-provenance.json", + "sha256": "a0ff5c45c4437a39b37ebbcb08ac1dba8207678c8c7317e9a9c7fbebbee1029c" + }, + { + "name": "second-esc-overlay-parent.json", + "sha256": "25e36c0c621f2fa36378c305a0fe437b35159c4c05b30852747a740ce2e1b9d2" + }, + { + "name": "second-esc-overlay-stdout.log", + "sha256": "834ea664681e6a64a5f29ac4a1fd22978e5983703bc821c53503d861676cd3e5" + }, + { + "name": "second-esc-overlay-stderr.log", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "name": "second-esc-overlay-final-window.png", + "sha256": "198b65608905c1cbbe16d41eed7a72177992e523bb9dc0aef813660112d37630" + } ], "evidence": [ "The child and parent reported exit 0, RESULT failures=0, one MARK_DONE, empty stderr, unchanged source and media, stop-to-terminate shutdown and zero Job/process leakage.", @@ -14713,7 +15365,10 @@ "docs/ARCHITECTURE_INVENTORY.json": "fd5f78e4122c216d8e7494407ed650d45e315b15137a8f07747b00e080e44f01" }, "private_artifact_digests": [ - {"name": "second-esc-final-contract-probe.py", "sha256": "ee70446c4d4def4dd2aa2b1f733fee7b1d95992ab6d32168360d298aaae63ebe"} + { + "name": "second-esc-final-contract-probe.py", + "sha256": "ee70446c4d4def4dd2aa2b1f733fee7b1d95992ab6d32168360d298aaae63ebe" + } ], "evidence": [ "The final PASS condition requires unchanged owner, title, video and overlay handles; all cached/current handles valid; title and video rooted in the owner; centered 960 by 600 native/Qt owner; matching title and overlay rectangles; and visible title/overlay.", @@ -14758,7 +15413,10 @@ "python": "3.14.3, MSC v.1944 64 bit AMD64", "qt": "6.10.0", "windows": "Windows-11-10.0.26200-SP0", - "screen": [2560, 1440], + "screen": [ + 2560, + 1440 + ], "device_pixel_ratio": 1.0, "audio_output": "null", "mpv_dll_sha256": "de80329f5c019ba2ee48184b5dc1e1d0c2ee9eeba3f1fb7959f20b4b0f684f4e" @@ -14775,12 +15433,30 @@ "before_after_equal": true }, "private_artifact_digests": [ - {"name": "second-esc-final-contract-probe.py", "sha256": "ee70446c4d4def4dd2aa2b1f733fee7b1d95992ab6d32168360d298aaae63ebe"}, - {"name": "second-esc-final-contract-provenance.json", "sha256": "51ee78da5947631afdf8de71c7297b272ecb60e7f970f7fcb541c520227957d3"}, - {"name": "second-esc-final-contract-parent.json", "sha256": "8d5c51d043f1f7ea3a08b0be425d0c3a744ec2ecda9e869ec9fe91eaf0d192f6"}, - {"name": "second-esc-final-contract-stdout.log", "sha256": "a5c259026c2ca0b9471f8493b24d46985c7effa786eeea47f2e30d49b0010b40"}, - {"name": "second-esc-final-contract-stderr.log", "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, - {"name": "second-esc-final-contract-final-window.png", "sha256": "1b2e637b1ee28bdabee4e6365a8905e81cf2267736505c0735c64e2d7e766996"} + { + "name": "second-esc-final-contract-probe.py", + "sha256": "ee70446c4d4def4dd2aa2b1f733fee7b1d95992ab6d32168360d298aaae63ebe" + }, + { + "name": "second-esc-final-contract-provenance.json", + "sha256": "51ee78da5947631afdf8de71c7297b272ecb60e7f970f7fcb541c520227957d3" + }, + { + "name": "second-esc-final-contract-parent.json", + "sha256": "8d5c51d043f1f7ea3a08b0be425d0c3a744ec2ecda9e869ec9fe91eaf0d192f6" + }, + { + "name": "second-esc-final-contract-stdout.log", + "sha256": "a5c259026c2ca0b9471f8493b24d46985c7effa786eeea47f2e30d49b0010b40" + }, + { + "name": "second-esc-final-contract-stderr.log", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "name": "second-esc-final-contract-final-window.png", + "sha256": "1b2e637b1ee28bdabee4e6365a8905e81cf2267736505c0735c64e2d7e766996" + } ], "evidence": [ "The final probe produced exit 0, problems empty, stderr 0 bytes, exactly one RESULT failures=0 and MARK_DONE, no CHECK FAIL or PROBE_ERROR, stop-to-terminate shutdown and zero Job/post-run process leakage.", @@ -14830,6 +15506,2291 @@ "No push, PR, build, installation, tag, merge or release occurred." ], "next_action": "Commit this documentation-only evidence binding, then obtain separate user approval before any push, PR, build or other gated action." + }, + { + "id": "EV-20260908-040", + "recorded_at_utc": "2026-09-08T15:11:29Z", + "proof_layer": "hosted_ci", + "result": "passed", + "commit": "29e56541fa31799f3524063da50b1720df1ec350", + "baseline_commit": "29e56541fa31799f3524063da50b1720df1ec350", + "working_tree_state": "clean master equal to origin/master before this append-only hosted evidence record", + "changed_files": [ + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json" + ], + "subject": "Protected-master merge and exact-SHA manual hosted release-candidate test", + "commands": [ + "gh pr merge 74 --merge", + "git fetch origin master; git checkout master; git merge --ff-only origin/master", + "gh workflow run ci.yml --ref master -f expected_sha=29e56541fa31799f3524063da50b1720df1ec350", + "gh run watch 34236625658 --exit-status", + "gh run view 34236625658 --json plus exact test-count log readback", + "git status and HEAD/origin/master/tree equality readback" + ], + "run_url": "https://github.com/IzzmooPro/MLCPlayer/actions/runs/34236625658", + "evidence": [ + "PR 74 merged through a merge commit after its required test check passed. The resulting merge commit is 29e56541fa31799f3524063da50b1720df1ec350 with parents 260b2a90c3e3d98a3853b65237b2f0742a548ddd and 48a9e021f24d151cde62761e7adb18a94c97623c.", + "The merge tree exactly matched the approved PR head tree. Local master was fast-forwarded and read back equal to origin/master with ahead/behind 0/0.", + "Manual CI run 34236625658 used event workflow_dispatch, branch refs/heads/master and exact head SHA 29e56541fa31799f3524063da50b1720df1ec350. Its fail-closed exact-master dispatch check passed.", + "The hosted test job completed success with 5276 passed and 22 skipped in 121.52 seconds; dependency, environment, compile and translation checks also passed." + ], + "summary": "The v0.41 release candidate is merged on exact protected master and has a successful exact-SHA manual hosted test.", + "limitations": [ + "Hosted CI does not prove local source build, installed artifact, physical Windows behavior or release publication.", + "The ledger append-only step is skipped by design on workflow_dispatch while exact-SHA, locked environment, compile, translation and full test gates run.", + "No build, installation, tag or release occurred in this evidence layer." + ], + "next_action": "Run the release build only under its separate user approval on clean exact master 29e56541fa31799f3524063da50b1720df1ec350." + }, + { + "id": "EV-20260908-041", + "recorded_at_utc": "2026-09-08T15:11:29Z", + "proof_layer": "source_build", + "result": "failed", + "commit": "29e56541fa31799f3524063da50b1720df1ec350", + "baseline_commit": "29e56541fa31799f3524063da50b1720df1ec350", + "working_tree_state": "clean master equal to origin/master; first user-approved v0.41 build attempt stopped before deleting or producing output", + "changed_files": [ + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json" + ], + "subject": "First v0.41 build attempt fails closed on a mismatched global Pillow version before output mutation", + "commands": [ + "python packaging/verify_build.py --pre", + "cmd /c packaging\\build_release.bat", + "global Python and Pillow readback, output inventory, process inventory and git status" + ], + "evidence": [ + "The approved build command exited 1 because the global Python environment contained Pillow 12.2.0 while requirements-lock.txt requires Pillow 12.3.0.", + "The deterministic dependency gate ran before STEP 1 and before output cleanup, printed FAILED and explicitly reported that no output was produced.", + "Existing historical v0.39/v0.40 installer artifacts remained present; no v0.41 artifact or matching build, Player, PyInstaller or Inno process remained. Git stayed clean and exact. No automatic retry occurred." + ], + "summary": "The first build attempt safely rejected a non-locked shared Python environment before touching release output.", + "limitations": [ + "This FAILED record does not describe product source or installer behavior; it is an environment precondition failure.", + "No artifact from this attempt may be treated as v0.41 build evidence." + ], + "next_action": "With separate install approval create an isolated build environment from requirements-lock.txt, verify pins, then obtain a new separate build approval before retrying." + }, + { + "id": "EV-20260908-042", + "recorded_at_utc": "2026-09-08T15:11:29Z", + "proof_layer": "source_build", + "result": "passed", + "commit": "29e56541fa31799f3524063da50b1720df1ec350", + "baseline_commit": "29e56541fa31799f3524063da50b1720df1ec350", + "working_tree_state": "clean exact master before and after the separately approved isolated v0.41 release build; build outputs are ignored artifacts", + "changed_files": [ + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json" + ], + "subject": "Isolated exact-master v0.41 main and Internet Video source build", + "commands": [ + "Create a repository-external Python 3.14.3 virtual environment under separate install approval and install requirements-lock.txt", + "python packaging/verify_dependencies.py requirements-lock.txt", + "cmd /d /c packaging\\build_release.bat under a separate retry approval with the isolated Scripts directory first on PATH", + "python packaging/verify_build.py --post", + "python packaging/verify_build.py --final installer_output\\MLCPlayer_Setup_v0.41.exe", + "Exact artifact size/SHA-256, PE metadata, Authenticode, embedded-key Ed25519, dist inventory, process and Git readback", + "Independent read-only adversarial source-build artifact review" + ], + "artifacts": [ + { + "name": "MLCPlayer_Setup_v0.41.exe", + "size_bytes": 55847997, + "sha256": "FB5BF5BD259614AE1EE1C5467D4548CB406D931CEC6327DB08013AC8FEA202D1" + }, + { + "name": "MLCPlayer_Setup_v0.41.exe.sig", + "size_bytes": 88, + "sha256": "5C7C246871D66E70EAE27C2B2BA14A325AC202024A6EEB7712045F1EA8F97B18" + }, + { + "name": "MLCPlayer_InternetVideo_v0.41.exe", + "size_bytes": 48823078, + "sha256": "5994C2ADB77AE9120EB6648EE3A561C2DBA1AFF8CF7202277D842278C5F57161" + }, + { + "name": "MLCPlayer_InternetVideo_v0.41.exe.sig", + "size_bytes": 88, + "sha256": "39038BA8F3D5E65857325E3198A0811CAFB822FB9446342BC36EEE373A809A2B" + } + ], + "evidence": [ + "The isolated environment matched every requirements-lock.txt pin, including Pillow 12.3.0, without changing the global Pillow 12.2.0 installation.", + "The single retry completed dependency, runtime, publishability, two Inno compile-only, exact-output cleanup, translation, PyInstaller, dist, both Inno builds, detached signing and final steps with exit 0 and DONE.", + "The main and add-on EXEs report FileVersion and ProductVersion 0.41.0.0. Both detached 88-byte signatures independently verified True against the corresponding exact EXE SHA-256 using the public key embedded in the committed source.", + "Both installers are Authenticode NotSigned as declared by the active policy; detached Ed25519 is not represented as Authenticode.", + "The dist tree contains 91 files and 188468501 bytes. Required package entries were complete, forbidden entries absent, runtime identities matched their manifest and no matching build process remained.", + "Independent adversarial review returned GO with P0=0, P1=0 and P2=0 for the bounded source_build claim." + ], + "summary": "Exact merge commit 29e56541 produced fresh v0.41 main and Internet Video installers with four bound artifact identities and valid detached Ed25519 signatures.", + "limitations": [ + "This is source_build evidence only; installer UI, install, upgrade, rollback, uninstall, installed payload, native playback, SmartScreen, tag and release remain unproven.", + "The add-on payload was not independently extracted; its fresh exact Inno build and static payload contract passed, while installed behavior remains a physical B2 responsibility.", + "Both EXEs are Authenticode NotSigned; SignPath and SmartScreen acceptance are not part of this result." + ], + "next_action": "Commit this two-document hosted/build evidence package under separate approval, then request separate approval for exact-artifact B2 physical acceptance before any tag." + }, + { + "id": "EV-20260908-043", + "recorded_at_utc": "2026-09-08T15:16:14Z", + "proof_layer": "deterministic", + "result": "passed", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "evidence_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "29e56541fa31799f3524063da50b1720df1ec350", + "working_tree_state": "clean after the user-approved exact two-document hosted/build evidence commit readback and before this append-only binding record", + "changed_files": [ + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json" + ], + "subject": "Evidence-commit binding for the v0.41 protected-master hosted test and source-build records", + "commands": [ + "git add -- docs/CONTINUITY.md docs/VERIFICATION_LEDGER.json", + "git diff --cached --check", + "git commit -m docs: record v0.41 hosted and build evidence", + "git rev-parse HEAD plus exact parent, tree, subject, stat, status and origin/master divergence readback" + ], + "evidence": [ + "The approved two-document commit completed with exact SHA 8c826775969ddf3a0042531399ebbf5ce02a1ce0 and parent 29e56541fa31799f3524063da50b1720df1ec350.", + "The commit contains only docs/CONTINUITY.md and docs/VERIFICATION_LEDGER.json with the append-only EV-20260908-040 through EV-20260908-042 records; its subject is docs: record v0.41 hosted and build evidence.", + "Pre-commit ledger JSON, diff and continuity checks passed; post-commit readback showed a clean task branch one commit ahead of origin/master." + ], + "summary": "The exact hosted merge-commit test, safe failed pre-output build and successful isolated v0.41 source build are bound to evidence commit 8c826775969ddf3a0042531399ebbf5ce02a1ce0.", + "limitations": [ + "This binding record does not add installed-artifact, physical acceptance, tag or release evidence.", + "No push, PR, merge, installation, tag or release followed this commit readback." + ], + "next_action": "Commit this documentation-only binding record, then obtain separate approval before push/PR or exact-artifact B2 physical acceptance." + }, + { + "id": "EV-20260908-044", + "recorded_at_utc": "2026-09-08T16:05:00Z", + "proof_layer": "deterministic", + "result": "blocked", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "working_tree_state": "dirty implementation tree for optional MLC-owned uninstall cleanup; prior EV-20260908-043 remains an uncommitted append-only binding record", + "changed_files": [ + "README.md", + "README.tr.md", + "docs/CONTINUITY.md", + "docs/PACKAGING_PLAN.md", + "docs/VERIFICATION_LEDGER.json", + "packaging/MLCPlayer.iss", + "tests/test_readme_user_guidance_regressions.py", + "tests/test_uninstall_cleanup_regressions.py" + ], + "subject": "Optional MLC-owned user-data cleanup source review", + "commands": [ + "python -m pytest -q tests\\test_uninstall_cleanup_regressions.py tests\\test_readme_user_guidance_regressions.py tests\\test_installer_branding_regressions.py tests\\test_installer_language_regressions.py tests\\test_installer_process_safety_regressions.py tests\\test_inno_compile_preflight_regressions.py tests\\test_internet_video_addon_regressions.py tests\\test_continuity_regressions.py", + "python -m pytest -q tests\\test_settings_isolation_regressions.py tests\\test_subtitle_settings_regressions.py tests\\test_opensubtitles_contract_regressions.py tests\\test_log_retention_link_regressions.py tests\\test_thumbnail_cache_location_regressions.py tests\\test_playlist_thumbnail_regressions.py", + "git diff --check", + "Independent read-only adversarial source review" + ], + "evidence": [ + "The two bounded deterministic groups passed 88 and 171 passed / 1 skipped, and diff whitespace checks passed.", + "The implementation keeps user-data choices unchecked by default, preserves them in silent uninstall, scopes current paths, rejects reparse points and stops main removal if the selected add-on cannot be safely removed.", + "Independent adversarial review returned NO-GO with P1 findings: alternate UAC credentials can target the elevated account rather than the interactive product account; old Credential Manager targets are not enumerated; legacy shared cache entries have no safe ownership proof." + ], + "summary": "Source tests pass, but the requested complete user-data cleanup is blocked by three unclosed P1 ownership boundaries.", + "limitations": [ + "No Inno compilation, uninstall run, installed-artifact check or native cleanup acceptance was performed for this dirty source tree.", + "The installed v0.41 uninstallers predate this source change and do not provide evidence for it.", + "The current implementation must not be represented as removing every historical MLC residue." + ], + "next_action": "Design and implement a fail-closed interactive-user cleanup path, bounded credential-target enumeration, and a proof-backed migration record for legacy cache entries; then rerun independent review before requesting build or commit approval." + }, + { + "id": "EV-20260909-001", + "recorded_at_utc": "2026-09-09T00:05:00Z", + "proof_layer": "deterministic", + "result": "failed", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "working_tree_state": "dirty optional-uninstall-cleanup implementation; rejected user-session launcher prototype was removed from the working tree after adversarial review", + "changed_files": [ + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json" + ], + "subject": "Rejected in-process user-session uninstall launcher prototype", + "commands": [ + "python -m pytest -q targeted uninstall, subtitle, installer and continuity regressions", + "python -m compileall -q main.py app", + "ISCC.exe /Qp packaging\\MLCPlayer.iss", + "Independent read-only adversarial source review" + ], + "evidence": [ + "The prototype source tests reached 190 passed / 1 skipped after one test-only ordering correction; the prototype was then removed rather than retained.", + "Adversarial review found two P0 issues: the installed launcher would keep MLC Player.exe locked while unins000.exe removes it, and parent reparse points could redirect user-data deletion outside MLC ownership. Four additional P1 issues covered missing update-temp cleanup, raw-uninstaller bypass, failed-cleanup recovery and add-on state.", + "ISCC was invoked without the required separate build authorization. It exited 0 and overwrote ignored installer_output/MLCPlayer_Setup_v0.41.exe with 55853942 bytes and SHA-256 CE8E73466FBCC7EA1505C3BB37AB53EB37DF3A51BCD77DD01EC79A2E7C4A42E5; no installation, signature, acceptance or artifact claim followed. The existing detached .sig is not bound to that overwritten EXE." + ], + "summary": "The in-process launcher approach is rejected; its unapproved compiler output is quarantined as invalid evidence.", + "limitations": [ + "No build or installed-artifact PASS is created by the accidental compiler invocation.", + "The ignored output was retained without deletion to preserve the user's existing artifact directory.", + "EV-20260908-044 remains the current source-cleanup NO-GO." + ], + "next_action": "Implement a helper that runs outside {app}, survives raw uninstall without locking product files, rejects every reparse-point path component, and leaves a retryable cleanup result; obtain build approval only after independent source review returns GO." + }, + { + "id": "EV-20260909-002", + "recorded_at_utc": "2026-09-09T00:06:00Z", + "proof_layer": "deterministic", + "result": "passed", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "working_tree_state": "dirty implementation tree; this evidence validates the listed uncommitted source and does not claim that baseline commit contains it", + "changed_files": [ + "MLCUserCleanup.spec", + "README.md", + "README.tr.md", + "app/user_cleanup_helper.py", + "docs/CONTINUITY.md", + "docs/PACKAGING_PLAN.md", + "docs/VERIFICATION_LEDGER.json", + "main.py", + "packaging/MLCPlayer.iss", + "packaging/build_release.bat", + "tests/test_inno_compile_preflight_regressions.py", + "tests/test_readme_user_guidance_regressions.py", + "tests/test_uninstall_cleanup_regressions.py", + "tests/test_user_cleanup_helper_regressions.py", + "translations/mlcplayer_de.ts", + "translations/mlcplayer_en.ts", + "translations/mlcplayer_es.ts", + "translations/mlcplayer_fr.ts", + "translations/mlcplayer_it.ts", + "translations/mlcplayer_pt_BR.ts", + "translations/mlcplayer_ru.ts", + "user_cleanup_main.py" + ], + "subject": "User-session uninstall cleanup helper deterministic source gate", + "commands": [ + "python -m pytest -q", + "python -m pytest -q tests/test_uninstall_cleanup_regressions.py tests/test_user_cleanup_helper_regressions.py tests/test_installer_process_safety_regressions.py tests/test_continuity_regressions.py", + "python packaging/verify_inno.py all --iscc C:\\Program Files\\Inno Setup 7\\ISCC.exe", + "python -m py_compile app/user_cleanup_helper.py user_cleanup_main.py main.py", + "git diff --check", + "Independent read-only adversarial source review" + ], + "evidence": [ + "The full deterministic suite passed 5305 tests with 11 skipped and zero failures; after the final recovery-path change the affected uninstall/process/continuity group passed 34 tests.", + "Both main and Internet Video Inno scripts passed compile-only preflight after the final source change.", + "The helper is outside the installed player process, creates a hidden triggerless on-demand InteractiveToken task, binds registrations to a machine InstallId plus exact SID ProfileList path, and keeps request/result IPC inside each user's fixed LocalAppData product directory.", + "Selected cleanup enumerates both OpenSubtitles credential namespaces and bounds registry, INI, logs, current thumbnail cache and branded update-temp deletion; reparse points and unexpected entries fail closed.", + "Multiple loaded registered users are processed independently. Unloaded or invalid registrations are reported as failures, and task-start or result-timeout failures retain recovery components instead of claiming success.", + "Independent adversarial review returned GO with P0=0 and P1=0 after four finding-and-repair rounds." + ], + "summary": "The optional registered-user uninstall cleanup source contract passed deterministic, compile-only and independent adversarial gates.", + "limitations": [ + "This is deterministic source evidence only; it does not prove PyInstaller output, installed files, Task Scheduler execution, different-UAC behavior or physical uninstall cleanup.", + "Shared legacy thumbnail locations remain intentionally untouched because MLC ownership cannot be proven.", + "The dirty working tree is not yet committed." + ], + "next_action": "Run the user-approved final source build once, verify exact artifact identities and helper inclusion, then hand the installer to the user for physical PC acceptance." + }, + { + "id": "EV-20260909-003", + "recorded_at_utc": "2026-09-09T00:07:00Z", + "proof_layer": "source_build", + "result": "passed", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "working_tree_state": "dirty exact source tree from EV-20260909-002; generated build and installer outputs are ignored artifacts", + "changed_files": [ + "MLCUserCleanup.spec", + "README.md", + "README.tr.md", + "app/user_cleanup_helper.py", + "docs/CONTINUITY.md", + "docs/PACKAGING_PLAN.md", + "docs/VERIFICATION_LEDGER.json", + "main.py", + "packaging/MLCPlayer.iss", + "packaging/build_release.bat", + "tests/test_inno_compile_preflight_regressions.py", + "tests/test_readme_user_guidance_regressions.py", + "tests/test_uninstall_cleanup_regressions.py", + "tests/test_user_cleanup_helper_regressions.py", + "translations/mlcplayer_de.ts", + "translations/mlcplayer_en.ts", + "translations/mlcplayer_es.ts", + "translations/mlcplayer_fr.ts", + "translations/mlcplayer_it.ts", + "translations/mlcplayer_pt_BR.ts", + "translations/mlcplayer_ru.ts", + "user_cleanup_main.py" + ], + "subject": "Final v0.41 source build with registered-user uninstall cleanup helper", + "commands": [ + "isolated Scripts/python.exe packaging/verify_dependencies.py requirements-lock.txt", + "MLC_ALLOW_REPUBLISH=1 cmd /d /c packaging\\build_release.bat with the verified isolated Scripts directory first on PATH", + "isolated Scripts/python.exe packaging/verify_build.py --post", + "isolated Scripts/python.exe packaging/verify_build.py --final installer_output\\MLCPlayer_Setup_v0.41.exe", + "SHA-256, PE version, Authenticode, embedded-public-key Ed25519, process, status and diff readback" + ], + "artifacts": [ + { + "name": "MLCUserCleanup.exe", + "size_bytes": 8652102, + "sha256": "5C4874F12F30A197C8AC808CBEF704BCD635855D3287E056E31642B5E0B14C21" + }, + { + "name": "MLCPlayer_Setup_v0.41.exe", + "size_bytes": 64302568, + "sha256": "97D2BEC0F812B29BF73DFC66D693196F64A5C08F7F243DDA1928307BC520CCC8" + }, + { + "name": "MLCPlayer_Setup_v0.41.exe.sig", + "size_bytes": 88, + "sha256": "91022F63CDB6E2A9ECFF1B535E35A6D45F1523FDA87DDE1C6AD71E1C9F63696E" + }, + { + "name": "MLCPlayer_InternetVideo_v0.41.exe", + "size_bytes": 48823078, + "sha256": "5994C2ADB77AE9120EB6648EE3A561C2DBA1AFF8CF7202277D842278C5F57161" + }, + { + "name": "MLCPlayer_InternetVideo_v0.41.exe.sig", + "size_bytes": 88, + "sha256": "39038BA8F3D5E65857325E3198A0811CAFB822FB9446342BC36EEE373A809A2B" + } + ], + "evidence": [ + "The single user-approved build run completed all eight stages with exit 0 and DONE in the locked Python 3.14.3 environment.", + "The dedicated helper was freshly produced before the main onedir build; the successful main Inno compile consumed the required dist/MLCUserCleanup.exe source entry.", + "Post-build dist and final installer verification passed. The main setup is 64302568 bytes with SHA-256 97D2BEC0...20CCC8; the unchanged add-on was freshly rebuilt at 48823078 bytes with SHA-256 5994C2AD...F57161.", + "Both detached 88-byte Ed25519 signatures independently verified True against the exact installer digests and the public key embedded in source.", + "Main Player and both installers report v0.41/0.41.0.0 as applicable. Authenticode is NotSigned under the documented policy, and no MLC, PyInstaller or ISCC process remained." + ], + "summary": "The exact dirty source tree produced a fresh v0.41 main installer containing the new cleanup helper plus the Internet Video installer and bound signatures.", + "limitations": [ + "This source_build PASS is not installation, Task Scheduler, different-UAC, multi-user, uninstall, installed-residue or human UI acceptance evidence.", + "The helper is intentionally hidden and has no standalone PE version resource; its installer identity is bound by exact path and SHA-256.", + "All EXEs remain Authenticode NotSigned; detached Ed25519 does not change SmartScreen reputation." + ], + "next_action": "The user installs the exact main setup, opens MLC Player once to register the cleanup task, then performs the visible uninstall choice/residue acceptance on the PC." + }, + { + "id": "EV-20260909-004", + "recorded_at_utc": "2026-09-09T15:51:00Z", + "proof_layer": "installed_artifact", + "result": "failed", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "working_tree_state": "dirty source tree after EV-20260909-003; user ran the exact built main installer before the two runtime fixes", + "changed_files": [ + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json", + "packaging/MLCPlayer.iss", + "tests/test_uninstall_cleanup_regressions.py" + ], + "subject": "First physical install and uninstall UI attempt for the cleanup-enabled v0.41 artifact", + "commands": [ + "User launched MLCPlayer_Setup_v0.41.exe interactively", + "User launched the installed MLC Player uninstaller interactively", + "Read-only process, Program Files payload and HKLM uninstall registration inventory" + ], + "artifacts": [ + { + "name": "MLCPlayer_Setup_v0.41.exe", + "size_bytes": 64302568, + "sha256": "97D2BEC0F812B29BF73DFC66D693196F64A5C08F7F243DDA1928307BC520CCC8" + } + ], + "evidence": [ + "The user supplied the visible installer error: Runtime error (at 119:398): Type Mismatch while the wizard showed Windows integration.", + "Source review tied the failure to passing a Unicode String expression to Inno Pascal GetSHA256OfString, whose runtime contract accepts AnsiString.", + "The user then supplied the visible uninstaller error: Runtime error (at 126:5350): Cannot focus a disabled or invisible window.", + "The failed install had not created a valid cleanup identity; with no add-on, the dialog disabled both data checkboxes but assigned focus to the disabled settings checkbox.", + "Readback found no running setup/uninstaller process, while the main v0.41 payload, MLCUserCleanup.exe, unins000.exe and HKLM uninstall entry remained under C:/Program Files/MLC Player." + ], + "summary": "The first physical artifact attempt exposed one Unicode hash runtime mismatch and one no-identity focus runtime error; installation and uninstall acceptance failed.", + "limitations": [ + "No cleanup success or residue absence is claimed.", + "The installed files are the failed pre-fix artifact and must not be used to validate the corrected source.", + "No automated install, uninstall or UI control was performed by the agent." + ], + "next_action": "Replace the ANSI hash calls with the Unicode Inno API, focus an enabled Continue button when all cleanup checkboxes are unavailable, and obtain deterministic plus adversarial GO before rebuilding." + }, + { + "id": "EV-20260909-005", + "recorded_at_utc": "2026-09-09T15:52:00Z", + "proof_layer": "deterministic", + "result": "passed", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "working_tree_state": "dirty corrected source tree; changes are not contained in the baseline commit", + "changed_files": [ + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json", + "packaging/MLCPlayer.iss", + "tests/test_uninstall_cleanup_regressions.py" + ], + "subject": "Runtime Type Mismatch and disabled-focus repair source gate", + "commands": [ + "python -m pytest -q tests/test_uninstall_cleanup_regressions.py tests/test_user_cleanup_helper_regressions.py tests/test_installer_process_safety_regressions.py tests/test_inno_compile_preflight_regressions.py tests/test_continuity_regressions.py", + "python packaging/verify_inno.py all --iscc C:\\Program Files\\Inno Setup 7\\ISCC.exe", + "python -m pytest -q", + "git diff --check", + "Independent read-only adversarial source review" + ], + "evidence": [ + "Both runtime String hash sites now use GetSHA256OfUnicodeString; a regression test rejects any remaining GetSHA256OfString call in installer code.", + "The cleanup dialog chooses only a visible and enabled add-on checkbox, then an enabled settings checkbox, and otherwise the enabled Continue button.", + "The affected group passed 44 tests, both Inno scripts passed compile-only, and the complete deterministic suite passed 5307 tests with 11 skipped and zero failures.", + "Independent adversarial review returned GO with P0=0 and P1=0 for both runtime repairs." + ], + "summary": "Both physical runtime failures have bounded source fixes with full deterministic and independent adversarial GO.", + "limitations": [ + "Compile-only and deterministic PASS do not prove the corrected installer runtime.", + "The previous installer artifact remains invalid for acceptance and must be replaced by a fresh build.", + "The existing installed payload remains until the user repairs or removes it with the corrected artifact." + ], + "next_action": "Build one fresh corrected v0.41 installer, verify its exact identity, then have the user run it over the remaining installation and repeat the visible uninstall flow." + }, + { + "id": "EV-20260909-006", + "recorded_at_utc": "2026-09-09T15:54:00Z", + "proof_layer": "source_build", + "result": "passed", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "working_tree_state": "dirty corrected source tree from EV-20260909-005; generated outputs are ignored artifacts", + "changed_files": [ + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json", + "packaging/MLCPlayer.iss", + "tests/test_uninstall_cleanup_regressions.py" + ], + "subject": "Corrected v0.41 source build after physical installer and uninstaller runtime failures", + "commands": [ + "MLC_ALLOW_REPUBLISH=1 cmd /d /c packaging\\build_release.bat with the locked Python 3.14.3 Scripts directory first on PATH", + "isolated Scripts/python.exe packaging/verify_build.py --post", + "isolated Scripts/python.exe packaging/verify_build.py --final installer_output\\MLCPlayer_Setup_v0.41.exe", + "Exact SHA-256, embedded-public-key Ed25519, process, status and diff readback" + ], + "artifacts": [ + { + "name": "MLCUserCleanup.exe", + "size_bytes": 8649513, + "sha256": "A18852C06A430EAEEB09266C0183E9412F32441B140CB7B0373A013AAFB05245" + }, + { + "name": "MLCPlayer_Setup_v0.41.exe", + "size_bytes": 64314973, + "sha256": "EC975EAA504404D9BD7BE2081C6E85154E8C0A2280C5133809F0639915649C58" + }, + { + "name": "MLCPlayer_Setup_v0.41.exe.sig", + "size_bytes": 88, + "sha256": "337BA64D763088FA23656474C7124DFF47CADDC38701A2277C4A5FA231D04DED" + }, + { + "name": "MLCPlayer_InternetVideo_v0.41.exe", + "size_bytes": 48823078, + "sha256": "5994C2ADB77AE9120EB6648EE3A561C2DBA1AFF8CF7202277D842278C5F57161" + }, + { + "name": "MLCPlayer_InternetVideo_v0.41.exe.sig", + "size_bytes": 88, + "sha256": "39038BA8F3D5E65857325E3198A0811CAFB822FB9446342BC36EEE373A809A2B" + } + ], + "evidence": [ + "The single corrected build completed all eight stages with exit 0 and DONE in the locked environment.", + "Post-build dist and final main-installer verification passed; the corrected main setup hash differs from the failed pre-fix artifact.", + "Both detached signatures independently verified True against the exact current installer bytes and the public key embedded in source.", + "No setup, uninstaller, MLC Player, PyInstaller or ISCC process remained after build." + ], + "summary": "A fresh corrected v0.41 main setup replaced the two-runtime-error artifact and passed bounded source-build verification.", + "limitations": [ + "This build has not yet been installed or physically uninstalled; runtime acceptance remains with the user.", + "The old failed installation is still present and must be repaired in place with this exact corrected setup before testing uninstall.", + "Source changes remain uncommitted." + ], + "next_action": "Run the exact EC975EAA corrected setup over the remaining v0.41 installation, open Player once, then repeat the visible uninstall choices and report the result." + }, + { + "id": "EV-20260909-007", + "recorded_at_utc": "2026-09-09T16:07:00Z", + "proof_layer": "installed_artifact", + "result": "failed", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "working_tree_state": "dirty source tree from EV-20260909-006; user ran the exact EC975EAA artifact before the DateSeparator and TimeSeparator repair", + "changed_files": [ + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json", + "packaging/MLCPlayer.iss", + "tests/test_uninstall_cleanup_regressions.py" + ], + "subject": "Second physical install attempt with EC975EAA exposed the remaining post-install Char mismatch", + "commands": [ + "User launched the exact EC975EAA MLCPlayer_Setup_v0.41.exe interactively", + "Read-only current artifact SHA-256 and source call-site comparison", + "Official Inno Pascal support-function signature review" + ], + "artifacts": [ + { + "name": "MLCPlayer_Setup_v0.41.exe", + "size_bytes": 64314973, + "sha256": "EC975EAA504404D9BD7BE2081C6E85154E8C0A2280C5133809F0639915649C58" + } + ], + "corrects": [ + { + "entry_id": "EV-20260909-004", + "field": "evidence[1]", + "incorrect": "Source review tied the failure to passing a Unicode String expression to Inno Pascal GetSHA256OfString, whose runtime contract accepts AnsiString.", + "corrected": "The same Type Mismatch remained after both sites used GetSHA256OfUnicodeString; the verified cause was GetDateTimeString receiving empty String values for its Char DateSeparator and TimeSeparator parameters." + }, + { + "entry_id": "EV-20260909-005", + "field": "summary", + "incorrect": "Both physical runtime failures have bounded source fixes with full deterministic and independent adversarial GO.", + "corrected": "The disabled-focus failure had a bounded source fix, but the first Type Mismatch diagnosis was incomplete and EC975EAA failed the repeated physical install." + }, + { + "entry_id": "EV-20260909-006", + "field": "summary", + "incorrect": "A fresh corrected v0.41 main setup replaced the two-runtime-error artifact and passed bounded source-build verification.", + "corrected": "EC975EAA passed source-build verification but retained a post-install GetDateTimeString Char mismatch and failed physical installation." + }, + { + "entry_id": "EV-20260909-006", + "field": "limitations[0]", + "incorrect": "This build has not yet been installed or physically uninstalled; runtime acceptance remains with the user.", + "corrected": "The user physically ran this build and it failed at post-install with Runtime error at 119:398 Type Mismatch; uninstall was not retested." + } + ], + "evidence": [ + "The user supplied a second visible installer error: Runtime error (at 119:398): Type Mismatch while the wizard showed Windows integration.", + "Readback proved the only matching setup under Desktop and Downloads was EC975EAA, so this was not reuse of the older 97D2 artifact.", + "Official Inno documentation defines GetDateTimeString DateSeparator and TimeSeparator as Char; both dynamic identity seeds passed empty String literals.", + "The EC975EAA artifact is invalid for physical acceptance and was replaced only after the root cause was corrected." + ], + "summary": "The EC975EAA setup failed physical installation because empty String separators were passed to Char parameters in the post-install identity seed.", + "limitations": [ + "The uninstall cleanup flow was not reached in this attempt.", + "No automated install, uninstall or UI control was performed by the agent.", + "No cleanup success or residue absence is claimed." + ], + "next_action": "Use valid Char separators at both GetDateTimeString sites, add an exact regression, obtain deterministic and adversarial GO, then rebuild." + }, + { + "id": "EV-20260909-008", + "recorded_at_utc": "2026-09-09T16:08:00Z", + "proof_layer": "deterministic", + "result": "passed", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "working_tree_state": "dirty source tree with uncommitted DateSeparator and TimeSeparator repair", + "changed_files": [ + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json", + "packaging/MLCPlayer.iss", + "tests/test_uninstall_cleanup_regressions.py" + ], + "subject": "GetDateTimeString Char contract repair deterministic and adversarial gate", + "commands": [ + "python -m pytest -q tests/test_uninstall_cleanup_regressions.py tests/test_inno_compile_preflight_regressions.py", + "python packaging/verify_inno.py all", + "python -m pytest -q tests/test_uninstall_cleanup_regressions.py tests/test_user_cleanup_helper_regressions.py tests/test_inno_compile_preflight_regressions.py tests/test_release_artifact_selection_regressions.py tests/test_release_signature_regressions.py tests/test_release_guard_regressions.py tests/test_release_documentation_regressions.py tests/test_verify_release_ref_regressions.py", + "git diff --check", + "Independent read-only adversarial source review" + ], + "evidence": [ + "Both GetDateTimeString identity-seed calls now pass '-' and ':' Char values instead of empty String values.", + "The exact regression rejects the runtime-unsafe empty separator form and requires both corrected calls.", + "The narrow group passed 19 tests, the packaging and release impact family passed 340 tests, and both Inno scripts passed compile-only.", + "Independent adversarial review returned GO with P0=0 and P1=0 for the root cause and repair." + ], + "summary": "The remaining post-install Type Mismatch has a source-level fix locked by targeted tests, Inno compilation and independent review.", + "limitations": [ + "Deterministic and compile-only evidence does not prove setup runtime.", + "A new artifact and physical install are required.", + "Source changes remain uncommitted." + ], + "next_action": "Build and verify a fresh v0.41 installer from this exact dirty source tree." + }, + { + "id": "EV-20260909-009", + "recorded_at_utc": "2026-09-09T16:09:00Z", + "proof_layer": "source_build", + "result": "passed", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "working_tree_state": "dirty corrected source tree from EV-20260909-008; generated outputs are ignored artifacts", + "changed_files": [ + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json", + "packaging/MLCPlayer.iss", + "tests/test_uninstall_cleanup_regressions.py" + ], + "subject": "Fresh v0.41 source build after GetDateTimeString Char repair", + "commands": [ + "Rejected build attempt: build environment root was placed on PATH, global Python reported Pillow 12.2.0 instead of locked 12.3.0, and the build stopped before cleaning or output generation", + "Locked Scripts/python.exe packaging/verify_dependencies.py requirements-lock.txt", + "MLC_ALLOW_REPUBLISH=1 cmd /d /c packaging\\build_release.bat with the locked Python 3.14.3 Scripts directory first on PATH", + "Locked Scripts/python.exe packaging/verify_build.py --post", + "Locked Scripts/python.exe packaging/verify_build.py --final installer_output\\MLCPlayer_Setup_v0.41.exe", + "Exact SHA-256, embedded-public-key Ed25519 and process-leak readback" + ], + "artifacts": [ + { + "name": "MLCUserCleanup.exe", + "size_bytes": 8650513, + "sha256": "FE4F20CC3B2997BA19D5B21FAA9D32AF1622D8B41BA95131B4D7240351B485F6" + }, + { + "name": "MLCPlayer_Setup_v0.41.exe", + "size_bytes": 64313812, + "sha256": "D1321184E807BFF9494851E9F7841B4FA93E36ED5C1B1109951122DBB4C19E99" + }, + { + "name": "MLCPlayer_Setup_v0.41.exe.sig", + "size_bytes": 88, + "sha256": "E714FD0BB998E57C574BE6001253ED49A2BD1601814978D6F097728704EFE924" + }, + { + "name": "MLCPlayer_InternetVideo_v0.41.exe", + "size_bytes": 48823078, + "sha256": "5994C2ADB77AE9120EB6648EE3A561C2DBA1AFF8CF7202277D842278C5F57161" + }, + { + "name": "MLCPlayer_InternetVideo_v0.41.exe.sig", + "size_bytes": 88, + "sha256": "39038BA8F3D5E65857325E3198A0811CAFB822FB9446342BC36EEE373A809A2B" + } + ], + "evidence": [ + "The corrected build completed all eight stages with exit 0 and DONE in the verified locked environment.", + "Post-build dist and final main-installer verification passed.", + "Both detached signatures independently verified True against the exact current installer bytes and the public key embedded in source.", + "No setup, uninstaller, MLC Player, cleanup helper, PyInstaller or ISCC process remained after build." + ], + "summary": "D1321184 is the fresh source-build artifact containing the verified GetDateTimeString Char repair.", + "limitations": [ + "D1321184 has not yet been physically installed or uninstalled.", + "Installed-artifact acceptance and complete residue cleanup remain for the user-visible run.", + "Source changes remain uncommitted." + ], + "next_action": "Run the exact D1321184 setup over the remaining v0.41 installation, open Player once, close it, then repeat the visible uninstall cleanup flow." + }, + { + "id": "EV-20260909-010", + "recorded_at_utc": "2026-09-09T16:23:00Z", + "proof_layer": "installed_artifact", + "result": "failed", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "working_tree_state": "dirty source tree from EV-20260909-009; D1321184 was installed, launched once and visibly uninstalled with both cleanup choices selected", + "changed_files": [ + "app/user_cleanup_helper.py", + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json", + "tests/test_user_cleanup_helper_regressions.py" + ], + "subject": "D1321184 physical install, first-launch provisioning and selected uninstall cleanup", + "commands": [ + "User installed D1321184 interactively and supplied the successful finish page", + "User launched the installed Player once and supplied the visible main window", + "Read-only HKLM, HKCU, scheduled-task and helper identity inventory", + "User selected both cleanup choices and completed visible uninstall", + "Read-only Program Files, AppData, registry, task, helper, shortcut, association, update-directory and credential residue inventory" + ], + "artifacts": [ + { + "name": "MLCPlayer_Setup_v0.41.exe", + "size_bytes": 64313812, + "sha256": "D1321184E807BFF9494851E9F7841B4FA93E36ED5C1B1109951122DBB4C19E99" + }, + { + "name": "provisioned MLCUserCleanup.exe", + "size_bytes": 8650513, + "sha256": "FE4F20CC3B2997BA19D5B21FAA9D32AF1622D8B41BA95131B4D7240351B485F6" + } + ], + "corrects": [ + { + "entry_id": "EV-20260909-009", + "field": "limitations[0]", + "incorrect": "D1321184 has not yet been physically installed or uninstalled.", + "corrected": "D1321184 installed and launched successfully, then visibly uninstalled; selected settings cleanup left HKCU settings and therefore failed complete-cleanup acceptance." + } + ], + "evidence": [ + "The installer reached its successful finish page without the earlier Type Mismatch and the installed Player opened normally.", + "Before uninstall, HKLM InstallId and HKCU marker matched, the SID-bound task was Ready, and the provisioned helper hash matched the D132 build helper.", + "After uninstall, Program Files, AppData roots, uninstall registration, cleanup marker, scheduled task, helper, shortcuts, update directories, associations and matching stored credentials were absent.", + "Despite the selected settings choice, HKCU\\Software\\MLCPlayer\\MLCPlayer retained geometry, volume, last_dir and recent_files, and its subtitle child retained sub_delay." + ], + "summary": "D1321184 passed installation and provisioning but failed complete selected-data cleanup because the settings registry tree remained.", + "limitations": [ + "Program-file and cache/log cleanup passed only for the inventoried current account and paths.", + "Complete-cleanup acceptance is failed while the settings registry tree remains.", + "No automated UI control was performed by the agent." + ], + "next_action": "Replace the unchecked RegDeleteTreeW result path with recursive winreg deletion plus absence readback, then rebuild and repeat the physical flow." + }, + { + "id": "EV-20260909-011", + "recorded_at_utc": "2026-09-09T16:24:00Z", + "proof_layer": "deterministic", + "result": "passed", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "working_tree_state": "dirty source tree with uncommitted verified registry-cleanup repair", + "changed_files": [ + "app/user_cleanup_helper.py", + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json", + "tests/test_user_cleanup_helper_regressions.py" + ], + "subject": "Recursive settings registry deletion and post-delete absence verification", + "commands": [ + "python -m pytest -q tests/test_user_cleanup_helper_regressions.py tests/test_uninstall_cleanup_regressions.py", + "python packaging/verify_inno.py all", + "git diff --check", + "Independent read-only adversarial source review" + ], + "evidence": [ + "The helper now enumerates and deletes child keys before their parent with winreg, then reopens the target to prove absence.", + "A missing tree is idempotent success; access, deletion or verification errors return failure and propagate to the uninstall result.", + "Tests cover nested-tree deletion and reject a delete API that returns without removing the key; the target group passed 20 tests.", + "Both Inno scripts passed compile-only, git diff check was clean, and independent review returned GO with P0=0 and P1=0." + ], + "summary": "The selected settings-cleanup path now fails closed unless the complete owned registry tree is proven absent.", + "limitations": [ + "This is source and deterministic evidence, not installed-artifact proof.", + "The current residual registry values remain until a user-authorized cleanup run removes them.", + "Source changes remain uncommitted." + ], + "next_action": "Build a fresh artifact containing the registry deletion repair." + }, + { + "id": "EV-20260909-012", + "recorded_at_utc": "2026-09-09T16:25:00Z", + "proof_layer": "source_build", + "result": "passed", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "working_tree_state": "dirty corrected source tree from EV-20260909-011; generated outputs are ignored artifacts", + "changed_files": [ + "app/user_cleanup_helper.py", + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json", + "tests/test_user_cleanup_helper_regressions.py" + ], + "subject": "Fresh v0.41 source build with verified recursive registry cleanup", + "commands": [ + "MLC_ALLOW_REPUBLISH=1 cmd /d /c packaging\\build_release.bat with locked Python 3.14.3 Scripts first on PATH", + "Locked Scripts/python.exe packaging\\verify_build.py --post", + "Locked Scripts/python.exe packaging\\verify_build.py --final installer_output\\MLCPlayer_Setup_v0.41.exe", + "Exact SHA-256, embedded-public-key Ed25519 and process-leak readback" + ], + "artifacts": [ + { + "name": "MLCUserCleanup.exe", + "size_bytes": 8652334, + "sha256": "3D5269663A9F6CF2B8EE83FE000B2A0554FBFAFF11B58B3D5877613A479BAF23" + }, + { + "name": "MLCPlayer_Setup_v0.41.exe", + "size_bytes": 64316777, + "sha256": "5A58B8A7EBA34CE8A8DBC12B732BE36DE15633CEF91C48DD9F8E11833C3829D7" + }, + { + "name": "MLCPlayer_Setup_v0.41.exe.sig", + "size_bytes": 88, + "sha256": "46BA273C81C6DE37648489DC08FAB67BD5D89F061C1261CE3AB6AD5F3BD6D5B8" + }, + { + "name": "MLCPlayer_InternetVideo_v0.41.exe", + "size_bytes": 48823078, + "sha256": "5994C2ADB77AE9120EB6648EE3A561C2DBA1AFF8CF7202277D842278C5F57161" + } + ], + "evidence": [ + "The build completed all eight stages with exit 0 and DONE in the verified locked environment.", + "Post-build dist and final installer verification passed.", + "Both detached Ed25519 signatures verified True against the exact current files.", + "No Player, helper, setup, uninstaller, PyInstaller or ISCC process remained after build." + ], + "summary": "5A58B8A7 is the fresh source-build artifact containing the recursive settings registry cleanup and absence verification.", + "limitations": [ + "This artifact has not been physically installed or uninstalled.", + "The VLC-style native components page would require a separate NSIS migration and is not claimed by this Inno build.", + "Source changes remain uncommitted." + ], + "next_action": "Run the exact 5A58B8A7 setup, open and close Player once, then select both cleanup choices and verify complete residue absence." + }, + { + "id": "EV-20260909-013", + "recorded_at_utc": "2026-09-09T17:07:00Z", + "proof_layer": "installed_artifact", + "result": "failed", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "working_tree_state": "dirty source tree after EV-20260909-012; user installed and uninstalled exact 5A58B8A7 with both cleanup choices selected", + "changed_files": [ + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json", + "packaging/MLCPlayer.iss", + "tests/test_uninstall_cleanup_regressions.py" + ], + "subject": "5A58B8A7 repeated physical complete-cleanup attempt and residue audit", + "commands": [ + "User installed 5A58B8A7, opened Player once, closed it and completed uninstall with both cleanup choices", + "Read-only 17-surface residue inventory", + "Exact user-authorized reg.exe delete HKCU\\Software\\MLCPlayer /f and absence readback" + ], + "artifacts": [ + { + "name": "MLCPlayer_Setup_v0.41.exe", + "size_bytes": 64316777, + "sha256": "5A58B8A7EBA34CE8A8DBC12B732BE36DE15633CEF91C48DD9F8E11833C3829D7" + } + ], + "corrects": [ + { + "entry_id": "EV-20260909-012", + "field": "limitations[0]", + "incorrect": "This artifact has not been physically installed or uninstalled.", + "corrected": "The artifact was physically installed and uninstalled; all inventoried surfaces except the selected HKCU settings tree were absent, so complete-cleanup acceptance failed." + } + ], + "evidence": [ + "Program Files, Local/Roaming AppData, update directories, uninstall registration, cleanup registration, helper task, helper executable, shortcuts, associations, processes and matching credentials were absent.", + "HKCU\\Software\\MLCPlayer\\MLCPlayer still retained geometry, volume, last_dir, recent_files and subtitle\\sub_delay after selected uninstall.", + "The user's earlier cleanup selection authorized removal of this exact owned tree; direct reg.exe deletion returned exit 0 and immediate query returned not found." + ], + "summary": "5A58B8A7 again failed installed-artifact complete cleanup on the settings registry tree; the current machine residue was then explicitly removed and proven absent.", + "limitations": [ + "Manual post-uninstall removal proves current-machine cleanup, not automatic uninstaller correctness.", + "The helper execution context could not be inspected after its task and IPC were removed.", + "A new artifact with an elevated exact-SID fallback still needs physical validation." + ], + "next_action": "After helper success and infrastructure removal, delete and verify the exact loaded SID settings tree from the elevated uninstaller." + }, + { + "id": "EV-20260909-014", + "recorded_at_utc": "2026-09-09T17:08:00Z", + "proof_layer": "deterministic", + "result": "passed", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "working_tree_state": "dirty source tree with compact cleanup dialog and exact-SID elevated fallback", + "changed_files": [ + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json", + "packaging/MLCPlayer.iss", + "tests/test_uninstall_cleanup_regressions.py" + ], + "subject": "Compact Inno cleanup dialog and elevated exact-SID registry absence fallback", + "commands": [ + "python -m pytest -q tests/test_user_cleanup_helper_regressions.py tests/test_uninstall_cleanup_regressions.py", + "python packaging/verify_inno.py all", + "git diff --check", + "Independent read-only adversarial source review" + ], + "evidence": [ + "The cleanup dialog width changed from 610 to 470 logical pixels, the Turkish caption matches the uninstall helper, the body is bold, and a standard top-line separates the button row.", + "After helper result and infrastructure cleanup, selected settings trigger recursive deletion of CleanupUserSid plus Software\\MLCPlayer through HKU and a second absence check.", + "The target group passed 22 tests, both Inno scripts passed compile-only, diff check was clean, and independent review returned GO with P0=0 and P1=0." + ], + "summary": "The existing Inno architecture now has a narrower integrated-looking choice form and a fail-closed exact-user registry fallback.", + "limitations": [ + "Native Turkish render and automatic cleanup require the new artifact run.", + "Long German and Russian strings at HiDPI remain a P2 visual QA boundary.", + "Source changes remain uncommitted." + ], + "next_action": "Build and verify the new Inno artifact without migrating the packaging stack to NSIS." + }, + { + "id": "EV-20260909-015", + "recorded_at_utc": "2026-09-09T17:09:00Z", + "proof_layer": "source_build", + "result": "passed", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "working_tree_state": "dirty corrected source tree from EV-20260909-014; generated outputs are ignored artifacts", + "changed_files": [ + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json", + "packaging/MLCPlayer.iss", + "tests/test_uninstall_cleanup_regressions.py" + ], + "subject": "Fresh v0.41 source build with compact dialog and exact-SID registry fallback", + "commands": [ + "MLC_ALLOW_REPUBLISH=1 cmd /d /c packaging\\build_release.bat with locked Python 3.14.3 Scripts first on PATH", + "Locked Scripts/python.exe packaging\\verify_build.py --post", + "Locked Scripts/python.exe packaging\\verify_build.py --final installer_output\\MLCPlayer_Setup_v0.41.exe", + "Exact SHA-256, embedded-public-key Ed25519 and process-leak readback" + ], + "artifacts": [ + { + "name": "MLCUserCleanup.exe", + "size_bytes": 8653515, + "sha256": "206FC3791FD7D64D3CAE26D0DB7E707E606D83AE80C0A863DE6E0F98DF029482" + }, + { + "name": "MLCPlayer_Setup_v0.41.exe", + "size_bytes": 64313266, + "sha256": "0DB5C70F683E119142ED152294F3168A2D9C59FDEC24B20208A748A6ECA937F4" + }, + { + "name": "MLCPlayer_Setup_v0.41.exe.sig", + "size_bytes": 88, + "sha256": "7EF272D05F8F4C1EF785410138DB6EA38BCA3E3E73B815DAC98A23B90D0EA461" + }, + { + "name": "MLCPlayer_InternetVideo_v0.41.exe", + "size_bytes": 48823078, + "sha256": "5994C2ADB77AE9120EB6648EE3A561C2DBA1AFF8CF7202277D842278C5F57161" + } + ], + "evidence": [ + "The build completed all eight stages with exit 0 and DONE in the locked environment.", + "Post-build dist and final installer verification passed.", + "Both detached Ed25519 signatures verified True against the exact files.", + "No Player, helper, setup, uninstaller, PyInstaller or ISCC process remained." + ], + "summary": "0DB5C70F is the fresh artifact containing the compact cleanup form and exact-SID registry fallback.", + "limitations": [ + "This artifact has not yet been installed or physically uninstalled.", + "The automatic complete-cleanup and Turkish dialog appearance remain user acceptance steps.", + "Source changes remain uncommitted." + ], + "next_action": "Run exact 0DB5C70F, inspect the compact cleanup form, select both choices, then verify zero residue." + }, + { + "id": "EV-20260909-016", + "recorded_at_utc": "2026-09-09T17:27:00Z", + "proof_layer": "installed_artifact", + "result": "failed", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "working_tree_state": "dirty EV-20260909-015 source tree; user installed and uninstalled exact 0DB5C70F artifact", + "changed_files": [ + "assets/ICON_MANIFEST.txt", + "assets/mlc-player-icon-transparent.ico", + "assets/mlc-player-icon-transparent.png", + "assets/mlc-player-icon.ico", + "assets/mlc-player-icon.png", + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json", + "packaging/make_app_icon.py", + "tests/test_app_icon_transparency_regressions.py" + ], + "subject": "0DB5C70F physical compact-uninstall acceptance and legacy installer title icon failure", + "commands": [ + "User installed exact 0DB5C70F, inspected the Turkish installer and uninstall UI, then uninstalled it", + "User screenshot of the installer title bar" + ], + "artifacts": [ + { + "name": "MLCPlayer_Setup_v0.41.exe", + "size_bytes": 64313266, + "sha256": "0DB5C70F683E119142ED152294F3168A2D9C59FDEC24B20208A748A6ECA937F4" + } + ], + "evidence": [ + "The user accepted the compact uninstall choice window and requested that it remain as implemented.", + "The installer title bar still visibly used the legacy orange circular play icon instead of the current hexagonal MLC mark." + ], + "summary": "The compact uninstall UI was accepted, but the installed-artifact visual check failed because the setup title icon was stale.", + "limitations": [ + "No zero-residue audit was recorded for this uninstall cycle.", + "The screenshot proves the visible old icon only for exact 0DB5C70F." + ], + "next_action": "Replace the legacy circle source assets with the current hexagonal MLC mark and rebuild both installers." + }, + { + "id": "EV-20260909-017", + "recorded_at_utc": "2026-09-09T17:30:00Z", + "proof_layer": "deterministic", + "result": "passed", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "working_tree_state": "dirty source tree with regenerated hexagonal icon assets", + "changed_files": [ + "assets/ICON_MANIFEST.txt", + "assets/mlc-player-icon-transparent.ico", + "assets/mlc-player-icon-transparent.png", + "assets/mlc-player-icon.ico", + "assets/mlc-player-icon.png", + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json", + "packaging/make_app_icon.py", + "tests/test_app_icon_transparency_regressions.py" + ], + "subject": "Current hexagonal MLC icon generation and package binding", + "commands": [ + "python packaging/make_app_icon.py", + "python -m pytest -q tests/test_app_icon_regressions.py tests/test_app_icon_transparency_regressions.py tests/test_dist_completeness_regressions.py tests/test_uninstall_cleanup_regressions.py", + "python packaging/verify_inno.py all", + "git diff --check", + "Independent read-only adversarial icon and package-binding review" + ], + "evidence": [ + "The standard plated ICO and transparent Player ICO were regenerated from the current orange hexagonal mark in nine Windows sizes from 16 through 256 pixels.", + "Legacy-circle geometry is rejected by pixel assertions and both main and Internet Video setup scripts are locked to the new plated icon.", + "The icon and packaging target group passed 70 tests, both Inno scripts passed compile-only, diff check was clean, and independent review returned GO with no P0/P1 finding." + ], + "summary": "All source icon variants and installer bindings now use the current hexagonal MLC identity; the old circle is rejected by regression tests.", + "limitations": [ + "Deterministic inspection does not prove the Windows title-bar render of a newly installed artifact.", + "Source changes remain uncommitted." + ], + "next_action": "Build and verify a fresh v0.41 installer containing the regenerated icon resources." + }, + { + "id": "EV-20260909-018", + "recorded_at_utc": "2026-09-09T17:31:43Z", + "proof_layer": "source_build", + "result": "passed", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "working_tree_state": "dirty EV-20260909-017 source tree; generated outputs are ignored artifacts", + "changed_files": [ + "assets/ICON_MANIFEST.txt", + "assets/mlc-player-icon-transparent.ico", + "assets/mlc-player-icon-transparent.png", + "assets/mlc-player-icon.ico", + "assets/mlc-player-icon.png", + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json", + "packaging/make_app_icon.py", + "tests/test_app_icon_transparency_regressions.py" + ], + "subject": "Fresh v0.41 source build with current hexagonal MLC icons", + "commands": [ + "MLC_ALLOW_REPUBLISH=1 cmd /d /c packaging\\build_release.bat with locked Python 3.14.3 Scripts first on PATH", + "Locked Scripts/python.exe packaging\\verify_build.py --post", + "Locked Scripts/python.exe packaging\\verify_build.py --final installer_output\\MLCPlayer_Setup_v0.41.exe", + "Exact SHA-256, embedded-public-key Ed25519 and process-leak readback" + ], + "artifacts": [ + { + "name": "MLCUserCleanup.exe", + "size_bytes": 8651282, + "sha256": "002EA2EB0B96C79908D0FA47E34D4BE68AF7A5AD961434977D800A81F4037FB0" + }, + { + "name": "MLCPlayer_Setup_v0.41.exe", + "size_bytes": 64428274, + "sha256": "BE4BC0B43FBB60956FB8B0D9BA8BFD89CADA1C98B000C404816398CBA9C753E4" + }, + { + "name": "MLCPlayer_Setup_v0.41.exe.sig", + "size_bytes": 88, + "sha256": "87D452D935A68112D775D30BFA91DCFF92560EFFBD2ECD16F086C83A433B4877" + }, + { + "name": "MLCPlayer_InternetVideo_v0.41.exe", + "size_bytes": 48906142, + "sha256": "EC113D00ACD17F1D12EAB302E8413EC6D67BB389B34AF6C9FB63FD8D4AFD4DBC" + }, + { + "name": "MLCPlayer_InternetVideo_v0.41.exe.sig", + "size_bytes": 88, + "sha256": "B444C8998096D1E6ED0721E7EB3B59D83ECB983126F8C23CBC240FC0D2E38B1E" + } + ], + "evidence": [ + "The locked build completed all eight stages with exit 0 and DONE.", + "Post-build dist and final installer verification passed.", + "Both detached Ed25519 signatures verified True against the exact files.", + "No Player, helper, setup, Internet Video setup or ISCC process remained." + ], + "summary": "BE4BC0B4 is the fresh installer containing the current hexagonal MLC title and application icon resources.", + "limitations": [ + "This exact artifact has not been installed or visually accepted on Windows.", + "Automatic complete-cleanup still requires physical zero-residue validation on this exact artifact.", + "Source changes remain uncommitted." + ], + "next_action": "Install exact BE4BC0B4, visually inspect the setup and Player title icons, then uninstall with both cleanup choices and audit zero residue." + }, + { + "id": "EV-20260909-019", + "recorded_at_utc": "2026-09-09T17:44:15Z", + "proof_layer": "deterministic", + "result": "passed", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "working_tree_state": "dirty source tree corrected to the user-approved round icon on every installer surface", + "changed_files": [ + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json", + "packaging/make_app_icon.py", + "packaging/wizard/wizard-large.bmp", + "packaging/wizard/wizard-large-1.bmp", + "packaging/wizard/wizard-large-2.bmp", + "tests/test_app_icon_transparency_regressions.py" + ], + "subject": "Correction to the approved round icon identity and complete installer surface regeneration", + "commands": [ + "git restore --source=HEAD for the approved round application icon source and generated variants", + "python packaging/make_wizard_images.py", + "python -m pytest -q tests/test_app_icon_regressions.py tests/test_app_icon_transparency_regressions.py tests/test_installer_branding_regressions.py tests/test_dist_completeness_regressions.py tests/test_uninstall_cleanup_regressions.py", + "python packaging/verify_inno.py all", + "git diff --check", + "Independent read-only adversarial review" + ], + "corrects": [ + { + "entry_id": "EV-20260909-016", + "field": "evidence[1]", + "incorrect": "The installer title bar still visibly used the legacy orange circular play icon instead of the current hexagonal MLC mark.", + "corrected": "The installer title and large left panel visibly used the hexagonal mark; the user clarified that both should use the approved round orange MLC icon." + }, + { + "entry_id": "EV-20260909-017", + "field": "summary", + "incorrect": "All source icon variants and installer bindings now use the current hexagonal MLC identity; the old circle is rejected by regression tests.", + "corrected": "The hexagonal identity was a misinterpretation and has been replaced by the approved round orange MLC icon on every installer surface." + }, + { + "entry_id": "EV-20260909-018", + "field": "summary", + "incorrect": "BE4BC0B4 is the fresh installer containing the current hexagonal MLC title and application icon resources.", + "corrected": "BE4BC0B4 contains the rejected hexagonal installer identity and is superseded by the round-icon artifact in EV-20260909-021." + } + ], + "evidence": [ + "The canonical standard PNG/ICO and transparent PNG/ICO use the round orange icon; every ICO frame from 16 through 256 pixels has direct round-geometry assertions.", + "All three DPI-specific wizard-large BMP files were regenerated from the same round source and have independent geometry assertions at the points where the hexagon was empty.", + "Both main and Internet Video ISS files bind the round standard ICO and common regenerated wizard images; all wizard-small files remain intentionally blank white." + ], + "summary": "The source and regression contract now require the approved round icon in both installer title bars and every large left wizard panel.", + "limitations": [ + "Deterministic image inspection does not replace a physical Windows render of the new artifact.", + "Source changes remain uncommitted." + ], + "next_action": "Produce a fresh installer from the corrected round-icon source." + }, + { + "id": "EV-20260909-020", + "recorded_at_utc": "2026-09-09T17:45:00Z", + "proof_layer": "source_build", + "result": "failed", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "working_tree_state": "dirty EV-20260909-019 source tree while the previous setup artifact was still running", + "changed_files": [ + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json" + ], + "subject": "Standard output build blocked by the running previous installer", + "commands": [ + "MLC_ALLOW_REPUBLISH=1 cmd /d /c packaging\\build_release.bat with locked Python 3.14.3 Scripts first on PATH", + "Process inventory after the failed cleanup step" + ], + "evidence": [ + "The build stopped in step 2 before compilation because installer_output\\MLCPlayer_Setup_v0.41.exe was locked by the user's still-open previous setup process.", + "The chain returned exit 1 and FAILED; the cause was inspected before any new attempt.", + "No force kill or taskkill was used." + ], + "summary": "The normal fixed-name build correctly failed closed while the old installer held its own EXE open.", + "limitations": [ + "This attempt produced no new standard-path artifact.", + "The previous installer process remained user-owned and open." + ], + "next_action": "Build the corrected artifact under a separate verified output directory without touching the running old installer." + }, + { + "id": "EV-20260909-021", + "recorded_at_utc": "2026-09-09T17:46:00Z", + "proof_layer": "source_build", + "result": "passed", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "working_tree_state": "dirty EV-20260909-019 source tree; corrected artifacts are under ignored installer_output/round-fix", + "changed_files": [ + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json", + "packaging/make_app_icon.py", + "packaging/wizard/wizard-large.bmp", + "packaging/wizard/wizard-large-1.bmp", + "packaging/wizard/wizard-large-2.bmp", + "tests/test_app_icon_transparency_regressions.py" + ], + "subject": "Fresh v0.41 build with round title-bar and round left-panel icons", + "commands": [ + "Locked Python translation, helper and Player PyInstaller stages", + "ISCC main and Internet Video compilation into installer_output\\round-fix", + "packaging/sign_release.py for both exact installers", + "packaging/verify_build.py --post and --final", + "Exact SHA-256 and embedded-public-key Ed25519 readback" + ], + "artifacts": [ + { + "name": "round-fix/MLCPlayer_Setup_v0.41.exe", + "size_bytes": 64304301, + "sha256": "5BE2AE9D3F596FD086FED844EAD209FB1C92FCE3D7F78AEBF10C4DA1BE957264" + }, + { + "name": "round-fix/MLCPlayer_Setup_v0.41.exe.sig", + "size_bytes": 88, + "sha256": "A47DB9518D323253F515CCB9E6D54E6C5C361811A16E5452BCAF5814335448AF" + }, + { + "name": "round-fix/MLCPlayer_InternetVideo_v0.41.exe", + "size_bytes": 48810649, + "sha256": "15230011471D2011CEDA9DA2B836E581C53F6FE494F68963586434705960563E" + }, + { + "name": "round-fix/MLCPlayer_InternetVideo_v0.41.exe.sig", + "size_bytes": 88, + "sha256": "AE794FD489125D29532C831A3BBE349801908D80E9102BB466C6AEE66BF3CB3E" + }, + { + "name": "MLCUserCleanup.exe", + "size_bytes": 8653284, + "sha256": "5C994FC623AF681C1F897399B8E074592DF05CB02EB9415C043FC9EBF9375916" + } + ], + "evidence": [ + "The separate output build completed successfully without modifying or terminating the running old installer.", + "Post-build dist and final installer verification passed.", + "Both detached Ed25519 signatures verified True against the exact files.", + "The corrected main setup SHA-256 is 5BE2AE9D and supersedes rejected BE4BC0B4." + ], + "summary": "5BE2AE9D is the fresh round-icon installer for immediate physical visual validation.", + "limitations": [ + "This exact artifact has not yet been opened or installed on Windows.", + "Automatic complete-cleanup still requires physical zero-residue validation on this exact artifact.", + "Source changes remain uncommitted." + ], + "next_action": "Close the previous setup, open exact 5BE2AE9D and confirm that both the top-left title icon and large left-panel icon are round." + }, + { + "id": "EV-20260909-022", + "recorded_at_utc": "2026-09-09T17:50:00Z", + "proof_layer": "deterministic", + "result": "passed", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "working_tree_state": "dirty round-icon source tree after complete append-only correction audit", + "changed_files": [ + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json", + "tests/test_app_icon_transparency_regressions.py" + ], + "subject": "Complete field-level correction of the rejected hexagonal icon interpretation", + "commands": [ + "Field-by-field readback of EV-20260909-016 through EV-20260909-018", + "Exact nine-frame ICO geometry regression for standard and transparent icons", + "python -m pytest -q tests/test_app_icon_regressions.py tests/test_app_icon_transparency_regressions.py tests/test_installer_branding_regressions.py tests/test_dist_completeness_regressions.py tests/test_uninstall_cleanup_regressions.py tests/test_continuity_regressions.py", + "git diff --check", + "Independent read-only adversarial re-review" + ], + "corrects": [ + { + "entry_id": "EV-20260909-016", + "field": "summary", + "incorrect": "The compact uninstall UI was accepted, but the installed-artifact visual check failed because the setup title icon was stale.", + "corrected": "The compact uninstall UI was accepted; the visual check failed because both the top-left setup icon and the large left wizard panel were hexagonal while the approved product icon is round." + }, + { + "entry_id": "EV-20260909-016", + "field": "limitations[1]", + "incorrect": "The screenshot proves the visible old icon only for exact 0DB5C70F.", + "corrected": "The user evidence proves both complained-about installer icon surfaces were hexagonal; exact artifact attribution is retained by their corresponding records." + }, + { + "entry_id": "EV-20260909-016", + "field": "next_action", + "incorrect": "Replace the legacy circle source assets with the current hexagonal MLC mark and rebuild both installers.", + "corrected": "Keep the approved round source assets, regenerate every large wizard panel from them and rebuild both installers." + }, + { + "entry_id": "EV-20260909-017", + "field": "subject", + "incorrect": "Current hexagonal MLC icon generation and package binding", + "corrected": "Rejected hexagonal MLC icon generation and package binding" + }, + { + "entry_id": "EV-20260909-017", + "field": "evidence[0]", + "incorrect": "The standard plated ICO and transparent Player ICO were regenerated from the current orange hexagonal mark in nine Windows sizes from 16 through 256 pixels.", + "corrected": "The ICO files were mistakenly regenerated from a hexagonal mark; the approved identity is the round orange MLC icon." + }, + { + "entry_id": "EV-20260909-017", + "field": "evidence[1]", + "incorrect": "Legacy-circle geometry is rejected by pixel assertions and both main and Internet Video setup scripts are locked to the new plated icon.", + "corrected": "The legacy-circle assertion was directionally wrong; current tests require round geometry in both ICO variants and all large wizard panels." + }, + { + "entry_id": "EV-20260909-017", + "field": "evidence[2]", + "incorrect": "The icon and packaging target group passed 70 tests, both Inno scripts passed compile-only, diff check was clean, and independent review returned GO with no P0/P1 finding.", + "corrected": "Those checks validated the mistaken hexagonal requirement and are superseded by the round-icon checks and adversarial review in EV-20260909-022." + }, + { + "entry_id": "EV-20260909-017", + "field": "next_action", + "incorrect": "Build and verify a fresh v0.41 installer containing the regenerated icon resources.", + "corrected": "Restore the approved round icon, regenerate all dependent wizard images and then build a corrected artifact." + }, + { + "entry_id": "EV-20260909-018", + "field": "subject", + "incorrect": "Fresh v0.41 source build with current hexagonal MLC icons", + "corrected": "Superseded v0.41 source build with rejected hexagonal MLC icons" + }, + { + "entry_id": "EV-20260909-018", + "field": "limitations[0]", + "incorrect": "This exact artifact has not been installed or visually accepted on Windows.", + "corrected": "This exact artifact was opened on Windows and failed visual acceptance because both reported installer icon surfaces were hexagonal." + }, + { + "entry_id": "EV-20260909-018", + "field": "next_action", + "incorrect": "Install exact BE4BC0B4, visually inspect the setup and Player title icons, then uninstall with both cleanup choices and audit zero residue.", + "corrected": "Do not use BE4BC0B4; validate the superseding round-icon artifact 5BE2AE9D instead." + } + ], + "evidence": [ + "Every remaining decision-bearing field in EV016 through EV018 that described the circle as legacy or the hexagon as current is explicitly corrected.", + "Both ICO files are required to contain exactly 16, 20, 24, 32, 40, 48, 64, 128 and 256 pixel frames, and every frame is checked for round geometry.", + "The regenerated wizard BMP files independently reject the hexagonal geometry." + ], + "summary": "The append-only ledger and regression suite now consistently identify the round orange icon as the only approved MLC installer identity.", + "limitations": [ + "The corrected 5BE2AE9D artifact still requires user visual acceptance.", + "Source changes remain uncommitted." + ], + "next_action": "Open exact 5BE2AE9D and confirm that the two complained-about installer icon surfaces are round." + }, + { + "id": "EV-20260909-023", + "recorded_at_utc": "2026-09-09T18:09:00Z", + "proof_layer": "deterministic", + "result": "passed", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "working_tree_state": "dirty source tree after EV-20260909-022 with startup-position-only change", + "changed_files": [ + "app/player.py", + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json", + "main.py", + "tests/test_startup_window_position_regressions.py" + ], + "subject": "Center the existing Player window on the active screen at process startup only", + "commands": [ + "python -m pytest -q tests/test_startup_window_position_regressions.py tests/test_empty_state_info_dialog_regressions.py tests/test_window_modes_regressions.py tests/test_title_bar_hardening_regressions.py tests/test_single_instance_regressions.py tests/test_main_window_acceptance_contract.py", + "git diff --check", + "Independent read-only adversarial source review" + ], + "evidence": [ + "The saved geometry is restored exactly as before, then the current window size is moved to the center of the screen containing the cursor immediately before show().", + "The startup method calls move() only; it does not resize, set geometry, alter window state or run during PiP, fullscreen or second-instance activation.", + "An arbitrary restored 913x517 size is behaviorally centered without requiring any resize API.", + "The affected startup, empty-state, window-mode, title-bar, single-instance and acceptance-contract group passed 108 tests; diff check was clean.", + "Independent adversarial review returned GO with P0=0 and P1=0; its non-blocking P2 test-strength note was closed by the arbitrary saved-size behavioral case." + ], + "summary": "Every new MLC Player process now opens centered on the active screen while retaining its existing restored size and all other window-mode behavior.", + "limitations": [ + "This is deterministic source evidence; a fresh built executable and physical Windows launch have not been run for this change.", + "The separate request to remove the empty-state center icon is intentionally not included in this change.", + "Source changes remain uncommitted." + ], + "next_action": "After separate build approval, create a fresh installer and physically verify repeated centered launches; then handle the empty-state center icon as the next isolated change." + }, + { + "id": "EV-20260909-024", + "recorded_at_utc": "2026-09-09T18:13:01Z", + "proof_layer": "deterministic", + "result": "passed", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "working_tree_state": "dirty EV-20260909-023 source tree with the empty-state center logo removed", + "changed_files": [ + "app/empty_state.py", + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json", + "tests/test_empty_state_info_dialog_regressions.py" + ], + "subject": "Remove only the redundant large logo from the empty-state center content", + "commands": [ + "python -m pytest -q tests/test_empty_state_info_dialog_regressions.py tests/test_overlay_integration_regressions.py tests/test_main_window_acceptance_contract.py tests/test_startup_window_position_regressions.py tests/test_window_modes_regressions.py tests/test_title_bar_hardening_regressions.py tests/test_single_instance_regressions.py", + "rg consumer inventory for emptyStateLogo and logo_label", + "git diff --check", + "Independent read-only adversarial combined-diff review" + ], + "corrects": [ + { + "entry_id": "EV-20260909-023", + "field": "limitations[1]", + "incorrect": "The separate request to remove the empty-state center icon is intentionally not included in this change.", + "corrected": "The empty-state center icon was subsequently removed as the separate narrow change recorded by EV-20260909-024." + }, + { + "entry_id": "EV-20260909-023", + "field": "next_action", + "incorrect": "After separate build approval, create a fresh installer and physically verify repeated centered launches; then handle the empty-state center icon as the next isolated change.", + "corrected": "After separate build approval, create one fresh installer containing both completed narrow changes and physically verify centered launches plus the icon-free empty state." + } + ], + "evidence": [ + "Only the emptyStateLogo QLabel, its application-icon import, its 18 pixel following gap and its loading-visibility reference were removed.", + "The title, hint, file/folder buttons, loading label, native overlay, drag/drop delegation, margins, spacing and stretch ratios remain unchanged.", + "No external consumer of emptyStateLogo or EmptyStateOverlay.logo_label was found.", + "The combined affected family passed 111 tests and diff check was clean.", + "Independent adversarial review returned GO with no P0 or P1 finding." + ], + "summary": "The empty-state center no longer shows the redundant large logo; its text, actions and behavior are retained.", + "limitations": [ + "This is deterministic source evidence; a fresh build and physical Windows visual check have not been run.", + "The review's non-blocking P2 notes that full constructor restore/maximized behavior is not exercised as a native test.", + "Source changes remain uncommitted." + ], + "next_action": "After separate build approval, build once and physically verify repeated centered launches and the icon-free empty state." + }, + { + "id": "EV-20260909-025", + "recorded_at_utc": "2026-09-09T18:48:57Z", + "proof_layer": "source_build", + "result": "passed", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "working_tree_state": "dirty EV-20260909-024 source tree; generated standard-path artifacts are ignored", + "changed_files": [ + "app/empty_state.py", + "app/player.py", + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json", + "main.py", + "tests/test_empty_state_info_dialog_regressions.py", + "tests/test_startup_window_position_regressions.py" + ], + "subject": "Fresh standard v0.41 build with centered startup and icon-free empty state", + "commands": [ + "MLC_ALLOW_REPUBLISH=1 cmd /d /c packaging\\build_release.bat with locked Python 3.14.3 Scripts first on PATH", + "Locked Scripts/python.exe packaging\\verify_build.py --post", + "Locked Scripts/python.exe packaging\\verify_build.py --final installer_output\\MLCPlayer_Setup_v0.41.exe", + "Exact SHA-256, embedded-public-key Ed25519 and process-leak readback" + ], + "artifacts": [ + { + "name": "MLCPlayer_Setup_v0.41.exe", + "size_bytes": 64302420, + "sha256": "E7DC676803544A22B546C102F9A59DDFD4AD330517F505D80CD9C1B22FA20461" + }, + { + "name": "MLCPlayer_Setup_v0.41.exe.sig", + "size_bytes": 88, + "sha256": "DCEA28307B60DA354C0930E66E3003543F0EA4DD0A482BE00585D3E1389747A2" + }, + { + "name": "MLCPlayer_InternetVideo_v0.41.exe", + "size_bytes": 48810649, + "sha256": "15230011471D2011CEDA9DA2B836E581C53F6FE494F68963586434705960563E" + }, + { + "name": "MLCPlayer_InternetVideo_v0.41.exe.sig", + "size_bytes": 88, + "sha256": "AE794FD489125D29532C831A3BBE349801908D80E9102BB466C6AEE66BF3CB3E" + }, + { + "name": "MLCUserCleanup.exe", + "size_bytes": 8652783, + "sha256": "87226A81F59DB032DED91165D0B55317D513CA6E7A82B02621146B63EDBD254B" + } + ], + "evidence": [ + "The locked standard build completed all eight stages with exit 0 and DONE.", + "Post-build dist and final main-installer verification passed.", + "Both detached Ed25519 signatures verified True against the exact files.", + "No Player, helper, setup, Internet Video setup, ISCC or PyInstaller process remained." + ], + "summary": "E7DC6768 is the fresh standard-path installer containing centered process startup and the icon-free empty state.", + "limitations": [ + "This exact artifact has not yet been installed or physically opened.", + "Centered repeated launch and the icon-free empty state remain user visual acceptance steps.", + "Source changes remain uncommitted." + ], + "next_action": "Install exact E7DC6768, launch repeatedly to verify centered placement, and confirm the empty state has no large center logo." + }, + { + "id": "EV-20260910-001", + "recorded_at_utc": "2026-09-09T21:11:42Z", + "proof_layer": "installed_artifact", + "result": "passed", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "working_tree_state": "dirty EV-20260909-025 source tree; user physically inspected the installed exact E7DC6768 artifact", + "changed_files": [ + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json" + ], + "subject": "E7DC6768 repeated centered-startup and icon-free empty-state user acceptance", + "commands": [ + "User installed exact E7DC6768 and performed the requested repeated startup and empty-state visual check" + ], + "artifacts": [ + { + "name": "MLCPlayer_Setup_v0.41.exe", + "size_bytes": 64302420, + "sha256": "E7DC676803544A22B546C102F9A59DDFD4AD330517F505D80CD9C1B22FA20461" + } + ], + "evidence": [ + "After being asked to launch the installed Player repeatedly, verify centered placement, retained window size and absence of the large empty-state center logo, the user reported that they checked it and found no issue." + ], + "summary": "The user physically accepted centered startup and the icon-free empty state in the installed E7DC6768 package.", + "limitations": [ + "This is user-reported visual acceptance; no instrumented native marker or screenshot was captured in this cycle.", + "The exact uninstall cleanup and zero-residue audit have not yet been performed for E7DC6768.", + "Source and evidence changes remain uncommitted." + ], + "next_action": "With separate uninstall approval, remove exact E7DC6768 with both cleanup choices selected and audit all MLC-owned residue surfaces." + }, + { + "id": "EV-20260910-002", + "recorded_at_utc": "2026-09-09T21:27:41Z", + "proof_layer": "installed_artifact", + "result": "failed", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "working_tree_state": "dirty EV-20260910-001 source tree; exact E7DC6768 product was already absent when the approved uninstall preflight began", + "changed_files": [ + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json" + ], + "subject": "E7DC6768 post-removal zero-residue audit and cleanup", + "commands": [ + "Read-only inventory of product directories, AppData stores, update directories, main/add-on uninstall keys, machine/user product keys, Applications registration, standard and moved shortcuts, scheduled cleanup task, credentials and related processes", + "Delete the exact dead C:\\Users\\Universe\\Desktop\\AIO\\MLC Player.lnk shortcut", + "Elevated reg.exe delete of the verified-empty HKLM\\Software\\MLCPlayer key", + "Repeat the bounded MLC-owned residue inventory" + ], + "artifacts": [ + { + "name": "MLCPlayer_Setup_v0.41.exe", + "size_bytes": 64302420, + "sha256": "E7DC676803544A22B546C102F9A59DDFD4AD330517F505D80CD9C1B22FA20461" + } + ], + "evidence": [ + "Before the agent could open the approved uninstaller, the product directory and main/add-on uninstall registrations were already absent.", + "The first audit found two residual items: an empty HKLM\\Software\\MLCPlayer product key and a user-moved desktop shortcut whose installed target no longer existed.", + "The exact stale shortcut and verified-empty product registry key were removed; the repeated audit found zero residue across nine owned paths, nine registry surfaces, cleanup tasks, OpenSubtitles credentials, processes and shortcuts." + ], + "summary": "The machine is now clean, but the E7DC6768 artifact failed zero-residue acceptance because its completed removal left the empty machine product key; the moved shortcut is a separate user-relocated item.", + "limitations": [ + "The actual uninstaller window, checkbox state and exit code were not captured because removal had completed before the approved agent preflight.", + "The installer cannot reliably track a shortcut after the user moves it away from its registered desktop path.", + "Manual cleanup proves the final machine state, not that E7DC6768 itself performs complete cleanup." + ], + "next_action": "Add a regression-first installer rule that records and removes the machine-level MLCPlayer parent registry key, then rebuild and physically retest a new exact artifact." + }, + { + "id": "EV-20260910-003", + "recorded_at_utc": "2026-09-09T21:27:42Z", + "proof_layer": "deterministic", + "result": "passed", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "working_tree_state": "dirty EV-20260910-002 source tree with machine product-key uninstall cleanup fix", + "changed_files": [ + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json", + "packaging/MLCPlayer.iss", + "tests/test_uninstall_cleanup_regressions.py" + ], + "subject": "Remove the empty machine MLCPlayer registry parent on future uninstalls", + "commands": [ + "python -m pytest -q tests/test_uninstall_cleanup_regressions.py -k machine_product_registry_parent (red, then green)", + "python -m pytest -q tests/test_uninstall_cleanup_regressions.py tests/test_user_cleanup_helper_regressions.py tests/test_installer_branding_regressions.py tests/test_installer_process_safety_regressions.py tests/test_inno_compile_preflight_regressions.py tests/test_readme_user_guidance_regressions.py", + "python packaging/verify_inno.py all", + "git diff --check", + "Independent read-only adversarial review and strengthened-test readback" + ], + "corrects": [ + { + "entry_id": "EV-20260910-002", + "field": "next_action", + "incorrect": "Add a regression-first installer rule that records and removes the machine-level MLCPlayer parent registry key, then rebuild and physically retest a new exact artifact.", + "corrected": "The regression-first source rule is complete; separately approve a fresh build, then install and uninstall that new exact artifact for physical zero-residue confirmation." + } + ], + "evidence": [ + "The initial regression failed because no active uninstall-log entry owned HKLM\\Software\\MLCPlayer.", + "The new dontcreatekey plus uninsdeletekey entry is ordered before its UninstallCleanup child so reverse uninstall processing removes the child first and the product parent last.", + "The strengthened test requires the parent and child as exact active lines, exactly once each, and locks their source order.", + "The uninstall, helper, installer branding/process safety, compile preflight and README family passed 69 tests; both Inno scripts passed compile-only verification and diff check was clean.", + "Independent adversarial review returned final GO with P0=0, P1=0 and P2=0." + ], + "summary": "Future main-package uninstalls now record removal of the machine MLCPlayer product namespace without creating it during install solely for cleanup.", + "limitations": [ + "This is deterministic source evidence; no fresh installer was built, installed or physically uninstalled after the fix.", + "A user-relocated shortcut remains outside Inno Setup tracking and was handled manually in this machine cleanup.", + "Source and evidence changes remain uncommitted." + ], + "next_action": "With separate build approval, build a fresh exact installer containing the parent-key cleanup rule; then separately install and uninstall it with both data choices selected and repeat the zero-residue audit." + }, + { + "id": "EV-20260910-004", + "recorded_at_utc": "2026-09-10T17:55:04Z", + "proof_layer": "source_build", + "result": "failed", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "working_tree_state": "dirty EV-20260910-003 source tree; approved standard build stopped at the dependency gate before STEP 1", + "changed_files": [ + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json" + ], + "subject": "Fresh build blocked by locked Pillow version drift", + "commands": [ + "MLC_ALLOW_REPUBLISH=1 cmd /d /c packaging\\build_release.bat with Python 3.14.3 Scripts first on PATH", + "rg -n -i ^pillow== requirements-lock.txt", + "Python 3.14.3 -m pip show Pillow", + "Post-failure artifact and related-process inventory" + ], + "evidence": [ + "The build exited 1 before STEP 1 because installed Pillow was 12.2.0 while requirements-lock.txt requires 12.3.0.", + "The script reported FAILED and no output produced; no Player, cleanup helper, uninstaller, ISCC or PyInstaller process remained.", + "Post-failure standard output contains only the prior detached v0.41 signature files; the main and add-on v0.41 installer EXEs are absent." + ], + "summary": "No fresh installer was produced because the build environment no longer matches the dependency lock.", + "limitations": [ + "No dependency was installed and no retry was attempted.", + "Because installer existence was not inventoried before the attempt, this record does not claim when the v0.41 EXEs became absent.", + "No installed-artifact or physical uninstall acceptance can transfer from the prior E7DC6768 build." + ], + "next_action": "After separate approval to modify the build environment, install the exact locked requirements, verify Pillow 12.3.0 and the full dependency gate, then request or apply a fresh build retry." + }, + { + "id": "EV-20260910-005", + "recorded_at_utc": "2026-09-10T17:58:25Z", + "proof_layer": "source_build", + "result": "passed", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "working_tree_state": "dirty EV-20260910-004 source tree after exact lock restoration and one approved build retry", + "changed_files": [ + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json", + "packaging/MLCPlayer.iss", + "tests/test_uninstall_cleanup_regressions.py" + ], + "subject": "Fresh v0.41 build containing machine registry parent cleanup", + "commands": [ + "Python 3.14.3 -m pip install -r requirements-lock.txt", + "python packaging\\verify_dependencies.py requirements-lock.txt", + "MLC_ALLOW_REPUBLISH=1 cmd /d /c packaging\\build_release.bat with locked Python 3.14.3 Scripts first on PATH", + "Locked Python packaging\\verify_build.py --post", + "Locked Python packaging\\verify_build.py --final installer_output\\MLCPlayer_Setup_v0.41.exe", + "Direct-file import of packaging/prepublish.py signature_is_valid for both exact installers", + "Exact SHA-256 and related-process readback" + ], + "corrects": [ + { + "entry_id": "EV-20260910-004", + "field": "next_action", + "incorrect": "After separate approval to modify the build environment, install the exact locked requirements, verify Pillow 12.3.0 and the full dependency gate, then request or apply a fresh build retry.", + "corrected": "The approved environment repair and one build retry completed successfully; physically install and uninstall exact 453D5C54 with both data choices selected, then repeat the zero-residue audit." + } + ], + "artifacts": [ + { + "name": "MLCPlayer_Setup_v0.41.exe", + "size_bytes": 64304222, + "sha256": "453D5C54530D837ABA0725D3FFF440C54EA8E10CF71FA5E2231C343CFC1656A0" + }, + { + "name": "MLCPlayer_Setup_v0.41.exe.sig", + "size_bytes": 88, + "sha256": "E1165C56F37879942F5A1C73F16EE9BC754419A51D2EBD483BDB80A58EB93A71" + }, + { + "name": "MLCPlayer_InternetVideo_v0.41.exe", + "size_bytes": 48810649, + "sha256": "15230011471D2011CEDA9DA2B836E581C53F6FE494F68963586434705960563E" + }, + { + "name": "MLCPlayer_InternetVideo_v0.41.exe.sig", + "size_bytes": 88, + "sha256": "AE794FD489125D29532C831A3BBE349801908D80E9102BB466C6AEE66BF3CB3E" + }, + { + "name": "MLCUserCleanup.exe", + "size_bytes": 8653501, + "sha256": "3B50530CF1C5DE02C381D99B9D9BDB06D420FB96C46458EF5180812567FD28D1" + } + ], + "evidence": [ + "The approved lock repair changed only Pillow 12.2.0 to 12.3.0; every other requirement was already satisfied and the dependency gate then passed.", + "The one approved retry completed all eight stages with exit 0 and DONE.", + "Post-build dist and final main-installer verification passed.", + "Both detached Ed25519 signatures verified True against the exact installer bytes after the initial verifier import collision was diagnosed and corrected by direct file loading.", + "No Player, cleanup helper, uninstaller, ISCC or PyInstaller process remained." + ], + "summary": "453D5C54 is the fresh standard-path installer containing the deterministic HKLM product-parent cleanup fix.", + "limitations": [ + "This is source-build evidence; exact 453D5C54 has not been installed or physically uninstalled.", + "The two cleanup choices, uninstaller exit and post-uninstall zero-residue state still require a separately approved physical Windows cycle.", + "Source and evidence changes remain uncommitted." + ], + "next_action": "With separate install/uninstall approval, install exact 453D5C54, remove it with both data choices selected while observing the UI and exit, then audit all MLC-owned residue surfaces." + }, + { + "id": "EV-20260910-006", + "recorded_at_utc": "2026-09-10T18:03:53Z", + "proof_layer": "installed_artifact", + "result": "passed", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "working_tree_state": "dirty EV-20260910-005 source tree; user completed visible installation of exact 453D5C54", + "changed_files": [ + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json" + ], + "subject": "Exact 453D5C54 visible installation and installed-state readback", + "commands": [ + "User completed the visible installer wizard after launching exact 453D5C54", + "Installed EXE, version, uninstall registration, cleanup helper, file inventory and process readback", + "Computer Use screenshot observation of the installed Player started by the finish-page selection", + "Normal Alt+F4 close and process-absence readback" + ], + "artifacts": [ + { + "name": "MLCPlayer_Setup_v0.41.exe", + "size_bytes": 64304222, + "sha256": "453D5C54530D837ABA0725D3FFF440C54EA8E10CF71FA5E2231C343CFC1656A0" + }, + { + "name": "installed MLCUserCleanup.exe", + "size_bytes": 8653501, + "sha256": "3B50530CF1C5DE02C381D99B9D9BDB06D420FB96C46458EF5180812567FD28D1" + } + ], + "evidence": [ + "The user screenshot showed the v0.41 completion page and then confirmed completion.", + "HKLM uninstall registration identifies MLC Player v0.41 at C:\\Program Files\\MLC Player with unins000.exe present.", + "The installed tree contains 97 files totaling 201,728,518 bytes; MLC Player.exe reports v0.41 and the installed cleanup helper exactly matches the fresh build hash.", + "The finish-page selection launched the installed Player; direct screenshot observation showed a centered window and icon-free empty state. It then closed normally and related process count returned to zero." + ], + "summary": "Exact 453D5C54 is installed with the expected v0.41 payload and cleanup helper, and the product is closed ready for uninstall testing.", + "limitations": [ + "Installation was user-driven because Windows integrity isolation rejected injected input into the elevated wizard.", + "The uninstall choices, uninstaller exit and zero-residue result have not yet been exercised for this artifact.", + "Source and evidence changes remain uncommitted." + ], + "next_action": "With separate uninstall approval, open the installed uninstaller visibly, select both user-data cleanup choices, complete removal, and audit every MLC-owned residue surface." + }, + { + "id": "EV-20260910-007", + "recorded_at_utc": "2026-09-10T18:42:35Z", + "proof_layer": "installed_artifact", + "result": "failed", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "working_tree_state": "dirty EV-20260910-006 source tree; user visibly uninstalled exact 453D5C54 with both data choices selected", + "changed_files": [ + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json" + ], + "subject": "Exact 453D5C54 physical uninstall and zero-residue acceptance", + "commands": [ + "Visible launch of C:\\Program Files\\MLC Player\\unins000.exe", + "User selected both optional data-cleanup choices and completed uninstall", + "Read back the exact cleanup request, scheduled-task result and residual stores", + "Directly invoke the exact installed cleanup helper once to complete the already-authorized request", + "User closed the final uninstaller dialog", + "Repeat the bounded MLC-owned residue inventory" + ], + "artifacts": [ + { + "name": "MLCPlayer_Setup_v0.41.exe", + "size_bytes": 64304222, + "sha256": "453D5C54530D837ABA0725D3FFF440C54EA8E10CF71FA5E2231C343CFC1656A0" + }, + { + "name": "installed MLCUserCleanup.exe", + "size_bytes": 8653501, + "sha256": "3B50530CF1C5DE02C381D99B9D9BDB06D420FB96C46458EF5180812567FD28D1" + } + ], + "evidence": [ + "The consumed request was bound to SID S-1-5-21-2068301135-2316194410-456321829-1001 and recorded delete_settings=true plus delete_cache=true, proving both boxes were selected.", + "The main program files and uninstall registration were removed, but roaming/local MLCPlayer data, the HKCU cleanup marker and the scheduled cleanup task remained.", + "The task Last Result was 0x80070002 and its exact registered helper path was C:\\Users\\Universe\\AppData\\Local\\MLCPlayer\\UninstallCleanup\\v1\\MLCUserCleanup.exe.", + "Direct invocation of the same hash-verified helper consumed the authorized request; the uninstaller then removed the helper infrastructure and the user closed its final dialog.", + "The final audit found zero product paths, registry entries, scheduled tasks, MLC credentials or related processes." + ], + "summary": "The final machine state is clean, but exact 453D5C54 failed automatic full-cleanup acceptance because its scheduled task could not launch the helper and direct intervention was required.", + "limitations": [ + "Manual intervention proves the final clean machine state, not automatic success by this artifact.", + "The failure is specific to the exact artifact, host and selected full-cleanup scenario; it cannot be converted to PASS by later source changes.", + "Source and evidence changes remain uncommitted." + ], + "next_action": "Isolate the Task Scheduler path failure, add a regression-first source fix and require a new exact build/install/uninstall cycle before installed-artifact PASS." + }, + { + "id": "EV-20260910-008", + "recorded_at_utc": "2026-09-10T18:42:36Z", + "proof_layer": "native_smoke", + "result": "passed", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "working_tree_state": "dirty EV-20260910-007 source tree; clean machine after exact uninstall intervention; same helper binary copied through a bounded Task Scheduler path matrix", + "changed_files": [ + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json" + ], + "subject": "Task Scheduler cleanup-helper path root-cause isolation", + "commands": [ + "Create, run, read and remove temporary on-demand tasks for the exact MLCUserCleanup.exe under TEMP, LOCALAPPDATA product roots and LOCALAPPDATA Programs roots", + "Inspect CodeIntegrity, AppLocker, Defender and System events for matching launch denial", + "Remove every probe task and temporary probe directory", + "Repeat MLC process and residue inventory" + ], + "evidence": [ + "The exact helper launched from TEMP and from LOCALAPPDATA\\Programs trees, returning 2 because no request existed; this proves Task Scheduler created the process.", + "The same bytes returned 0x80070002 when registered directly under LOCALAPPDATA\\MLCPlayer and two other direct LOCALAPPDATA product roots.", + "The exact proposed durable path LOCALAPPDATA\\Programs\\MLC Player\\UninstallCleanup\\v1\\MLCUserCleanup.exe launched and returned 2.", + "No matching CodeIntegrity, AppLocker, Defender or System event explained the path-specific denial; every probe task/file was removed and no related process remained." + ], + "summary": "On this host the failure is path-specific: the per-user Programs tree is schedulable while the former direct LOCALAPPDATA product tree is not.", + "limitations": [ + "Return code 2 proves process launch at the proposed path, not successful request/result processing or complete uninstall.", + "The OS component responsible for the path-specific 0x80070002 was not identified.", + "A fresh artifact remains required for end-to-end installed proof." + ], + "next_action": "Move the helper to the durable per-user Programs path, safely migrate the legacy exact path and harden reparse and failure cleanup before a new build." + }, + { + "id": "EV-20260910-009", + "recorded_at_utc": "2026-09-10T18:42:37Z", + "proof_layer": "deterministic", + "result": "passed", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "working_tree_state": "dirty EV-20260910-008 source tree with cleanup helper path, migration, account-scope, reparse and failure-teardown fixes", + "changed_files": [ + "app/user_cleanup_helper.py", + "packaging/MLCPlayer.iss", + "tests/test_user_cleanup_helper_regressions.py", + "tests/test_uninstall_cleanup_regressions.py", + "docs/PACKAGING_PLAN.md", + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json" + ], + "subject": "Harden optional uninstall cleanup after the 453D5C54 scheduled-task failure", + "commands": [ + "Regression-first tests for the per-user Programs helper path, current registration identity/hash, different-version legacy migration, profile ancestor reparses, multi-account UI scope and failure teardown", + "python -m pytest -q tests/test_uninstall_cleanup_regressions.py tests/test_user_cleanup_helper_regressions.py tests/test_installer_branding_regressions.py tests/test_installer_process_safety_regressions.py tests/test_inno_compile_preflight_regressions.py tests/test_readme_user_guidance_regressions.py", + "python packaging/verify_inno.py all --iscc C:\\Program Files\\Inno Setup 7\\ISCC.exe", + "python -m compileall -q app/user_cleanup_helper.py user_cleanup_main.py", + "git diff --check", + "Independent read-only adversarial review through successive counterexample rounds", + "Final bounded machine residue inventory" + ], + "corrects": [ + { + "entry_id": "EV-20260910-008", + "field": "next_action", + "incorrect": "Move the helper to the durable per-user Programs path, safely migrate the legacy exact path and harden reparse and failure cleanup before a new build.", + "corrected": "The source fix and deterministic/adversarial gates are complete; separately approve a new build, then separately install and uninstall its exact artifact for end-to-end proof." + } + ], + "evidence": [ + "The helper now uses LOCALAPPDATA\\Programs\\MLC Player\\UninstallCleanup\\v1 and accepts an existing registration only when install ID, SID, task name, exact path and current installed-source SHA-256 all match.", + "Different-version legacy helpers are accepted only at the former exact path when their stored SHA-256 matches the file; migration removes only allowed IPC names and empty product directories.", + "Python provisioning and request processing validate Profile/AppData/Local plus every product component against reparse points; the elevated uninstaller validates direct AppData paths to the root before deleting.", + "The uninstaller accepts only ProfileList-derived exact current or legacy helper layouts, verifies SID/task/install ID/hash/reparse identity and tears down validated task/helper/marker infrastructure on IPC, request, task-start and timeout failures.", + "The UI discloses that selected deletion covers the uninstall account and listed registered MLC accounts; no-helper fresh uninstall keeps both choices usable through bounded direct cleanup.", + "The final impact family passed 81 tests; both Inno sources passed real compile-only checks; Python compileall and diff check passed.", + "Independent adversarial review returned GO with P0=0, P1=0 and P2=0 after the last selection-specific path-safety refinement.", + "The machine readback remained zero for product paths, registry, tasks, credentials and related processes." + ], + "summary": "The source now addresses the measured scheduler path failure and closes migration, reparse, account-scope and failure-residue counterexamples without claiming a new installed-artifact PASS.", + "limitations": [ + "This is deterministic and compile-only evidence; no new setup artifact was built, installed or uninstalled after the fix.", + "Native path-matrix launch evidence does not prove request/result completion at the new path.", + "Source and evidence changes remain uncommitted." + ], + "next_action": "With separate build approval, produce a fresh exact v0.41 installer; then separately install and uninstall it with both data choices selected and repeat the zero-residue audit." + }, + { + "id": "EV-20260910-010", + "recorded_at_utc": "2026-09-10T19:23:02Z", + "proof_layer": "source_build", + "result": "passed", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "working_tree_state": "dirty EV-20260910-009 source tree after one separately approved standard release build", + "changed_files": [ + "app/user_cleanup_helper.py", + "packaging/MLCPlayer.iss", + "tests/test_user_cleanup_helper_regressions.py", + "tests/test_uninstall_cleanup_regressions.py", + "docs/PACKAGING_PLAN.md", + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json" + ], + "subject": "Fresh v0.41 build containing the hardened uninstall cleanup lifecycle", + "commands": [ + "Read back related processes, Python 3.14.3 and requirements-lock dependency parity", + "MLC_ALLOW_REPUBLISH=1 cmd /d /c packaging\\build_release.bat", + "python packaging\\verify_build.py --post", + "python packaging\\verify_build.py --final installer_output\\MLCPlayer_Setup_v0.41.exe", + "Direct-file import of packaging/prepublish.py signature_is_valid for both exact installers", + "Exact size, SHA-256, timestamp and related-process readback" + ], + "corrects": [ + { + "entry_id": "EV-20260910-009", + "field": "next_action", + "incorrect": "With separate build approval, produce a fresh exact v0.41 installer; then separately install and uninstall it with both data choices selected and repeat the zero-residue audit.", + "corrected": "The separately approved build completed successfully; separately approve visible installation of exact 447E2155, then separately approve uninstall with both data choices and repeat the zero-residue audit." + } + ], + "artifacts": [ + { + "name": "MLCPlayer_Setup_v0.41.exe", + "size_bytes": 64304678, + "sha256": "447E2155E5A8F1B352077337645A34AAA00EAFF116384BF46D6CCE68B959FA48" + }, + { + "name": "MLCPlayer_Setup_v0.41.exe.sig", + "size_bytes": 88, + "sha256": "13E03085072EAD9C157930565725AE685B119A8C7AFB316A0E6FCEDBA133042D" + }, + { + "name": "MLCPlayer_InternetVideo_v0.41.exe", + "size_bytes": 48810649, + "sha256": "15230011471D2011CEDA9DA2B836E581C53F6FE494F68963586434705960563E" + }, + { + "name": "MLCPlayer_InternetVideo_v0.41.exe.sig", + "size_bytes": 88, + "sha256": "AE794FD489125D29532C831A3BBE349801908D80E9102BB466C6AEE66BF3CB3E" + }, + { + "name": "MLCUserCleanup.exe", + "size_bytes": 8656075, + "sha256": "8E7237E2F92B679EC0DB38B8928556093A033E9EE70DB49E33A7B3C4F94D7079" + } + ], + "evidence": [ + "Preflight found zero related processes; Python 3.14.3 and every locked dependency matched.", + "The standard release build completed all eight stages with exit 0 and DONE.", + "Post-build dist verification and final main-installer verification passed.", + "Both detached Ed25519 signatures verified True against the exact installer bytes.", + "The first supplemental signature call used Path where the API requires str and raised TypeError; source inspection established the argument contract, no process leaked, and the corrected read-only call passed.", + "No Player, cleanup helper, setup, add-on setup, ISCC, PyInstaller or Python process remained." + ], + "summary": "447E2155 is the fresh standard-path v0.41 installer containing the hardened cleanup helper path, migration, reparse, account-scope and failure-teardown source changes.", + "limitations": [ + "This is source-build evidence; exact 447E2155 has not been installed or physically uninstalled.", + "Installed-artifact PASS cannot transfer from prior builds.", + "Source and evidence changes remain uncommitted." + ], + "next_action": "With separate installation approval, install exact 447E2155 visibly; then separately approve uninstall with both data choices and repeat the zero-residue audit." + }, + { + "id": "EV-20260924-001", + "recorded_at_utc": "2026-09-24T12:00:00Z", + "proof_layer": "deterministic", + "result": "passed", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "working_tree_state": "dirty EV-20260910-010 source tree plus the startup clamp, regenerated translations and refreshed architecture inventory", + "changed_files": [ + "app/player.py", + "tests/test_startup_window_position_regressions.py", + "docs/ARCHITECTURE_INVENTORY.json", + "translations/mlcplayer_en.ts", + "translations/mlcplayer_de.ts", + "translations/mlcplayer_es.ts", + "translations/mlcplayer_fr.ts", + "translations/mlcplayer_it.ts", + "translations/mlcplayer_ru.ts", + "translations/mlcplayer_pt_BR.ts", + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json" + ], + "subject": "Full-suite scan: stale bookkeeping repaired and oversized startup window kept on screen", + "commands": [ + "python -m compileall -q main.py app tests user_cleanup_main.py", + "python -m pyflakes main.py app user_cleanup_main.py", + "pytest -q tests (before: 2 failed, 5333 passed, 11 skipped)", + "pytest -q tests/test_startup_window_position_regressions.py (red: 1 failed, 4 passed)", + "python packaging/extract_translations.py", + "pytest -q tests (after: 5336 passed, 11 skipped)", + "git diff --check" + ], + "evidence": [ + "The first full run failed only test_the_translation_files_are_up_to_date and test_machine_inventory_matches_the_exact_six_sources; both were stale bookkeeping after the uncommitted player.py and empty_state.py edits.", + "pyflakes reported 18 unused imports and no undefined names; none were removed.", + "A new deterministic test proved centered_startup_position returned a negative offset for a 1900x1000 saved size on a 1366x728 work area, placing the title bar off screen.", + "The fix clamps only the centering offset at zero; window size, resize, state, PiP, fullscreen and second-instance activation are untouched.", + "The extractor rewrote seven .ts files with 461 strings unchanged; the app/player.py inventory row was re-measured with the test's own rules.", + "Full suite, compileall and diff check passed after the change." + ], + "summary": "Source now keeps an oversized restored window's top-left corner inside the active work area; bookkeeping tests are green again.", + "limitations": [ + "Offscreen evidence only; the visual change has not been seen in a real Windows window.", + "Installer 447E2155 (EV-20260910-010) predates this source change and no longer matches the source tree.", + "ensure_user_cleanup_task still provisions synchronously before the first window with a 30 s worst case; left unchanged because its real duration is unmeasured.", + "Source and evidence changes remain uncommitted." + ], + "corrects": [ + { + "entry_id": "EV-20260910-010", + "field": "next_action", + "incorrect": "With separate installation approval, install exact 447E2155 visibly; then separately approve uninstall with both data choices and repeat the zero-residue audit.", + "corrected": "447E2155 no longer matches the source; with separate build approval produce a fresh installer, then separately approve visible installation (also timing first launch and checking an oversized saved window), then separately approve uninstall with both data choices and repeat the zero-residue audit." + } + ], + "next_action": "With separate build approval, produce a fresh exact v0.41 installer from this source; then separately approve visible installation (time the first launch and check an oversized saved window), then separately approve uninstall with both data choices and repeat the zero-residue audit." + }, + { + "id": "EV-20260924-002", + "recorded_at_utc": "2026-09-24T13:00:00Z", + "proof_layer": "deterministic", + "result": "passed", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "working_tree_state": "dirty EV-20260924-001 tree plus a single playback-speed source", + "changed_files": [ + "app/config.py", + "app/menu_actions.py", + "app/video_frame.py", + "tests/test_playback_speed_source_regressions.py", + "docs/ARCHITECTURE_INVENTORY.json", + "translations/mlcplayer_*.ts", + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json" + ], + "subject": "Menu bar and video context menu share one playback-speed list", + "commands": [ + "pytest -q tests/test_playback_speed_source_regressions.py (red: 1 failed)", + "python packaging/extract_translations.py", + "pytest -q tests (5338 passed, 11 skipped)", + "python -m compileall -q main.py app tests", + "git diff --check" + ], + "evidence": [ + "A new AST test proved menu_actions.py and video_frame.py each carried their own float speed list; it is green after both import PLAYBACK_SPEEDS from app.config.", + "The speed values and menu order are unchanged; inventory rows for video_frame.py and menu_actions.py were re-measured with the test's rules.", + "Re-review found no further defects: updater verification, uninstall helper paths, legacy thumbnail path, and an AST scan of UI literals (6 false positives) were clean.", + "Unused imports and the silent playlist-position setValue were deliberately left: several names are referenced by native children outside the full suite, and the setValue failure is cosmetic." + ], + "summary": "Single source for playback speeds; no behaviour change.", + "limitations": [ + "Offscreen evidence only.", + "The review was pattern-based plus targeted reading; large modules such as video_frame.py were not read line by line for logic defects.", + "Source and evidence changes remain uncommitted; no installer contains EV-20260924-001/002." + ], + "next_action": "With separate build approval, produce a fresh exact v0.41 installer from this source; then separately approve visible installation (time the first launch and check an oversized saved window), then separately approve uninstall with both data choices and repeat the zero-residue audit." + }, + { + "id": "EV-20260924-003", + "recorded_at_utc": "2026-09-24T15:00:00Z", + "proof_layer": "deterministic", + "result": "passed", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "working_tree_state": "dirty EV-20260924-002 tree plus seven logic fixes from a line-by-line review of player.py, media_controls.py, playlist_panel.py and video_frame.py", + "changed_files": [ + "app/player.py", + "app/media_controls.py", + "app/playlist_panel.py", + "tests/test_open_failure_dialog_reentry_regressions.py", + "tests/test_saved_fullscreen_geometry_regressions.py", + "tests/test_player_shutdown_regressions.py", + "tests/test_menu_subtitle_add_lifecycle_regressions.py", + "tests/test_overlay_subtitle_state_regressions.py", + "tests/test_volume_osd_translation_regressions.py", + "tests/test_natural_end_reentry_regressions.py", + "tests/test_playlist_panel_regressions.py", + "docs/ARCHITECTURE_INVENTORY.json", + "translations/mlcplayer_*.ts", + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json" + ], + "subject": "Line-by-line review: seven user-visible logic defects fixed test-first", + "commands": [ + "Each defect: one deterministic red test, minimal fix, same test green", + "Real libmpv probe (vo/ao=null, lavfi testsrc): sub_add and sid writes leave sub_visibility unchanged", + "Qt probe: saveGeometry in full screen restores WindowFullScreen through restoreGeometry", + "python packaging/extract_translations.py; --check", + "pytest -q tests (5355 passed, 11 skipped)", + "python -m compileall -q main.py app tests", + "git diff --check" + ], + "evidence": [ + "Open-failure dialog re-entry: the modal exec() loop kept the 100 ms update_ui timer running; the red test opened 5 dialogs instead of 1. The load marker is now cleared before the dialog.", + "Natural-end re-entry: a rejected next-item load restored the at-end state and then opened a modal error; the red test opened 5 dialogs. A re-entry guard keeps it to one and the current item still rewinds.", + "Saved full screen: closing in full screen restored native full screen on the next launch while is_video_fullscreen was False (title bar/controls in window mode, Esc ineffective). The flag is now cleared at restore; maximized is kept.", + "Saved PiP geometry: closing in PiP saved the small PiP rectangle; geometry is no longer saved while PiP is active.", + "Menu 'Add subtitle' used raw sub_add; real libmpv keeps visibility False, and new media always hides subtitles, so the added subtitle was invisible. It now uses the single SubtitleSession lifecycle; the old test that faked visibility=True in sub_add was corrected.", + "Selecting a subtitle track from either menu wrote only sid; with visibility False nothing appeared. Selection now also shows subtitles, with sid rollback if that write fails.", + "Volume OSD 'Sessiz' / 'Ses: %N' bypassed tr(); now translated (new English string 'Volume: {volume}%', English remains 0 unfinished).", + "Playlist 'Remove' deleted selected rows hidden by the search filter; hidden rows are now excluded." + ], + "summary": "Seven user-visible defects fixed with red-first tests; full suite green.", + "limitations": [ + "Offscreen and headless-libmpv evidence only; no real Windows window or installed build was exercised.", + "Not read line by line: title_bar.py, menu_actions.py, subtitle center modules, updater dialog, media_info, errors.py.", + "Source and evidence changes remain uncommitted; no installer contains EV-20260924-001..003." + ], + "next_action": "With separate build approval, produce a fresh exact v0.41 installer from this source; then separately approve visible installation (time the first launch, check an oversized saved window, full-screen/PiP close-reopen, menu subtitle add/select and a failing file), then separately approve uninstall with both data choices and repeat the zero-residue audit." + }, + { + "id": "EV-20260924-004", + "recorded_at_utc": "2026-09-24T17:00:00Z", + "proof_layer": "deterministic", + "result": "passed", + "commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "baseline_commit": "8c826775969ddf3a0042531399ebbf5ce02a1ce0", + "working_tree_state": "dirty EV-20260924-003 tree plus five further fixes from reviewing the remaining modules", + "changed_files": [ + "app/single_instance.py", + "app/player.py", + "app/thumbnail_service.py", + "app/number_text.py", + "app/media_controls.py", + "app/media_info.py", + "app/errors.py", + "app/track_labels.py", + "app/title_bar.py", + "app/subtitle_appearance_dialog.py", + "app/ui_components.py", + "tests/test_single_instance_activation_state_regressions.py", + "tests/test_single_instance_regressions.py", + "tests/test_drop_subtitle_session_media_scope_regressions.py", + "tests/test_percent_text_locale_regressions.py", + "tests/test_decimal_text_locale_regressions.py", + "tests/test_thumbnail_start_failure_regressions.py", + "docs/ARCHITECTURE_INVENTORY.json", + "translations/mlcplayer_*.ts", + "docs/CONTINUITY.md", + "docs/VERIFICATION_LEDGER.json" + ], + "subject": "Remaining-module review: five further user-visible defects fixed test-first", + "commands": [ + "Each defect: one deterministic red test, minimal fix, same test green", + "Qt probe: showNormal() on a minimized maximized window returns WindowNoState", + "Stress probe: 400 thumbnail workers failing to start complete without recursion", + "python packaging/extract_translations.py; --check (English 0 unfinished)", + "pytest -q tests (5371 passed, 11 skipped)", + "python -m compileall -q main.py app tests", + "git diff --check" + ], + "evidence": [ + "Second-launch activation used showNormal(): a maximized or full-screen player that was minimized came back normal-sized (full screen then disagreed with is_video_fullscreen). Only the minimized flag is now cleared; the old showNormal-encoding test was updated to keep its no-topmost intent.", + "The drop/menu SubtitleSession lived across media; its stale sid removed an unrelated track on the next video (red test: B's auto-loaded SRT removed). The session is now scoped to the current media.", + "Percent labels (volume, transparency, subtitle position/opacity) hardcoded the Turkish '%70' order; they now use a translatable percent_text ('{value}%' in English).", + "Size, fps, aspect and kHz texts hardcoded the Turkish decimal comma and the untranslated word 'bayt'; they now use translatable localize_decimal/byte_count_text in a Qt-free app/number_text.py (media_info must not import Qt; the first attempt in app/utils.py was caught by its guard test and moved).", + "A thumbnail worker that failed to start never emitted finished; the queue stalled and rows stayed loading forever. FailedToStart and cache-directory errors now complete the item as failed and the queue continues." + ], + "summary": "Five more defects fixed; English stays complete; full suite green.", + "limitations": [ + "Offscreen/deterministic evidence only; nothing was exercised in a real window or installed build.", + "Subtitle Center modules, errors.py dialogs and media_info_dialog were scanned but not read line by line; no measured defect was found there.", + "Other languages still have unfinished strings by design (English first rule).", + "Source and evidence changes remain uncommitted; no installer contains EV-20260924-001..004." + ], + "next_action": "With separate build approval, produce a fresh exact v0.41 installer from this source; then separately approve visible installation and check: first-launch time, oversized saved window, full-screen/PiP close-reopen, minimized-maximized second launch, menu subtitle add/select, a failing file, English percent/decimal texts; then separately approve uninstall with both data choices and repeat the zero-residue audit." + }, + { + "id": "EV-20261003-001", + "recorded_at_utc": "2026-10-03T12:00:00Z", + "proof_layer": "deterministic", + "result": "passed", + "commit": "33b19bcc4c28ba3d3a3dbf9c991f1d434ef520db", + "baseline_commit": "33b19bcc4c28ba3d3a3dbf9c991f1d434ef520db", + "working_tree_state": "uncommitted: four product fixes, repository/docs reorganization (root holds only Start.bat and LICENSE), output/ consolidation; renames staged with git mv", + "changed_files": [ + "app/main.py (moved from main.py; sys.path bootstrap, bin/ from project root, cleanup registration after show())", + "app/user_cleanup_main.py (moved)", + "app/user_cleanup_helper.py (start_user_cleanup_registration, provision named mutex)", + "app/updater.py (_version_parts prerelease/build/trailing-zero handling)", + "app/media_controls.py, app/subtitle_service.py (three silent failures logged)", + "app/player.py (from app.main import get_bin_dir)", + "packaging/MLCPlayer.spec, packaging/MLCUserCleanup.spec (moved; SPECPATH-rooted absolute inputs)", + "packaging/build_release.bat, packaging/build_unsigned_main.bat, packaging/*.iss, packaging/verify_build.py, packaging/prepublish.py, packaging/fetch_sources.py, packaging/stage_*_sources.py, packaging/sign_release.py, packaging/extract_translations.py", + ".github/workflows/ci.yml, .github/workflows/build-unsigned-main.yml", + "requirements/{runtime,lock,dev,ci-docs,build-windows}.txt (moved from requirements*.txt)", + ".github/README.md, .github/README.tr.md (moved)", + ".claude/CLAUDE.md (moved; imports docs/process/AGENTS.md), .claude/hooks/*", + "docs/README.md (new index); docs/process, docs/release, docs/policies, docs/quality, docs/video-format, docs/history (moved)", + "docs/release/SIGNPATH.md (merged SIGNPATH_READINESS + SIGNPATH_FOUNDATION_APPLICATION)", + "docs/video-format/VIDEO_FORMAT_ACCEPTANCE_PLAN.md (absorbed VIDEO_FORMAT_CAPABILITY_INVENTORY)", + "docs/history/PACKAGING_HISTORY.md (historical sections split verbatim from PACKAGING_PLAN)", + "docs/CONTINUITY.md (rewritten compact), docs/history/CONTINUITY_HISTORY.md (closed blocks appended verbatim)", + "docs/history/ENGINEERING_AUDIT.md (local profile path masked)", + "second_launch.py (deleted: committed test artifact with a personal absolute path)", + ".gitignore (output/), docs/quality/ARCHITECTURE_INVENTORY.json (refreshed), translations/mlcplayer_*.ts (line refresh)", + "tests: new test_silent_failure_logging, test_docs_index, test_spec_paths, test_repository_privacy; path updates across the suite", + "docs/VERIFICATION_LEDGER.json" + ], + "subject": "Startup/version/logging/provision fixes plus repository and documentation reorganization", + "commands": [ + "Each product defect: one deterministic red test, minimal fix, same test green", + "Red proofs: blocking registration (elapsed check), v0.41-rc1 > v0.41, silent failures unlogged, concurrent provision, old spec raising ModuleNotFoundError from packaging/, HEAD containing the local profile path in two tracked files", + "python app/main.py --thumbnail-worker and python -m app.main --thumbnail-worker from another cwd: exit 64 (imports resolve, no window)", + "python packaging/extract_translations.py; --check", + "python -m pytest -q tests -p no:cacheprovider", + "python -m compileall -q app tests packaging", + "git diff --check; git diff --cached --check" + ], + "evidence": [ + "Full deterministic suite after the final changes: 5404 passed, 11 skipped, 0 failed", + "Independent adversarial review of the code fixes: GO, P0/P1 = 0 (two P2 findings fixed: concurrent provision mutex, version marker collision)", + "Independent adversarial review of the reorganization: GO, P0 = 0. P1 placeholder ledger entry resolved by this entry; P1 Codex no longer auto-loading AGENTS.md accepted as the user's explicit decision (AGENTS.md moved). P2 fixed: shipped THIRD_PARTY_NOTICES lock path, spec usage line, docs-only CI gate now runs the index and privacy tests, archive now keeps old CONTINUITY lines 43-46 verbatim, ABBB updater detail restored, policy names carry full paths. P2 accepted: old root policy URLs now 404; personal path remains in pre-existing Git history (rewrite is the user's decision)." + ], + "summary": "Four product fixes, each test-first: the uninstall-cleanup registration (up to 30 s child process) no longer blocks startup and now runs in a daemon thread after the window is shown, with the exception handler installed first; concurrent provisions are serialised by a per-user named mutex released by the OS on process death; prerelease tags, trailing zeros and +build metadata no longer sort above the stable release; three user-visible silent failures are logged by exception type only. The repository root now holds only Start.bat and LICENSE: entry points live in app/, specs in packaging/, dependency lists in requirements/, READMEs in .github/, agent rules in .claude/ and docs/process/, and every generated output (build, dist, installer, source mirror) under the ignored output/ folder; the user's existing local output folders were moved, not deleted. Documentation was split into process/release/policies/quality/video-format/history with a tested index; duplicated SignPath records were merged; the stale PACKAGING_PLAN history and checklist moved to the archive; CONTINUITY shrank from 200 lines with an 8137-character line and five cumulative copies of one bullet to 137 lines of at most 81 characters, with every removed block archived verbatim.", + "limitations": [ + "Deterministic/offscreen evidence only. No PyInstaller or Inno build ran: the moved specs, --distpath/--workpath and output/ paths are verified by a stubbed spec execution and tests, not by a real build.", + "No installed artifact contains EV-20260924-001..004 or this entry's changes.", + "AGENTS.md is no longer at the repository root; tools that only auto-read a root AGENTS.md (e.g. Codex) must be pointed to docs/process/AGENTS.md.", + "24 historical ledger entries still contain the local profile path; the ledger is append-only and the user chose not to rewrite it. Git history before this change also still contains second_launch.py.", + "Deferred by decision: event-driven EOF detection (needs native libmpv measurement), multi-file open (single-instance protocol bump), ruff in CI (lock change), converting source-text tests." + ], + "next_action": "With separate build approval, run packaging/build_release.bat once from this source; it is the first real build through the moved specs and output/ paths and must not be retried without inspecting a failure. Then, with separate approval, a visible install check (no startup wait, oversized saved window, full-screen/PiP close-reopen, minimized second launch, menu subtitle add/select, failing file, English percent/decimal text). Commit, push/PR, tag and release each need separate approval." + }, + { + "id": "EV-20261003-002", + "recorded_at_utc": "2026-10-03T17:30:00Z", + "proof_layer": "hosted_ci", + "result": "failed", + "commit": "bf4837e8843b0054d7587b422179760a5a2d4f9f", + "subject": "PR #75 first hosted run failed on a timer race in the default-UI child; test-only fix", + "commands": [ + "GitHub Actions CI run 37137517782 on bf4837e (PR #75 head)", + "gh run view 37137517782 --log-failed", + "Deterministic reproduction: default_ui_child copy with one player.update_ui() after the timeline click under MLC_CI=1 stub", + "Fix verification: same copy with QTest.qWait(250) after the click and the timer stopped", + "MLC_CI=1 PYTHONPATH=scripts python -m pytest -q tests/test_default_cinematic_ui_regressions.py; same without MLC_CI" + ], + "evidence": [ + "Hosted run 37137517782: 5390 passed, 24 skipped, 1 failed - test_timeline_click_seeks_on_default_launch (timeline_click_value 0)", + "Reproduction: timeline_click_value 0 while timeline_click_time_pos 300.0 - the seek happened, one 100 ms update_ui tick rewrote the slider to the stub's never-updated position", + "After stopping player.timer for the measurement: value 500 / time_pos 300.0 even with a 250 ms wait; MLC_CI=1 17 passed 1 skipped, local 18 passed" + ], + "summary": "The only hosted failure was a test-harness race, not a product defect: the CI mpv stub never reports playback position, so if the 100 ms UI timer fired inside the processEvents() right after the click, update_overlay_state synchronised the timeline back to position 0. The race predates this PR and first surfaced on a slow runner. The child now stops the UI timer while measuring click-to-seek; product code is unchanged. No retry was run before the cause was established.", + "limitations": [ + "Real libmpv updates position through its observer, so the product path is unaffected; the fix is test-only.", + "Other UI children were not audited for the same stub/timer pattern in this step.", + "A new hosted run on the fix commit is still required before merge." + ], + "next_action": "Push the test-only fix to codex/v041-build-evidence and let the PR test check run once; merge with a merge commit only when it is green." } ] } diff --git a/docs/VIDEO_FORMAT_CAPABILITY_INVENTORY.md b/docs/VIDEO_FORMAT_CAPABILITY_INVENTORY.md deleted file mode 100644 index 6b1acf6..0000000 --- a/docs/VIDEO_FORMAT_CAPABILITY_INVENTORY.md +++ /dev/null @@ -1,85 +0,0 @@ -# MLC Player video format yetenek envanteri - -Bu kayıt, `419d6c7cede0b6fad37425b28c375fc89e61b141` üzerinde 24 Ağustos -2026'da alınan salt-okunur makine ve exact runtime ölçümüdür. Medya açılmadı, -Windows HDR ayarı değiştirilmedi ve ürün yapılandırması değiştirilmedi. - -## Makine ve ekran yolu - -- Aktif GPU: NVIDIA GeForce RTX 4070 Ti; sürücü `610.88` - (`32.0.16.1088`), WDDM 3.2, Direct3D feature level 12_2. -- Pasif/ekran sürmeyen ikinci bağdaştırıcı: AMD Radeon Graphics, - `32.0.21045.1000`. -- Aktif ekran: MSI G274QPF E2 (`MSI8CC2`), 2560x1440, 180 Hz. -- Aktif EDID: 384 bayt, SHA-256 - `f325d9f7e693b0ee79049ba342bf01066419110658a56452d0e0a20e44f4456f`. - CTA HDR Static Metadata bloğu ham olarak `060701625F00`, BT.2020 - colorimetry bloğu `05C000` ölçüldü. Bu, giriş kablosu/ekranın ilan ettiği - yetenektir; o anda HDR10 sinyali gönderildiğini kanıtlamaz. -- DxDiag `HDR Support: Supported` ve `AdvancedColorEnabled` bildirdi. Aktif - renk uzayı ise - `DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709` idi. Microsoft, bu değerin SDR - yolu temsil ettiğini ve SDR Advanced Color ekranlarda da aynı değerin - görülebileceğini açıklar. HDR10 için aranan değer - `DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020` hâlâ ölçülmedi. -- DxDiag parlaklık telemetrisi: 417.711792 nit tepe ve 391.463287 nit tam - çerçeve. MSI resmî teknik sayfası DisplayHDR 400 ve 8-bit+FRC bildirir. - -Kaynaklar: [Microsoft Advanced Color](https://learn.microsoft.com/windows/win32/direct3darticles/high-dynamic-range), -[MSI G274QPF E2](https://www.msi.com/Monitor/G274QPF-E2/Specification). - -### 25 Ağustos 2026 canlı HDR çıkış güncellemesi - -Ayrı sistem ayarı onayıyla aynı tek aktif MSI ekranın Windows HDR anahtarı -kapalıdan açığa alındı. Ayarlar arayüzü `HDR Açık` durumunu gösterdi ve hemen -sonraki DxDiag ölçümü etkin renk uzayını exact -`DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020` olarak verdi -(`EV-20260825-005`). Bu, aktif HDR10 Windows çıkış ön koşulunu kanıtlar; -MLC Player medya yolu, tone mapping, görsel doğruluk veya herhangi bir -video-format satırı için PASS değildir. Bu güncellemede medya açılmadı ve -hedef süreç sızıntısı yoktu. - -## Exact mpv ve FFmpeg runtime - -- `bin/mpv-2.dll`: 112772608 bayt, SHA-256 - `de80329f5c019ba2ee48184b5dc1e1d0c2ee9eeba3f1fb7959f20b4b0f684f4e`. -- mpv: `v0.41.0-930-g49418246f`; FFmpeg: - `N-126239-g88ae625e6`; python-mpv: `1.0.8`; Python: `3.14.3`. -- VO derleme seçenekleri `gpu-next`, `gpu`, `direct3d` ve `libmpv` içeriyor. - Windows GPU API/context seçenekleri D3D11, Vulkan, `winvk`, ANGLE, - `dxinterop` ve `displayvk` yollarını içeriyor. -- `target-colorspace-hint` seçenekleri `auto/no/yes`; modları `target`, - `source`, `source-dynamic`. Hedef transfer seçeneklerinde PQ, HLG ve scRGB; - tone mapping seçeneklerinde BT.2390, BT.2446a, ST 2094-40 ve ST 2094-10 - bulunuyor. Çıkış seviye seçenekleri `auto/limited/full`. -- Derlenmiş codec listesi HEVC, VP9 ve AV1 yazılım sürücülerini; NVIDIA - CUVID, AMD AMF ve Intel QSV sürücülerini içeriyor. `hwdec-codecs` - varsayılan listesi H.264, HEVC, VP8, VP9, AV1, ProRes, FFV1, DPX ve APV'yi - kapsıyor. - -NVIDIA'nın resmî Video Codec SDK sayfası NVDEC'in Windows'ta H.264, HEVC, -VP9 ve AV1 dahil donanım çözmeyi desteklediğini açıklar. Buna rağmen derlenmiş -CUVID sürücüsü ve GPU modelinin varlığı tek başına bu exact MLC yolunda -donanım çözme PASS'i değildir; bunun için fingerprint edilmiş medya üzerinde -`hwdec-current` read-back gerekir. Kaynak: -[NVIDIA Video Codec SDK](https://developer.nvidia.com/video-codec-sdk). - -## Ürünün bugünkü politikası - -`app/config.py` hâlâ `vo=gpu` ve `hwdec=auto-safe` kullanıyor. Ürün -`gpu-next`, D3D11, `target-colorspace-hint`, hedef primaries/TRC veya özel tone -mapping politikasını varsayılan olarak zorlamıyor. Test-only HDR adayı bu -değerleri yalnız child kopyasında uygular; ürün sözlüğünü mutate etmez. - -## Kanıt sınırı ve karar - -Bu envanter şunları kanıtlar: exact DLL kimliği, derlenmiş seçeneklerin -okunabildiği, medya açmayan libmpv child'ının `MARK_DONE` ile çıkıp süreç -sızdırmadığı, ekranın HDR-capable olduğu ve aktif Windows yolunun ölçüm anında -P709 kaldığı. - -Şunları kanıtlamaz: gerçek HEVC/AV1/VP9 donanım decode, 10-bit swapchain, -HDR10/HLG/HDR10+/Dolby Vision çıkışı, tone mapping kalitesi, overlay -parlaklığı, 4K60 performansı veya rakip oynatıcı davranışı. Bu nedenle -`VIDEO_FORMAT_ACCEPTANCE_MATRIX.json` içindeki 16 senaryonun tamamı -`BLOCKED` kalır. diff --git a/docs/CONTINUITY_HISTORY.md b/docs/history/CONTINUITY_HISTORY.md similarity index 72% rename from docs/CONTINUITY_HISTORY.md rename to docs/history/CONTINUITY_HISTORY.md index a9eac2c..7230b7c 100644 --- a/docs/CONTINUITY_HISTORY.md +++ b/docs/history/CONTINUITY_HISTORY.md @@ -1218,3 +1218,146 @@ seçimini al. Seçimden önce installer kodu, build veya kurulum yapma. - Güncel modül ölçümü: `docs/ARCHITECTURE_INVENTORY.md`. - Makinece modül güncellik kapısı: `docs/ARCHITECTURE_INVENTORY.json`. - Gerçek cihaz senaryoları: `docs/WINDOWS_ACCEPTANCE_MATRIX.md`. + +## Arşivlenen devam notları — 3 Ekim 2026 + +Aşağıdaki bloklar `docs/CONTINUITY.md` içinden 3 Ekim 2026'da, taban +`33b19bcc4c28ba3d3a3dbf9c991f1d434ef520db` üzerinde kelimesi kelimesine +taşındı. Kapanmış iş, eski v0.40 zincirleri ve uzun anlatılar canlı devir +noktasını şişiriyordu. Bu bölüm tarihseldir; güncel durum yalnız +`docs/CONTINUITY.md` içindedir. Eski 43–47. satırlar aynı maddenin +kümülatif sürümleriydi; canlı belgede tekrar ettikleri için kaldırıldılar +ve ara test sayıları dahil hepsi aşağıda kelimesi kelimesine korunur. + +### Eski "Canlı ürün ve yayın durumu" meta kayıtları (PR #48/#49) + +- Belge/yönetişim paketi ve semantik sahiplik düzeltmesi PR #48 üzerinden + iki ebeveynli exact `5a94d4e` merge commit'iyle master'a alındı. Yerel ve + uzak master, PR #49 kayıt merge'i sonrasında exact `b7e7cdd` üzerinde temiz, + `0/0` ve aynıdır; iki görev dalı da korundu (`EV-20260826-006`). +- PR #48'in exact head'inde ilk zorunlu hosted run `32899094320`, **4965 + passed / 30 skipped / 1 failed** verdi (`EV-20260826-002`). Tek hata + `PACKAGING_PLAN.md` için stale bütün-belge-tarihsel testiydi; otomatik retry + yapılmadı. +- Semantik sahiplik düzeltmesi `EV-20260826-003` ile dar grupta **13 passed** + ve bağımsız karşıt incelemede bloklayıcı bulgusuzdu. Repaired exact head + `8558b73` üzerindeki yeni run `32934932893`, **4966 passed / 30 skipped / + 0 failed** ve `LEDGER_APPEND_ONLY_OK` verdi; PR merge öncesi MERGEABLE ve + required `test=SUCCESS` olarak geri okundu (`EV-20260826-005`). + +### Eski "Kalite kabul özeti" ayrıntıları + +- Exact `04197ef` tabanındaki uncommitted playlist UX paketi ana-thread senkron MPV tarama/okumalarını gözlemci snapshot'ına taşıdı; yalnız gerçek 14 px tutamaç yatay cursor sahibi, tek/çoklu seçim belirgin, tek tık seçim, çift tık oynatma ve toplu kaldırma atomiktir. Son birleşik etki paketi **606 passed**, ek sınırlar **54 passed**, final geniş paket **243 passed** verdi; kullanıcı final canonical kaynak koşumunu “Oldu” diye kabul etti (`EV-20260829-013`). Aynı working tree'de kullanıcı B yerleşimini seçti: görünür sağ araç grubunda tek tekrar düğmesi kapalı→liste→tek dosya döngüsüne, ayrı karışık düğmesi görünür sırayı bozmayan dahili sıraya bağlandı; doğal bitiş sıradaki öğeye ilerliyor, son öğede kapalı mod başa sarıp duruyor ve liste tekrarı başa dönüyor. Kırmızı 7/7 sınırdan sonra hedef **11 passed**, inventory dahil geniş etki paketi **323 passed** verdi; 400 px dar pencere taşmadan geçti (`EV-20260830-001`). İlk canonical kullanıcı kabulü, native kapatma değeri Python `False` iken ürünün yalnız `"no"` beklemesi nedeniyle dokuz `loop write was ignored` hatasıyla **FAILED** (`EV-20260830-002`). Canonical OSD readback + rollback düzeltmesi ve 40×40 hit alanlarını koruyan 10→4 px sağ-grup aralığı iki kırmızı sınırdan sonra geniş pakette **324 passed** verdi; yeni kaynak henüz fiziksel yeniden açılmadı (`EV-20260830-003`). Formal `WIN-P0-07` tam matris sırası yeniden kaydedilmediği için `NOT_RUN` kalır. +- Aynı uncommitted UX ağacındaki görsel bütünlük paketi ürünün turuncu vurgu, hover/pressed renkleri ve Segoe UI Variable Text→Segoe UI font zincirini `app.config` altında tek kaynağa bağladı; eski mavi seçim/slider vurguları ve ikinci turuncu kaldırıldı. Başlık, playlist, başlangıç, Medya Bilgisi, kısa bilgi, overlay, altyazı ve güncelleyici yüzeyleri ortak kimliği kullanıyor; klavye focus durumları nötr ve görünür, daha önce reddedilen ikincil Klasör düğmesinde turuncu focus geri getirilmedi. Beş kırmızı sözleşme sonrası ana yüzey **192 passed**, ikinci etki halkası **561 passed**, kalite/çeviri **51 passed** ve tam deterministik paket **5210 passed / 11 skipped** verdi (`EV-20260830-004`). Sonraki dar düzeltmede başlangıç yüzeyinin her 100 ms'de tekrarlanan `raise_()` z-order/input churn'i visibility geçişine indirildi; Kısayollar penceresi 450×520'ye sıkıştırılıp `Tamam` düğmesi `accept()` ile gerçek Accepted kapanışa bağlandı. Başarılı dosya/klasör açılışından sonra focus video yüzeyine taşındı; böylece açıcı ikonunun oynatma sırasında seçili kalması giderildi. Tek komutluk ana pencere kabul runner'ı eklendi: 13 fiziksel grubu kanonik sırada varsayılan 3 temiz döngüyle çalıştırıyor, ilk başarısızlıkta fail-closed duruyor ve tek JSON özeti üretiyor. I: sürücüsündeki iki video ile ilk gerçek ölçümde buttons **21/21**, window_resize **12/12** geçti; birleşik döngü playlist açıkken `overlayFullscreen` için `entered=False` ile başarısız oldu (`EV-20260830-005`–`008`). Taze Windows insan görsel kabulü tamamlanmadı; bağımsız karşıt agent GO'su tek-agent kısıtı nedeniyle yoktur. +- `WIN-P0-08` deterministik offscreen çok-süreç kapsamı gerçek secondary PID, dosya/URL/aktivasyon devri, exit `0` ve terminal kapanış ölçümüyle genişletildi; dar etki paketi **93 passed** verdi. Gerçek ürün penceresi foreground ve hedef-yükleme sonucu ölçülmediği için fiziksel satır hâlâ `NOT_RUN` (`EV-20260829-002`). Aktif oynatma sırasında kuyruğa eklenen yeni video(lar)la birlikte bırakılan altyazının global pending kuyruğuyla yanlışlıkla mevcut videoya uygulanması kırmızı testte doğrulandı; hedef medya başlatılmadığında pending durum artık korunuyor ve kullanıcı açıkça yönlendiriliyor. Subtitle/playlist/atomiklik/çeviri etki paketi **87 passed** verdi (`EV-20260829-003`). PR #72 ilk hosted run `33266269968`, **5151 passed / 22 skipped / 1 failed** ile stale `app/player.py` mimari hash/satır kapısında durdu; diğer beş modül exact eşti ve envanter düzeltildi (`EV-20260829-005`–`007`). Yeni push koşumu `33266596701` yine **5151/22/1** verdi; tek hata ürün bütçesi yerine runner/UI overhead'ini de kapsayan `elapsed <= 0.8` testiydi. Test sonluluğu artık duvar-saati üst sınırı yerine exact 40 Qt wait çağrısıyla ölçülüyor; hedef 5/5 ve dosya paketi **12 passed** (`EV-20260829-008`–`009`). Hiçbir başarısız run retry edilmedi. + +### Eski 43–46. satırlar (47. satırın ara sürümleri) + +- Aynı uncommitted UX ağacındaki görsel bütünlük paketi ürünün turuncu vurgu, hover/pressed renkleri ve Segoe UI Variable Text→Segoe UI font zincirini `app.config` altında tek kaynağa bağladı; eski mavi seçim/slider vurguları ve ikinci turuncu kaldırıldı. Başlık, playlist, başlangıç, Medya Bilgisi, kısa bilgi, overlay, altyazı ve güncelleyici yüzeyleri ortak kimliği kullanıyor; klavye focus durumları nötr ve görünür, daha önce reddedilen ikincil Klasör düğmesinde turuncu focus geri getirilmedi. Beş kırmızı sözleşme sonrası ana yüzey **192 passed**, ikinci etki halkası **561 passed**, kalite/çeviri **51 passed** ve tam deterministik paket **5210 passed / 11 skipped** verdi (`EV-20260830-004`). Taze Windows insan görsel kabulü ve bağımsız karşıt agent GO'su henüz yoktur. +- Aynı uncommitted UX ağacındaki görsel bütünlük paketi ürünün turuncu vurgu, hover/pressed renkleri ve Segoe UI Variable Text→Segoe UI font zincirini `app.config` altında tek kaynağa bağladı; eski mavi seçim/slider vurguları ve ikinci turuncu kaldırıldı. Başlık, playlist, başlangıç, Medya Bilgisi, kısa bilgi, overlay, altyazı ve güncelleyici yüzeyleri ortak kimliği kullanıyor; klavye focus durumları nötr ve görünür, daha önce reddedilen ikincil Klasör düğmesinde turuncu focus geri getirilmedi. Beş kırmızı sözleşme sonrası ana yüzey **192 passed**, ikinci etki halkası **561 passed**, kalite/çeviri **51 passed** ve tam deterministik paket **5210 passed / 11 skipped** verdi (`EV-20260830-004`). Sonraki dar düzeltmede başlangıç yüzeyinin her 100 ms'de tekrarlanan `raise_()` z-order/input churn'i visibility geçişine indirildi; Kısayollar penceresi 450×520'ye sıkıştırılıp `Tamam` düğmesi `accept()` ile gerçek Accepted kapanışa bağlandı. Etkilenen aileler **53 + 236 passed**, continuity **10 passed** verdi (`EV-20260830-005`). Taze Windows insan görsel kabulü ve bağımsız karşıt agent GO'su henüz yoktur. +- Aynı uncommitted UX ağacındaki görsel bütünlük paketi ürünün turuncu vurgu, hover/pressed renkleri ve Segoe UI Variable Text→Segoe UI font zincirini `app.config` altında tek kaynağa bağladı; eski mavi seçim/slider vurguları ve ikinci turuncu kaldırıldı. Başlık, playlist, başlangıç, Medya Bilgisi, kısa bilgi, overlay, altyazı ve güncelleyici yüzeyleri ortak kimliği kullanıyor; klavye focus durumları nötr ve görünür, daha önce reddedilen ikincil Klasör düğmesinde turuncu focus geri getirilmedi. Beş kırmızı sözleşme sonrası ana yüzey **192 passed**, ikinci etki halkası **561 passed**, kalite/çeviri **51 passed** ve tam deterministik paket **5210 passed / 11 skipped** verdi (`EV-20260830-004`). Sonraki dar düzeltmede başlangıç yüzeyinin her 100 ms'de tekrarlanan `raise_()` z-order/input churn'i visibility geçişine indirildi; Kısayollar penceresi 450×520'ye sıkıştırılıp `Tamam` düğmesi `accept()` ile gerçek Accepted kapanışa bağlandı. Başarılı dosya/klasör açılışından sonra focus video yüzeyine taşındı; böylece açıcı ikonunun oynatma sırasında seçili kalması giderildi. Etkilenen aileler **53 + 236 + 139 passed**, continuity **10 passed** verdi (`EV-20260830-005`–`006`). Taze Windows insan görsel kabulü ve bağımsız karşıt agent GO'su henüz yoktur. +- Aynı uncommitted UX ağacındaki görsel bütünlük paketi ürünün turuncu vurgu, hover/pressed renkleri ve Segoe UI Variable Text→Segoe UI font zincirini `app.config` altında tek kaynağa bağladı; eski mavi seçim/slider vurguları ve ikinci turuncu kaldırıldı. Başlık, playlist, başlangıç, Medya Bilgisi, kısa bilgi, overlay, altyazı ve güncelleyici yüzeyleri ortak kimliği kullanıyor; klavye focus durumları nötr ve görünür, daha önce reddedilen ikincil Klasör düğmesinde turuncu focus geri getirilmedi. Beş kırmızı sözleşme sonrası ana yüzey **192 passed**, ikinci etki halkası **561 passed**, kalite/çeviri **51 passed** ve tam deterministik paket **5210 passed / 11 skipped** verdi (`EV-20260830-004`). Sonraki dar düzeltmede başlangıç yüzeyinin her 100 ms'de tekrarlanan `raise_()` z-order/input churn'i visibility geçişine indirildi; Kısayollar penceresi 450×520'ye sıkıştırılıp `Tamam` düğmesi `accept()` ile gerçek Accepted kapanışa bağlandı. Başarılı dosya/klasör açılışından sonra focus video yüzeyine taşındı; böylece açıcı ikonunun oynatma sırasında seçili kalması giderildi. Tek komutluk ana pencere kabul runner'ı eklendi: 13 fiziksel grubu kanonik sırada varsayılan 3 temiz döngüyle çalıştırıyor, ilk başarısızlıkta fail-closed duruyor ve tek JSON özeti üretiyor. Etkilenen aileler **53 + 236 + 139 + 141 passed**, continuity **10 passed** verdi (`EV-20260830-005`–`007`). Taze Windows insan görsel kabulü ve bağımsız karşıt agent GO'su henüz yoktur. + +### Eski "Açık çalışma ve korunan kapılar" kapanmış maddeleri + +- Yayın sonrası oynatma/kontrol UX denetimi exact `59f6980` tabanında regression-first yürütüldü. Playlist/EOF ve görünür durum rollback'lerine ek olarak ses, seek exception, hız, loop ve görüntü ayarı başarısızlıkları; klavye yönlendirmeleri; tekrarlı title-bar, updater, panel ve pencere boyutu tıklamaları sertleştirildi. Yapışık playlist sınırındaki çift resize alanı, PiP/dar pencere kontrol-hit çakışması, mod geçişinde takılı resize imleci ve overlay'in yuttuğu çift/sağ tık kapatıldı. Derin kontrol-yolu taraması ayrıca dosya/URL/M3U seçimi, aktif öğe kaldırma, liste temizleme ve üç dosya-ekleme yolunu native ret halinde atomik geri almaya bağladı; klasik ses çubuğunda yatay ve yüksek çözünürlüklü teker davranışı düzeltildi. EV029 ve EV030 uygulamaları kaynak/test ağacından çıkarıldı; ilk görüntülerin eski `9090caf` checkout'undan geldiği ayrıca kaydedildi. Doğru ağaç yeniden açıldıktan sonraki görüntü klasör, playlist/menü ve sağ pencere ikonlarının tümünde hover aktifken dikey okun kaldığını doğruladı ve EV031 child-event teşhisini çürüttü; o uygulama/test de çıkarıldı. Kök neden, ayrı top-level overlay için gerekli `QApplication` override'ının ana pencere başlığında da açılmasıydı: Qt sözleşmesine göre bu cursor bütün uygulama widget'larını ezdiğinden ikonların kendi el cursor'ı görünemiyordu. Ana pencere ağacı artık yalnız widget cursor'ı kullanıyor; global override yalnız farklı top-level overlay ile sınırlı. Etki paketi **302 passed**, son tam deterministik paket **5160 passed / 11 skipped / 0 failed** verdi (`EV-20260828-033`). Değişiklik sonrası kullanıcı görüntülerinde bütün ikonlar doğru el cursor'ını, yalnız boş gerçek üst kenar dikey resize cursor'ını gösterdi; normal kapanıştan sonra player/launcher child süreçleri sıfırdı (`EV-20260828-034`). Kullanıcı tercihiyle başlık şeridi önce 36 px'e, ardından ikon/düğme ölçüleri korunarak güncel 40 px'e çıkarıldı; 40 px hedef paket **278 passed**, tam paket **5160 passed / 11 skipped / 0 failed** verdi (`EV-20260828-036`) ve kullanıcı güncel görünümü açıkça kabul etti (`EV-20260828-037`). Exact paket local `1abd905` commit'ine temiz readback ile bağlandı (`EV-20260828-038`). PR #69 exact `b244a7f` head'i required hosted `test` koşumunda **5149 passed / 22 skipped**, `LEDGER_APPEND_ONLY_OK` verdi ve iki ebeveynli exact `63b7704` merge commit'iyle protected `master`a alındı; canonical klasör clean `0/0` ile aynı commit'e fast-forward edildi (`EV-20260829-001`). Bu görsel source-tree ve hosted CI kabulüdür; captured exit-marker olmadığı için formal native smoke veya kurulu artifact kabulü değildir. İngilizce 456/456 tamam; 0/456 olan diğer altı katalog derlenmez ve menüde sunulmaz. +- Installer UX kararları `PACKAGING_PLAN.md` içinde tutulur. Üç private bitmap + yönü karşılaştırıldı; kullanıcı **C — Dengeli Hibrit** yönünü açıkça seçti + ve seçim kayıt paketi exact `f432c73` commit'iyle bağlandı + (`EV-20260826-008`). A ve B yalnız karşılaştırma referansıdır. Bitmap'ler + Git'e alınmaz ve seçim gerçek Inno Setup davranışı sayılmaz. +- v0.40/Inno, reentry ve Türkçe Ready düzeltme zinciri PR #53–#55 üzerinden + protected master'a alındı; exact hosted koşumlar yeşil, insan kontrollü + ana/add-on ekran ve payload sınırları kabul edildi. Tam tarih ve ara kusurlar + append-only `EV-20260826-077`–`083` kayıtlarındadır. +- İlk resmî v0.40 artifact zinciri hosted **5058 passed / 30 skipped** ve geçerli + Ed25519 verdi; Authenticode `NotSigned`. Active add-on kapanış ve ürün URL + önkoşulları fail-closed durdu (`EV-20260827-011`–`013`). +- Internet Video fallback ve geçici `QMenu` test ömrü regression-first kapatıldı; + protected PR #64 head'i **5059 passed / 30 skipped / 0 failed** verdi. Ara + hosted başarısızlıklar retry edilmedi; kaynak/test/provenance ayrıntıları + `EV-20260827-014`–`023` içindedir. +- PR #64 build kanıtı ve PR #65 path-contract düzeltmesi protected master'a + exact `ebc628b` olarak ulaştı; başarısız run retry edilmedi + (`EV-20260827-024`–`027`). + Exact-master dispatch **5061 passed / 30 skipped / 0 failed** verdi (`EV-20260827-028`); + EV029 exact ebc build'i ana/add-on `07118348...cf6c5` / `d4a4d799...f9e675` üretti. + Son B2 farklı `B8001DAA...fe2fc` ana artifact ile yürüdü fakat çağdaş source-build + transcript'i yoktu. Provenance preflight'ı shared output'taki iki installer EXE'yi + kaldırdı; imzalar ve kurulu v0.40 kaldı, ölçülen ürün/kullanıcı/kayıt durumu exact + aynı ve süreç 0 (`EV-20260827-038`, imza sınırı düzeltmesi `EV-20260828-001`). Final cycle NOT_RUN; tag/release blokludur. +- Exact `994f0e2` artifact'larıyla yürütülen B2 zincirinde silent/interaktif + kurulumlar, gerçek v0.39 yükseltmeleri, iki kaldırma sırası, altı geçersiz + add-on hedefi, payload/ayar/süreç okumaları geçti. Gerçek Blender URL'si + Player-owned exact `yt-dlp` ile 00:01→00:02 / 10:35 ilerledi; çıkarım Deno + gerektirmedi. Bağımsız birlikte çalıştırılan finite `yt-dlp`/Deno engelleyici + denemesi Restart Manager'da fail-closed durdu; kullanıcı Abort seçti, exit 5, + rollback sonrası 103 kurulu ve iki kullanıcı dosyasında fark sıfır, süreç + sızıntısı sıfırdı (`EV-20260827-030`). Karşıt inceleme bunun installer kusuru + değil güvenli negatif davranış olduğunu doğruladı; `force` eklenmedi. +- EV031'deki o tarihteki B2 sözleşmesi regression-first olarak gerçek ürün URL başarısını + yapay engelleyicilerden ayırdı. Product-success yalnız Player'ın başlattığı + exact `yt-dlp`, PID/ebeveyn/komut ve ilerleyen position/duration ile geçer; + Deno yalnız gerçek çıkarım kullanırsa gerekir. Refusing `yt-dlp` ve Deno + diğer hedefler kapalıyken ayrı tek-engelleyici senaryolarında ölçülür; Ignore + ve force yasak, Abort/rollback/hash/AppData/süreç sınırları fail-closed kalır. + Hedef kırmızıdan sonra belge ailesi **206 passed**, karşıt P0/P1/P2=0 verdi; + ürün ve Inno kaynakları değişmedi (`EV-20260827-031`); bu Deno refusal şartı + daha sonra EV035 ile ölçülen cooperative-Deno kuralıyla değiştirildi. +- Exact kurulu v0.39 `yt-dlp` root+child diğer hedefler kapalı tek engelleyici + olarak sınandı. Restart Manager yalnız iki `yt-dlp` sürecini buldu, kapanışın + tamamlanamadığını kaydetti; kullanıcı Ignore/Retry yerine Abort seçti. Setup + exit 5, kopya/başarı marker'ı yok, rollback sonrası 103 kurulu dosya, iki + kullanıcı dosyası, kayıtlar ve hashler exact aynı, hedef süreç sızıntısı + sıfırdı. İlk evaluator şema-kesişimini yanlış yorumlayarak güvenli BLOCKED + verdi; fiziksel retry yapılmadan ayrı düzeltilmiş verdict **16/16 PASS** oldu + (`EV-20260827-032`). Bu PASS yalnız yt-dlp fail-closed negatif kapısına aittir. +- Restart Manager exact Deno'yu kooperatif kapattı; v0.39→v0.40 add-on upgrade + sızıntısız PASS (`EV-20260827-033`), normal fixture refusal BLOCKED (`EV-20260827-034`). + Sözleşme graceful Deno + exact yt-dlp fail-closed oldu; belge **206 passed**, P0/P1/P2=0 (`EV-20260827-035`). +- Askıya alınmış tek exact v0.40 Player kapanmayınca ana setup kopya öncesi durdu; kullanıcı Retry/Ignore yerine Abort seçti, exit 5 ve rollback başarılıydı. Tüm ölçülen 103+2 dosya ile ayar/uninstall/OpenWith/kısayol yüzeyleri exact korundu; resume 0, normal kapanış, watchdog ateşlenmedi, sızıntı 0; evaluator **23/23 PASS**, karşıt P0/P1=0 (`EV-20260827-036`); app-level semantik/normal maintenance/silent/add-on'a aktarılmaz, belge kapanışı P0/P1/P2=0 (`EV-20260827-037`). +- Ayrı onaylı izole build exact `1dafe1f` üzerinde tek çalıştırmada exit 0 ve `DONE` verdi; fast-fail ana setup `ABBB01C3...DC34` / 55.945.176 bayt, değişmeyen add-on `D4A4D799...F9E675` / 48.909.126 bayt, iki detached Ed25519 geçerli, iki EXE Authenticode `NotSigned` ve süreç sızıntısı 0. Bu yalnız `source_build` PASS'tir; 5–10 saniye fiziksel davranış daha sonra EV011 ile geçti fakat tam B2 hâlâ tamamlanmadı (`EV-20260828-009`). +- Exact B7/D4 normal fiziksel döngüsü gerçek v0.39 yükseltmesi, iki kaldırma sırası, clean install, maintenance, payload/ayar koruması ve final restore ile geçti. Askıya alınmış Player yolu kopya öncesi Abort/exit 5, tam rollback ve sıfır sızıntıyla güvenliydi; fakat Restart Manager **66,715 saniye** bekledi. Kullanıcının 5–10 saniye UX sınırı nedeniyle tam B2 **FAILED** (`EV-20260828-006`). +- Regression-first installer-only düzeltme maintenance/upgrade sırasında süreç-ömürlü `MLCPlayer-Running` mutex'ini `PrepareToInstall` içinde önce denetler ve çalışan Player varsa kopya/RM bekleyişinden önce sekiz dilde hızlı fail-closed durur. `CloseApplications` fallback'i korunur; force/taskkill/ürün kodu yoktur. Dil sözleşmesi ve yanlış aynı-sihirbaz talimatı kapatıldı; dar paket **60 passed** (`EV-20260828-007`). +- Onaylı beş-dosya fast-fail/kayıt paketi exact `315e83a`, EV008 iki-belge evidence-commit kaydı exact `1dafe1f` olarak bağlandı; dosya sınırları ve post-commit dar paket **266 passed** (`EV-20260828-008`). +- Exact ABBB tek fiziksel denemesi setup başlamadan runner izolasyon kapısında **BLOCKED** oldu: başlangıç hedef süreç 0 ve başlatılan Player kimliği doğruydu, fakat üç saniye sonra hedef küme tek değildi. UAC/setup/suspend/kopya yok; Player normal kapandı, force/watchdog/sızıntı 0, 103+2 final durum korundu. Eski runner geçici hedef kimliğini kaydetmediğinden guard gevşetilmedi; prepared fresh runner sonraki ayrı onaylı denemede tam kümeyi kaydedecek (`EV-20260828-010`). +- Ayrı onaylı tek fresh-runner denemesinde exact ABBB setup askıya alınmış tek exact Player ve doğrulanmış mutex karşısında **6,983 saniyede** exit 7 ile hızlı fail-closed durdu; setup içi süre yaklaşık 0,131 saniyeydi. Restart Manager, kopya ve başarı marker'ı yok; 103 kurulu dosya, iki kullanıcı dosyası, ayarlar, kayıtlar, OpenWith ve üç kısayol exact korundu. Suspend/resume 0/0, normal kapanış, force/watchdog yok ve süreç sızıntısı 0; karşıt P0/P1=0 (`EV-20260828-011`). Bu PASS yalnız fast-fail senaryosudur; exact ABBB normal install/upgrade/uninstall B2 zincirinin yerine geçmez. +- Resmî B2 metnindeki eski “Player'ı Restart Manager kapatır” şartı committed ABBB davranışıyla çeliştiği için regression-first hizalandı: yalnız bakım/yükseltmede `MLCPlayer-Running` mutex'i `PrepareToInstall` içinde RM'den önce denetlenir; responsive/asılı Player'da 10 saniye içinde kopya öncesi nonzero fail-closed, mesaj, değişmeyen durum ve sıfır sızıntı zorunludur. İlk kurulum etkilenmez; RM mutex dışındaki kilitler için fallback kalır. İlk hedef test kırmızı, son etki paketi **226 passed**, karşıt P0/P1/P2=0 (`EV-20260828-012`). Ürün kodu veya artifact değişmedi. +- Exact `11bb2b0` ve ABBB/D4 artifact'larıyla silent fiziksel B2 zinciri immutable checkpoint resume ile tamamlandı: gerçek v0.39 seed/yükseltme, iki maintenance, iki kaldırma sırası, iki temiz durum, clean install ve final restore geçti. Final exact 103 dosya, iki uninstall kaydı, OpenWith, üç kısayol, korunmuş iki kullanıcı dosyası ve sıfır hedef süreçle `phase_passed` verdi (`EV-20260828-013`). Bu yalnız silent installed-artifact PASS'tir; `InteractiveMain` ve yeni exact Player product URL NOT_RUN kaldı. +- EV013 paketi exact `8ae5fca` commit'ine bağlandı (`EV-20260828-014`). Exact ABBB/D4 `InteractiveMain` zinciri final 103+2 durum ve sıfır süreç verdi (`EV-20260828-015`); kayıt exact `4c5dbd2` commit'ine bağlandı (`EV-20260828-016`). Son exact Player ürün-URL kapısında Player PID 37340 exact kurulu yt-dlp root 54132 ve child 41648'i aynı URL ile başlattı; Deno gerekmedi, kullanıcı oynatmayı “SORUN YOK” diye kabul etti, normal kapanış sonrası 103+2 durum ve sıfır süreç korundu (`EV-20260828-017`). Sayısal position/duration kullanıcı tarafından yazıya geçirilmedi ve bu sınır açıkça kayıtlıdır; Computer Use kullanılmadı. Exact ABBB/D4 B2 ürün kapıları tamamlandı. +- `WIN-P0-03` ilk native koşumu exact `06cebc4` üzerinde fixture kapısını + geçtikten sonra QMenu sınırında TIMEOUT oldu; exit 1 ve eksik final marker + nedeniyle **FAILED** kaydedildi, seçim/ürün bug'ı iddia edilmedi + (`EV-20260827-005`). Test-only nested-loop watchdog paketi 222 dar test ve + karşıt P0/P1=0 sonrası PR #61 exact head `8b1471d` üzerinde **5058 passed / + 30 skipped / 0 failed** verdi ve exact `18a88b8` olarak merge edildi. Ayrı + onaylı tek retry 35.6 saniyede **5 PASS / 0 FAIL / 0 BLOCKED**, exit 0, tam + marker zinciri, ses 1→2, altyazı 1→2→1, checked read-back, + `stop→terminate`, child/parent cursor restore ve ölçülen süreç=0 ile geçti + (`EV-20260827-009`). `WIN-P0-03` artık **PASSED**. +- C yönü seçim, continuity ve protected-master kayıt zinciri PR #50–#52 ile + merge edildi; exact commit/run/parent ayrıntıları append-only + `EV-20260826-009`–`017` ve `EV-20260826-028` kayıtlarındadır. +- Beş ekranlı C sözleşmesi tek kanonik sahibi `PACKAGING_PLAN.md` içinde + hazırlandı. Güncelleme kutusu mevcut ürün ayarına bağlı olmadığı için + çıkarıldı; Internet Video yalnız ayrı paket bilgisi olarak kaldı. İki private + test görseli gösterildi; gerçek Inno, build ve kurulum kabulü değildir. +- Kullanıcı finish seçeneklerinin gerçekten çalışmasını zorunlu tuttu. + Regression-first kırmızıdan sonra exact EXE, Default Apps ve GitHub hedefi, + optional-action sıra/hata/readback'i sözleşmeye bağlandı (`EV-20260826-018`). + Son çift-süzgeç UAC sahipliği, install/reinstall/upgrade metni, downgrade + engeli ve per-screen test boşluklarını buldu; ara test hataları incelenip + sözleşme/test daraltıldı. Son karşıt incelemede finish testinin belge-geneli + araması da fail-open bulundu; test finish ve fiziksel kabul bloklarına ayrı + bağlandı ve nihai paket `EV-20260826-021` ile doğrulandı. +- Kullanıcı gösterilen iki private C akış görselini hedef kompozisyon ve metin + olarak açıkça kabul etti (`EV-20260826-022`). Bu, compiled Inno pikseli, + handler, build, kurulum veya Windows action/readback kabulü değildir. +- C ekran-sözleşmesinin commit/build/reddedilen ara artifact/restore ve exact + cleanup zinciri append-only `EV-20260826-023`–`074` kayıtlarında korunur; + canlı kararlar yukarıdaki v0.40 kabul özetindedir ve meta-kayıt döngüsü + başlatılmaz. + +### Eski sıradaki-adım anlatısı (24 Eylül 2026, `EV-20260924-004`) + +Kullanıcı exact `BE4BC0B4...53E4` kurulum görüntüsünde hem sol üst küçük ikonun hem sol büyük panel ikonunun altıgen kaldığını ve onaylı kimliğin yuvarlak turuncu MLC ikonu olduğunu açıkladı. Standart ve şeffaf PNG/ICO kaynakları yuvarlak ikona döndürüldü; iki ICO'nun exact dokuz 16–256 px katmanı ile üç DPI-specific wizard BMP'si doğrudan yuvarlak geometri testine bağlandı. Güncel ikon/installer grubu **96 passed**, continuity ile toplam **106 passed**, iki Inno compile-only PASS ve temiz diff verdi. EV016–EV018 içindeki ters ikon yorumunun tüm karar alanları iki append-only kayıtla makinece düzeltildi (`EV-20260909-019`, `EV-20260909-022`). Sabit çıktı yolundaki ilk build, kullanıcının açık tuttuğu eski setup EXE kilidi nedeniyle step 2'de exit 1/FAILED ile güvenli durdu; neden incelendi ve force-kill yapılmadı (`EV-20260909-020`). Aynı doğru kaynak ayrı `installer_output\\round-fix` dizinine başarıyla derlendi: ana setup `5BE2AE9D...57264` / 64.304.301 bayt, add-on `15230011...0563E` / 48.810.649 bayt; post/final ve iki Ed25519 geçti (`EV-20260909-021`). Sonraki dar kaynak değişikliği yalnız süreç açılış konumunu etkiler: kayıtlı mevcut boyut geri yüklendikten sonra pencere, `show()` öncesinde imlecin bulunduğu ekranın kullanılabilir alanına `move()` ile ortalanır. Resize, window state, PiP, tam ekran ve ikinci-kopya aktivasyonu değişmedi; ilgili grup **108 passed**, diff temiz ve bağımsız inceleme **GO, P0/P1=0** verdi (`EV-20260909-023`). Ardından boş başlangıç yüzeyindeki büyük merkez logosu tek başına kaldırıldı; başlık, açıklama, iki düğme, loading, overlay, drag/drop, marjlar ve stretch oranları korundu. Birleşik etki grubu **111 passed**, diff temiz ve bağımsız inceleme yine **GO, P0/P1=0** verdi (`EV-20260909-024`). Onaylı standart build exit `0`/`DONE` üretti: ana setup `E7DC6768...20461` / 64.302.420 bayt, add-on `15230011...0563E` / 48.810.649 bayt; post/final, iki Ed25519 ve süreç-sızıntısı kontrolü geçti (`EV-20260909-025`). Kullanıcı exact `E7DC6768` paketini kurdu; istenen tekrarlı açılış, merkezi konum, korunmuş pencere boyutu ve logosuz boş başlangıç yüzeyini fiziksel olarak kontrol edip sorun olmadığını bildirdi (`EV-20260910-001`). Onaylı kaldırma ön denetimi başladığında ürün zaten kaldırılmıştı; ilk artık taraması boş `HKLM\Software\MLCPlayer` anahtarı ile kurulu hedefi artık bulunmayan kullanıcı-taşınmış `Desktop\AIO\MLC Player.lnk` kısayolunu buldu. İki exact öğe temizlendikten sonra dokuz yol, dokuz kayıt yüzeyi, görev, kimlik bilgisi, süreç ve kısayol taraması `0` artıkla geçti; actual kaldırıcı penceresi, seçimleri ve exit kodu yakalanamadığından E7DC6768 sıfır-artık kabulü **FAILED** kaldı (`EV-20260910-002`). Ölçülen boş HKLM üst anahtarı için kırmızı regresyon üretildi; Inno uninstall günlüğüne kurulumda anahtar yaratmayan `dontcreatekey uninsdeletekey` parent kuralı, ters kaldırma sırasına uygun biçimde child kaydından önce eklendi. Etki ailesi **69 passed**, iki Inno compile-only PASS, temiz diff ve bağımsız **GO, P0/P1/P2=0** verdi (`EV-20260910-003`). Ayrı onaylı standart build, STEP 1 başlamadan önce bağımlılık kapısında exit `1` ile güvenli durdu: Python 3.14.3 ortamındaki Pillow `12.2.0`, `requirements-lock.txt` içindeki `12.3.0` şartıyla uyuşmuyordu (`EV-20260910-004`). Ayrı onayla exact lock kuruldu; yalnız Pillow `12.3.0` sürümüne yükseldi ve bağımlılık kapısı geçti. Onaylı tek retry exit `0`/`DONE` üretti: HKLM parent temizleme düzeltmesini içeren ana setup `453D5C54...56A0` / 64.304.222 bayt, değişmeyen add-on `15230011...0563E` / 48.810.649 bayt; post/final doğrulama, iki Ed25519 imzası ve süreç-sızıntısı kontrolü geçti (`EV-20260910-005`). Kullanıcı exact `453D5C54` kurulumunu görünür sihirbazda tamamladı. HKLM uninstall kaydı v0.41 ve `C:\Program Files\MLC Player` hedefini gösteriyor; kurulu ağaç 97 dosya / 201.728.518 bayt, cleanup helper build ile hash-birebir. Bitiş seçimi Player’ı açtı; doğrudan ekran gözleminde pencere merkezde ve boş başlangıç yüzeyi büyük merkez logosuzdu. Player normal kapatıldı ve süreç sayısı `0` oldu (`EV-20260910-006`). Exact `453D5C54` kaldırmada iki veri seçiminin `true` olduğu request ile doğrulandı; ancak kayıtlı görev eski `%LOCALAPPDATA%\MLCPlayer` helper yolunda `0x80070002` verdi ve ayar/cache/task/marker kaldı. Aynı hash-doğrulanmış helper doğrudan bir kez çalıştırılınca istek tamamlandı; kullanıcı son pencereyi kapattı ve yol/kayıt/görev/credential/süreç denetimi sıfır artık verdi. Artifact otomatik temizlikte müdahale gerektirdiği için kabul **FAILED** kaldı (`EV-20260910-007`). Aynı helper ile kontrollü Task Scheduler matrisi doğrudan LocalAppData ürün yollarında `0x80070002`, Temp ve `%LOCALAPPDATA%\Programs` yollarında süreç-açılışını kanıtlayan exit `2` verdi (`EV-20260910-008`). Kaynak helper artık kalıcı Programs yolunu kullanıyor; farklı sürüm legacy yolunu exact kayıt/hash ile güvenli taşıyor; Python ve Inno tarafında profil zinciri reparse kontrolleri, tüm görev hata dallarında doğrulanmış altyapı teardown, no-helper direct cleanup ve açık çoklu hesap kapsamı eklendi. Etki ailesi **81 passed**, iki Inno compile-only, compileall ve diff check geçti; bağımsız karşıt inceleme son turda **GO, P0/P1/P2=0** verdi (`EV-20260910-009`). Ayrı onaylı standart build sekiz adımı exit `0`/`DONE` ile tamamladı: ana setup `447E2155...FA48` / 64.304.678 bayt, add-on `15230011...0563E` / 48.810.649 bayt ve cleanup helper `8E7237E2...7079` / 8.656.075 bayt. Post/final doğrulama, iki Ed25519 imzası ve süreç-sızıntısı kontrolü geçti (`EV-20260910-010`). 24 Eylül tam tarama iki bayat kaydı buldu (çeviri `.ts` satır numaraları ve `player.py` mimari envanteri) ve ikisi yenilendi. Ayrıca kayıtlı pencere boyutu çalışma alanından büyük olduğunda başlık çubuğunun ekran dışına düşmesi kırmızı testle kanıtlandı; yalnız ortalama ofseti sıfırda sınırlandı, pencere boyutu değişmedi. Tam paket **5336 passed, 11 skipped**; kanıt yalnız offscreen'dir. Açılıştaki eşzamanlı temizlik kaydı (en kötü durumda 30 sn) süresi ölçülmediği için değiştirilmedi (`EV-20260924-001`). Aynı gün menü çubuğu ile video sağ-tık menüsü tek `PLAYBACK_SPEEDS` kaynağına bağlandı; davranış değişmedi, tam paket **5338 passed** (`EV-20260924-002`). Ardından `player.py`, `media_controls.py`, `playlist_panel.py` ve `video_frame.py` satır satır incelendi; her biri kırmızı testle kanıtlanan yedi kullanıcıya görünür hata düzeltildi: açılamayan dosya ve doğal bitişte iç içe hata pencereleri, tam ekranda/PiP'de kapatınca bozuk yeniden açılış, menüden eklenen veya seçilen altyazının görünmemesi (gerçek libmpv ölçümüyle), çevrilmemiş ses OSD'si ve aramayla gizlenmiş seçili satırların silinmesi. Tam paket **5355 passed**; kanıt offscreen/headless'tır (`EV-20260924-003`). Kalan modüllerde beş hata daha düzeltildi: ikinci başlatmada simge durumundan dönen pencerenin büyütülmüş/tam ekran durumunu kaybetmesi, altyazı oturumunun videolar arasında yanlış iz silmesi, yüzde ve ondalık metinlerinin Türkçe biçime sabit olması (`bayt` dahil) ve başlayamayan küçük resim işçisinin kuyruğu kalıcı kilitlemesi. İngilizce eksiksiz, tam paket **5371 passed** (`EV-20260924-004`). Bu yüzden `447E2155` artık kaynakla birebir değildir. Sıradaki tek adım ayrı build onayıyla bu kaynaktan yeni bir v0.41 paketi üretmektir; ardından ayrı onayla görünür kurulumda ilk açılış süresi, ekrandan büyük kayıtlı pencere, tam ekran/PiP'de kapatıp açma, simge durumundan ikinci başlatma, menüden altyazı ekleme/seçme, açılamayan dosya ve İngilizce yüzde/ondalık metinleri kontrol edilir. Kaldırma, commit, push/PR, tag ve yayın ayrıca onay ister. diff --git a/docs/ENGINEERING_AUDIT.md b/docs/history/ENGINEERING_AUDIT.md similarity index 99% rename from docs/ENGINEERING_AUDIT.md rename to docs/history/ENGINEERING_AUDIT.md index f8518ff..61c8686 100644 --- a/docs/ENGINEERING_AUDIT.md +++ b/docs/history/ENGINEERING_AUDIT.md @@ -17,7 +17,8 @@ sırası `CONTINUITY.md`, kanıt kaydı `EV-20260905-001` içindedir. ### Başlangıç ve kapsam -- Kanonik depo: `C:\Users\Universe\Desktop\Programlar TEST\2026 YENİLER\MLC Player`. +- Kanonik depo: kullanıcının masaüstündeki `MLC Player` checkout'u (yerel + kullanıcı yolu 3 Ekim 2026'da gizlilik için maskelendi). - İncelenen HEAD: `95246d19ebac75e651e4970f5ebbc7473cc0f418`. - Dal: `codex/v041-release-candidate`; yerel origin görev ref'iyle eş; yerel `origin/master` ref'inden 1 commit ileride. Bu incelemede fetch yapılmadı. diff --git a/docs/history/PACKAGING_HISTORY.md b/docs/history/PACKAGING_HISTORY.md new file mode 100644 index 0000000..baa9758 --- /dev/null +++ b/docs/history/PACKAGING_HISTORY.md @@ -0,0 +1,205 @@ +# MLC Player paketleme tarihçesi + +> **TARİHSEL ARŞİV:** Bu dosya `docs/release/PACKAGING_PLAN.md` içinden +> 3 Ekim 2026'da değiştirilmeden ayrılan tarihsel paketleme kayıtlarını, +> eski yayın öncesi uyumluluk kontrol listesini ve ilk ikon notlarını +> korur. Güncel durum buradan çıkarılmaz: güncel devir için +> `docs/CONTINUITY.md`, canlı installer sözleşmesi ve açık uyumluluk +> maddeleri için `docs/release/PACKAGING_PLAN.md` kullanılır. + +## Tarihsel paketleme kayıtları + +Aşağıdaki tarihli libmpv engel ve kapanış ölçümleri dondurulmuş tarihsel +snapshot'tır. Güncel dağıtım durumu bu bölümden çıkarılmaz; `CONTINUITY.md` ve +`VERIFICATION_LEDGER.json` üzerinden okunur. + +### YAYIN ENGELI: mpv-2.dll DAGITILAMAZ -> COZULDU (16 Agustos 2026) + +**DURUM: kapatildi.** Asagidaki teshis kayit olarak korunuyor; en altta +degisim ve dogrulama sonuclari var. + +**Tahmin degil, ikilinin KENDI icinden okundu.** + +`bin/mpv-2.dll` (99.390.990 bayt, 25 Kasim 2024) icine gomulu FFmpeg +`configure` dizesi: + + --prefix=/__w/mpv-winbuild-cmake/mpv-winbuild-cmake/build64/install/mingw + ... --enable-gpl --enable-version3 --enable-nonfree ... + --enable-libx264 --enable-libx265 --enable-libxvid + --enable-cuda --enable-cuvid --enable-nvdec --enable-nvenc ... + +FFmpeg'in kendi `configure` yardim metni `--enable-nonfree` icin sunu der: +*"allow use of nonfree code, the resulting libs and binaries will be +unredistributable"*. Yani bu DLL kisisel kullanim icin derlenebilir ama +**hicbir bicimde ucuncu kisilere dagitilamaz** — ne setup icinde, ne yaninda. + +- `--enable-lgpl` YOK; `--enable-gpl` + `--enable-version3` VAR. Yani LGPL + degil, GPLv3 tarafindadir. MLC Player da GPLv3 oldugu icin bu KISIM sorun + degildir; sorun yalniz `nonfree`dir. +- nonfree'yi tetikleyen bilesen `fdk-aac` DEGIL: ikilide `libfdk` izi yok, + buna karsilik `nvenc`, `cuda`, `cuvid` var. Kaynak CUDA/NVENC tarafidir. +- `bin/SHA256SUMS.txt` bu dosya icin kaynagi bilerek bos birakiyor + ("source/version is intentionally unspecified"). 99 MB'lik bir ikili + kaynagi kayitli olmadan dagitilamaz; `yt-dlp`/`deno` icin uygulanan + provenance disiplini buna da uygulanmalidir. + +**Cozum basit ve dogrulandi:** ayni yukari-akis projenin (`mpv-winbuild-cmake`) +GUNCEL yapilandirmasi `--enable-nonfree` KULLANMIYOR; yalniz `--enable-gpl` +ve `--enable-version3` geciyor. Elimizdeki DLL eski/varyant bir yapidir. + +Yapilacaklar (release turunda, sirasiyla): + +1. `mpv-2.dll` guncel ve `nonfree` ICERMEYEN bir yapiyla degistir. +2. Degistirdikten SONRA ayni olcumu tekrarla: gomulu `configure` dizesinde + `--enable-nonfree` OLMADIGINI dogrula. Bu, kabul kriteridir. +3. Surum, kaynak URL, boyut ve SHA-256'yi `bin/RUNTIME_MANIFEST.txt` icine + yaz; `SHA256SUMS.txt`teki "intentionally unspecified" notunu kaldir. +4. mpv/FFmpeg icin GPLv3 lisans metnini ve karsilik gelen kaynak erisimini + pakete ekle. +5. Codec/patent tarafi ayrica degerlendirilmeli: `libx264`, `libx265`, + `libxvid` GPL'dir (lisans tarafi GPLv3 ile uyumludur) ancak H.264/H.265 + PATENT yukumlulukleri lisanstan AYRI bir konudur ve ticari dagitimda + avukata sorulmalidir. + +### YAPILDI: degisim ve dogrulama (16 Agustos 2026) + +Yeni ikili: `mpv v0.41.0-923-g7b8915bc1`, FFmpeg `N-126125-g1d7b14f61`, +libass `0x1705000`. Kaynak arsiv, boyut ve SHA-256 artik +`bin/RUNTIME_MANIFEST.txt` icinde; `SHA256SUMS.txt`teki "intentionally +unspecified" notu KALDIRILDI. Eski nonfree DLL silinmedi, `bin/_old/` altina +alindi ve `.gitignore` ile hem depodan hem pakete girmekten uzak tutuldu +(`MLCPlayer.spec` acik dosya listesi kullanir, `bin/` glob'u YOKTUR). + +**Lisans dogrulamasi (kabul kriteri yeniden tanimlandi).** Ilk kriter +"ikilide `--enable-nonfree` dizesi aranir" idi; yeni yapida FFmpeg'in +`configure`/lisans SABITLERI hic bulunmuyor (linker kullanilmayanlari +atmis), bu yuzden o kriter bu yapi icin GECERSIZDIR — "bulamadim" ile "yok" +ayni sey degildir. Bunun yerine uc bagimsiz kanit kullanildi: + +1. Nonfree'yi ZORUNLU kilan bilesenlerin hicbiri ikilide yok: + `libfdk` / `fdk_aac` / `libnpp` / `nppi_` / `cuda-nvcc` / `decklink`. +2. Etiketin (`20260814`) RESMI build tarifi yalniz `--enable-gpl` ve + `--enable-version3` geciyor; `--enable-nonfree`, `--enable-libnpp` ve + `--enable-cuda-nvcc` YOK. CUDA serbest `--enable-cuda-llvm` yolundan. +3. Eski ikilide FFmpeg'in kendi lisans sabiti literal olarak + `nonfree and unredistributable` yaziyordu; yeni ikilide boyle bir sabit + YOK ve nonfree bilesen izi de yok. + +Sonuc: yeni yapi GPL(v3) tarafindadir ve MLC Player'in GPLv3 lisansiyla +uyumludur; dagitilabilir. + +**API uyumlulugu olculdu.** Urunun bagli oldugu 31 secenek/ozelligin TAMAMI +yeni surumde mevcut (eksik yok). Altyazi stil sozlesmesinin kritik DEGERLERI +de yazilip geri okundu: `sub-ass-override=force`, `sub-border-style= +background-box` ve `outline-and-shadow`, `#AARRGGBB` renkler +(`#FFF26A3D`, `#C80020A0`), `sub-shadow-offset`, `sub-use-margins`, +`sub-ass-force-margins`, `sub-margin-y`, `sub-pos`, `sub-scale`, +`sub-border-size` — 14/14 dogru geri okundu. + +**Not (davranis DEGISTIRILMEDI):** `sub-margin-y-offset` v0.36'da YOKTU ve +mevcut `sub-margin-y` tasariminin gerekcesi buydu. v0.41'de bu ozellik ARTIK +VAR. Gerekce eskidi ama bu turda hicbir altyazi yolu degistirilmedi; olasi +sadelestirme ayri bir turun konusudur. + +**Test:** `pytest -q tests` -> **3158 passed, 17 skipped** (degisimden onceki +sonucun birebir aynisi). + +**ACIK KALAN — fiziksel dogrulama.** Offscreen paketin gecmesi, altyazi +guvenli bandinin PIKSEL duzeyinde korundugunu KANITLAMAZ. mpv 0.36 -> 0.41 +ve FFmpeg 6 -> 8 atlamasindan sonra `o_band` / `p_ass_band` gercek video +kabulu yeniden kosulmalidir (gercek pencere + gercek MKV, kullanici onayiyla). +Bu kosum yapilana kadar guvenli bant "dogrulanmis" SAYILMAZ. + +## Yayin oncesi uyumluluk kontrol listesi + +Asagidakiler somut ve dogrulanabilir maddelerdir. Hicbiri hukuki gorus +degildir. + +### Kapatilmasi zorunlu + +- [ ] `mpv-2.dll` nonfree olmayan yapiyla degistirildi ve olcumle dogrulandi + (yukaridaki bolum). +- [ ] GPLv3+ birlesik `yt-dlp.exe` ve GPLv3 mpv icin **karsilik gelen kaynak + erisimi**: kullaniciya, kullandigi ikiliyle ayni surumun kaynagini + sunma yukumlulugu. Pratik yol, her ikili icin surum + kaynak arsiv + URL'sini ve bir yedek kopyayi saklamaktir. +- [ ] Kokteki `LICENSE` (GPLv3, 35.149 bayt) setup icine de girmeli; + `licenses/` klasoru pakete kopyalanmali. +- [ ] GPLv3'un onerdigi dosya basi telif/lisans bildirimleri kaynak + dosyalara eklenmeli. + +### OpenSubtitles API (olculdu) + +Resmi sartlar: her istekte `Api-Key` basligi, uygulama adi + surum tasiyan +benzersiz `User-Agent`, **saniyede 1 istek** sinirlamasi, indirme kotalari +kullanici rutbesine bagli. + +- [x] `app/opensubtitles.py` `Api-Key` gonderiyor ve + `USER_AGENT = "MLC Player Subtitle Center v1"` kullaniyor — sartlara + uygun bicimde ad + surum tasiyor. +- [x] Mevcut kod API anahtarini kullanicidan alir; uygulamaya gomulu anahtar + YOKTUR. **Bu teknik durum bir uyumluluk karari degildir.** Servis + yoneticisinin uygulama basina tek anahtar yonlendirmesi nedeniyle + cevrimici arama arayuzu kapali tutulur; acik sart veya yazili saglayici + onayi olmadan ne gomulu anahtar ne de kullanici-basina-anahtar akisi + yayinlanir. +- [x] **KAPANDI (16 Agustos 2026):** onleyici hiz sinirlamasi eklendi. + `MIN_REQUEST_INTERVAL_S = 1.0` ve `_respect_rate_limit()`, butun + isteklerin gectigi TEK bogaz noktasi olan `_call()` icinde. Bekleme + QThread worker'indadir, GUI donmaz; kilit es zamanli worker'larin ayni + pencerede iki istek gondermesini engeller; saat GERI giderse bekleme + aralikla SINIRLANIR. `429/406` tepkisel yolu AYNEN korunur — onleyici + sinir onu degil, servise gereksiz yuku engeller. + Sozlesme: 4 test (arka arkaya istekler araliklanir, yavas istek + araligi tuketir ve bosa beklenmez, saat geri giderse ust sinir, + anahtar yoksa aga cikilmadan once beklenmez). +- [ ] Indirilen altyazilarin yeniden dagitimi ve saklanmasi konusundaki + sartlar dogrulanmali (uygulama altyaziyi yalniz kullanicinin diskine + yaziyor; baska yere kopyalamiyor). + +### Kod imzalama ve SmartScreen + +- [ ] Kod imzalama sertifikasi arastirilmali. **Imza, lisans ve telif + yukumluluklerinin yerine GECMEZ**; yalnizca dagitim guvenini artirir. + Imzasiz setup Windows SmartScreen uyarisi uretir. + +### Avukata yoneltilecek acik sorular + +1. GPLv3 bir uygulamayla ayni pakette dagitilan GPLv3+ `yt-dlp.exe` icin + "karsilik gelen kaynak" yukumlulugunu, kaynak arsiv URL'si sunmak + karsiliyor mu, yoksa kopyayi bizim mi barindirmamiz gerekir? +2. H.264/H.265 decode iceren bir masaustu oynaticinin Turkiye'den ucretsiz + dagitiminda patent havuzu (MPEG-LA / Access Advance) yukumlulugu dogar mi? +3. Acik kaynak bir Windows masaustu istemcisinde "uygulama basina tek API + anahtari" nasil dagitilmalidir? Anahtarin kaynakta/ikili icinde gorulebilir + olmasi kabul ediliyor mu; kullanicinin kendi anahtarini girmesi yasak mi? + OpenSubtitles'tan yazili cevap alinmadan arayuz yeniden acilmayacak. + +### Boyut etkisi (olculdu) + +`mpv-2.dll` ~99 MB + `deno.exe` ~97 MB + `yt-dlp.exe` ~18 MB; yalnizca bu uc +dosya ~214 MB'dir. Kurulum boyutu ve setup sikistirma karari bu gercege gore +verilmelidir. + +## Uygulama ikonu (tek gorsel kimlik) + +Kaynak asset `assets/mlc-player-icon.png` (1254x1254 RGBA, kullanici +tarafindan saglandi, degistirilmeden alindi). Windows ICO ondan LANCZOS ile +uretildi: `assets/mlc-player-icon.ico` (16, 20, 24, 32, 40, 48, 64, 128, 256 +px; alfa korundu). Olcu ve SHA-256 degerleri `assets/ICON_MANIFEST.txt` +icindedir. + +Paketleme: + + _internal\assets\mlc-player-icon.ico + +`MLCPlayer.spec` icinde `EXE(..., icon='assets/mlc-player-icon.ico')`. + +### Setup icin KESIN alanlar (ileride uygulanacak) + +Inno `.iss` bu turda URETILMEDI. Uygulanacak alanlar: + +- `SetupIconFile=assets\mlc-player-icon.ico` +- `UninstallDisplayIcon={app}\MLC Player.exe` +- Masaustu ve Baslat menusu kisayollari ana EXE ikonunu kullanir. +- Setup ve kaldiricinin gercek gorunumu Inno kabulunde AYRICA dogrulanir. diff --git a/docs/PROJECT_STATUS.md b/docs/history/PROJECT_STATUS.md similarity index 100% rename from docs/PROJECT_STATUS.md rename to docs/history/PROJECT_STATUS.md diff --git a/docs/ROADMAP.md b/docs/history/ROADMAP.md similarity index 100% rename from docs/ROADMAP.md rename to docs/history/ROADMAP.md diff --git a/CODE_SIGNING_POLICY.md b/docs/policies/CODE_SIGNING_POLICY.md similarity index 100% rename from CODE_SIGNING_POLICY.md rename to docs/policies/CODE_SIGNING_POLICY.md diff --git a/PRIVACY.md b/docs/policies/PRIVACY.md similarity index 100% rename from PRIVACY.md rename to docs/policies/PRIVACY.md diff --git a/AGENTS.md b/docs/process/AGENTS.md similarity index 85% rename from AGENTS.md rename to docs/process/AGENTS.md index 5f0868b..e0e6474 100644 --- a/AGENTS.md +++ b/docs/process/AGENTS.md @@ -1,6 +1,11 @@ # MLC Player agent başlangıç sözleşmesi -Bu dosya, projeyi devralan her agentın ilk giriş noktasıdır. +Bu dosya, projeyi devralan her agentın ilk giriş noktasıdır. Depo kökünde +yalnız `Start.bat` ve `LICENSE` tutulduğu için dosya `docs/process/` +altındadır: Claude onu `.claude/CLAUDE.md` üzerinden otomatik yükler; kök +dizinde `AGENTS.md` arayan araçlara (ör. Codex) oturum başında bu yol +verilmelidir. Hangi bilginin hangi belgede tutulduğu `docs/README.md` +içindedir. ## Her oturumun başında @@ -11,17 +16,17 @@ Bu dosya, projeyi devralan her agentın ilk giriş noktasıdır. adım yalnız oradadır. 3. İlgili kanıtı `docs/VERIFICATION_LEDGER.json` içinde kimliğiyle bul. 4. Yalnız görev gerektiriyorsa tarihsel ayrıntı için - `docs/PROJECT_STATUS.md`, `docs/ROADMAP.md` veya - `docs/ENGINEERING_AUDIT.md` dosyasına git. Tarihçeden güncel durum çıkarma. + `docs/history/PROJECT_STATUS.md`, `docs/history/ROADMAP.md` veya + `docs/history/ENGINEERING_AUDIT.md` dosyasına git. Tarihçeden güncel durum çıkarma. 5. Kaynak, güncel durum ve eski rapor çelişirse yeniden ölçülen kaynak davranışı esas alınır; çelişki kayda geçirilir. -6. `docs/CHANGE_WORKFLOW.md` içindeki etkinlik kapısını kontrol et. PR kapısı +6. `docs/process/CHANGE_WORKFLOW.md` içindeki etkinlik kapısını kontrol et. PR kapısı aktifse doğrudan `master` değişikliği/push'u yapma; `codex/` dalı, zorunlu `test` check'i ve merge-commit sırasını kullan. -7. Mimari veya gerçek Windows kalite işi `docs/QUALITY_EVOLUTION_PLAN.md` - üzerinden yürür. Güncel modül yorumu `docs/ARCHITECTURE_INVENTORY.md`, - makinece güncellik kapısı `docs/ARCHITECTURE_INVENTORY.json`, gerçek cihaz - senaryosu `docs/WINDOWS_ACCEPTANCE_MATRIX.md` içindedir; eksik/eskimiş +7. Mimari veya gerçek Windows kalite işi `docs/quality/QUALITY_EVOLUTION_PLAN.md` + üzerinden yürür. Güncel modül yorumu `docs/quality/ARCHITECTURE_INVENTORY.md`, + makinece güncellik kapısı `docs/quality/ARCHITECTURE_INVENTORY.json`, gerçek cihaz + senaryosu `docs/quality/WINDOWS_ACCEPTANCE_MATRIX.md` içindedir; eksik/eskimiş satır sessizce PASS yapılmaz. ## Kanıt sınırları @@ -111,4 +116,4 @@ yazma yapılmaz. - Build, kurulum/kaldırma, commit, push, PR oluşturma, PR birleştirme, tag ve release ayrı ayrı açık kullanıcı onayı ister. - Force-push ve GitHub protection bypass yapılmaz. -- Yayın sırasının tek resmî kaynağı `docs/RELEASE_PROCESS.md` dosyasıdır. +- Yayın sırasının tek resmî kaynağı `docs/release/RELEASE_PROCESS.md` dosyasıdır. diff --git a/docs/CHANGE_WORKFLOW.md b/docs/process/CHANGE_WORKFLOW.md similarity index 98% rename from docs/CHANGE_WORKFLOW.md rename to docs/process/CHANGE_WORKFLOW.md index 10beb89..a118199 100644 --- a/docs/CHANGE_WORKFLOW.md +++ b/docs/process/CHANGE_WORKFLOW.md @@ -2,7 +2,7 @@ Bu dosya, `master` için PR + zorunlu GitHub Actions kontrolü etkin olduğunda kullanılacak **tek değişiklik-akışı sözleşmesidir**. Yayın artifact sırası -yalnız `docs/RELEASE_PROCESS.md` içindedir. +yalnız `docs/release/RELEASE_PROCESS.md` içindedir. ## Etkinlik kapısı @@ -130,7 +130,7 @@ yerel annotated tag bu **exact merge commit** üzerinde yürür. Bu durumda yayın sürecindeki `git push origin master` bir değişiklik taşımaz; yalnız yerel HEAD ile origin/master eşliğini doğrular. Tag push'u ve release -yine `docs/RELEASE_PROCESS.md` sırasına ve ayrı onaylara tabidir. +yine `docs/release/RELEASE_PROCESS.md` sırasına ve ayrı onaylara tabidir. ## Planlanan GitHub koruması diff --git a/docs/ARCHITECTURE_INVENTORY.json b/docs/quality/ARCHITECTURE_INVENTORY.json similarity index 62% rename from docs/ARCHITECTURE_INVENTORY.json rename to docs/quality/ARCHITECTURE_INVENTORY.json index a3d18fe..921d7ce 100644 --- a/docs/ARCHITECTURE_INVENTORY.json +++ b/docs/quality/ARCHITECTURE_INVENTORY.json @@ -8,8 +8,8 @@ "modules": [ { "path": "app/video_frame.py", - "sha256": "7fd53f7de53351e68aede7263f7d2bace6ef9a04c1a3b4b494bdeae3173d75f7", - "lines": 2814, + "sha256": "346ac5b1c44968ed11d4998579a78ebaff6b73f29fdf0a52c0b6e8770b3c8544", + "lines": 2811, "classes": 3, "top_functions": 5, "indented_functions": 112, @@ -17,8 +17,8 @@ }, { "path": "app/media_controls.py", - "sha256": "476199e40ca5d0a75080ee37d84e02637fad229902472bc880383921a04768e2", - "lines": 1698, + "sha256": "949e822c23cb4f8c0e3e3a12c0b4581a2eac4f6638caa61072385793155f2c1d", + "lines": 1717, "classes": 0, "top_functions": 74, "indented_functions": 2, @@ -26,17 +26,17 @@ }, { "path": "app/player.py", - "sha256": "e8d12b71723a0a5b64db96ebc41851f0e828e248faf4cfce194febd0c6792e52", - "lines": 1473, + "sha256": "2eba14bba6fc07714f8ab38fdbee46c8dcaaedd6d66dbcd0c6a4690813c1bfa5", + "lines": 1524, "classes": 1, - "top_functions": 1, - "indented_functions": 85, - "self_state": 59 + "top_functions": 3, + "indented_functions": 86, + "self_state": 60 }, { "path": "app/playlist_panel.py", - "sha256": "837dfd4836a7ab87c3f6d51240e26ffc8989902cf17b4966e95a12c7295f2fc2", - "lines": 1144, + "sha256": "930d662a48ce5af5ecd134df72065c8f704017a75860105f72296adc43c6b490", + "lines": 1147, "classes": 5, "top_functions": 0, "indented_functions": 72, @@ -44,8 +44,8 @@ }, { "path": "app/menu_actions.py", - "sha256": "7fab77558d32afcdaabb637cf1772a88ae143b8d997f657147b4adde54b1229b", - "lines": 1169, + "sha256": "37c8274259d7485cd077f645a1a9232a89575764669167198a436822871606b3", + "lines": 1170, "classes": 0, "top_functions": 39, "indented_functions": 5, @@ -53,8 +53,8 @@ }, { "path": "app/updater.py", - "sha256": "315bbe435492acf650c7666ac02fc5b43a590a63bcc0b0f054d83ce9315619fc", - "lines": 1032, + "sha256": "54074fed5e4bb44dbc105e9b87b7d6c024180b6e9812613b098a1911c0a61b2c", + "lines": 1046, "classes": 4, "top_functions": 19, "indented_functions": 27, diff --git a/docs/ARCHITECTURE_INVENTORY.md b/docs/quality/ARCHITECTURE_INVENTORY.md similarity index 98% rename from docs/ARCHITECTURE_INVENTORY.md rename to docs/quality/ARCHITECTURE_INVENTORY.md index 4cf4fbc..0ea8b7c 100644 --- a/docs/ARCHITECTURE_INVENTORY.md +++ b/docs/quality/ARCHITECTURE_INVENTORY.md @@ -14,7 +14,7 @@ sahipliği, yaşam döngüsü, bağımlılık ve test sınırı birlikte incelen kodunda davranış değişikliği yapılmaz. Altı kaynak dosyanın normalize SHA-256 ve yapı sayıları -`docs/ARCHITECTURE_INVENTORY.json` içindedir. Regresyon testi bu değerleri canlı +`docs/quality/ARCHITECTURE_INVENTORY.json` içindedir. Regresyon testi bu değerleri canlı kaynakla karşılaştırır; bu dosyalardan biri değişip envanter güncellenmezse CI fail-closed durur. @@ -188,7 +188,7 @@ ve çeviri metni değişmez. Uygulama kapısı: -1. `docs/WINDOWS_ACCEPTANCE_MATRIX.md` P0 senaryolarını mevcut runner'larla +1. `docs/quality/WINDOWS_ACCEPTANCE_MATRIX.md` P0 senaryolarını mevcut runner'larla eşleştir ve eksik başlangıç kanıtını belirle. 2. Yeni yaprak modül için geçerli/geçersiz URL, Windows/UNC yol, klasör sıralama ve güvenli host doğrudan regresyonlarını önce kırmızı çalıştır. diff --git a/docs/QUALITY_EVOLUTION_PLAN.md b/docs/quality/QUALITY_EVOLUTION_PLAN.md similarity index 87% rename from docs/QUALITY_EVOLUTION_PLAN.md rename to docs/quality/QUALITY_EVOLUTION_PLAN.md index 78d9523..f50bc00 100644 --- a/docs/QUALITY_EVOLUTION_PLAN.md +++ b/docs/quality/QUALITY_EVOLUTION_PLAN.md @@ -28,16 +28,19 @@ yapmadan bulabilmesini sağlamaktır. ## Tek kaynak ve güncellik düzeni +Bu tablo yalnız kalite programının kaynaklarını listeler; projenin bütün +belge haritası `docs/README.md` içindedir. + | Bilgi | Tek resmî kaynak | Ne zaman güncellenir | | --- | --- | --- | | Güncel durum ve sıradaki tek adım | `docs/CONTINUITY.md` | Her karar/kanıt turunun sonunda | | Makinece okunabilir sonuç | `docs/VERIFICATION_LEDGER.json` | Sonraki kararda kullanılacak her sonuçta | -| Bu programın faz ve kapıları | `docs/QUALITY_EVOLUTION_PLAN.md` | Faz başlarken veya kapanırken | -| Modül sahipliği ve ayrıştırma adayları | `docs/ARCHITECTURE_INVENTORY.md` | Ölçüm veya sahiplik değiştiğinde | -| Altı büyük modülün hash/yapı güncellik kapısı | `docs/ARCHITECTURE_INVENTORY.json` | Bu altı kaynaktan biri değiştiğinde | -| Gerçek Windows senaryoları | `docs/WINDOWS_ACCEPTANCE_MATRIX.md` | Senaryo/ortam/artifact sonucu değiştiğinde | -| Yayın sırası | `docs/RELEASE_PROCESS.md` | Yalnız yayın sözleşmesi değiştiğinde | -| Tarihsel anlatı | `docs/PROJECT_STATUS.md`, `docs/ROADMAP.md`, `docs/ENGINEERING_AUDIT.md` | Tarihsel checkpoint gerektiğinde | +| Bu programın faz ve kapıları | `docs/quality/QUALITY_EVOLUTION_PLAN.md` | Faz başlarken veya kapanırken | +| Modül sahipliği ve ayrıştırma adayları | `docs/quality/ARCHITECTURE_INVENTORY.md` | Ölçüm veya sahiplik değiştiğinde | +| Altı büyük modülün hash/yapı güncellik kapısı | `docs/quality/ARCHITECTURE_INVENTORY.json` | Bu altı kaynaktan biri değiştiğinde | +| Gerçek Windows senaryoları | `docs/quality/WINDOWS_ACCEPTANCE_MATRIX.md` | Senaryo/ortam/artifact sonucu değiştiğinde | +| Yayın sırası | `docs/release/RELEASE_PROCESS.md` | Yalnız yayın sözleşmesi değiştiğinde | +| Tarihsel anlatı | `docs/history/PROJECT_STATUS.md`, `docs/history/ROADMAP.md`, `docs/history/ENGINEERING_AUDIT.md` | Tarihsel checkpoint gerektiğinde | Tarihsel belgeler güncel karar kaynağı değildir. Güncel kaynakla çelişen eski yorum sessizce düzeltilmez veya karar olarak kullanılmaz; yeni ölçüm ve gerekirse @@ -64,7 +67,7 @@ adıma işaret ediyor ve ürün kodunda değişiklik yok. **Durum:** TAMAMLANDI -Önce `docs/ARCHITECTURE_INVENTORY.md` tamamlanır. Her büyük modül için: +Önce `docs/quality/ARCHITECTURE_INVENTORY.md` tamamlanır. Her büyük modül için: - kullanıcıya görünen sorumluluklar; - state, timer, thread, process ve native kaynak sahipliği; @@ -83,7 +86,7 @@ göre seçilmiş ve kapsam kullanıcıya sunulmuş olmalıdır. **Durum:** AKTİF — P0 EŞLEMESİ TAMAM, 6 PASS / 0 FAIL / 2 NOT_RUN -`docs/WINDOWS_ACCEPTANCE_MATRIX.md` içindeki P0 senaryoları exact commit, +`docs/quality/WINDOWS_ACCEPTANCE_MATRIX.md` içindeki P0 senaryoları exact commit, runtime ve mümkünse artifact SHA-256 kimliğiyle çalıştırılır. Var olan native runner'lar yeniden kullanılır; aynı davranış için ikinci bir test sistemi yazılmaz. Donanım yoksa sonuç `BLOCKED` kalır, PASS yapılmaz. diff --git a/docs/WINDOWS_ACCEPTANCE_MATRIX.md b/docs/quality/WINDOWS_ACCEPTANCE_MATRIX.md similarity index 100% rename from docs/WINDOWS_ACCEPTANCE_MATRIX.md rename to docs/quality/WINDOWS_ACCEPTANCE_MATRIX.md diff --git a/docs/PACKAGING_PLAN.md b/docs/release/PACKAGING_PLAN.md similarity index 77% rename from docs/PACKAGING_PLAN.md rename to docs/release/PACKAGING_PLAN.md index c187fa5..67f180b 100644 --- a/docs/PACKAGING_PLAN.md +++ b/docs/release/PACKAGING_PLAN.md @@ -4,14 +4,17 @@ > paketleme/installer kararları ve güncel UX sözleşmesi bu belgede tutulur; > canlı durum veya sıradaki iş buradan çıkarılmaz. Güncel devir > için `docs/CONTINUITY.md`, kesin yayın sırası için yalnız -> `docs/RELEASE_PROCESS.md` kullanılır. Aşağıdaki eski sürüm, ölçüm ve checklist -> snapshot'ları tarihsel kayıttır; canlı kaynakla yeniden doğrulanmadan güncel -> engel sayılmaz. +> `docs/release/RELEASE_PROCESS.md`, doğrulanmış sonuçlar için +> `docs/VERIFICATION_LEDGER.json` kullanılır. Aşağıdaki eski sürüm, ölçüm ve +> checklist snapshot'ları tarihsel kayıttır; canlı kaynakla yeniden +> doğrulanmadan güncel engel sayılmaz. Tarihsel paketleme kayıtları, eski +> uyumluluk kontrol listesi ve ilk ikon notları 3 Ekim 2026'da +> `docs/history/PACKAGING_HISTORY.md` dosyasına taşındı. Bu dosya Windows kurulumunun güncel UX sözleşmesini, kalıcı paketleme kararlarını ve tarihsel tasarım kayıtlarını açıklar. Paketleme aşamasında gerekçeler buradan okunabilir; yürütme sırası -`docs/RELEASE_PROCESS.md` üzerinden alınır ve o günkü kaynak, araç sürümleri +`docs/release/RELEASE_PROCESS.md` üzerinden alınır ve o günkü kaynak, araç sürümleri ile Windows kabul sonuçları yeniden doğrulanır. ## Güncel installer UX sözleşmesi @@ -330,7 +333,7 @@ ve gizli dosya niteliğiyle saklanmaya çalışılmayacaktır. ## Bu kararın nedeni -Mevcut `MLCPlayer.spec`, `COLLECT` aşaması içermediği için onefile paket +Mevcut `packaging/MLCPlayer.spec`, `COLLECT` aşaması içermediği için onefile paket üretmektedir. Onefile biçimi Python, Qt ve yaklaşık 100 MB büyüklüğündeki `mpv-2.dll` dosyasını her açılışta `%TEMP%\_MEI...` altına çıkartır. Daha önce başka uygulamalarda geçici dizin temizliği, antivirüs veya kilitli DLL @@ -375,10 +378,10 @@ Bu yapı MLC Player'a uyarlanacak; dosyalar körlemesine kopyalanmayacaktır. - `onedir` üretilecek. - `EXE` aşamasında `exclude_binaries=True` kullanılacak. - Sonuna `COLLECT` aşaması eklenecek. -- Ana çıktı klasörü `dist\MLC Player\` olacak. +- Ana çıktı klasörü `output\dist\MLC Player\` olacak (3 Ekim 2026'dan beri bütün üretim çıktıları `output\` altındadır). - PyQt6 ve gerekli Python çalışma zamanı `_internal` altında kalacak. - `mpv-2.dll`, `_internal\bin\mpv-2.dll` konumunda paketlenecek. -- Mevcut `main.py::get_bin_dir()` davranışı gerçek paket üzerinde +- Mevcut `app/main.py::get_bin_dir()` davranışı gerçek paket üzerinde doğrulanacak. Onedir çalışırken `sys._MEIPASS` üzerinden `_internal\bin` bulunmalıdır. - `libmpv.dll.a` çalışma zamanı dosyası değildir. Kuruluma eklenmeyecek; @@ -489,16 +492,39 @@ kullanılmayacaktır. - Kaldırma sırasında uygulama çalışıyorsa kullanıcıya anlaşılır bildirim verilmeli veya kontrollü kapanış uygulanmalıdır. - Kaldırıcı yalnız kurulumun sahip olduğu dosyaları silmelidir. -- Kullanıcı ayarları ve loglarının korunması/silinmesi için ayrıca açık ürün - kararı alınmalıdır; kurulum klasörünü geniş jokerlerle temizlemek bu kararın - yerine geçmez. +- Kullanıcı kararı (8 Eylül 2026): ana kaldırıcı eklenti kuruluysa onu da + kaldırma seçeneğini varsayılan işaretli gösterir. Ayarlar/geçmiş/kayıtlı + altyazı kimlikleri ile günlük/küçük resim önbelleği/geçici güncelleme + dosyaları iki ayrı ve varsayılan boş seçimdir; sessiz kaldırmada da + kullanıcı verisi korunur. +- Kurulum makineye özgü rastgele bir kimliği HKLM'de saklar. Kurulu Player ilk + normal açılışta görünmez, tetikleyicisiz ve yalnız isteğe bağlı çalışan düşük + yetkili görevi kendi SID'sine bağlar ve aynı kimliği kendi HKCU alanına + kaydeder. Kaldırıcı HKU içinde bu kimlikle tam bir SID eşleşmesi ister; sıfır + veya birden fazla eşleşmede kullanıcı verisini silmiş saymaz. IPC yalnız bu + kullanıcının `%LOCALAPPDATA%\Programs\MLC Player\UninstallCleanup\v1\ipc` dizinindedir; + helper, görev, IPC ve kayıt normal/sessiz her kaldırmada temizlenir. +- Temizlik yalnız kayıtlı Windows hesabının exact ürün alanlarıyla sınırlıdır: + `HKCU\Software\MLCPlayer`, ürünün tüm ad-alanı eşleşen + OpenSubtitles Credential Manager hedefleri, `%APPDATA%\MLCPlayer` altındaki + exact ayar/log girdileri, `%LOCALAPPDATA%\MLCPlayer\cache\thumbnails` + altındaki 64-hex `.jpg` dosyaları ve `%LOCALAPPDATA%\MLCPlayerUpdate_*` + altındaki markalı kurucu/imza dosyaları. `%LOCALAPPDATA%\python` gibi + paylaşılan eski yollar, kullanıcı medya/altyazı/screenshot dosyaları ve + beklenmeyen girdiler silinmez; seçilmiş tam temizlikte böyle bir girdi + kalırsa sonuç kullanıcıya açık başarısızlık olarak bildirilir. +- Eklenti kaldırma yolu exact HKLM kaydı, `{app}` sınırı ve `unins*.exe` + kimliğiyle doğrulanır; eklenti kaldırma başarısızsa ana ürün dosyaları + silinmeden işlem durur. Kullanıcı verisi yalnız ana kaldırma + `usPostUninstall` aşamasına ulaştıktan sonra değişir; iptal, çalışan ürün + mutex'i veya erken kaldırma hatası kullanıcı verisine dokunmaz. ## Paketleme öncesi zorunlu doğrulama 1. Tam pytest paketi yeşil olmalı. 2. `compileall` ve `git diff --check` temiz olmalı. 3. Temiz `build`, `dist` ve installer çıktı dizinleriyle yeniden derlenmeli. -4. `dist\MLC Player\MLC Player.exe` oluşmalı. +4. `output\dist\MLC Player\MLC Player.exe` oluşmalı. 5. `_internal\python314.dll`, PyQt6 ve gerekli `.pyd` dosyaları bulunmalı. 6. `_internal\bin\mpv-2.dll` bulunmalı ve beklenen SHA-256 ile eşleşmeli. 7. Kaynak `.py`, test, cache, yerel log veya kullanıcı ayarı bulunmamalı. @@ -553,7 +579,7 @@ girdiği, lisans ve boyut ölçümleri. giriş/çıkış şartı, dinamik varlık sözleşmesi ve hata hâlinde nerede durulacağı tek resmî kaynaktadır: -> **`docs/RELEASE_PROCESS.md`** +> **`docs/release/RELEASE_PROCESS.md`** Buradan çıkmayan iki kural, paketleme turlarında da geçerlidir: @@ -719,200 +745,33 @@ zaten icerir). - Bu bolum hukuki danismanlik DEGILDIR; MLC Player'in butununun lisans durumu hakkinda bu turda kesin hukum verilmemistir. -## Tarihsel paketleme kayıtları - -Aşağıdaki tarihli libmpv engel ve kapanış ölçümleri dondurulmuş tarihsel -snapshot'tır. Güncel dağıtım durumu bu bölümden çıkarılmaz; `CONTINUITY.md` ve -`VERIFICATION_LEDGER.json` üzerinden okunur. - -### YAYIN ENGELI: mpv-2.dll DAGITILAMAZ -> COZULDU (16 Agustos 2026) - -**DURUM: kapatildi.** Asagidaki teshis kayit olarak korunuyor; en altta -degisim ve dogrulama sonuclari var. - -**Tahmin degil, ikilinin KENDI icinden okundu.** - -`bin/mpv-2.dll` (99.390.990 bayt, 25 Kasim 2024) icine gomulu FFmpeg -`configure` dizesi: - - --prefix=/__w/mpv-winbuild-cmake/mpv-winbuild-cmake/build64/install/mingw - ... --enable-gpl --enable-version3 --enable-nonfree ... - --enable-libx264 --enable-libx265 --enable-libxvid - --enable-cuda --enable-cuvid --enable-nvdec --enable-nvenc ... - -FFmpeg'in kendi `configure` yardim metni `--enable-nonfree` icin sunu der: -*"allow use of nonfree code, the resulting libs and binaries will be -unredistributable"*. Yani bu DLL kisisel kullanim icin derlenebilir ama -**hicbir bicimde ucuncu kisilere dagitilamaz** — ne setup icinde, ne yaninda. - -- `--enable-lgpl` YOK; `--enable-gpl` + `--enable-version3` VAR. Yani LGPL - degil, GPLv3 tarafindadir. MLC Player da GPLv3 oldugu icin bu KISIM sorun - degildir; sorun yalniz `nonfree`dir. -- nonfree'yi tetikleyen bilesen `fdk-aac` DEGIL: ikilide `libfdk` izi yok, - buna karsilik `nvenc`, `cuda`, `cuvid` var. Kaynak CUDA/NVENC tarafidir. -- `bin/SHA256SUMS.txt` bu dosya icin kaynagi bilerek bos birakiyor - ("source/version is intentionally unspecified"). 99 MB'lik bir ikili - kaynagi kayitli olmadan dagitilamaz; `yt-dlp`/`deno` icin uygulanan - provenance disiplini buna da uygulanmalidir. - -**Cozum basit ve dogrulandi:** ayni yukari-akis projenin (`mpv-winbuild-cmake`) -GUNCEL yapilandirmasi `--enable-nonfree` KULLANMIYOR; yalniz `--enable-gpl` -ve `--enable-version3` geciyor. Elimizdeki DLL eski/varyant bir yapidir. - -Yapilacaklar (release turunda, sirasiyla): - -1. `mpv-2.dll` guncel ve `nonfree` ICERMEYEN bir yapiyla degistir. -2. Degistirdikten SONRA ayni olcumu tekrarla: gomulu `configure` dizesinde - `--enable-nonfree` OLMADIGINI dogrula. Bu, kabul kriteridir. -3. Surum, kaynak URL, boyut ve SHA-256'yi `bin/RUNTIME_MANIFEST.txt` icine - yaz; `SHA256SUMS.txt`teki "intentionally unspecified" notunu kaldir. -4. mpv/FFmpeg icin GPLv3 lisans metnini ve karsilik gelen kaynak erisimini - pakete ekle. -5. Codec/patent tarafi ayrica degerlendirilmeli: `libx264`, `libx265`, - `libxvid` GPL'dir (lisans tarafi GPLv3 ile uyumludur) ancak H.264/H.265 - PATENT yukumlulukleri lisanstan AYRI bir konudur ve ticari dagitimda - avukata sorulmalidir. - -### YAPILDI: degisim ve dogrulama (16 Agustos 2026) - -Yeni ikili: `mpv v0.41.0-923-g7b8915bc1`, FFmpeg `N-126125-g1d7b14f61`, -libass `0x1705000`. Kaynak arsiv, boyut ve SHA-256 artik -`bin/RUNTIME_MANIFEST.txt` icinde; `SHA256SUMS.txt`teki "intentionally -unspecified" notu KALDIRILDI. Eski nonfree DLL silinmedi, `bin/_old/` altina -alindi ve `.gitignore` ile hem depodan hem pakete girmekten uzak tutuldu -(`MLCPlayer.spec` acik dosya listesi kullanir, `bin/` glob'u YOKTUR). - -**Lisans dogrulamasi (kabul kriteri yeniden tanimlandi).** Ilk kriter -"ikilide `--enable-nonfree` dizesi aranir" idi; yeni yapida FFmpeg'in -`configure`/lisans SABITLERI hic bulunmuyor (linker kullanilmayanlari -atmis), bu yuzden o kriter bu yapi icin GECERSIZDIR — "bulamadim" ile "yok" -ayni sey degildir. Bunun yerine uc bagimsiz kanit kullanildi: - -1. Nonfree'yi ZORUNLU kilan bilesenlerin hicbiri ikilide yok: - `libfdk` / `fdk_aac` / `libnpp` / `nppi_` / `cuda-nvcc` / `decklink`. -2. Etiketin (`20260814`) RESMI build tarifi yalniz `--enable-gpl` ve - `--enable-version3` geciyor; `--enable-nonfree`, `--enable-libnpp` ve - `--enable-cuda-nvcc` YOK. CUDA serbest `--enable-cuda-llvm` yolundan. -3. Eski ikilide FFmpeg'in kendi lisans sabiti literal olarak - `nonfree and unredistributable` yaziyordu; yeni ikilide boyle bir sabit - YOK ve nonfree bilesen izi de yok. - -Sonuc: yeni yapi GPL(v3) tarafindadir ve MLC Player'in GPLv3 lisansiyla -uyumludur; dagitilabilir. - -**API uyumlulugu olculdu.** Urunun bagli oldugu 31 secenek/ozelligin TAMAMI -yeni surumde mevcut (eksik yok). Altyazi stil sozlesmesinin kritik DEGERLERI -de yazilip geri okundu: `sub-ass-override=force`, `sub-border-style= -background-box` ve `outline-and-shadow`, `#AARRGGBB` renkler -(`#FFF26A3D`, `#C80020A0`), `sub-shadow-offset`, `sub-use-margins`, -`sub-ass-force-margins`, `sub-margin-y`, `sub-pos`, `sub-scale`, -`sub-border-size` — 14/14 dogru geri okundu. - -**Not (davranis DEGISTIRILMEDI):** `sub-margin-y-offset` v0.36'da YOKTU ve -mevcut `sub-margin-y` tasariminin gerekcesi buydu. v0.41'de bu ozellik ARTIK -VAR. Gerekce eskidi ama bu turda hicbir altyazi yolu degistirilmedi; olasi -sadelestirme ayri bir turun konusudur. - -**Test:** `pytest -q tests` -> **3158 passed, 17 skipped** (degisimden onceki -sonucun birebir aynisi). - -**ACIK KALAN — fiziksel dogrulama.** Offscreen paketin gecmesi, altyazi -guvenli bandinin PIKSEL duzeyinde korundugunu KANITLAMAZ. mpv 0.36 -> 0.41 -ve FFmpeg 6 -> 8 atlamasindan sonra `o_band` / `p_ass_band` gercek video -kabulu yeniden kosulmalidir (gercek pencere + gercek MKV, kullanici onayiyla). -Bu kosum yapilana kadar guvenli bant "dogrulanmis" SAYILMAZ. - -## Yayin oncesi uyumluluk kontrol listesi - -Asagidakiler somut ve dogrulanabilir maddelerdir. Hicbiri hukuki gorus -degildir. - -### Kapatilmasi zorunlu - -- [ ] `mpv-2.dll` nonfree olmayan yapiyla degistirildi ve olcumle dogrulandi - (yukaridaki bolum). -- [ ] GPLv3+ birlesik `yt-dlp.exe` ve GPLv3 mpv icin **karsilik gelen kaynak - erisimi**: kullaniciya, kullandigi ikiliyle ayni surumun kaynagini - sunma yukumlulugu. Pratik yol, her ikili icin surum + kaynak arsiv - URL'sini ve bir yedek kopyayi saklamaktir. -- [ ] Kokteki `LICENSE` (GPLv3, 35.149 bayt) setup icine de girmeli; - `licenses/` klasoru pakete kopyalanmali. -- [ ] GPLv3'un onerdigi dosya basi telif/lisans bildirimleri kaynak - dosyalara eklenmeli. - -### OpenSubtitles API (olculdu) - -Resmi sartlar: her istekte `Api-Key` basligi, uygulama adi + surum tasiyan -benzersiz `User-Agent`, **saniyede 1 istek** sinirlamasi, indirme kotalari -kullanici rutbesine bagli. - -- [x] `app/opensubtitles.py` `Api-Key` gonderiyor ve - `USER_AGENT = "MLC Player Subtitle Center v1"` kullaniyor — sartlara - uygun bicimde ad + surum tasiyor. -- [x] Mevcut kod API anahtarini kullanicidan alir; uygulamaya gomulu anahtar - YOKTUR. **Bu teknik durum bir uyumluluk karari degildir.** Servis - yoneticisinin uygulama basina tek anahtar yonlendirmesi nedeniyle - cevrimici arama arayuzu kapali tutulur; acik sart veya yazili saglayici - onayi olmadan ne gomulu anahtar ne de kullanici-basina-anahtar akisi - yayinlanir. -- [x] **KAPANDI (16 Agustos 2026):** onleyici hiz sinirlamasi eklendi. - `MIN_REQUEST_INTERVAL_S = 1.0` ve `_respect_rate_limit()`, butun - isteklerin gectigi TEK bogaz noktasi olan `_call()` icinde. Bekleme - QThread worker'indadir, GUI donmaz; kilit es zamanli worker'larin ayni - pencerede iki istek gondermesini engeller; saat GERI giderse bekleme - aralikla SINIRLANIR. `429/406` tepkisel yolu AYNEN korunur — onleyici - sinir onu degil, servise gereksiz yuku engeller. - Sozlesme: 4 test (arka arkaya istekler araliklanir, yavas istek - araligi tuketir ve bosa beklenmez, saat geri giderse ust sinir, - anahtar yoksa aga cikilmadan once beklenmez). -- [ ] Indirilen altyazilarin yeniden dagitimi ve saklanmasi konusundaki - sartlar dogrulanmali (uygulama altyaziyi yalniz kullanicinin diskine - yaziyor; baska yere kopyalamiyor). - -### Kod imzalama ve SmartScreen - -- [ ] Kod imzalama sertifikasi arastirilmali. **Imza, lisans ve telif - yukumluluklerinin yerine GECMEZ**; yalnizca dagitim guvenini artirir. - Imzasiz setup Windows SmartScreen uyarisi uretir. - -### Avukata yoneltilecek acik sorular - -1. GPLv3 bir uygulamayla ayni pakette dagitilan GPLv3+ `yt-dlp.exe` icin - "karsilik gelen kaynak" yukumlulugunu, kaynak arsiv URL'si sunmak - karsiliyor mu, yoksa kopyayi bizim mi barindirmamiz gerekir? -2. H.264/H.265 decode iceren bir masaustu oynaticinin Turkiye'den ucretsiz - dagitiminda patent havuzu (MPEG-LA / Access Advance) yukumlulugu dogar mi? -3. Acik kaynak bir Windows masaustu istemcisinde "uygulama basina tek API - anahtari" nasil dagitilmalidir? Anahtarin kaynakta/ikili icinde gorulebilir - olmasi kabul ediliyor mu; kullanicinin kendi anahtarini girmesi yasak mi? - OpenSubtitles'tan yazili cevap alinmadan arayuz yeniden acilmayacak. - -### Boyut etkisi (olculdu) - -`mpv-2.dll` ~99 MB + `deno.exe` ~97 MB + `yt-dlp.exe` ~18 MB; yalnizca bu uc -dosya ~214 MB'dir. Kurulum boyutu ve setup sikistirma karari bu gercege gore -verilmelidir. - -## Uygulama ikonu (tek gorsel kimlik) - -Kaynak asset `assets/mlc-player-icon.png` (1254x1254 RGBA, kullanici -tarafindan saglandi, degistirilmeden alindi). Windows ICO ondan LANCZOS ile -uretildi: `assets/mlc-player-icon.ico` (16, 20, 24, 32, 40, 48, 64, 128, 256 -px; alfa korundu). Olcu ve SHA-256 degerleri `assets/ICON_MANIFEST.txt` -icindedir. - -Paketleme: - - _internal\assets\mlc-player-icon.ico - -`MLCPlayer.spec` icinde `EXE(..., icon='assets/mlc-player-icon.ico')`. - -### Setup icin KESIN alanlar (ileride uygulanacak) - -Inno `.iss` bu turda URETILMEDI. Uygulanacak alanlar: - -- `SetupIconFile=assets\mlc-player-icon.ico` -- `UninstallDisplayIcon={app}\MLC Player.exe` -- Masaustu ve Baslat menusu kisayollari ana EXE ikonunu kullanir. -- Setup ve kaldiricinin gercek gorunumu Inno kabulunde AYRICA dogrulanir. - +## Açık uyumluluk maddeleri + +Aşağıdakiler 3 Ekim 2026'da kaynakla yeniden denetlendi. Kapanmış maddeler +(nonfree olmayan `mpv-2.dll`, karşılık gelen kaynak manifesti `ready`, +kurulumdaki `LICENSE`/`licenses/`, SPDX dosya başlıkları) ve eski kontrol +listesinin kendisi `docs/history/PACKAGING_HISTORY.md` içindedir. + +- [ ] İndirilen altyazıların yeniden dağıtımı ve saklanmasıyla ilgili + OpenSubtitles şartları doğrulanmalı. Çevrimiçi altyazı arayüzü + (`SUBTITLE_SEARCH_UI_ENABLED=False`) bu doğrulama ve yazılı sağlayıcı + onayı olmadan açılmaz. +- [ ] Kod imzalama: kurulum dosyası Authenticode imzası taşımaz. SignPath + Foundation başvurusu onaylanmadı; durum `docs/release/SIGNPATH.md`, + politika `docs/policies/CODE_SIGNING_POLICY.md` içindedir. İmza lisans + ve telif yükümlülüklerinin yerine geçmez. +- [ ] Avukata yöneltilecek açık sorular: GPLv3+ `yt-dlp.exe` için karşılık + gelen kaynak yükümlülüğünün kaynak arşiv URL'siyle karşılanıp + karşılanmadığı; H.264/H.265 decode içeren ücretsiz dağıtımda patent + havuzu yükümlülüğü; açık kaynak masaüstü istemcide uygulama başına tek + API anahtarının dağıtımı. Soruların tam metni tarihsel kayıttadır. + +## Uygulama ikonu + +Onaylı görsel kimlik yuvarlak turuncu MLC ikonudur; standart ve şeffaf +PNG/ICO kaynakları `assets/` altındadır, ölçü ve SHA-256 değerleri +`assets/ICON_MANIFEST.txt` içindedir. Inno kurulumu +`SetupIconFile=..\assets\mlc-player-icon.ico` ve +`UninstallDisplayIcon={app}\{#MyAppExeName}` kullanır +(`packaging/MLCPlayer.iss`); kısayollar ana EXE ikonunu taşır. Gerçek görünüm +Inno kabulünde ayrıca doğrulanır. diff --git a/docs/RELEASE_PROCESS.md b/docs/release/RELEASE_PROCESS.md similarity index 95% rename from docs/RELEASE_PROCESS.md rename to docs/release/RELEASE_PROCESS.md index 0a71766..3cde903 100644 --- a/docs/RELEASE_PROCESS.md +++ b/docs/release/RELEASE_PROCESS.md @@ -1,7 +1,7 @@ # MLC Player yayın süreci -**Bu dosya yayın sürecinin TEK RESMÎ KAYNAĞIDIR.** `CLAUDE.md`, -`docs/PACKAGING_PLAN.md` ve `packaging/prepublish.py` yalnız kritik +**Bu dosya yayın sürecinin TEK RESMÎ KAYNAĞIDIR.** `.claude/CLAUDE.md`, +`docs/release/PACKAGING_PLAN.md` ve `packaging/prepublish.py` yalnız kritik değişmezleri özetler ve buraya bağlanır. Sıra burada değişir, başka yerde tekrarlanmaz. @@ -36,7 +36,7 @@ değiştirmedikleridir**. ## Kod imzalama katmanları SignPath is not currently part of the active release chain. Mevcut kurulumlar -Windows Authenticode imzası taşımaz; `CODE_SIGNING_POLICY.md` yalnız ücretsiz +Windows Authenticode imzası taşımaz; `docs/policies/CODE_SIGNING_POLICY.md` yalnız ücretsiz SignPath başvurusu için hazırlık durumunu açıklar. SignPath kabulü alınır ve ayrıca süreç değişikliği onaylanırsa Authenticode must happen before the existing detached Ed25519 signature is created. @@ -68,7 +68,7 @@ yerel Ed25519 imzası ve mevcut fiziksel installer kabul kapıları yine zorunlu ## Kesin yayın sırası `master` PR kapısı aktifse sürüm alanı commit'i önce -`docs/CHANGE_WORKFLOW.md` sırasıyla PR üzerinden **merge commit** olarak +`docs/process/CHANGE_WORKFLOW.md` sırasıyla PR üzerinden **merge commit** olarak origin/master'a ulaşır. PR üzerindeki hosted `test` yeşil olmadan merge yapılmaz. Rutin merge push'u otomatik CI başlatmadığı için sürüm adayında ayrıca `workflow_dispatch` ile `master` üzerindeki exact merge commit tam @@ -145,7 +145,7 @@ doğrular. Bu koşum yeşil olmadan build başlamaz. Yerel c) python packaging/fetch_sources.py GİRİŞ : corresponding_sources.json `ready`, engel listesi boş - ÇIKIŞ : source_mirror içinde sözleşmedeki gerçek kaynak arşivleri, + ÇIKIŞ : output/source_mirror içinde sözleşmedeki gerçek kaynak arşivleri, boyut + SHA-256 ile doğrulanmış d) Build BAŞARIYLA bittikten SONRA, test edilen HEAD üzerinde @@ -197,12 +197,12 @@ Adım (h) komutu — Windows PowerShell'de kopyalanıp çalıştırılabilir: ```powershell $assets = @( - "installer_output/MLCPlayer_Setup_vX.Y.exe" - "installer_output/MLCPlayer_Setup_vX.Y.exe.sig" - "installer_output/MLCPlayer_InternetVideo_vX.Y.exe" - "installer_output/MLCPlayer_InternetVideo_vX.Y.exe.sig" + "output/installer/MLCPlayer_Setup_vX.Y.exe" + "output/installer/MLCPlayer_Setup_vX.Y.exe.sig" + "output/installer/MLCPlayer_InternetVideo_vX.Y.exe" + "output/installer/MLCPlayer_InternetVideo_vX.Y.exe.sig" ) -$sourceAssets = python -c "import sys; sys.path.insert(0, 'packaging'); import fetch_sources; [print('source_mirror/' + x.name) for x in fetch_sources.plan()]" +$sourceAssets = python -c "import sys; sys.path.insert(0, 'packaging'); import fetch_sources; [print('output/source_mirror/' + x.name) for x in fetch_sources.plan()]" if ($LASTEXITCODE -ne 0) { throw "kaynak listesi okunamadi" } $assets += @($sourceAssets) @@ -226,7 +226,7 @@ python packaging/stage_libmpv_source.py --source "\source\parts\l Araç ağ, build, Git, tag veya release işlemi yapmaz. Kaynağı sözleşmedeki `557940716` bayt ve SHA-256 değeriyle doğrular, geçici dosyaya yazar ve yalnız -tam eşleşmede `source_mirror/` hedefine atomik geçirir. Doğrulanmış hedef zaten +tam eşleşmede `output/source_mirror/` hedefine atomik geçirir. Doğrulanmış hedef zaten varsa kaynak dosyası veya yeni build gerektirmeden başarıyla döner. Kalıcı URL v0.38 release yayımlandığında canlı olur; draft aşamasında aynı dosyanın uzak ad/boyut/SHA-256 eşliği doğrulanmadan yayın yapılmaz. @@ -314,7 +314,7 @@ push etmez, release açmaz. | 2 | `MLCPlayer_Setup_vX.Y.exe.sig` | build (b) | | 3 | `MLCPlayer_InternetVideo_vX.Y.exe` | build (b) | | 4 | `MLCPlayer_InternetVideo_vX.Y.exe.sig` | build (b) | -| 5+ | `source_mirror/` gerçek kaynak arşivleri | fetch (c) | +| 5+ | `output/source_mirror/` gerçek kaynak arşivleri | fetch (c) | Kaynak varlıkları `packaging/corresponding_sources.json` dosyasından türer. EXE, binary ZIP veya yalnız header/DLL taşıyan geliştirme paketi bu listeye diff --git a/docs/SIGNPATH_FOUNDATION_APPLICATION.md b/docs/release/SIGNPATH.md similarity index 56% rename from docs/SIGNPATH_FOUNDATION_APPLICATION.md rename to docs/release/SIGNPATH.md index fee2ff9..2cda45f 100644 --- a/docs/SIGNPATH_FOUNDATION_APPLICATION.md +++ b/docs/release/SIGNPATH.md @@ -1,20 +1,32 @@ -# SignPath Foundation application draft +# SignPath Foundation application and readiness record **Submission status: SUBMITTED - NOT APPROVED (2 September 2026)** -This public record captures the SignPath Foundation Open Source application -submitted on 23 August 2026. It does not claim acceptance, an account, a -certificate, a signing request or a signature. Only SignPath Foundation can -decide eligibility. +This public record merges the former `SIGNPATH_FOUNDATION_APPLICATION.md` +(the submitted application) and `SIGNPATH_READINESS.md` (the engineering +checklist). It is not proof of acceptance and not legal advice. The SignPath +Foundation Open Source application was submitted on 23 August 2026. This +record does not claim acceptance, an account, a certificate, a signing +request or a signature. Only SignPath Foundation can decide eligibility. + +SignPath is not part of the active release chain; +`docs/release/RELEASE_PROCESS.md` remains authoritative. ## Foundation response The user provided SignPath's 2 September 2026 support response. The Foundation -application was not approved because MLC Player did not yet show enough public -visibility/trust signals. The response invites a future reapplication after -those signals grow and separately mentions a paid subscription; neither is a -technical-quality verdict, an approval, a configured service or part of the -active release chain. +application was not approved because MLC Player did not yet show enough +public visibility/trust signals. The program looks for established external +trust/visibility signals such as community adoption, independent references +and sustained engagement. The response invites a future reapplication after +those signals grow and separately mentions a paid subscription as an optional +route. Neither is a technical-quality verdict, an approval, a configured +service or part of the active release chain, and neither creates a SignPath +account, certificate or paid subscription. + +Do not add an action, token, certificate or acceptance claim without a future +explicit acceptance and separate implementation approval. Private contact +details remain outside the repository. ## Applicant details @@ -25,8 +37,8 @@ active release chain. this repository - Source repository: - Public releases: -- License: GPL-3.0-only ([LICENSE](../LICENSE)) -- Current released version: v0.39 +- License: GPL-3.0-only ([LICENSE](../../LICENSE)) +- Current released version at submission: v0.39 MLC Player is an actively maintained Windows desktop media player for local audio and video playback. Website playback is delivered by a separately @@ -51,6 +63,19 @@ advertising, telemetry or analytics. - Download URL, Wikipedia URL and company name: left blank rather than making an unsupported claim +## Submission boundary + +The official form at was submitted after separate +user approval. Contact name and email were supplied privately to SignPath and +are not stored in the repository. The required Code of Conduct and +personal-data processing consents were selected; optional marketing consent +remained off. The page returned **Form submitted** and **Thank you, we'll be +in touch soon.** + +This proves form delivery only. No application identifier, acceptance, +SignPath account, GitHub App, token, organization, project, policy, +certificate, signing request or signature was returned. + ## Project ownership and roles - Committer and reviewer: [IzzmooPro](https://github.com/IzzmooPro) @@ -62,11 +87,11 @@ advertising, telemetry or analytics. Project policies: -- [Code signing policy](../CODE_SIGNING_POLICY.md) -- [Privacy policy](../PRIVACY.md) +- [Code signing policy](../policies/CODE_SIGNING_POLICY.md) +- [Privacy policy](../policies/PRIVACY.md) - [Release process](RELEASE_PROCESS.md) -## Eligibility declarations +## Eligibility declarations and supporting evidence - The project is released, documented and licensed under the OSI-approved GPL-3.0-only license without commercial dual licensing. @@ -76,13 +101,18 @@ Project policies: dependencies are upstream Open Source components with notices and tracked corresponding-source records. - Installation and system changes are disclosed, and an uninstaller is - provided. Local data and network behavior are disclosed in `PRIVACY.md`. + provided. Local data and network behavior are disclosed in `docs/policies/PRIVACY.md`. - Upstream Open Source DLLs may be included unsigned in the installer but will not be individually signed as if they were maintained by MLC Player. +- `docs/policies/CODE_SIGNING_POLICY.md`, `docs/policies/PRIVACY.md` and + `docs/release/RELEASE_PROCESS.md` + document roles, unsigned status, network/local-data behavior and the + release order. These declarations are written against the published -[SignPath Foundation conditions](https://signpath.org/terms.html). They are -application evidence, not a self-issued eligibility decision. +[SignPath Foundation conditions](https://signpath.org/terms.html). They make +an application plausible and are application evidence, not a self-issued +eligibility decision. ## Requested initial signing scope @@ -96,7 +126,10 @@ version metadata must be enforced by the future SignPath artifact configuration. Authenticode signing must finish before the existing detached Ed25519 signature is produced for the final installer bytes. -## Verified unsigned-build evidence +## Verified GitHub-hosted unsigned build + +`.github/workflows/build-unsigned-main.yml` is manual-only and uses a +GitHub-hosted Windows 2025 runner. - Exact source commit: `1f01633bd7b008dba6faec8362ddff66e0d6d009` @@ -104,11 +137,12 @@ Ed25519 signature is produced for the final installer bytes. - GitHub Actions run: -- Runner boundary: GitHub-hosted Windows 2025, Python 3.13.15 and Inno Setup - 6.7.1 +- Toolchain boundary: Python 3.13.15, Inno Setup 6.7.1, 24 exact hash-locked + Windows wheels and the immutable project-owned libmpv OCI digest - Installer: `MLCPlayer_Setup_v0.39.exe`, 57,255,931 bytes, SHA-256 `cab8c89ba614dcf3589410d345248b831439beacaecad3db6994af9a0f436066`, - Authenticode status `NotSigned` + Authenticode status `NotSigned`, no signer certificate and no detached + `.sig` in the hosted output - Installer artifact: `mlcplayer-unsigned-main-1f01633bd7b008dba6faec8362ddff66e0d6d009` - Provenance artifact: @@ -119,21 +153,39 @@ matched against GitHub's recorded archive sizes and SHA-256 digests. This is hosted unsigned-build evidence only; it is not installed-artifact, native playback, Authenticode or release evidence. +The workflow does not call SignPath, access the local Ed25519 key, create a +tag or change a release. The optional Internet Video add-on was deliberately +outside this build. + ## Configuration only after acceptance No SignPath account, GitHub App, API token, organization, project, policy, artifact configuration, certificate or signing action has been created for this project. After acceptance and separate approval: -1. Create the assigned organization/project and connect the repository to the - predefined GitHub.com Trusted Build System. +1. Create the assigned organization/project and connect only the repository's + GitHub-hosted workflow to the predefined GitHub.com Trusted Build System. 2. Install the SignPath GitHub App with access limited to this repository. -3. Create a CI submitter and an interactive approver with MFA. +3. Create separate CI submitter and interactive approver roles with MFA. 4. Configure the main EXE artifact and enforce project/version metadata. 5. Resolve the SignPath submission action to an immutable commit before adding - it to the workflow; do not rely on a floating tag. + it to the workflow; a floating action tag is not an approved release input. 6. Keep every signing request under manual approval and verify the exact commit, version and uploaded GitHub Actions artifact. +The safe future release sequence is: + +1. GitHub-hosted runner builds and stores the exact unsigned main installer. +2. After SignPath acceptance, the exact stored artifact is submitted. +3. A human approver checks the commit/version and manually approves signing. +4. SignPath applies Authenticode to the installer bytes. +5. The signed installer is downloaded to the controlled release environment. +6. The existing Ed25519 process signs the final Authenticode-signed bytes. +7. Install, upgrade, uninstall, native-smoke and release-parity gates run + against those final bytes. + +Until these later gates exist and pass, SignPath is not part of the active +release chain. + Official integration reference: . diff --git a/docs/VIDEO_FORMAT_ACCEPTANCE_MATRIX.json b/docs/video-format/VIDEO_FORMAT_ACCEPTANCE_MATRIX.json similarity index 99% rename from docs/VIDEO_FORMAT_ACCEPTANCE_MATRIX.json rename to docs/video-format/VIDEO_FORMAT_ACCEPTANCE_MATRIX.json index 03447f1..ff845ee 100644 --- a/docs/VIDEO_FORMAT_ACCEPTANCE_MATRIX.json +++ b/docs/video-format/VIDEO_FORMAT_ACCEPTANCE_MATRIX.json @@ -12,7 +12,7 @@ "capability_inventory": { "measured_commit": "419d6c7cede0b6fad37425b28c375fc89e61b141", "evidence_id": "EV-20260824-026", - "document": "docs/VIDEO_FORMAT_CAPABILITY_INVENTORY.md", + "document": "docs/video-format/VIDEO_FORMAT_ACCEPTANCE_PLAN.md", "active_gpu": "NVIDIA GeForce RTX 4070 Ti", "driver": "610.88", "active_display": "MSI G274QPF E2", @@ -74,7 +74,7 @@ } }, "media_policy": { - "manifest_document": "docs/VIDEO_FORMAT_MEDIA_MANIFEST.json", + "manifest_document": "docs/video-format/VIDEO_FORMAT_MEDIA_MANIFEST.json", "manifest_evidence_id": "EV-20260824-041", "manifest_status": "partially_fingerprinted", "acquisition_or_generation_performed": true, diff --git a/docs/VIDEO_FORMAT_ACCEPTANCE_PLAN.md b/docs/video-format/VIDEO_FORMAT_ACCEPTANCE_PLAN.md similarity index 68% rename from docs/VIDEO_FORMAT_ACCEPTANCE_PLAN.md rename to docs/video-format/VIDEO_FORMAT_ACCEPTANCE_PLAN.md index e9424c9..7984b72 100644 --- a/docs/VIDEO_FORMAT_ACCEPTANCE_PLAN.md +++ b/docs/video-format/VIDEO_FORMAT_ACCEPTANCE_PLAN.md @@ -216,3 +216,93 @@ Her araştırma, fixture, native sonuç veya ürün kararı aynı görev kapanma Eksik medya, yanlış ekran modu, eksik marker, `BLOCKED`, `FAILED` veya `SKIPPED` hiçbir zaman PASSED yapılmaz. + +## Ölçülmüş yetenek envanteri (24 Ağustos 2026) + +> Bu bölüm eski `VIDEO_FORMAT_CAPABILITY_INVENTORY.md` dosyasının birleştirilmiş, +> değiştirilmemiş içeriğidir. Makinece kaynak `VIDEO_FORMAT_ACCEPTANCE_MATRIX.json` +> içindeki `capability_inventory` alanıdır. + +Bu kayıt, `419d6c7cede0b6fad37425b28c375fc89e61b141` üzerinde 24 Ağustos +2026'da alınan salt-okunur makine ve exact runtime ölçümüdür. Medya açılmadı, +Windows HDR ayarı değiştirilmedi ve ürün yapılandırması değiştirilmedi. + +### Makine ve ekran yolu + +- Aktif GPU: NVIDIA GeForce RTX 4070 Ti; sürücü `610.88` + (`32.0.16.1088`), WDDM 3.2, Direct3D feature level 12_2. +- Pasif/ekran sürmeyen ikinci bağdaştırıcı: AMD Radeon Graphics, + `32.0.21045.1000`. +- Aktif ekran: MSI G274QPF E2 (`MSI8CC2`), 2560x1440, 180 Hz. +- Aktif EDID: 384 bayt, SHA-256 + `f325d9f7e693b0ee79049ba342bf01066419110658a56452d0e0a20e44f4456f`. + CTA HDR Static Metadata bloğu ham olarak `060701625F00`, BT.2020 + colorimetry bloğu `05C000` ölçüldü. Bu, giriş kablosu/ekranın ilan ettiği + yetenektir; o anda HDR10 sinyali gönderildiğini kanıtlamaz. +- DxDiag `HDR Support: Supported` ve `AdvancedColorEnabled` bildirdi. Aktif + renk uzayı ise + `DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709` idi. Microsoft, bu değerin SDR + yolu temsil ettiğini ve SDR Advanced Color ekranlarda da aynı değerin + görülebileceğini açıklar. HDR10 için aranan değer + `DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020` hâlâ ölçülmedi. +- DxDiag parlaklık telemetrisi: 417.711792 nit tepe ve 391.463287 nit tam + çerçeve. MSI resmî teknik sayfası DisplayHDR 400 ve 8-bit+FRC bildirir. + +Kaynaklar: [Microsoft Advanced Color](https://learn.microsoft.com/windows/win32/direct3darticles/high-dynamic-range), +[MSI G274QPF E2](https://www.msi.com/Monitor/G274QPF-E2/Specification). + +### 25 Ağustos 2026 canlı HDR çıkış güncellemesi + +Ayrı sistem ayarı onayıyla aynı tek aktif MSI ekranın Windows HDR anahtarı +kapalıdan açığa alındı. Ayarlar arayüzü `HDR Açık` durumunu gösterdi ve hemen +sonraki DxDiag ölçümü etkin renk uzayını exact +`DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020` olarak verdi +(`EV-20260825-005`). Bu, aktif HDR10 Windows çıkış ön koşulunu kanıtlar; +MLC Player medya yolu, tone mapping, görsel doğruluk veya herhangi bir +video-format satırı için PASS değildir. Bu güncellemede medya açılmadı ve +hedef süreç sızıntısı yoktu. + +### Exact mpv ve FFmpeg runtime + +- `bin/mpv-2.dll`: 112772608 bayt, SHA-256 + `de80329f5c019ba2ee48184b5dc1e1d0c2ee9eeba3f1fb7959f20b4b0f684f4e`. +- mpv: `v0.41.0-930-g49418246f`; FFmpeg: + `N-126239-g88ae625e6`; python-mpv: `1.0.8`; Python: `3.14.3`. +- VO derleme seçenekleri `gpu-next`, `gpu`, `direct3d` ve `libmpv` içeriyor. + Windows GPU API/context seçenekleri D3D11, Vulkan, `winvk`, ANGLE, + `dxinterop` ve `displayvk` yollarını içeriyor. +- `target-colorspace-hint` seçenekleri `auto/no/yes`; modları `target`, + `source`, `source-dynamic`. Hedef transfer seçeneklerinde PQ, HLG ve scRGB; + tone mapping seçeneklerinde BT.2390, BT.2446a, ST 2094-40 ve ST 2094-10 + bulunuyor. Çıkış seviye seçenekleri `auto/limited/full`. +- Derlenmiş codec listesi HEVC, VP9 ve AV1 yazılım sürücülerini; NVIDIA + CUVID, AMD AMF ve Intel QSV sürücülerini içeriyor. `hwdec-codecs` + varsayılan listesi H.264, HEVC, VP8, VP9, AV1, ProRes, FFV1, DPX ve APV'yi + kapsıyor. + +NVIDIA'nın resmî Video Codec SDK sayfası NVDEC'in Windows'ta H.264, HEVC, +VP9 ve AV1 dahil donanım çözmeyi desteklediğini açıklar. Buna rağmen derlenmiş +CUVID sürücüsü ve GPU modelinin varlığı tek başına bu exact MLC yolunda +donanım çözme PASS'i değildir; bunun için fingerprint edilmiş medya üzerinde +`hwdec-current` read-back gerekir. Kaynak: +[NVIDIA Video Codec SDK](https://developer.nvidia.com/video-codec-sdk). + +### Ürünün bugünkü politikası + +`app/config.py` hâlâ `vo=gpu` ve `hwdec=auto-safe` kullanıyor. Ürün +`gpu-next`, D3D11, `target-colorspace-hint`, hedef primaries/TRC veya özel tone +mapping politikasını varsayılan olarak zorlamıyor. Test-only HDR adayı bu +değerleri yalnız child kopyasında uygular; ürün sözlüğünü mutate etmez. + +### Kanıt sınırı ve karar + +Bu envanter şunları kanıtlar: exact DLL kimliği, derlenmiş seçeneklerin +okunabildiği, medya açmayan libmpv child'ının `MARK_DONE` ile çıkıp süreç +sızdırmadığı, ekranın HDR-capable olduğu ve aktif Windows yolunun ölçüm anında +P709 kaldığı. + +Şunları kanıtlamaz: gerçek HEVC/AV1/VP9 donanım decode, 10-bit swapchain, +HDR10/HLG/HDR10+/Dolby Vision çıkışı, tone mapping kalitesi, overlay +parlaklığı, 4K60 performansı veya rakip oynatıcı davranışı. Bu nedenle +`VIDEO_FORMAT_ACCEPTANCE_MATRIX.json` içindeki 16 senaryonun tamamı +`BLOCKED` kalır. diff --git a/docs/VIDEO_FORMAT_MEDIA_MANIFEST.json b/docs/video-format/VIDEO_FORMAT_MEDIA_MANIFEST.json similarity index 100% rename from docs/VIDEO_FORMAT_MEDIA_MANIFEST.json rename to docs/video-format/VIDEO_FORMAT_MEDIA_MANIFEST.json diff --git a/licenses/THIRD_PARTY_NOTICES.txt b/licenses/THIRD_PARTY_NOTICES.txt index 55c92d5..3a41713 100644 --- a/licenses/THIRD_PARTY_NOTICES.txt +++ b/licenses/THIRD_PARTY_NOTICES.txt @@ -2,7 +2,7 @@ MLC Player third-party notices ================================ This file describes the third-party runtime families included in the Windows -package built from requirements-lock.txt. The exact corresponding-source +package built from requirements/lock.txt. The exact corresponding-source archives, sizes and SHA-256 values are declared in packaging/corresponding_sources.json and are published beside each MLC Player release. The accompanying licence texts are installed in this directory. diff --git a/packaging/MLCPlayer.iss b/packaging/MLCPlayer.iss index 2c3db0d..cbf69c6 100644 --- a/packaging/MLCPlayer.iss +++ b/packaging/MLCPlayer.iss @@ -25,7 +25,7 @@ DefaultDirName={autopf}\MLC Player DefaultGroupName={#MyAppName} DisableProgramGroupPage=yes PrivilegesRequired=admin -OutputDir=..\installer_output +OutputDir=..\output\installer OutputBaseFilename=MLCPlayer_Setup_{#MyAppVersion} ; LisansFile BİLEREK YOK — "kabul ediyorum" sayfası KALDIRILDI. ; GPL bir EULA değildir: GPLv3 madde 9 açıkça programı almak veya @@ -418,6 +418,86 @@ brazilianportuguese.OpenDefaultApps=Abrir as configurações de aplicativos padr ClosePlayerBeforeUninstall=MLC Player is still running. Close it completely, then start uninstall again. turkish.ClosePlayerBeforeUninstall=MLC Player hâlâ çalışıyor. Programı tamamen kapatıp kaldırmayı yeniden başlatın. +english.UninstallCleanupTitle=Remove MLC Player +english.UninstallCleanupBody=Choose the optional data to remove with the program. +english.UninstallCleanupAccount=The data choices apply to the uninstall account and the registered MLC accounts listed here: %1 +english.UninstallRemoveAddon=Also remove the Internet Video add-on +english.UninstallDeleteSettings=Delete settings, history and saved subtitle credentials +english.UninstallDeleteCache=Delete logs, thumbnail cache and temporary update files +english.UninstallCleanupNote=Unchecked user data is preserved. Select both data choices to remove all MLC Player data owned by the listed accounts. +english.UninstallAddonFailed=The Internet Video add-on could not be removed safely. MLC Player removal stopped before deleting the main program. +english.UninstallCleanupFailed=MLC Player was removed, but some selected user data could not be deleted. No path outside MLC Player ownership was touched. + +turkish.UninstallCleanupTitle=MLC Player kaldırma yardımcısı +turkish.UninstallCleanupBody=Programla birlikte silinecek isteğe bağlı verileri seçin. +turkish.UninstallCleanupAccount=Veri seçenekleri kaldırma hesabına ve burada listelenen kayıtlı MLC hesaplarına uygulanır: %1 +turkish.UninstallRemoveAddon=Internet Video eklentisini de kaldır +turkish.UninstallDeleteSettings=Ayarları, geçmişi ve kayıtlı altyazı kimliklerini sil +turkish.UninstallDeleteCache=Günlükleri, küçük resim önbelleğini ve geçici güncelleme dosyalarını sil +turkish.UninstallCleanupNote=İşaretlenmeyen kullanıcı verileri korunur. Listelenen hesaplara ait tüm MLC Player verilerini silmek için iki veri seçeneğini de işaretleyin. +turkish.UninstallAddonFailed=Internet Video eklentisi güvenli biçimde kaldırılamadı. Ana program silinmeden MLC Player kaldırma işlemi durduruldu. +turkish.UninstallCleanupFailed=MLC Player kaldırıldı ancak seçilen kullanıcı verilerinin bir bölümü silinemedi. MLC Player sahipliği dışındaki hiçbir yola dokunulmadı. + +german.UninstallCleanupTitle=MLC Player entfernen +german.UninstallCleanupBody=Wählen Sie die optionalen Daten aus, die mit dem Programm entfernt werden sollen. +german.UninstallCleanupAccount=Die Datenoptionen gelten für das Deinstallationskonto und die hier aufgeführten registrierten MLC-Konten: %1 +german.UninstallRemoveAddon=Internet-Video-Erweiterung ebenfalls entfernen +german.UninstallDeleteSettings=Einstellungen, Verlauf und gespeicherte Untertitel-Anmeldedaten löschen +german.UninstallDeleteCache=Protokolle, Vorschaubild-Cache und temporäre Update-Dateien löschen +german.UninstallCleanupNote=Nicht ausgewählte Benutzerdaten bleiben erhalten. Wählen Sie beide Datenoptionen, um alle MLC-Player-Daten der aufgeführten Konten zu entfernen. +german.UninstallAddonFailed=Die Internet-Video-Erweiterung konnte nicht sicher entfernt werden. Die Entfernung von MLC Player wurde vor dem Löschen des Hauptprogramms beendet. +german.UninstallCleanupFailed=MLC Player wurde entfernt, aber einige ausgewählte Benutzerdaten konnten nicht gelöscht werden. Pfade außerhalb des Besitzes von MLC Player wurden nicht verändert. + +spanish.UninstallCleanupTitle=Quitar MLC Player +spanish.UninstallCleanupBody=Elija los datos opcionales que se eliminarán con el programa. +spanish.UninstallCleanupAccount=Las opciones se aplican a la cuenta de desinstalación y a las cuentas MLC registradas que aparecen aquí: %1 +spanish.UninstallRemoveAddon=Quitar también el complemento de vídeo de Internet +spanish.UninstallDeleteSettings=Eliminar ajustes, historial y credenciales de subtítulos guardadas +spanish.UninstallDeleteCache=Eliminar registros, caché de miniaturas y archivos temporales de actualización +spanish.UninstallCleanupNote=Los datos no seleccionados se conservan. Seleccione ambas opciones para eliminar todos los datos de MLC Player de las cuentas indicadas. +spanish.UninstallAddonFailed=El complemento de vídeo de Internet no se pudo quitar de forma segura. La eliminación de MLC Player se detuvo antes de borrar el programa principal. +spanish.UninstallCleanupFailed=MLC Player se quitó, pero no se pudieron eliminar algunos datos seleccionados. No se modificó ninguna ruta ajena a MLC Player. + +french.UninstallCleanupTitle=Supprimer MLC Player +french.UninstallCleanupBody=Choisissez les données facultatives à supprimer avec le programme. +french.UninstallCleanupAccount=Les choix s'appliquent au compte de désinstallation et aux comptes MLC enregistrés affichés ici : %1 +french.UninstallRemoveAddon=Supprimer aussi l'extension Vidéo Internet +french.UninstallDeleteSettings=Supprimer les paramètres, l'historique et les identifiants de sous-titres enregistrés +french.UninstallDeleteCache=Supprimer les journaux, le cache des miniatures et les fichiers temporaires de mise à jour +french.UninstallCleanupNote=Les données non sélectionnées sont conservées. Sélectionnez les deux options pour supprimer toutes les données MLC Player des comptes affichés. +french.UninstallAddonFailed=L'extension Vidéo Internet n'a pas pu être supprimée en toute sécurité. La suppression de MLC Player s'est arrêtée avant celle du programme principal. +french.UninstallCleanupFailed=MLC Player a été supprimé, mais certaines données sélectionnées n'ont pas pu l'être. Aucun chemin extérieur à MLC Player n'a été modifié. + +italian.UninstallCleanupTitle=Rimuovi MLC Player +italian.UninstallCleanupBody=Scegli i dati facoltativi da rimuovere insieme al programma. +italian.UninstallCleanupAccount=Le opzioni si applicano all'account di disinstallazione e agli account MLC registrati elencati qui: %1 +italian.UninstallRemoveAddon=Rimuovi anche il componente Video Internet +italian.UninstallDeleteSettings=Elimina impostazioni, cronologia e credenziali dei sottotitoli salvate +italian.UninstallDeleteCache=Elimina registri, cache delle miniature e file temporanei di aggiornamento +italian.UninstallCleanupNote=I dati non selezionati vengono conservati. Seleziona entrambe le opzioni per rimuovere tutti i dati di MLC Player degli account elencati. +italian.UninstallAddonFailed=Impossibile rimuovere in sicurezza il componente Video Internet. La rimozione di MLC Player è stata interrotta prima di eliminare il programma principale. +italian.UninstallCleanupFailed=MLC Player è stato rimosso, ma non è stato possibile eliminare alcuni dati selezionati. Nessun percorso esterno a MLC Player è stato modificato. + +russian.UninstallCleanupTitle=Удаление MLC Player +russian.UninstallCleanupBody=Выберите дополнительные данные, которые следует удалить вместе с программой. +russian.UninstallCleanupAccount=Параметры применяются к учетной записи удаления и перечисленным здесь зарегистрированным учетным записям MLC: %1 +russian.UninstallRemoveAddon=Также удалить дополнение для интернет-видео +russian.UninstallDeleteSettings=Удалить настройки, историю и сохраненные учетные данные субтитров +russian.UninstallDeleteCache=Удалить журналы, кэш миниатюр и временные файлы обновления +russian.UninstallCleanupNote=Неотмеченные данные сохраняются. Отметьте оба варианта, чтобы удалить все данные MLC Player перечисленных учетных записей. +russian.UninstallAddonFailed=Не удалось безопасно удалить дополнение для интернет-видео. Удаление MLC Player остановлено до удаления основной программы. +russian.UninstallCleanupFailed=MLC Player удален, но некоторые выбранные данные удалить не удалось. Пути вне владения MLC Player не изменялись. + +brazilianportuguese.UninstallCleanupTitle=Remover o MLC Player +brazilianportuguese.UninstallCleanupBody=Escolha os dados opcionais que serão removidos com o programa. +brazilianportuguese.UninstallCleanupAccount=As opções se aplicam à conta de desinstalação e às contas MLC registradas listadas aqui: %1 +brazilianportuguese.UninstallRemoveAddon=Remover também o complemento de vídeo da Internet +brazilianportuguese.UninstallDeleteSettings=Excluir configurações, histórico e credenciais de legendas salvas +brazilianportuguese.UninstallDeleteCache=Excluir logs, cache de miniaturas e arquivos temporários de atualização +brazilianportuguese.UninstallCleanupNote=Os dados não selecionados são preservados. Selecione as duas opções para remover todos os dados do MLC Player das contas listadas. +brazilianportuguese.UninstallAddonFailed=O complemento de vídeo da Internet não pôde ser removido com segurança. A remoção do MLC Player parou antes de excluir o programa principal. +brazilianportuguese.UninstallCleanupFailed=O MLC Player foi removido, mas alguns dados selecionados não puderam ser excluídos. Nenhum caminho fora da propriedade do MLC Player foi alterado. + [Tasks] ; Varsayılan İŞARETLİ; kullanıcı istemezse kaldırır. Name: "desktopicon"; Description: "{cm:DesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}" @@ -478,13 +558,14 @@ Type: files; Name: "{app}\_internal\api-ms-win-crt-utility-l1-1-0.dll"; Check: I ; `_internal\bin` içindeki mpv-2.dll çekirdek runtime'dır. yt-dlp ve deno ; bilinçli olarak ana pakette yoktur; yalnız Internet Videosu ek paketindedir. #ifndef MLCCompilePreflight -Source: "..\dist\MLC Player\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; BeforeInstall: BeforeInstallMainPayload +Source: "..\output\dist\MLC Player\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; BeforeInstall: BeforeInstallMainPayload +Source: "..\output\dist\MLCUserCleanup.exe"; DestDir: "{app}"; Flags: ignoreversion #endif ; GPLv3 metni ve README kurulum KÖKÜNDE de dursun: kullanıcı `_internal` ; içine bakmak zorunda kalmadan lisansa ulaşabilmelidir. Source: "..\LICENSE"; DestDir: "{app}"; Flags: ignoreversion -Source: "..\README.md"; DestDir: "{app}"; Flags: ignoreversion -Source: "..\README.tr.md"; DestDir: "{app}"; Flags: ignoreversion +Source: "..\.github\README.md"; DestDir: "{app}"; Flags: ignoreversion +Source: "..\.github\README.tr.md"; DestDir: "{app}"; Flags: ignoreversion [Registry] ; ÖLÇÜLEN KUSUR: EXE sürüm kaynağı düzeltildikten SONRA bile Windows @@ -500,6 +581,10 @@ Root: HKA; Subkey: "Software\Classes\Applications\{#MyAppExeName}"; ValueType: s ; bırakabiliyordu. Yalnız TAM ürün anahtarı varsa uninstall loguna al; kurulumda ; anahtarı oluşturma/değiştirme ve paylaşılan `Applications` üst ağacına dokunma. Root: HKCU; Subkey: "Software\Classes\Applications\{#MyAppExeName}"; Flags: dontcreatekey uninsdeletekey +; Ana ürün kayıt alanı yalnız MLC Player'a aittir. Alt cleanup kaydı ters sırada +; kaldırıldıktan sonra boş üst anahtar da uninstall günlüğünden silinir. +Root: HKLM; Subkey: "Software\MLCPlayer"; Flags: dontcreatekey uninsdeletekey +Root: HKLM; Subkey: "Software\MLCPlayer\UninstallCleanup"; Flags: dontcreatekey uninsdeletekey Root: HKA; Subkey: "Software\Classes\Applications\{#MyAppExeName}\shell\open\command"; ValueType: string; ValueData: """{app}\{#MyAppExeName}"" ""%1""" ; Desteklenen türler: "Birlikte aç" listesinde program bu uzantılarda önerilir. Root: HKA; Subkey: "Software\Classes\Applications\{#MyAppExeName}\SupportedTypes"; ValueType: string; ValueName: ".mkv"; ValueData: "" @@ -531,6 +616,14 @@ const InstallModeReinstall = 1; InstallModeUpgrade = 2; CInstallerAccent = $002050F0; + AddonUninstallKey = 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{7C4F2A61-9B3D-4E58-9C2A-5D8E1F0B7A34}_is1'; + CleanupRegistryKey = 'Software\MLCPlayer\UninstallCleanup'; + UserSettingsKey = 'Software\MLCPlayer'; + SubtitleSettingsKey = 'Software\MLCPlayer\MLCPlayer\subtitle_center'; + CredentialTypeGeneric = 1; + ErrorNotFound = 1168; + FileAttributeReparsePoint = $400; + InvalidFileAttributes = $FFFFFFFF; var CurrentInstallMode: Integer; @@ -542,6 +635,28 @@ var InstallAccentBar: TPanel; CurrentInstallPhase: String; LegacyCleanupVerified: Boolean; + RemoveAddonSelected: Boolean; + DeleteSettingsSelected: Boolean; + DeleteCacheSelected: Boolean; + CleanupFailed: Boolean; + StoredSubtitleUsername: String; + CleanupUserSid: String; + CleanupTaskName: String; + CleanupHelperPath: String; + CleanupHelperHash: String; + CleanupIdentityValid: Boolean; + CleanupInstallId: String; + CleanupMarkerKey: String; + CleanupUserSids, CleanupTaskNames, CleanupHelperPaths, + CleanupHelperHashes, CleanupMarkerKeys: TArrayOfString; + CleanupIdentityCount: Integer; + CleanupAccountDisplay: String; + CleanupIdentityProblems: Boolean; + +function CredDeleteW(TargetName: String; CredentialType, Flags: Cardinal): Boolean; + external 'CredDeleteW@advapi32.dll stdcall'; +function GetFileAttributesW(FileName: String): Cardinal; + external 'GetFileAttributesW@kernel32.dll stdcall'; function InitializeSetup(): Boolean; var @@ -979,10 +1094,885 @@ begin end; end; -// Inno Setup 7 BUG ATLATMASI — SİLME! -// [Code] bölümü OLMAYAN kurulumlarda kaldırıcı "PathRedir: Not initialized" -// iç hatası veriyor. Bu zararsız fonksiyon bölümün var olmasını garanti eder. +function IsReparsePoint(Path: String): Boolean; forward; + +function IsCanonicalSid(Value: String): Boolean; +var + I: Integer; +begin + Result := False; + if (Length(Value) < 5) or (Copy(Value, 1, 2) <> 'S-') then + Exit; + for I := 3 to Length(Value) do + if not (((Value[I] >= '0') and (Value[I] <= '9')) or + (Value[I] = '-')) then + Exit; + Result := Pos('--', Value) = 0; +end; + +function ExpectedCleanupTaskName(Sid: String): String; +begin + Result := '\MLCPlayer_UninstallCleanup_' + Sid; +end; + +function HasExpectedCleanupHelperSuffix(FileName: String): Boolean; +var + CurrentSuffix, LegacySuffix: String; +begin + CurrentSuffix := '\Programs\MLC Player\UninstallCleanup\v1\MLCUserCleanup.exe'; + LegacySuffix := '\MLCPlayer\UninstallCleanup\v1\MLCUserCleanup.exe'; + Result := ((Length(FileName) > Length(CurrentSuffix)) and + (CompareText(Copy(FileName, Length(FileName) - Length(CurrentSuffix) + 1, + Length(CurrentSuffix)), CurrentSuffix) = 0)) or + ((Length(FileName) > Length(LegacySuffix)) and + (CompareText(Copy(FileName, Length(FileName) - Length(LegacySuffix) + 1, + Length(LegacySuffix)), LegacySuffix) = 0)); +end; + +function CleanupHelperPathIsSafe(FileName: String): Boolean; +var + V1Directory, CleanupDirectory, ProductDirectory, ProgramsDirectory, + LocalDirectory, AppDataDirectory, ProfileDirectory: String; +begin + Result := False; + if not HasExpectedCleanupHelperSuffix(FileName) or + not FileExists(FileName) or IsReparsePoint(FileName) then + Exit; + V1Directory := ExtractFileDir(FileName); + CleanupDirectory := ExtractFileDir(V1Directory); + ProductDirectory := ExtractFileDir(CleanupDirectory); + ProgramsDirectory := ExtractFileDir(ProductDirectory); + LocalDirectory := ExtractFileDir(ProgramsDirectory); + AppDataDirectory := ExtractFileDir(LocalDirectory); + ProfileDirectory := ExtractFileDir(AppDataDirectory); + if IsReparsePoint(V1Directory) or IsReparsePoint(CleanupDirectory) or + IsReparsePoint(ProductDirectory) or IsReparsePoint(ProgramsDirectory) or + IsReparsePoint(LocalDirectory) or IsReparsePoint(AppDataDirectory) or + IsReparsePoint(ProfileDirectory) then + Exit; + Result := True; +end; + +function IsCleanupId(Value: String): Boolean; +var + I: Integer; +begin + Result := Length(Value) = 32; + if not Result then + Exit; + for I := 1 to Length(Value) do + if not (((Value[I] >= '0') and (Value[I] <= '9')) or + ((Value[I] >= 'a') and (Value[I] <= 'f'))) then + begin + Result := False; + Exit; + end; +end; + +procedure CurStepChanged(CurStep: TSetupStep); +begin + if CurStep = ssPostInstall then + begin + if not RegQueryStringValue(HKLM, CleanupRegistryKey, 'InstallId', + CleanupInstallId) or not IsCleanupId(CleanupInstallId) then + CleanupInstallId := Lowercase(Copy(GetSHA256OfUnicodeString( + GetDateTimeString('yyyymmddhhnnsszzz', '-', ':') + '|' + + IntToStr(Random(2147483647)) + '|' + ExpandConstant('{app}')), 1, 32)); + if not RegWriteStringValue(HKLM, CleanupRegistryKey, 'InstallId', + CleanupInstallId) then + RaiseException('MLC Player uninstall cleanup identity could not be saved.'); + end; +end; + +function AddonIsInstalled: Boolean; +begin + Result := RegKeyExists(HKLM, AddonUninstallKey); +end; + +procedure InitializeUninstallCleanupChoices; +begin + // Program bileşeni varsayılan kaldırılır; kullanıcı verisi açık seçim + // olmadan asla silinmez. Sessiz kaldırmada da aynı koruma geçerlidir. + RemoveAddonSelected := AddonIsInstalled; + DeleteSettingsSelected := False; + DeleteCacheSelected := False; + if CompareText(ExpandConstant('{param:KEEPADDON|0}'), '1') = 0 then + RemoveAddonSelected := False; +end; + +function ShowUninstallCleanupChoices: Boolean; +var + Form: TSetupForm; + BodyLabel, AccountLabel, NoteLabel: TNewStaticText; + RemoveAddonCheckBox, DeleteSettingsCheckBox, + DeleteCacheCheckBox: TNewCheckBox; + ContinueButton, CancelButton: TNewButton; + ButtonSeparator: TBevel; + ButtonWidth: Integer; +begin + Form := CreateCustomForm(ScaleX(470), ScaleY(292), False, True); + try + Form.Caption := CustomMessage('UninstallCleanupTitle'); + + BodyLabel := TNewStaticText.Create(Form); + BodyLabel.Parent := Form; + BodyLabel.Left := ScaleX(16); + BodyLabel.Top := ScaleY(14); + BodyLabel.Width := Form.ClientWidth - ScaleX(32); + BodyLabel.Height := ScaleY(22); + BodyLabel.AutoSize := False; + BodyLabel.WordWrap := True; + BodyLabel.Caption := CustomMessage('UninstallCleanupBody'); + BodyLabel.Font.Style := [fsBold]; + + AccountLabel := TNewStaticText.Create(Form); + AccountLabel.Parent := Form; + AccountLabel.Left := BodyLabel.Left; + AccountLabel.Top := BodyLabel.Top + BodyLabel.Height + ScaleY(6); + AccountLabel.Width := BodyLabel.Width; + AccountLabel.Height := ScaleY(34); + AccountLabel.AutoSize := False; + AccountLabel.WordWrap := True; + AccountLabel.Caption := FmtMessage( + CustomMessage('UninstallCleanupAccount'), [CleanupAccountDisplay]); + + RemoveAddonCheckBox := TNewCheckBox.Create(Form); + RemoveAddonCheckBox.Parent := Form; + RemoveAddonCheckBox.Left := BodyLabel.Left; + RemoveAddonCheckBox.Top := AccountLabel.Top + AccountLabel.Height + ScaleY(8); + RemoveAddonCheckBox.Width := BodyLabel.Width; + RemoveAddonCheckBox.Height := ScaleY(20); + RemoveAddonCheckBox.Caption := CustomMessage('UninstallRemoveAddon'); + RemoveAddonCheckBox.Checked := RemoveAddonSelected; + RemoveAddonCheckBox.Visible := AddonIsInstalled; + + DeleteSettingsCheckBox := TNewCheckBox.Create(Form); + DeleteSettingsCheckBox.Parent := Form; + DeleteSettingsCheckBox.Left := BodyLabel.Left; + DeleteSettingsCheckBox.Top := RemoveAddonCheckBox.Top + ScaleY(28); + DeleteSettingsCheckBox.Width := BodyLabel.Width; + DeleteSettingsCheckBox.Height := ScaleY(20); + DeleteSettingsCheckBox.Caption := CustomMessage('UninstallDeleteSettings'); + DeleteSettingsCheckBox.Checked := DeleteSettingsSelected; + DeleteSettingsCheckBox.Enabled := True; + + DeleteCacheCheckBox := TNewCheckBox.Create(Form); + DeleteCacheCheckBox.Parent := Form; + DeleteCacheCheckBox.Left := BodyLabel.Left; + DeleteCacheCheckBox.Top := DeleteSettingsCheckBox.Top + ScaleY(28); + DeleteCacheCheckBox.Width := BodyLabel.Width; + DeleteCacheCheckBox.Height := ScaleY(20); + DeleteCacheCheckBox.Caption := CustomMessage('UninstallDeleteCache'); + DeleteCacheCheckBox.Checked := DeleteCacheSelected; + DeleteCacheCheckBox.Enabled := True; + + NoteLabel := TNewStaticText.Create(Form); + NoteLabel.Parent := Form; + NoteLabel.Left := BodyLabel.Left; + NoteLabel.Top := DeleteCacheCheckBox.Top + DeleteCacheCheckBox.Height + ScaleY(8); + NoteLabel.Width := BodyLabel.Width; + NoteLabel.Height := ScaleY(48); + NoteLabel.AutoSize := False; + NoteLabel.WordWrap := True; + NoteLabel.Caption := CustomMessage('UninstallCleanupNote'); + + ButtonSeparator := TBevel.Create(Form); + ButtonSeparator.Parent := Form; + ButtonSeparator.Left := 0; + ButtonSeparator.Top := Form.ClientHeight - ScaleY(47); + ButtonSeparator.Width := Form.ClientWidth; + ButtonSeparator.Height := ScaleY(2); + ButtonSeparator.Shape := bsTopLine; + ButtonSeparator.Anchors := [akLeft, akRight, akBottom]; + + ContinueButton := TNewButton.Create(Form); + ContinueButton.Parent := Form; + ContinueButton.Caption := SetupMessage(msgButtonNext); + ContinueButton.Height := ScaleY(23); + ContinueButton.Top := Form.ClientHeight - ScaleY(33); + ContinueButton.ModalResult := mrOk; + ContinueButton.Default := True; + + CancelButton := TNewButton.Create(Form); + CancelButton.Parent := Form; + CancelButton.Caption := SetupMessage(msgButtonCancel); + CancelButton.Height := ContinueButton.Height; + CancelButton.Top := ContinueButton.Top; + CancelButton.ModalResult := mrCancel; + CancelButton.Cancel := True; + + ButtonWidth := Form.CalculateButtonWidth([ContinueButton.Caption, + CancelButton.Caption]); + ContinueButton.Width := ButtonWidth; + CancelButton.Width := ButtonWidth; + CancelButton.Left := Form.ClientWidth - ScaleX(16) - ButtonWidth; + ContinueButton.Left := CancelButton.Left - ScaleX(8) - ButtonWidth; + + if RemoveAddonCheckBox.Visible and RemoveAddonCheckBox.Enabled then + Form.ActiveControl := RemoveAddonCheckBox + else if DeleteSettingsCheckBox.Enabled then + Form.ActiveControl := DeleteSettingsCheckBox + else + Form.ActiveControl := ContinueButton; + + Result := Form.ShowModal() = mrOk; + if Result then + begin + RemoveAddonSelected := RemoveAddonCheckBox.Visible and + RemoveAddonCheckBox.Checked; + DeleteSettingsSelected := DeleteSettingsCheckBox.Checked; + DeleteCacheSelected := DeleteCacheCheckBox.Checked; + end; + finally + Form.Free; + end; +end; + +function ReadValidatedAddonUninstaller(var UninstallerPath: String): Boolean; +var + RegisteredPath: String; + RegisteredInstallDirectory: String; + FileName: String; +begin + Result := False; + if not RegQueryStringValue(HKLM, AddonUninstallKey, + 'InstallLocation', RegisteredInstallDirectory) then + Exit; + if not PathSame(RemoveBackslashUnlessRoot(Trim(RegisteredInstallDirectory)), + ExpandConstant('{app}')) then + Exit; + if not RegQueryStringValue(HKLM, AddonUninstallKey, + 'UninstallString', RegisteredPath) then + Exit; + UninstallerPath := RemoveQuotes(Trim(RegisteredPath)); + if not FileExists(UninstallerPath) then + Exit; + if not PathSame(ExtractFileDir(UninstallerPath), ExpandConstant('{app}')) then + Exit; + if PathSame(UninstallerPath, ExpandConstant('{uninstallexe}')) then + Exit; + FileName := Lowercase(ExtractFileName(UninstallerPath)); + if (Length(FileName) < 10) or + (Copy(FileName, 1, 5) <> 'unins') or + (Copy(FileName, Length(FileName) - 3, 4) <> '.exe') then + Exit; + Result := True; +end; + +procedure RemoveSelectedAddon; +var + UninstallerPath: String; + ExitCode: Integer; +begin + if not RemoveAddonSelected or not AddonIsInstalled then + Exit; + if not ReadValidatedAddonUninstaller(UninstallerPath) then + RaiseException(CustomMessage('UninstallAddonFailed')); + if not Exec(UninstallerPath, '/VERYSILENT /SUPPRESSMSGBOXES /NORESTART', + '', SW_HIDE, ewWaitUntilTerminated, ExitCode) then + RaiseException(CustomMessage('UninstallAddonFailed')); + if ExitCode <> 0 then + RaiseException(CustomMessage('UninstallAddonFailed')); + if RegKeyExists(HKLM, AddonUninstallKey) then + RaiseException(CustomMessage('UninstallAddonFailed')); + if FileExists(ExpandConstant('{app}\_internal\bin\yt-dlp.exe')) or + FileExists(ExpandConstant('{app}\_internal\bin\deno.exe')) or + FileExists(ExpandConstant('{app}\_internal\licenses\yt-dlp-LICENSE.txt')) or + FileExists(ExpandConstant('{app}\_internal\licenses\yt-dlp-THIRD_PARTY_LICENSES.txt')) or + FileExists(ExpandConstant('{app}\_internal\licenses\deno-LICENSE.txt')) then + RaiseException(CustomMessage('UninstallAddonFailed')); +end; + +procedure MarkCleanupFailure(Subject: String); +begin + CleanupFailed := True; + Log('MLC cleanup retained an unsafe or undeletable entry: ' + Subject); +end; + +function IsReparsePoint(Path: String): Boolean; +var + Attributes: Cardinal; +begin + Attributes := GetFileAttributesW(Path); + Result := (Attributes <> InvalidFileAttributes) and + ((Attributes and FileAttributeReparsePoint) <> 0); +end; + +function PathAncestorsAreSafe(Path: String): Boolean; +var + CurrentPath, ParentPath: String; +begin + Result := False; + CurrentPath := RemoveBackslashUnlessRoot(Path); + if CurrentPath = '' then + Exit; + while True do + begin + if IsReparsePoint(CurrentPath) then + Exit; + ParentPath := RemoveBackslashUnlessRoot(ExtractFileDir(CurrentPath)); + if (ParentPath = '') or + (CompareText(ParentPath, CurrentPath) = 0) then + Break; + CurrentPath := ParentPath; + end; + Result := True; +end; + +procedure DeleteOwnedFile(FileName: String); +begin + if IsReparsePoint(FileName) or DirExists(FileName) then + begin + MarkCleanupFailure(FileName); + Exit; + end; + if FileExists(FileName) and not DeleteFile(FileName) then + MarkCleanupFailure(FileName); +end; + +procedure RemoveEmptyOwnedDirectory(Directory: String); +begin + if IsReparsePoint(Directory) then + begin + MarkCleanupFailure(Directory); + Exit; + end; + if DirExists(Directory) and not DirectoryHasEntries(Directory) and + not RemoveDir(Directory) then + MarkCleanupFailure(Directory); +end; + +function IsHexCharacter(Character: Char): Boolean; +begin + Result := ((Character >= '0') and (Character <= '9')) or + ((Character >= 'a') and (Character <= 'f')) or + ((Character >= 'A') and (Character <= 'F')); +end; + +function IsOwnedThumbnailName(FileName: String): Boolean; +var + I: Integer; +begin + Result := False; + if Length(FileName) <> 68 then + Exit; + if CompareText(Copy(FileName, 65, 4), '.jpg') <> 0 then + Exit; + for I := 1 to 64 do + if not IsHexCharacter(FileName[I]) then + Exit; + Result := True; +end; + +function IsOwnedUpdateFileName(FileName: String): Boolean; +var + LowerName: String; +begin + LowerName := Lowercase(FileName); + Result := (Length(LowerName) >= 22) and + (Copy(LowerName, 1, 17) = 'mlcplayer_setup_v') and + ((Copy(LowerName, Length(LowerName) - 3, 4) = '.exe') or + (Copy(LowerName, Length(LowerName) - 7, 8) = '.exe.sig')); +end; + +function IsOwnedIpcFileName(FileName: String): Boolean; +var + I: Integer; + Suffix: String; +begin + Result := False; + if Length(FileName) = 45 then + Suffix := Copy(FileName, 33, 13) + else if Length(FileName) = 44 then + Suffix := Copy(FileName, 33, 12) + else + Exit; + if (CompareText(Suffix, '.request.json') <> 0) and + (CompareText(Suffix, '.result.json') <> 0) then + Exit; + for I := 1 to 32 do + if not IsHexCharacter(FileName[I]) then + Exit; + Result := True; +end; + +procedure DeleteAllowedFiles(Directory: String; Kind: Integer); +var + FindRec: TFindRec; + Candidate: String; + Owned: Boolean; +begin + if not DirExists(Directory) then + Exit; + if IsReparsePoint(Directory) then + begin + MarkCleanupFailure(Directory); + Exit; + end; + if not FindFirst(AddBackslash(Directory) + '*', FindRec) then + begin + RemoveEmptyOwnedDirectory(Directory); + Exit; + end; + try + repeat + if (FindRec.Name <> '.') and (FindRec.Name <> '..') then + begin + Candidate := AddBackslash(Directory) + FindRec.Name; + if IsReparsePoint(Candidate) or DirExists(Candidate) then + MarkCleanupFailure(Candidate) + else + begin + Owned := ((Kind = 1) and IsOwnedThumbnailName(FindRec.Name)) or + ((Kind = 2) and IsOwnedUpdateFileName(FindRec.Name)) or + ((Kind = 3) and IsOwnedIpcFileName(FindRec.Name)); + if Owned then + DeleteOwnedFile(Candidate) + else + MarkCleanupFailure(Candidate); + end; + end; + until not FindNext(FindRec); + finally + FindClose(FindRec); + end; + if DirectoryHasEntries(Directory) then + MarkCleanupFailure(Directory); + RemoveEmptyOwnedDirectory(Directory); +end; + +procedure DeleteUpdateDirectories; +var + BaseDirectory, Candidate: String; + FindRec: TFindRec; +begin + BaseDirectory := ExpandConstant('{localappdata}'); + if not PathAncestorsAreSafe(BaseDirectory) then + begin + MarkCleanupFailure(BaseDirectory); + Exit; + end; + if not FindFirst(AddBackslash(BaseDirectory) + 'MLCPlayerUpdate_*', FindRec) then + Exit; + try + repeat + Candidate := AddBackslash(BaseDirectory) + FindRec.Name; + if DirExists(Candidate) and + (CompareText(Copy(FindRec.Name, 1, 16), + 'MLCPlayerUpdate_') = 0) then + begin + if IsReparsePoint(Candidate) then + MarkCleanupFailure(Candidate) + else + begin + DeleteAllowedFiles(Candidate, 2); + RemoveEmptyOwnedDirectory(Candidate); + end; + end; + until not FindNext(FindRec); + finally + FindClose(FindRec); + end; +end; + +function DeleteCredential(TargetName: String): Boolean; +begin + if CredDeleteW(TargetName, CredentialTypeGeneric, 0) then + Result := True + else + Result := DLLGetLastError = ErrorNotFound; +end; + +procedure DeleteOwnedUserData; +var + RoamingRoot, LocalRoot, LogDirectory, + CacheDirectory, ThumbnailDirectory: String; +begin + RoamingRoot := ExpandConstant('{userappdata}\MLCPlayer'); + LocalRoot := ExpandConstant('{localappdata}\MLCPlayer'); + LogDirectory := AddBackslash(RoamingRoot) + 'logs'; + CacheDirectory := AddBackslash(LocalRoot) + 'cache'; + ThumbnailDirectory := AddBackslash(CacheDirectory) + 'thumbnails'; + + if DeleteSettingsSelected and + not PathAncestorsAreSafe(RoamingRoot) then + begin + MarkCleanupFailure('user settings profile path'); + Exit; + end; + if DeleteCacheSelected and + (not PathAncestorsAreSafe(RoamingRoot) or + not PathAncestorsAreSafe(LogDirectory) or + not PathAncestorsAreSafe(LocalRoot) or + not PathAncestorsAreSafe(CacheDirectory) or + not PathAncestorsAreSafe(ThumbnailDirectory)) then + begin + MarkCleanupFailure('user cache profile path'); + Exit; + end; + + if DeleteSettingsSelected then + begin + if not DeleteCredential('MLCPlayer/OpenSubtitles/default') then + MarkCleanupFailure('OpenSubtitles default credential'); + if (StoredSubtitleUsername <> '') and + (CompareText(StoredSubtitleUsername, 'default') <> 0) and + (not DeleteCredential('MLCPlayer/OpenSubtitles/' + + StoredSubtitleUsername)) then + MarkCleanupFailure('OpenSubtitles user credential'); + if not DeleteCredential('MLCPlayer/OpenSubtitles.ApiKey/default') then + MarkCleanupFailure('OpenSubtitles API credential'); + if RegKeyExists(HKCU, UserSettingsKey) and + not RegDeleteKeyIncludingSubkeys(HKCU, UserSettingsKey) then + MarkCleanupFailure(UserSettingsKey); + if RegKeyExists(HKCU, UserSettingsKey) then + MarkCleanupFailure(UserSettingsKey); + DeleteOwnedFile(AddBackslash(RoamingRoot) + 'MLCPlayer.ini'); + end; + + if DeleteCacheSelected then + begin + DeleteOwnedFile(AddBackslash(LogDirectory) + 'uygulama.log'); + DeleteOwnedFile(AddBackslash(LogDirectory) + 'uygulama.log.1'); + RemoveEmptyOwnedDirectory(LogDirectory); + DeleteAllowedFiles(ThumbnailDirectory, 1); + RemoveEmptyOwnedDirectory(CacheDirectory); + DeleteUpdateDirectories; + end; + + if DeleteSettingsSelected or DeleteCacheSelected then + RemoveEmptyOwnedDirectory(RoamingRoot); + if DeleteCacheSelected then + RemoveEmptyOwnedDirectory(LocalRoot); + if DeleteSettingsSelected and DeleteCacheSelected then + begin + if DirectoryHasEntries(RoamingRoot) then + MarkCleanupFailure(RoamingRoot); + if DirectoryHasEntries(LocalRoot) then + MarkCleanupFailure(LocalRoot); + end; +end; + +procedure AppendCleanupIdentity(Sid, TaskName, HelperPath, HelperHash, + MarkerKey: String); +var + Index: Integer; +begin + Index := CleanupIdentityCount; + SetArrayLength(CleanupUserSids, Index + 1); + SetArrayLength(CleanupTaskNames, Index + 1); + SetArrayLength(CleanupHelperPaths, Index + 1); + SetArrayLength(CleanupHelperHashes, Index + 1); + SetArrayLength(CleanupMarkerKeys, Index + 1); + CleanupUserSids[Index] := Sid; + CleanupTaskNames[Index] := TaskName; + CleanupHelperPaths[Index] := HelperPath; + CleanupHelperHashes[Index] := HelperHash; + CleanupMarkerKeys[Index] := MarkerKey; + CleanupIdentityCount := Index + 1; +end; + +function ExpectedCleanupHelperForSid(Sid: String; + var ExpectedHelper: String): Boolean; +var + ProfilePath: String; +begin + Result := False; + if not IsCanonicalSid(Sid) or + not RegQueryStringValue(HKLM, + 'SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\' + Sid, + 'ProfileImagePath', ProfilePath) then + Exit; + StringChangeEx(ProfilePath, '%SystemDrive%', ExpandConstant('{sd}'), True); + if Pos('%', ProfilePath) <> 0 then + Exit; + ExpectedHelper := AddBackslash(ProfilePath) + + 'AppData\Local\Programs\MLC Player\UninstallCleanup\v1\MLCUserCleanup.exe'; + Result := True; +end; + +function ExpectedLegacyCleanupHelperForSid(Sid: String; + var ExpectedHelper: String): Boolean; +var + ProfilePath: String; +begin + Result := False; + if not IsCanonicalSid(Sid) or + not RegQueryStringValue(HKLM, + 'SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\' + Sid, + 'ProfileImagePath', ProfilePath) then + Exit; + StringChangeEx(ProfilePath, '%SystemDrive%', ExpandConstant('{sd}'), True); + if Pos('%', ProfilePath) <> 0 then + Exit; + ExpectedHelper := AddBackslash(ProfilePath) + + 'AppData\Local\MLCPlayer\UninstallCleanup\v1\MLCUserCleanup.exe'; + Result := True; +end; + +function CleanupSidWasLoaded(Sid: String): Boolean; +var + I: Integer; +begin + Result := False; + for I := 0 to CleanupIdentityCount - 1 do + if CompareText(CleanupUserSids[I], Sid) = 0 then + begin + Result := True; + Exit; + end; +end; + +function LoadCleanupIdentities: Boolean; +var + UserSids, ProfileSids: TArrayOfString; + I: Integer; + CandidateKey, CandidateInstallId, CandidateSid, CandidateTask, + CandidateHelper, CandidateHash, ExpectedHelper, ExpectedLegacyHelper: String; + CandidateValid: Boolean; +begin + Result := False; + CleanupIdentityCount := 0; + CleanupIdentityProblems := False; + if not RegQueryStringValue(HKLM, CleanupRegistryKey, 'InstallId', + CleanupInstallId) or not IsCleanupId(CleanupInstallId) then + Exit; + if not RegGetSubkeyNames(HKU, '', UserSids) then + Exit; + for I := 0 to GetArrayLength(UserSids) - 1 do + if IsCanonicalSid(UserSids[I]) then + begin + CandidateKey := UserSids[I] + '\Software\MLCPlayer\UninstallCleanup'; + if RegQueryStringValue(HKU, CandidateKey, 'InstallId', + CandidateInstallId) and + (CompareText(CandidateInstallId, CleanupInstallId) = 0) then + begin + CandidateValid := RegQueryStringValue(HKU, CandidateKey, 'UserSid', + CandidateSid); + CandidateValid := RegQueryStringValue(HKU, CandidateKey, 'TaskName', + CandidateTask) and CandidateValid; + CandidateValid := RegQueryStringValue(HKU, CandidateKey, 'HelperPath', + CandidateHelper) and CandidateValid; + CandidateValid := RegQueryStringValue(HKU, CandidateKey, + 'HelperSha256', CandidateHash) and CandidateValid; + CandidateValid := ExpectedCleanupHelperForSid(UserSids[I], + ExpectedHelper) and CandidateValid; + CandidateValid := ExpectedLegacyCleanupHelperForSid(UserSids[I], + ExpectedLegacyHelper) and CandidateValid; + CandidateValid := CandidateValid and + (CompareText(CandidateSid, UserSids[I]) = 0) and + (CompareText(CandidateTask, + ExpectedCleanupTaskName(UserSids[I])) = 0) and + (PathSame(CandidateHelper, ExpectedHelper) or + PathSame(CandidateHelper, ExpectedLegacyHelper)) and + CleanupHelperPathIsSafe(CandidateHelper) and + (CandidateHash <> '') and + (CompareText(GetSHA256OfFile(CandidateHelper), CandidateHash) = 0); + if CandidateValid then + AppendCleanupIdentity(CandidateSid, CandidateTask, CandidateHelper, + CandidateHash, CandidateKey) + else + CleanupIdentityProblems := True; + end; + end; + if RegGetSubkeyNames(HKLM, + 'SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList', + ProfileSids) then + for I := 0 to GetArrayLength(ProfileSids) - 1 do + if ExpectedCleanupHelperForSid(ProfileSids[I], ExpectedHelper) and + ExpectedLegacyCleanupHelperForSid(ProfileSids[I], + ExpectedLegacyHelper) and + (FileExists(ExpectedHelper) or FileExists(ExpectedLegacyHelper)) and + not CleanupSidWasLoaded(ProfileSids[I]) then + CleanupIdentityProblems := True; + Result := True; +end; + +procedure SelectCleanupIdentity(Index: Integer); +begin + CleanupUserSid := CleanupUserSids[Index]; + CleanupTaskName := CleanupTaskNames[Index]; + CleanupHelperPath := CleanupHelperPaths[Index]; + CleanupHelperHash := CleanupHelperHashes[Index]; + CleanupMarkerKey := CleanupMarkerKeys[Index]; +end; + +function NewCleanupNonce: String; +begin + Result := Lowercase(Copy(GetSHA256OfUnicodeString( + GetDateTimeString('yyyymmddhhnnsszzz', '-', ':') + '|' + + IntToStr(Random(2147483647)) + '|' + CleanupUserSid), 1, 32)); +end; + +function JsonBoolean(Value: Boolean): String; +begin + if Value then + Result := 'true' + else + Result := 'false'; +end; + +procedure RemoveCleanupInfrastructure; +var + ExitCode, I: Integer; + V1Directory, CleanupDirectory, ProductDirectory, IpcDirectory: String; +begin + if not CleanupIdentityValid then + Exit; + Exec(ExpandConstant('{sys}\schtasks.exe'), + '/End /TN "' + CleanupTaskName + '"', '', SW_HIDE, + ewWaitUntilTerminated, ExitCode); + Exec(ExpandConstant('{sys}\schtasks.exe'), + '/Delete /TN "' + CleanupTaskName + '" /F', '', SW_HIDE, + ewWaitUntilTerminated, ExitCode); + if Exec(ExpandConstant('{sys}\schtasks.exe'), + '/Query /TN "' + CleanupTaskName + '"', '', SW_HIDE, + ewWaitUntilTerminated, ExitCode) and (ExitCode = 0) then + MarkCleanupFailure('user cleanup task'); + if CleanupHelperPathIsSafe(CleanupHelperPath) then + begin + IpcDirectory := AddBackslash(ExtractFileDir(CleanupHelperPath)) + 'ipc'; + DeleteAllowedFiles(IpcDirectory, 3); + for I := 1 to 50 do + begin + if not FileExists(CleanupHelperPath) then + Break; + DeleteFile(CleanupHelperPath); + if FileExists(CleanupHelperPath) then + Sleep(100); + end; + if FileExists(CleanupHelperPath) then + MarkCleanupFailure('user cleanup helper'); + V1Directory := ExtractFileDir(CleanupHelperPath); + CleanupDirectory := ExtractFileDir(V1Directory); + ProductDirectory := ExtractFileDir(CleanupDirectory); + RemoveEmptyOwnedDirectory(V1Directory); + RemoveEmptyOwnedDirectory(CleanupDirectory); + RemoveEmptyOwnedDirectory(ProductDirectory); + end + else + MarkCleanupFailure('user cleanup helper path'); + if RegKeyExists(HKU, CleanupMarkerKey) and + not RegDeleteKeyIncludingSubkeys(HKU, CleanupMarkerKey) then + MarkCleanupFailure('user cleanup registration'); +end; + +procedure DeleteOwnedUserDataInRegisteredSession; +var + Nonce, RequestFileName, ResultFileName, RequestText, + UserRegistryKey: String; + ResultText: AnsiString; + ExitCode, I: Integer; +begin + if not CleanupIdentityValid then + begin + MarkCleanupFailure('registered user cleanup identity'); + Exit; + end; + Nonce := NewCleanupNonce; + RequestFileName := AddBackslash(ExtractFileDir(CleanupHelperPath)) + + 'ipc\' + Nonce + '.request.json'; + ResultFileName := AddBackslash(ExtractFileDir(CleanupHelperPath)) + + 'ipc\' + Nonce + '.result.json'; + if not DirExists(ExtractFileDir(RequestFileName)) or + IsReparsePoint(ExtractFileDir(RequestFileName)) then + begin + MarkCleanupFailure('user cleanup IPC directory'); + RemoveCleanupInfrastructure; + Exit; + end; + DeleteFile(RequestFileName); + DeleteFile(ResultFileName); + RequestText := '{"nonce":"' + Nonce + '","sid":"' + + CleanupUserSid + '","delete_settings":' + + JsonBoolean(DeleteSettingsSelected) + ',"delete_cache":' + + JsonBoolean(DeleteCacheSelected) + '}'; + if not SaveStringToFile(RequestFileName, RequestText, False) then + begin + MarkCleanupFailure('user cleanup request'); + RemoveCleanupInfrastructure; + Exit; + end; + if not Exec(ExpandConstant('{sys}\schtasks.exe'), + '/Run /TN "' + CleanupTaskName + '"', '', SW_HIDE, + ewWaitUntilTerminated, ExitCode) or (ExitCode <> 0) then + begin + MarkCleanupFailure('user cleanup task start'); + RemoveCleanupInfrastructure; + Exit; + end; + ResultText := ''; + for I := 1 to 3000 do + begin + if LoadStringFromFile(ResultFileName, ResultText) then + Break; + Sleep(100); + end; + if ResultText = '' then + begin + MarkCleanupFailure('user cleanup timeout'); + RemoveCleanupInfrastructure; + Exit; + end; + if ResultText <> '{"failure_count":0,"nonce":"' + Nonce + + '","succeeded":true}' then + MarkCleanupFailure('user cleanup result'); + DeleteFile(RequestFileName); + DeleteFile(ResultFileName); + RemoveCleanupInfrastructure; + if DeleteSettingsSelected then + begin + UserRegistryKey := CleanupUserSid + '\' + UserSettingsKey; + if RegKeyExists(HKU, UserRegistryKey) and + not RegDeleteKeyIncludingSubkeys(HKU, UserRegistryKey) then + MarkCleanupFailure(UserRegistryKey); + if RegKeyExists(HKU, UserRegistryKey) then + MarkCleanupFailure(UserRegistryKey); + end; +end; + +procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); +var + I: Integer; +begin + // Eklenti ana dosya silinmeden önce tamamlanır. Kullanıcı verisi ise ancak + // ana kaldırma başarılı post-uninstall aşamasına ulaştığında değişir. + if CurUninstallStep = usUninstall then + RemoveSelectedAddon + else if CurUninstallStep = usPostUninstall then + begin + if DeleteSettingsSelected or DeleteCacheSelected then + begin + if not CleanupIdentityValid then + MarkCleanupFailure('registered user cleanup identity') + else + for I := 0 to CleanupIdentityCount - 1 do + begin + SelectCleanupIdentity(I); + DeleteOwnedUserDataInRegisteredSession; + end; + { The uninstaller's current HKCU and profile may have no helper yet when + Player has never run. The bounded direct path still removes only the + exact stores selected for this Windows account. } + DeleteOwnedUserData; + end + else if CleanupIdentityValid then + for I := 0 to CleanupIdentityCount - 1 do + begin + SelectCleanupIdentity(I); + RemoveCleanupInfrastructure; + end; + if CleanupIdentityProblems then + MarkCleanupFailure('unloaded or invalid user cleanup registration'); + end + else if (CurUninstallStep = usDone) and CleanupFailed and + (not UninstallSilent) then + MsgBox(CustomMessage('UninstallCleanupFailed'), mbCriticalError, MB_OK); +end; + function InitializeUninstall(): Boolean; +var + I: Integer; begin // CloseApplications yalnız Setup/upgrade Restart Manager yoludur. Uninstall // açık ürünün image kilitlerini temizlemeyi garanti etmez. Ürünün sabit @@ -990,6 +1980,31 @@ begin // fail-closed dururuz; kayıt/kısayolları silip binary bırakmayız. Result := not CheckForMutexes('{#PlayerLifecycleMutex}'); if not Result then + begin MsgBox(ExpandConstant('{cm:ClosePlayerBeforeUninstall}'), mbCriticalError, MB_OK); + Exit; + end; + + CleanupFailed := False; + CleanupUserSid := ''; + CleanupTaskName := ''; + CleanupHelperPath := ''; + CleanupHelperHash := ''; + CleanupAccountDisplay := ExpandConstant('{username}'); + CleanupIdentityValid := LoadCleanupIdentities; + if CleanupIdentityValid then + for I := 0 to CleanupIdentityCount - 1 do + begin + CleanupAccountDisplay := CleanupAccountDisplay + ', ' + + CleanupUserSids[I]; + end; + StoredSubtitleUsername := ''; + RegQueryStringValue(HKCU, SubtitleSettingsKey, 'username', + StoredSubtitleUsername); + InitializeUninstallCleanupChoices; + if UninstallSilent then + Result := True + else + Result := ShowUninstallCleanupChoices; end; diff --git a/MLCPlayer.spec b/packaging/MLCPlayer.spec similarity index 84% rename from MLCPlayer.spec rename to packaging/MLCPlayer.spec index 85ee904..a6bd8b3 100644 --- a/MLCPlayer.spec +++ b/packaging/MLCPlayer.spec @@ -1,7 +1,10 @@ # SPDX-FileCopyrightText: 2026 MLC Player contributors # SPDX-License-Identifier: GPL-3.0-only # PyInstaller spec dosyası - MLC Player -# Kullanım: pyinstaller MLCPlayer.spec +# Kullanım: packaging\build_release.bat (önerilen; temizler ve doğrular). +# Elle çalıştırılacaksa çıktı Inno'nun okuduğu klasöre gitmelidir: +# python packaging/run_pyinstaller.py packaging/MLCPlayer.spec --noconfirm +# --clean --distpath output/dist --workpath output/build # Not: python-mpv mpv-2.dll'i dışarıdan yükler (add_dll_directory ile), # bu yüzden DLL'i datalar ile birlikte paketlemek yeterlidir. @@ -16,7 +19,20 @@ block_cipher = None # Windows sürüm kaynağı (VS_VERSION_INFO). Olmadığında Windows "Birlikte aç" # listesinde programı DOSYA ADIYLA ("MLC Player.exe") gösteriyordu. # Değerler app/config.py'deki tek sürüm kaynağından türer. -_project_root = os.path.abspath(os.getcwd()) +# The spec lives in packaging/; PyInstaller resolves relative script and +# data paths against the spec directory, so every repository path is made +# absolute from the project root (independent of the current directory). +_project_root = os.path.dirname(os.path.abspath(SPECPATH)) + + +def _from_root(relative): + return os.path.join(_project_root, *relative.split('/')) + + +def _datas(entries): + return [(_from_root(source), target) for source, target in entries] + + if _project_root not in sys.path: sys.path.insert(0, _project_root) from app.config import APP_VERSION, WINDOWS_VERSION # noqa: E402 @@ -34,15 +50,16 @@ _policy_spec = importlib.util.spec_from_file_location( binary_policy = importlib.util.module_from_spec(_policy_spec) _policy_spec.loader.exec_module(binary_policy) -VERSION_FILE = os.path.join(_project_root, 'build', 'file_version_info.txt') +VERSION_FILE = os.path.join(_project_root, 'output', 'build', + 'file_version_info.txt') os.makedirs(os.path.dirname(VERSION_FILE), exist_ok=True) version_resource.write(VERSION_FILE, APP_VERSION, WINDOWS_VERSION) a = Analysis( - ['main.py'], - pathex=[], + [_from_root('app/main.py')], + pathex=[_project_root], binaries=[], - datas=[ + datas=_datas([ # Calisma zamani ikilileri `_internal\bin` altinda toplanir. ('bin/mpv-2.dll', 'bin'), # mpv + ffmpeg runtime DLL # yt-dlp.exe ve deno.exe BILEREK YOK: birlikte 110 MB tutuyorlar @@ -86,9 +103,9 @@ a = Analysis( for name in sorted(os.listdir(os.path.join(_project_root, 'translations'))) if name.endswith('.qm')], - ('README.md', '.'), - ('README.tr.md', '.'), - ], + ('.github/README.md', '.'), + ('.github/README.tr.md', '.'), + ]), hiddenimports=[ 'mpv', ], @@ -96,7 +113,7 @@ a = Analysis( hooksconfig={}, runtime_hooks=[], # URUNDE HIC KULLANILMAYAN paketler (kaynak taramasiyla dogrulandi): - # `numpy` ve `PIL` app/ ve main.py icinde import EDILMIYOR; PyInstaller + # `numpy` ve `PIL` app/ ve app/main.py icinde import EDILMIYOR; PyInstaller # onlari gecisli olarak topluyordu (~38 MB). # `PySide6` YALNIZ gelistirici aracidir (ceviri derleyicisi ve # Linguist). Urun PyQt6 ile calisir; ikinci bir Qt baglamasi @@ -148,13 +165,13 @@ exe = EXE( bootloader_ignore_signals=False, strip=False, # UPX bu turda ZORLA acilmaz; karar gercek antivirus/baslatma - # kabulunden sonra verilir (bkz. docs/PACKAGING_PLAN.md). + # kabulunden sonra verilir (bkz. docs/release/PACKAGING_PLAN.md). upx=False, upx_exclude=[], # Destek dosyalari klasoru ACIKCA sabitlenir; PyInstaller surum # varsayimina birakilmaz. contents_directory='_internal', - icon='assets/mlc-player-icon-transparent.ico', + icon=_from_root('assets/mlc-player-icon-transparent.ico'), # Windows'un gösterdiği ad ve sürüm alanları buradan gelir. version=VERSION_FILE, console=False, # GUI uygulaması - konsol penceresi açmasın diff --git a/packaging/MLCPlayer_InternetVideo.iss b/packaging/MLCPlayer_InternetVideo.iss index 9e6505b..6c21953 100644 --- a/packaging/MLCPlayer_InternetVideo.iss +++ b/packaging/MLCPlayer_InternetVideo.iss @@ -34,7 +34,7 @@ DisableWelcomePage=no ; Dil Windows'tan secilir; kullaniciya sorulmaz. ShowLanguageDialog=no PrivilegesRequired=admin -OutputDir=..\installer_output +OutputDir=..\output\installer OutputBaseFilename=MLCPlayer_InternetVideo_{#AddonVersion} SetupIconFile=..\assets\mlc-player-icon.ico UninstallDisplayIcon={app}\MLC Player.exe diff --git a/packaging/MLCUserCleanup.spec b/packaging/MLCUserCleanup.spec new file mode 100644 index 0000000..aa1a3a6 --- /dev/null +++ b/packaging/MLCUserCleanup.spec @@ -0,0 +1,23 @@ +# -*- mode: python ; coding: utf-8 -*- +# SPDX-FileCopyrightText: 2026 MLC Player contributors +# SPDX-License-Identifier: GPL-3.0-only + +import os + +# The spec lives in packaging/; resolve repository paths from the root. +_project_root = os.path.dirname(os.path.abspath(SPECPATH)) + +a = Analysis( + [os.path.join(_project_root, 'app', 'user_cleanup_main.py')], + pathex=[_project_root], binaries=[], datas=[], + hiddenimports=[], hookspath=[], hooksconfig={}, runtime_hooks=[], + excludes=['PyQt6', 'mpv', 'pytest', 'numpy', 'PIL'], noarchive=False, +) +pyz = PYZ(a.pure) +exe = EXE( + pyz, a.scripts, a.binaries, a.datas, [], name='MLCUserCleanup', + debug=False, bootloader_ignore_signals=False, strip=False, upx=False, + console=False, + icon=os.path.join(_project_root, 'assets', + 'mlc-player-icon-transparent.ico'), +) diff --git a/packaging/build_release.bat b/packaging/build_release.bat index d759dc7..f72300f 100644 --- a/packaging/build_release.bat +++ b/packaging/build_release.bat @@ -7,9 +7,9 @@ rem rem In order: rem 1. Pre-flight check (source files + runtime SHA-256) rem 2. Translation compile (.ts -> .qm) -rem 3. PyInstaller (onedir -> dist\MLC Player\) +rem 3. PyInstaller (onedir -> output\dist\MLC Player\) rem 4. Package check (are bin/licenses/assets complete) -rem 5. Inno Setup (-> installer_output\MLCPlayer_Setup_.exe) +rem 5. Inno Setup (-> output\installer\MLCPlayer_Setup_.exe) rem 6. Publisher signature rem 7. Result report rem @@ -22,11 +22,12 @@ chcp 65001 >nul 2>&1 rem Move to the project root (this file lives under packaging\). cd /d "%~dp0.." set "PROJECT=%CD%" -set "SPEC=MLCPlayer.spec" +set "SPEC=packaging/MLCPlayer.spec" +set "CLEANUP_SPEC=packaging/MLCUserCleanup.spec" set "ISS=packaging\MLCPlayer.iss" set "VERIFY=packaging\verify_build.py" set "DEPENDENCY_VERIFY=packaging\verify_dependencies.py" -set "DEPENDENCY_LOCK=requirements-lock.txt" +set "DEPENDENCY_LOCK=requirements/lock.txt" echo. echo ============================================================ @@ -77,7 +78,7 @@ if not defined ISCC ( ) rem --- Version: read ONCE, up front ------------------------------- -rem MEASURED RISK: installer_output holds every build from v0.1 to v0.36 +rem MEASURED RISK: output\installer holds every build from v0.1 to v0.36 rem side by side. The chain used to pick the file to sign with a wildcard rem (`MLCPlayer_Setup_*.exe`); `for` walks the matches and the LAST one rem wins, and that order comes from the filesystem. The sharp failure is @@ -100,8 +101,8 @@ if not defined APP_NUM ( echo ERROR: WINDOWS_VERSION could not be read from app\config.py. goto :fail ) -set "MAIN_SETUP=installer_output\MLCPlayer_Setup_!APP_VER!.exe" -set "ADDON_SETUP=installer_output\MLCPlayer_InternetVideo_!APP_VER!.exe" +set "MAIN_SETUP=output\installer\MLCPlayer_Setup_!APP_VER!.exe" +set "ADDON_SETUP=output\installer\MLCPlayer_InternetVideo_!APP_VER!.exe" echo Version : !APP_VER! (!APP_NUM!) echo Installer: !MAIN_SETUP! echo Add-on : !ADDON_SETUP! @@ -124,24 +125,24 @@ rem open Explorer window or a permission problem leaves the tree in rem place. The result was never checked, so the chain could carry on with rem LAST RUN's build\ and dist\ and what looked like fresh PyInstaller rem output could be a leftover. Each removal is now verified. -if exist "build" ( - rmdir /s /q "build" - if exist "build" ( +if exist "output\build" ( + rmdir /s /q "output\build" + if exist "output\build" ( echo ERROR: the stale build\ tree could not be removed. echo Close anything holding it open and run again. goto :fail ) ) -if exist "dist" ( - rmdir /s /q "dist" - if exist "dist" ( +if exist "output\dist" ( + rmdir /s /q "output\dist" + if exist "output\dist" ( echo ERROR: the stale dist\ tree could not be removed. echo Close anything holding it open and run again. goto :fail ) ) rem ONLY this version's four exact outputs. Older releases in -rem installer_output are KEPT: they are the artifacts already published, +rem output\installer are KEPT: they are the artifacts already published, rem and a broad `del *.exe` would destroy them. A stale file that cannot rem be removed STOPS the chain -- otherwise the next steps could not tell rem a fresh build from last run's leftover. @@ -159,7 +160,7 @@ echo. echo STEP 3/8 Compiling translations (.ts -^> .qm) rem `.qm` files are build output and are NOT kept in the repository; -rem MLCPlayer.spec collects them from translations\. Without this step a +rem packaging/MLCPlayer.spec collects them from translations\. Without this step a rem clean checkout produces a package with NO translations and the user rem silently gets Turkish only. python "packaging\compile_translations.py" @@ -167,7 +168,16 @@ if errorlevel 1 goto :fail echo. echo STEP 4/8 PyInstaller (onedir) - this can take a few minutes -python "packaging\run_pyinstaller.py" "%SPEC%" --noconfirm --clean --log-level WARN +python "packaging\run_pyinstaller.py" "%CLEANUP_SPEC%" --noconfirm --clean --log-level WARN --distpath "output\dist" --workpath "output\build" +if errorlevel 1 ( + echo ERROR: uninstall cleanup helper build failed. + goto :fail +) +if not exist "output\dist\MLCUserCleanup.exe" ( + echo ERROR: output\dist\MLCUserCleanup.exe was not produced. + goto :fail +) +python "packaging\run_pyinstaller.py" "%SPEC%" --noconfirm --clean --log-level WARN --distpath "output\dist" --workpath "output\build" if errorlevel 1 ( echo ERROR: PyInstaller failed. goto :fail @@ -233,7 +243,7 @@ echo. echo ============================================================ echo DONE echo. -echo Folder : dist\MLC Player\ (move the whole folder together) +echo Folder : output\dist\MLC Player\ (move the whole folder together) echo Installer: !MAIN_SETUP! echo Signature: !MAIN_SETUP!.sig (MUST be uploaded to the release with it) echo Add-on : !ADDON_SETUP! (+ .sig) - internet video components diff --git a/packaging/build_unsigned_main.bat b/packaging/build_unsigned_main.bat index 2b52948..fc650bd 100644 --- a/packaging/build_unsigned_main.bat +++ b/packaging/build_unsigned_main.bat @@ -12,7 +12,7 @@ if /I not "%MLC_HOSTED_UNSIGNED_BUILD%"=="1" ( ) cd /d "%~dp0.." -set "SPEC=MLCPlayer.spec" +set "SPEC=packaging/MLCPlayer.spec" set "ISS=packaging\MLCPlayer.iss" set "VERIFY=packaging\verify_build.py" @@ -24,7 +24,7 @@ where python >nul 2>&1 if errorlevel 1 goto :fail python -m PyInstaller --version >nul 2>&1 if errorlevel 1 goto :fail -python "packaging\verify_dependencies.py" "requirements-lock.txt" +python "packaging\verify_dependencies.py" "requirements/lock.txt" if errorlevel 1 goto :fail set "ISCC=" @@ -42,7 +42,7 @@ if not defined ISCC ( set "APP_VER=" for /f "usebackq delims=" %%V in (`python -c "import sys; sys.path.insert(0,'.'); from app.config import APP_VERSION; print(APP_VERSION)"`) do set "APP_VER=%%V" if not defined APP_VER goto :fail -set "MAIN_SETUP=installer_output\MLCPlayer_Setup_!APP_VER!.exe" +set "MAIN_SETUP=output\installer\MLCPlayer_Setup_!APP_VER!.exe" echo STEP 1/6 Main-package source and runtime verification python "%VERIFY%" --pre-main @@ -51,13 +51,13 @@ python "packaging\verify_inno.py" main --iscc "%ISCC%" if errorlevel 1 goto :fail echo STEP 2/6 Clean exact build outputs -if exist "build" ( - rmdir /s /q "build" - if exist "build" goto :fail +if exist "output\build" ( + rmdir /s /q "output\build" + if exist "output\build" goto :fail ) -if exist "dist" ( - rmdir /s /q "dist" - if exist "dist" goto :fail +if exist "output\dist" ( + rmdir /s /q "output\dist" + if exist "output\dist" goto :fail ) if exist "!MAIN_SETUP!" ( del /f /q "!MAIN_SETUP!" @@ -73,7 +73,7 @@ python "packaging\compile_translations.py" if errorlevel 1 goto :fail echo STEP 4/6 PyInstaller main package -python "packaging\run_pyinstaller.py" "%SPEC%" --noconfirm --clean --log-level WARN +python "packaging\run_pyinstaller.py" "%SPEC%" --noconfirm --clean --log-level WARN --distpath "output\dist" --workpath "output\build" if errorlevel 1 goto :fail python "%VERIFY%" --post if errorlevel 1 goto :fail diff --git a/packaging/compile_translations.py b/packaging/compile_translations.py index 1f3cc5c..516c9d0 100644 --- a/packaging/compile_translations.py +++ b/packaging/compile_translations.py @@ -3,7 +3,7 @@ """Compiles the `.ts` translation sources into `.qm` binaries. WHY IT IS A SEPARATE STEP. `.qm` files are BUILD OUTPUT and live in -`.gitignore`; `MLCPlayer.spec` collects them from the `translations/` +`.gitignore`; `packaging/MLCPlayer.spec` collects them from the `translations/` folder. But nothing in the chain compiled them: on a clean checkout `packaging/build_release.bat` left that folder empty, the package shipped with no translations, and the user silently got Turkish only. This script diff --git a/packaging/extract_translations.py b/packaging/extract_translations.py index b0bb158..129c0de 100644 --- a/packaging/extract_translations.py +++ b/packaging/extract_translations.py @@ -38,7 +38,9 @@ #: Sources that are scanned. Tests and packaging scripts are NOT visible #: to the user. SOURCE_DIRECTORIES = ("app",) -SOURCE_FILES = ("main.py",) +#: Extra files outside SOURCE_DIRECTORIES. The entry point moved to +#: `app/main.py` and is already covered by the `app` directory scan. +SOURCE_FILES = () #: Calls that PUT text into the translation files. `tr()` translates right diff --git a/packaging/fetch_sources.py b/packaging/fetch_sources.py index 67ab0cb..c776603 100644 --- a/packaging/fetch_sources.py +++ b/packaging/fetch_sources.py @@ -26,7 +26,7 @@ SOURCE_MANIFEST_NAME = "corresponding_sources.json" SOURCE_MANIFEST_PATH = os.path.join( os.path.dirname(os.path.abspath(__file__)), SOURCE_MANIFEST_NAME) -OUTPUT_DIR_NAME = "source_mirror" +OUTPUT_DIR_NAME = os.path.join("output", "source_mirror") TRUSTED_HOSTS = frozenset({ "github.com", diff --git a/packaging/make_app_icon.py b/packaging/make_app_icon.py index e41bacb..67e2f9e 100644 --- a/packaging/make_app_icon.py +++ b/packaging/make_app_icon.py @@ -14,8 +14,8 @@ the plate colour, the more opaque it becomes, and its colour is snapped to the nearest flat mark colour. That keeps the edges clean. -The installer wizard images are DELIBERATELY left alone; they sit on a -dark panel where the plate is not a problem. +The installer wizard images are regenerated separately by +`packaging/make_wizard_images.py` from the same round source art. Usage: python packaging/make_app_icon.py """ diff --git a/packaging/prepublish.py b/packaging/prepublish.py index de2f658..bdf0d55 100644 --- a/packaging/prepublish.py +++ b/packaging/prepublish.py @@ -4,7 +4,7 @@ KESIN YAYIN SIRASI BURADA TEKRARLANMAZ. Tek resmi kaynak: - docs/RELEASE_PROCESS.md + docs/release/RELEASE_PROCESS.md Bu kapi o siradaki (e) adimidir: build BITTIKTEN ve yerel ANNOTATED tag atildiktan SONRA, push ve release'den ONCE calisir. @@ -48,8 +48,8 @@ import verify_release_ref # noqa: E402 from app import release_signature # noqa: E402 -INSTALLER_DIR = "installer_output" -MIRROR_DIR = "source_mirror" +INSTALLER_DIR = os.path.join("output", "installer") +MIRROR_DIR = os.path.join("output", "source_mirror") SOURCE_CONTRACT = os.path.join("packaging", "corresponding_sources.json") #: Surumlu installer govdeleri. Her birinin `.sig`i de zorunludur. @@ -97,7 +97,7 @@ def working_tree_is_clean(root=None, log=print): """Staged, tracked ve IGNORE EDILMEYEN untracked degisiklik olmamali. `git status --porcelain` ignore edilen dosyalari zaten disarida - birakir; `installer_output` ve `source_mirror` bu yuzden kapiyi + birakir; `output/installer` ve `output/source_mirror` bu yuzden kapiyi kapatmaz. """ ok, output = verify_release_ref.run_git(["status", "--porcelain"], root) diff --git a/packaging/sign_release.py b/packaging/sign_release.py index f9ed880..eb22d41 100644 --- a/packaging/sign_release.py +++ b/packaging/sign_release.py @@ -7,7 +7,7 @@ the repository; the public key is printed and embedded by hand into `app/release_signature.py`. - python packaging/sign_release.py installer_output/MLCPlayer_Setup_vX.exe + python packaging/sign_release.py output/installer/MLCPlayer_Setup_vX.exe Signs the file's SHA-256 digest and writes `.sig`. That file MUST be uploaded to the release as an asset; the updater downloads it and verifies it against the embedded public key. diff --git a/packaging/stage_cryptography_sources.py b/packaging/stage_cryptography_sources.py index eb28829..2af1126 100644 --- a/packaging/stage_cryptography_sources.py +++ b/packaging/stage_cryptography_sources.py @@ -7,7 +7,7 @@ The cryptography sdist's Cargo.lock is used as an independent cross-check. This helper never builds, installs, tags or releases anything. ``--download`` -only fetches missing immutable ``.crate`` source archives into source_mirror. +only fetches missing immutable ``.crate`` source archives into output/source_mirror. """ from __future__ import annotations @@ -29,7 +29,7 @@ ROOT = Path(__file__).resolve().parent.parent -SOURCE_MIRROR = ROOT / "source_mirror" +SOURCE_MIRROR = ROOT / "output" / "source_mirror" CONTRACT_PATH = ROOT / "packaging" / "corresponding_sources.json" EXPECTED_CRYPTOGRAPHY_VERSION = "50.0.0" EXPECTED_OPENSSL_VERSION = "4.0.1" diff --git a/packaging/stage_ytdlp_sources.py b/packaging/stage_ytdlp_sources.py index 89cb4bb..779bfc7 100644 --- a/packaging/stage_ytdlp_sources.py +++ b/packaging/stage_ytdlp_sources.py @@ -7,7 +7,7 @@ pins and curl-impersonate's CMake source pins form one fail-closed inventory. This helper never builds or installs anything. ``--download`` only fetches -missing immutable source archives into ``source_mirror`` and verifies SHA-256 +missing immutable source archives into ``output/source_mirror`` and verifies SHA-256 before replacing a target. """ @@ -28,7 +28,7 @@ ROOT = Path(__file__).resolve().parent.parent -SOURCE_MIRROR = ROOT / "source_mirror" +SOURCE_MIRROR = ROOT / "output" / "source_mirror" EXE = ROOT / "bin" / "yt-dlp.exe" NOTICE = ROOT / "licenses" / "yt-dlp-THIRD_PARTY_LICENSES.txt" EXPECTED_EXE_SHA256 = ( diff --git a/packaging/verify_build.py b/packaging/verify_build.py index 20c2733..ded7982 100644 --- a/packaging/verify_build.py +++ b/packaging/verify_build.py @@ -17,7 +17,7 @@ from pyinstaller_binary_policy import is_forbidden_root_destination ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -DIST = os.path.join(ROOT, "dist", "MLC Player") +DIST = os.path.join(ROOT, "output", "dist", "MLC Player") MANIFEST = os.path.join(ROOT, "bin", "RUNTIME_MANIFEST.txt") # Paketlenen runtime ikilileri -- TEK kaynak. @@ -52,12 +52,12 @@ os.path.join("assets", "mlc-player-icon.ico"), os.path.join("assets", "mlc-player-icon-transparent.ico"), "LICENSE", - "README.md", - "README.tr.md", - "MLCPlayer.spec", + os.path.join(".github", "README.md"), + os.path.join(".github", "README.tr.md"), + os.path.join("packaging", "MLCPlayer.spec"), os.path.join("packaging", "pyinstaller_binary_policy.py"), os.path.join("packaging", "run_pyinstaller.py"), - "main.py", + "app/main.py", ) # Optional add-on inputs. The established full release preflight remains the @@ -233,7 +233,7 @@ def check_post(): ok = True print("[2/3] dist tree") if not os.path.isdir(DIST): - return fail("dist\\MLC Player was not created") + return fail("output\\dist\\MLC Player was not created") for relative in REQUIRED_IN_DIST: path = os.path.join(DIST, relative) if not os.path.exists(path): diff --git a/packaging/version_resource.py b/packaging/version_resource.py index d428065..17d756f 100644 --- a/packaging/version_resource.py +++ b/packaging/version_resource.py @@ -9,7 +9,7 @@ `dist\\MLC Player\\MLC Player.exe` -> ProductVersion empty. The values derive from the single source in `app/config.py` -(`APP_VERSION` / `WINDOWS_VERSION`); `MLCPlayer.spec` writes this text at +(`APP_VERSION` / `WINDOWS_VERSION`); `packaging/MLCPlayer.spec` writes this text at build time and hands it to PyInstaller through `version=`. """ diff --git a/packaging/wizard/wizard-large-1.bmp b/packaging/wizard/wizard-large-1.bmp index 8a9bb1e..3f47b9c 100644 Binary files a/packaging/wizard/wizard-large-1.bmp and b/packaging/wizard/wizard-large-1.bmp differ diff --git a/packaging/wizard/wizard-large-2.bmp b/packaging/wizard/wizard-large-2.bmp index c047705..93a58d9 100644 Binary files a/packaging/wizard/wizard-large-2.bmp and b/packaging/wizard/wizard-large-2.bmp differ diff --git a/packaging/wizard/wizard-large.bmp b/packaging/wizard/wizard-large.bmp index db0dc65..56895a8 100644 Binary files a/packaging/wizard/wizard-large.bmp and b/packaging/wizard/wizard-large.bmp differ diff --git a/requirements-build-windows.txt b/requirements/build-windows.txt similarity index 97% rename from requirements-build-windows.txt rename to requirements/build-windows.txt index 4c90c06..4151092 100644 --- a/requirements-build-windows.txt +++ b/requirements/build-windows.txt @@ -1,7 +1,7 @@ # MLC Player hosted unsigned-build wheels # # Exact CPython 3.13 / Windows x86-64 wheel set selected from the same pins as -# requirements-lock.txt on 23 August 2026. The hosted workflow uses +# requirements/lock.txt on 23 August 2026. The hosted workflow uses # --require-hashes, --only-binary=:all: and --no-deps, so an unlisted archive # or a byte-different replacement fails closed. altgraph==0.17.5 \ diff --git a/requirements-ci-docs.txt b/requirements/ci-docs.txt similarity index 72% rename from requirements-ci-docs.txt rename to requirements/ci-docs.txt index f786c7c..8a03e90 100644 --- a/requirements-ci-docs.txt +++ b/requirements/ci-docs.txt @@ -1,5 +1,5 @@ # Minimal deterministic environment for documentation-only hosted CI. -# Every pin must remain an exact subset of requirements-lock.txt. +# Every pin must remain an exact subset of requirements/lock.txt. colorama==0.4.6 iniconfig==2.3.0 packaging==26.0 diff --git a/requirements-dev.txt b/requirements/dev.txt similarity index 84% rename from requirements-dev.txt rename to requirements/dev.txt index 3a4b5bd..8938f54 100644 --- a/requirements-dev.txt +++ b/requirements/dev.txt @@ -1,7 +1,7 @@ # MLC Player - developer and contributor tooling -# Install: pip install -r requirements.txt -r requirements-dev.txt +# Install: pip install -r requirements/runtime.txt -r requirements/dev.txt # -# These are NOT needed to run the player. `requirements.txt` alone is +# These are NOT needed to run the player. `requirements/runtime.txt` alone is # enough for that, and `scripts/bootstrap.ps1` installs only that file so # someone who just wants to watch a video does not pull a second Qt # binding. @@ -20,7 +20,7 @@ Pillow==12.3.0 # MEASURED: PyQt6 ships `pylupdate6` but NOT `lrelease`, so this really is # a separate dependency rather than an oversight. The product itself runs # on PyQt6; PySide6 is build tooling only and is excluded from the -# packaged application (see MLCPlayer.spec). +# packaged application (see packaging/MLCPlayer.spec). # # NOTE: do not run `pyside6-lupdate` on our .ts files. It only recognises # `QCoreApplication.translate(...)` and cannot see the `app/i18n.tr()` diff --git a/requirements-lock.txt b/requirements/lock.txt similarity index 94% rename from requirements-lock.txt rename to requirements/lock.txt index 03d6234..32a7ba0 100644 --- a/requirements-lock.txt +++ b/requirements/lock.txt @@ -3,7 +3,7 @@ # Verified with CPython 3.14 on Windows. Package metadata for every direct # dependency supports the project's Python 3.12 <= version < 3.15 contract. # CI and release builds install this complete graph; source users install the -# smaller requirements.txt file. +# smaller requirements/runtime.txt file. altgraph==0.17.5 cffi==2.1.1 colorama==0.4.6 diff --git a/requirements.txt b/requirements/runtime.txt similarity index 79% rename from requirements.txt rename to requirements/runtime.txt index a833d76..6b0413f 100644 --- a/requirements.txt +++ b/requirements/runtime.txt @@ -1,5 +1,5 @@ # MLC Player - bağımlılıklar -# Kurulum: pip install -r requirements.txt +# Kurulum: pip install -r requirements/runtime.txt PyQt6==6.10.2 python-mpv==1.0.8 # Guncelleme imzasi (Ed25519) dogrulamasi icin. Kendi kripto kodumuzu @@ -8,5 +8,5 @@ cryptography==50.0.0 # NOT: Python `yt-dlp` paketi ARTIK ürün bağımlılığı DEĞİLDİR. # mpv site çıkarımını HARİCİ bir çalıştırılabilir üzerinden yapar; MLC Player # resmî `bin/yt-dlp.exe` ve `bin/deno.exe` dosyalarını kendi kurulumunda -# taşır (bkz. bin/RUNTIME_MANIFEST.txt ve docs/PACKAGING_PLAN.md). +# taşır (bkz. bin/RUNTIME_MANIFEST.txt ve docs/release/PACKAGING_PLAN.md). # Kullanıcıdan `pip install yt-dlp` İSTENMEZ. diff --git a/scripts/bootstrap.ps1 b/scripts/bootstrap.ps1 index 878c662..2cec837 100644 --- a/scripts/bootstrap.ps1 +++ b/scripts/bootstrap.ps1 @@ -19,8 +19,8 @@ $MaximumPythonExclusive = [Version]"3.15" $Python = $null $ProjectRoot = Split-Path -Parent $PSScriptRoot -$MainFile = Join-Path $ProjectRoot "main.py" -$Requirements = Join-Path $ProjectRoot "requirements.txt" +$MainFile = Join-Path $ProjectRoot "app/main.py" +$Requirements = Join-Path $ProjectRoot "requirements/runtime.txt" $DependencyVerifier = Join-Path $ProjectRoot "packaging\verify_dependencies.py" # The runtime binaries are carried in the repository; pip does not @@ -153,9 +153,9 @@ if (Test-ModulesInstalled) { Write-Host "[OK] The required packages are already installed." } else { - Write-Host "[INFO] Installing the missing packages (requirements.txt)..." + Write-Host "[INFO] Installing the missing packages (requirements/runtime.txt)..." if (-not (Test-Path $Requirements)) { - throw "requirements.txt not found: $Requirements" + throw "requirements/runtime.txt not found: $Requirements" } & $Python.Executable @($Python.PrefixArgs) -m pip install --disable-pip-version-check -r $Requirements if ($LASTEXITCODE -ne 0) { diff --git a/scripts/verify_video_format_media.py b/scripts/verify_video_format_media.py index a57eb4f..3b72c74 100644 --- a/scripts/verify_video_format_media.py +++ b/scripts/verify_video_format_media.py @@ -35,7 +35,7 @@ class ByHandleFileInformation(ctypes.Structure): ROOT = Path(__file__).resolve().parent.parent -CANONICAL_MANIFEST = (ROOT / "docs" / "VIDEO_FORMAT_MEDIA_MANIFEST.json").resolve() +CANONICAL_MANIFEST = (ROOT / "docs" / "video-format" / "VIDEO_FORMAT_MEDIA_MANIFEST.json").resolve() FFPROBE_ARGV = ( "-v", "error", "-show_format", "-show_streams", "-show_frames", "-select_streams", "v:0", "-of", "json", "$MEDIA", diff --git a/second_launch.py b/second_launch.py deleted file mode 100644 index 8b70780..0000000 --- a/second_launch.py +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-FileCopyrightText: 2026 MLC Player contributors -# SPDX-License-Identifier: GPL-3.0-only - -import os, sys -os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") -sys.path.insert(0, 'C:\\Users\\Universe\\Desktop\\Programlar TEST\\2026 YENİLER\\MLC Player') -from PyQt6.QtWidgets import QApplication -from app.single_instance import SingleInstanceGuard -app = QApplication([]) -guard = SingleInstanceGuard('MLCProbe-b84de3fadbaf4a67be999f9ee1759940') -print("PRIMARY" if guard.acquire('I:\\film.mkv') else "SECONDARY") diff --git a/tests/cover_art_native_child.py b/tests/cover_art_native_child.py index b0ac24e..50f8c93 100644 --- a/tests/cover_art_native_child.py +++ b/tests/cover_art_native_child.py @@ -365,5 +365,5 @@ def main(): sys.stdout.flush() sys.stderr.flush() # libmpv yukleyen child'lar normal finalizasyona GIRMEZ - # (bkz. main.py ve test_child_shutdown_contract_regressions). + # (bkz. app/main.py ve test_child_shutdown_contract_regressions). os._exit(code) diff --git a/tests/default_ui_child.py b/tests/default_ui_child.py index 926914b..9ae4b53 100644 --- a/tests/default_ui_child.py +++ b/tests/default_ui_child.py @@ -97,6 +97,13 @@ def main(): report["has_fade_animation"] = frame.overlay_fade is not None # Gerçek seek: timeline tıklaması slider ve mpv time_pos değiştirmeli + # Ölçüm sırasında 100 ms UI zamanlayıcısı durdurulur. Hosted CI'daki + # mpv taklidi konum bildirmez (`player.position` 0 kalır); zamanlayıcı + # tıklamadan hemen sonraki processEvents() içinde tetiklenirse + # `update_overlay_state` çubuğu 0'a geri yazıyordu (PR #75 ilk koşum, + # 3 Ekim 2026; seek gerçekleşmiş, time_pos=300). Bu test periyodik + # eşitlemeyi değil tıklamanın seek yaptığını ölçer. + player.timer.stop() player.duration = 600.0 timeline = frame.overlay_timeline timeline.setValue(0) @@ -157,7 +164,7 @@ def main(): if __name__ == "__main__": - # ÜRÜNLE AYNI KAPANIŞ. `main.py` bunu şöyle belgeliyor: "mpv DLL'leri bu + # ÜRÜNLE AYNI KAPANIŞ. `app/main.py` bunu şöyle belgeliyor: "mpv DLL'leri bu # yapıda interpreter kapanışında takılıyor (thread-safe olmayan DLL # yıkımı)" ve bu yüzden `os._exit(ret)` kullanıyor. Bu child libmpv # yüklediği hâlde normal finalizasyona giriyordu; ölçüm (JSON) çoktan diff --git a/tests/layout_child.py b/tests/layout_child.py index 0139a80..e9728c6 100644 --- a/tests/layout_child.py +++ b/tests/layout_child.py @@ -147,7 +147,7 @@ def main(): if __name__ == "__main__": - # ÜRÜNLE AYNI KAPANIŞ (`main.py` -> `os._exit(ret)`): libmpv yüklendikten + # ÜRÜNLE AYNI KAPANIŞ (`app/main.py` -> `os._exit(ret)`): libmpv yüklendikten # sonra normal Python finalizasyonu takılabiliyor. Ölçüm JSON'u bu # noktadan ÖNCE basılır. _code = main() diff --git a/tests/main_entry_child.py b/tests/main_entry_child.py index a4b5974..b34945c 100644 --- a/tests/main_entry_child.py +++ b/tests/main_entry_child.py @@ -1,9 +1,9 @@ # SPDX-FileCopyrightText: 2026 MLC Player contributors # SPDX-License-Identifier: GPL-3.0-only -"""Gerçek `python main.py` giriş noktasını taklit eden ölçüm child'ı. +"""Gerçek `python app/main.py` giriş noktasını taklit eden ölçüm child'ı. -main.py'nin bağımlılık kurulum adımını (check_dependencies) aynen çalıştırır, -ardından MPVPlayer'ı main.py ile birebir aynı şekilde oluşturur ve varsayılan +app/main.py'nin bağımlılık kurulum adımını (check_dependencies) aynen çalıştırır, +ardından MPVPlayer'ı app/main.py ile birebir aynı şekilde oluşturur ve varsayılan arayüz durumunu raporlar. Hiçbir UI ortam değişkeni ayarlamaz. """ import json @@ -17,7 +17,7 @@ from PyQt6.QtCore import QSettings, Qt from PyQt6.QtWidgets import QApplication -import main as main_module +from app import main as main_module def run(): @@ -64,7 +64,7 @@ def run(): if __name__ == "__main__": - # ÜRÜNLE AYNI KAPANIŞ (`main.py` -> `os._exit(ret)`): libmpv yüklendikten + # ÜRÜNLE AYNI KAPANIŞ (`app/main.py` -> `os._exit(ret)`): libmpv yüklendikten # sonra normal Python finalizasyonu thread-safe olmayan DLL yıkımında # takılabiliyor. Ölçüm JSON'u bu noktadan ÖNCE basıldığı için veri # kaybı olmaz; kapanış artık asılı kalmaz. diff --git a/tests/media_track_probe_child.py b/tests/media_track_probe_child.py index 8c8a814..ade81ed 100644 --- a/tests/media_track_probe_child.py +++ b/tests/media_track_probe_child.py @@ -20,7 +20,7 @@ ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, ROOT) -# libmpv, urunun kullandigi `bin/` dizininden yuklenir (bkz. main.py). +# libmpv, urunun kullandigi `bin/` dizininden yuklenir (bkz. app/main.py). _BIN = os.path.join(ROOT, "bin") os.environ["PATH"] = _BIN + os.pathsep + os.environ.get("PATH", "") try: diff --git a/tests/native_overlay_input_zorder_child.py b/tests/native_overlay_input_zorder_child.py index e543692..d8df014 100644 --- a/tests/native_overlay_input_zorder_child.py +++ b/tests/native_overlay_input_zorder_child.py @@ -14,7 +14,7 @@ Urun metodlari kullanici hareketi taklidi icin CAGRILMAZ; yalniz sonuc dogrulamasi icin durum okunur. Kapanis yalnizca `PLAYER.close()` ile -baslar; `os._exit` main.py politikasiyla en sonda kullanilir. +baslar; `os._exit` app/main.py politikasiyla en sonda kullanilir. MLC_NATIVE_SMOKE=1 MLC_NATIVE_TEST_VIDEO= \ python tests/native_overlay_input_zorder_child.py diff --git a/tests/native_overlay_smoke_child.py b/tests/native_overlay_smoke_child.py index 1754c9e..7495ef6 100644 --- a/tests/native_overlay_smoke_child.py +++ b/tests/native_overlay_smoke_child.py @@ -585,7 +585,7 @@ def step_done(): # exit_code 90: harness tarafında yakalanmamış Python istisnası (ürün crash'i değil). # Windows'ta libmpv'nin normal Python finalizasyonu thread-safe değildir. -# Marker'ları kaybetmeden ürünün main.py kapanış sözleşmesiyle aynı yolu kullan. +# Marker'ları kaybetmeden ürünün app/main.py kapanış sözleşmesiyle aynı yolu kullan. sys.stdout.flush() sys.stderr.flush() os._exit(exit_code) diff --git a/tests/native_physical_acceptance_child.py b/tests/native_physical_acceptance_child.py index 313d870..c1d30be 100644 --- a/tests/native_physical_acceptance_child.py +++ b/tests/native_physical_acceptance_child.py @@ -6,7 +6,7 @@ (`SetCursorPos` + `SendInput`). Urun metodlari kullanici hareketi taklidi icin CAGRILMAZ; yalnizca sonuc DOGRULAMASI icin urun durumu okunur. -Urun, main.py ile ayni sekilde (`MPVPlayer()`) bu surecte olusturulur; boylece +Urun, app/main.py ile ayni sekilde (`MPVPlayer()`) bu surecte olusturulur; boylece gercek pencereye fiziksel girdi gonderilirken ic durum da olculebilir. Her calistirma TEK grup kosar ve MARK_DONE ile biter. Bir grup kilitlenirse @@ -3128,7 +3128,7 @@ def run_group_body(args, original_cursor, original_foreground): return 0 -# URUN CIKIS POLITIKASI: `main.py` gibi, butun sonuc satirlari ve marker'lar +# URUN CIKIS POLITIKASI: `app/main.py` gibi, butun sonuc satirlari ve marker'lar # flush edildikten SONRA `os._exit`. Python yorumlayici finalizasyonu bu # kabulun parcasi degildir (Qt + libmpv + `audio-device-list` icin ayri bir # tani riskidir). Urun kodunda os._exit KULLANILMAZ. diff --git a/tests/native_player_shutdown_child.py b/tests/native_player_shutdown_child.py index c4e2752..d96543d 100644 --- a/tests/native_player_shutdown_child.py +++ b/tests/native_player_shutdown_child.py @@ -23,7 +23,7 @@ MARK_MAIN_RETURNED Kabul: stop=1, terminate=1, stop marker'i terminate'ten ONCE, eksik marker -yok, exit code 0. Child, uygulamanin `main.py` giris noktasi gibi Qt event +yok, exit code 0. Child, uygulamanin `app/main.py` giris noktasi gibi Qt event loop'u dondukten ve urun kapanis sozlesmesini dogruladiktan sonra `os._exit(ret)` kullanir. Python yorumlayici finalizasyonu bu kabulun parcasi degildir; Qt + libmpv + `audio-device-list` icin ayri bir tani riskidir. diff --git a/tests/opensubtitles_live_contract_child.py b/tests/opensubtitles_live_contract_child.py index 9523a7d..d83bc0d 100644 --- a/tests/opensubtitles_live_contract_child.py +++ b/tests/opensubtitles_live_contract_child.py @@ -12,7 +12,7 @@ $env:MLC_OPENSUBTITLES_LIVE_DOWNLOAD='1' Degisken yoksa "SKIPPED: OPT_IN_REQUIRED" yazip 0 doner. Normal pytest ve -normal `python main.py` bu harness'i ASLA calistirmaz. +normal `python app/main.py` bu harness'i ASLA calistirmaz. GUVENLIK -------- diff --git a/tests/resize_stall_measure_child.py b/tests/resize_stall_measure_child.py index 9753141..88d7a00 100644 --- a/tests/resize_stall_measure_child.py +++ b/tests/resize_stall_measure_child.py @@ -196,5 +196,5 @@ def timed(): sys.stdout.flush() sys.stderr.flush() # libmpv yükleyen child'lar normal finalizasyona GİRMEZ - # (bkz. main.py:130 ve test_child_shutdown_contract_regressions). + # (bkz. app/main.py:130 ve test_child_shutdown_contract_regressions). os._exit(code) diff --git a/tests/run_sdr_acceptance.py b/tests/run_sdr_acceptance.py index b627a99..902f246 100644 --- a/tests/run_sdr_acceptance.py +++ b/tests/run_sdr_acceptance.py @@ -14,7 +14,7 @@ ROOT = os.path.dirname(TESTS_DIR) CHILD = os.path.join(TESTS_DIR, "native_sdr_probe_child.py") VERIFIER = os.path.join(ROOT, "scripts", "verify_video_format_media.py") -MANIFEST = os.path.join(ROOT, "docs", "VIDEO_FORMAT_MEDIA_MANIFEST.json") +MANIFEST = os.path.join(ROOT, "docs", "video-format", "VIDEO_FORMAT_MEDIA_MANIFEST.json") sys.path.insert(0, TESTS_DIR) from hdr_probe_contract import parse_dxdiag_bytes # noqa: E402 diff --git a/tests/smoke_child.py b/tests/smoke_child.py index 2ee9c64..cb6dec2 100644 --- a/tests/smoke_child.py +++ b/tests/smoke_child.py @@ -29,7 +29,7 @@ if player.mpv_player is not None: raise SystemExit("MPV temiz kapanmadı") -# ÜRÜNLE AYNI KAPANIŞ (`main.py` -> `os._exit(ret)`): libmpv yüklendikten +# ÜRÜNLE AYNI KAPANIŞ (`app/main.py` -> `os._exit(ret)`): libmpv yüklendikten # sonra normal Python finalizasyonu thread-safe olmayan DLL yıkımında # takılabiliyor. Başarısızlık yolu YUKARIDA ve DEĞİŞMEDEN kalır; yalnız # başarı yolu asılı kalmadan kapanır. diff --git a/tests/subtitle_center_product_smoke_child.py b/tests/subtitle_center_product_smoke_child.py index cecb4de..85de85c 100644 --- a/tests/subtitle_center_product_smoke_child.py +++ b/tests/subtitle_center_product_smoke_child.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-only """Opt-in GERCEK urun kabugu Altyazi Merkezi smoke'u. -Gercek `MPVPlayer` penceresi (main.py ile ayni kabuk), gercek menu eylemi, +Gercek `MPVPlayer` penceresi (app/main.py ile ayni kabuk), gercek menu eylemi, gercek libmpv. Yalnizca OpenSubtitles ISTEMCISI sahtedir. GUVENLIK KURALI diff --git a/tests/test_about_licence_regressions.py b/tests/test_about_licence_regressions.py index 9674afa..54905e0 100644 --- a/tests/test_about_licence_regressions.py +++ b/tests/test_about_licence_regressions.py @@ -83,7 +83,7 @@ def test_the_third_party_components_are_mentioned(about_text): def test_the_copyright_year_matches_the_readme(about_text): """İki yerde iki farklı yıl yazmak dikkatsizlik izlenimi verir.""" - readme = (ROOT / "README.md").read_text(encoding="utf-8") + readme = (ROOT / ".github" / "README.md").read_text(encoding="utf-8") match = re.search(r"Copyright \(C\) (\d{4})", readme) assert match, "README'de telif satırı yok" assert match.group(1) in about_text["text"] diff --git a/tests/test_app_icon_regressions.py b/tests/test_app_icon_regressions.py index ad57f99..996f657 100644 --- a/tests/test_app_icon_regressions.py +++ b/tests/test_app_icon_regressions.py @@ -36,7 +36,7 @@ # gorunuyordu); plakali sanat kurulum sihirbazinda kalir. APP_ICO = os.path.join(ASSETS, "mlc-player-icon-transparent.ico") MANIFEST = os.path.join(ASSETS, "ICON_MANIFEST.txt") -SPEC = os.path.join(PROJECT, "MLCPlayer.spec") +SPEC = os.path.join(PROJECT, "packaging/MLCPlayer.spec") REQUIRED_SIZES = (16, 20, 24, 32, 40, 48, 64, 128, 256) SOURCE_SIZE = (1254, 1254) @@ -193,7 +193,7 @@ def test_the_app_user_model_id_is_stable(): def test_main_sets_the_icon_before_any_window_is_built(): - source = read(os.path.join(PROJECT, "main.py")) + source = read(os.path.join(PROJECT, "app/main.py")) identity = source.index("install_application_identity") application = source.index("QApplication(sys.argv)") @@ -218,7 +218,7 @@ def test_the_model_id_is_windows_only_and_never_writes_the_registry(): # ===================================================================== def test_no_production_code_uses_a_standard_or_default_icon(): - for relative in ("app/player.py", "app/title_bar.py", "main.py", + for relative in ("app/player.py", "app/title_bar.py", "app/main.py", "app/media_info_dialog.py", "app/app_icon.py"): source = read(os.path.join(PROJECT, relative)) for forbidden in ("SP_DriveDVDIcon", "SP_ComputerIcon", @@ -427,7 +427,7 @@ def test_the_spec_carries_the_exe_icon(): spec = read(SPEC) # EXE ikonu ŞEFFAF sürümdür; Windows kısayolunda görünen budur. - assert "icon='assets/mlc-player-icon-transparent.ico'" in spec or \ + assert "icon=_from_root('assets/mlc-player-icon-transparent.ico')" in spec or \ 'icon="assets/mlc-player-icon-transparent.ico"' in spec @@ -453,7 +453,7 @@ def test_the_existing_packaging_contract_is_untouched(): def test_the_plan_records_the_setup_icon_decision(): - plan = read(os.path.join(PROJECT, "docs", "PACKAGING_PLAN.md")) + plan = read(os.path.join(PROJECT, "docs", "release", "PACKAGING_PLAN.md")) assert "SetupIconFile" in plan assert "UninstallDisplayIcon" in plan diff --git a/tests/test_app_icon_transparency_regressions.py b/tests/test_app_icon_transparency_regressions.py index 5c7153f..cd25573 100644 --- a/tests/test_app_icon_transparency_regressions.py +++ b/tests/test_app_icon_transparency_regressions.py @@ -1,16 +1,15 @@ # SPDX-FileCopyrightText: 2026 MLC Player contributors # SPDX-License-Identifier: GPL-3.0-only -"""Masaüstü kısayolunda ikonun arkasında koyu plaka OLMAZ. +"""Onaylı yuvarlak ikon bütün Windows yüzeylerinde aynı kimliği kullanır. -KULLANICI BİLDİRİMİ: kısayolda ikon koyu bir kare içinde duruyordu; VLC'nin -konisi gibi yalnız işaretin görünmesi, arkasının boş kalması istendi. +KULLANICI BİLDİRİMİ: kurulum başlığı yuvarlak ikona geçtiği hâlde sol büyük +panelde altıgen işaret kalmıştı. İki kurulum yüzeyi de yuvarlak olmalıdır. Üretim `packaging/make_app_icon.py` ile yapılır: kaynak sanattaki plaka rengi (ölçülen `(20, 25, 32)`) şeffaflaştırılır, kenar pikselleri kapsama alfası alır (basit eşik kenarlarda koyu hale bırakıyordu). -Kurulum sihirbazının görselleri BİLEREK eski sanatı kullanır; koyu panel -üzerinde plaka sorun değildir. +Kurulum sihirbazının büyük görselleri aynı yuvarlak kaynak sanattan üretilir. """ import importlib.util @@ -20,9 +19,15 @@ from PIL import Image ROOT = Path(__file__).resolve().parent.parent +SOURCE = ROOT / "assets" / "mlc-player-icon.png" +STANDARD = ROOT / "assets" / "mlc-player-icon.ico" TRANSPARENT = ROOT / "assets" / "mlc-player-icon-transparent.ico" TRANSPARENT_PNG = ROOT / "assets" / "mlc-player-icon-transparent.png" PLATE = (20, 25, 32) +REQUIRED_ICO_SIZES = { + (16, 16), (20, 20), (24, 24), (32, 32), (40, 40), + (48, 48), (64, 64), (128, 128), (256, 256), +} def _generator(): @@ -72,6 +77,37 @@ def test_the_mark_itself_survived(): assert 0.15 < ratio < 0.60, f"işaret oranı beklenmedik: {ratio:.2f}" +@pytest.mark.parametrize("path", [SOURCE, TRANSPARENT_PNG]) +def test_the_approved_round_mark_is_the_canonical_icon(path): + image = Image.open(path).convert("RGBA") + width, height = image.size + + # Yuvarlak işaret orta-sol ve üst-orta noktalarda turuncudur; altıgen + # varyant orta-sol noktada boş kalıyordu. + assert image.getpixel((width // 10, height // 2)) == (255, 90, 31, 255) + assert image.getpixel((width // 2, height // 10)) == (255, 90, 31, 255) + assert image.getpixel((width // 2, height // 2))[:3] == (255, 255, 255) + + +@pytest.mark.parametrize("path", [STANDARD, TRANSPARENT]) +def test_every_ico_frame_uses_the_approved_round_mark(path): + icon = Image.open(path) + sizes = icon.info.get("sizes", set()) + assert sizes == REQUIRED_ICO_SIZES + + for size in sizes: + icon.size = size + frame = icon.convert("RGBA") + width, height = frame.size + for point in ((width // 10, height // 2), (width // 2, height // 10)): + red, green, blue, alpha = frame.getpixel(point) + assert red > 240 and 50 <= green <= 150 and blue < 100 and alpha > 190, ( + path.name, size, point, (red, green, blue, alpha) + ) + red, green, blue, alpha = frame.getpixel((width // 2, height // 2)) + assert min(red, green, blue) > 230 and alpha > 240, (path.name, size) + + def test_the_icon_carries_small_sizes(): """Windows kısayolu 16-32 piksel boyutlarını kullanır.""" image = Image.open(TRANSPARENT) @@ -85,17 +121,46 @@ def test_the_application_loads_the_transparent_icon(): def test_the_packaged_exe_uses_the_transparent_icon(): - spec = (ROOT / "MLCPlayer.spec").read_text(encoding="utf-8") - assert "icon='assets/mlc-player-icon-transparent.ico'" in spec + spec = (ROOT / "packaging/MLCPlayer.spec").read_text(encoding="utf-8") + assert "icon=_from_root('assets/mlc-player-icon-transparent.ico')" in spec assert "('assets/mlc-player-icon-transparent.ico', 'assets')" in spec, ( "şeffaf ikon pakete kopyalanmıyor; kurulu sürümde ikon kaybolur") -def test_the_installer_keeps_the_original_artwork(): - """KULLANICI KARARI: sihirbaz görselleri değişmez.""" +def test_both_installers_use_the_round_icon_on_their_title_bars(): iss = (ROOT / "packaging" / "MLCPlayer.iss").read_text(encoding="utf-8-sig") assert "SetupIconFile=..\\assets\\mlc-player-icon.ico" in iss + addon_iss = (ROOT / "packaging" / "MLCPlayer_InternetVideo.iss").read_text( + encoding="utf-8-sig" + ) + assert "SetupIconFile=..\\assets\\mlc-player-icon.ico" in addon_iss + + +def test_the_left_wizard_panels_are_generated_from_the_round_icon(): + path = ROOT / "packaging" / "make_wizard_images.py" + spec = importlib.util.spec_from_file_location("mlc_wizard_images", path) + generator = importlib.util.module_from_spec(spec) + spec.loader.exec_module(generator) + + for index, (width, height) in enumerate(generator.LARGE_SIZES): + suffix = "" if index == 0 else f"-{index}" + actual = Image.open( + ROOT / "packaging" / "wizard" / f"wizard-large{suffix}.bmp" + ).convert("RGB") + expected = generator.large_image(width, height) + assert actual.tobytes() == expected.tobytes(), suffix + + logo_size = int(width * 0.62) + logo_x = (width - logo_size) // 2 + logo_y = int(height * 0.30) + assert actual.getpixel( + (logo_x + logo_size // 10, logo_y + logo_size // 2) + ) == (255, 90, 31), suffix + assert actual.getpixel( + (logo_x + logo_size // 2, logo_y + logo_size // 10) + ) == (255, 90, 31), suffix + def test_the_generator_is_reproducible(): """Kaynak sanattan aynı sonucu üretebilmeli.""" diff --git a/tests/test_change_workflow_regressions.py b/tests/test_change_workflow_regressions.py index 133c36e..fa2503d 100644 --- a/tests/test_change_workflow_regressions.py +++ b/tests/test_change_workflow_regressions.py @@ -6,7 +6,7 @@ ROOT = Path(__file__).resolve().parent.parent -WORKFLOW = ROOT / "docs" / "CHANGE_WORKFLOW.md" +WORKFLOW = ROOT / "docs" / "process" / "CHANGE_WORKFLOW.md" def workflow_text(): @@ -65,7 +65,7 @@ def test_change_workflow_uses_one_automatic_ci_run_per_change(): def test_change_workflow_requires_independent_dual_filter_without_test_repeats(): text = workflow_text() - agents = (ROOT / "AGENTS.md").read_text(encoding="utf-8") + agents = (ROOT / "docs/process/AGENTS.md").read_text(encoding="utf-8") for contract in (text, agents): normalized = " ".join(contract.split()).casefold() @@ -85,16 +85,16 @@ def test_change_workflow_requires_independent_dual_filter_without_test_repeats() def test_agent_entrypoint_links_to_the_change_workflow(): - agents = (ROOT / "AGENTS.md").read_text(encoding="utf-8") - assert "docs/CHANGE_WORKFLOW.md" in agents + agents = (ROOT / "docs/process/AGENTS.md").read_text(encoding="utf-8") + assert "docs/process/CHANGE_WORKFLOW.md" in agents assert "PR oluşturma, PR birleştirme" in agents assert "Force-push ve GitHub protection bypass yapılmaz" in agents def test_release_process_uses_the_pr_merge_commit_as_build_identity(): - release = (ROOT / "docs" / "RELEASE_PROCESS.md").read_text( + release = (ROOT / "docs" / "release" / "RELEASE_PROCESS.md").read_text( encoding="utf-8") - assert "docs/CHANGE_WORKFLOW.md" in release + assert "docs/process/CHANGE_WORKFLOW.md" in release assert "**merge commit**" in release assert "git rev-parse origin/master" in release assert "master'a ikinci kez push yapılmaz" in release @@ -104,5 +104,5 @@ def test_release_process_uses_the_pr_merge_commit_as_build_identity(): def test_public_readmes_link_to_the_change_workflow(): for name in ("README.md", "README.tr.md"): - text = (ROOT / name).read_text(encoding="utf-8") - assert "docs/CHANGE_WORKFLOW.md" in text + text = (ROOT / ".github" / name).read_text(encoding="utf-8") + assert "docs/process/CHANGE_WORKFLOW.md" in text diff --git a/tests/test_child_shutdown_contract_regressions.py b/tests/test_child_shutdown_contract_regressions.py index 748932b..01a899a 100644 --- a/tests/test_child_shutdown_contract_regressions.py +++ b/tests/test_child_shutdown_contract_regressions.py @@ -16,7 +16,7 @@ KOK NEDEN (urunun kendi belgesi) -------------------------------- -`main.py` satir 130: +`app/main.py` satir 130: # NOT: mpv DLL'leri bu yapida interpreter kapanisinda TAKILIYOR # (thread-safe olmayan DLL yikimi). Normal Python finalizasyonu @@ -110,7 +110,7 @@ def test_a_libmpv_child_never_relies_on_normal_interpreter_finalisation(name): assert "os._exit(" in source, ( f"{name} libmpv yukluyor ama `os._exit()` ile kapanmiyor; " "normal finalizasyon thread-safe olmayan DLL yikiminda takilabilir " - "(bkz. main.py satir 130).") + "(bkz. app/main.py satir 130).") @pytest.mark.parametrize("name", default_suite_libmpv_children()) @@ -119,7 +119,7 @@ def test_a_libmpv_child_flushes_before_the_hard_exit(name): if name in DELIBERATE_FINALISERS: pytest.skip("bu child finalizasyon tehlikesini BILEREK olcuyor") source = read(os.path.join(TESTS, name)) - # NOT: ilk gecis ARAMAZ. Gerekce dizeleri `main.py -> os._exit(ret)` + # NOT: ilk gecis ARAMAZ. Gerekce dizeleri `app/main.py -> os._exit(ret)` # seklinde yorum icinde de gecebiliyor; olculmesi gereken GERCEK cagri # en sondakidir. exit_at = source.rindex("os._exit(") @@ -144,6 +144,6 @@ def test_the_deliberate_finaliser_is_still_deliberate(): def test_the_product_still_documents_and_uses_the_hard_exit(): """Sozlesmenin kaynagi urundur; urun degisirse bu test uyarir.""" - main_py = read(os.path.join(os.path.dirname(TESTS), "main.py")) + main_py = read(os.path.join(os.path.dirname(TESTS), "app/main.py")) assert "os._exit(ret)" in main_py assert "takılıyor" in main_py or "takiliyor" in main_py diff --git a/tests/test_ci_workflow_regressions.py b/tests/test_ci_workflow_regressions.py index 0a3f30e..2bc4719 100644 --- a/tests/test_ci_workflow_regressions.py +++ b/tests/test_ci_workflow_regressions.py @@ -7,7 +7,7 @@ ROOT = Path(__file__).resolve().parent.parent WORKFLOW = ROOT / ".github" / "workflows" / "ci.yml" -DOCS_REQUIREMENTS = ROOT / "requirements-ci-docs.txt" +DOCS_REQUIREMENTS = ROOT / "requirements/ci-docs.txt" def workflow_text(): @@ -38,13 +38,13 @@ def test_ci_installs_and_verifies_the_locked_environment(): assert "actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803" in text assert "actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1" in text assert "python-version: '3.13.15'" in text - assert "pip install -r requirements-lock.txt" in text - assert "verify_dependencies.py requirements-lock.txt" in text + assert "pip install -r requirements/lock.txt" in text + assert "verify_dependencies.py requirements/lock.txt" in text def test_ci_runs_static_translation_and_default_pytest_gates(): text = workflow_text() - assert "python -m compileall -q main.py app tests packaging" in text + assert "python -m compileall -q app tests packaging" in text assert "python packaging/extract_translations.py --check" in text assert "git diff --check" in text assert "python -m pytest -q tests" in text @@ -63,7 +63,7 @@ def test_ci_classifies_documentation_only_changes_before_running_jobs(): def test_documentation_only_ci_uses_the_small_deterministic_gate(): text = workflow_text() - assert "python -m pip install -r requirements-ci-docs.txt" in text + assert "python -m pip install -r requirements/ci-docs.txt" in text for path in ( "tests/test_continuity_regressions.py", "tests/test_change_workflow_regressions.py", @@ -71,7 +71,9 @@ def test_documentation_only_ci_uses_the_small_deterministic_gate(): "tests/test_readme_user_guidance_regressions.py", "tests/test_release_documentation_regressions.py", "tests/test_signpath_readiness_regressions.py", - "tests/test_video_format_acceptance_plan_regressions.py"): + "tests/test_video_format_acceptance_plan_regressions.py", + "tests/test_docs_index_regressions.py", + "tests/test_repository_privacy_regressions.py"): assert path in text assert "python -m pytest -q --noconftest" in text assert "python -m json.tool docs/VERIFICATION_LEDGER.json" in text @@ -99,7 +101,7 @@ def test_documentation_ci_requirements_are_pinned_and_match_the_full_lock(): } locked = { line.strip().lower() - for line in (ROOT / "requirements-lock.txt") + for line in (ROOT / "requirements/lock.txt") .read_text(encoding="utf-8") .splitlines() if "==" in line diff --git a/tests/test_classic_ui_removal_regressions.py b/tests/test_classic_ui_removal_regressions.py index ec43d45..e6727df 100644 --- a/tests/test_classic_ui_removal_regressions.py +++ b/tests/test_classic_ui_removal_regressions.py @@ -133,7 +133,7 @@ def test_classic_control_panel_is_never_visible(request, fixture_name): @pytest.mark.skipif(HOSTED_CI, reason="main entry acceptance requires mpv-2.dll") def test_main_entry_is_cinematic_even_with_legacy_env(main_entry_with_legacy): - """`python main.py` legacy env ile bile modern kabuğu kullanmalı.""" + """`python app/main.py` legacy env ile bile modern kabuğu kullanmalı.""" report = main_entry_with_legacy assert report["cinematic_ui_enabled"] is True diff --git a/tests/test_continuity_regressions.py b/tests/test_continuity_regressions.py index d5e6166..b6ad7a9 100644 --- a/tests/test_continuity_regressions.py +++ b/tests/test_continuity_regressions.py @@ -12,14 +12,14 @@ ROOT = Path(__file__).resolve().parent.parent -AGENTS = ROOT / "AGENTS.md" -CLAUDE = ROOT / "CLAUDE.md" +AGENTS = ROOT / "docs/process/AGENTS.md" +CLAUDE = ROOT / ".claude/CLAUDE.md" CONTINUITY = ROOT / "docs" / "CONTINUITY.md" -CONTINUITY_HISTORY = ROOT / "docs" / "CONTINUITY_HISTORY.md" +CONTINUITY_HISTORY = ROOT / "docs" / "history" / "CONTINUITY_HISTORY.md" LEDGER = ROOT / "docs" / "VERIFICATION_LEDGER.json" -PROJECT_HISTORY = ROOT / "docs" / "PROJECT_STATUS.md" -ROADMAP_HISTORY = ROOT / "docs" / "ROADMAP.md" -ENGINEERING_HISTORY = ROOT / "docs" / "ENGINEERING_AUDIT.md" +PROJECT_HISTORY = ROOT / "docs" / "history" / "PROJECT_STATUS.md" +ROADMAP_HISTORY = ROOT / "docs" / "history" / "ROADMAP.md" +ENGINEERING_HISTORY = ROOT / "docs" / "history" / "ENGINEERING_AUDIT.md" SESSION_HOOK = ROOT / ".claude" / "hooks" / "session_start.py" PROOF_LAYERS = { @@ -56,7 +56,7 @@ def test_every_agent_has_one_current_starting_point(): agents = read(AGENTS) assert "docs/CONTINUITY.md" in agents assert "docs/VERIFICATION_LEDGER.json" in agents - assert "docs/RELEASE_PROCESS.md" in agents + assert "docs/release/RELEASE_PROCESS.md" in agents assert "ayrı ayrı açık" in agents @@ -67,15 +67,23 @@ def test_continuity_is_compact_and_history_is_losslessly_routed(): history_ids = set(re.findall(r"EV-[0-9]{8}-[0-9]{3}", history)) assert len(current.splitlines()) <= 200 + # Satır sınırı tek başına yetmez: 24 Eylül 2026'da sınır, metin tek + # satıra yığılarak aşılıyordu (8137 karakterlik "sıradaki adım" satırı, + # aynı maddenin beş kümülatif kopyası). Uzunluk da sınırlıdır. + longest = max(len(line) for line in current.splitlines()) + assert longest <= 120, f"CONTINUITY satırı çok uzun: {longest}" + assert len(current) <= 16000, "CONTINUITY şişti; kapanan işi arşivle" assert "CONTINUITY_HISTORY.md" in current assert "TARİHSEL ARŞİV" in history assert "## Sıradaki tek adım" not in history assert history_ids - assert len(history_ids) == 170 + # 3 Ekim 2026: kapanmış canlı bloklar kelimesi kelimesine arşive eklendi + # (170 -> 247 kimlik). Özet, arşivin kazara değişmesini yakalar. + assert len(history_ids) == 247 assert history_ids <= ledger_ids normalized_history = "\n".join(history.splitlines()) + "\n" assert sha256(normalized_history.encode("utf-8")).hexdigest() == ( - "8bf29c2f732dd105a9e231bd820fe7f49623ce8e2ff508c5759a225ce60c8ed1" + "de38d5cac30f23f3ceb6db583b3506703da31a1c7ac169882cf6019445581d6f" ) @@ -92,7 +100,7 @@ def test_legacy_agent_guide_points_to_current_dynamic_contracts(): text = read(CLAUDE) assert "docs/CONTINUITY.md" in text assert "docs/VERIFICATION_LEDGER.json" in text - assert "docs/RELEASE_PROCESS.md" in text + assert "docs/release/RELEASE_PROCESS.md" in text assert "Release varlık sayısı sabit değildir" in text assert "Her release'e SEKİZ dosya" not in text assert "fetch_sources.py::FETCHABLE" not in text @@ -235,5 +243,5 @@ def test_session_start_injects_the_current_next_step_not_stale_history(): assert "git rev-list --left-right --count HEAD...origin/master" in context assert "Son kanıt" in context assert current_next_step() in context - assert "docs/PROJECT_STATUS.md ->" not in context + assert "docs/history/PROJECT_STATUS.md ->" not in context assert "SIRADAKİ PLAN (17 Ağustos 2026" not in context diff --git a/tests/test_decimal_text_locale_regressions.py b/tests/test_decimal_text_locale_regressions.py new file mode 100644 index 0000000..eec603c --- /dev/null +++ b/tests/test_decimal_text_locale_regressions.py @@ -0,0 +1,51 @@ +# SPDX-FileCopyrightText: 2026 MLC Player contributors +# SPDX-License-Identifier: GPL-3.0-only +"""Ondalık ayırıcı ve bayt birimi dile göre yazılmalı. + +Boyut, fps, oran ve kHz metinleri `.replace(".", ",")` ile Türkçe virgüle +sabitlenmişti ve `bayt` kelimesi çevrilmiyordu; İngilizce Medya Bilgisi +`1,5 GB`, `23,976 fps`, `512 bayt` gösteriyordu. +""" + +import ast +from pathlib import Path + +from app import number_text as utils + +ROOT = Path(__file__).resolve().parent.parent +ENGLISH = {"{whole},{fraction}": "{whole}.{fraction}", + "{count} bayt": "{count} bytes"} + + +def test_sizes_rates_and_frequencies_follow_the_language(monkeypatch): + from app import errors, media_info, track_labels + + monkeypatch.setattr(utils, "tr", lambda text: ENGLISH.get(text, text)) + assert media_info.format_size_text(1536) == "1.5 KB" + assert media_info.format_size_text(512) == "512 bytes" + assert errors.format_bytes(3 * 1024 * 1024) == "3.0 MB" + assert errors.format_bytes(7) == "7 bytes" + assert media_info._fps_text({"demux-fps": 23.976}) == "23.976 fps" + assert "48" in track_labels.__dict__["sample_rate_label"](48000) \ + if "sample_rate_label" in track_labels.__dict__ else True + + +def test_turkish_source_output_is_unchanged(): + from app import errors, media_info + + assert media_info.format_size_text(1536) == "1,5 KB" + assert errors.format_bytes(7) == "7 bayt" + assert media_info._fps_text({"demux-fps": 23.976}) == "23,976 fps" + + +def test_no_module_hardcodes_the_turkish_decimal_comma(): + offenders = [] + for path in sorted((ROOT / "app").glob("*.py")): + source = path.read_text(encoding="utf-8") + for node in ast.walk(ast.parse(source)): + if (isinstance(node, ast.Call) + and getattr(node.func, "attr", None) == "replace" + and [getattr(a, "value", None) for a in node.args] + == [".", ","]): + offenders.append(f"{path.name}:{node.lineno}") + assert offenders == [] diff --git a/tests/test_default_cinematic_ui_regressions.py b/tests/test_default_cinematic_ui_regressions.py index 871c2c7..3441c0a 100644 --- a/tests/test_default_cinematic_ui_regressions.py +++ b/tests/test_default_cinematic_ui_regressions.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-only """Sinematik arayüzün ürünün VARSAYILANI olduğunu doğrulayan testler. -Normal `python main.py` çalıştırmasında hiçbir ortam değişkeni gerekmemeli. +Normal `python app/main.py` çalıştırmasında hiçbir ortam değişkeni gerekmemeli. Klasik görünüm yalnızca açık teşhis anahtarı MLCPLAYER_CLASSIC_UI=1 ile gelir. """ import json @@ -156,14 +156,14 @@ def test_product_code_no_longer_reads_the_legacy_variable(): def test_main_entry_does_not_inject_ui_environment(): - with open(os.path.join(PROJECT_ROOT, "main.py"), encoding="utf-8") as handle: + with open(os.path.join(PROJECT_ROOT, "app/main.py"), encoding="utf-8") as handle: source = handle.read() for name in UI_ENV_VARS: assert f'environ["{name}"]' not in source assert f"environ['{name}']" not in source -# --- 4. Gerçek main.py giriş noktası --- +# --- 4. Gerçek app/main.py giriş noktası --- @pytest.mark.skipif(HOSTED_CI, reason="main entry acceptance requires mpv-2.dll") def test_main_entry_starts_with_cinematic_ui(main_entry): diff --git a/tests/test_dependency_lock_regressions.py b/tests/test_dependency_lock_regressions.py index 483a93e..8241327 100644 --- a/tests/test_dependency_lock_regressions.py +++ b/tests/test_dependency_lock_regressions.py @@ -9,9 +9,9 @@ ROOT = Path(__file__).resolve().parent.parent -RUNTIME = ROOT / "requirements.txt" -DEV = ROOT / "requirements-dev.txt" -LOCK = ROOT / "requirements-lock.txt" +RUNTIME = ROOT / "requirements/runtime.txt" +DEV = ROOT / "requirements/dev.txt" +LOCK = ROOT / "requirements/lock.txt" ADVISORY_FIXED_MINIMUMS = { "pillow": (12, 3, 0), @@ -72,7 +72,7 @@ def test_advisory_affected_developer_tools_stay_at_fixed_versions(): def test_advisory_affected_tools_are_not_shipped_as_player_runtime(): runtime = exact_pins(RUNTIME) assert not ({"pillow", "pytest", "setuptools"} & runtime.keys()) - spec = (ROOT / "MLCPlayer.spec").read_text(encoding="utf-8") + spec = (ROOT / "packaging/MLCPlayer.spec").read_text(encoding="utf-8") excludes = spec.split("excludes=", 1)[1].split("]", 1)[0].lower() for name in ("pil", "pillow", "pytest", "setuptools"): assert repr(name) in excludes @@ -82,7 +82,7 @@ def test_bootstrap_checks_versions_instead_of_only_import_presence(): bootstrap = (ROOT / "scripts" / "bootstrap.ps1").read_text( encoding="utf-8") assert "verify_dependencies.py" in bootstrap - assert "requirements.txt" in bootstrap + assert "requirements/runtime.txt" in bootstrap assert 'MaximumPythonExclusive = [Version]"3.15"' in bootstrap @@ -92,7 +92,7 @@ def test_release_build_fails_before_cleanup_on_an_unlocked_environment(): check = build.index("verify_dependencies.py") cleanup = build.index("STEP 2/8 Cleaning previous output") assert check < cleanup - assert "requirements-lock.txt" in build + assert "requirements/lock.txt" in build def test_dependency_verifier_fails_closed_for_a_missing_package(tmp_path): diff --git a/tests/test_developer_dependency_regressions.py b/tests/test_developer_dependency_regressions.py index 165da73..176cd23 100644 --- a/tests/test_developer_dependency_regressions.py +++ b/tests/test_developer_dependency_regressions.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-only """Depoyu klonlayan biri testleri ve yayın zincirini KOŞABİLMELİDİR. -KIRMIZI KANIT (17 Ağustos 2026, ölçüldü). `requirements.txt` yalnız +KIRMIZI KANIT (17 Ağustos 2026, ölçüldü). `requirements/runtime.txt` yalnız `PyQt6`, `python-mpv` ve `cryptography` beyan ediyor. Ama depo şunları ADIYLA çağırıyor: @@ -13,24 +13,24 @@ `pyside6-lrelease` **PySide6** ile gelir. Ölçüldü: PyQt6 `pylupdate6` sağlıyor ama `lrelease` SAĞLAMIYOR — yani bağımlılık gerçek, tesadüf -değil. Temiz bir kopyada `pip install -r requirements.txt` yapan biri +değil. Temiz bir kopyada `pip install -r requirements/runtime.txt` yapan biri testleri koşamaz ve `build_release.bat` 3. adımda durur. -NEDEN `requirements.txt`E EKLENMEZ: o dosyayı `scripts/bootstrap.ps1` +NEDEN `requirements/runtime.txt`E EKLENMEZ: o dosyayı `scripts/bootstrap.ps1` SON KULLANICI için kuruyor. Programı kaynaktan çalıştırmak isteyen birine ikinci bir Qt bağlaması indirtmek doğru değil. Geliştirici araçları ayrı dosyada durur. PAKETE DE GİRMEMELİDİR: ürün PyQt6 ile çalışır; PySide6 yalnız derleme -aracıdır ve `MLCPlayer.spec` `excludes` listesinde olmalıdır. +aracıdır ve `packaging/MLCPlayer.spec` `excludes` listesinde olmalıdır. """ import re from pathlib import Path ROOT = Path(__file__).resolve().parent.parent -RUNTIME = ROOT / "requirements.txt" -DEV = ROOT / "requirements-dev.txt" +RUNTIME = ROOT / "requirements/runtime.txt" +DEV = ROOT / "requirements/dev.txt" def read(path): @@ -79,7 +79,7 @@ def test_the_runtime_file_stays_lean(): def test_pyside6_is_excluded_from_the_package(): """Ürün PyQt6 ile çalışır; PySide6 derleme aracıdır.""" - spec = (ROOT / "MLCPlayer.spec").read_text(encoding="utf-8") + spec = (ROOT / "packaging/MLCPlayer.spec").read_text(encoding="utf-8") excludes = spec.split("excludes=", 1)[1].split("]", 1)[0] assert "PySide6" in excludes @@ -101,9 +101,9 @@ def test_every_externally_invoked_tool_is_declared(): def test_the_readme_documents_the_translation_workflow(): """`.ts` dosyaları hazır ama nasıl çevrileceği yazılı değildi.""" - readme = (ROOT / "README.md").read_text(encoding="utf-8") + readme = (ROOT / ".github" / "README.md").read_text(encoding="utf-8") section = readme.split("## Contributing", 1)[1].split("\n## ", 1)[0] - assert "requirements-dev.txt" in section + assert "requirements/dev.txt" in section assert "linguist" in section.lower() assert "translations/" in section @@ -111,7 +111,7 @@ def test_the_readme_documents_the_translation_workflow(): def test_the_readme_warns_against_the_wrong_extractor(): """TUZAK: `pyside6-lupdate` bizim `tr()` sarmalayıcımızı GÖREMEZ ve `.ts` dosyalarını eksik yeniden yazar. Çıkarma bize aittir.""" - readme = (ROOT / "README.md").read_text(encoding="utf-8") + readme = (ROOT / ".github" / "README.md").read_text(encoding="utf-8") section = readme.split("## Contributing", 1)[1].split("\n## ", 1)[0] assert "lupdate" in section, "yanlış araç uyarısı yok" assert "extract_translations.py" in section diff --git a/tests/test_dist_completeness_regressions.py b/tests/test_dist_completeness_regressions.py index 7f42634..a00f30f 100644 --- a/tests/test_dist_completeness_regressions.py +++ b/tests/test_dist_completeness_regressions.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-only """The post-build gate must verify every user- and licence-critical file. -Static references in ``MLCPlayer.spec`` are not sufficient: a collection or +Static references in ``packaging/MLCPlayer.spec`` are not sufficient: a collection or translation-build regression can still produce a runnable but incomplete ``dist`` tree. These checks make the actual post-build inventory fail closed. """ diff --git a/tests/test_docs_index_regressions.py b/tests/test_docs_index_regressions.py new file mode 100644 index 0000000..6ff3ffb --- /dev/null +++ b/tests/test_docs_index_regressions.py @@ -0,0 +1,63 @@ +# SPDX-FileCopyrightText: 2026 MLC Player contributors +# SPDX-License-Identifier: GPL-3.0-only +"""`docs/README.md` belge haritası ile `docs/` ağacı birebir eşleşir. + +Neden (3 Ekim 2026): `docs/` kökünde 19 belge düz biçimde birikmişti; iki +SignPath belgesi aynı durumu üç kez tekrar ediyor, bir envanter hiçbir +belgeden atıf almıyor, canlı ve tarihsel belgeler aynı klasörde duruyordu. +Harita her belgenin tek sorumluluğunu yazar; bu test haritanın eskimesini +ve haritaya girmeden yeni belge eklenmesini engeller. +""" +import re +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +DOCS = ROOT / "docs" +INDEX = DOCS / "README.md" +DOC_SUFFIXES = {".md", ".json"} + + +def _listed(): + text = INDEX.read_text(encoding="utf-8") + return set(re.findall(r"`(docs/[^`\s]+\.(?:md|json))`", text)) + + +def _present(): + return { + path.relative_to(ROOT).as_posix() + for path in DOCS.rglob("*") + if path.is_file() and path.suffix in DOC_SUFFIXES and path != INDEX + } + + +def test_every_listed_document_exists(): + missing = sorted(path for path in _listed() if not (ROOT / path).is_file()) + assert missing == [] + + +def test_every_document_is_listed_in_the_index(): + unlisted = sorted(_present() - _listed()) + assert unlisted == [], "docs/README.md haritasına eklenmemiş belge" + + +def test_history_documents_declare_themselves_historical(): + for path in sorted((DOCS / "history").glob("*.md")): + opening = "\n".join(path.read_text(encoding="utf-8").splitlines()[:12]) + assert "TARİHSEL" in opening.upper(), path.name + assert "docs/CONTINUITY.md" in opening, path.name + + +def test_repository_root_holds_only_the_launcher_and_licence(): + allowed = {"Start.bat", "LICENSE", ".gitignore", ".gitattributes"} + tracked_root = { + path.name for path in ROOT.iterdir() + if path.is_file() and path.name in _tracked_root_names() + } + assert tracked_root <= allowed, sorted(tracked_root - allowed) + + +def _tracked_root_names(): + import subprocess + out = subprocess.run(["git", "ls-files"], cwd=ROOT, capture_output=True, + encoding="utf-8", check=True).stdout + return {line for line in out.splitlines() if line and "/" not in line} diff --git a/tests/test_drop_subtitle_session_media_scope_regressions.py b/tests/test_drop_subtitle_session_media_scope_regressions.py new file mode 100644 index 0000000..7e16823 --- /dev/null +++ b/tests/test_drop_subtitle_session_media_scope_regressions.py @@ -0,0 +1,69 @@ +# SPDX-FileCopyrightText: 2026 MLC Player contributors +# SPDX-License-Identifier: GPL-3.0-only +"""Bırakılan/menüden eklenen altyazı oturumu medya değişince sıfırlanmalı. + +`SubtitleSession` önceki MLC track'inin `sid`'ini hatırlar ve yeni ekleme +sonrası onu kaldırır. Oturum videolar arasında yaşadığı için A videosundan +kalan `sid` B videosunda BAŞKA bir track'i gösterir (ör. B'nin otomatik +yüklenen kendi SRT'si) ve o track yanlışlıkla kaldırılıyordu. +""" + +from types import SimpleNamespace + +from app.player import MPVPlayer + + +class FakeMpv: + def __init__(self): + self.track_list = [] + self.sid = False + self.sub_visibility = False + self.removed = [] + + def sub_add(self, path, *args): + next_id = max([t["id"] for t in self.track_list] + [0]) + 1 + self.track_list.append({"id": next_id, "type": "sub", + "external": True, + "external-filename": path}) + + def sub_remove(self, sid): + self.removed.append(sid) + self.track_list = [t for t in self.track_list if t["id"] != sid] + + +def _player(mpv, current_file): + player = SimpleNamespace( + mpv_player=mpv, current_file=current_file, + _drop_subtitle_session=None, + video_frame=SimpleNamespace(show_osd=lambda *a, **k: None, + _update_overlay_subtitle_state=None), + _subtitle_track_wait=lambda: None) + player.activate = lambda path: MPVPlayer._activate_dropped_subtitle( + player, path) + return player + + +def test_new_media_does_not_remove_a_track_by_a_stale_sid(tmp_path): + first = tmp_path / "A.en.srt" + second = tmp_path / "B.tr.srt" + auto = tmp_path / "B.srt" + for path in (first, second, auto): + path.write_text("1\n00:00:01,000 --> 00:00:02,000\nx\n", + encoding="utf-8") + mpv = FakeMpv() + player = _player(mpv, str(tmp_path / "A.mkv")) + assert player.activate(str(first)) is True + assert mpv.sid == 1 + + # B videosu yüklendi: MPV track listesini sıfırlar ve B'nin kendi + # SRT'sini otomatik yükler; o da id 1 alır. + player.current_file = str(tmp_path / "B.mkv") + mpv.track_list = [{"id": 1, "type": "sub", "external": True, + "external-filename": str(auto)}] + mpv.sid = 1 + + assert player.activate(str(second)) is True + + assert mpv.removed == [] + assert [t["id"] for t in mpv.track_list] == [1, 2] + assert mpv.sid == 2 diff --git a/tests/test_empty_state_info_dialog_regressions.py b/tests/test_empty_state_info_dialog_regressions.py index 5a4ab69..2a18e55 100644 --- a/tests/test_empty_state_info_dialog_regressions.py +++ b/tests/test_empty_state_info_dialog_regressions.py @@ -46,6 +46,7 @@ def test_empty_state_matches_the_approved_layout(): surface.open_folder_button.styleSheet() assert surface.open_file_button.icon().isNull() assert surface.open_folder_button.icon().isNull() + assert surface.findChild(QLabel, "emptyStateLogo") is None assert surface.parentWidget() is frame assert surface.isWindow() is False assert surface.testAttribute(Qt.WidgetAttribute.WA_NativeWindow) diff --git a/tests/test_exe_version_resource_regressions.py b/tests/test_exe_version_resource_regressions.py index 8a3dd4e..08514a3 100644 --- a/tests/test_exe_version_resource_regressions.py +++ b/tests/test_exe_version_resource_regressions.py @@ -18,7 +18,7 @@ from app.config import APP_VERSION, WINDOWS_VERSION ROOT = Path(__file__).resolve().parent.parent -SPEC = ROOT / "MLCPlayer.spec" +SPEC = ROOT / "packaging/MLCPlayer.spec" def _load_version_resource(): diff --git a/tests/test_frozen_dll_provenance_regressions.py b/tests/test_frozen_dll_provenance_regressions.py index 7844054..c7c8a8a 100644 --- a/tests/test_frozen_dll_provenance_regressions.py +++ b/tests/test_frozen_dll_provenance_regressions.py @@ -15,7 +15,7 @@ PACKAGING = ROOT / "packaging" POLICY_PATH = PACKAGING / "pyinstaller_binary_policy.py" RUNNER_PATH = PACKAGING / "run_pyinstaller.py" -SPEC_PATH = ROOT / "MLCPlayer.spec" +SPEC_PATH = ROOT / "packaging/MLCPlayer.spec" sys.path.insert(0, str(PACKAGING)) import verify_build # noqa: E402 @@ -139,7 +139,7 @@ def test_unknown_root_icu_provenance_fails_closed(source): def test_project_or_python_root_icu_requires_explicit_review(): policy = load_policy() - source = ROOT / "README.md" + source = ROOT / ".github" / "README.md" with pytest.raises(policy.BinaryPolicyError): policy.sanitize_binaries( @@ -290,7 +290,7 @@ def fake_run(command, **kwargs): return SimpleNamespace(returncode=7) code = runner.run_pyinstaller( - ["MLCPlayer.spec", "--clean"], + ["packaging/MLCPlayer.spec", "--clean"], environ={"SystemRoot": str(system_root)}, executable=executable, base_prefix=python_dir, @@ -300,7 +300,7 @@ def fake_run(command, **kwargs): assert code == 7 assert captured["command"] == [ str(executable.resolve()), "-m", "PyInstaller", - "MLCPlayer.spec", "--clean"] + "packaging/MLCPlayer.spec", "--clean"] assert captured["cwd"] == str(ROOT) assert captured["check"] is False @@ -316,4 +316,5 @@ def test_build_chains_use_the_clean_runner(relative): assert build_lines == [ 'python "packaging\\run_pyinstaller.py" "%SPEC%" ' - '--noconfirm --clean --log-level WARN'] + '--noconfirm --clean --log-level WARN ' + '--distpath "output\\dist" --workpath "output\\build"'] diff --git a/tests/test_hdr_acceptance_contract_regressions.py b/tests/test_hdr_acceptance_contract_regressions.py index a3b3e08..380bd3a 100644 --- a/tests/test_hdr_acceptance_contract_regressions.py +++ b/tests/test_hdr_acceptance_contract_regressions.py @@ -19,7 +19,7 @@ ROOT = Path(__file__).resolve().parent.parent RUNNER = ROOT / "tests" / "run_hdr_acceptance.py" CHILD = ROOT / "tests" / "native_hdr_probe_child.py" -MATRIX = ROOT / "docs" / "WINDOWS_ACCEPTANCE_MATRIX.md" +MATRIX = ROOT / "docs" / "quality" / "WINDOWS_ACCEPTANCE_MATRIX.md" def read(path): diff --git a/tests/test_inno_compile_preflight_regressions.py b/tests/test_inno_compile_preflight_regressions.py index bf713eb..3c752d2 100644 --- a/tests/test_inno_compile_preflight_regressions.py +++ b/tests/test_inno_compile_preflight_regressions.py @@ -111,10 +111,14 @@ def test_preflight_skips_only_the_generated_payload_and_normal_build_does_not(): unsigned = (ROOT / "packaging" / "build_unsigned_main.bat").read_text( encoding="utf-8") payload = ( - 'Source: "..\\dist\\MLC Player\\*"; DestDir: "{app}"; ' + 'Source: "..\\output\\dist\\MLC Player\\*"; DestDir: "{app}"; ' 'Flags: ignoreversion recursesubdirs createallsubdirs; ' 'BeforeInstall: BeforeInstallMainPayload' ) + cleanup_helper = ( + 'Source: "..\\output\\dist\\MLCUserCleanup.exe"; DestDir: "{app}"; ' + 'Flags: ignoreversion' + ) guarded = installer.split("#ifndef MLCCompilePreflight", 1)[1].split( "#endif", 1)[0] active_guarded_lines = [ @@ -123,7 +127,7 @@ def test_preflight_skips_only_the_generated_payload_and_normal_build_does_not(): ] assert payload in guarded - assert active_guarded_lines == [payload] + assert active_guarded_lines == [payload, cleanup_helper] assert installer.count("#ifndef MLCCompilePreflight") == 1 assert installer.count("#endif") >= 1 assert '/DMLCCompilePreflight=1' not in release diff --git a/tests/test_installed_licence_bundle_regressions.py b/tests/test_installed_licence_bundle_regressions.py index ace5a2c..5733374 100644 --- a/tests/test_installed_licence_bundle_regressions.py +++ b/tests/test_installed_licence_bundle_regressions.py @@ -8,7 +8,7 @@ ROOT = Path(__file__).resolve().parent.parent LICENCES = ROOT / "licenses" -SPEC = ROOT / "MLCPlayer.spec" +SPEC = ROOT / "packaging/MLCPlayer.spec" VERIFY = ROOT / "packaging" / "verify_build.py" REQUIRED = ( diff --git a/tests/test_installer_branding_regressions.py b/tests/test_installer_branding_regressions.py index e167267..fc94b4c 100644 --- a/tests/test_installer_branding_regressions.py +++ b/tests/test_installer_branding_regressions.py @@ -17,7 +17,7 @@ ROOT = Path(__file__).resolve().parent.parent ISS = ROOT / "packaging" / "MLCPlayer.iss" WIZARD_DIR = ROOT / "packaging" / "wizard" -PACKAGING_PLAN = ROOT / "docs" / "PACKAGING_PLAN.md" +PACKAGING_PLAN = ROOT / "docs" / "release" / "PACKAGING_PLAN.md" SEPARATE_UX_PLAN = ROOT / "docs" / "INSTALLER_UX_PLAN.md" REPO_URL = "https://github.com/IzzmooPro/MLCPlayer" diff --git a/tests/test_installer_process_safety_regressions.py b/tests/test_installer_process_safety_regressions.py index 30d2a20..028a2fe 100644 --- a/tests/test_installer_process_safety_regressions.py +++ b/tests/test_installer_process_safety_regressions.py @@ -15,7 +15,7 @@ MAIN_ISS = ROOT / "packaging" / "MLCPlayer.iss" ADDON_ISS = ROOT / "packaging" / "MLCPlayer_InternetVideo.iss" SINGLE_INSTANCE = ROOT / "app" / "single_instance.py" -PACKAGING_PLAN = ROOT / "docs" / "PACKAGING_PLAN.md" +PACKAGING_PLAN = ROOT / "docs" / "release" / "PACKAGING_PLAN.md" def read(path): diff --git a/tests/test_internet_video_addon_regressions.py b/tests/test_internet_video_addon_regressions.py index fe37b11..ef1c707 100644 --- a/tests/test_internet_video_addon_regressions.py +++ b/tests/test_internet_video_addon_regressions.py @@ -17,7 +17,7 @@ from app import runtime_binaries as runtime ROOT = Path(__file__).resolve().parent.parent -SPEC = ROOT / "MLCPlayer.spec" +SPEC = ROOT / "packaging/MLCPlayer.spec" ADDON_ISS = ROOT / "packaging" / "MLCPlayer_InternetVideo.iss" CHAIN = ROOT / "packaging" / "build_release.bat" @@ -195,7 +195,7 @@ def test_the_chain_builds_and_signs_the_addon(): — bu bir DAVRANIŞ değil, uygulama ayrıntısıdır. O değişken jokerle doldurulan bir arama sonucuydu: - for %%F in ("installer_output\\MLCPlayer_InternetVideo_*.exe") ... + for %%F in ("output/installer\\MLCPlayer_InternetVideo_*.exe") ... Ürün artık kesin `ADDON_SETUP` yolunu doğrudan imzalıyor; joker kaldırıldığı için değişken de kalktı ve test ürün DÜZELDİĞİ için diff --git a/tests/test_language_settings_regressions.py b/tests/test_language_settings_regressions.py index dfcc4a9..ce79e33 100644 --- a/tests/test_language_settings_regressions.py +++ b/tests/test_language_settings_regressions.py @@ -23,7 +23,7 @@ @pytest.fixture(autouse=True) def isolated_settings(tmp_path, monkeypatch): - """Gerçek kullanıcı ayarına dokunulmaz (CLAUDE.md kuralı).""" + """Gerçek kullanıcı ayarına dokunulmaz (.claude/CLAUDE.md kuralı).""" from PyQt6.QtCore import QSettings store = QSettings(str(tmp_path / "dil.ini"), QSettings.Format.IniFormat) monkeypatch.setattr(i18n, "_settings", lambda: store) @@ -177,7 +177,7 @@ def test_the_menu_marks_the_current_choice(qt_app_for_menu, isolated_settings): def test_the_startup_path_applies_the_language(): """Dil, pencereler kurulmadan ÖNCE uygulanmalı; sonra menüler geç kalır.""" - source = (ROOT / "main.py").read_text(encoding="utf-8") + source = (ROOT / "app/main.py").read_text(encoding="utf-8") assert "apply_language(app)" in source assert source.index("apply_language(app)") < source.index("MPVPlayer()") diff --git a/tests/test_licence_documentation_regressions.py b/tests/test_licence_documentation_regressions.py index 7f56e7b..fc9bb99 100644 --- a/tests/test_licence_documentation_regressions.py +++ b/tests/test_licence_documentation_regressions.py @@ -28,14 +28,14 @@ def licence_section(name): - text = (ROOT / name).read_text(encoding="utf-8") + text = (ROOT / ".github" / name).read_text(encoding="utf-8") marker = "## Licence" if name == "README.md" else "## Lisans" assert marker in text, f"{name}: lisans bölümü yok" return text.split(marker, 1)[1] def open_items_section(): - text = (ROOT / "README.tr.md").read_text(encoding="utf-8") + text = (ROOT / ".github" / "README.tr.md").read_text(encoding="utf-8") marker = "### Yayın öncesi açık maddeler" assert marker in text, "açık maddeler bölümü yok" body = text.split(marker, 1)[1] @@ -73,7 +73,7 @@ def test_the_list_does_not_claim_these_block_distribution(): def test_the_closed_items_are_recorded_as_closed(): """Kapananlar sessizce SİLİNMEZ; neyin ne zaman kapandığı kalmalı.""" - text = (ROOT / "README.tr.md").read_text(encoding="utf-8") + text = (ROOT / ".github" / "README.tr.md").read_text(encoding="utf-8") assert "**Kapanan maddeler:**" in text closed = text.split("**Kapanan maddeler:**", 1)[1].lower() for topic in ("dosya başı", "hakkında penceresi", "binary köken kaydı"): diff --git a/tests/test_local_subtitle_autoload_regressions.py b/tests/test_local_subtitle_autoload_regressions.py index dae7302..e79b14b 100644 --- a/tests/test_local_subtitle_autoload_regressions.py +++ b/tests/test_local_subtitle_autoload_regressions.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-only """Video acilisinda ayni klasordeki eslesen SRT'yi SESSIZ etkinlestirme (2B). -Sozlesme kaynagi: `docs/PROJECT_STATUS.md` (14 Agustos 2026 arastirmasi). +Sozlesme kaynagi: `docs/history/PROJECT_STATUS.md` (14 Agustos 2026 arastirmasi). - `MPV_CONFIG` genis `sub_auto="fuzzy"` yerine `exact` kullanir; global `sub_visibility="no"` KALIR ve yalniz DOGRULANMIS yerel SRT bulununca acilir. diff --git a/tests/test_manual_workflow_ref_regressions.py b/tests/test_manual_workflow_ref_regressions.py index 4b7a150..c0b7bf2 100644 --- a/tests/test_manual_workflow_ref_regressions.py +++ b/tests/test_manual_workflow_ref_regressions.py @@ -9,8 +9,8 @@ ROOT = Path(__file__).resolve().parent.parent WORKFLOWS = ROOT / ".github" / "workflows" -CHANGE_WORKFLOW = ROOT / "docs" / "CHANGE_WORKFLOW.md" -RELEASE_PROCESS = ROOT / "docs" / "RELEASE_PROCESS.md" +CHANGE_WORKFLOW = ROOT / "docs" / "process" / "CHANGE_WORKFLOW.md" +RELEASE_PROCESS = ROOT / "docs" / "release" / "RELEASE_PROCESS.md" MANUAL = ( "ci.yml", "build-unsigned-main.yml", diff --git a/tests/test_menu_subtitle_add_lifecycle_regressions.py b/tests/test_menu_subtitle_add_lifecycle_regressions.py new file mode 100644 index 0000000..330186b --- /dev/null +++ b/tests/test_menu_subtitle_add_lifecycle_regressions.py @@ -0,0 +1,53 @@ +# SPDX-FileCopyrightText: 2026 MLC Player contributors +# SPDX-License-Identifier: GPL-3.0-only +"""Menüden "Altyazı Ekle" sürükle-bırakla AYNI yaşam döngüsünü kullanmalı. + +Gerçek libmpv ölçümü (24 Eylül 2026, vo/ao=null, lavfi testsrc): +`sub_visibility=False` iken `sub_add()` izi seçer (`sid 1`) ama +görünürlük `False` KALIR. Yeni medya açılışında görünürlük kapatıldığı +için menü yolu "Altyazı eklendi" deyip altyazıyı göstermiyordu. Sürükle- +bırak yolu (`SubtitleSession.apply`) izi doğrular, seçer ve görünür yapar. +""" + +from types import SimpleNamespace + +from app import media_controls + + +def _player(duration, activated): + return SimpleNamespace( + current_file="film.mkv", duration=duration, _pending_subs=[], + video_frame=SimpleNamespace(show_osd=lambda *a, **k: None), + mpv_player=SimpleNamespace( + sub_add=lambda path: activated.append(("raw_sub_add", path))), + _activate_dropped_subtitle=lambda path: activated.append( + ("session", path)) or True) + + +def test_menu_add_uses_the_single_subtitle_lifecycle(monkeypatch, tmp_path): + subtitle = tmp_path / "film.srt" + subtitle.write_text("1\n00:00:01,000 --> 00:00:02,000\nmerhaba\n", + encoding="utf-8") + monkeypatch.setattr( + media_controls.QFileDialog, "getOpenFileName", + staticmethod(lambda *a, **k: (str(subtitle), ""))) + calls = [] + + media_controls.open_subtitle(_player(120.0, calls)) + + assert calls == [("session", str(subtitle))] + + +def test_menu_add_before_load_still_queues(monkeypatch, tmp_path): + subtitle = tmp_path / "film.srt" + subtitle.write_text("x", encoding="utf-8") + monkeypatch.setattr( + media_controls.QFileDialog, "getOpenFileName", + staticmethod(lambda *a, **k: (str(subtitle), ""))) + calls = [] + player = _player(0, calls) + + media_controls.open_subtitle(player) + + assert calls == [] + assert player._pending_subs == [str(subtitle)] diff --git a/tests/test_natural_end_reentry_regressions.py b/tests/test_natural_end_reentry_regressions.py new file mode 100644 index 0000000..42bd03f --- /dev/null +++ b/tests/test_natural_end_reentry_regressions.py @@ -0,0 +1,53 @@ +# SPDX-FileCopyrightText: 2026 MLC Player contributors +# SPDX-License-Identifier: GPL-3.0-only +"""Doğal bitişte sıradaki öğe yüklenemezse hata BİR KEZ gösterilmeli. + +`handle_natural_end()` 100 ms'lik `update_ui` timer'ından çağrılır. +`play_from_playlist()` başarısız yüklemede durumu (sonda, çekirdek boşta) +geri alır ve SONRA modal hata penceresini açar; modal döngüde timer +çalışmaya devam ettiği için her tik aynı yüklemeyi yeniden deneyip yeni +bir pencere açıyordu. +""" + +from types import SimpleNamespace + +from app import media_controls + + +def _player(): + def command_async(*args): + if args and args[0] == "loadfile": + raise RuntimeError("load rejected") + + return SimpleNamespace( + playlist=["a.mkv", "b.mkv"], current_playlist_index=0, + current_file="a.mkv", duration=100.0, position=100.0, + _core_idle=True, _eof_rewound=False, loop_file=False, + loop_playlist=False, shuffle=False, is_paused=False, + settings=None, title_bar=None, play_icon="play", pause_icon="pause", + play_button=SimpleNamespace(setIcon=lambda _i: None), + video_frame=SimpleNamespace(control_overlay=None), + mpv_player=SimpleNamespace(command_async=command_async, pause=False, + sub_delay=0.0, sub_visibility=False), + set_title=lambda: None) + + +def test_failed_next_item_shows_one_error_and_rewinds(monkeypatch): + player = _player() + shown = [] + + def modal_error(*_args, **_kwargs): + shown.append(True) + if len(shown) < 5: + # Modal döngü içindeki timer tikini taklit eder. + media_controls.handle_natural_end(player) + + monkeypatch.setattr(media_controls, "show_user_error", modal_error) + + media_controls.handle_natural_end(player) + + assert len(shown) == 1 + # Sıradaki yüklenemedi: mevcut öğe başa sarılıp duraklatılır. + assert player._eof_rewound is True + assert player.is_paused is True + assert player.current_playlist_index == 0 diff --git a/tests/test_open_failure_dialog_reentry_regressions.py b/tests/test_open_failure_dialog_reentry_regressions.py new file mode 100644 index 0000000..205a7e7 --- /dev/null +++ b/tests/test_open_failure_dialog_reentry_regressions.py @@ -0,0 +1,52 @@ +# SPDX-FileCopyrightText: 2026 MLC Player contributors +# SPDX-License-Identifier: GPL-3.0-only +"""Açılamayan dosya için hata penceresi bir kez açılmalı. + +Hata penceresi modal `exec()` ile açılır; modal döngü sürerken 100 ms'lik +`update_ui` timer'ı çalışmaya devam eder. Koşul pencere kapanana kadar +doğru kaldığından her tik yeni bir pencere daha açıyordu. +""" + +import time + +from app import player as player_module +from app.player import MPVPlayer + +from tests.test_overlay_integration_regressions import make_product_window + + +def test_open_failure_dialog_is_not_reopened_by_timer_ticks(monkeypatch, tmp_path): + app, window, _frame = make_product_window(monkeypatch, tmp_path) + # Gerçek `MPVPlayer.__init__` varsayılanları (update_ui'nin okuduğu). + for name, value in (("_title_bar_raise_pending", False), + ("_eof_rewound", False), ("loop_file", False), + ("loop_playlist", False), ("shuffle", False), + ("_shuffle_signature", ()), ("_shuffle_order", []), + ("_shuffle_cursor", -1), ("_url_loading_active", False), + ("_url_loading_started_at", 0.0)): + setattr(window, name, value) + window.current_file = "bozuk.mkv" + window.duration = 0 + window._core_idle = True + window._load_started_at = time.time() - 10 + shown = [] + + def modal_error(*_args, **_kwargs): + shown.append(True) + if len(shown) < 5: + # Modal döngü içindeki timer tikini taklit eder. + MPVPlayer.update_ui(window) + + monkeypatch.setattr(player_module, "show_user_error", modal_error) + errors = [] + monkeypatch.setattr(player_module, "safe_console", errors.append) + try: + MPVPlayer.update_ui(window) + assert errors == [] + assert len(shown) == 1 + # Pencere kapandıktan sonra durum sıfırlandı; yeni tik tekrar açmaz. + MPVPlayer.update_ui(window) + assert len(shown) == 1 + finally: + window.close() + app.processEvents() diff --git a/tests/test_overlay_subtitle_state_regressions.py b/tests/test_overlay_subtitle_state_regressions.py index a68a899..920f1c5 100644 --- a/tests/test_overlay_subtitle_state_regressions.py +++ b/tests/test_overlay_subtitle_state_regressions.py @@ -7,6 +7,7 @@ altyazı yükleme sonrası da doğru renk gösterilmelidir. """ import os +import types from types import SimpleNamespace os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") @@ -254,6 +255,23 @@ def test_menu_toggle_updates_the_icon(product_window): # --- 8/9. Dil seçimi ve harici altyazı --- +def test_selecting_a_track_while_hidden_makes_it_visible(product_window): + # Yeni medya açılışında görünürlük kapatılır; kullanıcının menüden + # AÇIKÇA seçtiği parça gösterilmeli. Gerçek libmpv `sid` yazımı + # görünürlüğü değiştirmez (24 Eylül 2026 ölçümü). + app, window, frame = product_window( + sub_visibility=False, sid=False, + tracks=[{"id": 1, "type": "sub"}, {"id": 2, "type": "sub"}]) + frame.update_overlay_state() + + media_controls.select_subtitle_language(window, 2) + app.processEvents() + + assert window.mpv_player.sid == 2 + assert window.mpv_player.sub_visibility is True + assert frame.overlay_subtitles_active is True + + def test_selecting_a_subtitle_language_turns_the_icon_orange(product_window): app, window, frame = product_window( sub_visibility=True, sid=False, @@ -277,13 +295,21 @@ def test_loading_an_external_subtitle_updates_the_icon(product_window, subtitle.write_text("1\n00:00:01,000 --> 00:00:02,000\nmerhaba\n", encoding="utf-8") - def sub_add(path): + def sub_add(path, *args): + # Gerçek libmpv gibi: iz eklenir ve seçilir ama görünürlük + # DEĞİŞMEZ (24 Eylül 2026 ölçümü). Görünür yapmak ürünün işidir. window.mpv_player.track_list = list(window.mpv_player.track_list) + [ - {"id": 3, "type": "sub", "external": True}] + {"id": 3, "type": "sub", "external": True, + "external-filename": path}] window.mpv_player.sid = 3 - window.mpv_player.sub_visibility = True window.mpv_player.sub_add = sub_add + # Menü yolu ürünün TEK altyazı yaşam döngüsünü (gerçek yöntem) kullanır. + from app.player import MPVPlayer + window._drop_subtitle_session = None + window._subtitle_track_wait = lambda: None + window._activate_dropped_subtitle = types.MethodType( + MPVPlayer._activate_dropped_subtitle, window) monkeypatch.setattr( "app.media_controls.QFileDialog.getOpenFileName", staticmethod(lambda *args, **kwargs: (str(subtitle), ""))) diff --git a/tests/test_percent_text_locale_regressions.py b/tests/test_percent_text_locale_regressions.py new file mode 100644 index 0000000..ab2e6df --- /dev/null +++ b/tests/test_percent_text_locale_regressions.py @@ -0,0 +1,43 @@ +# SPDX-FileCopyrightText: 2026 MLC Player contributors +# SPDX-License-Identifier: GPL-3.0-only +"""Yüzde değerleri dile göre yazılmalı (Türkçe `%70`, İngilizce `70%`). + +Ses, şeffaflık, altyazı konumu ve opaklık etiketleri `f"%{değer}"` ile +Türkçe sıraya sabitlenmişti; İngilizce arayüz `Transparency: %70` +gösteriyordu. Tek çevrilebilir biçimleyici kullanılır. +""" + +import ast +from pathlib import Path + +from app import number_text as utils + +ROOT = Path(__file__).resolve().parent.parent + + +def test_percent_text_goes_through_the_translator(monkeypatch): + monkeypatch.setattr(utils, "tr", lambda text: text.replace( + "%{value}", "{value}%")) + assert utils.percent_text(70) == "70%" + assert utils.percent_text(70.6) == "71%" + + +def test_percent_text_defaults_to_turkish_order(): + assert utils.percent_text(5) == "%5" + + +def test_no_ui_string_hardcodes_the_turkish_percent_order(): + offenders = [] + for path in sorted((ROOT / "app").glob("*.py")): + tree = ast.parse(path.read_text(encoding="utf-8")) + for node in ast.walk(tree): + if not isinstance(node, ast.JoinedStr): + continue + for part, following in zip(node.values, node.values[1:]): + if (isinstance(part, ast.Constant) + and str(part.value).endswith("%") + and isinstance(following, ast.FormattedValue)): + offenders.append(f"{path.name}:{node.lineno}") + # Günlük/konsol satırları Türkçe kalabilir; yalnız arayüz metinleri. + ui = [o for o in offenders if not o.startswith(("errors.py",))] + assert ui == [] diff --git a/tests/test_physical_child_contract_regressions.py b/tests/test_physical_child_contract_regressions.py index f11e8c3..cd8c1aa 100644 --- a/tests/test_physical_child_contract_regressions.py +++ b/tests/test_physical_child_contract_regressions.py @@ -113,7 +113,7 @@ def test_child_closes_only_through_the_product_path(): def test_child_exit_uses_product_policy_after_results(): - """`os._exit` yalnizca sonuc satirlarindan SONRA (main.py politikasi).""" + """`os._exit` yalnizca sonuc satirlarindan SONRA (app/main.py politikasi).""" source = read_child_source() done = source.index('print(f"MARK_DONE group={GROUP}"') exit_call = source.index("os._exit(main())") diff --git a/tests/test_physical_playback_seek_regressions.py b/tests/test_physical_playback_seek_regressions.py index 2276f88..68485b4 100644 --- a/tests/test_physical_playback_seek_regressions.py +++ b/tests/test_physical_playback_seek_regressions.py @@ -9,7 +9,7 @@ ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) CHILD = os.path.join(ROOT, "tests", "native_physical_acceptance_child.py") RUNNER = os.path.join(ROOT, "tests", "run_physical_acceptance.py") -MATRIX = os.path.join(ROOT, "docs", "WINDOWS_ACCEPTANCE_MATRIX.md") +MATRIX = os.path.join(ROOT, "docs", "quality", "WINDOWS_ACCEPTANCE_MATRIX.md") def read(path): diff --git a/tests/test_playback_speed_source_regressions.py b/tests/test_playback_speed_source_regressions.py new file mode 100644 index 0000000..d6bfbef --- /dev/null +++ b/tests/test_playback_speed_source_regressions.py @@ -0,0 +1,27 @@ +# SPDX-FileCopyrightText: 2026 MLC Player contributors +# SPDX-License-Identifier: GPL-3.0-only +"""Menü çubuğu ve video sağ-tık menüsü aynı hız listesini kullanmalı.""" + +import ast +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent + + +def _float_list_literals(path): + tree = ast.parse(path.read_text(encoding="utf-8")) + found = [] + for node in ast.walk(tree): + if isinstance(node, (ast.List, ast.Tuple)) and len(node.elts) >= 4 and all( + isinstance(e, ast.Constant) and isinstance(e.value, float) + for e in node.elts): + found.append(node.lineno) + return found + + +def test_speed_lists_have_a_single_source(): + # Ayrı ayrı yazılmış listeler zamanla ayrışır; iki menü farklı hız sunar. + for name in ("menu_actions.py", "video_frame.py"): + assert _float_list_literals(ROOT / "app" / name) == [], name + from app.config import PLAYBACK_SPEEDS + assert PLAYBACK_SPEEDS == (0.5, 0.75, 1.0, 1.25, 1.5, 2.0) diff --git a/tests/test_player_shutdown_regressions.py b/tests/test_player_shutdown_regressions.py index 545260d..f73b58c 100644 --- a/tests/test_player_shutdown_regressions.py +++ b/tests/test_player_shutdown_regressions.py @@ -14,7 +14,7 @@ KAPSAM NOTU: bu testler native `0xC0000005` hatasının kök nedenini kanıtlamaz ve ölçmez. Bu turda izole edilen tek kesin tetikleyici, Qt + libmpv + `audio-device-list` okumasının ardından gelen DOĞAL Python -finalizasyonudur; ürünün `main.py` yolu o faza girmez. Buradaki ölçüm +finalizasyonudur; ürünün `app/main.py` yolu o faza girmez. Buradaki ölçüm yalnızca ürünün kapanış çağrı SIRASI ve SAYISIDIR; kaynak metni veya AST kontrolü yapılmaz. """ @@ -760,3 +760,23 @@ def test_native_shutdown_child_uses_the_product_exit_policy(): encoding="utf-8") assert "os._exit(exit_code)" in child_source + + +def test_close_in_picture_in_picture_keeps_the_previous_window_size(close_bench): + # PiP küçük ve köşeye yapışık geçici bir moddur; o anki geometri + # kaydedilirse sonraki açılış küçük pencereyle başlar. + env = close_bench() + env.player.picture_in_picture_enabled = True + + env.close() + + assert "settings.geometry" not in env.calls + assert "settings.last_dir" in env.calls + + +def test_normal_close_still_saves_the_window_geometry(close_bench): + env = close_bench() + + env.close() + + assert "settings.geometry" in env.calls diff --git a/tests/test_playlist_panel_regressions.py b/tests/test_playlist_panel_regressions.py index 36d2887..e1bf0d9 100644 --- a/tests/test_playlist_panel_regressions.py +++ b/tests/test_playlist_panel_regressions.py @@ -875,3 +875,19 @@ def remove(self, key): assert player.playlist == [r"C:\media\opened.mkv"] assert player.current_playlist_index == 0 assert refreshed == [True] + + +def test_remove_ignores_selected_rows_hidden_by_the_search(playlist_window): + # Seçim yapıldıktan sonra arama satırları gizler ama seçimi bırakmaz; + # "Kaldır" kullanıcının göremediği öğeleri de siliyordu. + app, window, frame = playlist_window + panel = _open(app, window, frame) + view = panel.playlist_view + view.item(0).setSelected(True) + view.item(3).setSelected(True) + + panel.search_field.setText("last") + app.processEvents() + panel._remove_selected() + + assert window.removed_batches == [[3]] diff --git a/tests/test_playlist_thumbnail_regressions.py b/tests/test_playlist_thumbnail_regressions.py index 528efe7..25f354c 100644 --- a/tests/test_playlist_thumbnail_regressions.py +++ b/tests/test_playlist_thumbnail_regressions.py @@ -29,7 +29,7 @@ def test_thumbnail_cache_key_tracks_file_identity_and_metadata(tmp_path): def test_worker_command_is_source_and_frozen_safe(monkeypatch, tmp_path): source_program, source_args = build_worker_command("in.mkv", "out.jpg") assert source_program == sys.executable - assert source_args[0].endswith("main.py") + assert source_args[0].endswith("app/main.py") assert source_args[1:] == ["--thumbnail-worker", "in.mkv", "out.jpg"] monkeypatch.setattr(sys, "frozen", True, raising=False) diff --git a/tests/test_prepublish_regressions.py b/tests/test_prepublish_regressions.py index 3a50dd9..045ed73 100644 --- a/tests/test_prepublish_regressions.py +++ b/tests/test_prepublish_regressions.py @@ -109,8 +109,8 @@ def build(version="v0.37", tag=None, tag_version=None): repo = tmp_path / "depo" (repo / "app").mkdir(parents=True) (repo / "packaging").mkdir(parents=True) - (repo / "installer_output").mkdir(parents=True) - (repo / "source_mirror").mkdir(parents=True) + (repo / "output/installer").mkdir(parents=True) + (repo / "output/source_mirror").mkdir(parents=True) (repo / "bin").mkdir(parents=True) git(str(repo), "init", "-q") @@ -125,11 +125,11 @@ def write_sources(value): # Artifact ve mirror dosyalari izlenmez; aksi halde calisma agaci # kirli sayilirdi. (repo / ".gitignore").write_text( - "installer_output/\nsource_mirror/\nbin/\n", encoding="utf-8") + "output/\nbin/\n", encoding="utf-8") sources = [] for name, data in MIRROR_CONTENTS.items(): - (repo / "source_mirror" / name).write_bytes(data) + (repo / "output/source_mirror" / name).write_bytes(data) sources.append({ "name": name, "url": f"https://github.com/example/source/{name}", @@ -156,7 +156,7 @@ def write_sources(value): git(str(repo), "tag", chosen_tag) for stem in ("MLCPlayer_Setup", "MLCPlayer_InternetVideo"): - exe = repo / "installer_output" / f"{stem}_{version}.exe" + exe = repo / "output/installer" / f"{stem}_{version}.exe" exe.write_bytes(f"{stem} {version} govdesi".encode("utf-8")) write_signature(signing_key, exe) return str(repo) @@ -224,7 +224,7 @@ def test_an_unignored_untracked_file_is_rejected(release): def test_ignored_files_do_not_make_the_tree_dirty(release): - """`installer_output` ve `source_mirror` ignore'dur; kapiyi kapatmaz.""" + """`output/installer` ve `output/source_mirror` ignore'dur; kapiyi kapatmaz.""" repo = release() assert prepublish.run("v0.37", repo) is True @@ -240,7 +240,7 @@ def test_ignored_files_do_not_make_the_tree_dirty(release): ]) def test_each_missing_installer_artifact_fails(release, capsys, missing): repo = release() - os.remove(os.path.join(repo, "installer_output", missing)) + os.remove(os.path.join(repo, "output/installer", missing)) assert prepublish.run("v0.37", repo) is False assert missing in capsys.readouterr().out @@ -249,7 +249,7 @@ def test_each_missing_installer_artifact_fails(release, capsys, missing): @pytest.mark.parametrize("missing", sorted(MIRROR_CONTENTS)) def test_each_missing_mirror_asset_fails(release, capsys, missing): repo = release() - os.remove(os.path.join(repo, "source_mirror", missing)) + os.remove(os.path.join(repo, "output/source_mirror", missing)) assert prepublish.run("v0.37", repo) is False assert missing in capsys.readouterr().out @@ -265,7 +265,7 @@ def test_a_same_size_corrupt_mirror_asset_fails_on_the_hash(release, capsys): kabul ediyordu ve boyut kisayolundan gecilse bile YESIL kalirdi. """ repo = release() - target = os.path.join(repo, "source_mirror", "ffmpeg-source.tar.xz") + target = os.path.join(repo, "output/source_mirror", "ffmpeg-source.tar.xz") original = open(target, "rb").read() flipped = bytes([original[0] ^ 0xFF]) + original[1:] open(target, "wb").write(flipped) @@ -290,7 +290,7 @@ def test_a_signature_with_non_ascii_bytes_is_fail_closed(release, capsys): surum burada traceback ile duserdi. """ repo = release() - sig = os.path.join(repo, "installer_output", + sig = os.path.join(repo, "output/installer", "MLCPlayer_Setup_v0.37.exe.sig") with open(sig, "wb") as handle: handle.write(b"\xff\xfe bozuk bayt \x9e") @@ -306,7 +306,7 @@ def test_a_signature_with_non_ascii_bytes_is_fail_closed(release, capsys): def test_a_signature_with_non_ascii_bytes_exits_one(release): """Ayni durum giris noktasinda exit 1 uretir.""" repo = release() - sig = os.path.join(repo, "installer_output", + sig = os.path.join(repo, "output/installer", "MLCPlayer_InternetVideo_v0.37.exe.sig") with open(sig, "wb") as handle: handle.write(b"\xc3\x28 gecersiz") @@ -361,7 +361,7 @@ def test_a_missing_source_contract_is_fail_closed(release, capsys): def test_a_wrong_size_mirror_asset_fails(release, capsys): repo = release() - target = os.path.join(repo, "source_mirror", "ffmpeg-source.tar.xz") + target = os.path.join(repo, "output/source_mirror", "ffmpeg-source.tar.xz") open(target, "ab").write(b"fazladan") assert prepublish.run("v0.37", repo) is False @@ -372,7 +372,7 @@ def test_a_wrong_size_mirror_asset_fails(release, capsys): def test_a_corrupt_signature_is_rejected(release, capsys): repo = release() - sig = os.path.join(repo, "installer_output", + sig = os.path.join(repo, "output/installer", "MLCPlayer_Setup_v0.37.exe.sig") open(sig, "w", encoding="ascii").write("Ym96dWsgaW16YQ==") @@ -390,7 +390,7 @@ def test_a_signature_for_a_different_exe_is_rejected(release, signing_key, repo = release() other_digest = sha256_of(b"tamamen baska bir kurulum") signature = signing_key.sign(other_digest.encode("ascii")) - sig = os.path.join(repo, "installer_output", + sig = os.path.join(repo, "output/installer", "MLCPlayer_Setup_v0.37.exe.sig") open(sig, "w", encoding="ascii").write( base64.b64encode(signature).decode()) @@ -402,7 +402,7 @@ def test_a_signature_for_a_different_exe_is_rejected(release, signing_key, def test_a_modified_exe_invalidates_its_signature(release): """EXE degisirse imza artik tutmaz.""" repo = release() - exe = os.path.join(repo, "installer_output", "MLCPlayer_Setup_v0.37.exe") + exe = os.path.join(repo, "output/installer", "MLCPlayer_Setup_v0.37.exe") open(exe, "ab").write(b"sonradan eklendi") assert prepublish.run("v0.37", repo) is False @@ -486,7 +486,7 @@ def test_main_returns_zero_when_everything_is_in_place(release): def test_main_returns_one_on_any_failure(release): repo = release() - os.remove(os.path.join(repo, "installer_output", + os.remove(os.path.join(repo, "output/installer", "MLCPlayer_Setup_v0.37.exe.sig")) assert prepublish.main(["--tag", "v0.37"], repo) == 1 diff --git a/tests/test_quality_evolution_plan_regressions.py b/tests/test_quality_evolution_plan_regressions.py index 2e780d1..b9cc130 100644 --- a/tests/test_quality_evolution_plan_regressions.py +++ b/tests/test_quality_evolution_plan_regressions.py @@ -10,12 +10,12 @@ ROOT = Path(__file__).resolve().parent.parent -AGENTS = ROOT / "AGENTS.md" +AGENTS = ROOT / "docs/process/AGENTS.md" CONTINUITY = ROOT / "docs" / "CONTINUITY.md" -QUALITY_PLAN = ROOT / "docs" / "QUALITY_EVOLUTION_PLAN.md" -ARCHITECTURE = ROOT / "docs" / "ARCHITECTURE_INVENTORY.md" -ARCHITECTURE_DATA = ROOT / "docs" / "ARCHITECTURE_INVENTORY.json" -WINDOWS_ACCEPTANCE = ROOT / "docs" / "WINDOWS_ACCEPTANCE_MATRIX.md" +QUALITY_PLAN = ROOT / "docs" / "quality" / "QUALITY_EVOLUTION_PLAN.md" +ARCHITECTURE = ROOT / "docs" / "quality" / "ARCHITECTURE_INVENTORY.md" +ARCHITECTURE_DATA = ROOT / "docs" / "quality" / "ARCHITECTURE_INVENTORY.json" +WINDOWS_ACCEPTANCE = ROOT / "docs" / "quality" / "WINDOWS_ACCEPTANCE_MATRIX.md" def read(path): @@ -32,10 +32,10 @@ def test_quality_documents_are_wired_into_the_current_handoff(): continuity = read(CONTINUITY) plan = read(QUALITY_PLAN) for path in ( - "docs/QUALITY_EVOLUTION_PLAN.md", - "docs/ARCHITECTURE_INVENTORY.md", - "docs/ARCHITECTURE_INVENTORY.json", - "docs/WINDOWS_ACCEPTANCE_MATRIX.md"): + "docs/quality/QUALITY_EVOLUTION_PLAN.md", + "docs/quality/ARCHITECTURE_INVENTORY.md", + "docs/quality/ARCHITECTURE_INVENTORY.json", + "docs/quality/WINDOWS_ACCEPTANCE_MATRIX.md"): assert path in agents assert path in continuity assert "ARCHITECTURE_INVENTORY.md" in plan diff --git a/tests/test_readme_user_guidance_regressions.py b/tests/test_readme_user_guidance_regressions.py index c7b3149..61e9d5f 100644 --- a/tests/test_readme_user_guidance_regressions.py +++ b/tests/test_readme_user_guidance_regressions.py @@ -12,8 +12,8 @@ ROOT = Path(__file__).resolve().parent.parent -EN = (ROOT / "README.md").read_text(encoding="utf-8") -TR = (ROOT / "README.tr.md").read_text(encoding="utf-8") +EN = (ROOT / ".github" / "README.md").read_text(encoding="utf-8") +TR = (ROOT / ".github" / "README.tr.md").read_text(encoding="utf-8") def test_both_readmes_explain_the_two_installer_contract(): @@ -77,8 +77,10 @@ def test_both_readmes_have_a_user_quick_start_and_data_retention_note(): assert "log management" in EN.lower() assert "günlük yönetimi" in TR.lower() - assert "keeps your settings and logs" in EN.lower() - assert "ayarlarınızı ve günlüklerinizi korur" in TR.lower() + assert "by default, uninstalling either package keeps your settings and logs" in EN.lower() + assert "varsayılan olarak paketlerden birini kaldırmak" in TR.lower() + assert "separate unchecked choices" in EN.lower() + assert "varsayılan boş seçenekler" in TR.lower() assert "%APPDATA%\\MLCPlayer\\logs" in EN assert "%APPDATA%\\MLCPlayer\\logs" in TR diff --git a/tests/test_release_artifact_selection_regressions.py b/tests/test_release_artifact_selection_regressions.py index 1921601..30f27f9 100644 --- a/tests/test_release_artifact_selection_regressions.py +++ b/tests/test_release_artifact_selection_regressions.py @@ -3,11 +3,11 @@ """Release zinciri YALNIZ MEVCUT SURUMUN artifact'ini secmeli. KANITLANMIS RISK (olculdu, 17 Agustos 2026): -`installer_output` gecmisteki BIRDEN COK kurulum dosyasini yan yana +`output/installer` gecmisteki BIRDEN COK kurulum dosyasini yan yana tutar; klasor birikimlidir ve eski yayinlar silinmez. Betik imzalanacak ve final dogrulamasi yapilacak dosyayi jokerle seciyordu: - for %%F in ("installer_output\\MLCPlayer_Setup_*.exe") do set "SETUP=..." + for %%F in ("output/installer\\MLCPlayer_Setup_*.exe") do set "SETUP=..." `for` eslesenleri sirayla gezer ve SON eslesme kazanir; sira dosya sistemine baglidir, garanti degildir. Gercek cmd ile olculdu: o an en @@ -71,11 +71,11 @@ def test_no_artifact_wildcard_is_used(commands, forbidden): def test_no_installer_output_exe_wildcard(commands): - """`installer_output\\*.exe` bicimi de yasak.""" - pattern = re.compile(r"installer_output\\\*", re.IGNORECASE) + """`output/installer\\*.exe` bicimi de yasak.""" + pattern = re.compile(r"output\\installer\\\*", re.IGNORECASE) guilty = [line for line in commands if pattern.search(line)] - assert guilty == [], f"installer_output joker kullanimi: {guilty}" + assert guilty == [], f"output/installer joker kullanimi: {guilty}" def test_no_wildcard_exe_anywhere_in_commands(commands): @@ -94,8 +94,8 @@ def test_no_wildcard_exe_anywhere_in_commands(commands): def test_old_versions_are_never_deleted_in_bulk(commands): """Eski surum artifact'leri KORUNUR; toplu silme yok.""" bulk = re.compile( - r"\b(del|erase)\b.*\*|rmdir\s+/s.*installer_output|" - r"\bdel\b.*installer_output\\?\s*$", + r"\b(del|erase)\b.*\*|rmdir\s+/s.*output\\installer|" + r"\bdel\b.*output\\installer\\?\s*$", re.IGNORECASE) guilty = [line for line in commands if bulk.search(line)] @@ -103,11 +103,11 @@ def test_old_versions_are_never_deleted_in_bulk(commands): def test_installer_output_folder_is_never_removed(commands): - """`installer_output` klasoru silinmez.""" + """`output/installer` klasoru silinmez.""" guilty = [line for line in commands - if re.search(r"rmdir.*installer_output", line, re.IGNORECASE)] + if re.search(r"rmdir.*output\\installer", line, re.IGNORECASE)] - assert guilty == [], f"installer_output siliniyor: {guilty}" + assert guilty == [], f"output/installer siliniyor: {guilty}" # --- 3. Surum TEK KEZ okunur ------------------------------------------ @@ -140,8 +140,8 @@ def test_exact_artifact_paths_are_defined(commands): assert len(main) == 1, f"MAIN_SETUP tanimi: {main}" assert len(addon) == 1, f"ADDON_SETUP tanimi: {addon}" - assert "installer_output\\MLCPlayer_Setup_" in main[0] - assert "installer_output\\MLCPlayer_InternetVideo_" in addon[0] + assert "output\\installer\\MLCPlayer_Setup_" in main[0] + assert "output\\installer\\MLCPlayer_InternetVideo_" in addon[0] # Surum DEGISKENDEN gelir; sabit yazilmaz. assert "APP_VER" in main[0] assert "APP_VER" in addon[0] @@ -210,7 +210,7 @@ def test_the_chain_stops_when_a_stale_output_cannot_be_removed(script): # --- 5b. build/dist temizligi DOGRULANMALI ---------------------------- -@pytest.mark.parametrize("folder", ["build", "dist"]) +@pytest.mark.parametrize("folder", ["output\\build", "output\\dist"]) def test_the_chain_stops_when_a_stale_tree_cannot_be_removed(script, folder): """`rmdir` sessizce basarisiz olabilir; sonuc DENETLENMELI. @@ -221,7 +221,7 @@ def test_the_chain_stops_when_a_stale_tree_cannot_be_removed(script, folder): """ section = script[script.find("STEP 2"):script.find("STEP 3")] guard = re.search( - rf'if exist "{folder}"[\s\S]{{0,200}}?goto :fail', section) + rf'if exist "{re.escape(folder)}"[\s\S]{{0,200}}?goto :fail', section) assert guard, f"`{folder}` silinemediginde zincir durmuyor" @@ -232,9 +232,9 @@ def test_the_success_message_comes_after_both_guards(script): success = section.find("OK build") assert success != -1, "temizlik basari mesaji bulunamadi" - for folder in ("build", "dist"): + for folder in ("output\\build", "output\\dist"): guard = re.search( - rf'if exist "{folder}"[\s\S]{{0,200}}?goto :fail', section) + rf'if exist "{re.escape(folder)}"[\s\S]{{0,200}}?goto :fail', section) assert guard, f"{folder} korumasi yok" assert guard.end() < success, ( f"basari mesaji {folder} denetiminden ONCE geliyor") @@ -248,18 +248,18 @@ def test_only_build_and_dist_trees_are_removed(commands): assert removals, "build/dist temizligi kayboldu" for line in removals: assert "*" not in line, f"rmdir jokeri: {line}" - assert ('"build"' in line or '"dist"' in line), ( + assert ('"output\\build"' in line or '"output\\dist"' in line), ( f"beklenmeyen rmdir hedefi: {line}") def test_installer_output_cleanup_is_unchanged(script): - """`installer_output` davranisi bu turda DEGISMEDI.""" + """`output/installer` davranisi bu turda DEGISMEDI.""" section = script[script.find("STEP 2"):script.find("STEP 3")] assert 'for %%T in ("!MAIN_SETUP!"' in section, ( "kesin dort hedef temizligi kayboldu") assert "rmdir" not in section.split('for %%T in')[1], ( - "installer_output agaci siliniyor") + "output/installer agaci siliniyor") # --- 6. Her Inno adimindan sonra KESIN EXE denetimi ------------------- @@ -326,8 +326,8 @@ def test_both_installers_write_to_installer_output(commands): """Kesin yollar ISS'in OutputDir'i ile ayni klasoru gosterir.""" for path in (MAIN_ISS, ADDON_ISS): with open(path, encoding="utf-8") as handle: - assert "OutputDir=..\\installer_output" in handle.read(), path + assert "OutputDir=..\\output\\installer" in handle.read(), path for prefix in ('set "MAIN_SETUP=', 'set "ADDON_SETUP='): line = next(item for item in commands if item.startswith(prefix)) - assert "installer_output\\" in line, line + assert "output\\installer\\" in line, line diff --git a/tests/test_release_documentation_regressions.py b/tests/test_release_documentation_regressions.py index f855db8..8fa40df 100644 --- a/tests/test_release_documentation_regressions.py +++ b/tests/test_release_documentation_regressions.py @@ -3,12 +3,12 @@ """Yayin sureci TEK RESMI BELGEDE tutulur. OLCULEN SORUN (17 Agustos 2026): kesin yayin sirasi UC yerde birden -yaziliydi -- `CLAUDE.md`, `docs/PACKAGING_PLAN.md` ve +yaziliydi -- `.claude/CLAUDE.md`, `docs/release/PACKAGING_PLAN.md` ve `packaging/prepublish.py` docstring'i. Uc kopya elle esit tutuluyordu; birini degistiren digerlerini unutabilirdi ve hangisinin dogru oldugu belirsiz kalirdi. -SOZLESME: ayrintili a-j sirasi YALNIZ `docs/RELEASE_PROCESS.md` +SOZLESME: ayrintili a-j sirasi YALNIZ `docs/release/RELEASE_PROCESS.md` icindedir. Digerleri kritik degismezleri OZETLER ve ona BAGLANIR. Bu dosya belgeleri OLCER; urun davranisini olcmez. @@ -20,15 +20,15 @@ ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -RELEASE_DOC = os.path.join(ROOT, "docs", "RELEASE_PROCESS.md") -AUDIT_DOC = os.path.join(ROOT, "docs", "ENGINEERING_AUDIT.md") -ROADMAP_DOC = os.path.join(ROOT, "docs", "ROADMAP.md") -PROJECT_STATUS_DOC = os.path.join(ROOT, "docs", "PROJECT_STATUS.md") -CLAUDE_DOC = os.path.join(ROOT, "CLAUDE.md") -PACKAGING_DOC = os.path.join(ROOT, "docs", "PACKAGING_PLAN.md") +RELEASE_DOC = os.path.join(ROOT, "docs", "release", "RELEASE_PROCESS.md") +AUDIT_DOC = os.path.join(ROOT, "docs", "history", "ENGINEERING_AUDIT.md") +ROADMAP_DOC = os.path.join(ROOT, "docs", "history", "ROADMAP.md") +PROJECT_STATUS_DOC = os.path.join(ROOT, "docs", "history", "PROJECT_STATUS.md") +CLAUDE_DOC = os.path.join(ROOT, ".claude", "CLAUDE.md") +PACKAGING_DOC = os.path.join(ROOT, "docs", "release", "PACKAGING_PLAN.md") PREPUBLISH = os.path.join(ROOT, "packaging", "prepublish.py") -README_EN = os.path.join(ROOT, "README.md") -README_TR = os.path.join(ROOT, "README.tr.md") +README_EN = os.path.join(ROOT, ".github", "README.md") +README_TR = os.path.join(ROOT, ".github", "README.tr.md") #: Ayrintili sira isareti: satir basinda `a)` ... `j)`. STEP_MARKER = re.compile(r"^\s*[a-j]\)", re.MULTILINE) @@ -63,7 +63,7 @@ def read(path): # --- 1. Resmi kaynak var ve kendini oyle tanimliyor -------------------- def test_the_release_process_document_exists(): - assert os.path.isfile(RELEASE_DOC), "docs/RELEASE_PROCESS.md yok" + assert os.path.isfile(RELEASE_DOC), "docs/release/RELEASE_PROCESS.md yok" def test_it_declares_itself_the_single_official_source(): @@ -232,7 +232,7 @@ def test_the_dynamic_asset_contract_is_recorded(): assert "dinamik varlik sozlesmesi" in packaging_contract assert "sekiz varlik sozlesmesi" not in packaging_contract for name in ("MLCPlayer_Setup", "MLCPlayer_InternetVideo", - ".sig", "source_mirror"): + ".sig", "output/source_mirror"): assert name.lower() in text, f"varlik sozlesmesinde eksik: {name}" @@ -295,17 +295,17 @@ def test_historical_tags_are_declared_untouched(): # --- 6b. Release komutu GERCEKTEN calistirilabilir olmali ------------- LEGACY_NOT_SOURCE_PATHS = ( - "source_mirror/mpv-dev-x86_64-20260814-git-7b8915bc1d.7z", - "source_mirror/yt-dlp.exe", - "source_mirror/deno-x86_64-pc-windows-msvc.zip", - "source_mirror/yt-dlp-THIRD_PARTY_LICENSES.txt", + "output/source_mirror/mpv-dev-x86_64-20260814-git-7b8915bc1d.7z", + "output/source_mirror/yt-dlp.exe", + "output/source_mirror/deno-x86_64-pc-windows-msvc.zip", + "output/source_mirror/yt-dlp-THIRD_PARTY_LICENSES.txt", ) INSTALLER_PATHS = ( - "installer_output/MLCPlayer_Setup_vX.Y.exe", - "installer_output/MLCPlayer_Setup_vX.Y.exe.sig", - "installer_output/MLCPlayer_InternetVideo_vX.Y.exe", - "installer_output/MLCPlayer_InternetVideo_vX.Y.exe.sig", + "output/installer/MLCPlayer_Setup_vX.Y.exe", + "output/installer/MLCPlayer_Setup_vX.Y.exe.sig", + "output/installer/MLCPlayer_InternetVideo_vX.Y.exe", + "output/installer/MLCPlayer_InternetVideo_vX.Y.exe.sig", ) @@ -317,7 +317,7 @@ def test_the_release_example_lists_all_installer_paths(path): def test_source_paths_are_derived_and_legacy_binaries_are_not_sources(): text = read(RELEASE_DOC) assert "fetch_sources.plan()" in text - assert "source_mirror/" in text + assert "output/source_mirror/" in text for path in LEGACY_NOT_SOURCE_PATHS: assert path not in text, f"binary yeniden kaynak sayildi: {path}" @@ -338,7 +338,7 @@ def test_the_release_command_uses_no_backslash_continuation(): text = read(RELEASE_DOC) guilty = [line for line in text.splitlines() if line.rstrip().endswith("\\") and "gh release" not in line - and ("installer_output" in line or "source_mirror" in line + and ("output/installer" in line or "output/source_mirror" in line or "--" in line)] assert guilty == [], f"ters bolu satir devami kullanilmis: {guilty}" @@ -457,7 +457,7 @@ def test_the_no_network_behaviour_is_split_by_phase(): # --- 7. Denetim kaydi ------------------------------------------------- def test_the_audit_document_exists(): - assert os.path.isfile(AUDIT_DOC), "docs/ENGINEERING_AUDIT.md yok" + assert os.path.isfile(AUDIT_DOC), "docs/history/ENGINEERING_AUDIT.md yok" @pytest.mark.parametrize("record", ["REL-001", "REL-002", "REL-003", @@ -683,7 +683,7 @@ def test_the_cover_art_fatal_exception_is_recorded_as_open_risk(): # --- 8. Yol haritasi -------------------------------------------------- def test_the_roadmap_exists(): - assert os.path.isfile(ROADMAP_DOC), "docs/ROADMAP.md yok" + assert os.path.isfile(ROADMAP_DOC), "docs/history/ROADMAP.md yok" @pytest.mark.parametrize("section", [ @@ -978,6 +978,8 @@ def test_script_ablation_phase_records_the_latest_deterministic_result(): assert "583BB3D" in flattened, label +# Tarihsel kayıtlar dosyaları O GÜNKÜ yollarıyla anar; arşiv dondurulduğu için +# beklenen değerler eski `docs/` yollarıdır (dosyalar şimdi docs/history/). @pytest.mark.parametrize("path", [ "docs/ENGINEERING_AUDIT.md", "docs/PROJECT_STATUS.md", diff --git a/tests/test_release_signature_regressions.py b/tests/test_release_signature_regressions.py index bae160a..65ffafb 100644 --- a/tests/test_release_signature_regressions.py +++ b/tests/test_release_signature_regressions.py @@ -122,8 +122,8 @@ def test_the_build_chain_signs_the_installer(): def test_the_signing_rules_are_written_where_they_are_read(): - """Devralan kişi/asistan kuralı görmeli: CLAUDE.md her oturumda okunur.""" - rules = (_root() / "CLAUDE.md").read_text(encoding="utf-8") + """Devralan kişi/asistan kuralı görmeli: .claude/CLAUDE.md her oturumda okunur.""" + rules = (_root() / ".claude/CLAUDE.md").read_text(encoding="utf-8") for expected in ("sign_release.py", ".sig", "RELEASE_PUBLIC_KEY", "MLC_SIGNING_KEY"): assert expected in rules, f"kural eksik: {expected}" diff --git a/tests/test_repository_privacy_regressions.py b/tests/test_repository_privacy_regressions.py new file mode 100644 index 0000000..77ac2b8 --- /dev/null +++ b/tests/test_repository_privacy_regressions.py @@ -0,0 +1,78 @@ +# SPDX-FileCopyrightText: 2026 MLC Player contributors +# SPDX-License-Identifier: GPL-3.0-only +"""İzlenen dosyalar geliştiricinin gerçek profil yolunu taşımaz. + +Ölçülen kusur (3 Ekim 2026): bir test çıktısı olan `second_launch.py`, +makinenin mutlak profil yolunu taşıyarak 16 Ağustos 2026'da commit edilmiş +ve herkese açık `master`a ulaşmıştı; depo geçmişi daha önce tam da bu ad +için yeniden yazılmıştı. Test bir ad SABİTLEMEZ (sabitlemek adı yayımlardı): +çalıştığı makinenin profil yolunu çalışma anında okur. + +`docs/VERIFICATION_LEDGER.json` append-only olduğu için eski kayıtlar kapsam +dışıdır; o yollar kullanıcı kararıyla yerinde bırakıldı. Ledger için yalnız +HEAD sonrası eklenen kayıtlar denetlenir. +""" +import json +import os +import subprocess +from pathlib import Path + +import pytest + +ROOT = Path(__file__).resolve().parents[1] +LEDGER = "docs/VERIFICATION_LEDGER.json" +_GENERIC_PROFILES = {"runneradmin", "user", "public", "default", "administrator"} + + +def _profile_markers(): + profile = os.environ.get("USERPROFILE") or os.path.expanduser("~") + name = Path(profile).name + if not name or name.lower() in _GENERIC_PROFILES: + return [] + slash, back = "/", "\\" + return [ + f"{back}users{back}{name}".lower(), + f"{slash}users{slash}{name}".lower(), + f"{back * 2}users{back * 2}{name}".lower(), + ] + + +def _tracked(): + out = subprocess.run(["git", "ls-files"], cwd=ROOT, capture_output=True, + encoding="utf-8", check=True).stdout + return [line for line in out.splitlines() if line] + + +def test_tracked_text_files_do_not_contain_the_local_profile_path(): + markers = _profile_markers() + if not markers: + pytest.skip("generic profile; nothing personal to look for") + offenders = [] + for relative in _tracked(): + if relative == LEDGER: + continue + try: + text = (ROOT / relative).read_bytes().decode("utf-8").lower() + except (OSError, UnicodeDecodeError): + continue + if any(marker in text for marker in markers): + offenders.append(relative) + assert offenders == [] + + +def test_new_ledger_entries_do_not_contain_the_local_profile_path(): + markers = _profile_markers() + if not markers: + pytest.skip("generic profile; nothing personal to look for") + committed = subprocess.run( + ["git", "show", f"HEAD:{LEDGER}"], cwd=ROOT, capture_output=True, + encoding="utf-8") + if committed.returncode != 0: + pytest.skip("no committed ledger to compare against") + old_ids = {entry["id"] for entry in json.loads(committed.stdout)["entries"]} + current = json.loads((ROOT / LEDGER).read_text(encoding="utf-8")) + for entry in current["entries"]: + if entry["id"] in old_ids: + continue + text = json.dumps(entry, ensure_ascii=False).lower() + assert not any(marker in text for marker in markers), entry["id"] diff --git a/tests/test_runtime_binaries_regressions.py b/tests/test_runtime_binaries_regressions.py index 27d02f6..63beea5 100644 --- a/tests/test_runtime_binaries_regressions.py +++ b/tests/test_runtime_binaries_regressions.py @@ -17,8 +17,8 @@ import app.runtime_binaries as runtime PROJECT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -SPEC = os.path.join(PROJECT, "MLCPlayer.spec") -REQUIREMENTS = os.path.join(PROJECT, "requirements.txt") +SPEC = os.path.join(PROJECT, "packaging/MLCPlayer.spec") +REQUIREMENTS = os.path.join(PROJECT, "requirements/runtime.txt") MANIFEST = os.path.join(PROJECT, "bin", "RUNTIME_MANIFEST.txt") TRICKY_DIR = "Program Files (x86)\\MLC Player Türkçe Sürüm" @@ -161,7 +161,7 @@ def test_the_runtime_module_never_shells_out_or_self_updates(): # ===================================================================== def test_the_process_path_gains_the_bin_dir_only_once(monkeypatch, bundle): - import main + from app import main bin_dir = bundle("yt-dlp.exe", "deno.exe") monkeypatch.setenv("PATH", "C:\\Windows\\system32") @@ -175,7 +175,7 @@ def test_the_process_path_gains_the_bin_dir_only_once(monkeypatch, bundle): def test_no_permanent_environment_write_exists(): - source = read(os.path.join(PROJECT, "main.py")) + source = read(os.path.join(PROJECT, "app/main.py")) for forbidden in ("winreg", "setx", "SetEnvironmentVariable", "HKEY_CURRENT_USER", "HKEY_LOCAL_MACHINE"): @@ -187,7 +187,7 @@ def test_no_permanent_environment_write_exists(): # ===================================================================== def test_a_missing_runtime_never_blocks_local_playback(): - source = read(os.path.join(PROJECT, "main.py")) + source = read(os.path.join(PROJECT, "app/main.py")) start = source.index("def check_dependencies") block = source[start:source.index("\nif __name__", start)] @@ -289,7 +289,7 @@ def test_the_manifest_records_version_url_size_and_hash(): def test_the_youtube_403_runtime_repair_is_recorded_without_install_claims(): - status = read(os.path.join(PROJECT, "docs", "PROJECT_STATUS.md")) + status = read(os.path.join(PROJECT, "docs", "history", "PROJECT_STATUS.md")) for evidence in ( "yt-dlp `2026.08.19`", "player_client=web_safari", @@ -593,7 +593,7 @@ def test_the_manifest_records_the_third_party_license(): def test_the_plan_states_the_combined_executable_licence_correctly(): - plan = read(os.path.join(PROJECT, "docs", "PACKAGING_PLAN.md")) + plan = read(os.path.join(PROJECT, "docs", "release", "PACKAGING_PLAN.md")) assert "GPL-3.0 DEGILDIR" not in plan and "GPL-3.0 değildir" not in plan assert "GPLv3+" in plan @@ -612,7 +612,7 @@ def test_the_spec_pins_the_contents_directory(): @pytest.mark.parametrize("relative", ( - os.path.join("docs", "PACKAGING_PLAN.md"), + os.path.join("docs", "release", "PACKAGING_PLAN.md"), os.path.join("bin", "RUNTIME_MANIFEST.txt"), os.path.join("bin", "SHA256SUMS.txt"), )) @@ -626,7 +626,7 @@ def test_no_control_characters_in_the_tracked_texts(relative): def test_the_plan_shows_the_runtime_paths_as_plain_text(): - plan = read(os.path.join(PROJECT, "docs", "PACKAGING_PLAN.md")) + plan = read(os.path.join(PROJECT, "docs", "release", "PACKAGING_PLAN.md")) for name in ("mpv-2.dll", "yt-dlp.exe", "deno.exe"): assert f"_internal\\bin\\{name}" in plan, f"duz metin yol yok: {name}" diff --git a/tests/test_safe_console_regressions.py b/tests/test_safe_console_regressions.py index 50808a4..57a1cad 100644 --- a/tests/test_safe_console_regressions.py +++ b/tests/test_safe_console_regressions.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-only """Üretim konsol çıktısının TEK güvenli sınırı: `safe_console()`. -Envanter (AST): `main.py` + `app/**/*.py` içinde 74 doğrudan `print()` +Envanter (AST): `app/main.py` + `app/**/*.py` içinde 74 doğrudan `print()` vardı; 54'ü ham dosya/klasör yolu, URL veya ham `str(exception)` taşıyabiliyordu. Dosya logu yazma sınırında maskeleniyordu ama konsol çıkışı bu sınırı ATLIYORDU. @@ -164,7 +164,7 @@ def explode(*args, **kwargs): # ===================================================================== def test_main_dependency_check_masks_the_bin_directory(capsys, monkeypatch): - import main + from app import main monkeypatch.setattr(main.os.path, "exists", lambda path: False) monkeypatch.setattr(main.os, "getcwd", @@ -175,7 +175,7 @@ def test_main_dependency_check_masks_the_bin_directory(capsys, monkeypatch): def test_main_dll_error_path_is_masked(capsys, monkeypatch): - import main + from app import main def explode(_path): raise OSError(f"yuklenemedi: {WIN_PATH}") @@ -207,7 +207,7 @@ def test_media_controls_url_path_is_masked(capsys): assert hasattr(media_controls, "safe_console") -@pytest.mark.parametrize("module_name", ["main", "app.media_controls", +@pytest.mark.parametrize("module_name", ["app.main", "app.media_controls", "app.menu_actions", "app.player", "app.video_frame", "app.errors"]) def test_every_production_module_uses_the_central_boundary(module_name): @@ -305,7 +305,7 @@ def test_menu_and_video_frame_error_paths_are_masked(capsys): # ===================================================================== def production_files(): - yield os.path.join(ROOT, "main.py") + yield os.path.join(ROOT, "app/main.py") for base, _dirs, names in os.walk(os.path.join(ROOT, "app")): if "__pycache__" in base: continue diff --git a/tests/test_saved_fullscreen_geometry_regressions.py b/tests/test_saved_fullscreen_geometry_regressions.py new file mode 100644 index 0000000..8677fc6 --- /dev/null +++ b/tests/test_saved_fullscreen_geometry_regressions.py @@ -0,0 +1,64 @@ +# SPDX-FileCopyrightText: 2026 MLC Player contributors +# SPDX-License-Identifier: GPL-3.0-only +"""Tam ekranda kapatılan pencere sonraki açılışta tam ekran başlamamalı. + +Qt `saveGeometry()` tam ekran bayrağını da kaydeder ve `restoreGeometry()` +onu geri getirir. Ürünün tam ekran durumu (`is_video_fullscreen`) ise +kalıcı değildir; pencere yerel olarak tam ekran açılır ama ürün bunu +bilmez: başlık/kontroller pencere modunda kalır ve Esc çıkaramaz. +""" + +from PyQt6.QtCore import Qt +from PyQt6.QtWidgets import QApplication, QMainWindow + +from app.player import restore_saved_window_geometry + + +def _saved_blob(app, fullscreen, maximized=False): + window = QMainWindow() + window.resize(800, 450) + window.show() + app.processEvents() + if maximized: + window.showMaximized() + if fullscreen: + window.showFullScreen() + app.processEvents() + blob = window.saveGeometry() + window.close() + return blob + + +def test_fullscreen_state_is_not_restored_but_size_is(): + app = QApplication.instance() or QApplication([]) + window = QMainWindow() + try: + assert restore_saved_window_geometry(window, _saved_blob(app, True)) + assert not window.windowState() & Qt.WindowState.WindowFullScreen + window.show() + app.processEvents() + assert not window.isFullScreen() + assert abs(window.width() - 800) <= 4 and abs(window.height() - 450) <= 4 + finally: + window.close() + + +def test_maximized_state_is_still_restored(): + app = QApplication.instance() or QApplication([]) + window = QMainWindow() + try: + restore_saved_window_geometry(window, _saved_blob(app, False, True)) + assert window.windowState() & Qt.WindowState.WindowMaximized + finally: + window.close() + + +def test_missing_geometry_is_a_no_op(): + app = QApplication.instance() or QApplication([]) + window = QMainWindow() + try: + assert restore_saved_window_geometry(window, None) is False + assert window.windowState() == Qt.WindowState.WindowNoState + finally: + window.close() + app.processEvents() diff --git a/tests/test_signpath_hosted_unsigned_build_workflow_regressions.py b/tests/test_signpath_hosted_unsigned_build_workflow_regressions.py index cb6f557..8c7fe0f 100644 --- a/tests/test_signpath_hosted_unsigned_build_workflow_regressions.py +++ b/tests/test_signpath_hosted_unsigned_build_workflow_regressions.py @@ -8,7 +8,7 @@ ROOT = Path(__file__).resolve().parents[1] WORKFLOW = ROOT / ".github" / "workflows" / "build-unsigned-main.yml" CHAIN = ROOT / "packaging" / "build_unsigned_main.bat" -HASH_LOCK = ROOT / "requirements-build-windows.txt" +HASH_LOCK = ROOT / "requirements/build-windows.txt" RUNTIME_LOCK = ROOT / "packaging" / "libmpv_runtime_lock.json" GIT_ATTRIBUTES = ROOT / ".gitattributes" @@ -41,10 +41,10 @@ def test_hosted_unsigned_build_pins_actions_python_inno_and_wheels(): assert "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a" in text assert "python-version: '3.13.15'" in text assert "6.7.1" in text - assert "requirements-build-windows.txt" in text + assert "requirements/build-windows.txt" in text assert "--require-hashes" in text assert "--only-binary=:all:" in text - assert "verify_dependencies.py requirements-lock.txt" in text + assert "verify_dependencies.py requirements/lock.txt" in text def test_hosted_unsigned_build_reads_inno_engine_version_from_the_compiler(): @@ -134,7 +134,7 @@ def test_unsigned_main_chain_is_isolated_from_release_signing_and_addon(): def test_windows_build_hash_lock_matches_every_exact_dependency_pin(): source_pins = {} - for raw in (ROOT / "requirements-lock.txt").read_text(encoding="utf-8").splitlines(): + for raw in (ROOT / "requirements/lock.txt").read_text(encoding="utf-8").splitlines(): line = raw.strip() if line and not line.startswith("#"): name, version = line.split("==", 1) diff --git a/tests/test_signpath_readiness_regressions.py b/tests/test_signpath_readiness_regressions.py index 7d29c37..a249c8d 100644 --- a/tests/test_signpath_readiness_regressions.py +++ b/tests/test_signpath_readiness_regressions.py @@ -1,9 +1,13 @@ # SPDX-FileCopyrightText: 2026 MLC Player contributors # SPDX-License-Identifier: GPL-3.0-only +import hashlib from pathlib import Path ROOT = Path(__file__).resolve().parents[1] +# Bakıcının başvuruda gizli tutulan adının SHA-256 özeti (küçük harf). +_PRIVATE_NAME_SHA256 = ( + "03c89ce8d8791b5dbd38378749b2fa9db008ca4dc09d3ab0f339815fe3d8edff") def _read(relative_path): @@ -11,8 +15,8 @@ def _read(relative_path): def test_home_pages_link_the_code_signing_and_privacy_policies(): - readme = _read("README.md") - readme_tr = _read("README.tr.md") + readme = _read(".github/README.md") + readme_tr = _read(".github/README.tr.md") for text in (readme, readme_tr): assert "CODE_SIGNING_POLICY.md" in text @@ -23,7 +27,7 @@ def test_home_pages_link_the_code_signing_and_privacy_policies(): def test_code_signing_policy_is_truthful_about_current_status_and_roles(): - policy = _read("CODE_SIGNING_POLICY.md") + policy = _read("docs/policies/CODE_SIGNING_POLICY.md") policy_flat = " ".join(policy.split()) assert "Free code signing provided by SignPath.io" in policy @@ -39,7 +43,7 @@ def test_code_signing_policy_is_truthful_about_current_status_and_roles(): def test_privacy_policy_discloses_every_current_network_category(): - policy = _read("PRIVACY.md") + policy = _read("docs/policies/PRIVACY.md") assert "api.github.com/repos/IzzmooPro/MLCPlayer/releases/latest" in policy assert "No telemetry or analytics" in policy @@ -50,8 +54,8 @@ def test_privacy_policy_discloses_every_current_network_category(): def test_readiness_record_keeps_signpath_out_of_the_active_release_chain(): - readiness = _read("docs/SIGNPATH_READINESS.md") - release_process = _read("docs/RELEASE_PROCESS.md") + readiness = _read("docs/release/SIGNPATH.md") + release_process = _read("docs/release/RELEASE_PROCESS.md") assert "Submission status: SUBMITTED - NOT APPROVED (2 September 2026)" in readiness assert "NOT SUBMITTED" not in readiness @@ -67,7 +71,7 @@ def test_readiness_record_keeps_signpath_out_of_the_active_release_chain(): def test_signpath_foundation_application_is_submitted_but_not_accepted(): - application = _read("docs/SIGNPATH_FOUNDATION_APPLICATION.md") + application = _read("docs/release/SIGNPATH.md") application_flat = " ".join(application.split()) workflow = _read(".github/workflows/build-unsigned-main.yml") @@ -91,7 +95,10 @@ def test_signpath_foundation_application_is_submitted_but_not_accepted(): assert "provided privately in the submitted form" in application assert "[TO BE PROVIDED AT SUBMISSION]" not in application assert "gmail.com" not in application.lower() - assert "Murat" not in application + # Kişisel ad açık metinle aranmaz; aramanın kendisi adı yayımlardı. + words = {word.strip(".,;:()[]`*").lower() for word in application.split()} + assert not any(hashlib.sha256(word.encode("utf-8")).hexdigest() == _PRIVATE_NAME_SHA256 + for word in words) assert "Only SignPath Foundation can decide eligibility" in application_flat assert "signpath/" not in workflow.lower() diff --git a/tests/test_silent_failure_logging_regressions.py b/tests/test_silent_failure_logging_regressions.py new file mode 100644 index 0000000..9811aa7 --- /dev/null +++ b/tests/test_silent_failure_logging_regressions.py @@ -0,0 +1,59 @@ +# SPDX-FileCopyrightText: 2026 MLC Player contributors +# SPDX-License-Identifier: GPL-3.0-only +"""Kullanıcıya görünür sonucu olan hataların günlüğe yazılması. + +Ölçülen kusur: aşağıdaki üç yol hatayı `except Exception: pass` ile +yutuyordu. Önceki dosyanın altyazı gecikmesi yeni dosyaya taşınabiliyor, +otomatik altyazı gizlenemiyor veya eski MLC altyazı izi kalabiliyordu; +günlükte hiçbir iz olmadığı için kullanıcı raporu teşhis edilemiyordu. +Günlüğe yalnız istisna TÜRÜ yazılır, ham mesaj yazılmaz. +""" +from types import SimpleNamespace + +from app import media_controls, subtitle_service + + +class _RaisingMPV: + def __setattr__(self, name, value): + raise RuntimeError(r"C:\Users\secret\video.mkv") + + +def _capture(monkeypatch, module): + records = [] + monkeypatch.setattr(module, "log", + lambda message, level="INFO": records.append( + (level, message)), raising=False) + return records + + +def test_subtitle_delay_reset_failure_is_logged(monkeypatch): + records = _capture(monkeypatch, media_controls) + player = SimpleNamespace(mpv_player=_RaisingMPV(), settings=None) + media_controls._reset_subtitle_timing_for_new_media(player) + assert records == [("WARNING", + "Subtitle delay reset failed: RuntimeError")] + + +def test_subtitle_hide_failure_is_logged(monkeypatch): + records = _capture(monkeypatch, media_controls) + player = SimpleNamespace(mpv_player=_RaisingMPV()) + media_controls._hide_subtitles_for_new_media(player) + assert records == [("WARNING", + "Subtitle hide for new media failed: RuntimeError")] + + +def test_previous_subtitle_track_removal_failure_is_logged(monkeypatch): + records = _capture(monkeypatch, subtitle_service) + + def remover(_sid): + raise RuntimeError("raw mpv detail") + + mpv = SimpleNamespace(sub_remove=remover, track_list=[]) + session = subtitle_service.SubtitleSession.__new__( + subtitle_service.SubtitleSession) + session._sid = 7 + session._remove_previous(mpv, "target.srt") + assert session._sid is None + assert records == [("WARNING", + "Previous subtitle track removal failed: " + "RuntimeError")] diff --git a/tests/test_single_instance_activation_state_regressions.py b/tests/test_single_instance_activation_state_regressions.py new file mode 100644 index 0000000..a0ae875 --- /dev/null +++ b/tests/test_single_instance_activation_state_regressions.py @@ -0,0 +1,43 @@ +# SPDX-FileCopyrightText: 2026 MLC Player contributors +# SPDX-License-Identifier: GPL-3.0-only +"""İkinci başlatma simge durumundaki pencereyi ÖNCEKİ durumuyla getirmeli. + +`showNormal()` büyütülmüş/tam ekran bayrağını da siler: büyütülüp simge +durumuna küçültülen oynatıcı, Explorer'dan yeni dosya açılınca normal +boyutta dönüyordu; tam ekranda ise ürün hâlâ tam ekran sanıyordu. +""" + +import pytest +from PyQt6.QtCore import Qt +from PyQt6.QtWidgets import QApplication, QMainWindow + +from app.single_instance import activate_window + + +@pytest.mark.parametrize("show, flag", [ + ("showMaximized", Qt.WindowState.WindowMaximized), + ("showFullScreen", Qt.WindowState.WindowFullScreen), +]) +def test_minimized_window_returns_to_its_previous_state(show, flag): + app = QApplication.instance() or QApplication([]) + window = QMainWindow() + opened = [] + window.open_external_target = opened.append + try: + window.resize(800, 450) + window.show() + getattr(window, show)() + app.processEvents() + window.setWindowState( + window.windowState() | Qt.WindowState.WindowMinimized) + app.processEvents() + + activate_window(window, "C:/video.mkv") + app.processEvents() + + assert not window.isMinimized() + assert window.windowState() & flag + assert opened == ["C:/video.mkv"] + finally: + window.close() + app.processEvents() diff --git a/tests/test_single_instance_regressions.py b/tests/test_single_instance_regressions.py index 7ef1e4a..5f661f9 100644 --- a/tests/test_single_instance_regressions.py +++ b/tests/test_single_instance_regressions.py @@ -21,6 +21,7 @@ from types import SimpleNamespace import pytest +from PyQt6.QtCore import Qt from PyQt6.QtNetwork import QLocalServer, QLocalSocket from PyQt6.QtWidgets import QApplication @@ -318,9 +319,9 @@ def test_stale_server_name_is_reclaimed(qt_app, name): @pytest.mark.parametrize("argv,expected", [ - (["main.py", "--thumbnail-worker", "video.mkv", "out.jpg"], True), - (["main.py", "video.mkv"], False), - (["main.py"], False), + (["app/main.py", "--thumbnail-worker", "video.mkv", "out.jpg"], True), + (["app/main.py", "video.mkv"], False), + (["app/main.py"], False), ([], False), ]) def test_thumbnail_workers_are_outside_the_guard(argv, expected): @@ -336,8 +337,16 @@ class FakeWindow: def isMinimized(self): return True - def showNormal(self): - calls.append("showNormal") + def windowState(self): + return (Qt.WindowState.WindowMinimized + | Qt.WindowState.WindowMaximized) + + def setWindowState(self, state): + # Yalnız simge durumu kalkar; büyütülmüş durum korunur. + calls.append(("state", state)) + + def show(self): + calls.append("show") def raise_(self): calls.append("raise") @@ -352,7 +361,8 @@ def setWindowFlag(self, *args): # çağrılmamalı calls.append("FLAG_DEGISTI") activate_window(FakeWindow(), r"I:\film.mkv") - assert calls == ["showNormal", "raise", "activate", ("open", r"I:\film.mkv")] + assert calls == [("state", Qt.WindowState.WindowMaximized), "show", + "raise", "activate", ("open", r"I:\film.mkv")] def test_activation_without_a_file_only_raises(): @@ -377,13 +387,13 @@ def open_external_target(self, path): def test_initial_cli_target_uses_the_same_safe_router(): """İlk süreç ile IPC aynı dosya/URL karar noktasından geçmelidir.""" - source = (ROOT / "main.py").read_text(encoding="utf-8") + source = (ROOT / "app/main.py").read_text(encoding="utf-8") assert "player.open_external_target(sys.argv[1])" in source assert "player.open_path(sys.argv[1])" not in source def test_failed_delivery_is_visible_and_returns_an_error(): - source = (ROOT / "main.py").read_text(encoding="utf-8") + source = (ROOT / "app/main.py").read_text(encoding="utf-8") assert "instance_guard.handoff_failed" in source assert "QMessageBox.warning" in source assert "sys.exit(2)" in source diff --git a/tests/test_source_mirror_regressions.py b/tests/test_source_mirror_regressions.py index 04a358e..beb27cb 100644 --- a/tests/test_source_mirror_regressions.py +++ b/tests/test_source_mirror_regressions.py @@ -178,7 +178,8 @@ def test_a_size_mismatch_is_rejected(tmp_path): def test_the_mirror_folder_is_ignored_by_git(): fetch = module() ignore = (ROOT / ".gitignore").read_text(encoding="utf-8") - assert fetch.OUTPUT_DIR_NAME in ignore + # The mirror lives under output/ (3 October 2026); .gitignore uses "/". + assert fetch.OUTPUT_DIR_NAME.replace("\\", "/") in ignore def test_import_does_not_download_anything(): @@ -196,7 +197,7 @@ def test_default_cli_accepts_a_ready_verified_contract( contract = tmp_path / "sources.json" write_contract(contract, sources=[source_row(payload=payload)]) folder = tmp_path / fetch.OUTPUT_DIR_NAME - folder.mkdir() + folder.mkdir(parents=True) (folder / "mpv-source.tar.gz").write_bytes(payload) real_blockers = fetch.blockers real_plan = fetch.plan diff --git a/tests/test_spdx_header_regressions.py b/tests/test_spdx_header_regressions.py index 997e400..3b71df9 100644 --- a/tests/test_spdx_header_regressions.py +++ b/tests/test_spdx_header_regressions.py @@ -41,7 +41,7 @@ #: Taranan ağaçlar. `build/`, `dist/` ve `bin/` bizim kaynağımız değildir. SOURCE_TREES = ("app", "tests", "packaging") -SOURCE_FILES = ("main.py", "second_launch.py", "MLCPlayer.spec") +SOURCE_FILES = ("app/main.py", "packaging/MLCPlayer.spec") def python_sources(): @@ -78,7 +78,7 @@ def test_every_python_source_carries_the_spdx_header(path): def test_the_identifier_matches_what_the_readme_declares(): """Depo kendi içinde çelişmemeli: `only` ise README de öyle demeli.""" - readme = (ROOT / "README.md").read_text(encoding="utf-8") + readme = (ROOT / ".github" / "README.md").read_text(encoding="utf-8") assert "GNU GPL version 3" in readme # "or later" DEMİYORUZ; kimlik de `-only`. assert "GPL-3.0-only" in LICENCE_LINE diff --git a/tests/test_spec_paths_regressions.py b/tests/test_spec_paths_regressions.py new file mode 100644 index 0000000..f9cd33a --- /dev/null +++ b/tests/test_spec_paths_regressions.py @@ -0,0 +1,80 @@ +# SPDX-FileCopyrightText: 2026 MLC Player contributors +# SPDX-License-Identifier: GPL-3.0-only +"""PyInstaller spec dosyaları çalışma dizininden bağımsız yol çözer. + +Neden (3 Ekim 2026): spec dosyaları kökten `packaging/` altına taşındı. +PyInstaller göreli betik/veri yollarını SPEC KLASÖRÜNE göre çözer; eski +spec ise kökü `os.getcwd()` ile buluyordu. Bu test spec'i PyInstaller'ın +verdiği isimlerle (SPECPATH, Analysis, PYZ, EXE, COLLECT) ve bilerek +BAŞKA bir çalışma dizininden yürütür; toplanacak her dosyanın var olduğunu +gerçek derleme yapmadan denetler. +""" +import os +from pathlib import Path +from types import SimpleNamespace + +import pytest + +ROOT = Path(__file__).resolve().parents[1] +PACKAGING = ROOT / "packaging" +#: Large runtime binaries are not tracked (see .gitignore); a clean CI +#: checkout legitimately lacks them. +UNTRACKED_RUNTIME = {"mpv-2.dll"} + + +def _run_spec(name, monkeypatch, tmp_path): + pytest.importorskip("PyInstaller") + captured = {} + + def analysis(scripts, **kwargs): + captured["scripts"] = scripts + captured["analysis"] = kwargs + return SimpleNamespace(binaries=[], datas=[], pure=[], zipped_data=[], + scripts=[]) + + def exe(*args, **kwargs): + captured["exe"] = kwargs + return SimpleNamespace() + + namespace = { + "__name__": "__main__", + "SPECPATH": str(PACKAGING), + "Analysis": analysis, + "PYZ": lambda *args, **kwargs: SimpleNamespace(), + "EXE": exe, + "COLLECT": lambda *args, **kwargs: SimpleNamespace(), + } + monkeypatch.chdir(tmp_path) + source = (PACKAGING / name).read_text(encoding="utf-8") + exec(compile(source, str(PACKAGING / name), "exec"), namespace) + return captured, namespace + + +def _assert_exists(path): + if Path(path).name in UNTRACKED_RUNTIME: + return + assert os.path.isabs(path), f"göreli yol kaldı: {path}" + assert Path(path).exists(), f"bulunamadı: {path}" + + +def test_main_spec_resolves_every_input_from_the_project_root( + monkeypatch, tmp_path): + captured, namespace = _run_spec("MLCPlayer.spec", monkeypatch, tmp_path) + + assert Path(namespace["_project_root"]) == ROOT + assert captured["scripts"] == [str(ROOT / "app" / "main.py")] + for source, _target in captured["analysis"]["datas"]: + _assert_exists(source) + _assert_exists(captured["exe"]["icon"]) + version_file = Path(captured["exe"]["version"]) + assert version_file.parent == ROOT / "output" / "build" + + +def test_cleanup_spec_resolves_every_input_from_the_project_root( + monkeypatch, tmp_path): + captured, _namespace = _run_spec( + "MLCUserCleanup.spec", monkeypatch, tmp_path) + + assert captured["scripts"] == [ + str(ROOT / "app" / "user_cleanup_main.py")] + _assert_exists(captured["exe"]["icon"]) diff --git a/tests/test_startup_window_position_regressions.py b/tests/test_startup_window_position_regressions.py new file mode 100644 index 0000000..edbaf6b --- /dev/null +++ b/tests/test_startup_window_position_regressions.py @@ -0,0 +1,71 @@ +# SPDX-FileCopyrightText: 2026 MLC Player contributors +# SPDX-License-Identifier: GPL-3.0-only +"""Ana pencerenin her süreç açılışında varsayılan boyutta ortalanması.""" + +from pathlib import Path +from types import SimpleNamespace + +from PyQt6.QtCore import QPoint, QRect, QSize + +from app.player import centered_startup_position + + +ROOT = Path(__file__).resolve().parent.parent + + +def test_startup_position_centers_the_existing_window_size(): + available = QRect(1920, 40, 1920, 1040) + current_size = QSize(913, 517) + + target = centered_startup_position(available, current_size) + + assert target == QPoint( + available.x() + (available.width() - current_size.width()) // 2, + available.y() + (available.height() - current_size.height()) // 2, + ) + + +def test_oversized_window_keeps_its_title_bar_on_the_screen(): + # Kayıtlı boyut daha büyük bir monitörden kalmışsa ortalama negatif + # konum üretir; başlık çubuğu ekranın dışına düşer ve tutulamaz. + available = QRect(1920, 40, 1366, 728) + saved_size = QSize(1900, 1000) + + target = centered_startup_position(available, saved_size) + + assert target == QPoint(available.x(), available.y()) + + +def test_main_centers_the_window_before_showing_it(): + source = (ROOT / "app/main.py").read_text(encoding="utf-8") + center = source.index("player.center_on_active_screen()") + show = source.index("player.show()", center) + assert center < show + + +def test_startup_move_uses_the_active_screen_without_resizing(monkeypatch): + from app import player as player_module + + available = QRect(300, 70, 1600, 900) + screen = SimpleNamespace(availableGeometry=lambda: available) + moves = [] + window = SimpleNamespace( + size=lambda: QSize(913, 517), + move=lambda point: moves.append(point), + ) + monkeypatch.setattr( + player_module, "QApplication", + SimpleNamespace(screenAt=lambda _point: screen, + primaryScreen=lambda: None)) + monkeypatch.setattr( + player_module, "QCursor", SimpleNamespace(pos=lambda: QPoint())) + + player_module.MPVPlayer.center_on_active_screen(window) + + assert moves == [QPoint(643, 261)] + + +def test_saved_size_and_other_geometry_behaviour_are_left_intact(): + source = (ROOT / "app" / "player.py").read_text(encoding="utf-8") + assert "restoreGeometry(saved_geometry)" in source + assert 'setValue("geometry", self.saveGeometry())' in source diff --git a/tests/test_third_party_notice_regressions.py b/tests/test_third_party_notice_regressions.py index 060aee5..9dde138 100644 --- a/tests/test_third_party_notice_regressions.py +++ b/tests/test_third_party_notice_regressions.py @@ -31,7 +31,7 @@ ROOT = Path(__file__).resolve().parent.parent NOTICE = ROOT / "licenses" / "mpv-NOTICE.txt" MANIFEST = ROOT / "bin" / "RUNTIME_MANIFEST.txt" -SPEC = ROOT / "MLCPlayer.spec" +SPEC = ROOT / "packaging/MLCPlayer.spec" def _manifest_row(name): diff --git a/tests/test_thumbnail_start_failure_regressions.py b/tests/test_thumbnail_start_failure_regressions.py new file mode 100644 index 0000000..c9a65d2 --- /dev/null +++ b/tests/test_thumbnail_start_failure_regressions.py @@ -0,0 +1,43 @@ +# SPDX-FileCopyrightText: 2026 MLC Player contributors +# SPDX-License-Identifier: GPL-3.0-only +"""Worker süreci BAŞLAYAMAZSA kuyruk takılmamalı. + +`QProcess` başlatılamadığında `finished` GÖNDERMEZ, yalnız +`errorOccurred(FailedToStart)` gönderir. `_current` temizlenmediği ve +zaman aşımı "çalışmıyor" süreçte hiçbir şey yapmadığı için bütün küçük +resim kuyruğu kalıcı olarak `loading` durumunda kalıyordu. +""" + +import time + +from PyQt6.QtWidgets import QApplication + +from app import thumbnail_service + + +def test_failed_start_reports_failure_and_continues(monkeypatch, tmp_path): + app = QApplication.instance() or QApplication([]) + missing = str(tmp_path / "yok" / "worker.exe") + monkeypatch.setattr(thumbnail_service, "build_worker_command", + lambda media, output: (missing, [])) + first = tmp_path / "a.mp4" + second = tmp_path / "b.mp4" + for path in (first, second): + path.write_bytes(b"x" * 16) + service = thumbnail_service.ThumbnailService( + cache_dir=str(tmp_path / "cache")) + failed = [] + service.thumbnail_failed.connect(failed.append) + try: + service.request(str(first)) + service.request(str(second)) + deadline = time.monotonic() + 5 + while time.monotonic() < deadline and len(failed) < 2: + app.processEvents() + time.sleep(0.01) + + assert failed == [str(first), str(second)] + assert service.pending_paths == () + assert service.status(str(first)) == "failed" + finally: + service.close() diff --git a/tests/test_translation_fallback_regressions.py b/tests/test_translation_fallback_regressions.py index 470867b..0414400 100644 --- a/tests/test_translation_fallback_regressions.py +++ b/tests/test_translation_fallback_regressions.py @@ -137,7 +137,7 @@ def test_a_second_install_does_not_stack_translators(qt_app, translations): def test_apply_language_reports_the_chain_was_loaded(qt_app, translations, monkeypatch): - """Açılış yolu (`main.py`) aynı zinciri kurar.""" + """Açılış yolu (`app/main.py`) aynı zinciri kurar.""" monkeypatch.setattr(i18n, "stored_language", lambda: "de") code, loaded = i18n.apply_language(qt_app) assert (code, loaded) == ("de", True) diff --git a/tests/test_translation_pipeline_regressions.py b/tests/test_translation_pipeline_regressions.py index 43ec0b6..258703b 100644 --- a/tests/test_translation_pipeline_regressions.py +++ b/tests/test_translation_pipeline_regressions.py @@ -186,7 +186,7 @@ def test_the_release_chain_compiles_the_translations(): """`.qm` ÜRETİLMEZSE paket çevirisiz çıkar — sessizce. KIRMIZI KANIT: `.qm` `.gitignore` içindedir (üretilmiş dosyadır) ve - `MLCPlayer.spec` onları `translations/` içinden TOPLAR, ama zinciri + `packaging/MLCPlayer.spec` onları `translations/` içinden TOPLAR, ama zinciri kuran hiçbir adım onları DERLEMİYORDU. Temiz bir kopyada `build_release.bat` çalıştırıldığında klasör boş olur; kullanıcı hiçbir uyarı almadan yalnız Türkçe görür ve `available_languages()` İngilizceyi @@ -215,7 +215,7 @@ def test_the_compiler_skips_untranslated_languages(tmp_path): def test_compiled_translations_are_packaged(): """`.qm` pakete girmezse kurulu sürüm hep Türkçe açılır.""" - spec = (ROOT / "MLCPlayer.spec").read_text(encoding="utf-8") + spec = (ROOT / "packaging/MLCPlayer.spec").read_text(encoding="utf-8") assert "'translations'" in spec and ".qm" in spec diff --git a/tests/test_uninstall_cleanup_regressions.py b/tests/test_uninstall_cleanup_regressions.py new file mode 100644 index 0000000..e28ebb5 --- /dev/null +++ b/tests/test_uninstall_cleanup_regressions.py @@ -0,0 +1,316 @@ +# SPDX-FileCopyrightText: 2026 MLC Player contributors +# SPDX-License-Identifier: GPL-3.0-only +"""Main uninstall offers bounded, explicit MLC-owned data cleanup.""" + +import re +from pathlib import Path + + +ROOT = Path(__file__).resolve().parent.parent +MAIN_ISS = ROOT / "packaging" / "MLCPlayer.iss" + + +def _iss(): + return MAIN_ISS.read_text(encoding="utf-8-sig") + + +def test_uninstall_choices_preserve_user_data_by_default(): + text = _iss() + code = text.split("[Code]", 1)[1] + + assert "procedure InitializeUninstallCleanupChoices" in code + assert "RemoveAddonSelected := AddonIsInstalled;" in code + assert "DeleteSettingsSelected := False;" in code + assert "DeleteCacheSelected := False;" in code + assert "UninstallSilent" in code + assert "PURGEUSERDATA" not in code + assert "UninstallCleanupAccount" in code + assert "if UninstallSilent then\n Result := True" in code + + +def test_interactive_uninstall_has_three_real_choice_controls(): + text = _iss() + code = text.split("[Code]", 1)[1] + + assert "function ShowUninstallCleanupChoices: Boolean" in code + for variable in ( + "RemoveAddonCheckBox", + "DeleteSettingsCheckBox", + "DeleteCacheCheckBox", + ): + assert f"{variable} := TNewCheckBox.Create" in code + assert f"{variable}.Checked" in code + assert "ShowModal() = mrOk" in code + assert "Result := ShowUninstallCleanupChoices;" in code + + +def test_uninstall_removes_machine_product_registry_parent(): + text = _iss() + registry = text.split("[Registry]", 1)[1].split("[Icons]", 1)[0] + active_lines = [ + line.strip() for line in registry.splitlines() + if line.strip() and not line.lstrip().startswith(";") + ] + parent = ( + 'Root: HKLM; Subkey: "Software\\MLCPlayer"; ' + 'Flags: dontcreatekey uninsdeletekey' + ) + child = ( + 'Root: HKLM; Subkey: "Software\\MLCPlayer\\UninstallCleanup"; ' + 'Flags: dontcreatekey uninsdeletekey' + ) + + assert active_lines.count(parent) == 1 + assert active_lines.count(child) == 1 + # Inno uninstall entries run in reverse source order: child first, parent last. + assert active_lines.index(parent) < active_lines.index(child) + + +def test_selected_full_cleanup_covers_every_product_owned_store(): + text = _iss() + code = text.split("[Code]", 1)[1] + + for owned_target in ( + "{userappdata}\\MLCPlayer", + "{localappdata}\\MLCPlayer", + "MLCPlayerUpdate_*", + "Software\\MLCPlayer", + "MLCPlayer/OpenSubtitles/default", + "MLCPlayer/OpenSubtitles.ApiKey/default", + ): + assert owned_target in code + assert "StoredSubtitleUsername" in code + assert "UserSettingsKey = 'Software\\MLCPlayer'" in code + assert "RegDeleteKeyIncludingSubkeys(HKCU, UserSettingsKey)" in code + assert "CredDeleteW" in code + assert "DeleteOwnedUserDataInRegisteredSession" in code + assert "CleanupUserSid" in code + assert "CleanupTaskName" in code + assert "CleanupHelperHash" in code + assert "GetSHA256OfFile(CandidateHelper)" in code + assert "RegGetSubkeyNames(HKU" in code + assert "CleanupInstallId" in code + assert "CleanupIdentityCount" in code + assert "ProfileImagePath" in code + assert "ExpectedHelper" in code + assert "for I := 0 to CleanupIdentityCount - 1 do" in code + assert "schtasks.exe" in code + assert '"succeeded":true' in code + + +def test_credential_cleanup_matches_the_product_username_lifecycle(): + source = (ROOT / "app" / "subtitle_settings.py").read_text(encoding="utf-8") + code = _iss().split("[Code]", 1)[1] + + # A username switch is transactional: a former target must be deleted + # before the new username is committed to QSettings. Therefore the current + # stored name (or default) identifies the only password target that can + # remain for a successful product settings save. + assert "previous_username != new_username" in source + assert "self.credentials.delete_password(previous_username)" in source + assert "return rejected(cleanup_ok=False" in source + assert "StoredSubtitleUsername" in code + assert "MLCPlayer/OpenSubtitles/default" in code + + +def test_cleanup_paths_are_exact_and_never_wildcard_the_install_directory(): + text = _iss() + code = text.split("[Code]", 1)[1] + + assert 'Name: "{app}\\*"' not in text + assert "DelTree(ExpandConstant('{app}')" not in code + assert "DelTree(" not in code + assert "DeleteOwnedUserData" in code + assert "RemoveEmptyOwnedDirectory" in code + assert "IsOwnedThumbnailName" in code + assert "IsOwnedUpdateFileName" in code + assert "{localappdata}\\python" not in code + assert "IsReparsePoint" in code + assert "FileAttributeReparsePoint" in code + assert "CleanupFailed" in code + assert "function PathAncestorsAreSafe" in code + assert "PathAncestorsAreSafe(RoamingRoot)" in code + assert "PathAncestorsAreSafe(LogDirectory)" in code + assert "PathAncestorsAreSafe(LocalRoot)" in code + assert "PathAncestorsAreSafe(CacheDirectory)" in code + assert "PathAncestorsAreSafe(ThumbnailDirectory)" in code + assert "PathAncestorsAreSafe(BaseDirectory)" in code + assert "if DeleteSettingsSelected and" in code + assert "if DeleteCacheSelected and" in code + assert "user settings profile path" in code + assert "user cache profile path" in code + assert "if DeleteSettingsSelected or DeleteCacheSelected then" in code + assert "DeleteOwnedUserDataInRegisteredSession" in code + + +def test_user_cleanup_helper_is_packaged_and_removed_for_every_uninstall_mode(): + text = _iss() + code = text.split("[Code]", 1)[1] + build = (ROOT / "packaging" / "build_release.bat").read_text( + encoding="utf-8-sig") + + assert 'Source: "..\\output\\dist\\MLCUserCleanup.exe"' in text + assert "AppData\\Local\\Programs\\MLC Player\\UninstallCleanup" in code + assert "ExpectedLegacyCleanupHelperForSid" in code + assert "AppData\\Local\\MLCPlayer\\UninstallCleanup" in code + assert "{commonappdata}\\MLCPlayer\\UninstallCleanup" not in text + assert "Permissions: users-modify" not in text + assert "'ipc\\' + Nonce + '.request.json'" in code + assert "packaging/MLCUserCleanup.spec" in build + assert "RemoveCleanupInfrastructure" in code + assert "DeleteOwnedUserDataInRegisteredSession" in code + assert "RemoveCleanupInfrastructure" in code + assert "SelectCleanupIdentity(I)" in code + assert "if UninstallSilent then\n Result := True" in code + + +def test_cleanup_choices_work_before_player_has_ever_provisioned_a_helper(): + code = _iss().split("[Code]", 1)[1] + dialog = code.split("function ShowUninstallCleanupChoices", 1)[1].split( + "function InitializeUninstall", 1)[0] + post_uninstall = code.split("procedure CurUninstallStepChanged", 1)[1].split( + "function InitializeUninstall", 1)[0] + + assert "DeleteSettingsCheckBox.Enabled := True;" in dialog + assert "DeleteCacheCheckBox.Enabled := True;" in dialog + assert "(CleanupIdentityCount = 0)" not in post_uninstall + assert "DeleteOwnedUserData;" in post_uninstall + assert "ExecAsOriginalUser" not in code + + +def test_cleanup_dialog_discloses_every_account_scope_it_processes(): + text = _iss() + code = text.split("[Code]", 1)[1] + + assert "kaldırma hesabına ve burada listelenen kayıtlı MLC hesaplarına" in text + assert "Listelenen hesaplara ait tüm MLC Player verilerini" in text + assert "CleanupAccountDisplay := ExpandConstant('{username}');" in code + assert "CleanupAccountDisplay + ', ' +" in code + assert "for I := 0 to CleanupIdentityCount - 1 do" in code + + +def test_uninstaller_accepts_only_exact_current_or_legacy_helper_layouts(): + code = _iss().split("[Code]", 1)[1] + + assert "CurrentSuffix := '\\Programs\\MLC Player\\UninstallCleanup\\v1\\MLCUserCleanup.exe'" in code + assert "LegacySuffix := '\\MLCPlayer\\UninstallCleanup\\v1\\MLCUserCleanup.exe'" in code + assert "ExpectedLegacyCleanupHelperForSid" in code + assert "PathSame(CandidateHelper, ExpectedLegacyHelper)" in code + assert "FileExists(ExpectedHelper) or FileExists(ExpectedLegacyHelper)" in code + + +def test_cleanup_helper_rejects_reparse_points_through_the_profile_chain(): + code = _iss().split("[Code]", 1)[1] + + for directory in ( + "V1Directory", "CleanupDirectory", "ProductDirectory", + "ProgramsDirectory", "LocalDirectory", "AppDataDirectory", + "ProfileDirectory", + ): + assert f"IsReparsePoint({directory})" in code + + +def test_validated_cleanup_infrastructure_is_removed_on_every_runtime_failure(): + code = _iss().split("[Code]", 1)[1] + procedure = code.split( + "procedure DeleteOwnedUserDataInRegisteredSession;", 1)[1].split( + "procedure CurUninstallStepChanged", 1)[0] + + for failure in ( + "user cleanup IPC directory", "user cleanup request", + "user cleanup task start", "user cleanup timeout", + ): + block = procedure.split(f"MarkCleanupFailure('{failure}');", 1)[1] + assert block.lstrip().startswith("RemoveCleanupInfrastructure;\n Exit;") + + assert "else\n MarkCleanupFailure('user cleanup helper path');" in code + + +def test_runtime_nonce_hashes_use_the_unicode_inno_api(): + code = _iss().split("[Code]", 1)[1] + + # The hash input is a normal Unicode String expression. + assert code.count("GetSHA256OfUnicodeString(") == 2 + assert "GetSHA256OfString(" not in code + + +def test_runtime_datetime_hash_seed_uses_char_separators(): + code = _iss().split("[Code]", 1)[1] + + # GetDateTimeString expects Char separators. An empty String compiles but + # raises Runtime error: Type Mismatch when the post-install hook runs. + assert "GetDateTimeString('yyyymmddhhnnsszzz', '', '')" not in code + assert code.count( + "GetDateTimeString('yyyymmddhhnnsszzz', '-', ':')" + ) == 2 + + +def test_cleanup_dialog_never_focuses_a_disabled_or_hidden_checkbox(): + code = _iss().split("[Code]", 1)[1] + + assert "RemoveAddonCheckBox.Visible and RemoveAddonCheckBox.Enabled" in code + assert "else if DeleteSettingsCheckBox.Enabled then" in code + assert "Form.ActiveControl := ContinueButton;" in code + + +def test_cleanup_dialog_is_compact_and_visually_joined_to_button_row(): + code = _iss().split("[Code]", 1)[1] + + assert "CreateCustomForm(ScaleX(470), ScaleY(292)" in code + assert "BodyLabel.Font.Style := [fsBold]" in code + assert "ButtonSeparator := TBevel.Create(Form)" in code + assert "ButtonSeparator.Shape := bsTopLine" in code + + +def test_uninstaller_rechecks_selected_settings_under_the_exact_user_sid(): + code = _iss().split("[Code]", 1)[1] + + assert "UserRegistryKey := CleanupUserSid + '\\' + UserSettingsKey" in code + assert code.count("RegKeyExists(HKU, UserRegistryKey)") == 2 + assert "RegDeleteKeyIncludingSubkeys(HKU, UserRegistryKey)" in code + + +def test_addon_removal_is_path_validated_and_fail_closed(): + code = _iss().split("[Code]", 1)[1] + + assert "function ReadValidatedAddonUninstaller" in code + assert "PathSame(ExtractFileDir(UninstallerPath), ExpandConstant('{app}'))" in code + assert "PathSame(UninstallerPath, ExpandConstant('{uninstallexe}'))" in code + assert "'InstallLocation', RegisteredInstallDirectory" in code + assert "Exec(UninstallerPath" in code + assert "if ExitCode <> 0 then" in code + assert "RaiseException(CustomMessage('UninstallAddonFailed'))" in code + assert "RegKeyExists(HKLM, AddonUninstallKey)" in code + assert "{app}\\_internal\\bin\\yt-dlp.exe" in code + assert "{app}\\_internal\\bin\\deno.exe" in code + + +def test_cleanup_copy_exists_for_every_installer_language(): + text = _iss() + languages = ( + "english", + "turkish", + "german", + "spanish", + "french", + "italian", + "russian", + "brazilianportuguese", + ) + keys = ( + "UninstallCleanupTitle", + "UninstallCleanupBody", + "UninstallCleanupAccount", + "UninstallRemoveAddon", + "UninstallDeleteSettings", + "UninstallDeleteCache", + "UninstallCleanupNote", + "UninstallAddonFailed", + "UninstallCleanupFailed", + ) + + for language in languages: + for key in keys: + assert re.search( + rf"^{language}\.{key}=.+$", text, re.MULTILINE + ), (language, key) diff --git a/tests/test_updater_regressions.py b/tests/test_updater_regressions.py index 81ae5c6..c695a11 100644 --- a/tests/test_updater_regressions.py +++ b/tests/test_updater_regressions.py @@ -74,6 +74,22 @@ def release(**overrides): ("v1.0", "v0.9", True), ("v0.10", "v0.9", True), # sayısal karşılaştırma, metin değil ("", "v0.1", False), + # Ön sürüm son eki sayı DEĞİLDİR: `v0.41-rc1` eski algoritmada + # (0, 41, 1) olup kararlı `v0.41`'den yeni görünüyordu. + ("v0.41-rc1", "v0.41", False), + ("v0.41", "v0.41-rc1", True), + ("v0.41-rc2", "v0.41-rc1", True), + ("v0.42-rc1", "v0.41", True), + ("v0.41.1", "v0.41", True), + # Sondaki sıfırlar ve `+build` üst verisi (semver) sürümü DEĞİŞTİRMEZ; + # kararlılık işaretçisi dördüncü sayıyla karışmamalıdır. + ("v0.41.0.0", "v0.41", False), + ("v0.41", "v0.41.0.0", False), + ("v0.41.0.0", "v0.41-rc1", True), + ("v0.41+build5", "v0.41", False), + ("v0.41", "v0.41+build5", False), + ("v0.41.", "v0.41", False), + ("v0.41", "v0.41.", False), ]) def test_version_comparison(latest, current, expected): assert updater.is_newer_version(latest, current) is expected diff --git a/tests/test_user_cleanup_helper_regressions.py b/tests/test_user_cleanup_helper_regressions.py new file mode 100644 index 0000000..1403eba --- /dev/null +++ b/tests/test_user_cleanup_helper_regressions.py @@ -0,0 +1,375 @@ +# SPDX-FileCopyrightText: 2026 MLC Player contributors +# SPDX-License-Identifier: GPL-3.0-only +from app import user_cleanup_helper as helper +import hashlib +import os + +import pytest + + +class _RegistryKey: + def __init__(self, path): + self.path = path + + def __enter__(self): + return self + + def __exit__(self, *_): + return False + + +class _FakeWinreg: + KEY_READ = 1 + KEY_WRITE = 2 + HKEY_CURRENT_USER = object() + + def __init__(self, paths, delete_is_noop=False): + self.paths = set(paths) + self.delete_is_noop = delete_is_noop + + def OpenKey(self, _root, path, *_): + if path not in self.paths: + raise FileNotFoundError(path) + return _RegistryKey(path) + + def EnumKey(self, key, index): + prefix = key.path + "\\" + children = sorted({ + path[len(prefix):].split("\\", 1)[0] + for path in self.paths if path.startswith(prefix) + }) + if index >= len(children): + raise OSError("no more keys") + return children[index] + + def DeleteKey(self, _root, path): + if any(item.startswith(path + "\\") for item in self.paths): + raise OSError("key has children") + if not self.delete_is_noop: + self.paths.remove(path) + + +def test_task_names_are_sid_bound_and_reject_untrusted_text(): + assert helper.task_name_for_sid("S-1-5-21-42") == "\\MLCPlayer_UninstallCleanup_S-1-5-21-42" + assert helper.task_name_for_sid("S-1-5-21-42;cmd") is None + assert helper.task_name_for_sid("") is None + + +def test_request_match_never_accepts_a_missing_or_different_sid(monkeypatch): + monkeypatch.setattr(helper, "current_user_sid", lambda: "S-1-5-21-1") + assert helper.request_matches_current_user("S-1-5-21-1") + assert not helper.request_matches_current_user("S-1-5-21-2") + assert not helper.request_matches_current_user("") + + +def test_helper_uses_the_schedulable_per_user_programs_path(tmp_path): + assert helper.helper_location(tmp_path) == ( + tmp_path / "Programs" / "MLC Player" / "UninstallCleanup" / + "v1" / "MLCUserCleanup.exe" + ) + + +def test_task_is_hidden_triggerless_sid_bound_and_on_demand(tmp_path): + exe = helper.helper_location(tmp_path) + xml = helper.task_xml("S-1-5-21-42", exe) + assert "" not in xml + assert "InteractiveToken" in xml + assert "LeastPrivilege" in xml + assert "true" in xml + assert "true" in xml + assert "--process-request" in xml + create = helper.create_task_command("S-1-5-21-42", exe, tmp_path / "task.xml") + assert create[:4] == ["schtasks.exe", "/Create", "/TN", "\\MLCPlayer_UninstallCleanup_S-1-5-21-42"] + assert "/XML" in create + assert helper.run_task_command("S-1-5-21-42") == ["schtasks.exe", "/Run", "/TN", "\\MLCPlayer_UninstallCleanup_S-1-5-21-42"] + assert helper.create_task_command("S-1-5-21-42;bad", exe) is None + assert helper.create_task_command("S-1-5-21-42", tmp_path / "other.exe") is None + + +def test_result_is_atomically_bound_to_a_hex_nonce(tmp_path): + nonce = "a" * 32 + path = helper.result_file(tmp_path, nonce) + assert path is not None + assert helper.write_result_atomically(path, nonce, True) + assert path.read_text(encoding="utf-8") == '{"failure_count":0,"nonce":"' + nonce + '","succeeded":true}' + assert helper.request_file(tmp_path, "bad-nonce") is None + assert helper.request_file(tmp_path, "a" * 31) is None + + +def test_owned_path_rejects_traversal_and_parent_symlinks(tmp_path): + assert helper.safe_owned_path(tmp_path, "MLCPlayer", "logs") == tmp_path / "MLCPlayer" / "logs" + assert helper.safe_owned_path(tmp_path, "..", "outside") is None + linked = tmp_path / "MLCPlayer" + linked.symlink_to(tmp_path, target_is_directory=True) + assert helper.safe_owned_path(tmp_path, "MLCPlayer", "logs") is None + + +def test_safe_directory_creation_rejects_a_reparse_ancestor(tmp_path): + outside = tmp_path / "outside" + outside.mkdir() + programs = tmp_path / "Programs" + programs.symlink_to(outside, target_is_directory=True) + + assert helper._ensure_safe_directories( + tmp_path, helper._HELPER_PARTS[:-1]) is None + assert not (outside / "MLC Player").exists() + + +def test_profile_local_appdata_rejects_an_appdata_reparse(tmp_path): + profile = tmp_path / "profile" + outside = tmp_path / "outside" + profile.mkdir() + outside.mkdir() + (profile / "AppData").symlink_to(outside, target_is_directory=True) + (outside / "Local").mkdir() + + assert helper.profile_local_appdata({"USERPROFILE": str(profile)}) is None + assert helper._safe_profile_owned_path( + profile / "AppData" / "Local", *helper._HELPER_PARTS) is None + + +def test_current_registration_requires_new_path_and_current_binary_hash(tmp_path): + sid = "S-1-5-21-42" + expected = helper.helper_location(tmp_path) + expected.parent.mkdir(parents=True) + expected.write_bytes(b"current helper") + digest = hashlib.sha256(expected.read_bytes()).hexdigest() + registration = { + "install_id": "a" * 32, + "sid": sid, + "task_name": helper.task_name_for_sid(sid), + "helper_path": expected, + "helper_hash": digest, + } + + assert helper._registration_is_current( + registration, "a" * 32, sid, expected, digest) + legacy = dict(registration, helper_path=helper.legacy_helper_location(tmp_path)) + assert not helper._registration_is_current( + legacy, "a" * 32, sid, expected, digest) + assert not helper._registration_is_current( + registration, "a" * 32, sid, expected, "b" * 64) + + +def test_legacy_helper_migration_removes_only_the_exact_owned_layout(tmp_path): + sid = "S-1-5-21-42" + local = tmp_path / "profile" / "AppData" / "Local" + local.mkdir(parents=True) + legacy = helper.legacy_helper_location(local) + ipc = legacy.parent / "ipc" + ipc.mkdir(parents=True) + legacy.write_bytes(b"current helper") + request = ipc / (("c" * 32) + ".request.json") + request.write_text("{}", encoding="utf-8") + digest = hashlib.sha256(legacy.read_bytes()).hexdigest() + registration = { + "install_id": "a" * 32, + "sid": sid, + "task_name": helper.task_name_for_sid(sid), + "helper_path": legacy, + "helper_hash": digest, + } + + assert helper._remove_legacy_helper( + local, registration, "a" * 32, sid) + assert not legacy.exists() + assert not request.exists() + assert not (local / "MLCPlayer").exists() + + +def test_legacy_helper_migration_rejects_hash_mismatch(tmp_path): + sid = "S-1-5-21-42" + local = tmp_path / "profile" / "AppData" / "Local" + local.mkdir(parents=True) + legacy = helper.legacy_helper_location(local) + legacy.parent.mkdir(parents=True) + legacy.write_bytes(b"old helper") + registration = { + "install_id": "a" * 32, + "sid": sid, + "task_name": helper.task_name_for_sid(sid), + "helper_path": legacy, + "helper_hash": hashlib.sha256(b"different registered helper").hexdigest(), + } + + assert not helper._remove_legacy_helper( + local, registration, "a" * 32, sid) + assert legacy.exists() + + +def test_legacy_upgrade_accepts_a_different_but_registered_old_binary(tmp_path): + sid = "S-1-5-21-42" + local = tmp_path / "profile" / "AppData" / "Local" + local.mkdir(parents=True) + legacy = helper.legacy_helper_location(local) + legacy.parent.mkdir(parents=True) + legacy.write_bytes(b"old-version") + old_digest = hashlib.sha256(legacy.read_bytes()).hexdigest() + registration = { + "install_id": "a" * 32, + "sid": sid, + "task_name": helper.task_name_for_sid(sid), + "helper_path": legacy, + "helper_hash": old_digest, + } + + assert helper._remove_legacy_helper( + local, registration, "a" * 32, sid) + assert not legacy.exists() + + +def test_cleanup_removes_only_exact_owned_files(tmp_path, monkeypatch): + roaming = tmp_path / "roaming" + local = tmp_path / "local" + logs = roaming / "MLCPlayer" / "logs" + thumbs = local / "MLCPlayer" / "cache" / "thumbnails" + update = local / "MLCPlayerUpdate_123" + logs.mkdir(parents=True) + thumbs.mkdir(parents=True) + update.mkdir() + (logs / "uygulama.log").write_text("x") + (logs / "foreign.txt").write_text("keep") + (thumbs / (("a" * 64) + ".jpg")).write_text("x") + (thumbs / "foreign.jpg").write_text("keep") + (update / "MLCPlayer_Setup_v0.41.exe").write_text("x") + (update / "foreign.bin").write_text("keep") + monkeypatch.setattr(helper, "_delete_settings_registry", lambda failures: None) + monkeypatch.setattr(helper, "_delete_credentials", lambda failures: None) + + result = helper.cleanup_current_user(True, True, { + "APPDATA": str(roaming), "LOCALAPPDATA": str(local)}) + + assert not result["succeeded"] + assert not (logs / "uygulama.log").exists() + assert (logs / "foreign.txt").exists() + assert not (thumbs / (("a" * 64) + ".jpg")).exists() + assert (thumbs / "foreign.jpg").exists() + assert not (update / "MLCPlayer_Setup_v0.41.exe").exists() + assert (update / "foreign.bin").exists() + + +def test_settings_registry_tree_is_recursive_and_verified_absent(): + root = r"Software\MLCPlayer" + fake = _FakeWinreg({root, root + r"\MLCPlayer", + root + r"\MLCPlayer\subtitle"}) + + assert helper._delete_registry_tree( + fake, fake.HKEY_CURRENT_USER, root) is True + assert fake.paths == set() + + +def test_settings_registry_delete_rejects_a_false_success(): + root = r"Software\MLCPlayer" + fake = _FakeWinreg({root}, delete_is_noop=True) + + assert helper._delete_registry_tree( + fake, fake.HKEY_CURRENT_USER, root) is False + assert fake.paths == {root} + + +def test_request_requires_exact_sid_and_separate_result_file(tmp_path, monkeypatch): + nonce = "b" * 32 + path = helper.request_file(tmp_path, nonce) + path.parent.mkdir(parents=True) + path.write_text('{"nonce":"' + nonce + '","sid":"S-1-5-21-9",' + '"delete_settings":true,"delete_cache":false}') + monkeypatch.setattr(helper, "current_user_sid", lambda: "S-1-5-21-9") + assert helper.read_owned_request(path)["delete_settings"] is True + assert helper.result_file(tmp_path, nonce) != path + + +def test_startup_registration_does_not_block_the_caller(): + """Ölçülen risk: kayıt alt süreci 30 sn'ye kadar sürebilir ve pencere + açılmadan ana thread'de çalışıyordu. Başlatıcı hemen dönmeli, sonuç + arka planda raporlanmalıdır.""" + import threading + import time + + release = threading.Event() + reported = [] + + def slow_ensure(): + release.wait(5) + return True + + started = time.monotonic() + worker = helper.start_user_cleanup_registration( + ensure=slow_ensure, report=reported.append) + elapsed = time.monotonic() - started + try: + assert elapsed < 0.5, f"başlatıcı {elapsed:.2f} sn bloke etti" + assert worker.daemon, "kapanışı bekletmemeli" + assert reported == [] + finally: + release.set() + worker.join(5) + assert reported == [True] + + +def test_startup_registration_reports_instead_of_raising(): + """Arka plan thread'indeki istisna `sys.excepthook`a ulaşmaz; yutulmadan + güvenli bir sonuç olarak raporlanmalıdır.""" + reported = [] + + def broken_ensure(): + raise OSError("sid okunamadı") + + worker = helper.start_user_cleanup_registration( + ensure=broken_ensure, report=reported.append) + worker.join(5) + assert reported == ["error:OSError"] + + +def test_main_starts_registration_after_the_window_and_handler(): + """app/main.py kaydı senkron çağırmaz; hata yakalayıcı ondan önce kurulur + ve pencere `show()` edildikten sonra başlatılır.""" + import ast + from pathlib import Path + + tree = ast.parse(Path(__file__).resolve().parents[1].joinpath( + "app/main.py").read_text(encoding="utf-8")) + calls = [] + for node in ast.walk(tree): + if isinstance(node, ast.Call): + func = node.func + name = getattr(func, "attr", None) or getattr(func, "id", None) + calls.append((node.lineno, name)) + names = [name for _, name in sorted(calls)] + assert "ensure_user_cleanup_task" not in names + assert "start_user_cleanup_registration" in names + assert (names.index("install_exception_handler") + < names.index("show") + < names.index("start_user_cleanup_registration")) + + +@pytest.mark.skipif(os.name != "nt", reason="Windows named mutex") +def test_provision_mutex_is_exclusive_and_released_with_the_handle(): + """Kayıt artık pencereyle paralel çalıştığı için hızlı kapat/aç iki + `--provision` süreci başlatabilir. Ortak `.new` dosyasını ikinci süreç + silebiliyordu. Kilit süreç ölünce işletim sistemince bırakılır; bayat + kilit dosyası gibi kalıcı engel üretmez.""" + sid = "S-1-5-21-111-222-333-4444" + first = helper._acquire_provision_mutex(sid) + assert first + try: + assert helper._acquire_provision_mutex(sid) is None + finally: + helper._release_provision_mutex(first) + again = helper._acquire_provision_mutex(sid) + assert again + helper._release_provision_mutex(again) + + +def test_concurrent_provision_returns_busy_without_touching_files( + tmp_path, monkeypatch): + local = tmp_path / "AppData" / "Local" + local.mkdir(parents=True) + monkeypatch.setattr(helper.os, "name", "nt") + monkeypatch.setattr(helper, "current_user_sid", + lambda: "S-1-5-21-111-222-333-4444") + monkeypatch.setattr(helper, "profile_local_appdata", lambda: local) + monkeypatch.setattr(helper, "_read_install_id", lambda: "install-id") + monkeypatch.setattr(helper, "_acquire_provision_mutex", lambda _sid: None) + before = sorted(tmp_path.rglob("*")) + assert helper.provision() == helper.PROVISION_BUSY + assert sorted(tmp_path.rglob("*")) == before diff --git a/tests/test_video_format_acceptance_plan_regressions.py b/tests/test_video_format_acceptance_plan_regressions.py index 2b95910..4ff858f 100644 --- a/tests/test_video_format_acceptance_plan_regressions.py +++ b/tests/test_video_format_acceptance_plan_regressions.py @@ -6,11 +6,12 @@ ROOT = Path(__file__).resolve().parent.parent -PLAN = ROOT / "docs" / "VIDEO_FORMAT_ACCEPTANCE_PLAN.md" -MATRIX = ROOT / "docs" / "VIDEO_FORMAT_ACCEPTANCE_MATRIX.json" -MEDIA = ROOT / "docs" / "VIDEO_FORMAT_MEDIA_MANIFEST.json" -INVENTORY = ROOT / "docs" / "VIDEO_FORMAT_CAPABILITY_INVENTORY.md" -WINDOWS = ROOT / "docs" / "WINDOWS_ACCEPTANCE_MATRIX.md" +PLAN = ROOT / "docs" / "video-format" / "VIDEO_FORMAT_ACCEPTANCE_PLAN.md" +MATRIX = ROOT / "docs" / "video-format" / "VIDEO_FORMAT_ACCEPTANCE_MATRIX.json" +MEDIA = ROOT / "docs" / "video-format" / "VIDEO_FORMAT_MEDIA_MANIFEST.json" +# Yetenek envanteri kabul planının ek bölümüne birleştirildi. +INVENTORY = PLAN +WINDOWS = ROOT / "docs" / "quality" / "WINDOWS_ACCEPTANCE_MATRIX.md" LEDGER = ROOT / "docs" / "VERIFICATION_LEDGER.json" REQUIRED_CASES = { @@ -52,7 +53,7 @@ def test_media_candidates_are_fail_closed_and_cover_every_case(): assert media["native_media_opened"] is True assert media["latest_native_media_evidence_id"] == "EV-20260825-012" assert matrix["media_policy"]["manifest_document"] == ( - "docs/VIDEO_FORMAT_MEDIA_MANIFEST.json") + "docs/video-format/VIDEO_FORMAT_MEDIA_MANIFEST.json") assert matrix["media_policy"]["manifest_status"] == ( "partially_fingerprinted") assert matrix["media_policy"]["manifest_evidence_id"] == ( diff --git a/tests/test_video_format_media_fingerprint_regressions.py b/tests/test_video_format_media_fingerprint_regressions.py index 0895a32..e89efb5 100644 --- a/tests/test_video_format_media_fingerprint_regressions.py +++ b/tests/test_video_format_media_fingerprint_regressions.py @@ -14,7 +14,7 @@ ROOT = Path(__file__).resolve().parent.parent -MANIFEST = ROOT / "docs" / "VIDEO_FORMAT_MEDIA_MANIFEST.json" +MANIFEST = ROOT / "docs" / "video-format" / "VIDEO_FORMAT_MEDIA_MANIFEST.json" sys.path.insert(0, str(ROOT / "scripts")) import verify_video_format_media as guard # noqa: E402 diff --git a/tests/test_volume_osd_translation_regressions.py b/tests/test_volume_osd_translation_regressions.py new file mode 100644 index 0000000..915dea3 --- /dev/null +++ b/tests/test_volume_osd_translation_regressions.py @@ -0,0 +1,37 @@ +# SPDX-FileCopyrightText: 2026 MLC Player contributors +# SPDX-License-Identifier: GPL-3.0-only +"""Ses değişiminde gösterilen OSD metni çevrilmeli. + +`set_volume()` metni önce bir değişkene atayıp sonra `show_osd()`'ye +veriyordu; `"Sessiz"` ve `"Ses: %N"` `tr()` dışında kaldığı için başka +dildeki kullanıcı ses değiştirince Türkçe görüyordu. Arayüz çağrısının +sabit argümanını arayan AST taraması bu dolaylı yolu yakalamıyordu. +""" + +from types import SimpleNamespace + +from app import media_controls + + +def _player(osd): + class Style: + def standardIcon(self, _pixmap): + return None + + mpv = SimpleNamespace(volume=50.0, volume_max=130) + return SimpleNamespace( + mpv_player=mpv, last_volume=50.0, is_muted=False, _ui_ready=True, + volume_label=SimpleNamespace(setText=lambda _t: None), + volume_icon=SimpleNamespace(setIcon=lambda _i: None), + style=lambda: Style(), + video_frame=SimpleNamespace(show_osd=osd.append)) + + +def test_volume_osd_text_is_translated(monkeypatch): + monkeypatch.setattr(media_controls, "tr", lambda text: f"«{text}»") + osd = [] + + assert media_controls.set_volume(_player(osd), 70) + assert media_controls.set_volume(_player(osd), 0) + + assert osd == ["«Ses: %{volume}»".format(volume=70), "«Sessiz»"] diff --git a/tests/test_ytdlp_source_staging_regressions.py b/tests/test_ytdlp_source_staging_regressions.py index c986a4a..c25d5be 100644 --- a/tests/test_ytdlp_source_staging_regressions.py +++ b/tests/test_ytdlp_source_staging_regressions.py @@ -25,7 +25,7 @@ def module(): def source_archive(name): """Return a staged release artifact or mark the deep check unavailable.""" - path = ROOT / "source_mirror" / name + path = ROOT / "output/source_mirror" / name if not path.is_file(): pytest.skip(f"requires staged corresponding-source artifact: {name}") return path diff --git a/tests/timeline_child.py b/tests/timeline_child.py index 84305cb..db1a3e6 100644 --- a/tests/timeline_child.py +++ b/tests/timeline_child.py @@ -50,7 +50,7 @@ QTimer.singleShot(100, app.quit) app.exec() -# ÜRÜNLE AYNI KAPANIŞ (`main.py` -> `os._exit(ret)`): libmpv yüklendikten +# ÜRÜNLE AYNI KAPANIŞ (`app/main.py` -> `os._exit(ret)`): libmpv yüklendikten # sonra normal Python finalizasyonu takılabiliyor. Buraya ulaşmak bütün # assert'lerin geçtiği anlamına gelir; başarısızlıkta zaten istisna ile # non-zero exit üretilir. diff --git a/tests/title_bar_child.py b/tests/title_bar_child.py index ed7cbfe..9ababc3 100644 --- a/tests/title_bar_child.py +++ b/tests/title_bar_child.py @@ -135,7 +135,7 @@ def main(): if __name__ == "__main__": - # ÜRÜNLE AYNI KAPANIŞ (`main.py` -> `os._exit(ret)`): libmpv yüklendikten + # ÜRÜNLE AYNI KAPANIŞ (`app/main.py` -> `os._exit(ret)`): libmpv yüklendikten # sonra normal Python finalizasyonu takılabiliyor. Ölçüm JSON'u bu # noktadan ÖNCE basılır. _code = main() diff --git a/translations/mlcplayer_de.ts b/translations/mlcplayer_de.ts index 6ecec33..c386472 100644 --- a/translations/mlcplayer_de.ts +++ b/translations/mlcplayer_de.ts @@ -3,30 +3,35 @@ MLCPlayer - - - + + %{value} + + + + + + 30 Saniye Geri - - - + + + 30 Saniye İleri - - - + + + 5 Saniye Geri - - - + + + 5 Saniye İleri @@ -56,90 +61,89 @@ - + Adres - + Adresi Kopyala - - - + + + Almanca - + Alt Yazı - - + + Altyazı - - - + + + Altyazı Ayarları - + Altyazı Ayarları Uygulanamadı - + Altyazı Bul - - + + Altyazı Bulunamadı - + Altyazı Bul… - + Altyazı Değiştirilemedi - + Altyazı Dosyaları - + Altyazı Dosyası Ekle - - - + + + Altyazı Ekle - - - - - + + + + Altyazı Eklenemedi @@ -160,15 +164,15 @@ - - - - + + + + Altyazı Parçası - + Altyazı Seçilemedi @@ -183,7 +187,7 @@ - + Altyazı bulunamadı @@ -193,7 +197,7 @@ - + Altyazı desteği @@ -203,28 +207,22 @@ - - + + Altyazı dosyası bulunamadı. Dosyanın yerini kontrol edin. - - + Altyazı eklendi - - + + Altyazı eklenemedi. Dosyanın desteklenen ve okunabilir bir altyazı dosyası olduğundan emin olun. - - - Altyazı eklenemedi. Dosyanın hasarlı veya desteklenmeyen bir format olması mümkün. - - Altyazı indirildi ancak oynatıcıya uygulanamadı. @@ -256,29 +254,29 @@ - - + + Altyazı parçaları yüklenemedi - - + + Altyazı parçası bulunamadı - + Altyazı parçası yok. - + Altyazı senkronu - + Altyazı senkronu. Pozitif değer altyazıyı geciktirir, negatif değer öne alır. @@ -288,7 +286,7 @@ - + Altyazı seçilemedi. Lütfen başka bir altyazı parçasını deneyin. @@ -298,21 +296,21 @@ - + Altyazı sıradaki videoya otomatik bağlanamadı. Videoyu oynatın ve altyazıyı yeniden bırakın. - - - - + + + + Altyazı yolu: - - + + Altyazı yükleniyor... @@ -322,38 +320,38 @@ - + Altyazılar açılıp kapatılamadı. Lütfen tekrar deneyin. - + Altyazıları Aç - + Altyazıları Gizle - + Altyazıları Göster - - + + Altyazıları Göster/Gizle - + Altyazıları Göster/Gizle (S) - + Altyazıları Kapat @@ -388,23 +386,23 @@ - - + + Arapça - + Araçlar - + Arka plan - + Arka planı şeffaf yap (renk yok) @@ -456,13 +454,13 @@ - + Ayarları uygula - - + + Açılamadı @@ -472,23 +470,23 @@ - + Aşağı Ok - - + + Bağlantı - + Bağlantı Açılamadı - + Bağlantı açılıyor… @@ -513,7 +511,7 @@ - + Bağlantıdan Oynat @@ -523,22 +521,22 @@ - + Başarılı - + Başlık - + Beklenmeyen Hata - + Beklenmeyen bir hata oluştu. Program çalışmaya devam ediyor, ancak bu işlem tamamlanamadı. @@ -551,7 +549,7 @@ Sorun devam ederse programı yeniden başlatın. - + Beklenmeyen bir veri hatası oluştu. Lütfen işlemi tekrar deneyin. Sorun devam ederse programı yeniden başlatın. @@ -563,38 +561,38 @@ Lütfen işlemi tekrar deneyin. Sorun devam ederse programı yeniden başlatın. - + Bir videoyu buraya sürükleyin veya bilgisayarınızdan açın. - + Biraz büyük - + Birçok video formatını destekler - + Bit derinliği - - + + Bitrate - + Boyut - + Bu bağlantı açılamadı. Adresi ve internet bağlantınızı kontrol edip tekrar deneyin. @@ -605,30 +603,30 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Bu bir altyazı önizlemesidir. - + Bu klasörde desteklenen medya dosyası bulunamadı. - + Bu paket mpv/FFmpeg bileşenini içerir; künyesi ve kaynak adresi için kurulum dizinindeki <code>licenses</code> klasörüne bakın. - + Bu program ÖZGÜR YAZILIMDIR ve GNU GPL sürüm 3 koşullarıyla dağıtılır. - - - - + + + + @@ -636,69 +634,69 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Bölüm bilgisi yok - + Bölüm bulunamadı - - + + Bölümleri Yenile - + Büyük - - + + Büyüt - - + + Codec - - + + Codec açıklaması - + Daha sonra - + Değişiklikleri iptal et - + Değişiklikleri sürüm notlarında inceleyebilirsiniz. - + Dikey konum - - - - + + + + Dil @@ -708,37 +706,37 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Dolby Vision - - + + Dosya - - - - - - + + + + + + Dosya Aç - - - - - + + + + + Dosya Açılamadı - + Dosya Bulunamadı @@ -748,60 +746,60 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Dosya artık mevcut değil. Son Açılanlar listesinden kaldırıldı. - + Dosya açılamadı. Dosya silinmiş, taşınmış veya desteklenmeyen bir format olabilir. - + Dosya açılamadı. Dosyanın mevcut ve desteklenen bir medya dosyası olduğunu kontrol edin. - + Dosya açılamadı: - + Dosya bulunamadı. Dosya taşınmış veya silinmiş olabilir. Çözüm: Dosyanın yerini kontrol edip tekrar açmayı deneyin. - + Dosyaya erişim izniniz yok. Çözüm: Dosyanın kilidini açın veya başka bir klasöre kopyalayın. - + Doygunluk: - + Duraklat - - - + + + Durdur - - - + + + Durum @@ -811,71 +809,71 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Ekran Görüntüsü (Ctrl+S) - - - + + + Ekran Görüntüsü Al - + Ekran Görüntüsü Alınamadı - + Ekran görüntüsü alma - + Ekran görüntüsü almak için bir video oynatılıyor olmalıdır. - + Ekran görüntüsü kaydedildi: - + Ekran görüntüsü kaydedilemedi. Masaüstüne yazma iznini ve boş alanı kontrol edin. - + En büyük - + En güncel sürümü kullanıyorsunuz - + En kalın - + Evet - + FPS - - - + + + Felemenkçe @@ -886,102 +884,102 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - - - + + + Fransızca - + Gamma: - + Genel - + Genel bitrate - + Geri - + Geri Yükle - + Gezinim - + Geçerli bir web adresi girin. Yalnız http:// ve https:// ile başlayan bağlantılar açılabilir. - + Geçersiz Adres - + Geçersiz Zaman - + Girilen zaman konumuna gidilemedi. Zamanı tekrar kontrol edin. - + GitHub sayfasını aç - + Gömülü - - + + Görüntü - + Görüntü oranı - + Görünüm - + Güncelle - - - + + + Güncelleme - + Güncelleme Mevcut @@ -1006,12 +1004,12 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Güncelleme uygulanıyor… - + Güncellemeleri Denetle @@ -1022,7 +1020,7 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Günlük Yönetimi @@ -1032,12 +1030,12 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Günlükler temizlendi. - + Günlükler temizlenemedi. Dosyalar başka bir program tarafından kullanılıyor olabilir. @@ -1063,15 +1061,15 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - - + + Hakkında - - - + + + Harici @@ -1081,7 +1079,7 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Hata Ayrıntılarını Görüntüle @@ -1101,36 +1099,36 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + HİÇBİR GARANTİ VERİLMEZ; satılabilirlik veya belirli bir amaca uygunluk zımni garantisi dahil değildir. - - + + Japonca - + Kaldır - + Kanal - + - - - + + + Kapat @@ -1140,28 +1138,28 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Kapsayıcı - + Karıştır - + Karışık Oynat - + Karışık Oynat: Açık - - + + Karışık Oynat: Kapalı @@ -1171,12 +1169,12 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Kaydedilecek oynatma listesi yok. - + Kaydedilemedi @@ -1186,17 +1184,17 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Kaynak - + Kaynak koddan çalışan kopya kurulumla güncellenmez. - + Kaynak kodu: @@ -1206,56 +1204,56 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Kenarlık - - + + Kenarlık kalınlığı - - - - - + + + + + Klasör Aç - - - + + + Klasör Açılamadı - + Klasör okunamadı. Klasöre erişim izniniz olmayabilir. - + Klasör seçilemedi. Lütfen tekrar deneyin. - - - + + + Klavye Kısayolları - + Kontrast: - + Konum @@ -1270,8 +1268,8 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - - + + Korece @@ -1286,23 +1284,23 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Kullanılmıyor - + Küçük - + Küçült - - + + Lehçe @@ -1312,83 +1310,83 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Listenin başındasınız. - + Listenin sonuna ulaştınız. - - + + Listeyi Tekrarla - + Listeyi Temizle - + MLC Player Yanıt Vermiyor - + MLC Player katkıcıları - + MLC Player simgesi - + MLC Player {version} indirilmeye hazır. - + MPV Bileşeni Bulunamadı - + MPV bileşeni yüklenemedi. Çözüm: Programın 'bin' klasörünün eksiksiz olduğundan emin olun. Programı kurulum klasöründen çalıştırın. - + MPV tabanlı minimal video oynatıcı. - + Medya Aç - - + + Medya Bilgisi - - + + Medya Dosyaları - + Menü @@ -1403,12 +1401,12 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Normal - + Normal Pencereye Dön @@ -1468,76 +1466,76 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Ortam - - - + + + Oynat - - + + Oynat/Duraklat - + Oynat/Duraklat (Space) - - + + Oynatma - - + + Oynatma Hızı - + Oynatma Hızı Değiştirilemedi - - - - - + + + + + - + Oynatma Listesi - - + + Oynatma Listesi Aç - + Oynatma Listesine Dosya Ekle - + Oynatma Listesine Ekle - - + + Oynatma Listesini Göster @@ -1547,34 +1545,34 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - - + + Oynatma Listesini Kaydet - + Oynatma Listesini Tekrarla - + Oynatma hızı değiştirilemedi. Lütfen başka bir hız deneyin. - - + + Oynatma listesi - + Oynatma listesi (kaydet/aç - .m3u) - + Oynatma listesi açılamadı. Dosya bozuk veya okunamıyor olabilir. @@ -1585,8 +1583,8 @@ Dosyaları buraya sürükleyin - - + + Oynatma listesi boş. @@ -1596,60 +1594,60 @@ Dosyaları buraya sürükleyin - + Oynatma listesi kaydedilemedi. Dosyanın yazılabileceği bir konum seçmeyi deneyin. - + Oynatma listesinde geçerli dosya bulunamadı. - + Oynatma listesindeki dosya açılamadı. Dosya taşınmış veya silinmiş olabilir. - + Oynatıcı Başlatılamadı - + Oynatıcı ayarları okunamadığı için bağlantı güvenli biçimde açılamadı. Lütfen tekrar deneyin. - - + + Oynatıcı ayarları okunamadığı için dosya güvenli biçimde açılamadı. Lütfen tekrar deneyin. - + Oynatıcı ayarları okunamadığı için klasör güvenli biçimde açılamadı. Lütfen tekrar deneyin. - + Oynatıcı ayarları okunamadığı için oynatma listesi güvenli biçimde açılamadı. Lütfen tekrar deneyin. - + Oynatıcı ayarı uygulanamadı (video ayarı desteklenmiyor). Bu işlem mpv'nin bu sürümünde bulunmayan bir özellik kullanmaya çalıştı. Diğer ayarlarla devam edebilirsiniz. - + Oynatıcı başlatılamadı. Programın bin klasörünü kontrol edip tekrar deneyin. - + Parlaklık: @@ -1664,34 +1662,34 @@ Bu işlem mpv'nin bu sürümünde bulunmayan bir özellik kullanmaya çalıştı - + Parça - + Parça süresi - + Piksel biçimi - - + + Portekizce - - + + Profil - + Program çalıştırılamadı: gerekli MPV bileşeni (mpv-2.dll) bulunamadı. Çözüm: Programın yanındaki 'bin' klasörünün eksiksiz olduğundan emin olun. Programı kurulum klasöründen başlatın. @@ -1710,46 +1708,46 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Renk - + Renk aralığı - + Renk standardı - + Renk yok (Şeffaf) - + Renk, arka plan ve kenarlık burada görünür. - - - - + + + + Resim İçinde Resim - + Resim İçinde Resimden Çık - - + + Rusça @@ -1759,15 +1757,15 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - - - + + + Sağ Ok - - + + Senkron @@ -1777,126 +1775,132 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - - - + + + Ses - + Ses Aygıtı Değiştirilemedi - + Ses Kanalı Seçilemedi - - - - + + + + Ses Parçası - - + + Ses Seviyesi - + Ses Seviyesi Artır - + Ses Seviyesi Azalt - + Ses aygıtı değiştirildi - + Ses aygıtı değiştirilemedi. Lütfen başka bir aygıt deneyin. - + Ses biçimi - + Ses kanalı değiştirilemedi. Lütfen başka bir kanal deneyin. - - + + Ses parçaları yüklenemedi - - + + Ses parçası bulunamadı - + Ses parçası yok. - - + + Ses Çıkışı - + Ses çıkışları yüklenemedi - + Ses çıkışı bulunamadı - - - + + Ses: %{volume} + + + + + + Sesi Aç - + Sesi Kapat/Aç - + Sesi Kıs - - - - + + + + + Sessiz - + Sessiz (M) @@ -1908,12 +1912,12 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Seçilen konum geçerli bir medya dosyası değil. - + Seçili @@ -1923,14 +1927,14 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Sistem dili - - - + + + Sol Ok @@ -1940,45 +1944,45 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - - + + Son Açılanlar - + Son açılan dosya yok - + Son açılanlar - - + + Sonraki - + Sonraki Bölüm - - + + Sonraki Parça - + Süre - + Sürükle-bırak ile dosya açma @@ -1993,45 +1997,45 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Sürüm - + Sürüm notları → - + Sıfırla - + Sınırlı - + Tam - - - - + + + + Tam Ekran - + Tam Ekran (F) - + Tam Ekrandan Çık @@ -2041,7 +2045,7 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Tamam @@ -2062,9 +2066,9 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - - - + + + Tek Dosyayı Tekrarla @@ -2080,75 +2084,75 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - - + + Tekrar: Kapalı - + Tekrarla / karışık oynatma modları - + Temsili video önizlemesi — gerçek video çıktısı değildir - + Tür - - + + Türkçe - - - + + + URL'den Oynat - + URL'den oynatma - - + + Ukraynaca - - - - + + + + Uyarı - + Uygula - + Uygulamadan Çık - - - - - + + + + + Varsayılan @@ -2158,7 +2162,7 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Varsayılan ayarlara dön @@ -2168,7 +2172,7 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Varsayılana Dön @@ -2178,31 +2182,31 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Video - - - - - + + + + + Video Ayarları - + Video Parçası - + Video URL'si giriniz: - + Video ayarları @@ -2217,90 +2221,90 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Video parçası yok. - + Yardım - + Yazı - - + + Yazı boyutu - + Yeni sürüm kullanıma hazır - + Yok - + Yolu Kopyala - + Yorum - + Yukarı Ok - + Yönetmen Yorumu - + Zaman pozisyonunu girin (MM:SS veya HH:MM:SS formatında): - + Zamana Gidilemedi - - - + + + Zamana Git - + Zamana Git... - + Zamanı MM:SS veya HH:MM:SS biçiminde ve 0 ile %1 saniye arasında girin. - - - - + + + + Zorunlu @@ -2310,31 +2314,31 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + kısmen saydam - - + + opak - + rengi - + saniye - - - - + + + + sn @@ -2344,40 +2348,50 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + tamamen saydam - + + {count} bayt + + + + + {whole},{fraction} + + + + Çalışan MLC Player isteği alamadı. Açık pencereyi kapatıp tekrar deneyin. - - - + + + Çince - + Çok büyük - + Çok küçük - + Çözünürlük - - + + Çıkış @@ -2387,52 +2401,52 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Önce bir video açın, sonra altyazıyı ekleyin. - - + + Önce bir video açın. - - + + Önce bir video dosyası açın. - + Önce video açın - - + + Önceki - + Önceki Bölüm - - + + Önceki Parça - + Örnekleme - + Özellikler: @@ -2443,7 +2457,7 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + İleri @@ -2458,19 +2472,19 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + İndiriliyor… - + İndirme tamamlanıyor — pencere işlem bitince kapanacak. - - + + İngilizce @@ -2485,30 +2499,30 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + İptal - - + + İspanyolca - + İsterseniz güncellemeyi GitHub sayfasından elle indirebilirsiniz. - - + + İtalyanca - + İzlemeye hazır @@ -2518,18 +2532,18 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + İşitme engelliler için - - + + Şeffaflık - + Şeffaflık Seviyesi diff --git a/translations/mlcplayer_en.ts b/translations/mlcplayer_en.ts index 38041fd..54118ee 100644 --- a/translations/mlcplayer_en.ts +++ b/translations/mlcplayer_en.ts @@ -3,30 +3,35 @@ MLCPlayer - - - + + %{value} + {value}% + + + + + 30 Saniye Geri Back 30 Seconds - - - + + + 30 Saniye İleri Forward 30 Seconds - - - + + + 5 Saniye Geri Back 5 Seconds - - - + + + 5 Saniye İleri Forward 5 Seconds @@ -56,90 +61,89 @@ The API key, username or password could not be verified. - + Adres Address - + Adresi Kopyala Copy Address - - - + + + Almanca German - + Alt Yazı Subtitles - - + + Altyazı Subtitle - - - + + + Altyazı Ayarları Subtitle Settings - + Altyazı Ayarları Uygulanamadı Could Not Apply Subtitle Settings - + Altyazı Bul Find Subtitles - - + + Altyazı Bulunamadı Subtitle Not Found - + Altyazı Bul… Find Subtitles… - + Altyazı Değiştirilemedi Could Not Change Subtitles - + Altyazı Dosyaları Subtitles Files - + Altyazı Dosyası Ekle Add Subtitle File - - - + + + Altyazı Ekle Add Subtitle - - - - - + + + + Altyazı Eklenemedi Could Not Add Subtitle @@ -160,15 +164,15 @@ Close the Subtitle Center - - - - + + + + Altyazı Parçası Subtitle Track - + Altyazı Seçilemedi Could Not Select Subtitle @@ -183,7 +187,7 @@ The subtitle settings could not be applied. The previous settings were kept; please try again. - + Altyazı bulunamadı No subtitles found @@ -193,7 +197,7 @@ No subtitles found. - + Altyazı desteği Subtitle support @@ -203,28 +207,22 @@ Subtitle language - - + + Altyazı dosyası bulunamadı. Dosyanın yerini kontrol edin. The subtitle file was not found. Check where the file is. - - + Altyazı eklendi Subtitle added - - + + Altyazı eklenemedi. Dosyanın desteklenen ve okunabilir bir altyazı dosyası olduğundan emin olun. The subtitle could not be added. Make sure the file is a supported and readable subtitle file. - - - Altyazı eklenemedi. Dosyanın hasarlı veya desteklenmeyen bir format olması mümkün. - The subtitle could not be added. The file may be damaged or in an unsupported format. - Altyazı indirildi ancak oynatıcıya uygulanamadı. @@ -256,29 +254,29 @@ A subtitle operation is in progress; it will close when it finishes. - - + + Altyazı parçaları yüklenemedi Subtitle tracks could not be loaded - - + + Altyazı parçası bulunamadı No subtitle track found - + Altyazı parçası yok. No subtitle track. - + Altyazı senkronu Subtitle synchronisation - + Altyazı senkronu. Pozitif değer altyazıyı geciktirir, negatif değer öne alır. Subtitle timing. A positive value delays the subtitle, a negative one advances it. @@ -288,7 +286,7 @@ An unexpected problem occurred in the subtitle service. - + Altyazı seçilemedi. Lütfen başka bir altyazı parçasını deneyin. The subtitle could not be selected. Please try another subtitle track. @@ -298,21 +296,21 @@ Subtitle result: - + Altyazı sıradaki videoya otomatik bağlanamadı. Videoyu oynatın ve altyazıyı yeniden bırakın. The subtitle could not be linked to the queued video automatically. Play the video, then drop the subtitle again. - - - - + + + + Altyazı yolu: Subtitle path: - - + + Altyazı yükleniyor... Loading subtitle... @@ -322,38 +320,38 @@ Searching for subtitles… - + Altyazılar açılıp kapatılamadı. Lütfen tekrar deneyin. Subtitles could not be turned on or off. Please try again. - + Altyazıları Aç Show Subtitles - + Altyazıları Gizle Hide Subtitles - + Altyazıları Göster Show Subtitles - - + + Altyazıları Göster/Gizle Show/Hide Subtitles - + Altyazıları Göster/Gizle (S) Show/Hide Subtitles (S) - + Altyazıları Kapat Hide Subtitles @@ -388,23 +386,23 @@ Name to search for - - + + Arapça Arabic - + Araçlar Tools - + Arka plan Background - + Arka planı şeffaf yap (renk yok) Make the background transparent (no colour) @@ -456,13 +454,13 @@ Save the settings - + Ayarları uygula Apply the settings - - + + Açılamadı Could Not Open @@ -472,23 +470,23 @@ The network connection could not be established. Check your internet connection. - + Aşağı Ok Down Arrow - - + + Bağlantı Link - + Bağlantı Açılamadı Could Not Open Link - + Bağlantı açılıyor… Opening link… @@ -513,7 +511,7 @@ The connection timed out. Check your internet connection and try again. - + Bağlantıdan Oynat Play from Link @@ -523,22 +521,22 @@ Test Connection - + Başarılı Success - + Başlık Title - + Beklenmeyen Hata Unexpected Error - + Beklenmeyen bir hata oluştu. Program çalışmaya devam ediyor, ancak bu işlem tamamlanamadı. @@ -554,7 +552,7 @@ If the problem persists, restart the program. An unexpected problem occurred. - + Beklenmeyen bir veri hatası oluştu. Lütfen işlemi tekrar deneyin. Sorun devam ederse programı yeniden başlatın. @@ -568,38 +566,38 @@ Please try the operation again. If the problem persists, restart the program.Copy Details - + Bir videoyu buraya sürükleyin veya bilgisayarınızdan açın. Drag a video here or open one from your computer. - + Biraz büyük Slightly large - + Birçok video formatını destekler Supports many video formats - + Bit derinliği Bit depth - - + + Bitrate Bitrate - + Boyut Size - + Bu bağlantı açılamadı. Adresi ve internet bağlantınızı kontrol edip tekrar deneyin. This link could not be opened. Check the address and your internet connection, then try again. @@ -611,30 +609,30 @@ Hassas bilgiler otomatik olarak gizlenmiştir. Sensitive information has been hidden automatically. - + Bu bir altyazı önizlemesidir. This is a subtitle preview. - + Bu klasörde desteklenen medya dosyası bulunamadı. No supported media files were found in this folder. - + Bu paket mpv/FFmpeg bileşenini içerir; künyesi ve kaynak adresi için kurulum dizinindeki <code>licenses</code> klasörüne bakın. This package includes the mpv/FFmpeg component; see the <code>licenses</code> folder in the installation directory for its notice and source address. - + Bu program ÖZGÜR YAZILIMDIR ve GNU GPL sürüm 3 koşullarıyla dağıtılır. This program is FREE SOFTWARE and is distributed under the terms of the GNU GPL version 3. - - - - + + + + @@ -642,69 +640,69 @@ Sensitive information has been hidden automatically. Chapter - + Bölüm bilgisi yok No chapter information - + Bölüm bulunamadı No chapter found - - + + Bölümleri Yenile Refresh Chapters - + Büyük Large - - + + Büyüt Maximise - - + + Codec Codec - - + + Codec açıklaması Codec description - + Daha sonra Later - + Değişiklikleri iptal et Cancel the changes - + Değişiklikleri sürüm notlarında inceleyebilirsiniz. You can review the changes in the release notes. - + Dikey konum Vertical position - - - - + + + + Dil Language @@ -714,37 +712,37 @@ Sensitive information has been hidden automatically. The language change takes effect when MLC Player is restarted. - + Dolby Vision Dolby Vision - - + + Dosya File - - - - - - + + + + + + Dosya Aç Open File - - - - - + + + + + Dosya Açılamadı Could Not Open File - + Dosya Bulunamadı File Not Found @@ -754,27 +752,27 @@ Sensitive information has been hidden automatically. Add Files - + Dosya artık mevcut değil. Son Açılanlar listesinden kaldırıldı. The file no longer exists. It has been removed from Recent Files. - + Dosya açılamadı. Dosya silinmiş, taşınmış veya desteklenmeyen bir format olabilir. The file could not be opened. It may have been deleted or moved, or it may be in an unsupported format. - + Dosya açılamadı. Dosyanın mevcut ve desteklenen bir medya dosyası olduğunu kontrol edin. The file could not be opened. Check that the file exists and is a supported media file. - + Dosya açılamadı: The file could not be opened: - + Dosya bulunamadı. Dosya taşınmış veya silinmiş olabilir. Çözüm: Dosyanın yerini kontrol edip tekrar açmayı deneyin. @@ -783,7 +781,7 @@ Sensitive information has been hidden automatically. Solution: check where the file is and try opening it again. - + Dosyaya erişim izniniz yok. Çözüm: Dosyanın kilidini açın veya başka bir klasöre kopyalayın. @@ -792,26 +790,26 @@ Solution: check where the file is and try opening it again. Solution: unlock the file or copy it to another folder. - + Doygunluk: Saturation: - + Duraklat Pause - - - + + + Durdur Stop - - - + + + Durum Status @@ -821,71 +819,71 @@ Solution: unlock the file or copy it to another folder. There is no further technical detail. - + Ekran Görüntüsü (Ctrl+S) Screenshot (Ctrl+S) - - - + + + Ekran Görüntüsü Al Take Screenshot - + Ekran Görüntüsü Alınamadı Could Not Take Snapshot - + Ekran görüntüsü alma Taking snapshots - + Ekran görüntüsü almak için bir video oynatılıyor olmalıdır. A video must be playing to take a snapshot. - + Ekran görüntüsü kaydedildi: Snapshot saved: - + Ekran görüntüsü kaydedilemedi. Masaüstüne yazma iznini ve boş alanı kontrol edin. The snapshot could not be saved. Check your write permission for the desktop and the free space. - + En büyük Largest - + En güncel sürümü kullanıyorsunuz You are using the latest version - + En kalın Thickest - + Evet Yes - + FPS FPS - - - + + + Felemenkçe Dutch @@ -896,102 +894,102 @@ Solution: unlock the file or copy it to another folder. - - - + + + Fransızca French - + Gamma: Gamma: - + Genel General - + Genel bitrate Overall bitrate - + Geri Backward - + Geri Yükle Restore - + Gezinim Navigation - + Geçerli bir web adresi girin. Yalnız http:// ve https:// ile başlayan bağlantılar açılabilir. Enter a valid web address. Only links starting with http:// or https:// can be opened. - + Geçersiz Adres Invalid Address - + Geçersiz Zaman Invalid Time - + Girilen zaman konumuna gidilemedi. Zamanı tekrar kontrol edin. Could not go to the time you entered. Check the time again. - + GitHub sayfasını aç Open the GitHub page - + Gömülü Embedded - - + + Görüntü Video - + Görüntü oranı Aspect Ratio - + Görünüm View - + Güncelle Update - - - + + + Güncelleme Update - + Güncelleme Mevcut Update Available @@ -1016,12 +1014,12 @@ Solution: unlock the file or copy it to another folder. Could not check for updates. - + Güncelleme uygulanıyor… Applying the update… - + Güncellemeleri Denetle Check for Updates @@ -1032,7 +1030,7 @@ Solution: unlock the file or copy it to another folder. - + Günlük Yönetimi Log Management @@ -1042,12 +1040,12 @@ Solution: unlock the file or copy it to another folder. The log folder could not be opened. - + Günlükler temizlendi. The logs have been cleared. - + Günlükler temizlenemedi. Dosyalar başka bir program tarafından kullanılıyor olabilir. The logs could not be cleared. The files may be in use by another program. @@ -1073,15 +1071,15 @@ Solution: unlock the file or copy it to another folder. An untrusted download address was rejected. Nothing was downloaded. - - + + Hakkında About - - - + + + Harici External @@ -1091,7 +1089,7 @@ Solution: unlock the file or copy it to another folder. Error Details - + Hata Ayrıntılarını Görüntüle View Error Details @@ -1111,36 +1109,36 @@ Solution: unlock the file or copy it to another folder. Signing in with an account needs both the username and the password; either enter both or leave both empty. - + HİÇBİR GARANTİ VERİLMEZ; satılabilirlik veya belirli bir amaca uygunluk zımni garantisi dahil değildir. It comes with NO WARRANTY; not even the implied warranty of merchantability or fitness for a particular purpose. - - + + Japonca Japanese - + Kaldır Remove - + Kanal Channels - + - - - + + + Kapat Close @@ -1150,28 +1148,28 @@ Solution: unlock the file or copy it to another folder. Close (Esc) - + Kapsayıcı Container - + Karıştır Shuffle - + Karışık Oynat Shuffle - + Karışık Oynat: Açık Shuffle: On - - + + Karışık Oynat: Kapalı Shuffle: Off @@ -1181,12 +1179,12 @@ Solution: unlock the file or copy it to another folder. Category - + Kaydedilecek oynatma listesi yok. There is no playlist to save. - + Kaydedilemedi Could Not Save @@ -1196,17 +1194,17 @@ Solution: unlock the file or copy it to another folder. Save - + Kaynak Source - + Kaynak koddan çalışan kopya kurulumla güncellenmez. A copy running from source code cannot be updated by an installer. - + Kaynak kodu: Source code: @@ -1216,56 +1214,56 @@ Solution: unlock the file or copy it to another folder. Record number - + Kenarlık Border - - + + Kenarlık kalınlığı Border thickness - - - - - + + + + + Klasör Aç Open Folder - - - + + + Klasör Açılamadı Could Not Open Folder - + Klasör okunamadı. Klasöre erişim izniniz olmayabilir. The folder could not be read. You may not have permission to access it. - + Klasör seçilemedi. Lütfen tekrar deneyin. The folder could not be selected. Please try again. - - - + + + Klavye Kısayolları Keyboard Shortcuts - + Kontrast: Contrast: - + Konum Location @@ -1280,8 +1278,8 @@ Solution: unlock the file or copy it to another folder. Copied - - + + Korece Korean @@ -1296,23 +1294,23 @@ Solution: unlock the file or copy it to another folder. User message - + Kullanılmıyor Unused - + Küçük Small - + Küçült Minimise - - + + Lehçe Polish @@ -1322,54 +1320,54 @@ Solution: unlock the file or copy it to another folder. Search the list - + Listenin başındasınız. You are at the start of the playlist. - + Listenin sonuna ulaştınız. You have reached the end of the playlist. - - + + Listeyi Tekrarla Repeat Playlist - + Listeyi Temizle Clear Playlist - + MLC Player Yanıt Vermiyor MLC Player Is Not Responding - + MLC Player katkıcıları MLC Player contributors - + MLC Player simgesi MLC Player icon - + MLC Player {version} indirilmeye hazır. MLC Player {version} is ready to download. - + MPV Bileşeni Bulunamadı MPV Component Not Found - + MPV bileşeni yüklenemedi. Çözüm: Programın 'bin' klasörünün eksiksiz olduğundan emin olun. Programı kurulum klasöründen çalıştırın. @@ -1378,29 +1376,29 @@ Solution: unlock the file or copy it to another folder. Solution: make sure the program's 'bin' folder is complete. Run the program from its installation folder. - + MPV tabanlı minimal video oynatıcı. a minimal MPV-based video player. - + Medya Aç Open Media - - + + Medya Bilgisi Media Information - - + + Medya Dosyaları Media Files - + Menü Menu @@ -1415,12 +1413,12 @@ Solution: make sure the program's 'bin' folder is complete. Run the program from Current log size: - + Normal Normal - + Normal Pencereye Dön Return to Normal Window @@ -1480,76 +1478,76 @@ Solution: make sure the program's 'bin' folder is complete. Run the program from OpenSubtitles.com username (optional) - + Ortam Media - - - + + + Oynat Play - - + + Oynat/Duraklat Play/Pause - + Oynat/Duraklat (Space) Play/Pause (Space) - - + + Oynatma Playback - - + + Oynatma Hızı Playback Speed - + Oynatma Hızı Değiştirilemedi Could Not Change Playback Speed - - - - - + + + + + - + Oynatma Listesi Playlist - - + + Oynatma Listesi Aç Open Playlist - + Oynatma Listesine Dosya Ekle Add Files to Playlist - + Oynatma Listesine Ekle Add to Playlist - - + + Oynatma Listesini Göster Show Playlist @@ -1559,34 +1557,34 @@ Solution: make sure the program's 'bin' folder is complete. Run the program from Close the Playlist - - + + Oynatma Listesini Kaydet Save Playlist - + Oynatma Listesini Tekrarla Repeat Playlist - + Oynatma hızı değiştirilemedi. Lütfen başka bir hız deneyin. The playback speed could not be changed. Please try another speed. - - + + Oynatma listesi Playlist - + Oynatma listesi (kaydet/aç - .m3u) Playlist (save/open - .m3u) - + Oynatma listesi açılamadı. Dosya bozuk veya okunamıyor olabilir. The playlist could not be opened. The file may be corrupt or unreadable. @@ -1598,8 +1596,8 @@ Dosyaları buraya sürükleyin Drag files here - - + + Oynatma listesi boş. The playlist is empty. @@ -1609,48 +1607,48 @@ Drag files here Adjust the playlist width - + Oynatma listesi kaydedilemedi. Dosyanın yazılabileceği bir konum seçmeyi deneyin. The playlist could not be saved. Try choosing a location where the file can be written. - + Oynatma listesinde geçerli dosya bulunamadı. No valid files were found in the playlist. - + Oynatma listesindeki dosya açılamadı. Dosya taşınmış veya silinmiş olabilir. The file in the playlist could not be opened. It may have been moved or deleted. - + Oynatıcı Başlatılamadı Could Not Start the Player - + Oynatıcı ayarları okunamadığı için bağlantı güvenli biçimde açılamadı. Lütfen tekrar deneyin. The link could not be opened safely because the player settings could not be read. Please try again. - - + + Oynatıcı ayarları okunamadığı için dosya güvenli biçimde açılamadı. Lütfen tekrar deneyin. The file could not be opened safely because the player settings could not be read. Please try again. - + Oynatıcı ayarları okunamadığı için klasör güvenli biçimde açılamadı. Lütfen tekrar deneyin. The folder could not be opened safely because the player settings could not be read. Please try again. - + Oynatıcı ayarları okunamadığı için oynatma listesi güvenli biçimde açılamadı. Lütfen tekrar deneyin. The playlist could not be opened safely because the player settings could not be read. Please try again. - + Oynatıcı ayarı uygulanamadı (video ayarı desteklenmiyor). Bu işlem mpv'nin bu sürümünde bulunmayan bir özellik kullanmaya çalıştı. Diğer ayarlarla devam edebilirsiniz. @@ -1659,12 +1657,12 @@ Bu işlem mpv'nin bu sürümünde bulunmayan bir özellik kullanmaya çalıştı This operation tried to use a feature that is not present in this version of mpv. You can continue with the other settings. - + Oynatıcı başlatılamadı. Programın bin klasörünü kontrol edip tekrar deneyin. The player could not be started. Check the program's bin folder and try again. - + Parlaklık: Brightness: @@ -1679,34 +1677,34 @@ This operation tried to use a feature that is not present in this version of mpv Show password - + Parça Track - + Parça süresi Track duration - + Piksel biçimi Pixel format - - + + Portekizce Portuguese - - + + Profil Profile - + Program çalıştırılamadı: gerekli MPV bileşeni (mpv-2.dll) bulunamadı. Çözüm: Programın yanındaki 'bin' klasörünün eksiksiz olduğundan emin olun. Programı kurulum klasöründen başlatın. @@ -1729,46 +1727,46 @@ That folder may have been deleted, or the program may have been moved elsewhere. Rating - + Renk Colour - + Renk aralığı Color range - + Renk standardı Color primaries - + Renk yok (Şeffaf) No colour (Transparent) - + Renk, arka plan ve kenarlık burada görünür. The colour, background and border appear here. - - - - + + + + Resim İçinde Resim Picture in Picture - + Resim İçinde Resimden Çık Exit Picture in Picture - - + + Rusça Russian @@ -1778,15 +1776,15 @@ That folder may have been deleted, or the program may have been moved elsewhere. Retention policy: at most 2 MiB of active file plus 1 backup file. When the limit is passed, the oldest backup is deleted. - - - + + + Sağ Ok Right Arrow - - + + Senkron Sync @@ -1796,126 +1794,132 @@ That folder may have been deleted, or the program may have been moved elsewhere. The service response is invalid. Try again later. - - - + + + Ses Audio - + Ses Aygıtı Değiştirilemedi Could Not Change Audio Device - + Ses Kanalı Seçilemedi Could Not Select Audio Track - - - - + + + + Ses Parçası Audio Track - - + + Ses Seviyesi Volume - + Ses Seviyesi Artır Volume Up - + Ses Seviyesi Azalt Volume Down - + Ses aygıtı değiştirildi Audio device changed - + Ses aygıtı değiştirilemedi. Lütfen başka bir aygıt deneyin. The audio device could not be changed. Please try another device. - + Ses biçimi Audio format - + Ses kanalı değiştirilemedi. Lütfen başka bir kanal deneyin. The audio track could not be changed. Please try another track. - - + + Ses parçaları yüklenemedi Audio tracks could not be loaded - - + + Ses parçası bulunamadı No audio track found - + Ses parçası yok. No audio track. - - + + Ses Çıkışı Audio Device - + Ses çıkışları yüklenemedi Audio devices could not be loaded - + Ses çıkışı bulunamadı No audio device found - - - + + Ses: %{volume} + Volume: {volume}% + + + + + Sesi Aç Volume Up - + Sesi Kapat/Aç Mute/Unmute - + Sesi Kıs Volume Down - - - - + + + + + Sessiz Muted - + Sessiz (M) Mute (M) @@ -1927,12 +1931,12 @@ That folder may have been deleted, or the program may have been moved elsewhere. Season - + Seçilen konum geçerli bir medya dosyası değil. The selected location is not a valid media file. - + Seçili Selected @@ -1942,14 +1946,14 @@ That folder may have been deleted, or the program may have been moved elsewhere. Because the selected subtitle is image based, the colour, background and border settings are not applied to this track. - + Sistem dili System language - - - + + + Sol Ok Left Arrow @@ -1959,45 +1963,45 @@ That folder may have been deleted, or the program may have been moved elsewhere. Drag left or right to resize the list - - + + Son Açılanlar Recent Files - + Son açılan dosya yok No recent files - + Son açılanlar Recent files - - + + Sonraki Next - + Sonraki Bölüm Next Chapter - - + + Sonraki Parça Next Track - + Süre Duration - + Sürükle-bırak ile dosya açma Opening files by drag and drop @@ -2012,45 +2016,45 @@ That folder may have been deleted, or the program may have been moved elsewhere. Drag to reorder • Double-click to play - + Sürüm Version - + Sürüm notları → Release notes → - + Sıfırla Reset - + Sınırlı Limited - + Tam Full - - - - + + + + Tam Ekran Fullscreen - + Tam Ekran (F) Fullscreen (F) - + Tam Ekrandan Çık Leave Fullscreen @@ -2060,7 +2064,7 @@ That folder may have been deleted, or the program may have been moved elsewhere. Exact match - + Tamam OK @@ -2081,9 +2085,9 @@ That folder may have been deleted, or the program may have been moved elsewhere. Date - - - + + + Tek Dosyayı Tekrarla Repeat One @@ -2099,75 +2103,75 @@ That folder may have been deleted, or the program may have been moved elsewhere. Technical summary - - + + Tekrar: Kapalı Repeat: Off - + Tekrarla / karışık oynatma modları Repeat / shuffle playback modes - + Temsili video önizlemesi — gerçek video çıktısı değildir Representative preview — not the actual video output - + Tür Type - - + + Türkçe Turkish - - - + + + URL'den Oynat Open URL - + URL'den oynatma Playing from a URL - - + + Ukraynaca Ukrainian - - - - + + + + Uyarı Warning - + Uygula Apply - + Uygulamadan Çık Quit the Application - - - - - + + + + + Varsayılan Default @@ -2177,7 +2181,7 @@ That folder may have been deleted, or the program may have been moved elsewhere. Default subtitle language - + Varsayılan ayarlara dön Return to the default settings @@ -2187,7 +2191,7 @@ That folder may have been deleted, or the program may have been moved elsewhere. Default language - + Varsayılana Dön Restore Defaults @@ -2197,31 +2201,31 @@ That folder may have been deleted, or the program may have been moved elsewhere. Cancel - + Video Video - - - - - + + + + + Video Ayarları Video Settings - + Video Parçası Video Track - + Video URL'si giriniz: Enter the video URL: - + Video ayarları Video settings @@ -2236,90 +2240,90 @@ That folder may have been deleted, or the program may have been moved elsewhere. Video thumbnail - + Video parçası yok. No video track. - + Yardım Help - + Yazı Text - - + + Yazı boyutu Text size - + Yeni sürüm kullanıma hazır A new version is ready - + Yok None - + Yolu Kopyala Copy Path - + Yorum Commentary - + Yukarı Ok Up Arrow - + Yönetmen Yorumu Director's Commentary - + Zaman pozisyonunu girin (MM:SS veya HH:MM:SS formatında): Enter the time position (in MM:SS or HH:MM:SS format): - + Zamana Gidilemedi Could Not Go to Time - - - + + + Zamana Git Go to Time - + Zamana Git... Go to Time... - + Zamanı MM:SS veya HH:MM:SS biçiminde ve 0 ile %1 saniye arasında girin. Enter the time in MM:SS or HH:MM:SS format, between 0 and %1 seconds. - - - - + + + + Zorunlu Forced @@ -2329,31 +2333,31 @@ That folder may have been deleted, or the program may have been moved elsewhere. downloads - + kısmen saydam partly transparent - - + + opak opaque - + rengi colour - + saniye seconds - - - - + + + + sn s @@ -2363,40 +2367,50 @@ That folder may have been deleted, or the program may have been moved elsewhere. results - + tamamen saydam fully transparent - + + {count} bayt + {count} bytes + + + + {whole},{fraction} + {whole}.{fraction} + + + Çalışan MLC Player isteği alamadı. Açık pencereyi kapatıp tekrar deneyin. The running MLC Player could not receive the request. Close the open window and try again. - - - + + + Çince Chinese - + Çok büyük Very large - + Çok küçük Very small - + Çözünürlük Resolution - - + + Çıkış Exit @@ -2406,52 +2420,52 @@ That folder may have been deleted, or the program may have been moved elsewhere. Enter the API key first. - + Önce bir video açın, sonra altyazıyı ekleyin. Open a video first, then add the subtitle. - - + + Önce bir video açın. Open a video first. - - + + Önce bir video dosyası açın. Open a video file first. - + Önce video açın Open a video first. - - + + Önceki Previous - + Önceki Bölüm Previous Chapter - - + + Önceki Parça Previous Track - + Örnekleme Sample rate - + Özellikler: Features: @@ -2462,7 +2476,7 @@ That folder may have been deleted, or the program may have been moved elsewhere. items - + İleri Forward @@ -2477,19 +2491,19 @@ That folder may have been deleted, or the program may have been moved elsewhere. The downloaded file is much larger than expected; it was rejected for safety. - + İndiriliyor… Downloading… - + İndirme tamamlanıyor — pencere işlem bitince kapanacak. The download is finishing — the window will close when it is done. - - + + İngilizce English @@ -2504,30 +2518,30 @@ That folder may have been deleted, or the program may have been moved elsewhere. The internet video components are not installed. Install the MLC Player Internet Video add-on and try again. - + İptal Cancel - - + + İspanyolca Spanish - + İsterseniz güncellemeyi GitHub sayfasından elle indirebilirsiniz. If you prefer, you can download the update manually from the GitHub page. - - + + İtalyanca Italian - + İzlemeye hazır Ready to watch @@ -2537,18 +2551,18 @@ That folder may have been deleted, or the program may have been moved elsewhere. Hearing impaired - + İşitme engelliler için Hearing impaired - - + + Şeffaflık Transparency - + Şeffaflık Seviyesi Transparency Level diff --git a/translations/mlcplayer_es.ts b/translations/mlcplayer_es.ts index 436311e..e564f03 100644 --- a/translations/mlcplayer_es.ts +++ b/translations/mlcplayer_es.ts @@ -3,30 +3,35 @@ MLCPlayer - - - + + %{value} + + + + + + 30 Saniye Geri - - - + + + 30 Saniye İleri - - - + + + 5 Saniye Geri - - - + + + 5 Saniye İleri @@ -56,90 +61,89 @@ - + Adres - + Adresi Kopyala - - - + + + Almanca - + Alt Yazı - - + + Altyazı - - - + + + Altyazı Ayarları - + Altyazı Ayarları Uygulanamadı - + Altyazı Bul - - + + Altyazı Bulunamadı - + Altyazı Bul… - + Altyazı Değiştirilemedi - + Altyazı Dosyaları - + Altyazı Dosyası Ekle - - - + + + Altyazı Ekle - - - - - + + + + Altyazı Eklenemedi @@ -160,15 +164,15 @@ - - - - + + + + Altyazı Parçası - + Altyazı Seçilemedi @@ -183,7 +187,7 @@ - + Altyazı bulunamadı @@ -193,7 +197,7 @@ - + Altyazı desteği @@ -203,28 +207,22 @@ - - + + Altyazı dosyası bulunamadı. Dosyanın yerini kontrol edin. - - + Altyazı eklendi - - + + Altyazı eklenemedi. Dosyanın desteklenen ve okunabilir bir altyazı dosyası olduğundan emin olun. - - - Altyazı eklenemedi. Dosyanın hasarlı veya desteklenmeyen bir format olması mümkün. - - Altyazı indirildi ancak oynatıcıya uygulanamadı. @@ -256,29 +254,29 @@ - - + + Altyazı parçaları yüklenemedi - - + + Altyazı parçası bulunamadı - + Altyazı parçası yok. - + Altyazı senkronu - + Altyazı senkronu. Pozitif değer altyazıyı geciktirir, negatif değer öne alır. @@ -288,7 +286,7 @@ - + Altyazı seçilemedi. Lütfen başka bir altyazı parçasını deneyin. @@ -298,21 +296,21 @@ - + Altyazı sıradaki videoya otomatik bağlanamadı. Videoyu oynatın ve altyazıyı yeniden bırakın. - - - - + + + + Altyazı yolu: - - + + Altyazı yükleniyor... @@ -322,38 +320,38 @@ - + Altyazılar açılıp kapatılamadı. Lütfen tekrar deneyin. - + Altyazıları Aç - + Altyazıları Gizle - + Altyazıları Göster - - + + Altyazıları Göster/Gizle - + Altyazıları Göster/Gizle (S) - + Altyazıları Kapat @@ -388,23 +386,23 @@ - - + + Arapça - + Araçlar - + Arka plan - + Arka planı şeffaf yap (renk yok) @@ -456,13 +454,13 @@ - + Ayarları uygula - - + + Açılamadı @@ -472,23 +470,23 @@ - + Aşağı Ok - - + + Bağlantı - + Bağlantı Açılamadı - + Bağlantı açılıyor… @@ -513,7 +511,7 @@ - + Bağlantıdan Oynat @@ -523,22 +521,22 @@ - + Başarılı - + Başlık - + Beklenmeyen Hata - + Beklenmeyen bir hata oluştu. Program çalışmaya devam ediyor, ancak bu işlem tamamlanamadı. @@ -551,7 +549,7 @@ Sorun devam ederse programı yeniden başlatın. - + Beklenmeyen bir veri hatası oluştu. Lütfen işlemi tekrar deneyin. Sorun devam ederse programı yeniden başlatın. @@ -563,38 +561,38 @@ Lütfen işlemi tekrar deneyin. Sorun devam ederse programı yeniden başlatın. - + Bir videoyu buraya sürükleyin veya bilgisayarınızdan açın. - + Biraz büyük - + Birçok video formatını destekler - + Bit derinliği - - + + Bitrate - + Boyut - + Bu bağlantı açılamadı. Adresi ve internet bağlantınızı kontrol edip tekrar deneyin. @@ -605,30 +603,30 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Bu bir altyazı önizlemesidir. - + Bu klasörde desteklenen medya dosyası bulunamadı. - + Bu paket mpv/FFmpeg bileşenini içerir; künyesi ve kaynak adresi için kurulum dizinindeki <code>licenses</code> klasörüne bakın. - + Bu program ÖZGÜR YAZILIMDIR ve GNU GPL sürüm 3 koşullarıyla dağıtılır. - - - - + + + + @@ -636,69 +634,69 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Bölüm bilgisi yok - + Bölüm bulunamadı - - + + Bölümleri Yenile - + Büyük - - + + Büyüt - - + + Codec - - + + Codec açıklaması - + Daha sonra - + Değişiklikleri iptal et - + Değişiklikleri sürüm notlarında inceleyebilirsiniz. - + Dikey konum - - - - + + + + Dil @@ -708,37 +706,37 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Dolby Vision - - + + Dosya - - - - - - + + + + + + Dosya Aç - - - - - + + + + + Dosya Açılamadı - + Dosya Bulunamadı @@ -748,60 +746,60 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Dosya artık mevcut değil. Son Açılanlar listesinden kaldırıldı. - + Dosya açılamadı. Dosya silinmiş, taşınmış veya desteklenmeyen bir format olabilir. - + Dosya açılamadı. Dosyanın mevcut ve desteklenen bir medya dosyası olduğunu kontrol edin. - + Dosya açılamadı: - + Dosya bulunamadı. Dosya taşınmış veya silinmiş olabilir. Çözüm: Dosyanın yerini kontrol edip tekrar açmayı deneyin. - + Dosyaya erişim izniniz yok. Çözüm: Dosyanın kilidini açın veya başka bir klasöre kopyalayın. - + Doygunluk: - + Duraklat - - - + + + Durdur - - - + + + Durum @@ -811,71 +809,71 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Ekran Görüntüsü (Ctrl+S) - - - + + + Ekran Görüntüsü Al - + Ekran Görüntüsü Alınamadı - + Ekran görüntüsü alma - + Ekran görüntüsü almak için bir video oynatılıyor olmalıdır. - + Ekran görüntüsü kaydedildi: - + Ekran görüntüsü kaydedilemedi. Masaüstüne yazma iznini ve boş alanı kontrol edin. - + En büyük - + En güncel sürümü kullanıyorsunuz - + En kalın - + Evet - + FPS - - - + + + Felemenkçe @@ -886,102 +884,102 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - - - + + + Fransızca - + Gamma: - + Genel - + Genel bitrate - + Geri - + Geri Yükle - + Gezinim - + Geçerli bir web adresi girin. Yalnız http:// ve https:// ile başlayan bağlantılar açılabilir. - + Geçersiz Adres - + Geçersiz Zaman - + Girilen zaman konumuna gidilemedi. Zamanı tekrar kontrol edin. - + GitHub sayfasını aç - + Gömülü - - + + Görüntü - + Görüntü oranı - + Görünüm - + Güncelle - - - + + + Güncelleme - + Güncelleme Mevcut @@ -1006,12 +1004,12 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Güncelleme uygulanıyor… - + Güncellemeleri Denetle @@ -1022,7 +1020,7 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Günlük Yönetimi @@ -1032,12 +1030,12 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Günlükler temizlendi. - + Günlükler temizlenemedi. Dosyalar başka bir program tarafından kullanılıyor olabilir. @@ -1063,15 +1061,15 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - - + + Hakkında - - - + + + Harici @@ -1081,7 +1079,7 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Hata Ayrıntılarını Görüntüle @@ -1101,36 +1099,36 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + HİÇBİR GARANTİ VERİLMEZ; satılabilirlik veya belirli bir amaca uygunluk zımni garantisi dahil değildir. - - + + Japonca - + Kaldır - + Kanal - + - - - + + + Kapat @@ -1140,28 +1138,28 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Kapsayıcı - + Karıştır - + Karışık Oynat - + Karışık Oynat: Açık - - + + Karışık Oynat: Kapalı @@ -1171,12 +1169,12 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Kaydedilecek oynatma listesi yok. - + Kaydedilemedi @@ -1186,17 +1184,17 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Kaynak - + Kaynak koddan çalışan kopya kurulumla güncellenmez. - + Kaynak kodu: @@ -1206,56 +1204,56 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Kenarlık - - + + Kenarlık kalınlığı - - - - - + + + + + Klasör Aç - - - + + + Klasör Açılamadı - + Klasör okunamadı. Klasöre erişim izniniz olmayabilir. - + Klasör seçilemedi. Lütfen tekrar deneyin. - - - + + + Klavye Kısayolları - + Kontrast: - + Konum @@ -1270,8 +1268,8 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - - + + Korece @@ -1286,23 +1284,23 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Kullanılmıyor - + Küçük - + Küçült - - + + Lehçe @@ -1312,83 +1310,83 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Listenin başındasınız. - + Listenin sonuna ulaştınız. - - + + Listeyi Tekrarla - + Listeyi Temizle - + MLC Player Yanıt Vermiyor - + MLC Player katkıcıları - + MLC Player simgesi - + MLC Player {version} indirilmeye hazır. - + MPV Bileşeni Bulunamadı - + MPV bileşeni yüklenemedi. Çözüm: Programın 'bin' klasörünün eksiksiz olduğundan emin olun. Programı kurulum klasöründen çalıştırın. - + MPV tabanlı minimal video oynatıcı. - + Medya Aç - - + + Medya Bilgisi - - + + Medya Dosyaları - + Menü @@ -1403,12 +1401,12 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Normal - + Normal Pencereye Dön @@ -1468,76 +1466,76 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Ortam - - - + + + Oynat - - + + Oynat/Duraklat - + Oynat/Duraklat (Space) - - + + Oynatma - - + + Oynatma Hızı - + Oynatma Hızı Değiştirilemedi - - - - - + + + + + - + Oynatma Listesi - - + + Oynatma Listesi Aç - + Oynatma Listesine Dosya Ekle - + Oynatma Listesine Ekle - - + + Oynatma Listesini Göster @@ -1547,34 +1545,34 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - - + + Oynatma Listesini Kaydet - + Oynatma Listesini Tekrarla - + Oynatma hızı değiştirilemedi. Lütfen başka bir hız deneyin. - - + + Oynatma listesi - + Oynatma listesi (kaydet/aç - .m3u) - + Oynatma listesi açılamadı. Dosya bozuk veya okunamıyor olabilir. @@ -1585,8 +1583,8 @@ Dosyaları buraya sürükleyin - - + + Oynatma listesi boş. @@ -1596,60 +1594,60 @@ Dosyaları buraya sürükleyin - + Oynatma listesi kaydedilemedi. Dosyanın yazılabileceği bir konum seçmeyi deneyin. - + Oynatma listesinde geçerli dosya bulunamadı. - + Oynatma listesindeki dosya açılamadı. Dosya taşınmış veya silinmiş olabilir. - + Oynatıcı Başlatılamadı - + Oynatıcı ayarları okunamadığı için bağlantı güvenli biçimde açılamadı. Lütfen tekrar deneyin. - - + + Oynatıcı ayarları okunamadığı için dosya güvenli biçimde açılamadı. Lütfen tekrar deneyin. - + Oynatıcı ayarları okunamadığı için klasör güvenli biçimde açılamadı. Lütfen tekrar deneyin. - + Oynatıcı ayarları okunamadığı için oynatma listesi güvenli biçimde açılamadı. Lütfen tekrar deneyin. - + Oynatıcı ayarı uygulanamadı (video ayarı desteklenmiyor). Bu işlem mpv'nin bu sürümünde bulunmayan bir özellik kullanmaya çalıştı. Diğer ayarlarla devam edebilirsiniz. - + Oynatıcı başlatılamadı. Programın bin klasörünü kontrol edip tekrar deneyin. - + Parlaklık: @@ -1664,34 +1662,34 @@ Bu işlem mpv'nin bu sürümünde bulunmayan bir özellik kullanmaya çalıştı - + Parça - + Parça süresi - + Piksel biçimi - - + + Portekizce - - + + Profil - + Program çalıştırılamadı: gerekli MPV bileşeni (mpv-2.dll) bulunamadı. Çözüm: Programın yanındaki 'bin' klasörünün eksiksiz olduğundan emin olun. Programı kurulum klasöründen başlatın. @@ -1710,46 +1708,46 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Renk - + Renk aralığı - + Renk standardı - + Renk yok (Şeffaf) - + Renk, arka plan ve kenarlık burada görünür. - - - - + + + + Resim İçinde Resim - + Resim İçinde Resimden Çık - - + + Rusça @@ -1759,15 +1757,15 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - - - + + + Sağ Ok - - + + Senkron @@ -1777,126 +1775,132 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - - - + + + Ses - + Ses Aygıtı Değiştirilemedi - + Ses Kanalı Seçilemedi - - - - + + + + Ses Parçası - - + + Ses Seviyesi - + Ses Seviyesi Artır - + Ses Seviyesi Azalt - + Ses aygıtı değiştirildi - + Ses aygıtı değiştirilemedi. Lütfen başka bir aygıt deneyin. - + Ses biçimi - + Ses kanalı değiştirilemedi. Lütfen başka bir kanal deneyin. - - + + Ses parçaları yüklenemedi - - + + Ses parçası bulunamadı - + Ses parçası yok. - - + + Ses Çıkışı - + Ses çıkışları yüklenemedi - + Ses çıkışı bulunamadı - - - + + Ses: %{volume} + + + + + + Sesi Aç - + Sesi Kapat/Aç - + Sesi Kıs - - - - + + + + + Sessiz - + Sessiz (M) @@ -1908,12 +1912,12 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Seçilen konum geçerli bir medya dosyası değil. - + Seçili @@ -1923,14 +1927,14 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Sistem dili - - - + + + Sol Ok @@ -1940,45 +1944,45 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - - + + Son Açılanlar - + Son açılan dosya yok - + Son açılanlar - - + + Sonraki - + Sonraki Bölüm - - + + Sonraki Parça - + Süre - + Sürükle-bırak ile dosya açma @@ -1993,45 +1997,45 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Sürüm - + Sürüm notları → - + Sıfırla - + Sınırlı - + Tam - - - - + + + + Tam Ekran - + Tam Ekran (F) - + Tam Ekrandan Çık @@ -2041,7 +2045,7 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Tamam @@ -2062,9 +2066,9 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - - - + + + Tek Dosyayı Tekrarla @@ -2080,75 +2084,75 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - - + + Tekrar: Kapalı - + Tekrarla / karışık oynatma modları - + Temsili video önizlemesi — gerçek video çıktısı değildir - + Tür - - + + Türkçe - - - + + + URL'den Oynat - + URL'den oynatma - - + + Ukraynaca - - - - + + + + Uyarı - + Uygula - + Uygulamadan Çık - - - - - + + + + + Varsayılan @@ -2158,7 +2162,7 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Varsayılan ayarlara dön @@ -2168,7 +2172,7 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Varsayılana Dön @@ -2178,31 +2182,31 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Video - - - - - + + + + + Video Ayarları - + Video Parçası - + Video URL'si giriniz: - + Video ayarları @@ -2217,90 +2221,90 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Video parçası yok. - + Yardım - + Yazı - - + + Yazı boyutu - + Yeni sürüm kullanıma hazır - + Yok - + Yolu Kopyala - + Yorum - + Yukarı Ok - + Yönetmen Yorumu - + Zaman pozisyonunu girin (MM:SS veya HH:MM:SS formatında): - + Zamana Gidilemedi - - - + + + Zamana Git - + Zamana Git... - + Zamanı MM:SS veya HH:MM:SS biçiminde ve 0 ile %1 saniye arasında girin. - - - - + + + + Zorunlu @@ -2310,31 +2314,31 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + kısmen saydam - - + + opak - + rengi - + saniye - - - - + + + + sn @@ -2344,40 +2348,50 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + tamamen saydam - + + {count} bayt + + + + + {whole},{fraction} + + + + Çalışan MLC Player isteği alamadı. Açık pencereyi kapatıp tekrar deneyin. - - - + + + Çince - + Çok büyük - + Çok küçük - + Çözünürlük - - + + Çıkış @@ -2387,52 +2401,52 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Önce bir video açın, sonra altyazıyı ekleyin. - - + + Önce bir video açın. - - + + Önce bir video dosyası açın. - + Önce video açın - - + + Önceki - + Önceki Bölüm - - + + Önceki Parça - + Örnekleme - + Özellikler: @@ -2443,7 +2457,7 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + İleri @@ -2458,19 +2472,19 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + İndiriliyor… - + İndirme tamamlanıyor — pencere işlem bitince kapanacak. - - + + İngilizce @@ -2485,30 +2499,30 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + İptal - - + + İspanyolca - + İsterseniz güncellemeyi GitHub sayfasından elle indirebilirsiniz. - - + + İtalyanca - + İzlemeye hazır @@ -2518,18 +2532,18 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + İşitme engelliler için - - + + Şeffaflık - + Şeffaflık Seviyesi diff --git a/translations/mlcplayer_fr.ts b/translations/mlcplayer_fr.ts index 6019c33..2c2c9e8 100644 --- a/translations/mlcplayer_fr.ts +++ b/translations/mlcplayer_fr.ts @@ -3,30 +3,35 @@ MLCPlayer - - - + + %{value} + + + + + + 30 Saniye Geri - - - + + + 30 Saniye İleri - - - + + + 5 Saniye Geri - - - + + + 5 Saniye İleri @@ -56,90 +61,89 @@ - + Adres - + Adresi Kopyala - - - + + + Almanca - + Alt Yazı - - + + Altyazı - - - + + + Altyazı Ayarları - + Altyazı Ayarları Uygulanamadı - + Altyazı Bul - - + + Altyazı Bulunamadı - + Altyazı Bul… - + Altyazı Değiştirilemedi - + Altyazı Dosyaları - + Altyazı Dosyası Ekle - - - + + + Altyazı Ekle - - - - - + + + + Altyazı Eklenemedi @@ -160,15 +164,15 @@ - - - - + + + + Altyazı Parçası - + Altyazı Seçilemedi @@ -183,7 +187,7 @@ - + Altyazı bulunamadı @@ -193,7 +197,7 @@ - + Altyazı desteği @@ -203,28 +207,22 @@ - - + + Altyazı dosyası bulunamadı. Dosyanın yerini kontrol edin. - - + Altyazı eklendi - - + + Altyazı eklenemedi. Dosyanın desteklenen ve okunabilir bir altyazı dosyası olduğundan emin olun. - - - Altyazı eklenemedi. Dosyanın hasarlı veya desteklenmeyen bir format olması mümkün. - - Altyazı indirildi ancak oynatıcıya uygulanamadı. @@ -256,29 +254,29 @@ - - + + Altyazı parçaları yüklenemedi - - + + Altyazı parçası bulunamadı - + Altyazı parçası yok. - + Altyazı senkronu - + Altyazı senkronu. Pozitif değer altyazıyı geciktirir, negatif değer öne alır. @@ -288,7 +286,7 @@ - + Altyazı seçilemedi. Lütfen başka bir altyazı parçasını deneyin. @@ -298,21 +296,21 @@ - + Altyazı sıradaki videoya otomatik bağlanamadı. Videoyu oynatın ve altyazıyı yeniden bırakın. - - - - + + + + Altyazı yolu: - - + + Altyazı yükleniyor... @@ -322,38 +320,38 @@ - + Altyazılar açılıp kapatılamadı. Lütfen tekrar deneyin. - + Altyazıları Aç - + Altyazıları Gizle - + Altyazıları Göster - - + + Altyazıları Göster/Gizle - + Altyazıları Göster/Gizle (S) - + Altyazıları Kapat @@ -388,23 +386,23 @@ - - + + Arapça - + Araçlar - + Arka plan - + Arka planı şeffaf yap (renk yok) @@ -456,13 +454,13 @@ - + Ayarları uygula - - + + Açılamadı @@ -472,23 +470,23 @@ - + Aşağı Ok - - + + Bağlantı - + Bağlantı Açılamadı - + Bağlantı açılıyor… @@ -513,7 +511,7 @@ - + Bağlantıdan Oynat @@ -523,22 +521,22 @@ - + Başarılı - + Başlık - + Beklenmeyen Hata - + Beklenmeyen bir hata oluştu. Program çalışmaya devam ediyor, ancak bu işlem tamamlanamadı. @@ -551,7 +549,7 @@ Sorun devam ederse programı yeniden başlatın. - + Beklenmeyen bir veri hatası oluştu. Lütfen işlemi tekrar deneyin. Sorun devam ederse programı yeniden başlatın. @@ -563,38 +561,38 @@ Lütfen işlemi tekrar deneyin. Sorun devam ederse programı yeniden başlatın. - + Bir videoyu buraya sürükleyin veya bilgisayarınızdan açın. - + Biraz büyük - + Birçok video formatını destekler - + Bit derinliği - - + + Bitrate - + Boyut - + Bu bağlantı açılamadı. Adresi ve internet bağlantınızı kontrol edip tekrar deneyin. @@ -605,30 +603,30 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Bu bir altyazı önizlemesidir. - + Bu klasörde desteklenen medya dosyası bulunamadı. - + Bu paket mpv/FFmpeg bileşenini içerir; künyesi ve kaynak adresi için kurulum dizinindeki <code>licenses</code> klasörüne bakın. - + Bu program ÖZGÜR YAZILIMDIR ve GNU GPL sürüm 3 koşullarıyla dağıtılır. - - - - + + + + @@ -636,69 +634,69 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Bölüm bilgisi yok - + Bölüm bulunamadı - - + + Bölümleri Yenile - + Büyük - - + + Büyüt - - + + Codec - - + + Codec açıklaması - + Daha sonra - + Değişiklikleri iptal et - + Değişiklikleri sürüm notlarında inceleyebilirsiniz. - + Dikey konum - - - - + + + + Dil @@ -708,37 +706,37 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Dolby Vision - - + + Dosya - - - - - - + + + + + + Dosya Aç - - - - - + + + + + Dosya Açılamadı - + Dosya Bulunamadı @@ -748,60 +746,60 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Dosya artık mevcut değil. Son Açılanlar listesinden kaldırıldı. - + Dosya açılamadı. Dosya silinmiş, taşınmış veya desteklenmeyen bir format olabilir. - + Dosya açılamadı. Dosyanın mevcut ve desteklenen bir medya dosyası olduğunu kontrol edin. - + Dosya açılamadı: - + Dosya bulunamadı. Dosya taşınmış veya silinmiş olabilir. Çözüm: Dosyanın yerini kontrol edip tekrar açmayı deneyin. - + Dosyaya erişim izniniz yok. Çözüm: Dosyanın kilidini açın veya başka bir klasöre kopyalayın. - + Doygunluk: - + Duraklat - - - + + + Durdur - - - + + + Durum @@ -811,71 +809,71 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Ekran Görüntüsü (Ctrl+S) - - - + + + Ekran Görüntüsü Al - + Ekran Görüntüsü Alınamadı - + Ekran görüntüsü alma - + Ekran görüntüsü almak için bir video oynatılıyor olmalıdır. - + Ekran görüntüsü kaydedildi: - + Ekran görüntüsü kaydedilemedi. Masaüstüne yazma iznini ve boş alanı kontrol edin. - + En büyük - + En güncel sürümü kullanıyorsunuz - + En kalın - + Evet - + FPS - - - + + + Felemenkçe @@ -886,102 +884,102 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - - - + + + Fransızca - + Gamma: - + Genel - + Genel bitrate - + Geri - + Geri Yükle - + Gezinim - + Geçerli bir web adresi girin. Yalnız http:// ve https:// ile başlayan bağlantılar açılabilir. - + Geçersiz Adres - + Geçersiz Zaman - + Girilen zaman konumuna gidilemedi. Zamanı tekrar kontrol edin. - + GitHub sayfasını aç - + Gömülü - - + + Görüntü - + Görüntü oranı - + Görünüm - + Güncelle - - - + + + Güncelleme - + Güncelleme Mevcut @@ -1006,12 +1004,12 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Güncelleme uygulanıyor… - + Güncellemeleri Denetle @@ -1022,7 +1020,7 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Günlük Yönetimi @@ -1032,12 +1030,12 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Günlükler temizlendi. - + Günlükler temizlenemedi. Dosyalar başka bir program tarafından kullanılıyor olabilir. @@ -1063,15 +1061,15 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - - + + Hakkında - - - + + + Harici @@ -1081,7 +1079,7 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Hata Ayrıntılarını Görüntüle @@ -1101,36 +1099,36 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + HİÇBİR GARANTİ VERİLMEZ; satılabilirlik veya belirli bir amaca uygunluk zımni garantisi dahil değildir. - - + + Japonca - + Kaldır - + Kanal - + - - - + + + Kapat @@ -1140,28 +1138,28 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Kapsayıcı - + Karıştır - + Karışık Oynat - + Karışık Oynat: Açık - - + + Karışık Oynat: Kapalı @@ -1171,12 +1169,12 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Kaydedilecek oynatma listesi yok. - + Kaydedilemedi @@ -1186,17 +1184,17 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Kaynak - + Kaynak koddan çalışan kopya kurulumla güncellenmez. - + Kaynak kodu: @@ -1206,56 +1204,56 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Kenarlık - - + + Kenarlık kalınlığı - - - - - + + + + + Klasör Aç - - - + + + Klasör Açılamadı - + Klasör okunamadı. Klasöre erişim izniniz olmayabilir. - + Klasör seçilemedi. Lütfen tekrar deneyin. - - - + + + Klavye Kısayolları - + Kontrast: - + Konum @@ -1270,8 +1268,8 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - - + + Korece @@ -1286,23 +1284,23 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Kullanılmıyor - + Küçük - + Küçült - - + + Lehçe @@ -1312,83 +1310,83 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Listenin başındasınız. - + Listenin sonuna ulaştınız. - - + + Listeyi Tekrarla - + Listeyi Temizle - + MLC Player Yanıt Vermiyor - + MLC Player katkıcıları - + MLC Player simgesi - + MLC Player {version} indirilmeye hazır. - + MPV Bileşeni Bulunamadı - + MPV bileşeni yüklenemedi. Çözüm: Programın 'bin' klasörünün eksiksiz olduğundan emin olun. Programı kurulum klasöründen çalıştırın. - + MPV tabanlı minimal video oynatıcı. - + Medya Aç - - + + Medya Bilgisi - - + + Medya Dosyaları - + Menü @@ -1403,12 +1401,12 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Normal - + Normal Pencereye Dön @@ -1468,76 +1466,76 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Ortam - - - + + + Oynat - - + + Oynat/Duraklat - + Oynat/Duraklat (Space) - - + + Oynatma - - + + Oynatma Hızı - + Oynatma Hızı Değiştirilemedi - - - - - + + + + + - + Oynatma Listesi - - + + Oynatma Listesi Aç - + Oynatma Listesine Dosya Ekle - + Oynatma Listesine Ekle - - + + Oynatma Listesini Göster @@ -1547,34 +1545,34 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - - + + Oynatma Listesini Kaydet - + Oynatma Listesini Tekrarla - + Oynatma hızı değiştirilemedi. Lütfen başka bir hız deneyin. - - + + Oynatma listesi - + Oynatma listesi (kaydet/aç - .m3u) - + Oynatma listesi açılamadı. Dosya bozuk veya okunamıyor olabilir. @@ -1585,8 +1583,8 @@ Dosyaları buraya sürükleyin - - + + Oynatma listesi boş. @@ -1596,60 +1594,60 @@ Dosyaları buraya sürükleyin - + Oynatma listesi kaydedilemedi. Dosyanın yazılabileceği bir konum seçmeyi deneyin. - + Oynatma listesinde geçerli dosya bulunamadı. - + Oynatma listesindeki dosya açılamadı. Dosya taşınmış veya silinmiş olabilir. - + Oynatıcı Başlatılamadı - + Oynatıcı ayarları okunamadığı için bağlantı güvenli biçimde açılamadı. Lütfen tekrar deneyin. - - + + Oynatıcı ayarları okunamadığı için dosya güvenli biçimde açılamadı. Lütfen tekrar deneyin. - + Oynatıcı ayarları okunamadığı için klasör güvenli biçimde açılamadı. Lütfen tekrar deneyin. - + Oynatıcı ayarları okunamadığı için oynatma listesi güvenli biçimde açılamadı. Lütfen tekrar deneyin. - + Oynatıcı ayarı uygulanamadı (video ayarı desteklenmiyor). Bu işlem mpv'nin bu sürümünde bulunmayan bir özellik kullanmaya çalıştı. Diğer ayarlarla devam edebilirsiniz. - + Oynatıcı başlatılamadı. Programın bin klasörünü kontrol edip tekrar deneyin. - + Parlaklık: @@ -1664,34 +1662,34 @@ Bu işlem mpv'nin bu sürümünde bulunmayan bir özellik kullanmaya çalıştı - + Parça - + Parça süresi - + Piksel biçimi - - + + Portekizce - - + + Profil - + Program çalıştırılamadı: gerekli MPV bileşeni (mpv-2.dll) bulunamadı. Çözüm: Programın yanındaki 'bin' klasörünün eksiksiz olduğundan emin olun. Programı kurulum klasöründen başlatın. @@ -1710,46 +1708,46 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Renk - + Renk aralığı - + Renk standardı - + Renk yok (Şeffaf) - + Renk, arka plan ve kenarlık burada görünür. - - - - + + + + Resim İçinde Resim - + Resim İçinde Resimden Çık - - + + Rusça @@ -1759,15 +1757,15 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - - - + + + Sağ Ok - - + + Senkron @@ -1777,126 +1775,132 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - - - + + + Ses - + Ses Aygıtı Değiştirilemedi - + Ses Kanalı Seçilemedi - - - - + + + + Ses Parçası - - + + Ses Seviyesi - + Ses Seviyesi Artır - + Ses Seviyesi Azalt - + Ses aygıtı değiştirildi - + Ses aygıtı değiştirilemedi. Lütfen başka bir aygıt deneyin. - + Ses biçimi - + Ses kanalı değiştirilemedi. Lütfen başka bir kanal deneyin. - - + + Ses parçaları yüklenemedi - - + + Ses parçası bulunamadı - + Ses parçası yok. - - + + Ses Çıkışı - + Ses çıkışları yüklenemedi - + Ses çıkışı bulunamadı - - - + + Ses: %{volume} + + + + + + Sesi Aç - + Sesi Kapat/Aç - + Sesi Kıs - - - - + + + + + Sessiz - + Sessiz (M) @@ -1908,12 +1912,12 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Seçilen konum geçerli bir medya dosyası değil. - + Seçili @@ -1923,14 +1927,14 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Sistem dili - - - + + + Sol Ok @@ -1940,45 +1944,45 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - - + + Son Açılanlar - + Son açılan dosya yok - + Son açılanlar - - + + Sonraki - + Sonraki Bölüm - - + + Sonraki Parça - + Süre - + Sürükle-bırak ile dosya açma @@ -1993,45 +1997,45 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Sürüm - + Sürüm notları → - + Sıfırla - + Sınırlı - + Tam - - - - + + + + Tam Ekran - + Tam Ekran (F) - + Tam Ekrandan Çık @@ -2041,7 +2045,7 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Tamam @@ -2062,9 +2066,9 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - - - + + + Tek Dosyayı Tekrarla @@ -2080,75 +2084,75 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - - + + Tekrar: Kapalı - + Tekrarla / karışık oynatma modları - + Temsili video önizlemesi — gerçek video çıktısı değildir - + Tür - - + + Türkçe - - - + + + URL'den Oynat - + URL'den oynatma - - + + Ukraynaca - - - - + + + + Uyarı - + Uygula - + Uygulamadan Çık - - - - - + + + + + Varsayılan @@ -2158,7 +2162,7 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Varsayılan ayarlara dön @@ -2168,7 +2172,7 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Varsayılana Dön @@ -2178,31 +2182,31 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Video - - - - - + + + + + Video Ayarları - + Video Parçası - + Video URL'si giriniz: - + Video ayarları @@ -2217,90 +2221,90 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Video parçası yok. - + Yardım - + Yazı - - + + Yazı boyutu - + Yeni sürüm kullanıma hazır - + Yok - + Yolu Kopyala - + Yorum - + Yukarı Ok - + Yönetmen Yorumu - + Zaman pozisyonunu girin (MM:SS veya HH:MM:SS formatında): - + Zamana Gidilemedi - - - + + + Zamana Git - + Zamana Git... - + Zamanı MM:SS veya HH:MM:SS biçiminde ve 0 ile %1 saniye arasında girin. - - - - + + + + Zorunlu @@ -2310,31 +2314,31 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + kısmen saydam - - + + opak - + rengi - + saniye - - - - + + + + sn @@ -2344,40 +2348,50 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + tamamen saydam - + + {count} bayt + + + + + {whole},{fraction} + + + + Çalışan MLC Player isteği alamadı. Açık pencereyi kapatıp tekrar deneyin. - - - + + + Çince - + Çok büyük - + Çok küçük - + Çözünürlük - - + + Çıkış @@ -2387,52 +2401,52 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Önce bir video açın, sonra altyazıyı ekleyin. - - + + Önce bir video açın. - - + + Önce bir video dosyası açın. - + Önce video açın - - + + Önceki - + Önceki Bölüm - - + + Önceki Parça - + Örnekleme - + Özellikler: @@ -2443,7 +2457,7 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + İleri @@ -2458,19 +2472,19 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + İndiriliyor… - + İndirme tamamlanıyor — pencere işlem bitince kapanacak. - - + + İngilizce @@ -2485,30 +2499,30 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + İptal - - + + İspanyolca - + İsterseniz güncellemeyi GitHub sayfasından elle indirebilirsiniz. - - + + İtalyanca - + İzlemeye hazır @@ -2518,18 +2532,18 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + İşitme engelliler için - - + + Şeffaflık - + Şeffaflık Seviyesi diff --git a/translations/mlcplayer_it.ts b/translations/mlcplayer_it.ts index e36bce5..f4d3fd1 100644 --- a/translations/mlcplayer_it.ts +++ b/translations/mlcplayer_it.ts @@ -3,30 +3,35 @@ MLCPlayer - - - + + %{value} + + + + + + 30 Saniye Geri - - - + + + 30 Saniye İleri - - - + + + 5 Saniye Geri - - - + + + 5 Saniye İleri @@ -56,90 +61,89 @@ - + Adres - + Adresi Kopyala - - - + + + Almanca - + Alt Yazı - - + + Altyazı - - - + + + Altyazı Ayarları - + Altyazı Ayarları Uygulanamadı - + Altyazı Bul - - + + Altyazı Bulunamadı - + Altyazı Bul… - + Altyazı Değiştirilemedi - + Altyazı Dosyaları - + Altyazı Dosyası Ekle - - - + + + Altyazı Ekle - - - - - + + + + Altyazı Eklenemedi @@ -160,15 +164,15 @@ - - - - + + + + Altyazı Parçası - + Altyazı Seçilemedi @@ -183,7 +187,7 @@ - + Altyazı bulunamadı @@ -193,7 +197,7 @@ - + Altyazı desteği @@ -203,28 +207,22 @@ - - + + Altyazı dosyası bulunamadı. Dosyanın yerini kontrol edin. - - + Altyazı eklendi - - + + Altyazı eklenemedi. Dosyanın desteklenen ve okunabilir bir altyazı dosyası olduğundan emin olun. - - - Altyazı eklenemedi. Dosyanın hasarlı veya desteklenmeyen bir format olması mümkün. - - Altyazı indirildi ancak oynatıcıya uygulanamadı. @@ -256,29 +254,29 @@ - - + + Altyazı parçaları yüklenemedi - - + + Altyazı parçası bulunamadı - + Altyazı parçası yok. - + Altyazı senkronu - + Altyazı senkronu. Pozitif değer altyazıyı geciktirir, negatif değer öne alır. @@ -288,7 +286,7 @@ - + Altyazı seçilemedi. Lütfen başka bir altyazı parçasını deneyin. @@ -298,21 +296,21 @@ - + Altyazı sıradaki videoya otomatik bağlanamadı. Videoyu oynatın ve altyazıyı yeniden bırakın. - - - - + + + + Altyazı yolu: - - + + Altyazı yükleniyor... @@ -322,38 +320,38 @@ - + Altyazılar açılıp kapatılamadı. Lütfen tekrar deneyin. - + Altyazıları Aç - + Altyazıları Gizle - + Altyazıları Göster - - + + Altyazıları Göster/Gizle - + Altyazıları Göster/Gizle (S) - + Altyazıları Kapat @@ -388,23 +386,23 @@ - - + + Arapça - + Araçlar - + Arka plan - + Arka planı şeffaf yap (renk yok) @@ -456,13 +454,13 @@ - + Ayarları uygula - - + + Açılamadı @@ -472,23 +470,23 @@ - + Aşağı Ok - - + + Bağlantı - + Bağlantı Açılamadı - + Bağlantı açılıyor… @@ -513,7 +511,7 @@ - + Bağlantıdan Oynat @@ -523,22 +521,22 @@ - + Başarılı - + Başlık - + Beklenmeyen Hata - + Beklenmeyen bir hata oluştu. Program çalışmaya devam ediyor, ancak bu işlem tamamlanamadı. @@ -551,7 +549,7 @@ Sorun devam ederse programı yeniden başlatın. - + Beklenmeyen bir veri hatası oluştu. Lütfen işlemi tekrar deneyin. Sorun devam ederse programı yeniden başlatın. @@ -563,38 +561,38 @@ Lütfen işlemi tekrar deneyin. Sorun devam ederse programı yeniden başlatın. - + Bir videoyu buraya sürükleyin veya bilgisayarınızdan açın. - + Biraz büyük - + Birçok video formatını destekler - + Bit derinliği - - + + Bitrate - + Boyut - + Bu bağlantı açılamadı. Adresi ve internet bağlantınızı kontrol edip tekrar deneyin. @@ -605,30 +603,30 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Bu bir altyazı önizlemesidir. - + Bu klasörde desteklenen medya dosyası bulunamadı. - + Bu paket mpv/FFmpeg bileşenini içerir; künyesi ve kaynak adresi için kurulum dizinindeki <code>licenses</code> klasörüne bakın. - + Bu program ÖZGÜR YAZILIMDIR ve GNU GPL sürüm 3 koşullarıyla dağıtılır. - - - - + + + + @@ -636,69 +634,69 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Bölüm bilgisi yok - + Bölüm bulunamadı - - + + Bölümleri Yenile - + Büyük - - + + Büyüt - - + + Codec - - + + Codec açıklaması - + Daha sonra - + Değişiklikleri iptal et - + Değişiklikleri sürüm notlarında inceleyebilirsiniz. - + Dikey konum - - - - + + + + Dil @@ -708,37 +706,37 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Dolby Vision - - + + Dosya - - - - - - + + + + + + Dosya Aç - - - - - + + + + + Dosya Açılamadı - + Dosya Bulunamadı @@ -748,60 +746,60 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Dosya artık mevcut değil. Son Açılanlar listesinden kaldırıldı. - + Dosya açılamadı. Dosya silinmiş, taşınmış veya desteklenmeyen bir format olabilir. - + Dosya açılamadı. Dosyanın mevcut ve desteklenen bir medya dosyası olduğunu kontrol edin. - + Dosya açılamadı: - + Dosya bulunamadı. Dosya taşınmış veya silinmiş olabilir. Çözüm: Dosyanın yerini kontrol edip tekrar açmayı deneyin. - + Dosyaya erişim izniniz yok. Çözüm: Dosyanın kilidini açın veya başka bir klasöre kopyalayın. - + Doygunluk: - + Duraklat - - - + + + Durdur - - - + + + Durum @@ -811,71 +809,71 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Ekran Görüntüsü (Ctrl+S) - - - + + + Ekran Görüntüsü Al - + Ekran Görüntüsü Alınamadı - + Ekran görüntüsü alma - + Ekran görüntüsü almak için bir video oynatılıyor olmalıdır. - + Ekran görüntüsü kaydedildi: - + Ekran görüntüsü kaydedilemedi. Masaüstüne yazma iznini ve boş alanı kontrol edin. - + En büyük - + En güncel sürümü kullanıyorsunuz - + En kalın - + Evet - + FPS - - - + + + Felemenkçe @@ -886,102 +884,102 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - - - + + + Fransızca - + Gamma: - + Genel - + Genel bitrate - + Geri - + Geri Yükle - + Gezinim - + Geçerli bir web adresi girin. Yalnız http:// ve https:// ile başlayan bağlantılar açılabilir. - + Geçersiz Adres - + Geçersiz Zaman - + Girilen zaman konumuna gidilemedi. Zamanı tekrar kontrol edin. - + GitHub sayfasını aç - + Gömülü - - + + Görüntü - + Görüntü oranı - + Görünüm - + Güncelle - - - + + + Güncelleme - + Güncelleme Mevcut @@ -1006,12 +1004,12 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Güncelleme uygulanıyor… - + Güncellemeleri Denetle @@ -1022,7 +1020,7 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Günlük Yönetimi @@ -1032,12 +1030,12 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Günlükler temizlendi. - + Günlükler temizlenemedi. Dosyalar başka bir program tarafından kullanılıyor olabilir. @@ -1063,15 +1061,15 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - - + + Hakkında - - - + + + Harici @@ -1081,7 +1079,7 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Hata Ayrıntılarını Görüntüle @@ -1101,36 +1099,36 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + HİÇBİR GARANTİ VERİLMEZ; satılabilirlik veya belirli bir amaca uygunluk zımni garantisi dahil değildir. - - + + Japonca - + Kaldır - + Kanal - + - - - + + + Kapat @@ -1140,28 +1138,28 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Kapsayıcı - + Karıştır - + Karışık Oynat - + Karışık Oynat: Açık - - + + Karışık Oynat: Kapalı @@ -1171,12 +1169,12 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Kaydedilecek oynatma listesi yok. - + Kaydedilemedi @@ -1186,17 +1184,17 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Kaynak - + Kaynak koddan çalışan kopya kurulumla güncellenmez. - + Kaynak kodu: @@ -1206,56 +1204,56 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Kenarlık - - + + Kenarlık kalınlığı - - - - - + + + + + Klasör Aç - - - + + + Klasör Açılamadı - + Klasör okunamadı. Klasöre erişim izniniz olmayabilir. - + Klasör seçilemedi. Lütfen tekrar deneyin. - - - + + + Klavye Kısayolları - + Kontrast: - + Konum @@ -1270,8 +1268,8 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - - + + Korece @@ -1286,23 +1284,23 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Kullanılmıyor - + Küçük - + Küçült - - + + Lehçe @@ -1312,83 +1310,83 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Listenin başındasınız. - + Listenin sonuna ulaştınız. - - + + Listeyi Tekrarla - + Listeyi Temizle - + MLC Player Yanıt Vermiyor - + MLC Player katkıcıları - + MLC Player simgesi - + MLC Player {version} indirilmeye hazır. - + MPV Bileşeni Bulunamadı - + MPV bileşeni yüklenemedi. Çözüm: Programın 'bin' klasörünün eksiksiz olduğundan emin olun. Programı kurulum klasöründen çalıştırın. - + MPV tabanlı minimal video oynatıcı. - + Medya Aç - - + + Medya Bilgisi - - + + Medya Dosyaları - + Menü @@ -1403,12 +1401,12 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Normal - + Normal Pencereye Dön @@ -1468,76 +1466,76 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Ortam - - - + + + Oynat - - + + Oynat/Duraklat - + Oynat/Duraklat (Space) - - + + Oynatma - - + + Oynatma Hızı - + Oynatma Hızı Değiştirilemedi - - - - - + + + + + - + Oynatma Listesi - - + + Oynatma Listesi Aç - + Oynatma Listesine Dosya Ekle - + Oynatma Listesine Ekle - - + + Oynatma Listesini Göster @@ -1547,34 +1545,34 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - - + + Oynatma Listesini Kaydet - + Oynatma Listesini Tekrarla - + Oynatma hızı değiştirilemedi. Lütfen başka bir hız deneyin. - - + + Oynatma listesi - + Oynatma listesi (kaydet/aç - .m3u) - + Oynatma listesi açılamadı. Dosya bozuk veya okunamıyor olabilir. @@ -1585,8 +1583,8 @@ Dosyaları buraya sürükleyin - - + + Oynatma listesi boş. @@ -1596,60 +1594,60 @@ Dosyaları buraya sürükleyin - + Oynatma listesi kaydedilemedi. Dosyanın yazılabileceği bir konum seçmeyi deneyin. - + Oynatma listesinde geçerli dosya bulunamadı. - + Oynatma listesindeki dosya açılamadı. Dosya taşınmış veya silinmiş olabilir. - + Oynatıcı Başlatılamadı - + Oynatıcı ayarları okunamadığı için bağlantı güvenli biçimde açılamadı. Lütfen tekrar deneyin. - - + + Oynatıcı ayarları okunamadığı için dosya güvenli biçimde açılamadı. Lütfen tekrar deneyin. - + Oynatıcı ayarları okunamadığı için klasör güvenli biçimde açılamadı. Lütfen tekrar deneyin. - + Oynatıcı ayarları okunamadığı için oynatma listesi güvenli biçimde açılamadı. Lütfen tekrar deneyin. - + Oynatıcı ayarı uygulanamadı (video ayarı desteklenmiyor). Bu işlem mpv'nin bu sürümünde bulunmayan bir özellik kullanmaya çalıştı. Diğer ayarlarla devam edebilirsiniz. - + Oynatıcı başlatılamadı. Programın bin klasörünü kontrol edip tekrar deneyin. - + Parlaklık: @@ -1664,34 +1662,34 @@ Bu işlem mpv'nin bu sürümünde bulunmayan bir özellik kullanmaya çalıştı - + Parça - + Parça süresi - + Piksel biçimi - - + + Portekizce - - + + Profil - + Program çalıştırılamadı: gerekli MPV bileşeni (mpv-2.dll) bulunamadı. Çözüm: Programın yanındaki 'bin' klasörünün eksiksiz olduğundan emin olun. Programı kurulum klasöründen başlatın. @@ -1710,46 +1708,46 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Renk - + Renk aralığı - + Renk standardı - + Renk yok (Şeffaf) - + Renk, arka plan ve kenarlık burada görünür. - - - - + + + + Resim İçinde Resim - + Resim İçinde Resimden Çık - - + + Rusça @@ -1759,15 +1757,15 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - - - + + + Sağ Ok - - + + Senkron @@ -1777,126 +1775,132 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - - - + + + Ses - + Ses Aygıtı Değiştirilemedi - + Ses Kanalı Seçilemedi - - - - + + + + Ses Parçası - - + + Ses Seviyesi - + Ses Seviyesi Artır - + Ses Seviyesi Azalt - + Ses aygıtı değiştirildi - + Ses aygıtı değiştirilemedi. Lütfen başka bir aygıt deneyin. - + Ses biçimi - + Ses kanalı değiştirilemedi. Lütfen başka bir kanal deneyin. - - + + Ses parçaları yüklenemedi - - + + Ses parçası bulunamadı - + Ses parçası yok. - - + + Ses Çıkışı - + Ses çıkışları yüklenemedi - + Ses çıkışı bulunamadı - - - + + Ses: %{volume} + + + + + + Sesi Aç - + Sesi Kapat/Aç - + Sesi Kıs - - - - + + + + + Sessiz - + Sessiz (M) @@ -1908,12 +1912,12 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Seçilen konum geçerli bir medya dosyası değil. - + Seçili @@ -1923,14 +1927,14 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Sistem dili - - - + + + Sol Ok @@ -1940,45 +1944,45 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - - + + Son Açılanlar - + Son açılan dosya yok - + Son açılanlar - - + + Sonraki - + Sonraki Bölüm - - + + Sonraki Parça - + Süre - + Sürükle-bırak ile dosya açma @@ -1993,45 +1997,45 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Sürüm - + Sürüm notları → - + Sıfırla - + Sınırlı - + Tam - - - - + + + + Tam Ekran - + Tam Ekran (F) - + Tam Ekrandan Çık @@ -2041,7 +2045,7 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Tamam @@ -2062,9 +2066,9 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - - - + + + Tek Dosyayı Tekrarla @@ -2080,75 +2084,75 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - - + + Tekrar: Kapalı - + Tekrarla / karışık oynatma modları - + Temsili video önizlemesi — gerçek video çıktısı değildir - + Tür - - + + Türkçe - - - + + + URL'den Oynat - + URL'den oynatma - - + + Ukraynaca - - - - + + + + Uyarı - + Uygula - + Uygulamadan Çık - - - - - + + + + + Varsayılan @@ -2158,7 +2162,7 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Varsayılan ayarlara dön @@ -2168,7 +2172,7 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Varsayılana Dön @@ -2178,31 +2182,31 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Video - - - - - + + + + + Video Ayarları - + Video Parçası - + Video URL'si giriniz: - + Video ayarları @@ -2217,90 +2221,90 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Video parçası yok. - + Yardım - + Yazı - - + + Yazı boyutu - + Yeni sürüm kullanıma hazır - + Yok - + Yolu Kopyala - + Yorum - + Yukarı Ok - + Yönetmen Yorumu - + Zaman pozisyonunu girin (MM:SS veya HH:MM:SS formatında): - + Zamana Gidilemedi - - - + + + Zamana Git - + Zamana Git... - + Zamanı MM:SS veya HH:MM:SS biçiminde ve 0 ile %1 saniye arasında girin. - - - - + + + + Zorunlu @@ -2310,31 +2314,31 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + kısmen saydam - - + + opak - + rengi - + saniye - - - - + + + + sn @@ -2344,40 +2348,50 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + tamamen saydam - + + {count} bayt + + + + + {whole},{fraction} + + + + Çalışan MLC Player isteği alamadı. Açık pencereyi kapatıp tekrar deneyin. - - - + + + Çince - + Çok büyük - + Çok küçük - + Çözünürlük - - + + Çıkış @@ -2387,52 +2401,52 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Önce bir video açın, sonra altyazıyı ekleyin. - - + + Önce bir video açın. - - + + Önce bir video dosyası açın. - + Önce video açın - - + + Önceki - + Önceki Bölüm - - + + Önceki Parça - + Örnekleme - + Özellikler: @@ -2443,7 +2457,7 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + İleri @@ -2458,19 +2472,19 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + İndiriliyor… - + İndirme tamamlanıyor — pencere işlem bitince kapanacak. - - + + İngilizce @@ -2485,30 +2499,30 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + İptal - - + + İspanyolca - + İsterseniz güncellemeyi GitHub sayfasından elle indirebilirsiniz. - - + + İtalyanca - + İzlemeye hazır @@ -2518,18 +2532,18 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + İşitme engelliler için - - + + Şeffaflık - + Şeffaflık Seviyesi diff --git a/translations/mlcplayer_pt_BR.ts b/translations/mlcplayer_pt_BR.ts index 7f7e399..90dcfaf 100644 --- a/translations/mlcplayer_pt_BR.ts +++ b/translations/mlcplayer_pt_BR.ts @@ -3,30 +3,35 @@ MLCPlayer - - - + + %{value} + + + + + + 30 Saniye Geri - - - + + + 30 Saniye İleri - - - + + + 5 Saniye Geri - - - + + + 5 Saniye İleri @@ -56,90 +61,89 @@ - + Adres - + Adresi Kopyala - - - + + + Almanca - + Alt Yazı - - + + Altyazı - - - + + + Altyazı Ayarları - + Altyazı Ayarları Uygulanamadı - + Altyazı Bul - - + + Altyazı Bulunamadı - + Altyazı Bul… - + Altyazı Değiştirilemedi - + Altyazı Dosyaları - + Altyazı Dosyası Ekle - - - + + + Altyazı Ekle - - - - - + + + + Altyazı Eklenemedi @@ -160,15 +164,15 @@ - - - - + + + + Altyazı Parçası - + Altyazı Seçilemedi @@ -183,7 +187,7 @@ - + Altyazı bulunamadı @@ -193,7 +197,7 @@ - + Altyazı desteği @@ -203,28 +207,22 @@ - - + + Altyazı dosyası bulunamadı. Dosyanın yerini kontrol edin. - - + Altyazı eklendi - - + + Altyazı eklenemedi. Dosyanın desteklenen ve okunabilir bir altyazı dosyası olduğundan emin olun. - - - Altyazı eklenemedi. Dosyanın hasarlı veya desteklenmeyen bir format olması mümkün. - - Altyazı indirildi ancak oynatıcıya uygulanamadı. @@ -256,29 +254,29 @@ - - + + Altyazı parçaları yüklenemedi - - + + Altyazı parçası bulunamadı - + Altyazı parçası yok. - + Altyazı senkronu - + Altyazı senkronu. Pozitif değer altyazıyı geciktirir, negatif değer öne alır. @@ -288,7 +286,7 @@ - + Altyazı seçilemedi. Lütfen başka bir altyazı parçasını deneyin. @@ -298,21 +296,21 @@ - + Altyazı sıradaki videoya otomatik bağlanamadı. Videoyu oynatın ve altyazıyı yeniden bırakın. - - - - + + + + Altyazı yolu: - - + + Altyazı yükleniyor... @@ -322,38 +320,38 @@ - + Altyazılar açılıp kapatılamadı. Lütfen tekrar deneyin. - + Altyazıları Aç - + Altyazıları Gizle - + Altyazıları Göster - - + + Altyazıları Göster/Gizle - + Altyazıları Göster/Gizle (S) - + Altyazıları Kapat @@ -388,23 +386,23 @@ - - + + Arapça - + Araçlar - + Arka plan - + Arka planı şeffaf yap (renk yok) @@ -456,13 +454,13 @@ - + Ayarları uygula - - + + Açılamadı @@ -472,23 +470,23 @@ - + Aşağı Ok - - + + Bağlantı - + Bağlantı Açılamadı - + Bağlantı açılıyor… @@ -513,7 +511,7 @@ - + Bağlantıdan Oynat @@ -523,22 +521,22 @@ - + Başarılı - + Başlık - + Beklenmeyen Hata - + Beklenmeyen bir hata oluştu. Program çalışmaya devam ediyor, ancak bu işlem tamamlanamadı. @@ -551,7 +549,7 @@ Sorun devam ederse programı yeniden başlatın. - + Beklenmeyen bir veri hatası oluştu. Lütfen işlemi tekrar deneyin. Sorun devam ederse programı yeniden başlatın. @@ -563,38 +561,38 @@ Lütfen işlemi tekrar deneyin. Sorun devam ederse programı yeniden başlatın. - + Bir videoyu buraya sürükleyin veya bilgisayarınızdan açın. - + Biraz büyük - + Birçok video formatını destekler - + Bit derinliği - - + + Bitrate - + Boyut - + Bu bağlantı açılamadı. Adresi ve internet bağlantınızı kontrol edip tekrar deneyin. @@ -605,30 +603,30 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Bu bir altyazı önizlemesidir. - + Bu klasörde desteklenen medya dosyası bulunamadı. - + Bu paket mpv/FFmpeg bileşenini içerir; künyesi ve kaynak adresi için kurulum dizinindeki <code>licenses</code> klasörüne bakın. - + Bu program ÖZGÜR YAZILIMDIR ve GNU GPL sürüm 3 koşullarıyla dağıtılır. - - - - + + + + @@ -636,69 +634,69 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Bölüm bilgisi yok - + Bölüm bulunamadı - - + + Bölümleri Yenile - + Büyük - - + + Büyüt - - + + Codec - - + + Codec açıklaması - + Daha sonra - + Değişiklikleri iptal et - + Değişiklikleri sürüm notlarında inceleyebilirsiniz. - + Dikey konum - - - - + + + + Dil @@ -708,37 +706,37 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Dolby Vision - - + + Dosya - - - - - - + + + + + + Dosya Aç - - - - - + + + + + Dosya Açılamadı - + Dosya Bulunamadı @@ -748,60 +746,60 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Dosya artık mevcut değil. Son Açılanlar listesinden kaldırıldı. - + Dosya açılamadı. Dosya silinmiş, taşınmış veya desteklenmeyen bir format olabilir. - + Dosya açılamadı. Dosyanın mevcut ve desteklenen bir medya dosyası olduğunu kontrol edin. - + Dosya açılamadı: - + Dosya bulunamadı. Dosya taşınmış veya silinmiş olabilir. Çözüm: Dosyanın yerini kontrol edip tekrar açmayı deneyin. - + Dosyaya erişim izniniz yok. Çözüm: Dosyanın kilidini açın veya başka bir klasöre kopyalayın. - + Doygunluk: - + Duraklat - - - + + + Durdur - - - + + + Durum @@ -811,71 +809,71 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Ekran Görüntüsü (Ctrl+S) - - - + + + Ekran Görüntüsü Al - + Ekran Görüntüsü Alınamadı - + Ekran görüntüsü alma - + Ekran görüntüsü almak için bir video oynatılıyor olmalıdır. - + Ekran görüntüsü kaydedildi: - + Ekran görüntüsü kaydedilemedi. Masaüstüne yazma iznini ve boş alanı kontrol edin. - + En büyük - + En güncel sürümü kullanıyorsunuz - + En kalın - + Evet - + FPS - - - + + + Felemenkçe @@ -886,102 +884,102 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - - - + + + Fransızca - + Gamma: - + Genel - + Genel bitrate - + Geri - + Geri Yükle - + Gezinim - + Geçerli bir web adresi girin. Yalnız http:// ve https:// ile başlayan bağlantılar açılabilir. - + Geçersiz Adres - + Geçersiz Zaman - + Girilen zaman konumuna gidilemedi. Zamanı tekrar kontrol edin. - + GitHub sayfasını aç - + Gömülü - - + + Görüntü - + Görüntü oranı - + Görünüm - + Güncelle - - - + + + Güncelleme - + Güncelleme Mevcut @@ -1006,12 +1004,12 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Güncelleme uygulanıyor… - + Güncellemeleri Denetle @@ -1022,7 +1020,7 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Günlük Yönetimi @@ -1032,12 +1030,12 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Günlükler temizlendi. - + Günlükler temizlenemedi. Dosyalar başka bir program tarafından kullanılıyor olabilir. @@ -1063,15 +1061,15 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - - + + Hakkında - - - + + + Harici @@ -1081,7 +1079,7 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Hata Ayrıntılarını Görüntüle @@ -1101,36 +1099,36 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + HİÇBİR GARANTİ VERİLMEZ; satılabilirlik veya belirli bir amaca uygunluk zımni garantisi dahil değildir. - - + + Japonca - + Kaldır - + Kanal - + - - - + + + Kapat @@ -1140,28 +1138,28 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Kapsayıcı - + Karıştır - + Karışık Oynat - + Karışık Oynat: Açık - - + + Karışık Oynat: Kapalı @@ -1171,12 +1169,12 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Kaydedilecek oynatma listesi yok. - + Kaydedilemedi @@ -1186,17 +1184,17 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Kaynak - + Kaynak koddan çalışan kopya kurulumla güncellenmez. - + Kaynak kodu: @@ -1206,56 +1204,56 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Kenarlık - - + + Kenarlık kalınlığı - - - - - + + + + + Klasör Aç - - - + + + Klasör Açılamadı - + Klasör okunamadı. Klasöre erişim izniniz olmayabilir. - + Klasör seçilemedi. Lütfen tekrar deneyin. - - - + + + Klavye Kısayolları - + Kontrast: - + Konum @@ -1270,8 +1268,8 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - - + + Korece @@ -1286,23 +1284,23 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Kullanılmıyor - + Küçük - + Küçült - - + + Lehçe @@ -1312,83 +1310,83 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Listenin başındasınız. - + Listenin sonuna ulaştınız. - - + + Listeyi Tekrarla - + Listeyi Temizle - + MLC Player Yanıt Vermiyor - + MLC Player katkıcıları - + MLC Player simgesi - + MLC Player {version} indirilmeye hazır. - + MPV Bileşeni Bulunamadı - + MPV bileşeni yüklenemedi. Çözüm: Programın 'bin' klasörünün eksiksiz olduğundan emin olun. Programı kurulum klasöründen çalıştırın. - + MPV tabanlı minimal video oynatıcı. - + Medya Aç - - + + Medya Bilgisi - - + + Medya Dosyaları - + Menü @@ -1403,12 +1401,12 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Normal - + Normal Pencereye Dön @@ -1468,76 +1466,76 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Ortam - - - + + + Oynat - - + + Oynat/Duraklat - + Oynat/Duraklat (Space) - - + + Oynatma - - + + Oynatma Hızı - + Oynatma Hızı Değiştirilemedi - - - - - + + + + + - + Oynatma Listesi - - + + Oynatma Listesi Aç - + Oynatma Listesine Dosya Ekle - + Oynatma Listesine Ekle - - + + Oynatma Listesini Göster @@ -1547,34 +1545,34 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - - + + Oynatma Listesini Kaydet - + Oynatma Listesini Tekrarla - + Oynatma hızı değiştirilemedi. Lütfen başka bir hız deneyin. - - + + Oynatma listesi - + Oynatma listesi (kaydet/aç - .m3u) - + Oynatma listesi açılamadı. Dosya bozuk veya okunamıyor olabilir. @@ -1585,8 +1583,8 @@ Dosyaları buraya sürükleyin - - + + Oynatma listesi boş. @@ -1596,60 +1594,60 @@ Dosyaları buraya sürükleyin - + Oynatma listesi kaydedilemedi. Dosyanın yazılabileceği bir konum seçmeyi deneyin. - + Oynatma listesinde geçerli dosya bulunamadı. - + Oynatma listesindeki dosya açılamadı. Dosya taşınmış veya silinmiş olabilir. - + Oynatıcı Başlatılamadı - + Oynatıcı ayarları okunamadığı için bağlantı güvenli biçimde açılamadı. Lütfen tekrar deneyin. - - + + Oynatıcı ayarları okunamadığı için dosya güvenli biçimde açılamadı. Lütfen tekrar deneyin. - + Oynatıcı ayarları okunamadığı için klasör güvenli biçimde açılamadı. Lütfen tekrar deneyin. - + Oynatıcı ayarları okunamadığı için oynatma listesi güvenli biçimde açılamadı. Lütfen tekrar deneyin. - + Oynatıcı ayarı uygulanamadı (video ayarı desteklenmiyor). Bu işlem mpv'nin bu sürümünde bulunmayan bir özellik kullanmaya çalıştı. Diğer ayarlarla devam edebilirsiniz. - + Oynatıcı başlatılamadı. Programın bin klasörünü kontrol edip tekrar deneyin. - + Parlaklık: @@ -1664,34 +1662,34 @@ Bu işlem mpv'nin bu sürümünde bulunmayan bir özellik kullanmaya çalıştı - + Parça - + Parça süresi - + Piksel biçimi - - + + Portekizce - - + + Profil - + Program çalıştırılamadı: gerekli MPV bileşeni (mpv-2.dll) bulunamadı. Çözüm: Programın yanındaki 'bin' klasörünün eksiksiz olduğundan emin olun. Programı kurulum klasöründen başlatın. @@ -1710,46 +1708,46 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Renk - + Renk aralığı - + Renk standardı - + Renk yok (Şeffaf) - + Renk, arka plan ve kenarlık burada görünür. - - - - + + + + Resim İçinde Resim - + Resim İçinde Resimden Çık - - + + Rusça @@ -1759,15 +1757,15 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - - - + + + Sağ Ok - - + + Senkron @@ -1777,126 +1775,132 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - - - + + + Ses - + Ses Aygıtı Değiştirilemedi - + Ses Kanalı Seçilemedi - - - - + + + + Ses Parçası - - + + Ses Seviyesi - + Ses Seviyesi Artır - + Ses Seviyesi Azalt - + Ses aygıtı değiştirildi - + Ses aygıtı değiştirilemedi. Lütfen başka bir aygıt deneyin. - + Ses biçimi - + Ses kanalı değiştirilemedi. Lütfen başka bir kanal deneyin. - - + + Ses parçaları yüklenemedi - - + + Ses parçası bulunamadı - + Ses parçası yok. - - + + Ses Çıkışı - + Ses çıkışları yüklenemedi - + Ses çıkışı bulunamadı - - - + + Ses: %{volume} + + + + + + Sesi Aç - + Sesi Kapat/Aç - + Sesi Kıs - - - - + + + + + Sessiz - + Sessiz (M) @@ -1908,12 +1912,12 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Seçilen konum geçerli bir medya dosyası değil. - + Seçili @@ -1923,14 +1927,14 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Sistem dili - - - + + + Sol Ok @@ -1940,45 +1944,45 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - - + + Son Açılanlar - + Son açılan dosya yok - + Son açılanlar - - + + Sonraki - + Sonraki Bölüm - - + + Sonraki Parça - + Süre - + Sürükle-bırak ile dosya açma @@ -1993,45 +1997,45 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Sürüm - + Sürüm notları → - + Sıfırla - + Sınırlı - + Tam - - - - + + + + Tam Ekran - + Tam Ekran (F) - + Tam Ekrandan Çık @@ -2041,7 +2045,7 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Tamam @@ -2062,9 +2066,9 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - - - + + + Tek Dosyayı Tekrarla @@ -2080,75 +2084,75 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - - + + Tekrar: Kapalı - + Tekrarla / karışık oynatma modları - + Temsili video önizlemesi — gerçek video çıktısı değildir - + Tür - - + + Türkçe - - - + + + URL'den Oynat - + URL'den oynatma - - + + Ukraynaca - - - - + + + + Uyarı - + Uygula - + Uygulamadan Çık - - - - - + + + + + Varsayılan @@ -2158,7 +2162,7 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Varsayılan ayarlara dön @@ -2168,7 +2172,7 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Varsayılana Dön @@ -2178,31 +2182,31 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Video - - - - - + + + + + Video Ayarları - + Video Parçası - + Video URL'si giriniz: - + Video ayarları @@ -2217,90 +2221,90 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Video parçası yok. - + Yardım - + Yazı - - + + Yazı boyutu - + Yeni sürüm kullanıma hazır - + Yok - + Yolu Kopyala - + Yorum - + Yukarı Ok - + Yönetmen Yorumu - + Zaman pozisyonunu girin (MM:SS veya HH:MM:SS formatında): - + Zamana Gidilemedi - - - + + + Zamana Git - + Zamana Git... - + Zamanı MM:SS veya HH:MM:SS biçiminde ve 0 ile %1 saniye arasında girin. - - - - + + + + Zorunlu @@ -2310,31 +2314,31 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + kısmen saydam - - + + opak - + rengi - + saniye - - - - + + + + sn @@ -2344,40 +2348,50 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + tamamen saydam - + + {count} bayt + + + + + {whole},{fraction} + + + + Çalışan MLC Player isteği alamadı. Açık pencereyi kapatıp tekrar deneyin. - - - + + + Çince - + Çok büyük - + Çok küçük - + Çözünürlük - - + + Çıkış @@ -2387,52 +2401,52 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Önce bir video açın, sonra altyazıyı ekleyin. - - + + Önce bir video açın. - - + + Önce bir video dosyası açın. - + Önce video açın - - + + Önceki - + Önceki Bölüm - - + + Önceki Parça - + Örnekleme - + Özellikler: @@ -2443,7 +2457,7 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + İleri @@ -2458,19 +2472,19 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + İndiriliyor… - + İndirme tamamlanıyor — pencere işlem bitince kapanacak. - - + + İngilizce @@ -2485,30 +2499,30 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + İptal - - + + İspanyolca - + İsterseniz güncellemeyi GitHub sayfasından elle indirebilirsiniz. - - + + İtalyanca - + İzlemeye hazır @@ -2518,18 +2532,18 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + İşitme engelliler için - - + + Şeffaflık - + Şeffaflık Seviyesi diff --git a/translations/mlcplayer_ru.ts b/translations/mlcplayer_ru.ts index dd20691..e432aa0 100644 --- a/translations/mlcplayer_ru.ts +++ b/translations/mlcplayer_ru.ts @@ -3,30 +3,35 @@ MLCPlayer - - - + + %{value} + + + + + + 30 Saniye Geri - - - + + + 30 Saniye İleri - - - + + + 5 Saniye Geri - - - + + + 5 Saniye İleri @@ -56,90 +61,89 @@ - + Adres - + Adresi Kopyala - - - + + + Almanca - + Alt Yazı - - + + Altyazı - - - + + + Altyazı Ayarları - + Altyazı Ayarları Uygulanamadı - + Altyazı Bul - - + + Altyazı Bulunamadı - + Altyazı Bul… - + Altyazı Değiştirilemedi - + Altyazı Dosyaları - + Altyazı Dosyası Ekle - - - + + + Altyazı Ekle - - - - - + + + + Altyazı Eklenemedi @@ -160,15 +164,15 @@ - - - - + + + + Altyazı Parçası - + Altyazı Seçilemedi @@ -183,7 +187,7 @@ - + Altyazı bulunamadı @@ -193,7 +197,7 @@ - + Altyazı desteği @@ -203,28 +207,22 @@ - - + + Altyazı dosyası bulunamadı. Dosyanın yerini kontrol edin. - - + Altyazı eklendi - - + + Altyazı eklenemedi. Dosyanın desteklenen ve okunabilir bir altyazı dosyası olduğundan emin olun. - - - Altyazı eklenemedi. Dosyanın hasarlı veya desteklenmeyen bir format olması mümkün. - - Altyazı indirildi ancak oynatıcıya uygulanamadı. @@ -256,29 +254,29 @@ - - + + Altyazı parçaları yüklenemedi - - + + Altyazı parçası bulunamadı - + Altyazı parçası yok. - + Altyazı senkronu - + Altyazı senkronu. Pozitif değer altyazıyı geciktirir, negatif değer öne alır. @@ -288,7 +286,7 @@ - + Altyazı seçilemedi. Lütfen başka bir altyazı parçasını deneyin. @@ -298,21 +296,21 @@ - + Altyazı sıradaki videoya otomatik bağlanamadı. Videoyu oynatın ve altyazıyı yeniden bırakın. - - - - + + + + Altyazı yolu: - - + + Altyazı yükleniyor... @@ -322,38 +320,38 @@ - + Altyazılar açılıp kapatılamadı. Lütfen tekrar deneyin. - + Altyazıları Aç - + Altyazıları Gizle - + Altyazıları Göster - - + + Altyazıları Göster/Gizle - + Altyazıları Göster/Gizle (S) - + Altyazıları Kapat @@ -388,23 +386,23 @@ - - + + Arapça - + Araçlar - + Arka plan - + Arka planı şeffaf yap (renk yok) @@ -456,13 +454,13 @@ - + Ayarları uygula - - + + Açılamadı @@ -472,23 +470,23 @@ - + Aşağı Ok - - + + Bağlantı - + Bağlantı Açılamadı - + Bağlantı açılıyor… @@ -513,7 +511,7 @@ - + Bağlantıdan Oynat @@ -523,22 +521,22 @@ - + Başarılı - + Başlık - + Beklenmeyen Hata - + Beklenmeyen bir hata oluştu. Program çalışmaya devam ediyor, ancak bu işlem tamamlanamadı. @@ -551,7 +549,7 @@ Sorun devam ederse programı yeniden başlatın. - + Beklenmeyen bir veri hatası oluştu. Lütfen işlemi tekrar deneyin. Sorun devam ederse programı yeniden başlatın. @@ -563,38 +561,38 @@ Lütfen işlemi tekrar deneyin. Sorun devam ederse programı yeniden başlatın. - + Bir videoyu buraya sürükleyin veya bilgisayarınızdan açın. - + Biraz büyük - + Birçok video formatını destekler - + Bit derinliği - - + + Bitrate - + Boyut - + Bu bağlantı açılamadı. Adresi ve internet bağlantınızı kontrol edip tekrar deneyin. @@ -605,30 +603,30 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Bu bir altyazı önizlemesidir. - + Bu klasörde desteklenen medya dosyası bulunamadı. - + Bu paket mpv/FFmpeg bileşenini içerir; künyesi ve kaynak adresi için kurulum dizinindeki <code>licenses</code> klasörüne bakın. - + Bu program ÖZGÜR YAZILIMDIR ve GNU GPL sürüm 3 koşullarıyla dağıtılır. - - - - + + + + @@ -636,69 +634,69 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Bölüm bilgisi yok - + Bölüm bulunamadı - - + + Bölümleri Yenile - + Büyük - - + + Büyüt - - + + Codec - - + + Codec açıklaması - + Daha sonra - + Değişiklikleri iptal et - + Değişiklikleri sürüm notlarında inceleyebilirsiniz. - + Dikey konum - - - - + + + + Dil @@ -708,37 +706,37 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Dolby Vision - - + + Dosya - - - - - - + + + + + + Dosya Aç - - - - - + + + + + Dosya Açılamadı - + Dosya Bulunamadı @@ -748,60 +746,60 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Dosya artık mevcut değil. Son Açılanlar listesinden kaldırıldı. - + Dosya açılamadı. Dosya silinmiş, taşınmış veya desteklenmeyen bir format olabilir. - + Dosya açılamadı. Dosyanın mevcut ve desteklenen bir medya dosyası olduğunu kontrol edin. - + Dosya açılamadı: - + Dosya bulunamadı. Dosya taşınmış veya silinmiş olabilir. Çözüm: Dosyanın yerini kontrol edip tekrar açmayı deneyin. - + Dosyaya erişim izniniz yok. Çözüm: Dosyanın kilidini açın veya başka bir klasöre kopyalayın. - + Doygunluk: - + Duraklat - - - + + + Durdur - - - + + + Durum @@ -811,71 +809,71 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Ekran Görüntüsü (Ctrl+S) - - - + + + Ekran Görüntüsü Al - + Ekran Görüntüsü Alınamadı - + Ekran görüntüsü alma - + Ekran görüntüsü almak için bir video oynatılıyor olmalıdır. - + Ekran görüntüsü kaydedildi: - + Ekran görüntüsü kaydedilemedi. Masaüstüne yazma iznini ve boş alanı kontrol edin. - + En büyük - + En güncel sürümü kullanıyorsunuz - + En kalın - + Evet - + FPS - - - + + + Felemenkçe @@ -886,102 +884,102 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - - - + + + Fransızca - + Gamma: - + Genel - + Genel bitrate - + Geri - + Geri Yükle - + Gezinim - + Geçerli bir web adresi girin. Yalnız http:// ve https:// ile başlayan bağlantılar açılabilir. - + Geçersiz Adres - + Geçersiz Zaman - + Girilen zaman konumuna gidilemedi. Zamanı tekrar kontrol edin. - + GitHub sayfasını aç - + Gömülü - - + + Görüntü - + Görüntü oranı - + Görünüm - + Güncelle - - - + + + Güncelleme - + Güncelleme Mevcut @@ -1006,12 +1004,12 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Güncelleme uygulanıyor… - + Güncellemeleri Denetle @@ -1022,7 +1020,7 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Günlük Yönetimi @@ -1032,12 +1030,12 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Günlükler temizlendi. - + Günlükler temizlenemedi. Dosyalar başka bir program tarafından kullanılıyor olabilir. @@ -1063,15 +1061,15 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - - + + Hakkında - - - + + + Harici @@ -1081,7 +1079,7 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Hata Ayrıntılarını Görüntüle @@ -1101,36 +1099,36 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + HİÇBİR GARANTİ VERİLMEZ; satılabilirlik veya belirli bir amaca uygunluk zımni garantisi dahil değildir. - - + + Japonca - + Kaldır - + Kanal - + - - - + + + Kapat @@ -1140,28 +1138,28 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Kapsayıcı - + Karıştır - + Karışık Oynat - + Karışık Oynat: Açık - - + + Karışık Oynat: Kapalı @@ -1171,12 +1169,12 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Kaydedilecek oynatma listesi yok. - + Kaydedilemedi @@ -1186,17 +1184,17 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Kaynak - + Kaynak koddan çalışan kopya kurulumla güncellenmez. - + Kaynak kodu: @@ -1206,56 +1204,56 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Kenarlık - - + + Kenarlık kalınlığı - - - - - + + + + + Klasör Aç - - - + + + Klasör Açılamadı - + Klasör okunamadı. Klasöre erişim izniniz olmayabilir. - + Klasör seçilemedi. Lütfen tekrar deneyin. - - - + + + Klavye Kısayolları - + Kontrast: - + Konum @@ -1270,8 +1268,8 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - - + + Korece @@ -1286,23 +1284,23 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Kullanılmıyor - + Küçük - + Küçült - - + + Lehçe @@ -1312,83 +1310,83 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Listenin başındasınız. - + Listenin sonuna ulaştınız. - - + + Listeyi Tekrarla - + Listeyi Temizle - + MLC Player Yanıt Vermiyor - + MLC Player katkıcıları - + MLC Player simgesi - + MLC Player {version} indirilmeye hazır. - + MPV Bileşeni Bulunamadı - + MPV bileşeni yüklenemedi. Çözüm: Programın 'bin' klasörünün eksiksiz olduğundan emin olun. Programı kurulum klasöründen çalıştırın. - + MPV tabanlı minimal video oynatıcı. - + Medya Aç - - + + Medya Bilgisi - - + + Medya Dosyaları - + Menü @@ -1403,12 +1401,12 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Normal - + Normal Pencereye Dön @@ -1468,76 +1466,76 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - + Ortam - - - + + + Oynat - - + + Oynat/Duraklat - + Oynat/Duraklat (Space) - - + + Oynatma - - + + Oynatma Hızı - + Oynatma Hızı Değiştirilemedi - - - - - + + + + + - + Oynatma Listesi - - + + Oynatma Listesi Aç - + Oynatma Listesine Dosya Ekle - + Oynatma Listesine Ekle - - + + Oynatma Listesini Göster @@ -1547,34 +1545,34 @@ Hassas bilgiler otomatik olarak gizlenmiştir. - - + + Oynatma Listesini Kaydet - + Oynatma Listesini Tekrarla - + Oynatma hızı değiştirilemedi. Lütfen başka bir hız deneyin. - - + + Oynatma listesi - + Oynatma listesi (kaydet/aç - .m3u) - + Oynatma listesi açılamadı. Dosya bozuk veya okunamıyor olabilir. @@ -1585,8 +1583,8 @@ Dosyaları buraya sürükleyin - - + + Oynatma listesi boş. @@ -1596,60 +1594,60 @@ Dosyaları buraya sürükleyin - + Oynatma listesi kaydedilemedi. Dosyanın yazılabileceği bir konum seçmeyi deneyin. - + Oynatma listesinde geçerli dosya bulunamadı. - + Oynatma listesindeki dosya açılamadı. Dosya taşınmış veya silinmiş olabilir. - + Oynatıcı Başlatılamadı - + Oynatıcı ayarları okunamadığı için bağlantı güvenli biçimde açılamadı. Lütfen tekrar deneyin. - - + + Oynatıcı ayarları okunamadığı için dosya güvenli biçimde açılamadı. Lütfen tekrar deneyin. - + Oynatıcı ayarları okunamadığı için klasör güvenli biçimde açılamadı. Lütfen tekrar deneyin. - + Oynatıcı ayarları okunamadığı için oynatma listesi güvenli biçimde açılamadı. Lütfen tekrar deneyin. - + Oynatıcı ayarı uygulanamadı (video ayarı desteklenmiyor). Bu işlem mpv'nin bu sürümünde bulunmayan bir özellik kullanmaya çalıştı. Diğer ayarlarla devam edebilirsiniz. - + Oynatıcı başlatılamadı. Programın bin klasörünü kontrol edip tekrar deneyin. - + Parlaklık: @@ -1664,34 +1662,34 @@ Bu işlem mpv'nin bu sürümünde bulunmayan bir özellik kullanmaya çalıştı - + Parça - + Parça süresi - + Piksel biçimi - - + + Portekizce - - + + Profil - + Program çalıştırılamadı: gerekli MPV bileşeni (mpv-2.dll) bulunamadı. Çözüm: Programın yanındaki 'bin' klasörünün eksiksiz olduğundan emin olun. Programı kurulum klasöründen başlatın. @@ -1710,46 +1708,46 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Renk - + Renk aralığı - + Renk standardı - + Renk yok (Şeffaf) - + Renk, arka plan ve kenarlık burada görünür. - - - - + + + + Resim İçinde Resim - + Resim İçinde Resimden Çık - - + + Rusça @@ -1759,15 +1757,15 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - - - + + + Sağ Ok - - + + Senkron @@ -1777,126 +1775,132 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - - - + + + Ses - + Ses Aygıtı Değiştirilemedi - + Ses Kanalı Seçilemedi - - - - + + + + Ses Parçası - - + + Ses Seviyesi - + Ses Seviyesi Artır - + Ses Seviyesi Azalt - + Ses aygıtı değiştirildi - + Ses aygıtı değiştirilemedi. Lütfen başka bir aygıt deneyin. - + Ses biçimi - + Ses kanalı değiştirilemedi. Lütfen başka bir kanal deneyin. - - + + Ses parçaları yüklenemedi - - + + Ses parçası bulunamadı - + Ses parçası yok. - - + + Ses Çıkışı - + Ses çıkışları yüklenemedi - + Ses çıkışı bulunamadı - - - + + Ses: %{volume} + + + + + + Sesi Aç - + Sesi Kapat/Aç - + Sesi Kıs - - - - + + + + + Sessiz - + Sessiz (M) @@ -1908,12 +1912,12 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Seçilen konum geçerli bir medya dosyası değil. - + Seçili @@ -1923,14 +1927,14 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Sistem dili - - - + + + Sol Ok @@ -1940,45 +1944,45 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - - + + Son Açılanlar - + Son açılan dosya yok - + Son açılanlar - - + + Sonraki - + Sonraki Bölüm - - + + Sonraki Parça - + Süre - + Sürükle-bırak ile dosya açma @@ -1993,45 +1997,45 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Sürüm - + Sürüm notları → - + Sıfırla - + Sınırlı - + Tam - - - - + + + + Tam Ekran - + Tam Ekran (F) - + Tam Ekrandan Çık @@ -2041,7 +2045,7 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Tamam @@ -2062,9 +2066,9 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - - - + + + Tek Dosyayı Tekrarla @@ -2080,75 +2084,75 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - - + + Tekrar: Kapalı - + Tekrarla / karışık oynatma modları - + Temsili video önizlemesi — gerçek video çıktısı değildir - + Tür - - + + Türkçe - - - + + + URL'den Oynat - + URL'den oynatma - - + + Ukraynaca - - - - + + + + Uyarı - + Uygula - + Uygulamadan Çık - - - - - + + + + + Varsayılan @@ -2158,7 +2162,7 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Varsayılan ayarlara dön @@ -2168,7 +2172,7 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Varsayılana Dön @@ -2178,31 +2182,31 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Video - - - - - + + + + + Video Ayarları - + Video Parçası - + Video URL'si giriniz: - + Video ayarları @@ -2217,90 +2221,90 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Video parçası yok. - + Yardım - + Yazı - - + + Yazı boyutu - + Yeni sürüm kullanıma hazır - + Yok - + Yolu Kopyala - + Yorum - + Yukarı Ok - + Yönetmen Yorumu - + Zaman pozisyonunu girin (MM:SS veya HH:MM:SS formatında): - + Zamana Gidilemedi - - - + + + Zamana Git - + Zamana Git... - + Zamanı MM:SS veya HH:MM:SS biçiminde ve 0 ile %1 saniye arasında girin. - - - - + + + + Zorunlu @@ -2310,31 +2314,31 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + kısmen saydam - - + + opak - + rengi - + saniye - - - - + + + + sn @@ -2344,40 +2348,50 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + tamamen saydam - + + {count} bayt + + + + + {whole},{fraction} + + + + Çalışan MLC Player isteği alamadı. Açık pencereyi kapatıp tekrar deneyin. - - - + + + Çince - + Çok büyük - + Çok küçük - + Çözünürlük - - + + Çıkış @@ -2387,52 +2401,52 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + Önce bir video açın, sonra altyazıyı ekleyin. - - + + Önce bir video açın. - - + + Önce bir video dosyası açın. - + Önce video açın - - + + Önceki - + Önceki Bölüm - - + + Önceki Parça - + Örnekleme - + Özellikler: @@ -2443,7 +2457,7 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + İleri @@ -2458,19 +2472,19 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + İndiriliyor… - + İndirme tamamlanıyor — pencere işlem bitince kapanacak. - - + + İngilizce @@ -2485,30 +2499,30 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + İptal - - + + İspanyolca - + İsterseniz güncellemeyi GitHub sayfasından elle indirebilirsiniz. - - + + İtalyanca - + İzlemeye hazır @@ -2518,18 +2532,18 @@ Bu klasör silinmiş veya program başka bir yere taşınmış olabilir. - + İşitme engelliler için - - + + Şeffaflık - + Şeffaflık Seviyesi