From ae72b1d67acb1264f8d27da5c6f350f8368362f1 Mon Sep 17 00:00:00 2001 From: mrwnslz Date: Sun, 4 Oct 2026 12:45:19 +0200 Subject: [PATCH] Stille-Suche: Folge erst in Python laden, ffmpeg liest die lokale Datei MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Die statische ffmpeg-Build stürzt im Container beim Auflösen von Hostnamen ab (SIGSEGV). Co-Authored-By: Claude Opus 5.5 --- podcasts/silence.py | 47 +++++++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/podcasts/silence.py b/podcasts/silence.py index 40774a7..2c5ab04 100644 --- a/podcasts/silence.py +++ b/podcasts/silence.py @@ -7,17 +7,22 @@ episode (PodcastEpisode.silences); the browser then jumps over them while it plays (podcasts-web.js). """ import logging +import os import re import subprocess +import tempfile import threading +import requests + from django.db import close_old_connections log = logging.getLogger(__name__) NOISE = '-35dB' # quieter than this counts as silence MIN_SILENCE = 0.4 # seconds; shorter pauses stay (they belong to speech) -TIMEOUT = 900 # seconds for one episode (download and decode) +TIMEOUT = 900 # seconds for decoding one episode +MAX_BYTES = 800 * 1024 * 1024 # a larger file isn't an episode to analyse MAX_RUNNING = 2 # episodes analysed at the same time _START = re.compile(r'silence_start: (-?[\d.]+)') @@ -47,18 +52,36 @@ def parse(stderr): def analyze(url): - """The pauses of the audio at `url` (ffmpeg reads it straight from there).""" + """The pauses of the audio at `url`. Downloaded here first: the static ffmpeg + build can't look up host names (it crashes doing so), it reads a local file.""" import imageio_ffmpeg - proc = subprocess.run( - [imageio_ffmpeg.get_ffmpeg_exe(), '-nostdin', '-hide_banner', '-nostats', - '-user_agent', 'diora podcast silence', '-vn', '-sn', '-dn', '-i', url, - '-af', f'silencedetect=noise={NOISE}:d={MIN_SILENCE}', '-f', 'null', '-'], - capture_output=True, text=True, timeout=TIMEOUT, - ) - if proc.returncode != 0: - tail = (proc.stderr or '').strip().splitlines()[-1:] or ['ffmpeg failed'] - raise RuntimeError(tail[0][:200]) - return parse(proc.stderr) + fd, path = tempfile.mkstemp(suffix='.audio') + try: + with os.fdopen(fd, 'wb') as out, requests.get( + url, stream=True, timeout=(10, 60), headers={'User-Agent': 'diora podcast silence'}) as r: + if r.status_code >= 400: + raise RuntimeError(f'HTTP {r.status_code}') + size = 0 + for chunk in r.iter_content(1 << 20): + size += len(chunk) + if size > MAX_BYTES: + raise RuntimeError('file too large') + out.write(chunk) + proc = subprocess.run( + [imageio_ffmpeg.get_ffmpeg_exe(), '-nostdin', '-hide_banner', '-nostats', + '-vn', '-sn', '-dn', '-i', path, + '-af', f'silencedetect=noise={NOISE}:d={MIN_SILENCE}', '-f', 'null', '-'], + capture_output=True, text=True, timeout=TIMEOUT, + ) + if proc.returncode != 0: + tail = (proc.stderr or '').strip().splitlines()[-1:] or [f'ffmpeg failed ({proc.returncode})'] + raise RuntimeError(tail[0][:200]) + return parse(proc.stderr) + finally: + try: + os.remove(path) + except OSError: + pass def ensure(episode_id):