commit 2f72a440d10871477d6103b287066aa5403117b2 Author: RIMPAC Date: Fri Jun 19 16:36:24 2026 +0300 Publish bytecode release package diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b021f81 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +.venv/ +__pycache__/ +*.py[cod] +build/ + +tmp/ +generated/ + +vendor/ffmpeg/*.exe +vendor/ffmpeg/*.zip +vendor/ffmpeg/_extract/ + +vendor/gifsicle/gifsicle.exe +vendor/gifsicle/node_modules/ +vendor/gifsicle/package.json +vendor/gifsicle/package-lock.json + +streamdeck/**/node_modules/ +streamdeck/**/logs/ + +*.log diff --git a/README.md b/README.md new file mode 100644 index 0000000..0e70683 --- /dev/null +++ b/README.md @@ -0,0 +1,89 @@ +# rimpac_gif_creator + +Локальная Windows-утилита для записи выделенной области экрана в маленький GIF через вложенный `ffmpeg.exe`. + +## Запуск + +1. Один раз установите локальный FFmpeg, если `vendor/ffmpeg/ffmpeg.exe` отсутствует: + + ```powershell + powershell -ExecutionPolicy Bypass -File .\tools\install_ffmpeg.ps1 + ``` + +2. Установите локальный Gifsicle для дополнительного сжатия, если `vendor/gifsicle/gifsicle.exe` отсутствует: + + ```powershell + powershell -ExecutionPolicy Bypass -File .\tools\install_gifsicle.ps1 + ``` + +3. Запустите приложение двойным кликом по `rimpac_gif_creator.vbs`. + +`run.bat` оставлен как технический запуск с консолью, если нужно увидеть ошибки. + +## Тесты + +Быстрый набор unit/integrity-тестов запускается без дополнительных зависимостей: + +```powershell +python -m unittest discover -s tests -v +``` + +Тесты проверяют логику версий и обновлений, утилиты сборки GIF, кастомный установщик, Stream Deck-плагин, тёмный логотип, а также то, что публичный release-payload не содержит исходники приложения и установщика. + +## Установка и обновления + +Для продуктовой сборки используется кастомный русский GUI-инсталлятор: + +```powershell +powershell -ExecutionPolicy Bypass -File .\tools\build_custom_installer.ps1 -PublicRepoUrl "https://git.safe-cap.com/RIMPAC/Gif_Generator_Releases.git" +``` + +Скрипт готовит приватную промежуточную папку `build/package/rimpac_gif_creator`, компилирует `app.py` и `updater.py` в `app.pyc` / `updater.pyc`, собирает публичный `build/release_repo` без `.py` исходников приложения, а затем упаковывает этот payload внутрь одного `build/installer/RIMPAC_GIF_Creator_Setup.exe` через PyInstaller. + +Инсталлятор на русском языке, по умолчанию ставит программу в `C:\RIMPAC\GIF Creator`, создаёт ярлык `RIMPAC GIF Creator` на рабочем столе и использует иконку из `assets/rimpac_gif_creator.ico`. Это один `RIMPAC_GIF_Creator_Setup.exe`: `ffmpeg.exe`, `ffprobe.exe`, `gifsicle.exe` и release-payload кладутся внутрь установщика заранее, без видимых командных строк, UAC-запросов и загрузок после установки. В главном окне установщика есть явная галка Stream Deck с объяснением: если у пользователя есть Stream Deck, инсталлятор дополнительно ставит плагин в `%APPDATA%\Elgato\StreamDeck\Plugins`; если галочку не ставить, устанавливается только программа и ярлык. + +В папку установки также кладётся `Uninstall.exe`. Установщик регистрирует удаление в Windows для текущего пользователя, поэтому программу можно удалить через `Приложения и компоненты` или прямым запуском `C:\RIMPAC\GIF Creator\Uninstall.exe`. Удалятор убирает папку программы, ярлык с рабочего стола, запись удаления Windows и, по галочке, Stream Deck-плагин. + +Текущая версия release-пакета: `beta 0.1`. Версия хранится в `version.json` и поднимается только вручную перед выпуском новой версии. + +Кнопка `Проверить обновления` запускает `updater.pyc`: он делает `git fetch`, читает `version.json` из установленной программы и из `origin/main`, а затем обновляется только если в публичном релизном репозитории версия новее и канал совпадает. Если версия такая же, новый коммит в релизном git не будет устанавливаться автоматически. При наличии новой версии updater закрывает программу, выполняет `git pull --ff-only`, обновляет зависимости из `requirements.txt` и запускает программу снова. За счёт git скачиваются только изменившиеся объекты, а не весь проект архивом. + +Публичный URL обновлений хранится в `update_config.json`: `https://git.safe-cap.com/RIMPAC/Gif_Generator_Releases.git`. В этом репозитории должны лежать только release-файлы (`*.pyc`, конфиги, иконки, launcher-скрипты), без `app.py` и `updater.py`. + +## Управление + +- Зажмите левую кнопку мыши и выделите прямоугольник. +- До начала записи можно тянуть рамку за края/углы, чтобы изменить размер, или за центр, чтобы перенести всю область. +- На панели рамки кнопка `Папка` открывает `generated`, кнопка `Очист.` удаляет все GIF из этой папки. +- Нажмите красный круг, чтобы начать запись. +- Во время записи красный круг становится квадратом, а рамка светится красным. +- Во время записи большой оверлей скрывается: остаются только тонкая красная рамка и маленькая панель, а клики за их пределами проходят в игру/систему. +- Нажмите красный квадрат, чтобы остановить запись. +- Нажмите крестик на панели, чтобы отменить и закрыть приложение. +- После записи у выбранной рамки появится предпросмотр: двумя ползунками можно обрезать начало и конец. +- При движении конечного ползунка предпросмотр сразу показывает последний кадр выбранного диапазона. +- На экране предпросмотра или прогресса `Escape` отменяет операцию и удаляет временные файлы. +- Нажмите `Создать GIF`. +- После нажатия `Создать GIF` окно предпросмотра заменяется экраном прогресса там, где была выбранная рамка. +- После сборки в этом же окне показывается итоговый размер GIF и статус копирования в буфер. +- После успешной сборки это же окно остается открытым, пока вы не закроете его крестиком или `Escape`. +- После галочки GIF уже лежит в буфере обмена как файл и сохраняется в `generated`. +- Кнопки `Папка GIF` / `Папка` открывают `generated`, кнопки `Очистить` / `Очист.` удаляют все GIF из этой папки. + +GIF создается с частотой 12 кадров/с, шириной до 640 px, ограниченной палитрой и финальной оптимизацией через локальный `gifsicle`. + +Бинарники `ffmpeg.exe`, `ffprobe.exe` и `gifsicle.exe` не хранятся в git, чтобы репозиторий не раздувался. Их можно восстановить скриптами из `tools`. + +При запуске приложение автоматически открывает оверлей на том мониторе, где сейчас находится курсор. Захват кадров выполняется через `mss`, чтобы корректно работать с несколькими мониторами. + +## Stream Deck + +Плагин лежит в `streamdeck/com.rimpac.gif-creator.sdPlugin` и установлен в: + +```text +C:\Users\User\AppData\Roaming\Elgato\StreamDeck\Plugins\com.rimpac.gif-creator.sdPlugin +``` + +В Stream Deck ищите категорию `RIMPAC` и кнопку `GIF Creator`. + +Если кнопка не появилась сразу, перезапустите приложение Stream Deck. diff --git a/Uninstall.exe b/Uninstall.exe new file mode 100644 index 0000000..1c2b1ce Binary files /dev/null and b/Uninstall.exe differ diff --git a/app.pyc b/app.pyc new file mode 100644 index 0000000..56ec238 Binary files /dev/null and b/app.pyc differ diff --git a/assets/installer_small.bmp b/assets/installer_small.bmp new file mode 100644 index 0000000..e52362f Binary files /dev/null and b/assets/installer_small.bmp differ diff --git a/assets/installer_wizard.bmp b/assets/installer_wizard.bmp new file mode 100644 index 0000000..46b8a34 Binary files /dev/null and b/assets/installer_wizard.bmp differ diff --git a/assets/rimpac_gif_creator.ico b/assets/rimpac_gif_creator.ico new file mode 100644 index 0000000..dffaa99 Binary files /dev/null and b/assets/rimpac_gif_creator.ico differ diff --git a/assets/rimpac_logo.png b/assets/rimpac_logo.png new file mode 100644 index 0000000..8874739 Binary files /dev/null and b/assets/rimpac_logo.png differ diff --git a/assets/rimpac_logo_dark.png b/assets/rimpac_logo_dark.png new file mode 100644 index 0000000..6f7bb8d Binary files /dev/null and b/assets/rimpac_logo_dark.png differ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..0332c2b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +pillow>=11.0.0 +mss>=10.0.0 diff --git a/rimpac_gif_creator.vbs b/rimpac_gif_creator.vbs new file mode 100644 index 0000000..b10c093 --- /dev/null +++ b/rimpac_gif_creator.vbs @@ -0,0 +1,3 @@ +Set shell = CreateObject("WScript.Shell") +root = CreateObject("Scripting.FileSystemObject").GetParentFolderName(WScript.ScriptFullName) +shell.Run """" & root & "\run.bat" & """", 0, False diff --git a/run.bat b/run.bat new file mode 100644 index 0000000..dbb41c2 --- /dev/null +++ b/run.bat @@ -0,0 +1,15 @@ +@echo off +setlocal +cd /d "%~dp0" + +if not exist ".venv\Scripts\python.exe" ( + py -m venv .venv + ".venv\Scripts\python.exe" -m pip install --upgrade pip + ".venv\Scripts\python.exe" -m pip install -r requirements.txt +) + +if exist "app.py" ( + ".venv\Scripts\python.exe" app.py +) else ( + ".venv\Scripts\python.exe" app.pyc +) diff --git a/streamdeck/com.rimpac.gif-creator.sdPlugin/bin/plugin.js b/streamdeck/com.rimpac.gif-creator.sdPlugin/bin/plugin.js new file mode 100644 index 0000000..fa76062 --- /dev/null +++ b/streamdeck/com.rimpac.gif-creator.sdPlugin/bin/plugin.js @@ -0,0 +1,45 @@ +import streamDeck, { SingletonAction } from "@elgato/streamdeck"; +import { spawn } from "node:child_process"; +import { existsSync, readFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const pluginDir = dirname(dirname(fileURLToPath(import.meta.url))); +const launcherConfig = join(pluginDir, "launcher.json"); +const fallbackLauncher = "F:\\AI\\TopSecretProjects\\ffmpeg-projects\\rimpac_gif_creator\\rimpac_gif_creator.vbs"; + +function getAppLauncher() { + if (!existsSync(launcherConfig)) { + return fallbackLauncher; + } + try { + const config = JSON.parse(readFileSync(launcherConfig, "utf8")); + return config.appLauncher || fallbackLauncher; + } catch { + return fallbackLauncher; + } +} + +class LaunchGifCreatorAction extends SingletonAction { + manifestId = "com.rimpac.gif-creator.launch"; + + async onKeyDown(ev) { + const appLauncher = getAppLauncher(); + if (!existsSync(appLauncher)) { + streamDeck.logger.error(`Launcher not found: ${appLauncher}`); + await ev.action.showAlert(); + return; + } + + const child = spawn("wscript.exe", [appLauncher], { + detached: true, + stdio: "ignore", + windowsHide: true, + }); + child.unref(); + await ev.action.showOk(); + } +} + +streamDeck.actions.registerAction(new LaunchGifCreatorAction()); +streamDeck.connect(); diff --git a/streamdeck/com.rimpac.gif-creator.sdPlugin/imgs/actions/launch/icon.png b/streamdeck/com.rimpac.gif-creator.sdPlugin/imgs/actions/launch/icon.png new file mode 100644 index 0000000..b07ef86 Binary files /dev/null and b/streamdeck/com.rimpac.gif-creator.sdPlugin/imgs/actions/launch/icon.png differ diff --git a/streamdeck/com.rimpac.gif-creator.sdPlugin/imgs/actions/launch/icon@2x.png b/streamdeck/com.rimpac.gif-creator.sdPlugin/imgs/actions/launch/icon@2x.png new file mode 100644 index 0000000..189fc26 Binary files /dev/null and b/streamdeck/com.rimpac.gif-creator.sdPlugin/imgs/actions/launch/icon@2x.png differ diff --git a/streamdeck/com.rimpac.gif-creator.sdPlugin/imgs/actions/launch/key.png b/streamdeck/com.rimpac.gif-creator.sdPlugin/imgs/actions/launch/key.png new file mode 100644 index 0000000..b07ef86 Binary files /dev/null and b/streamdeck/com.rimpac.gif-creator.sdPlugin/imgs/actions/launch/key.png differ diff --git a/streamdeck/com.rimpac.gif-creator.sdPlugin/imgs/actions/launch/key@2x.png b/streamdeck/com.rimpac.gif-creator.sdPlugin/imgs/actions/launch/key@2x.png new file mode 100644 index 0000000..189fc26 Binary files /dev/null and b/streamdeck/com.rimpac.gif-creator.sdPlugin/imgs/actions/launch/key@2x.png differ diff --git a/streamdeck/com.rimpac.gif-creator.sdPlugin/manifest.json b/streamdeck/com.rimpac.gif-creator.sdPlugin/manifest.json new file mode 100644 index 0000000..f58f66b --- /dev/null +++ b/streamdeck/com.rimpac.gif-creator.sdPlugin/manifest.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://schemas.elgato.com/streamdeck/plugins/manifest.json", + "UUID": "com.rimpac.gif-creator", + "Name": "RIMPAC GIF Creator", + "Description": "Launches the local rimpac_gif_creator screen GIF recorder.", + "Author": "RIMPAC", + "Version": "1.0.0.0", + "SDKVersion": 2, + "Software": { + "MinimumVersion": "7.1" + }, + "Nodejs": { + "Version": "24" + }, + "OS": [ + { + "Platform": "windows", + "MinimumVersion": "10" + } + ], + "CodePath": "bin/plugin.js", + "Category": "RIMPAC ffmpeg", + "CategoryIcon": "imgs/actions/launch/icon", + "Icon": "imgs/actions/launch/icon", + "Actions": [ + { + "UUID": "com.rimpac.gif-creator.launch", + "Name": "GIF Creator", + "Tooltip": "Start rimpac_gif_creator.", + "Icon": "imgs/actions/launch/icon", + "States": [ + { + "Image": "imgs/actions/launch/key", + "Title": "" + } + ] + } + ] +} \ No newline at end of file diff --git a/streamdeck/com.rimpac.gif-creator.sdPlugin/package-lock.json b/streamdeck/com.rimpac.gif-creator.sdPlugin/package-lock.json new file mode 100644 index 0000000..618d71b --- /dev/null +++ b/streamdeck/com.rimpac.gif-creator.sdPlugin/package-lock.json @@ -0,0 +1,74 @@ +{ + "name": "com.rimpac.gif-creator.sdplugin", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "com.rimpac.gif-creator.sdplugin", + "version": "1.0.0", + "dependencies": { + "@elgato/streamdeck": "^2.0.4" + } + }, + "node_modules/@elgato/schemas": { + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/@elgato/schemas/-/schemas-0.4.15.tgz", + "integrity": "sha512-UpELAqazf52PFer+hqg/hvEhYoPHCH3R0af6P7Ih3Hk6xCCJENn7cA5TIpXAIGEWe6dIteHuo/e/iPGLUP7yXA==", + "license": "MIT" + }, + "node_modules/@elgato/streamdeck": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@elgato/streamdeck/-/streamdeck-2.1.0.tgz", + "integrity": "sha512-d6wgSe93NbKAW0yT6/rabGCYUWic6nAoQoSLAXVadqTIUVmt+5Tfu7OaIGKnGER6AOn6dOpX6havRLFFFcUmOw==", + "license": "MIT", + "dependencies": { + "@elgato/schemas": "^0.4.14", + "@elgato/utils": "^0.4.4", + "ws": "^8.19.0" + }, + "engines": { + "node": ">=20.5.1" + } + }, + "node_modules/@elgato/utils": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/@elgato/utils/-/utils-0.4.5.tgz", + "integrity": "sha512-kBoqM8LEhhVLv7lNftYqaecNna3T/L85QQWU56yR1iFCvqlyyD22WmBljRcQkw5ZNvib55Br3cnrsxX3ajp+dQ==", + "license": "MIT", + "dependencies": { + "zod": "^3.25.24" + } + }, + "node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/streamdeck/com.rimpac.gif-creator.sdPlugin/package.json b/streamdeck/com.rimpac.gif-creator.sdPlugin/package.json new file mode 100644 index 0000000..39c023f --- /dev/null +++ b/streamdeck/com.rimpac.gif-creator.sdPlugin/package.json @@ -0,0 +1,9 @@ +{ + "name": "com.rimpac.gif-creator.sdplugin", + "version": "1.0.0", + "private": true, + "type": "module", + "dependencies": { + "@elgato/streamdeck": "^2.0.4" + } +} diff --git a/update_config.json b/update_config.json new file mode 100644 index 0000000..84e15d4 --- /dev/null +++ b/update_config.json @@ -0,0 +1,5 @@ +{ + "branch": "main", + "repo_url": "https://git.safe-cap.com/RIMPAC/Gif_Generator_Releases.git", + "remote": "origin" +} diff --git a/updater.pyc b/updater.pyc new file mode 100644 index 0000000..59dc774 Binary files /dev/null and b/updater.pyc differ diff --git a/vendor/ffmpeg/ffmpeg.exe b/vendor/ffmpeg/ffmpeg.exe new file mode 100644 index 0000000..39a1221 Binary files /dev/null and b/vendor/ffmpeg/ffmpeg.exe differ diff --git a/vendor/ffmpeg/ffprobe.exe b/vendor/ffmpeg/ffprobe.exe new file mode 100644 index 0000000..a24c086 Binary files /dev/null and b/vendor/ffmpeg/ffprobe.exe differ diff --git a/vendor/gifsicle/gifsicle.exe b/vendor/gifsicle/gifsicle.exe new file mode 100644 index 0000000..36be255 Binary files /dev/null and b/vendor/gifsicle/gifsicle.exe differ diff --git a/version.json b/version.json new file mode 100644 index 0000000..2620be5 --- /dev/null +++ b/version.json @@ -0,0 +1,4 @@ +{ + "channel": "beta", + "version": "0.1" +}