Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,7 @@ student_clap/
# (vendored redis/pg binaries, assets, nfpm config, runtime launchers), the shared
# PyInstaller spec + build tooling, and build artifacts. None of it is used by the
# Linux container, so keep it out of the image.
macos/
linux/
windows/
native-build/
AudioMuse-AI.spec
scripts/standalone/
build/
Expand Down Expand Up @@ -67,7 +65,5 @@ model/
*.bin
*.h5
*.pb
CLAMP3/weights_*.pth
CLAMP3/*.npy
.cache/
*.tar.gz
16 changes: 8 additions & 8 deletions .github/workflows/build-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
# Mirrors build-macos.yml. The package bundles EVERYTHING (Python runtime,
# onnxruntime, PyAV/ffmpeg, the ONNX models, an embedded PostgreSQL via pgserver
# and an embedded Redis), so an installed package needs no Docker and no
# separately-installed database/broker. See linux/README.md for why Postgres and
# separately-installed database/broker. See native-build/linux/README.md for why Postgres and
# Redis are bundled rather than declared as system dependencies (pgvector +
# unaccent/pg_trgm against an exact PG minor, plus the per-distro package-name /
# version skew, make a portable single .deb/.rpm "hard dependency" infeasible).
#
# The small native build inputs (redis-server, the unaccent/pg_trgm contrib
# modules) are built from source in this workflow (linux/vendor/*) rather than
# modules) are built from source in this workflow (native-build/linux/vendor/*) rather than
# committed, then baked into the bundle by the shared AudioMuse-AI.spec.
#
# The ~5 GB of models are NOT in git. This workflow assembles ./model from the
Expand Down Expand Up @@ -59,7 +59,7 @@ jobs:
# * x86_64 -> pgserver wheel (+ vendored unaccent/pg_trgm grafted in).
# * aarch64 -> a from-source PostgreSQL (server + the two contrib modules)
# built here and bundled wholesale; managed by
# linux/embedded_pg.py at runtime.
# native-build/linux/embedded_pg.py at runtime.
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -114,8 +114,8 @@ jobs:
- name: Validate the desktop entries
run: |
set -euo pipefail
desktop-file-validate linux/packaging/AudioMuse-AI.desktop
desktop-file-validate linux/packaging/AudioMuse-AI-stop.desktop
desktop-file-validate native-build/linux/packaging/AudioMuse-AI.desktop
desktop-file-validate native-build/linux/packaging/AudioMuse-AI-stop.desktop
echo "Desktop entries are valid."

- name: Install Python dependencies
Expand All @@ -127,21 +127,21 @@ jobs:
pip install -r requirements/linux.txt

- name: Build vendored redis-server
run: bash linux/vendor/build-redis.sh
run: bash native-build/linux/vendor/build-redis.sh

# x86_64: compile unaccent/pg_trgm against the installed pgserver's PG.
- name: Build vendored PostgreSQL contrib (x86_64, against pgserver)
if: matrix.arch == 'x86_64'
run: |
set -euo pipefail
source .venv-linux/bin/activate # pgserver must be importable
bash linux/vendor/pg-contrib/build-pg-contrib.sh
bash native-build/linux/vendor/pg-contrib/build-pg-contrib.sh

# aarch64: no pgserver wheel -> build a relocatable PostgreSQL (server +
# unaccent/pg_trgm) from source and bundle the whole tree.
- name: Build vendored PostgreSQL from source (aarch64)
if: matrix.arch == 'aarch64'
run: bash linux/vendor/postgres/build-postgres.sh
run: bash native-build/linux/vendor/postgres/build-postgres.sh

- name: Assemble ./model (mirrors the Dockerfile/macOS models stage)
env:
Expand Down
25 changes: 13 additions & 12 deletions .github/workflows/build-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,17 @@
# reliably universal2, so we build natively on an arm64 runner (macos-14).
#
# Reproducibility: the small, exact-version native build inputs are committed in
# git (macos/vendor/redis/arm64/redis-server and macos/vendor/pg-contrib/arm64/*),
# git (native-build/macos/vendor/redis/arm64/redis-server and
# native-build/macos/vendor/pg-contrib/arm64/*),
# NOT regenerated at build time, so the bundle's embedded Redis and the Postgres
# unaccent/pg_trgm contrib extensions are byte-identical to the tested local build.
#
# The app is ad-hoc signed only (no Apple Developer account); end users clear
# quarantine with `xattr -dr ...` after download (see macos/README.md).
# quarantine with `xattr -dr ...` after download (see native-build/macos/README.md).
#
# The ~5 GB of models are NOT in git. This workflow assembles ./model from the
# GitHub releases the Dockerfile uses, INCLUDING the HuggingFace cache
# (huggingface_models.tar.gz) — macos/env.py points HF_HOME at model/huggingface
# (huggingface_models.tar.gz) — native-build/macos/env.py points HF_HOME at model/huggingface
# for the CLAP RoBERTa tokenizer, so the bundle needs it. The HF cache is then
# trimmed to just that tokenizer (bert/bart and the roberta weights are unused by
# the app, ~1.4 GB) so the release zip stays under GitHub's 2 GB asset limit.
Expand Down Expand Up @@ -86,20 +87,20 @@ jobs:
# These are committed in git (not regenerated here) so the bundle is
# reproducible. If a fresh checkout is missing them, fail loudly.
required=(
macos/vendor/redis/arm64/redis-server
macos/vendor/pg-contrib/arm64/lib/unaccent.dylib
macos/vendor/pg-contrib/arm64/lib/pg_trgm.dylib
macos/vendor/pg-contrib/arm64/extension/unaccent.control
macos/vendor/pg-contrib/arm64/extension/pg_trgm.control
macos/vendor/pg-contrib/arm64/tsearch_data/unaccent.rules
native-build/macos/vendor/redis/arm64/redis-server
native-build/macos/vendor/pg-contrib/arm64/lib/unaccent.dylib
native-build/macos/vendor/pg-contrib/arm64/lib/pg_trgm.dylib
native-build/macos/vendor/pg-contrib/arm64/extension/unaccent.control
native-build/macos/vendor/pg-contrib/arm64/extension/pg_trgm.control
native-build/macos/vendor/pg-contrib/arm64/tsearch_data/unaccent.rules
)
missing=0
for f in "${required[@]}"; do
if [ ! -s "$f" ]; then echo "::error::Missing committed vendor file: $f"; missing=1; fi
done
[ "$missing" -eq 0 ] || { echo "::error::macos/vendor/ must be committed (see macos/vendor/pg-contrib/README.md)."; exit 1; }
chmod +x macos/vendor/redis/arm64/redis-server
file macos/vendor/redis/arm64/redis-server
[ "$missing" -eq 0 ] || { echo "::error::native-build/macos/vendor/ must be committed (see native-build/macos/vendor/pg-contrib/README.md)."; exit 1; }
chmod +x native-build/macos/vendor/redis/arm64/redis-server
file native-build/macos/vendor/redis/arm64/redis-server

- name: Assemble ./model (mirrors the Dockerfile models stage)
env:
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/build-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,20 +81,20 @@ jobs:

- name: Build vendored redis-server.exe
shell: cmd
run: windows\vendor\build-redis.bat
run: native-build\windows\vendor\build-redis.bat

- name: Verify vendored PostgreSQL contrib is present
shell: powershell
run: |
$dest = "windows\vendor\pg-contrib\$env:ARCH"
$dest = "native-build\windows\vendor\pg-contrib\$env:ARCH"
$required = @("lib\unaccent.dll", "lib\pg_trgm.dll", "extension\unaccent.control", "extension\pg_trgm.control", "extension\unaccent--1.1.sql", "extension\pg_trgm--1.3.sql", "tsearch_data\unaccent.rules")
$bad = @()
foreach ($f in $required) {
$p = Join-Path $dest $f
if (-not (Test-Path $p) -or (Get-Item $p).Length -eq 0) { $bad += $f }
}
if ($bad.Count -ne 0) {
Write-Error "Missing or empty vendored pg-contrib files: $($bad -join ', '). Regenerate with windows/vendor/pg-contrib/build-pg-contrib-cross.sh -- never ship empty placeholders (they silently disable unaccent/pg_trgm search)."
Write-Error "Missing or empty vendored pg-contrib files: $($bad -join ', '). Regenerate with native-build/windows/vendor/pg-contrib/build-pg-contrib-cross.sh -- never ship empty placeholders (they silently disable unaccent/pg_trgm search)."
exit 1
}
Write-Host "Vendored pg-contrib present and non-empty."
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -143,30 +143,30 @@ jobs:
- name: Run integration tests
run: |
# Run the MusiCNN integration test
pytest test/test_analysis_integration.py -s -v --tb=short
pytest test/integration/test_analysis_integration.py -s -v --tb=short

# Run the CLAP integration test
pytest test/test_clap_analysis_integration.py -s -v --tb=short
pytest test/integration/test_clap_analysis_integration.py -s -v --tb=short

- name: Run provider migration integration test
env:
AUDIOMUSE_TEST_DATABASE_URL: postgresql://postgres:postgres@localhost:5432/audiomuse_migration_test
run: |
pytest test/test_provider_migration_integration.py -s -v --tb=short
pytest test/integration/test_provider_migration_integration.py -s -v --tb=short

- name: Run app endpoint and auth integration tests
env:
AUDIOMUSE_TEST_DATABASE_URL: postgresql://postgres:postgres@localhost:5432/audiomuse_migration_test
run: |
pytest test/test_app_endpoints_integration.py test/test_auth_users_integration.py -m integration -s -v --tb=short
pytest test/integration/test_app_endpoints_integration.py test/integration/test_auth_users_integration.py -m integration -s -v --tb=short

- name: Run lyrics integration test
run: |
# On first run test/lyrics_expected_gte_512.json does not exist yet: the
# test enters RECORD mode automatically, writes the file and exits
# successfully. The next step then commits it back to main so
# subsequent runs pin against it (cosine similarity >= 0.99).
pytest test/test_lyrics_analysis_integration.py -s -v --tb=short
pytest test/integration/test_lyrics_analysis_integration.py -s -v --tb=short

- name: Commit recorded lyrics expected vectors
# Push back the auto-recorded vectors on:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ jobs:

- name: Run unit tests
run: |
pytest tests/unit/ -v --tb=short
pytest test/unit/ -v --tb=short
22 changes: 6 additions & 16 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ venv/
.venv-windows/
env/

# Linux native build inputs built fresh in CI (not committed; see linux/vendor/README.md)
/linux/vendor/redis/
/linux/vendor/pg-contrib/*/
/linux/vendor/postgres/*/
/windows/vendor/redis/
# Linux native build inputs built fresh in CI (not committed; see native-build/linux/vendor/README.md)
/native-build/linux/vendor/redis/
/native-build/linux/vendor/pg-contrib/*/
/native-build/linux/vendor/postgres/*/
/native-build/windows/vendor/redis/

# Local environment variables
# IMPORTANT: Never commit your .env file with secrets!
Expand Down Expand Up @@ -46,16 +46,6 @@ env/
htmlcov/
nul

# Large model files in query folder
/query/*.pt
/query/*.onnx


# CLAMP3 weight files (large model files)
/CLAMP3/weights_*.pth
/CLAMP3/*.pth


# Downloaded ONNX models (from run_worker_macos.sh or similar)
/model/

Expand All @@ -82,4 +72,4 @@ analysis.md
.claude/settings.local.json
.claude/settings.local.json
lyrics_model_gte_vnni.tar.gz
windows/vendor/redis/amd64/redis-server.exe
native-build/windows/vendor/redis/amd64/redis-server.exe
10 changes: 10 additions & 0 deletions .sonarcloud.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Vendored third-party code (PostgreSQL contrib extensions bundled per platform)
# is not ours to analyze.
sonar.exclusions=native-build/*/vendor/**

# Copy-paste detection exclusions:
# - native-build/ holds three intentionally parallel per-platform trees
# (linux/macos/windows supervisor, launcher, env, paths); their similarity
# is by design, not accidental duplication.
# - test/ duplication is repeated setup/fixture boilerplate across tests.
sonar.cpd.exclusions=native-build/**,test/**
7 changes: 5 additions & 2 deletions AudioMuse-AI.spec
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ import glob
import importlib.util
import os
import platform
import sys

from PyInstaller.utils.hooks import collect_data_files, collect_dynamic_libs, collect_submodules

ROOT = SPECPATH
NATIVE = os.path.join(ROOT, "native-build")
sys.path.insert(0, NATIVE)

_cfg_path = os.path.join(ROOT, "scripts", "standalone", "config.py")
_cfg_spec = importlib.util.spec_from_file_location("_amai_build_config", _cfg_path)
Expand Down Expand Up @@ -80,11 +83,11 @@ if not USE_PGSERVER:

a = Analysis(
[os.path.join(ROOT, cfg["launcher"])],
pathex=[ROOT],
pathex=[ROOT, NATIVE],
binaries=binaries,
datas=datas,
hiddenimports=hiddenimports,
hookspath=[os.path.join(ROOT, "macos/hooks")],
hookspath=[os.path.join(NATIVE, "macos", "hooks")],
hooksconfig={},
runtime_hooks=[],
excludes=excludes,
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ The following table details the most important paths in the repository, their pu
| :---- | :---- |
| app.py, app_*.py | The main entry point for the Flask web application. It handles the initialization of the Flask app, database connections, and the registration of API routes and blueprints. |
| tasks/ | **The Core Logic Hub.** This is where the most intensive computations occur. Each API or async task then point to an specific implementation in this directory|
| tasks/mediaserver.py | In this fail the generic method to interact with the mediaservers are specialized to call the specific one |
| tasks/mediaserver/ | In this package the generic methods to interact with the mediaservers (`__init__.py`) dispatch to the specific backend (`jellyfin.py`, `navidrome.py`, `emby.py`, `lyrion.py`) |
| tasks/ai/ | All AI / MCP code. `tasks/ai/api.py` is the provider dispatcher (called via `tasks.ai.api.call_with_tools()`), with backends in `tasks/ai/providers/{openai,gemini,mistral,ollama}.py`. Prompts are in `tasks/ai/prompts.py`. MCP tool schemas + dispatcher in `tasks/ai/tools.py`; tool bodies in `tasks/ai/tool_impl.py`. Two-stage planner (intent classifier + plan validation + execution) in `tasks/ai/planner.py`. Vocabulary normalization helpers in `tasks/ai/vocab.py`. |
| config.py | Contains the application's default, non-sensitive configuration parameters. These values serve as fallbacks and can be easily overridden by environment variables, providing a flexible and secure configuration system. |
| Authentication | Configured in `config.py` by `AUTH_ENABLED`, `AUDIOMUSE_USER`, `AUDIOMUSE_PASSWORD`, `API_TOKEN`, and `JWT_SECRET`. Enforcement happens in `app.py` and `app_helepr.py` functionality |
Expand Down
8 changes: 1 addition & 7 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
# RQ imports
from rq.job import Job, JobStatus
from rq.exceptions import NoSuchJobError
from tasks.setup_manager import SetupManager
from tasks.setup_manager import setup_manager

# Redis client
from redis import Redis
Expand All @@ -29,7 +29,6 @@
# The Flask instance lives in `flask_app` so RQ task modules can import it
# without creating a circular import back into this file.
from flask_app import app
setup_manager = SetupManager()

# Import helper functions
from app_helper import (
Expand Down Expand Up @@ -760,11 +759,6 @@ def listen_for_index_reloads():

# --- Import and Register Blueprints ---
# This is the original, working structure.
# Import tasks modules to ensure they're available to RQ workers
import tasks.clustering
import tasks.analysis


from app_chat import chat_bp
from app_clustering import clustering_bp
from app_analysis import analysis_bp
Expand Down
4 changes: 2 additions & 2 deletions app_clustering.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

def clustering_task_failure_handler(job, connection, type, value, tb):
"""A failure handler for the main clustering task, executed by the worker."""
from app import app
from flask_app import app
from app_helper import save_task_status, TASK_STATUS_FAILURE
with app.app_context():
task_id = getattr(job, 'id', None) or getattr(job, 'get_id', lambda: None)()
Expand Down Expand Up @@ -247,7 +247,7 @@ def start_clustering_endpoint():
"""
# Local imports to prevent circular dependency at startup
from app_helper import rq_queue_high, get_active_main_task
from app_helper import clean_up_previous_main_tasks, save_task_status, TASK_STATUS_PENDING, TASK_STATUS_FAILURE
from app_helper import clean_up_previous_main_tasks, save_task_status, TASK_STATUS_PENDING

# Check for any existing active main task to prevent parallel batch runs
active_task = get_active_main_task()
Expand Down
6 changes: 4 additions & 2 deletions app_cron.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from flask import Blueprint, render_template, jsonify, request
from psycopg2.extras import DictCursor
from app_helper import get_db, rq_queue_high, save_task_status, TASK_STATUS_PENDING
from database import get_db
from taskqueue import rq_queue_high
from app_helper import save_task_status, TASK_STATUS_PENDING
import uuid, time, logging
from config import (
TOP_N_MOODS,
Expand Down Expand Up @@ -292,7 +294,7 @@ def run_due_cron_jobs():
f"(playlist_id={playlist_id}, tracks={len(track_ids)}, job_id={job_id})"
)
except NotImplementedError:
# MPD or unsupported backend: keep the legacy date-suffixed behavior.
# Unsupported backend: keep the legacy date-suffixed behavior.
legacy_name = f"Sonic Fingerprint (Cron {time.strftime('%Y-%m-%d')})"
playlist_id = create_playlist_from_ids(legacy_name, track_ids)
logger.info(
Expand Down
3 changes: 2 additions & 1 deletion app_dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
from flask import Blueprint, render_template, jsonify
from psycopg2.extras import DictCursor

from app_helper import get_db, redis_conn
from database import get_db
from taskqueue import redis_conn
from tz_helper import LOCAL_TZ_FMT, UTC_NOW_SQL, to_local_str

logger = logging.getLogger(__name__)
Expand Down
Loading
Loading