deepctl-shared-utils 0.1.10__tar.gz → 0.1.11__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- deepctl_shared_utils-0.1.11/PKG-INFO +60 -0
- deepctl_shared_utils-0.1.11/README.md +39 -0
- {deepctl_shared_utils-0.1.10 → deepctl_shared_utils-0.1.11}/pyproject.toml +2 -2
- deepctl_shared_utils-0.1.11/src/deepctl_shared_utils/__init__.py +31 -0
- deepctl_shared_utils-0.1.11/src/deepctl_shared_utils/ffprobe.py +225 -0
- deepctl_shared_utils-0.1.11/src/deepctl_shared_utils/ffprobe_models.py +38 -0
- {deepctl_shared_utils-0.1.10 → deepctl_shared_utils-0.1.11}/src/deepctl_shared_utils/validation.py +20 -25
- deepctl_shared_utils-0.1.11/src/deepctl_shared_utils.egg-info/PKG-INFO +60 -0
- {deepctl_shared_utils-0.1.10 → deepctl_shared_utils-0.1.11}/src/deepctl_shared_utils.egg-info/SOURCES.txt +2 -0
- {deepctl_shared_utils-0.1.10 → deepctl_shared_utils-0.1.11}/src/deepctl_shared_utils.egg-info/requires.txt +1 -0
- deepctl_shared_utils-0.1.10/PKG-INFO +0 -23
- deepctl_shared_utils-0.1.10/README.md +0 -3
- deepctl_shared_utils-0.1.10/src/deepctl_shared_utils/__init__.py +0 -13
- deepctl_shared_utils-0.1.10/src/deepctl_shared_utils.egg-info/PKG-INFO +0 -23
- {deepctl_shared_utils-0.1.10 → deepctl_shared_utils-0.1.11}/setup.cfg +0 -0
- {deepctl_shared_utils-0.1.10 → deepctl_shared_utils-0.1.11}/src/deepctl_shared_utils/models.py +0 -0
- {deepctl_shared_utils-0.1.10 → deepctl_shared_utils-0.1.11}/src/deepctl_shared_utils/py.typed +0 -0
- {deepctl_shared_utils-0.1.10 → deepctl_shared_utils-0.1.11}/src/deepctl_shared_utils.egg-info/dependency_links.txt +0 -0
- {deepctl_shared_utils-0.1.10 → deepctl_shared_utils-0.1.11}/src/deepctl_shared_utils.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: deepctl-shared-utils
|
|
3
|
+
Version: 0.1.11
|
|
4
|
+
Summary: Shared utilities for deepctl
|
|
5
|
+
Author-email: Deepgram <devrel@deepgram.com>
|
|
6
|
+
Maintainer-email: Deepgram <devrel@deepgram.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Keywords: deepgram,utilities,validation,helpers
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Requires-Dist: pydantic>=2.0.0
|
|
18
|
+
Requires-Dist: click>=8.0.0
|
|
19
|
+
Requires-Dist: httpx>=0.24.0
|
|
20
|
+
Requires-Dist: rich>=13.0.0
|
|
21
|
+
|
|
22
|
+
# deepctl-shared-utils
|
|
23
|
+
|
|
24
|
+
> Part of [deepctl](https://github.com/deepgram/cli) — Official Deepgram CLI
|
|
25
|
+
|
|
26
|
+
Shared utilities for deepctl
|
|
27
|
+
|
|
28
|
+
This package provides internal APIs for deepctl and its command packages. It is not intended for direct use.
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
This package is included with deepctl and does not need to be installed separately.
|
|
33
|
+
|
|
34
|
+
### Install deepctl
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# Install with pip
|
|
38
|
+
pip install deepctl
|
|
39
|
+
|
|
40
|
+
# Or install with uv
|
|
41
|
+
uv tool install deepctl
|
|
42
|
+
|
|
43
|
+
# Or install with pipx
|
|
44
|
+
pipx install deepctl
|
|
45
|
+
|
|
46
|
+
# Or run without installing
|
|
47
|
+
uvx deepctl --help
|
|
48
|
+
pipx run deepctl --help
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Dependencies
|
|
52
|
+
|
|
53
|
+
- `pydantic>=2.0.0`
|
|
54
|
+
- `click>=8.0.0`
|
|
55
|
+
- `httpx>=0.24.0`
|
|
56
|
+
- `rich>=13.0.0`
|
|
57
|
+
|
|
58
|
+
## License
|
|
59
|
+
|
|
60
|
+
MIT — see [LICENSE](../../LICENSE)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# deepctl-shared-utils
|
|
2
|
+
|
|
3
|
+
> Part of [deepctl](https://github.com/deepgram/cli) — Official Deepgram CLI
|
|
4
|
+
|
|
5
|
+
Shared utilities for deepctl
|
|
6
|
+
|
|
7
|
+
This package provides internal APIs for deepctl and its command packages. It is not intended for direct use.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
This package is included with deepctl and does not need to be installed separately.
|
|
12
|
+
|
|
13
|
+
### Install deepctl
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Install with pip
|
|
17
|
+
pip install deepctl
|
|
18
|
+
|
|
19
|
+
# Or install with uv
|
|
20
|
+
uv tool install deepctl
|
|
21
|
+
|
|
22
|
+
# Or install with pipx
|
|
23
|
+
pipx install deepctl
|
|
24
|
+
|
|
25
|
+
# Or run without installing
|
|
26
|
+
uvx deepctl --help
|
|
27
|
+
pipx run deepctl --help
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Dependencies
|
|
31
|
+
|
|
32
|
+
- `pydantic>=2.0.0`
|
|
33
|
+
- `click>=8.0.0`
|
|
34
|
+
- `httpx>=0.24.0`
|
|
35
|
+
- `rich>=13.0.0`
|
|
36
|
+
|
|
37
|
+
## License
|
|
38
|
+
|
|
39
|
+
MIT — see [LICENSE](../../LICENSE)
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "deepctl-shared-utils"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.11" # x-release-please-version
|
|
8
8
|
description = "Shared utilities for deepctl"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -20,7 +20,7 @@ classifiers = [
|
|
|
20
20
|
]
|
|
21
21
|
keywords = ["deepgram", "utilities", "validation", "helpers"]
|
|
22
22
|
requires-python = ">=3.10"
|
|
23
|
-
dependencies = ["pydantic>=2.0.0", "click>=8.0.0", "httpx>=0.24.0"]
|
|
23
|
+
dependencies = ["pydantic>=2.0.0", "click>=8.0.0", "httpx>=0.24.0", "rich>=13.0.0"]
|
|
24
24
|
|
|
25
25
|
[tool.setuptools]
|
|
26
26
|
package-dir = { "" = "src" }
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Shared utilities for deepctl."""
|
|
2
|
+
|
|
3
|
+
from .ffprobe import (
|
|
4
|
+
check_ffprobe,
|
|
5
|
+
get_ffprobe_path,
|
|
6
|
+
print_ffprobe_install_instructions,
|
|
7
|
+
probe_buffer,
|
|
8
|
+
probe_file,
|
|
9
|
+
require_ffprobe,
|
|
10
|
+
)
|
|
11
|
+
from .ffprobe_models import AudioFormatInfo, AudioProbeResult, AudioStreamInfo
|
|
12
|
+
from .models import FileInfo
|
|
13
|
+
from .validation import validate_audio_file, validate_date_format, validate_url
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"AudioFormatInfo",
|
|
17
|
+
"AudioProbeResult",
|
|
18
|
+
"AudioStreamInfo",
|
|
19
|
+
"FileInfo",
|
|
20
|
+
"check_ffprobe",
|
|
21
|
+
"get_ffprobe_path",
|
|
22
|
+
"print_ffprobe_install_instructions",
|
|
23
|
+
"probe_buffer",
|
|
24
|
+
"probe_file",
|
|
25
|
+
"require_ffprobe",
|
|
26
|
+
"validate_audio_file",
|
|
27
|
+
"validate_date_format",
|
|
28
|
+
"validate_url",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
__version__ = "0.1.11" # x-release-please-version
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"""Shared ffprobe wrapper for audio file analysis."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import platform
|
|
8
|
+
import shutil
|
|
9
|
+
import subprocess
|
|
10
|
+
import tempfile
|
|
11
|
+
from typing import TYPE_CHECKING
|
|
12
|
+
|
|
13
|
+
from rich.console import Console
|
|
14
|
+
from rich.panel import Panel
|
|
15
|
+
|
|
16
|
+
from .ffprobe_models import AudioFormatInfo, AudioProbeResult, AudioStreamInfo
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from deepctl_core import Config
|
|
20
|
+
|
|
21
|
+
console = Console()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_ffprobe_path(config: Config | None = None) -> str | None:
|
|
25
|
+
"""Get the ffprobe binary path.
|
|
26
|
+
|
|
27
|
+
Checks stored config path first, then falls back to auto-detection
|
|
28
|
+
via shutil.which().
|
|
29
|
+
"""
|
|
30
|
+
if config is not None:
|
|
31
|
+
stored_path = config.get("tools.ffprobe_path")
|
|
32
|
+
if (
|
|
33
|
+
stored_path
|
|
34
|
+
and os.path.isfile(stored_path)
|
|
35
|
+
and os.access(stored_path, os.X_OK)
|
|
36
|
+
):
|
|
37
|
+
return str(stored_path)
|
|
38
|
+
|
|
39
|
+
return shutil.which("ffprobe")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def check_ffprobe(config: Config | None = None) -> bool:
|
|
43
|
+
"""Check if ffprobe is available."""
|
|
44
|
+
return get_ffprobe_path(config) is not None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def print_ffprobe_install_instructions() -> None:
|
|
48
|
+
"""Print platform-specific ffprobe installation instructions."""
|
|
49
|
+
system = platform.system()
|
|
50
|
+
|
|
51
|
+
if system == "Darwin":
|
|
52
|
+
instructions = (
|
|
53
|
+
"[bold]Install FFmpeg (includes ffprobe):[/bold]\n\n"
|
|
54
|
+
" [cyan]brew install ffmpeg[/cyan]\n"
|
|
55
|
+
)
|
|
56
|
+
elif system == "Linux":
|
|
57
|
+
instructions = (
|
|
58
|
+
"[bold]Install FFmpeg (includes ffprobe):[/bold]\n\n"
|
|
59
|
+
" [cyan]sudo apt install ffmpeg[/cyan] # Debian/Ubuntu\n"
|
|
60
|
+
" [cyan]sudo dnf install ffmpeg[/cyan] # Fedora\n"
|
|
61
|
+
" [cyan]sudo pacman -S ffmpeg[/cyan] # Arch\n"
|
|
62
|
+
)
|
|
63
|
+
elif system == "Windows":
|
|
64
|
+
instructions = (
|
|
65
|
+
"[bold]Install FFmpeg (includes ffprobe):[/bold]\n\n"
|
|
66
|
+
" [cyan]winget install FFmpeg[/cyan]\n"
|
|
67
|
+
" [cyan]choco install ffmpeg[/cyan] # Chocolatey\n"
|
|
68
|
+
" [cyan]scoop install ffmpeg[/cyan] # Scoop\n"
|
|
69
|
+
)
|
|
70
|
+
else:
|
|
71
|
+
instructions = (
|
|
72
|
+
"[bold]Install FFmpeg (includes ffprobe):[/bold]\n\n"
|
|
73
|
+
" Download from [cyan]https://ffmpeg.org/download.html[/cyan]\n"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
instructions += (
|
|
77
|
+
"\nAfter installing, you can set a custom path with:\n"
|
|
78
|
+
" [dim]dg ffprobe --path /path/to/ffprobe[/dim]"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
console.print(
|
|
82
|
+
Panel(
|
|
83
|
+
instructions,
|
|
84
|
+
title="[red]ffprobe not found[/red]",
|
|
85
|
+
border_style="red",
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def require_ffprobe(config: Config | None = None) -> bool:
|
|
91
|
+
"""Check for ffprobe and print install instructions if missing.
|
|
92
|
+
|
|
93
|
+
Returns True if ffprobe is available, False otherwise.
|
|
94
|
+
"""
|
|
95
|
+
if check_ffprobe(config):
|
|
96
|
+
return True
|
|
97
|
+
|
|
98
|
+
print_ffprobe_install_instructions()
|
|
99
|
+
return False
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _parse_probe_data(data: dict[str, object]) -> AudioProbeResult:
|
|
103
|
+
"""Parse raw ffprobe JSON output into an AudioProbeResult."""
|
|
104
|
+
result = AudioProbeResult(raw_data=data)
|
|
105
|
+
|
|
106
|
+
fmt = data.get("format")
|
|
107
|
+
if isinstance(fmt, dict):
|
|
108
|
+
duration_str = fmt.get("duration")
|
|
109
|
+
size_str = fmt.get("size")
|
|
110
|
+
result.format = AudioFormatInfo(
|
|
111
|
+
format_name=fmt.get("format_name"),
|
|
112
|
+
format_long_name=fmt.get("format_long_name"),
|
|
113
|
+
duration=float(duration_str) if duration_str else None,
|
|
114
|
+
size=int(size_str) if size_str else None,
|
|
115
|
+
bit_rate=fmt.get("bit_rate"),
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
streams_data = data.get("streams")
|
|
119
|
+
if isinstance(streams_data, list):
|
|
120
|
+
for stream in streams_data:
|
|
121
|
+
if not isinstance(stream, dict):
|
|
122
|
+
continue
|
|
123
|
+
if stream.get("codec_type") != "audio":
|
|
124
|
+
continue
|
|
125
|
+
duration_str = stream.get("duration")
|
|
126
|
+
result.streams.append(
|
|
127
|
+
AudioStreamInfo(
|
|
128
|
+
codec_name=stream.get("codec_name"),
|
|
129
|
+
codec_long_name=stream.get("codec_long_name"),
|
|
130
|
+
sample_rate=stream.get("sample_rate"),
|
|
131
|
+
channels=stream.get("channels"),
|
|
132
|
+
channel_layout=stream.get("channel_layout"),
|
|
133
|
+
bit_rate=stream.get("bit_rate"),
|
|
134
|
+
bits_per_sample=stream.get("bits_per_sample"),
|
|
135
|
+
duration=float(duration_str) if duration_str else None,
|
|
136
|
+
)
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
return result
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def probe_file(file_path: str, config: Config | None = None) -> AudioProbeResult | None:
|
|
143
|
+
"""Probe an audio file using ffprobe.
|
|
144
|
+
|
|
145
|
+
Returns AudioProbeResult on success, None on failure.
|
|
146
|
+
"""
|
|
147
|
+
ffprobe_bin = get_ffprobe_path(config)
|
|
148
|
+
if not ffprobe_bin:
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
cmd = [
|
|
153
|
+
ffprobe_bin,
|
|
154
|
+
"-v",
|
|
155
|
+
"quiet",
|
|
156
|
+
"-print_format",
|
|
157
|
+
"json",
|
|
158
|
+
"-show_format",
|
|
159
|
+
"-show_streams",
|
|
160
|
+
file_path,
|
|
161
|
+
]
|
|
162
|
+
|
|
163
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
|
164
|
+
|
|
165
|
+
if result.returncode != 0:
|
|
166
|
+
return None
|
|
167
|
+
|
|
168
|
+
data = json.loads(result.stdout)
|
|
169
|
+
return _parse_probe_data(data)
|
|
170
|
+
|
|
171
|
+
except (subprocess.TimeoutExpired, json.JSONDecodeError, OSError):
|
|
172
|
+
return None
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def probe_buffer(
|
|
176
|
+
audio_bytes: bytes,
|
|
177
|
+
label: str = "audio",
|
|
178
|
+
config: Config | None = None,
|
|
179
|
+
) -> AudioProbeResult | None:
|
|
180
|
+
"""Probe audio bytes by writing to a temp file and running ffprobe.
|
|
181
|
+
|
|
182
|
+
Returns AudioProbeResult on success, None on failure.
|
|
183
|
+
"""
|
|
184
|
+
if not audio_bytes:
|
|
185
|
+
return None
|
|
186
|
+
|
|
187
|
+
ffprobe_bin = get_ffprobe_path(config)
|
|
188
|
+
if not ffprobe_bin:
|
|
189
|
+
return None
|
|
190
|
+
|
|
191
|
+
tmp_name = None
|
|
192
|
+
try:
|
|
193
|
+
with tempfile.NamedTemporaryFile(
|
|
194
|
+
suffix=".raw", delete=False, prefix=f"deepctl_{label}_"
|
|
195
|
+
) as tmp_file:
|
|
196
|
+
tmp_name = tmp_file.name
|
|
197
|
+
tmp_file.write(audio_bytes)
|
|
198
|
+
|
|
199
|
+
cmd = [
|
|
200
|
+
ffprobe_bin,
|
|
201
|
+
"-v",
|
|
202
|
+
"quiet",
|
|
203
|
+
"-print_format",
|
|
204
|
+
"json",
|
|
205
|
+
"-show_format",
|
|
206
|
+
"-show_streams",
|
|
207
|
+
tmp_name,
|
|
208
|
+
]
|
|
209
|
+
|
|
210
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
|
|
211
|
+
|
|
212
|
+
if result.returncode != 0:
|
|
213
|
+
return None
|
|
214
|
+
|
|
215
|
+
data = json.loads(result.stdout)
|
|
216
|
+
return _parse_probe_data(data)
|
|
217
|
+
|
|
218
|
+
except (subprocess.TimeoutExpired, json.JSONDecodeError, OSError):
|
|
219
|
+
return None
|
|
220
|
+
finally:
|
|
221
|
+
if tmp_name:
|
|
222
|
+
try:
|
|
223
|
+
os.unlink(tmp_name)
|
|
224
|
+
except OSError:
|
|
225
|
+
pass
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Pydantic models for ffprobe audio analysis results."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AudioStreamInfo(BaseModel):
|
|
11
|
+
"""Information about a single audio stream from ffprobe."""
|
|
12
|
+
|
|
13
|
+
codec_name: str | None = None
|
|
14
|
+
codec_long_name: str | None = None
|
|
15
|
+
sample_rate: str | None = None
|
|
16
|
+
channels: int | None = None
|
|
17
|
+
channel_layout: str | None = None
|
|
18
|
+
bit_rate: str | None = None
|
|
19
|
+
bits_per_sample: int | None = None
|
|
20
|
+
duration: float | None = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class AudioFormatInfo(BaseModel):
|
|
24
|
+
"""Format-level information from ffprobe."""
|
|
25
|
+
|
|
26
|
+
format_name: str | None = None
|
|
27
|
+
format_long_name: str | None = None
|
|
28
|
+
duration: float | None = None
|
|
29
|
+
size: int | None = None
|
|
30
|
+
bit_rate: str | None = None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class AudioProbeResult(BaseModel):
|
|
34
|
+
"""Complete ffprobe analysis result."""
|
|
35
|
+
|
|
36
|
+
format: AudioFormatInfo | None = None
|
|
37
|
+
streams: list[AudioStreamInfo] = Field(default_factory=list)
|
|
38
|
+
raw_data: dict[str, Any] | None = None
|
{deepctl_shared_utils-0.1.10 → deepctl_shared_utils-0.1.11}/src/deepctl_shared_utils/validation.py
RENAMED
|
@@ -88,17 +88,14 @@ def validate_audio_file(file_path: str | Path) -> bool:
|
|
|
88
88
|
|
|
89
89
|
# Check if file is readable
|
|
90
90
|
if not os.access(path, os.R_OK):
|
|
91
|
-
console.print(
|
|
92
|
-
f"[red]Error:[/red] File is not readable: {file_path}"
|
|
93
|
-
)
|
|
91
|
+
console.print(f"[red]Error:[/red] File is not readable: {file_path}")
|
|
94
92
|
return False
|
|
95
93
|
|
|
96
94
|
# Check file size (warn if very large)
|
|
97
95
|
size_mb = path.stat().st_size / (1024 * 1024)
|
|
98
96
|
if size_mb > 500: # 500 MB
|
|
99
97
|
console.print(
|
|
100
|
-
f"[yellow]Warning:[/yellow] Large file detected "
|
|
101
|
-
f"({size_mb:.1f} MB)"
|
|
98
|
+
f"[yellow]Warning:[/yellow] Large file detected ({size_mb:.1f} MB)"
|
|
102
99
|
)
|
|
103
100
|
console.print("[dim]Large files may take longer to process[/dim]")
|
|
104
101
|
|
|
@@ -173,10 +170,19 @@ def _check_url_accessibility(url: str) -> bool:
|
|
|
173
170
|
console.print("[dim]Checking URL accessibility...[/dim]")
|
|
174
171
|
|
|
175
172
|
with httpx.Client(timeout=10.0) as client:
|
|
176
|
-
#
|
|
173
|
+
# Try HEAD first; fall back to a ranged GET if the server
|
|
174
|
+
# doesn't support HEAD (some CDNs/redirect services return
|
|
175
|
+
# 404 or 405 for HEAD but serve GET fine).
|
|
177
176
|
response = client.head(url, follow_redirects=True)
|
|
178
177
|
|
|
179
|
-
if response.status_code
|
|
178
|
+
if response.status_code not in range(200, 400):
|
|
179
|
+
response = client.get(
|
|
180
|
+
url,
|
|
181
|
+
follow_redirects=True,
|
|
182
|
+
headers={"Range": "bytes=0-0"},
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
if response.status_code in (200, 206):
|
|
180
186
|
# Check content type if available
|
|
181
187
|
content_type = response.headers.get("content-type", "").lower()
|
|
182
188
|
if content_type and not any(
|
|
@@ -264,13 +270,10 @@ def validate_project_id(project_id: str) -> bool:
|
|
|
264
270
|
return False
|
|
265
271
|
|
|
266
272
|
# Check if it's a valid UUID format
|
|
267
|
-
uuid_pattern =
|
|
268
|
-
r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
|
|
269
|
-
)
|
|
273
|
+
uuid_pattern = r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
|
|
270
274
|
if not re.match(uuid_pattern, project_id, re.IGNORECASE):
|
|
271
275
|
console.print(
|
|
272
|
-
"[yellow]Warning:[/yellow] Project ID doesn't match expected "
|
|
273
|
-
"UUID format"
|
|
276
|
+
"[yellow]Warning:[/yellow] Project ID doesn't match expected UUID format"
|
|
274
277
|
)
|
|
275
278
|
return False
|
|
276
279
|
|
|
@@ -336,8 +339,7 @@ def validate_language_code(language: str) -> bool:
|
|
|
336
339
|
|
|
337
340
|
if language not in supported_languages:
|
|
338
341
|
console.print(
|
|
339
|
-
f"[yellow]Warning:[/yellow] Language '{language}' may not be "
|
|
340
|
-
f"supported"
|
|
342
|
+
f"[yellow]Warning:[/yellow] Language '{language}' may not be supported"
|
|
341
343
|
)
|
|
342
344
|
console.print(
|
|
343
345
|
"[dim]Common supported languages: en-US, es-ES, fr-FR, de-DE, "
|
|
@@ -387,9 +389,7 @@ def validate_model_name(model: str) -> bool:
|
|
|
387
389
|
}
|
|
388
390
|
|
|
389
391
|
if model not in supported_models:
|
|
390
|
-
console.print(
|
|
391
|
-
f"[yellow]Warning:[/yellow] Model '{model}' may not be supported"
|
|
392
|
-
)
|
|
392
|
+
console.print(f"[yellow]Warning:[/yellow] Model '{model}' may not be supported")
|
|
393
393
|
console.print(
|
|
394
394
|
"[dim]Common supported models: nova-2, nova, enhanced, base, "
|
|
395
395
|
"meeting, phonecall[/dim]"
|
|
@@ -446,9 +446,7 @@ def validate_file_permissions(file_path: str | Path) -> bool:
|
|
|
446
446
|
|
|
447
447
|
# Check read permission
|
|
448
448
|
if not os.access(path, os.R_OK):
|
|
449
|
-
console.print(
|
|
450
|
-
f"[red]Error:[/red] No read permission for file: {file_path}"
|
|
451
|
-
)
|
|
449
|
+
console.print(f"[red]Error:[/red] No read permission for file: {file_path}")
|
|
452
450
|
return False
|
|
453
451
|
|
|
454
452
|
# Check if file is not empty
|
|
@@ -501,12 +499,9 @@ def validate_output_format(format_type: str) -> bool:
|
|
|
501
499
|
supported_formats = {"json", "yaml", "table", "csv"}
|
|
502
500
|
|
|
503
501
|
if format_type not in supported_formats:
|
|
502
|
+
console.print(f"[red]Error:[/red] Unsupported output format: {format_type}")
|
|
504
503
|
console.print(
|
|
505
|
-
f"[
|
|
506
|
-
)
|
|
507
|
-
console.print(
|
|
508
|
-
f"[dim]Supported formats: "
|
|
509
|
-
f"{', '.join(sorted(supported_formats))}[/dim]"
|
|
504
|
+
f"[dim]Supported formats: {', '.join(sorted(supported_formats))}[/dim]"
|
|
510
505
|
)
|
|
511
506
|
return False
|
|
512
507
|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: deepctl-shared-utils
|
|
3
|
+
Version: 0.1.11
|
|
4
|
+
Summary: Shared utilities for deepctl
|
|
5
|
+
Author-email: Deepgram <devrel@deepgram.com>
|
|
6
|
+
Maintainer-email: Deepgram <devrel@deepgram.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Keywords: deepgram,utilities,validation,helpers
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Requires-Dist: pydantic>=2.0.0
|
|
18
|
+
Requires-Dist: click>=8.0.0
|
|
19
|
+
Requires-Dist: httpx>=0.24.0
|
|
20
|
+
Requires-Dist: rich>=13.0.0
|
|
21
|
+
|
|
22
|
+
# deepctl-shared-utils
|
|
23
|
+
|
|
24
|
+
> Part of [deepctl](https://github.com/deepgram/cli) — Official Deepgram CLI
|
|
25
|
+
|
|
26
|
+
Shared utilities for deepctl
|
|
27
|
+
|
|
28
|
+
This package provides internal APIs for deepctl and its command packages. It is not intended for direct use.
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
This package is included with deepctl and does not need to be installed separately.
|
|
33
|
+
|
|
34
|
+
### Install deepctl
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# Install with pip
|
|
38
|
+
pip install deepctl
|
|
39
|
+
|
|
40
|
+
# Or install with uv
|
|
41
|
+
uv tool install deepctl
|
|
42
|
+
|
|
43
|
+
# Or install with pipx
|
|
44
|
+
pipx install deepctl
|
|
45
|
+
|
|
46
|
+
# Or run without installing
|
|
47
|
+
uvx deepctl --help
|
|
48
|
+
pipx run deepctl --help
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Dependencies
|
|
52
|
+
|
|
53
|
+
- `pydantic>=2.0.0`
|
|
54
|
+
- `click>=8.0.0`
|
|
55
|
+
- `httpx>=0.24.0`
|
|
56
|
+
- `rich>=13.0.0`
|
|
57
|
+
|
|
58
|
+
## License
|
|
59
|
+
|
|
60
|
+
MIT — see [LICENSE](../../LICENSE)
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: deepctl-shared-utils
|
|
3
|
-
Version: 0.1.10
|
|
4
|
-
Summary: Shared utilities for deepctl
|
|
5
|
-
Author-email: Deepgram <devrel@deepgram.com>
|
|
6
|
-
Maintainer-email: Deepgram <devrel@deepgram.com>
|
|
7
|
-
License-Expression: MIT
|
|
8
|
-
Keywords: deepgram,utilities,validation,helpers
|
|
9
|
-
Classifier: Development Status :: 3 - Alpha
|
|
10
|
-
Classifier: Intended Audience :: Developers
|
|
11
|
-
Classifier: Programming Language :: Python :: 3
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
-
Requires-Python: >=3.10
|
|
16
|
-
Description-Content-Type: text/markdown
|
|
17
|
-
Requires-Dist: pydantic>=2.0.0
|
|
18
|
-
Requires-Dist: click>=8.0.0
|
|
19
|
-
Requires-Dist: httpx>=0.24.0
|
|
20
|
-
|
|
21
|
-
# deepctl-shared-utils
|
|
22
|
-
|
|
23
|
-
Shared utilities for deepctl. This package is an internal component of deepctl and is not intended for standalone use.
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
"""Shared utilities for deepctl."""
|
|
2
|
-
|
|
3
|
-
from .models import FileInfo
|
|
4
|
-
from .validation import validate_audio_file, validate_date_format, validate_url
|
|
5
|
-
|
|
6
|
-
__all__ = [
|
|
7
|
-
"FileInfo",
|
|
8
|
-
"validate_audio_file",
|
|
9
|
-
"validate_date_format",
|
|
10
|
-
"validate_url",
|
|
11
|
-
]
|
|
12
|
-
|
|
13
|
-
__version__ = "0.1.0"
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: deepctl-shared-utils
|
|
3
|
-
Version: 0.1.10
|
|
4
|
-
Summary: Shared utilities for deepctl
|
|
5
|
-
Author-email: Deepgram <devrel@deepgram.com>
|
|
6
|
-
Maintainer-email: Deepgram <devrel@deepgram.com>
|
|
7
|
-
License-Expression: MIT
|
|
8
|
-
Keywords: deepgram,utilities,validation,helpers
|
|
9
|
-
Classifier: Development Status :: 3 - Alpha
|
|
10
|
-
Classifier: Intended Audience :: Developers
|
|
11
|
-
Classifier: Programming Language :: Python :: 3
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
-
Requires-Python: >=3.10
|
|
16
|
-
Description-Content-Type: text/markdown
|
|
17
|
-
Requires-Dist: pydantic>=2.0.0
|
|
18
|
-
Requires-Dist: click>=8.0.0
|
|
19
|
-
Requires-Dist: httpx>=0.24.0
|
|
20
|
-
|
|
21
|
-
# deepctl-shared-utils
|
|
22
|
-
|
|
23
|
-
Shared utilities for deepctl. This package is an internal component of deepctl and is not intended for standalone use.
|
|
File without changes
|
{deepctl_shared_utils-0.1.10 → deepctl_shared_utils-0.1.11}/src/deepctl_shared_utils/models.py
RENAMED
|
File without changes
|
{deepctl_shared_utils-0.1.10 → deepctl_shared_utils-0.1.11}/src/deepctl_shared_utils/py.typed
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|