mirror of
https://github.com/onyx-and-iris/voicemeeter-compact.git
synced 2026-03-12 05:09:12 +00:00
Compare commits
No commits in common. "270bda2dc145f23fc5e3063a5909b864ea170a6b" and "b0f634f1e86862b3f66d4787005f9a4c423d2ecd" have entirely different histories.
270bda2dc1
...
b0f634f1e8
198
.github/workflows/release.yml
vendored
198
.github/workflows/release.yml
vendored
@ -1,198 +0,0 @@
|
|||||||
name: Release Voicemeeter Compact
|
|
||||||
|
|
||||||
on:
|
|
||||||
release:
|
|
||||||
types: [published]
|
|
||||||
push:
|
|
||||||
tags: ['v*.*.*']
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: windows-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Set up Python
|
|
||||||
uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: '3.12'
|
|
||||||
|
|
||||||
- name: Install Poetry
|
|
||||||
uses: snok/install-poetry@v1
|
|
||||||
with:
|
|
||||||
version: latest
|
|
||||||
virtualenvs-create: true
|
|
||||||
virtualenvs-in-project: true
|
|
||||||
|
|
||||||
- name: Install Task
|
|
||||||
run: |
|
|
||||||
Invoke-WebRequest -OutFile go-task.zip -Uri "https://github.com/go-task/task/releases/latest/download/task_windows_amd64.zip"
|
|
||||||
Expand-Archive -Path go-task.zip -DestinationPath .
|
|
||||||
Move-Item task.exe C:\Windows\System32\
|
|
||||||
shell: pwsh
|
|
||||||
|
|
||||||
- name: Download Forest TTK Theme
|
|
||||||
run: |
|
|
||||||
# Clone the Forest theme repository
|
|
||||||
git clone https://github.com/rdbende/Forest-ttk-theme.git temp-forest-theme
|
|
||||||
|
|
||||||
# Copy the required theme files to theme/forest
|
|
||||||
Copy-Item "temp-forest-theme\forest-dark.tcl" "theme\forest\"
|
|
||||||
Copy-Item "temp-forest-theme\forest-light.tcl" "theme\forest\"
|
|
||||||
Copy-Item "temp-forest-theme\forest-dark" "theme\forest\" -Recurse
|
|
||||||
Copy-Item "temp-forest-theme\forest-light" "theme\forest\" -Recurse
|
|
||||||
|
|
||||||
# Clean up
|
|
||||||
Remove-Item temp-forest-theme -Recurse -Force
|
|
||||||
shell: pwsh
|
|
||||||
|
|
||||||
- name: Download Azure TTK Theme
|
|
||||||
run: |
|
|
||||||
# Clone the Azure theme repository
|
|
||||||
git clone https://github.com/rdbende/Azure-ttk-theme.git temp-azure-theme
|
|
||||||
|
|
||||||
# Copy the required theme files to theme/azure
|
|
||||||
Copy-Item "temp-azure-theme\azure.tcl" "theme\azure\"
|
|
||||||
Copy-Item "temp-azure-theme\theme" "theme\azure\" -Recurse
|
|
||||||
|
|
||||||
# Clean up
|
|
||||||
Remove-Item temp-azure-theme -Recurse -Force
|
|
||||||
shell: pwsh
|
|
||||||
|
|
||||||
- name: Cache Poetry dependencies
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: .venv
|
|
||||||
key: venv-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }}
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: poetry install --with build
|
|
||||||
|
|
||||||
- name: Build artifacts with dynamic taskfile
|
|
||||||
run: task --taskfile Taskfile.dynamic.yml build-all
|
|
||||||
|
|
||||||
- name: Create release archives
|
|
||||||
run: task --taskfile Taskfile.dynamic.yml compress-all
|
|
||||||
|
|
||||||
# Sunvalley theme variants
|
|
||||||
- name: Upload build artifacts - Sunvalley Basic
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: sunvalley-basic
|
|
||||||
path: dist/sunvalley-basic.zip
|
|
||||||
|
|
||||||
- name: Upload build artifacts - Sunvalley Banana
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: sunvalley-banana
|
|
||||||
path: dist/sunvalley-banana.zip
|
|
||||||
|
|
||||||
- name: Upload build artifacts - Sunvalley Potato
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: sunvalley-potato
|
|
||||||
path: dist/sunvalley-potato.zip
|
|
||||||
|
|
||||||
# Forest theme variants (dark)
|
|
||||||
- name: Upload build artifacts - Forest Basic Dark
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: forest-basic-dark
|
|
||||||
path: dist/forest-basic-dark.zip
|
|
||||||
|
|
||||||
- name: Upload build artifacts - Forest Banana Dark
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: forest-banana-dark
|
|
||||||
path: dist/forest-banana-dark.zip
|
|
||||||
|
|
||||||
- name: Upload build artifacts - Forest Potato Dark
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: forest-potato-dark
|
|
||||||
path: dist/forest-potato-dark.zip
|
|
||||||
|
|
||||||
# Forest theme variants (light)
|
|
||||||
- name: Upload build artifacts - Forest Basic Light
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: forest-basic-light
|
|
||||||
path: dist/forest-basic-light.zip
|
|
||||||
|
|
||||||
- name: Upload build artifacts - Forest Banana Light
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: forest-banana-light
|
|
||||||
path: dist/forest-banana-light.zip
|
|
||||||
|
|
||||||
- name: Upload build artifacts - Forest Potato Light
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: forest-potato-light
|
|
||||||
path: dist/forest-potato-light.zip
|
|
||||||
|
|
||||||
# Azure theme variants (dark)
|
|
||||||
- name: Upload build artifacts - Azure Basic Dark
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: azure-basic-dark
|
|
||||||
path: dist/azure-basic-dark.zip
|
|
||||||
|
|
||||||
- name: Upload build artifacts - Azure Banana Dark
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: azure-banana-dark
|
|
||||||
path: dist/azure-banana-dark.zip
|
|
||||||
|
|
||||||
- name: Upload build artifacts - Azure Potato Dark
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: azure-potato-dark
|
|
||||||
path: dist/azure-potato-dark.zip
|
|
||||||
|
|
||||||
# Azure theme variants (light)
|
|
||||||
- name: Upload build artifacts - Azure Basic Light
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: azure-basic-light
|
|
||||||
path: dist/azure-basic-light.zip
|
|
||||||
|
|
||||||
- name: Upload build artifacts - Azure Banana Light
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: azure-banana-light
|
|
||||||
path: dist/azure-banana-light.zip
|
|
||||||
|
|
||||||
- name: Upload build artifacts - Azure Potato Light
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: azure-potato-light
|
|
||||||
path: dist/azure-potato-light.zip
|
|
||||||
|
|
||||||
release:
|
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
|
||||||
needs: build
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Download all artifacts
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
|
|
||||||
- name: Create Release
|
|
||||||
run: |
|
|
||||||
TAG_NAME=${GITHUB_REF#refs/tags/}
|
|
||||||
gh release create $TAG_NAME --title "Release $TAG_NAME" --generate-notes
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Upload release assets
|
|
||||||
run: |
|
|
||||||
TAG_NAME=${GITHUB_REF#refs/tags/}
|
|
||||||
find . -name "*.zip" -exec gh release upload $TAG_NAME {} \;
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
114
.gitignore
vendored
114
.gitignore
vendored
@ -1,9 +1,9 @@
|
|||||||
# Generated by ignr: github.com/onyx-and-iris/ignr
|
# quick test
|
||||||
|
quick.py
|
||||||
|
|
||||||
## Python ##
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[codz]
|
*.py[cod]
|
||||||
*$py.class
|
*$py.class
|
||||||
|
|
||||||
# C extensions
|
# C extensions
|
||||||
@ -23,6 +23,7 @@ parts/
|
|||||||
sdist/
|
sdist/
|
||||||
var/
|
var/
|
||||||
wheels/
|
wheels/
|
||||||
|
pip-wheel-metadata/
|
||||||
share/python-wheels/
|
share/python-wheels/
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
.installed.cfg
|
.installed.cfg
|
||||||
@ -49,10 +50,9 @@ htmlcov/
|
|||||||
nosetests.xml
|
nosetests.xml
|
||||||
coverage.xml
|
coverage.xml
|
||||||
*.cover
|
*.cover
|
||||||
*.py.cover
|
*.py,cover
|
||||||
.hypothesis/
|
.hypothesis/
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
cover/
|
|
||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
*.mo
|
*.mo
|
||||||
@ -75,7 +75,6 @@ instance/
|
|||||||
docs/_build/
|
docs/_build/
|
||||||
|
|
||||||
# PyBuilder
|
# PyBuilder
|
||||||
.pybuilder/
|
|
||||||
target/
|
target/
|
||||||
|
|
||||||
# Jupyter Notebook
|
# Jupyter Notebook
|
||||||
@ -86,9 +85,7 @@ profile_default/
|
|||||||
ipython_config.py
|
ipython_config.py
|
||||||
|
|
||||||
# pyenv
|
# pyenv
|
||||||
# For a library or package, you might want to ignore these files since the code is
|
.python-version
|
||||||
# intended to run in multiple environments; otherwise, check them in:
|
|
||||||
# .python-version
|
|
||||||
|
|
||||||
# pipenv
|
# pipenv
|
||||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
@ -97,37 +94,7 @@ ipython_config.py
|
|||||||
# install all needed dependencies.
|
# install all needed dependencies.
|
||||||
#Pipfile.lock
|
#Pipfile.lock
|
||||||
|
|
||||||
# UV
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
||||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
||||||
# commonly ignored for libraries.
|
|
||||||
#uv.lock
|
|
||||||
|
|
||||||
# poetry
|
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
||||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
||||||
# commonly ignored for libraries.
|
|
||||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
||||||
#poetry.lock
|
|
||||||
#poetry.toml
|
|
||||||
|
|
||||||
# pdm
|
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
||||||
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
||||||
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
||||||
#pdm.lock
|
|
||||||
#pdm.toml
|
|
||||||
.pdm-python
|
|
||||||
.pdm-build/
|
|
||||||
|
|
||||||
# pixi
|
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
||||||
#pixi.lock
|
|
||||||
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
||||||
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
||||||
.pixi
|
|
||||||
|
|
||||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
||||||
__pypackages__/
|
__pypackages__/
|
||||||
|
|
||||||
# Celery stuff
|
# Celery stuff
|
||||||
@ -139,13 +106,13 @@ celerybeat.pid
|
|||||||
|
|
||||||
# Environments
|
# Environments
|
||||||
.env
|
.env
|
||||||
.envrc
|
|
||||||
.venv
|
.venv
|
||||||
env/
|
env/
|
||||||
venv/
|
venv/
|
||||||
ENV/
|
ENV/
|
||||||
env.bak/
|
env.bak/
|
||||||
venv.bak/
|
venv.bak/
|
||||||
|
venv_vmcompact/
|
||||||
|
|
||||||
# Spyder project settings
|
# Spyder project settings
|
||||||
.spyderproject
|
.spyderproject
|
||||||
@ -165,65 +132,8 @@ dmypy.json
|
|||||||
# Pyre type checker
|
# Pyre type checker
|
||||||
.pyre/
|
.pyre/
|
||||||
|
|
||||||
# pytype static type analyzer
|
# build
|
||||||
.pytype/
|
theme/
|
||||||
|
spec/
|
||||||
|
|
||||||
# Cython debug symbols
|
.vscode/
|
||||||
cython_debug/
|
|
||||||
|
|
||||||
# PyCharm
|
|
||||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
||||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
||||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
||||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
||||||
#.idea/
|
|
||||||
|
|
||||||
# Abstra
|
|
||||||
# Abstra is an AI-powered process automation framework.
|
|
||||||
# Ignore directories containing user credentials, local state, and settings.
|
|
||||||
# Learn more at https://abstra.io/docs
|
|
||||||
.abstra/
|
|
||||||
|
|
||||||
# Visual Studio Code
|
|
||||||
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
||||||
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
||||||
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
||||||
# you could uncomment the following to ignore the entire vscode folder
|
|
||||||
.vscode/
|
|
||||||
|
|
||||||
# Ruff stuff:
|
|
||||||
.ruff_cache/
|
|
||||||
|
|
||||||
# PyPI configuration file
|
|
||||||
.pypirc
|
|
||||||
|
|
||||||
# Cursor
|
|
||||||
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
|
|
||||||
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
|
||||||
# refer to https://docs.cursor.com/context/ignore-files
|
|
||||||
.cursorignore
|
|
||||||
.cursorindexingignore
|
|
||||||
|
|
||||||
# Marimo
|
|
||||||
marimo/_static/
|
|
||||||
marimo/_lsp/
|
|
||||||
__marimo__/
|
|
||||||
|
|
||||||
# End of ignr
|
|
||||||
|
|
||||||
# Test files
|
|
||||||
test-*.py
|
|
||||||
|
|
||||||
# Forest/Azure theme files
|
|
||||||
theme/**/*
|
|
||||||
!theme/*/
|
|
||||||
!theme/**/.gitkeep
|
|
||||||
|
|
||||||
# Spec files
|
|
||||||
spec/**/*
|
|
||||||
!spec/*/
|
|
||||||
!spec/**/.gitkeep
|
|
||||||
|
|
||||||
# Taskfile build files
|
|
||||||
Taskfile.unified.yml
|
|
||||||
SPEC_CONSOLIDATION.md
|
|
||||||
@ -13,11 +13,13 @@ tasks:
|
|||||||
cmd: poetry run pyinstaller --noconfirm --distpath dist/{{.ITEM.THEME}}-{{.ITEM.KIND}} spec/azure/{{.ITEM.THEME}}-{{.ITEM.KIND}}.spec
|
cmd: poetry run pyinstaller --noconfirm --distpath dist/{{.ITEM.THEME}}-{{.ITEM.KIND}} spec/azure/{{.ITEM.THEME}}-{{.ITEM.KIND}}.spec
|
||||||
|
|
||||||
rewrite:
|
rewrite:
|
||||||
|
internal: true
|
||||||
desc: Run the source code rewriter
|
desc: Run the source code rewriter
|
||||||
cmds:
|
cmds:
|
||||||
- poetry run python tools/rewriter.py --rewrite --theme {{.THEME}}
|
- poetry run python tools/rewriter.py --rewrite --theme {{.THEME}}
|
||||||
|
|
||||||
restore:
|
restore:
|
||||||
|
internal: true
|
||||||
desc: Restore the backup files
|
desc: Restore the backup files
|
||||||
cmds:
|
cmds:
|
||||||
- poetry run python tools/rewriter.py --restore
|
- poetry run python tools/rewriter.py --restore
|
||||||
|
|||||||
@ -1,76 +0,0 @@
|
|||||||
version: '3'
|
|
||||||
|
|
||||||
# Dynamic build system - no spec files needed!
|
|
||||||
# Usage: task build THEMES="azure forest" or task build-all
|
|
||||||
|
|
||||||
vars:
|
|
||||||
THEMES: '{{.THEMES | default "all"}}'
|
|
||||||
SHELL: pwsh
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
build:
|
|
||||||
desc: Build specified themes dynamically (no spec files needed)
|
|
||||||
cmds:
|
|
||||||
- poetry run python tools/dynamic_builder.py {{.THEMES}}
|
|
||||||
|
|
||||||
build-all:
|
|
||||||
desc: Build all themes
|
|
||||||
cmds:
|
|
||||||
- poetry run python tools/dynamic_builder.py all
|
|
||||||
|
|
||||||
build-azure:
|
|
||||||
desc: Build only azure theme
|
|
||||||
cmds:
|
|
||||||
- poetry run python tools/dynamic_builder.py azure
|
|
||||||
|
|
||||||
build-forest:
|
|
||||||
desc: Build only forest theme
|
|
||||||
cmds:
|
|
||||||
- poetry run python tools/dynamic_builder.py forest
|
|
||||||
|
|
||||||
build-sunvalley:
|
|
||||||
desc: Build only sunvalley theme
|
|
||||||
cmds:
|
|
||||||
- poetry run python tools/dynamic_builder.py sunvalley
|
|
||||||
|
|
||||||
compress:
|
|
||||||
desc: Compress artifacts for specified theme
|
|
||||||
cmds:
|
|
||||||
- task: compress-{{.THEME}}
|
|
||||||
|
|
||||||
compress-all:
|
|
||||||
desc: Compress artifacts for all themes
|
|
||||||
cmds:
|
|
||||||
- for:
|
|
||||||
matrix:
|
|
||||||
THEME: [azure, forest, sunvalley]
|
|
||||||
task: compress-{{.ITEM.THEME}}
|
|
||||||
|
|
||||||
compress-azure:
|
|
||||||
cmds:
|
|
||||||
- for:
|
|
||||||
matrix:
|
|
||||||
KIND: [basic, banana, potato]
|
|
||||||
VARIANT: [azure-light, azure-dark]
|
|
||||||
cmd: '{{.SHELL}} -Command "Compress-Archive -Path dist/{{.ITEM.VARIANT}}-{{.ITEM.KIND}} -DestinationPath dist/{{.ITEM.VARIANT}}-{{.ITEM.KIND}}.zip -Force"'
|
|
||||||
|
|
||||||
compress-forest:
|
|
||||||
cmds:
|
|
||||||
- for:
|
|
||||||
matrix:
|
|
||||||
KIND: [basic, banana, potato]
|
|
||||||
VARIANT: [forest-light, forest-dark]
|
|
||||||
cmd: '{{.SHELL}} -Command "Compress-Archive -Path dist/{{.ITEM.VARIANT}}-{{.ITEM.KIND}} -DestinationPath dist/{{.ITEM.VARIANT}}-{{.ITEM.KIND}}.zip -Force"'
|
|
||||||
|
|
||||||
compress-sunvalley:
|
|
||||||
cmds:
|
|
||||||
- for:
|
|
||||||
matrix:
|
|
||||||
KIND: [basic, banana, potato]
|
|
||||||
cmd: '{{.SHELL}} -Command "Compress-Archive -Path dist/sunvalley-{{.ITEM.KIND}} -DestinationPath dist/sunvalley-{{.ITEM.KIND}}.zip -Force"'
|
|
||||||
|
|
||||||
clean:
|
|
||||||
desc: Clean all build artifacts
|
|
||||||
cmds:
|
|
||||||
- |
|
|
||||||
{{.SHELL}} -Command "Remove-Item -Path build/*,dist/* -Recurse -Force -ErrorAction SilentlyContinue"
|
|
||||||
@ -13,11 +13,13 @@ tasks:
|
|||||||
cmd: poetry run pyinstaller --noconfirm --distpath dist/{{.ITEM.THEME}}-{{.ITEM.KIND}} spec/forest/{{.ITEM.THEME}}-{{.ITEM.KIND}}.spec
|
cmd: poetry run pyinstaller --noconfirm --distpath dist/{{.ITEM.THEME}}-{{.ITEM.KIND}} spec/forest/{{.ITEM.THEME}}-{{.ITEM.KIND}}.spec
|
||||||
|
|
||||||
rewrite:
|
rewrite:
|
||||||
|
internal: true
|
||||||
desc: Run the source code rewriter
|
desc: Run the source code rewriter
|
||||||
cmds:
|
cmds:
|
||||||
- poetry run python tools/rewriter.py --rewrite --theme {{.THEME}}
|
- poetry run python tools/rewriter.py --rewrite --theme {{.THEME}}
|
||||||
|
|
||||||
restore:
|
restore:
|
||||||
|
internal: true
|
||||||
desc: Restore the backup files
|
desc: Restore the backup files
|
||||||
cmds:
|
cmds:
|
||||||
- poetry run python tools/rewriter.py --restore
|
- poetry run python tools/rewriter.py --restore
|
||||||
|
|||||||
@ -30,14 +30,8 @@ tasks:
|
|||||||
- task: compress
|
- task: compress
|
||||||
- echo "Release complete"
|
- echo "Release complete"
|
||||||
|
|
||||||
generate-specs:
|
|
||||||
desc: Generate all spec files from templates
|
|
||||||
cmds:
|
|
||||||
- poetry run python tools/spec_generator.py --clean
|
|
||||||
|
|
||||||
build:
|
build:
|
||||||
desc: Build all artifacts
|
desc: Build all artifacts
|
||||||
deps: [generate-specs]
|
|
||||||
cmds:
|
cmds:
|
||||||
- for:
|
- for:
|
||||||
matrix:
|
matrix:
|
||||||
|
|||||||
4
poetry.lock
generated
4
poetry.lock
generated
@ -223,7 +223,7 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vban-cmd"
|
name = "vban-cmd"
|
||||||
version = "2.10.2"
|
version = "2.10.1"
|
||||||
description = "Python interface for the VBAN RT Packet Service (Sendtext)"
|
description = "Python interface for the VBAN RT Packet Service (Sendtext)"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.10"
|
python-versions = ">=3.10"
|
||||||
@ -258,4 +258,4 @@ url = "../voicemeeter-api-python"
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.1"
|
lock-version = "2.1"
|
||||||
python-versions = ">=3.10,<3.14"
|
python-versions = ">=3.10,<3.14"
|
||||||
content-hash = "f1e1782280c5e165fef043ca2695ea5f5c93fd00a66ace809266e0196fef6b71"
|
content-hash = "171da15ce55f47b4e651aade40ab21afd5ef589ff7ff26e51caf6840d25b98a1"
|
||||||
|
|||||||
@ -8,7 +8,7 @@ readme = "README.md"
|
|||||||
requires-python = ">=3.10,<3.14"
|
requires-python = ">=3.10,<3.14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"voicemeeter-api (>=2.7.2,<3.0.0)",
|
"voicemeeter-api (>=2.7.2,<3.0.0)",
|
||||||
"vban-cmd (>=2.10.2,<3.0.0)",
|
"vban-cmd (>=2.10.1,<3.0.0)",
|
||||||
"sv-ttk (>=2.6.0,<3.0.0)",
|
"sv-ttk (>=2.6.0,<3.0.0)",
|
||||||
"tomli (>=2.0.1,<3.0) ; python_version < '3.11'",
|
"tomli (>=2.0.1,<3.0) ; python_version < '3.11'",
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,318 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Dynamic build system for voicemeeter-compact.
|
|
||||||
Generates spec files on-the-fly and builds executables without storing intermediate files.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import shutil
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import tempfile
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Dict
|
|
||||||
|
|
||||||
# Build configuration
|
|
||||||
THEMES = {
|
|
||||||
'azure': ['azure-light', 'azure-dark'],
|
|
||||||
'forest': ['forest-light', 'forest-dark'],
|
|
||||||
'sunvalley': ['sunvalley'],
|
|
||||||
}
|
|
||||||
|
|
||||||
KINDS = ['basic', 'banana', 'potato']
|
|
||||||
|
|
||||||
# Templates (same as spec_generator.py)
|
|
||||||
PYTHON_TEMPLATE = """import voicemeeterlib
|
|
||||||
|
|
||||||
import vmcompact
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
KIND_ID = '{kind}'
|
|
||||||
|
|
||||||
with voicemeeterlib.api(KIND_ID) as vmr:{theme_arg}
|
|
||||||
app = vmcompact.connect(KIND_ID, vmr{theme_param})
|
|
||||||
app.mainloop()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
"""
|
|
||||||
|
|
||||||
SPEC_TEMPLATE = """# -*- mode: python ; coding: utf-8 -*-
|
|
||||||
|
|
||||||
block_cipher = None
|
|
||||||
|
|
||||||
added_files = [
|
|
||||||
( '{img_path}', 'img' ),{theme_files}
|
|
||||||
( '{config_path}', 'configs' ),
|
|
||||||
]
|
|
||||||
|
|
||||||
a = Analysis(
|
|
||||||
['{script_path}'],
|
|
||||||
pathex=[],
|
|
||||||
binaries=[],
|
|
||||||
datas=added_files,
|
|
||||||
hiddenimports=[],
|
|
||||||
hookspath=[],
|
|
||||||
hooksconfig={{}},
|
|
||||||
runtime_hooks=[],
|
|
||||||
excludes=[],
|
|
||||||
win_no_prefer_redirects=False,
|
|
||||||
win_private_assemblies=False,
|
|
||||||
cipher=block_cipher,
|
|
||||||
noarchive=False,
|
|
||||||
)
|
|
||||||
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
|
||||||
|
|
||||||
exe = EXE(
|
|
||||||
pyz,
|
|
||||||
a.scripts,
|
|
||||||
[],
|
|
||||||
exclude_binaries=True,
|
|
||||||
name='{theme_variant}-{kind}',
|
|
||||||
debug=False,
|
|
||||||
bootloader_ignore_signals=False,
|
|
||||||
strip=False,
|
|
||||||
upx=True,
|
|
||||||
console=False,
|
|
||||||
disable_windowed_traceback=False,
|
|
||||||
argv_emulation=False,
|
|
||||||
target_arch=None,
|
|
||||||
codesign_identity=None,
|
|
||||||
entitlements_file=None,
|
|
||||||
)
|
|
||||||
coll = COLLECT(
|
|
||||||
exe,
|
|
||||||
a.binaries,
|
|
||||||
a.zipfiles,
|
|
||||||
a.datas,
|
|
||||||
strip=False,
|
|
||||||
upx=True,
|
|
||||||
upx_exclude=[],
|
|
||||||
name='{theme_variant}-{kind}',
|
|
||||||
)
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class DynamicBuilder:
|
|
||||||
def __init__(self, base_dir: Path, dist_dir: Path):
|
|
||||||
self.base_dir = base_dir
|
|
||||||
self.dist_dir = dist_dir
|
|
||||||
self.temp_dir = None
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
self.temp_dir = Path(tempfile.mkdtemp(prefix='vmcompact_build_'))
|
|
||||||
print(f'Using temp directory: {self.temp_dir}')
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
||||||
if self.temp_dir and self.temp_dir.exists():
|
|
||||||
shutil.rmtree(self.temp_dir)
|
|
||||||
print(f'Cleaned up temp directory: {self.temp_dir}')
|
|
||||||
|
|
||||||
def create_python_file(self, theme_variant: str, kind: str) -> Path:
|
|
||||||
"""Create a temporary Python launcher file."""
|
|
||||||
if theme_variant == 'sunvalley':
|
|
||||||
theme_arg = ''
|
|
||||||
theme_param = ''
|
|
||||||
else:
|
|
||||||
theme_arg = f"\n theme = '{theme_variant}'"
|
|
||||||
theme_param = ', theme=theme'
|
|
||||||
|
|
||||||
content = PYTHON_TEMPLATE.format(
|
|
||||||
kind=kind, theme_arg=theme_arg, theme_param=theme_param
|
|
||||||
)
|
|
||||||
|
|
||||||
py_file = self.temp_dir / f'{theme_variant}-{kind}.py'
|
|
||||||
with open(py_file, 'w') as f:
|
|
||||||
f.write(content)
|
|
||||||
|
|
||||||
return py_file
|
|
||||||
|
|
||||||
def create_spec_file(self, theme_variant: str, kind: str, py_file: Path) -> Path:
|
|
||||||
"""Create a temporary PyInstaller spec file."""
|
|
||||||
if theme_variant == 'sunvalley':
|
|
||||||
theme_files = ''
|
|
||||||
else:
|
|
||||||
theme_base = theme_variant.split('-')[0]
|
|
||||||
theme_path = (self.base_dir / 'theme' / theme_base).as_posix()
|
|
||||||
theme_files = f"\n ( '{theme_path}', 'theme' ),"
|
|
||||||
|
|
||||||
content = SPEC_TEMPLATE.format(
|
|
||||||
script_path=py_file.as_posix(),
|
|
||||||
img_path=(self.base_dir / 'vmcompact' / 'img').as_posix(),
|
|
||||||
config_path=(self.base_dir / 'configs').as_posix(),
|
|
||||||
theme_files=theme_files,
|
|
||||||
kind=kind,
|
|
||||||
theme_variant=theme_variant,
|
|
||||||
)
|
|
||||||
|
|
||||||
spec_file = self.temp_dir / f'{theme_variant}-{kind}.spec'
|
|
||||||
with open(spec_file, 'w') as f:
|
|
||||||
f.write(content)
|
|
||||||
|
|
||||||
return spec_file
|
|
||||||
|
|
||||||
def build_variant(self, theme_variant: str, kind: str) -> bool:
|
|
||||||
"""Build a single theme/kind variant."""
|
|
||||||
print(f'Building {theme_variant}-{kind}...')
|
|
||||||
|
|
||||||
# Create temporary files
|
|
||||||
py_file = self.create_python_file(theme_variant, kind)
|
|
||||||
spec_file = self.create_spec_file(theme_variant, kind, py_file)
|
|
||||||
|
|
||||||
# Build with PyInstaller
|
|
||||||
dist_path = self.dist_dir / f'{theme_variant}-{kind}'
|
|
||||||
cmd = [
|
|
||||||
'poetry',
|
|
||||||
'run',
|
|
||||||
'pyinstaller',
|
|
||||||
'--noconfirm',
|
|
||||||
'--distpath',
|
|
||||||
str(dist_path.parent),
|
|
||||||
str(spec_file),
|
|
||||||
]
|
|
||||||
|
|
||||||
try:
|
|
||||||
result = subprocess.run(
|
|
||||||
cmd, cwd=self.base_dir, capture_output=True, text=True
|
|
||||||
)
|
|
||||||
if result.returncode == 0:
|
|
||||||
print(f'✓ Built {theme_variant}-{kind}')
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
print(f'✗ Failed to build {theme_variant}-{kind}')
|
|
||||||
print(f'Error: {result.stderr}')
|
|
||||||
return False
|
|
||||||
except Exception as e:
|
|
||||||
print(f'✗ Exception building {theme_variant}-{kind}: {e}')
|
|
||||||
return False
|
|
||||||
|
|
||||||
def build_theme(self, theme_family: str) -> Dict[str, bool]:
|
|
||||||
"""Build all variants for a theme family."""
|
|
||||||
results = {}
|
|
||||||
|
|
||||||
if theme_family not in THEMES:
|
|
||||||
print(f'Unknown theme: {theme_family}')
|
|
||||||
return results
|
|
||||||
|
|
||||||
variants = THEMES[theme_family]
|
|
||||||
|
|
||||||
for variant in variants:
|
|
||||||
for kind in KINDS:
|
|
||||||
success = self.build_variant(variant, kind)
|
|
||||||
results[f'{variant}-{kind}'] = success
|
|
||||||
|
|
||||||
return results
|
|
||||||
|
|
||||||
|
|
||||||
def run_rewriter(theme_family: str, base_dir: Path) -> bool:
|
|
||||||
"""Run the theme rewriter if needed."""
|
|
||||||
if theme_family in ['azure', 'forest']:
|
|
||||||
print(f'Running rewriter for {theme_family} theme...')
|
|
||||||
cmd = [
|
|
||||||
'poetry',
|
|
||||||
'run',
|
|
||||||
'python',
|
|
||||||
'tools/rewriter.py',
|
|
||||||
'--rewrite',
|
|
||||||
'--theme',
|
|
||||||
theme_family,
|
|
||||||
]
|
|
||||||
try:
|
|
||||||
result = subprocess.run(cmd, cwd=base_dir)
|
|
||||||
return result.returncode == 0
|
|
||||||
except Exception as e:
|
|
||||||
print(f'Rewriter failed: {e}')
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def restore_rewriter(base_dir: Path) -> bool:
|
|
||||||
"""Restore files after rewriter."""
|
|
||||||
print('Restoring rewriter changes...')
|
|
||||||
cmd = ['poetry', 'run', 'python', 'tools/rewriter.py', '--restore']
|
|
||||||
try:
|
|
||||||
result = subprocess.run(cmd, cwd=base_dir)
|
|
||||||
return result.returncode == 0
|
|
||||||
except Exception as e:
|
|
||||||
print(f'Restore failed: {e}')
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description='Dynamic build system for voicemeeter-compact'
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'themes',
|
|
||||||
nargs='*',
|
|
||||||
choices=list(THEMES.keys()) + ['all'],
|
|
||||||
help='Themes to build (default: all)',
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'--dist-dir',
|
|
||||||
type=Path,
|
|
||||||
default=Path('dist'),
|
|
||||||
help='Distribution output directory',
|
|
||||||
)
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
if not args.themes or 'all' in args.themes:
|
|
||||||
themes_to_build = list(THEMES.keys())
|
|
||||||
else:
|
|
||||||
themes_to_build = args.themes
|
|
||||||
|
|
||||||
base_dir = Path.cwd()
|
|
||||||
args.dist_dir.mkdir(exist_ok=True)
|
|
||||||
|
|
||||||
print(f'Building themes: {", ".join(themes_to_build)}')
|
|
||||||
|
|
||||||
all_results = {}
|
|
||||||
|
|
||||||
with DynamicBuilder(base_dir, args.dist_dir) as builder:
|
|
||||||
for theme_family in themes_to_build:
|
|
||||||
# Run rewriter if needed
|
|
||||||
if not run_rewriter(theme_family, base_dir):
|
|
||||||
print(f'Skipping {theme_family} due to rewriter failure')
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Build theme
|
|
||||||
results = builder.build_theme(theme_family)
|
|
||||||
all_results.update(results)
|
|
||||||
|
|
||||||
finally:
|
|
||||||
# Always restore rewriter changes
|
|
||||||
if theme_family in ['azure', 'forest']:
|
|
||||||
restore_rewriter(base_dir)
|
|
||||||
|
|
||||||
# Report results
|
|
||||||
print('\n' + '=' * 50)
|
|
||||||
print('BUILD SUMMARY')
|
|
||||||
print('=' * 50)
|
|
||||||
|
|
||||||
success_count = 0
|
|
||||||
total_count = 0
|
|
||||||
|
|
||||||
for build_name, success in all_results.items():
|
|
||||||
status = '✓' if success else '✗'
|
|
||||||
print(f'{status} {build_name}')
|
|
||||||
if success:
|
|
||||||
success_count += 1
|
|
||||||
total_count += 1
|
|
||||||
|
|
||||||
print(f'\nSuccess: {success_count}/{total_count}')
|
|
||||||
|
|
||||||
if success_count == total_count:
|
|
||||||
print('All builds completed successfully!')
|
|
||||||
sys.exit(0)
|
|
||||||
else:
|
|
||||||
print('Some builds failed!')
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
@ -1,8 +1,3 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""Rewrites app.py, builders.py, menu.py, and navigation.py to remove sv_ttk dependencies and apply theme changes for the build process.
|
|
||||||
Also provides a cleanup function to restore the original files after building.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@ -30,6 +25,9 @@ def rewrite_app(theme):
|
|||||||
with open(outfile, 'w') as output:
|
with open(outfile, 'w') as output:
|
||||||
for line in input:
|
for line in input:
|
||||||
match line:
|
match line:
|
||||||
|
# App init()
|
||||||
|
case ' def __init__(self, vmr):\n':
|
||||||
|
output.write(' def __init__(self, vmr, theme):\n')
|
||||||
case ' self._vmr = vmr\n':
|
case ' self._vmr = vmr\n':
|
||||||
write_outs(
|
write_outs(
|
||||||
output,
|
output,
|
||||||
@ -38,7 +36,7 @@ def rewrite_app(theme):
|
|||||||
' self._theme = theme\n',
|
' self._theme = theme\n',
|
||||||
' self._theme_name = theme.split("-")[0]\n',
|
' self._theme_name = theme.split("-")[0]\n',
|
||||||
' self._theme_type = theme.split("-")[-1]\n',
|
' self._theme_type = theme.split("-")[-1]\n',
|
||||||
' tcldir = Path.cwd() / "theme" / self._theme_name\n',
|
' tcldir = Path.cwd() / "theme"\n',
|
||||||
' if not tcldir.is_dir():\n',
|
' if not tcldir.is_dir():\n',
|
||||||
' tcldir = Path.cwd() / "_internal" / "theme"\n',
|
' tcldir = Path.cwd() / "_internal" / "theme"\n',
|
||||||
' match self._theme_name:\n',
|
' match self._theme_name:\n',
|
||||||
@ -48,6 +46,11 @@ def rewrite_app(theme):
|
|||||||
' self.tk.call("source", tcldir.resolve() / f"{self._theme_name}.tcl")\n',
|
' self.tk.call("source", tcldir.resolve() / f"{self._theme_name}.tcl")\n',
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
# def connect()
|
||||||
|
case 'def connect(kind_id: str, vmr) -> App:\n':
|
||||||
|
output.write('def connect(kind_id: str, vmr, theme) -> App:\n')
|
||||||
|
case ' return VMMIN_cls(vmr)\n':
|
||||||
|
output.write(' return VMMIN_cls(vmr, theme)\n')
|
||||||
case _:
|
case _:
|
||||||
output.write(line)
|
output.write(line)
|
||||||
|
|
||||||
@ -220,54 +223,37 @@ def rewrite_menu(theme):
|
|||||||
output.write(line)
|
output.write(line)
|
||||||
|
|
||||||
|
|
||||||
def rewrite_navigation(theme):
|
|
||||||
navigation_logger = logger.getChild('navigation')
|
|
||||||
navigation_logger.info('rewriting navigation.py')
|
|
||||||
infile = Path(SRC_DIR) / 'navigation.bk'
|
|
||||||
outfile = Path(PACKAGE_DIR) / 'navigation.py'
|
|
||||||
with open(infile, 'r') as input:
|
|
||||||
with open(outfile, 'w') as output:
|
|
||||||
for line in input:
|
|
||||||
match line:
|
|
||||||
case ' self.builder.create_info_button()\n':
|
|
||||||
if theme.startswith('azure'):
|
|
||||||
output.write(
|
|
||||||
' # self.builder.create_info_button()\n'
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
output.write(line)
|
|
||||||
case _:
|
|
||||||
output.write(line)
|
|
||||||
|
|
||||||
|
|
||||||
def prepare_for_build(theme):
|
def prepare_for_build(theme):
|
||||||
################# MOVE FILES FROM PACKAGE DIR INTO SRC DIR #########################
|
################# MOVE FILES FROM PACKAGE DIR INTO SRC DIR #########################
|
||||||
for file in (
|
for file in (
|
||||||
PACKAGE_DIR / 'app.py',
|
PACKAGE_DIR / 'app.py',
|
||||||
PACKAGE_DIR / 'builders.py',
|
PACKAGE_DIR / 'builders.py',
|
||||||
PACKAGE_DIR / 'menu.py',
|
PACKAGE_DIR / 'menu.py',
|
||||||
PACKAGE_DIR / 'navigation.py',
|
|
||||||
):
|
):
|
||||||
if file.exists():
|
if file.exists():
|
||||||
logger.debug(f'moving {str(file)}')
|
logger.debug(f'moving {str(file)}')
|
||||||
file.rename(SRC_DIR / f'{file.stem}.bk')
|
file.rename(SRC_DIR / f'{file.stem}.bk')
|
||||||
|
|
||||||
###################### RUN THE FILE REWRITER FOR EACH *.BK #########################
|
###################### RUN THE FILE REWRITER FOR EACH *.BK #########################
|
||||||
for step in (rewrite_app, rewrite_builders, rewrite_menu, rewrite_navigation):
|
for step in (rewrite_app, rewrite_builders, rewrite_menu):
|
||||||
step(theme)
|
step(theme)
|
||||||
|
|
||||||
|
|
||||||
def cleanup():
|
def cleanup():
|
||||||
########################## RESTORE *.BK FILES #####################################
|
########################## RESTORE *.BK FILES #####################################
|
||||||
|
for file in (
|
||||||
|
PACKAGE_DIR / 'app.py',
|
||||||
|
PACKAGE_DIR / 'builders.py',
|
||||||
|
PACKAGE_DIR / 'menu.py',
|
||||||
|
):
|
||||||
|
file.unlink()
|
||||||
|
|
||||||
for file in (
|
for file in (
|
||||||
SRC_DIR / 'app.bk',
|
SRC_DIR / 'app.bk',
|
||||||
SRC_DIR / 'builders.bk',
|
SRC_DIR / 'builders.bk',
|
||||||
SRC_DIR / 'menu.bk',
|
SRC_DIR / 'menu.bk',
|
||||||
SRC_DIR / 'navigation.bk',
|
|
||||||
):
|
):
|
||||||
if file.exists():
|
file.rename(PACKAGE_DIR / f'{file.stem}.py')
|
||||||
logger.debug(f'moving {str(file)}')
|
|
||||||
file.replace(PACKAGE_DIR / f'{file.stem}.py')
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@ -1,192 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Spec file generator for voicemeeter-compact builds.
|
|
||||||
Generates Python launcher files and PyInstaller spec files from templates.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
# Build configuration
|
|
||||||
THEMES = {
|
|
||||||
'azure': ['azure-light', 'azure-dark'],
|
|
||||||
'forest': ['forest-light', 'forest-dark'],
|
|
||||||
'sunvalley': ['sunvalley'], # Single variant, no light/dark
|
|
||||||
}
|
|
||||||
|
|
||||||
KINDS = ['basic', 'banana', 'potato']
|
|
||||||
|
|
||||||
# Templates
|
|
||||||
PYTHON_TEMPLATE = """import voicemeeterlib
|
|
||||||
|
|
||||||
import vmcompact
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
KIND_ID = '{kind}'
|
|
||||||
|
|
||||||
with voicemeeterlib.api(KIND_ID) as vmr:{theme_arg}
|
|
||||||
app = vmcompact.connect(KIND_ID, vmr{theme_param})
|
|
||||||
app.mainloop()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
"""
|
|
||||||
|
|
||||||
SPEC_TEMPLATE = """# -*- mode: python ; coding: utf-8 -*-
|
|
||||||
|
|
||||||
|
|
||||||
block_cipher = None
|
|
||||||
|
|
||||||
added_files = [
|
|
||||||
( '../../vmcompact/img', 'img' ),{theme_files}
|
|
||||||
( '../../configs', 'configs' ),
|
|
||||||
]
|
|
||||||
|
|
||||||
a = Analysis(
|
|
||||||
['{script_name}'],
|
|
||||||
pathex=[],
|
|
||||||
binaries=[],
|
|
||||||
datas=added_files,
|
|
||||||
hiddenimports=[],
|
|
||||||
hookspath=[],
|
|
||||||
hooksconfig={{}},
|
|
||||||
runtime_hooks=[],
|
|
||||||
excludes=[],
|
|
||||||
win_no_prefer_redirects=False,
|
|
||||||
win_private_assemblies=False,
|
|
||||||
cipher=block_cipher,
|
|
||||||
noarchive=False,
|
|
||||||
)
|
|
||||||
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
|
||||||
|
|
||||||
exe = EXE(
|
|
||||||
pyz,
|
|
||||||
a.scripts,
|
|
||||||
[],
|
|
||||||
exclude_binaries=True,
|
|
||||||
name='{kind}',
|
|
||||||
debug=False,
|
|
||||||
bootloader_ignore_signals=False,
|
|
||||||
strip=False,
|
|
||||||
upx=True,
|
|
||||||
console=False,
|
|
||||||
disable_windowed_traceback=False,
|
|
||||||
argv_emulation=False,
|
|
||||||
target_arch=None,
|
|
||||||
codesign_identity=None,
|
|
||||||
entitlements_file=None,
|
|
||||||
)
|
|
||||||
coll = COLLECT(
|
|
||||||
exe,
|
|
||||||
a.binaries,
|
|
||||||
a.zipfiles,
|
|
||||||
a.datas,
|
|
||||||
strip=False,
|
|
||||||
upx=True,
|
|
||||||
upx_exclude=[],
|
|
||||||
name='{kind}',
|
|
||||||
)
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def generate_python_file(theme_variant: str, kind: str, output_dir: Path) -> None:
|
|
||||||
"""Generate a Python launcher file."""
|
|
||||||
if theme_variant == 'sunvalley':
|
|
||||||
# Sunvalley doesn't use theme parameter
|
|
||||||
theme_arg = ''
|
|
||||||
theme_param = ''
|
|
||||||
else:
|
|
||||||
theme_arg = f"\n theme = '{theme_variant}'"
|
|
||||||
theme_param = ', theme=theme'
|
|
||||||
|
|
||||||
content = PYTHON_TEMPLATE.format(
|
|
||||||
kind=kind, theme_arg=theme_arg, theme_param=theme_param
|
|
||||||
)
|
|
||||||
|
|
||||||
filename = f'{theme_variant}-{kind}.py'
|
|
||||||
output_path = output_dir / filename
|
|
||||||
|
|
||||||
with open(output_path, 'w') as f:
|
|
||||||
f.write(content)
|
|
||||||
|
|
||||||
print(f'Generated: {output_path}')
|
|
||||||
|
|
||||||
|
|
||||||
def generate_spec_file(theme_variant: str, kind: str, output_dir: Path) -> None:
|
|
||||||
"""Generate a PyInstaller spec file."""
|
|
||||||
script_name = f'{theme_variant}-{kind}.py'
|
|
||||||
|
|
||||||
if theme_variant == 'sunvalley':
|
|
||||||
# Sunvalley doesn't include theme files
|
|
||||||
theme_files = ''
|
|
||||||
else:
|
|
||||||
theme_base = theme_variant.split('-')[0] # 'azure' from 'azure-dark'
|
|
||||||
theme_files = f"\n ( '../../theme/{theme_base}', 'theme' ),"
|
|
||||||
|
|
||||||
content = SPEC_TEMPLATE.format(
|
|
||||||
script_name=script_name, theme_files=theme_files, kind=kind
|
|
||||||
)
|
|
||||||
|
|
||||||
filename = f'{theme_variant}-{kind}.spec'
|
|
||||||
output_path = output_dir / filename
|
|
||||||
|
|
||||||
with open(output_path, 'w') as f:
|
|
||||||
f.write(content)
|
|
||||||
|
|
||||||
print(f'Generated: {output_path}')
|
|
||||||
|
|
||||||
|
|
||||||
def generate_all_files(output_base_dir: Path) -> None:
|
|
||||||
"""Generate all Python and spec files for all theme/kind combinations."""
|
|
||||||
for theme_family, theme_variants in THEMES.items():
|
|
||||||
theme_dir = output_base_dir / theme_family
|
|
||||||
theme_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
for theme_variant in theme_variants:
|
|
||||||
for kind in KINDS:
|
|
||||||
generate_python_file(theme_variant, kind, theme_dir)
|
|
||||||
generate_spec_file(theme_variant, kind, theme_dir)
|
|
||||||
|
|
||||||
|
|
||||||
def clean_existing_files(output_base_dir: Path) -> None:
|
|
||||||
"""Remove all existing generated files."""
|
|
||||||
for theme_family in THEMES.keys():
|
|
||||||
theme_dir = output_base_dir / theme_family
|
|
||||||
if theme_dir.exists():
|
|
||||||
for file in theme_dir.glob('*.py'):
|
|
||||||
file.unlink()
|
|
||||||
print(f'Removed: {file}')
|
|
||||||
for file in theme_dir.glob('*.spec'):
|
|
||||||
file.unlink()
|
|
||||||
print(f'Removed: {file}')
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description='Generate spec files for voicemeeter-compact'
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'--clean', action='store_true', help='Clean existing files before generating'
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'--output-dir',
|
|
||||||
type=Path,
|
|
||||||
default=Path('spec'),
|
|
||||||
help='Output directory for spec files (default: spec)',
|
|
||||||
)
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
if args.clean:
|
|
||||||
print('Cleaning existing files...')
|
|
||||||
clean_existing_files(args.output_dir)
|
|
||||||
|
|
||||||
print('Generating spec files...')
|
|
||||||
generate_all_files(args.output_dir)
|
|
||||||
print('Done!')
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
@ -6,11 +6,10 @@ from tkinter import messagebox, ttk
|
|||||||
from typing import NamedTuple
|
from typing import NamedTuple
|
||||||
|
|
||||||
import voicemeeterlib
|
import voicemeeterlib
|
||||||
from voicemeeterlib import kinds
|
|
||||||
|
|
||||||
from .builders import MainFrameBuilder
|
from .builders import MainFrameBuilder
|
||||||
from .configurations import loader
|
from .configurations import loader
|
||||||
from .data import _base_values, _configuration, get_configuration
|
from .data import _base_values, _configuration, _kinds_all, get_configuration
|
||||||
from .errors import VMCompactError
|
from .errors import VMCompactError
|
||||||
from .menu import Menus
|
from .menu import Menus
|
||||||
from .subject import Subject
|
from .subject import Subject
|
||||||
@ -38,7 +37,7 @@ class App(tk.Tk):
|
|||||||
)
|
)
|
||||||
return APP_cls
|
return APP_cls
|
||||||
|
|
||||||
def __init__(self, vmr, theme):
|
def __init__(self, vmr):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.logger = logger.getChild(self.__class__.__name__)
|
self.logger = logger.getChild(self.__class__.__name__)
|
||||||
self._vmr = vmr
|
self._vmr = vmr
|
||||||
@ -199,14 +198,14 @@ class App(tk.Tk):
|
|||||||
self.destroy()
|
self.destroy()
|
||||||
|
|
||||||
|
|
||||||
_apps = {kind.name: App.make(kind) for kind in kinds.all}
|
_apps = {kind.name: App.make(kind) for kind in _kinds_all}
|
||||||
|
|
||||||
|
|
||||||
def connect(kind_id: str, vmr, theme=None) -> App:
|
def connect(kind_id: str, vmr) -> App:
|
||||||
"""return App of the kind requested"""
|
"""return App of the kind requested"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
VMMIN_cls = _apps[kind_id]
|
VMMIN_cls = _apps[kind_id]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise VMCompactError(f'Invalid kind: {kind_id}')
|
raise VMCompactError(f'Invalid kind: {kind_id}')
|
||||||
return VMMIN_cls(vmr, theme)
|
return VMMIN_cls(vmr)
|
||||||
|
|||||||
@ -6,7 +6,6 @@ from tkinter import ttk
|
|||||||
|
|
||||||
import sv_ttk
|
import sv_ttk
|
||||||
|
|
||||||
from . import util
|
|
||||||
from .banner import Banner
|
from .banner import Banner
|
||||||
from .channels import _make_channelframe
|
from .channels import _make_channelframe
|
||||||
from .config import BusConfig, StripConfig
|
from .config import BusConfig, StripConfig
|
||||||
@ -562,22 +561,31 @@ class StripConfigFrameBuilder(ChannelConfigFrameBuilder):
|
|||||||
class BusConfigFrameBuilder(ChannelConfigFrameBuilder):
|
class BusConfigFrameBuilder(ChannelConfigFrameBuilder):
|
||||||
"""Responsible for building channel configframe widgets"""
|
"""Responsible for building channel configframe widgets"""
|
||||||
|
|
||||||
def __init__(self, configframe, app):
|
|
||||||
super().__init__(configframe)
|
|
||||||
self.app = app
|
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
self.configframe.bus_mode_map = util.get_busmode_fullnames(self.app.kind)
|
# fmt: off
|
||||||
self.configframe.bus_mode_map_reverse = util.get_busmode_fullnames_reversed(
|
self.configframe.bus_mode_map = {
|
||||||
self.app.kind
|
"normal": "Normal",
|
||||||
)
|
"amix": "Mix Down A",
|
||||||
self.configframe.bus_modes = util.get_busmode_shortnames(self.app.kind)
|
"bmix": "Mix Down B",
|
||||||
|
"repeat": "Stereo Repeat",
|
||||||
|
"composite": "Composite",
|
||||||
|
"tvmix": "Up Mix TV",
|
||||||
|
"upmix21": "Up Mix 2.1",
|
||||||
|
"upmix41": "Up Mix 4.1",
|
||||||
|
"upmix61": "Up Mix 6.1",
|
||||||
|
"centeronly": "Center Only",
|
||||||
|
"lfeonly": "LFE Only",
|
||||||
|
"rearonly": "Rear Only",
|
||||||
|
}
|
||||||
|
self.configframe.bus_mode_map_reverse = {v: k for k, v in self.configframe.bus_mode_map.items()}
|
||||||
|
self.configframe.bus_modes = list(self.configframe.bus_mode_map.keys())
|
||||||
|
# fmt: on
|
||||||
self.configframe.int_params = ('mono',)
|
self.configframe.int_params = ('mono',)
|
||||||
self.configframe.int_param_vars = [
|
self.configframe.int_param_vars = [
|
||||||
tk.IntVar(value=getattr(self.configframe.target, param))
|
tk.IntVar(value=getattr(self.configframe.target, param))
|
||||||
for param in self.configframe.int_params
|
for param in self.configframe.int_params
|
||||||
]
|
]
|
||||||
self.configframe.mono_modes = util.get_busmono_modes()
|
self.configframe.mono_modes = ['mono: off', 'mono: on', 'stereo reverse']
|
||||||
self.configframe.bus_mono_label_text = tk.StringVar(
|
self.configframe.bus_mono_label_text = tk.StringVar(
|
||||||
value=self.configframe.mono_modes[self.configframe.target.mono]
|
value=self.configframe.mono_modes[self.configframe.target.mono]
|
||||||
)
|
)
|
||||||
@ -591,12 +599,10 @@ class BusConfigFrameBuilder(ChannelConfigFrameBuilder):
|
|||||||
|
|
||||||
def create_bus_mode_button(self):
|
def create_bus_mode_button(self):
|
||||||
self.configframe.busmode_button = ttk.Button(
|
self.configframe.busmode_button = ttk.Button(
|
||||||
self.configframe,
|
self.configframe, textvariable=self.configframe.bus_mode_label_text
|
||||||
textvariable=self.configframe.bus_mode_label_text,
|
|
||||||
width=15,
|
|
||||||
)
|
)
|
||||||
self.configframe.busmode_button.grid(
|
self.configframe.busmode_button.grid(
|
||||||
column=0, row=0, columnspan=2, sticky=(tk.W), padx=1, pady=1
|
column=0, row=0, columnspan=2, sticky=(tk.W)
|
||||||
)
|
)
|
||||||
self.configframe.busmode_button.bind(
|
self.configframe.busmode_button.bind(
|
||||||
'<Button-1>',
|
'<Button-1>',
|
||||||
@ -613,9 +619,7 @@ class BusConfigFrameBuilder(ChannelConfigFrameBuilder):
|
|||||||
|
|
||||||
def create_bus_mono_button(self):
|
def create_bus_mono_button(self):
|
||||||
self.configframe.mono_button = ttk.Button(
|
self.configframe.mono_button = ttk.Button(
|
||||||
self.configframe,
|
self.configframe, textvariable=self.configframe.bus_mono_label_text
|
||||||
textvariable=self.configframe.bus_mono_label_text,
|
|
||||||
width=15,
|
|
||||||
)
|
)
|
||||||
self.configframe.mono_button.bind(
|
self.configframe.mono_button.bind(
|
||||||
'<Button-1>',
|
'<Button-1>',
|
||||||
@ -625,9 +629,7 @@ class BusConfigFrameBuilder(ChannelConfigFrameBuilder):
|
|||||||
'<Button-3>',
|
'<Button-3>',
|
||||||
partial(self.configframe.pause_updates, self.configframe.rotate_mono_left),
|
partial(self.configframe.pause_updates, self.configframe.rotate_mono_left),
|
||||||
)
|
)
|
||||||
self.configframe.mono_button.grid(
|
self.configframe.mono_button.grid(column=0, row=1, sticky=(tk.W))
|
||||||
column=0, row=1, sticky=(tk.W), padx=1, pady=1
|
|
||||||
)
|
|
||||||
|
|
||||||
def create_param_buttons(self):
|
def create_param_buttons(self):
|
||||||
param_buttons = [
|
param_buttons = [
|
||||||
|
|||||||
@ -217,7 +217,7 @@ class BusConfig(Config):
|
|||||||
self.grid(column=0, row=1, columnspan=4, padx=(2,))
|
self.grid(column=0, row=1, columnspan=4, padx=(2,))
|
||||||
else:
|
else:
|
||||||
self.grid(column=0, row=3, columnspan=4, padx=(2,))
|
self.grid(column=0, row=3, columnspan=4, padx=(2,))
|
||||||
self.builder = builders.BusConfigFrameBuilder(self, parent)
|
self.builder = builders.BusConfigFrameBuilder(self)
|
||||||
self.builder.setup()
|
self.builder.setup()
|
||||||
self.make_row_0()
|
self.make_row_0()
|
||||||
self.make_row_1()
|
self.make_row_1()
|
||||||
|
|||||||
@ -61,6 +61,10 @@ class BaseValues(metaclass=SingletonMeta):
|
|||||||
_base_values = BaseValues()
|
_base_values = BaseValues()
|
||||||
_configuration = Configurations()
|
_configuration = Configurations()
|
||||||
|
|
||||||
|
_kinds = {kind.name: kind for kind in kinds.kinds_all}
|
||||||
|
|
||||||
|
_kinds_all = _kinds.values()
|
||||||
|
|
||||||
|
|
||||||
def kind_get(kind_id):
|
def kind_get(kind_id):
|
||||||
return kinds.request_kind_map(kind_id)
|
return _kinds[kind_id]
|
||||||
|
|||||||
@ -1,34 +0,0 @@
|
|||||||
def get_busmode_fullnames(kind) -> dict:
|
|
||||||
if kind.name == 'basic':
|
|
||||||
return {
|
|
||||||
'normal': 'Normal',
|
|
||||||
'amix': 'Mix Down A',
|
|
||||||
'repeat': 'Stereo Repeat',
|
|
||||||
'composite': 'Composite',
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
'normal': 'Normal',
|
|
||||||
'amix': 'Mix Down A',
|
|
||||||
'bmix': 'Mix Down B',
|
|
||||||
'repeat': 'Stereo Repeat',
|
|
||||||
'composite': 'Composite',
|
|
||||||
'tvmix': 'Up Mix TV',
|
|
||||||
'upmix21': 'Up Mix 2.1',
|
|
||||||
'upmix41': 'Up Mix 4.1',
|
|
||||||
'upmix61': 'Up Mix 6.1',
|
|
||||||
'centeronly': 'Center Only',
|
|
||||||
'lfeonly': 'LFE Only',
|
|
||||||
'rearonly': 'Rear Only',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def get_busmode_fullnames_reversed(kind) -> dict:
|
|
||||||
return {v: k for k, v in get_busmode_fullnames(kind).items()}
|
|
||||||
|
|
||||||
|
|
||||||
def get_busmode_shortnames(kind) -> list:
|
|
||||||
return list(get_busmode_fullnames(kind).keys())
|
|
||||||
|
|
||||||
|
|
||||||
def get_busmono_modes() -> list:
|
|
||||||
return ['Mono: off', 'Mono: on', 'Stereo Reverse']
|
|
||||||
Loading…
x
Reference in New Issue
Block a user