blitztext-app-linux/linux/blitztext/sound.py
mARTin-B78 7a9dc5a2f7 feat: MP3/OGG/FLAC sound support + browse dialog with auto-preview (v2.03.16)
Sound fields accept WAV/MP3/OGG/FLAC/M4A/AAC/AIFF/Opus. Browse dialog
auto-plays each file on selection so you can preview before confirming.
sound.py falls back to ffplay/gst-play-1.0 for formats not supported
by pw-play/paplay.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 00:37:07 +02:00

57 lines
1.9 KiB
Python

"""Play short audio cues (a user WAV, or a built-in system sound) without blocking."""
from __future__ import annotations
import os
import shutil
import subprocess
_FREEDESKTOP = "/usr/share/sounds/freedesktop/stereo/{name}.oga"
# (player, extra_args, wav_only)
# Ordered: native PipeWire/PulseAudio first, then ffplay/gst as universal fallback.
_PLAYERS: list[tuple[str, list[str], bool]] = [
("pw-play", [], False),
("paplay", [], False),
("aplay", [], True), # WAV only
("ffplay", ["-nodisp", "-autoexit", "-loglevel", "quiet"], False),
("gst-play-1.0", [], False),
]
_NATIVE_EXTS = {".wav", ".oga", ".ogg", ".flac"}
def play(path: str = "", *, fallback: str | None = None) -> "subprocess.Popen | None":
"""Play `path` (WAV/MP3/OGG/FLAC/…); fallback to a freedesktop system sound.
Returns the Popen object so callers can terminate a preview, or None.
"""
target = ""
if path:
expanded = os.path.expanduser(path)
if os.path.exists(expanded):
target = expanded
if not target and fallback:
fd = _FREEDESKTOP.format(name=fallback)
if os.path.exists(fd):
target = fd
if not target:
return None
ext = os.path.splitext(target)[1].lower()
for player, extra, wav_only in _PLAYERS:
if not shutil.which(player):
continue
if wav_only and ext != ".wav":
continue
if player == "paplay" and ext not in _NATIVE_EXTS:
continue
try:
proc = subprocess.Popen(
[player] + extra + [target],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
)
return proc
except OSError:
continue
return None