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
import ffmpeg
from pydub import AudioSegment
from app.database import db
from app.models.sound import Sound
@@ -632,9 +631,17 @@ class SoundNormalizerService:
# Calculate file hash
file_hash = SoundNormalizerService._calculate_file_hash(file_path)
# Get duration using pydub
audio = AudioSegment.from_wav(file_path)
duration = len(audio) # Duration in milliseconds
# Get duration using ffmpeg
probe = ffmpeg.probe(file_path)
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 {
"duration": duration,

View File

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

View File

@@ -15,7 +15,6 @@ dependencies = [
"flask-migrate==4.1.0",
"flask-socketio==5.5.1",
"flask-sqlalchemy==3.1.1",
"pydub==0.25.1",
"python-dotenv==1.1.1",
"python-vlc>=3.0.21203",
"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 },
]
[[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]]
name = "pygments"
version = "2.19.2"
@@ -645,7 +636,6 @@ dependencies = [
{ name = "flask-migrate" },
{ name = "flask-socketio" },
{ name = "flask-sqlalchemy" },
{ name = "pydub" },
{ name = "python-dotenv" },
{ name = "python-vlc" },
{ name = "requests" },
@@ -671,7 +661,6 @@ requires-dist = [
{ name = "flask-migrate", specifier = "==4.1.0" },
{ name = "flask-socketio", specifier = "==5.5.1" },
{ name = "flask-sqlalchemy", specifier = "==3.1.1" },
{ name = "pydub", specifier = "==0.25.1" },
{ name = "python-dotenv", specifier = "==1.1.1" },
{ name = "python-vlc", specifier = ">=3.0.21203" },
{ name = "requests", specifier = "==2.32.4" },