-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathgraphlink_audio.py
More file actions
129 lines (104 loc) · 3.57 KB
/
Copy pathgraphlink_audio.py
File metadata and controls
129 lines (104 loc) · 3.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import mimetypes
import wave
from pathlib import Path
try:
from mutagen import File as MutagenFile
MUTAGEN_AVAILABLE = True
except ImportError:
MutagenFile = None
MUTAGEN_AVAILABLE = False
SUPPORTED_AUDIO_EXTENSIONS = {
".aac",
".flac",
".m4a",
".mp3",
".mp4",
".mpeg",
".mpga",
".oga",
".ogg",
".opus",
".wav",
".webm",
}
MAX_AUDIO_DURATION_SECONDS = 4 * 60 * 60
_AUDIO_MIME_OVERRIDES = {
".aac": "audio/aac",
".flac": "audio/flac",
".m4a": "audio/mp4",
".mp3": "audio/mpeg",
".mp4": "audio/mp4",
".mpeg": "audio/mpeg",
".mpga": "audio/mpeg",
".oga": "audio/ogg",
".ogg": "audio/ogg",
".opus": "audio/ogg",
".wav": "audio/wav",
".webm": "audio/webm",
}
class AudioValidationError(ValueError):
pass
def is_supported_audio_file(file_path: str) -> bool:
return Path(file_path).suffix.lower() in SUPPORTED_AUDIO_EXTENSIONS
def guess_audio_mime_type(file_path: str) -> str:
extension = Path(file_path).suffix.lower()
if extension in _AUDIO_MIME_OVERRIDES:
return _AUDIO_MIME_OVERRIDES[extension]
guessed, _ = mimetypes.guess_type(file_path)
return guessed or "application/octet-stream"
def format_duration(seconds: float | int | None) -> str:
if seconds is None:
return "Unknown"
total_seconds = max(0, int(round(float(seconds))))
hours, remainder = divmod(total_seconds, 3600)
minutes, secs = divmod(remainder, 60)
return f"{hours:d}:{minutes:02d}:{secs:02d}" if hours else f"{minutes:d}:{secs:02d}"
def inspect_audio_file(file_path: str) -> dict:
path = Path(file_path)
if not path.is_file():
raise AudioValidationError(f"File not found: {file_path}")
if path.suffix.lower() not in SUPPORTED_AUDIO_EXTENSIONS:
raise AudioValidationError(
f"Unsupported audio format '{path.suffix.lower() or path.name}'."
)
duration_seconds = _probe_audio_duration_seconds(path)
if duration_seconds is None:
dependency_hint = ""
if not MUTAGEN_AVAILABLE:
dependency_hint = " Install dependencies with: pip install -r requirements.txt"
raise AudioValidationError(
f"Could not read the duration for '{path.name}'.{dependency_hint}"
)
if duration_seconds <= 0:
raise AudioValidationError(f"'{path.name}' does not contain a readable audio stream.")
if duration_seconds > MAX_AUDIO_DURATION_SECONDS:
raise AudioValidationError(
f"'{path.name}' is {format_duration(duration_seconds)} long. "
f"The maximum supported length is {format_duration(MAX_AUDIO_DURATION_SECONDS)}."
)
return {
"path": str(path.resolve()),
"mime_type": guess_audio_mime_type(str(path)),
"duration_seconds": float(duration_seconds),
"byte_size": path.stat().st_size,
}
def _probe_audio_duration_seconds(path: Path) -> float | None:
if MUTAGEN_AVAILABLE:
try:
parsed = MutagenFile(path)
info = getattr(parsed, "info", None)
length = float(getattr(info, "length", 0) or 0)
if length > 0:
return length
except Exception:
pass
if path.suffix.lower() == ".wav":
try:
with wave.open(str(path), "rb") as wav_file:
frame_rate = wav_file.getframerate()
frame_count = wav_file.getnframes()
if frame_rate > 0 and frame_count > 0:
return frame_count / float(frame_rate)
except Exception:
pass
return None