Repository navigation
Expand file tree
/
Copy path_files.py
More file actions
103 lines (82 loc) · 3.01 KB
/
Copy path_files.py
File metadata and controls
103 lines (82 loc) · 3.01 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
"""Helpers for normalizing the many ways callers pass us audio data.
The public ``client.jobs.create(audio=...)`` accepts:
* ``str`` or ``pathlib.Path`` — read from disk
* ``bytes`` / ``bytearray`` — already in memory
* file-like with ``.read()`` — read once into memory
Everything is reduced to a ``(filename, content_type, raw_bytes)`` tuple.
"""
from __future__ import annotations
import mimetypes
import os
from pathlib import Path
from typing import IO, Any
AudioInput = str | Path | bytes | bytearray | IO[bytes]
_DEFAULT_CONTENT_TYPE = "audio/mpeg"
_EXT_TO_MIME = {
".mp3": "audio/mpeg",
".wav": "audio/wav",
".flac": "audio/flac",
".m4a": "audio/mp4",
".aac": "audio/aac",
".ogg": "audio/ogg",
".opus": "audio/opus",
".webm": "audio/webm",
}
def normalize_audio_input(
audio: AudioInput,
*,
file_name: str | None = None,
content_type: str | None = None,
) -> tuple[str, str, bytes]:
"""Return ``(filename, content_type, raw_bytes)`` for any supported input.
Raises :class:`TypeError` for unsupported argument types and
:class:`FileNotFoundError` for missing paths.
"""
if isinstance(audio, (bytes, bytearray)):
if not file_name:
raise ValueError(
"file_name is required when audio is raw bytes; the server "
"needs a filename to derive the upload key."
)
return file_name, content_type or _guess_mime(file_name), bytes(audio)
if isinstance(audio, (str, Path)):
path = Path(audio)
if not path.exists():
raise FileNotFoundError(f"Audio file not found: {path}")
return (
file_name or path.name,
content_type or _guess_mime(path.name),
path.read_bytes(),
)
read = getattr(audio, "read", None)
if callable(read):
data = read()
if isinstance(data, str):
data = data.encode("utf-8")
if not isinstance(data, (bytes, bytearray)):
raise TypeError(
f"file-like object's .read() must return bytes, got {type(data).__name__}"
)
derived_name = file_name or _name_from_filelike(audio)
if not derived_name:
raise ValueError(
"file_name is required when audio is a file-like object without a .name attribute."
)
return (
derived_name,
content_type or _guess_mime(derived_name),
bytes(data),
)
raise TypeError(
f"Unsupported audio input type: {type(audio).__name__}. "
"Pass a Path, str, bytes, or file-like object."
)
def _guess_mime(name: str) -> str:
suffix = os.path.splitext(name)[1].lower()
return _EXT_TO_MIME.get(suffix) or mimetypes.guess_type(name)[0] or _DEFAULT_CONTENT_TYPE
def _name_from_filelike(obj: Any) -> str | None:
name = getattr(obj, "name", None)
if isinstance(name, str) and name:
return os.path.basename(name)
return None
__all__ = ["AudioInput", "normalize_audio_input"]