feat: Replace pydub with ffmpeg for audio duration and metadata extraction in sound services
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
11
uv.lock
generated
@@ -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" },
|
||||||
|
|||||||
Reference in New Issue
Block a user