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
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ jobs:
run: uv sync --frozen --all-extras

- name: Run mypy
run: uv run mypy src/ || true
continue-on-error: true
run: uv run mypy src/

# Validate packaging scripts (syntax check only - real validation happens during build)
packaging:
Expand Down
33 changes: 18 additions & 15 deletions packaging/tunecover.spec
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ Build command:
Output:
dist/TuneCover/ (folder with executable and dependencies)

Note: On Windows, you need chromaprint.dll in the system PATH or bundled.
Note: On Windows, fpcalc.exe must be placed in the packaging/ directory
(or project root) before building. CI downloads it automatically.
Download from: https://acoustid.org/chromaprint
"""

Expand Down Expand Up @@ -59,25 +60,25 @@ hiddenimports = [
'audioread.maddec',
]

# Platform-specific chromaprint binaries
# Users must ensure chromaprint is installed or bundled
# Bundle fpcalc binary on Windows so audio fingerprinting works out-of-the-box.
# CI downloads fpcalc.exe into packaging/ before running PyInstaller.
binaries = []

# On Windows, look for chromaprint.dll in common locations
if sys.platform == 'win32':
chromaprint_paths = [
os.path.join(os.environ.get('PROGRAMFILES', ''), 'Chromaprint', 'chromaprint.dll'),
os.path.join(os.environ.get('LOCALAPPDATA', ''), 'Chromaprint', 'chromaprint.dll'),
'chromaprint.dll', # Current directory or PATH
fpcalc_search_paths = [
os.path.join(SPECPATH, 'fpcalc.exe'), # CI places it here
os.path.join(SPECPATH, '..', 'fpcalc.exe'), # Project root
'fpcalc.exe', # Current directory
]
chromaprint_found = False
for path in chromaprint_paths:
fpcalc_found = False
for path in fpcalc_search_paths:
if os.path.exists(path):
binaries.append((path, '.'))
chromaprint_found = True
fpcalc_found = True
print(f"INFO: Bundling fpcalc.exe from {os.path.abspath(path)}")
break
if not chromaprint_found:
print("WARNING: chromaprint.dll not found. Audio fingerprinting will not work.")
if not fpcalc_found:
print("WARNING: fpcalc.exe not found. Audio fingerprinting will not work.")
print(" Download from: https://acoustid.org/chromaprint")

a = Analysis(
Expand Down Expand Up @@ -182,7 +183,8 @@ exe = EXE(
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
# UPX disabled: compressing Qt DLLs causes runtime crashes on Windows.
upx=False,
console=False, # GUI application, no console window
disable_windowed_traceback=False,
argv_emulation=False,
Expand All @@ -199,7 +201,8 @@ coll = COLLECT(
a.zipfiles,
a.datas,
strip=False,
upx=True,
# UPX disabled: compressing Qt DLLs causes runtime crashes on Windows.
upx=False,
upx_exclude=[],
name='TuneCover',
)
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "tunecover"
version = "0.1.0"
version = "0.1.1"
description = "A GUI application for fetching and embedding album artwork in music libraries"
readme = "README.md"
requires-python = ">=3.12"
Expand Down
28 changes: 26 additions & 2 deletions src/core/fingerprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import platform
import shutil
import subprocess
import sys
import threading
from difflib import SequenceMatcher
from pathlib import Path
Expand Down Expand Up @@ -57,7 +58,8 @@ def _find_fpcalc_binary() -> str | None:
1. Custom path set via set_fpcalc_path()
2. FPCALC_COMMAND environment variable
3. System PATH (via shutil.which)
4. Common installation paths for each platform
4. PyInstaller bundle (frozen builds only)
5. Common installation paths for each platform

Returns:
Path to fpcalc binary if found, None otherwise
Expand Down Expand Up @@ -86,7 +88,29 @@ def _find_fpcalc_binary() -> str | None:
_detected_fpcalc_path = which_path
return which_path

# 4. Check platform-specific common locations
# 4. Check PyInstaller bundle (frozen builds only)
# In one-folder mode, fpcalc sits next to the executable.
# In one-file mode, it is extracted to sys._MEIPASS at runtime.
if getattr(sys, "frozen", False):
fpcalc_name = "fpcalc.exe" if platform.system() == "Windows" else "fpcalc"
# One-folder mode: next to the executable
candidate = Path(sys.executable).parent / fpcalc_name
if candidate.is_file():
path_str = str(candidate)
logger.info(f"Found bundled fpcalc (one-folder): {path_str}")
_detected_fpcalc_path = path_str
return path_str
# One-file mode: extracted to temporary _MEIPASS directory
meipass = getattr(sys, "_MEIPASS", None)
if meipass:
candidate = Path(meipass) / fpcalc_name
if candidate.is_file():
path_str = str(candidate)
logger.info(f"Found bundled fpcalc (one-file): {path_str}")
_detected_fpcalc_path = path_str
return path_str

# 5. Check platform-specific common locations
system = platform.system()
common_paths: list[str] = []

Expand Down
Loading
Loading