feat: Replace pydub with ffmpeg for audio duration and metadata extraction in sound services

This commit is contained in:
JSC
2025-07-19 09:40:31 +02:00
parent 4cfc2ec0a2
commit b1f9667edd
4 changed files with 32 additions and 39 deletions

View File

@@ -7,7 +7,6 @@ import re
from pathlib import Path from pathlib import Path
import ffmpeg import ffmpeg
from pydub import AudioSegment
from app.database import db from app.database import db
from app.models.sound import Sound from app.models.sound import Sound
@@ -632,9 +631,17 @@ class SoundNormalizerService:
# Calculate file hash # Calculate file hash
file_hash = SoundNormalizerService._calculate_file_hash(file_path) file_hash = SoundNormalizerService._calculate_file_hash(file_path)
# Get duration using pydub # Get duration using ffmpeg
audio = AudioSegment.from_wav(file_path) probe = ffmpeg.probe(file_path)
duration = len(audio) # Duration in milliseconds audio_stream = next(
(s for s in probe['streams'] if s['codec_type'] == 'audio'),
None
)
if audio_stream and 'duration' in audio_stream:
duration = int(float(audio_stream['duration']) * 1000) # Convert to milliseconds
else:
duration = 0
return { return {
"duration": duration, "duration": duration,

View File

@@ -4,8 +4,7 @@ import hashlib
import logging import logging
from pathlib import Path from pathlib import Path
from pydub import AudioSegment import ffmpeg
from pydub.utils import mediainfo
from app.database import db from app.database import db
from app.models.sound import Sound from app.models.sound import Sound
@@ -281,32 +280,31 @@ class SoundScannerService:
@staticmethod @staticmethod
def _extract_audio_metadata(file_path: str) -> dict: def _extract_audio_metadata(file_path: str) -> dict:
"""Extract metadata from audio file using pydub and mediainfo.""" """Extract metadata from audio file using ffmpeg-python."""
try: try:
# Get file size # Get file size
file_size = Path(file_path).stat().st_size file_size = Path(file_path).stat().st_size
# Load audio file with pydub for basic info # Use ffmpeg to probe audio metadata
audio = AudioSegment.from_file(file_path) probe = ffmpeg.probe(file_path)
audio_stream = next(
(s for s in probe['streams'] if s['codec_type'] == 'audio'),
None
)
# Extract basic metadata from AudioSegment if not audio_stream:
duration = len(audio) raise ValueError("No audio stream found in file")
channels = audio.channels
sample_rate = audio.frame_rate
# Use mediainfo for more accurate bitrate information # Extract metadata from ffmpeg probe
bitrate = None duration = int(float(audio_stream.get('duration', 0)) * 1000) # Convert to milliseconds
try: channels = int(audio_stream.get('channels', 0))
info = mediainfo(file_path) sample_rate = int(audio_stream.get('sample_rate', 0))
if info and "bit_rate" in info: bitrate = int(audio_stream.get('bit_rate', 0)) if audio_stream.get('bit_rate') else None
bitrate = int(info["bit_rate"])
elif info and "bitrate" in info: # Fallback bitrate calculation if not available
bitrate = int(info["bitrate"]) if not bitrate and duration > 0:
except (ValueError, KeyError, TypeError):
# Fallback to calculated bitrate if mediainfo fails
if duration > 0:
file_size_bits = file_size * 8 file_size_bits = file_size * 8
bitrate = int(file_size_bits / duration / 1000) bitrate = int(file_size_bits / (duration / 1000))
return { return {
"duration": duration, "duration": duration,

View File

@@ -15,7 +15,6 @@ dependencies = [
"flask-migrate==4.1.0", "flask-migrate==4.1.0",
"flask-socketio==5.5.1", "flask-socketio==5.5.1",
"flask-sqlalchemy==3.1.1", "flask-sqlalchemy==3.1.1",
"pydub==0.25.1",
"python-dotenv==1.1.1", "python-dotenv==1.1.1",
"python-vlc>=3.0.21203", "python-vlc>=3.0.21203",
"requests==2.32.4", "requests==2.32.4",

11
uv.lock generated
View File

@@ -505,15 +505,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 }, { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 },
] ]
[[package]]
name = "pydub"
version = "0.25.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/fe/9a/e6bca0eed82db26562c73b5076539a4a08d3cffd19c3cc5913a3e61145fd/pydub-0.25.1.tar.gz", hash = "sha256:980a33ce9949cab2a569606b65674d748ecbca4f0796887fd6f46173a7b0d30f", size = 38326 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a6/53/d78dc063216e62fc55f6b2eebb447f6a4b0a59f55c8406376f76bf959b08/pydub-0.25.1-py2.py3-none-any.whl", hash = "sha256:65617e33033874b59d87db603aa1ed450633288aefead953b30bded59cb599a6", size = 32327 },
]
[[package]] [[package]]
name = "pygments" name = "pygments"
version = "2.19.2" version = "2.19.2"
@@ -645,7 +636,6 @@ dependencies = [
{ name = "flask-migrate" }, { name = "flask-migrate" },
{ name = "flask-socketio" }, { name = "flask-socketio" },
{ name = "flask-sqlalchemy" }, { name = "flask-sqlalchemy" },
{ name = "pydub" },
{ name = "python-dotenv" }, { name = "python-dotenv" },
{ name = "python-vlc" }, { name = "python-vlc" },
{ name = "requests" }, { name = "requests" },
@@ -671,7 +661,6 @@ requires-dist = [
{ name = "flask-migrate", specifier = "==4.1.0" }, { name = "flask-migrate", specifier = "==4.1.0" },
{ name = "flask-socketio", specifier = "==5.5.1" }, { name = "flask-socketio", specifier = "==5.5.1" },
{ name = "flask-sqlalchemy", specifier = "==3.1.1" }, { name = "flask-sqlalchemy", specifier = "==3.1.1" },
{ name = "pydub", specifier = "==0.25.1" },
{ name = "python-dotenv", specifier = "==1.1.1" }, { name = "python-dotenv", specifier = "==1.1.1" },
{ name = "python-vlc", specifier = ">=3.0.21203" }, { name = "python-vlc", specifier = ">=3.0.21203" },
{ name = "requests", specifier = "==2.32.4" }, { name = "requests", specifier = "==2.32.4" },