deepctl-cmd-debug-audio 0.1.0__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_cmd_debug_audio-0.1.0/PKG-INFO +27 -0
- deepctl_cmd_debug_audio-0.1.0/README.md +5 -0
- deepctl_cmd_debug_audio-0.1.0/pyproject.toml +42 -0
- deepctl_cmd_debug_audio-0.1.0/setup.cfg +4 -0
- deepctl_cmd_debug_audio-0.1.0/src/deepctl_cmd_debug_audio/__init__.py +5 -0
- deepctl_cmd_debug_audio-0.1.0/src/deepctl_cmd_debug_audio/command.py +393 -0
- deepctl_cmd_debug_audio-0.1.0/src/deepctl_cmd_debug_audio/models.py +49 -0
- deepctl_cmd_debug_audio-0.1.0/src/deepctl_cmd_debug_audio.egg-info/PKG-INFO +27 -0
- deepctl_cmd_debug_audio-0.1.0/src/deepctl_cmd_debug_audio.egg-info/SOURCES.txt +11 -0
- deepctl_cmd_debug_audio-0.1.0/src/deepctl_cmd_debug_audio.egg-info/dependency_links.txt +1 -0
- deepctl_cmd_debug_audio-0.1.0/src/deepctl_cmd_debug_audio.egg-info/entry_points.txt +2 -0
- deepctl_cmd_debug_audio-0.1.0/src/deepctl_cmd_debug_audio.egg-info/requires.txt +5 -0
- deepctl_cmd_debug_audio-0.1.0/src/deepctl_cmd_debug_audio.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: deepctl-cmd-debug-audio
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Audio debug subcommand for deepctl
|
|
5
|
+
Author-email: Deepgram <devrel@deepgram.com>
|
|
6
|
+
Maintainer-email: Deepgram <devrel@deepgram.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Keywords: deepgram,cli,debug,audio,diagnostics
|
|
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: deepctl-core>=0.1.0
|
|
18
|
+
Requires-Dist: click>=8.0.0
|
|
19
|
+
Requires-Dist: rich>=13.0.0
|
|
20
|
+
Requires-Dist: pydantic>=2.0.0
|
|
21
|
+
Requires-Dist: ffmpeg-python>=0.2.0
|
|
22
|
+
|
|
23
|
+
# deepctl-cmd-debug-audio
|
|
24
|
+
|
|
25
|
+
Audio debug subcommand for the Deepgram CLI (deepctl). This package provides the `audio` subcommand under the `debug` command group, allowing users to debug audio-related issues using ffprobe presets.
|
|
26
|
+
|
|
27
|
+
`ffmpeg` is required.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "deepctl-cmd-debug-audio"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Audio debug subcommand for deepctl"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
authors = [{ name = "Deepgram", email = "devrel@deepgram.com" }]
|
|
12
|
+
maintainers = [{ name = "Deepgram", email = "devrel@deepgram.com" }]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 3 - Alpha",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3.10",
|
|
18
|
+
"Programming Language :: Python :: 3.11",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
]
|
|
21
|
+
keywords = ["deepgram", "cli", "debug", "audio", "diagnostics"]
|
|
22
|
+
requires-python = ">=3.10"
|
|
23
|
+
dependencies = [
|
|
24
|
+
"deepctl-core>=0.1.0",
|
|
25
|
+
"click>=8.0.0",
|
|
26
|
+
"rich>=13.0.0",
|
|
27
|
+
"pydantic>=2.0.0",
|
|
28
|
+
"ffmpeg-python>=0.2.0",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.scripts]
|
|
32
|
+
# Internal commands not meant to be called directly
|
|
33
|
+
|
|
34
|
+
[project.entry-points."deepctl.subcommands.debug"]
|
|
35
|
+
audio = "deepctl_cmd_debug_audio.command:AudioCommand"
|
|
36
|
+
|
|
37
|
+
[tool.setuptools]
|
|
38
|
+
package-dir = { "" = "src" }
|
|
39
|
+
|
|
40
|
+
[tool.setuptools.packages.find]
|
|
41
|
+
where = ["src"]
|
|
42
|
+
include = ["deepctl_cmd_debug_audio*"]
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
"""Audio debug command for deepctl."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import shutil
|
|
5
|
+
import subprocess
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import ffmpeg # type: ignore[import-untyped]
|
|
9
|
+
from deepctl_core import AuthManager, BaseCommand, Config, DeepgramClient
|
|
10
|
+
from rich import box
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
from rich.panel import Panel
|
|
13
|
+
from rich.table import Table
|
|
14
|
+
|
|
15
|
+
from .models import AudioDebugResult, AudioFormat, AudioInfo, AudioStream
|
|
16
|
+
|
|
17
|
+
console = Console()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AudioCommand(BaseCommand):
|
|
21
|
+
"""Debug audio file issues."""
|
|
22
|
+
|
|
23
|
+
name = "audio"
|
|
24
|
+
help = "Debug audio file issues for Deepgram transcription"
|
|
25
|
+
short_help = "Debug audio issues"
|
|
26
|
+
|
|
27
|
+
# Audio debug doesn't require auth
|
|
28
|
+
requires_auth = False
|
|
29
|
+
requires_project = False
|
|
30
|
+
ci_friendly = True
|
|
31
|
+
|
|
32
|
+
def get_arguments(self) -> list[dict[str, Any]]:
|
|
33
|
+
"""Get command arguments and options."""
|
|
34
|
+
return [
|
|
35
|
+
{
|
|
36
|
+
"names": ["--file", "-f"],
|
|
37
|
+
"help": "Path to audio file or URL to debug",
|
|
38
|
+
"type": str,
|
|
39
|
+
"is_option": True,
|
|
40
|
+
"required": True,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"names": ["--verbose", "-v"],
|
|
44
|
+
"help": "Show detailed diagnostic information",
|
|
45
|
+
"is_flag": True,
|
|
46
|
+
"is_option": True,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"names": ["--extra-verbose", "-vv"],
|
|
50
|
+
"help": "Show raw ffprobe output",
|
|
51
|
+
"is_flag": True,
|
|
52
|
+
"is_option": True,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"names": ["--ffprobe-args"],
|
|
56
|
+
"help": (
|
|
57
|
+
"Custom ffprobe arguments "
|
|
58
|
+
"(e.g., '-show_streams -show_format')"
|
|
59
|
+
),
|
|
60
|
+
"type": str,
|
|
61
|
+
"is_option": True,
|
|
62
|
+
},
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
def check_ffmpeg_installed(self) -> bool:
|
|
66
|
+
"""Check if ffmpeg/ffprobe is installed."""
|
|
67
|
+
return shutil.which("ffprobe") is not None
|
|
68
|
+
|
|
69
|
+
def run_ffprobe(
|
|
70
|
+
self, file_path: str, custom_args: str | None = None
|
|
71
|
+
) -> dict[str, Any]:
|
|
72
|
+
"""Run ffprobe on the given file."""
|
|
73
|
+
try:
|
|
74
|
+
if custom_args:
|
|
75
|
+
# Parse custom arguments and run ffprobe directly
|
|
76
|
+
cmd = ["ffprobe", "-v", "quiet", "-print_format", "json"]
|
|
77
|
+
cmd.extend(custom_args.split())
|
|
78
|
+
cmd.append(file_path)
|
|
79
|
+
|
|
80
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
81
|
+
if result.returncode != 0:
|
|
82
|
+
raise Exception(f"ffprobe failed: {result.stderr}")
|
|
83
|
+
|
|
84
|
+
return dict(json.loads(result.stdout))
|
|
85
|
+
else:
|
|
86
|
+
# Use python-ffmpeg for standard probe
|
|
87
|
+
probe = ffmpeg.probe(file_path)
|
|
88
|
+
return dict(probe)
|
|
89
|
+
except Exception as e:
|
|
90
|
+
raise Exception(f"Failed to probe file: {e!s}")
|
|
91
|
+
|
|
92
|
+
def parse_audio_info(self, probe_data: dict[str, Any]) -> AudioInfo:
|
|
93
|
+
"""Parse ffprobe output into AudioInfo model."""
|
|
94
|
+
audio_info = AudioInfo(raw_data=probe_data)
|
|
95
|
+
|
|
96
|
+
# Parse format information
|
|
97
|
+
if "format" in probe_data:
|
|
98
|
+
format_data = probe_data["format"]
|
|
99
|
+
audio_info.format = AudioFormat(
|
|
100
|
+
filename=format_data.get("filename", ""),
|
|
101
|
+
format_name=format_data.get("format_name"),
|
|
102
|
+
format_long_name=format_data.get("format_long_name"),
|
|
103
|
+
duration=(
|
|
104
|
+
float(format_data.get("duration", 0))
|
|
105
|
+
if format_data.get("duration")
|
|
106
|
+
else None
|
|
107
|
+
),
|
|
108
|
+
size=(
|
|
109
|
+
int(format_data.get("size", 0))
|
|
110
|
+
if format_data.get("size")
|
|
111
|
+
else None
|
|
112
|
+
),
|
|
113
|
+
bit_rate=format_data.get("bit_rate"),
|
|
114
|
+
nb_streams=(
|
|
115
|
+
int(format_data.get("nb_streams", 0))
|
|
116
|
+
if format_data.get("nb_streams")
|
|
117
|
+
else None
|
|
118
|
+
),
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Parse audio streams
|
|
122
|
+
if "streams" in probe_data:
|
|
123
|
+
for stream in probe_data["streams"]:
|
|
124
|
+
if stream.get("codec_type") == "audio":
|
|
125
|
+
audio_stream = AudioStream(
|
|
126
|
+
codec_name=stream.get("codec_name"),
|
|
127
|
+
codec_long_name=stream.get("codec_long_name"),
|
|
128
|
+
sample_rate=stream.get("sample_rate"),
|
|
129
|
+
channels=stream.get("channels"),
|
|
130
|
+
channel_layout=stream.get("channel_layout"),
|
|
131
|
+
duration=(
|
|
132
|
+
float(stream.get("duration", 0))
|
|
133
|
+
if stream.get("duration")
|
|
134
|
+
else None
|
|
135
|
+
),
|
|
136
|
+
bit_rate=stream.get("bit_rate"),
|
|
137
|
+
bits_per_sample=stream.get("bits_per_sample"),
|
|
138
|
+
)
|
|
139
|
+
audio_info.streams.append(audio_stream)
|
|
140
|
+
|
|
141
|
+
return audio_info
|
|
142
|
+
|
|
143
|
+
def display_basic_info(self, audio_info: AudioInfo) -> None:
|
|
144
|
+
"""Display basic audio information."""
|
|
145
|
+
console.print("\n[green]✓[/green] Audio File Analysis Complete\n")
|
|
146
|
+
|
|
147
|
+
# Format info
|
|
148
|
+
if audio_info.format:
|
|
149
|
+
console.print("[bold]File Information:[/bold]")
|
|
150
|
+
format_name = (
|
|
151
|
+
audio_info.format.format_long_name
|
|
152
|
+
or audio_info.format.format_name
|
|
153
|
+
or "Unknown"
|
|
154
|
+
)
|
|
155
|
+
console.print(f" • Format: {format_name}")
|
|
156
|
+
|
|
157
|
+
if audio_info.format.duration:
|
|
158
|
+
minutes, seconds = divmod(audio_info.format.duration, 60)
|
|
159
|
+
console.print(
|
|
160
|
+
f" • Duration: {int(minutes):02d}:{seconds:06.3f}"
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
if audio_info.format.size:
|
|
164
|
+
size_mb = audio_info.format.size / (1024 * 1024)
|
|
165
|
+
console.print(f" • Size: {size_mb:.2f} MB")
|
|
166
|
+
|
|
167
|
+
if audio_info.format.bit_rate:
|
|
168
|
+
bit_rate_kbps = int(audio_info.format.bit_rate) / 1000
|
|
169
|
+
console.print(f" • Bit Rate: {bit_rate_kbps:.0f} kbps")
|
|
170
|
+
|
|
171
|
+
# Stream info
|
|
172
|
+
if audio_info.streams:
|
|
173
|
+
console.print("\n[bold]Audio Stream Information:[/bold]")
|
|
174
|
+
for i, stream in enumerate(audio_info.streams):
|
|
175
|
+
if len(audio_info.streams) > 1:
|
|
176
|
+
console.print(f"\n Stream {i + 1}:")
|
|
177
|
+
codec_name = (
|
|
178
|
+
stream.codec_long_name or stream.codec_name or "Unknown"
|
|
179
|
+
)
|
|
180
|
+
console.print(f" • Codec: {codec_name}")
|
|
181
|
+
if stream.sample_rate:
|
|
182
|
+
console.print(f" • Sample Rate: {stream.sample_rate} Hz")
|
|
183
|
+
if stream.channels:
|
|
184
|
+
console.print(
|
|
185
|
+
f" • Channels: {stream.channels} "
|
|
186
|
+
f"({stream.channel_layout or 'Unknown layout'})"
|
|
187
|
+
)
|
|
188
|
+
if stream.bit_rate:
|
|
189
|
+
bit_rate_kbps = int(stream.bit_rate) / 1000
|
|
190
|
+
console.print(
|
|
191
|
+
f" • Stream Bit Rate: {bit_rate_kbps:.0f} kbps"
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
def display_verbose_info(self, audio_info: AudioInfo) -> None:
|
|
195
|
+
"""Display detailed audio information in table format."""
|
|
196
|
+
console.print("\n[green]✓[/green] Detailed Audio File Analysis\n")
|
|
197
|
+
|
|
198
|
+
# Format table
|
|
199
|
+
if audio_info.format:
|
|
200
|
+
format_table = Table(title="Format Information", box=box.ROUNDED)
|
|
201
|
+
format_table.add_column("Property", style="cyan")
|
|
202
|
+
format_table.add_column("Value", style="white")
|
|
203
|
+
|
|
204
|
+
format_data = audio_info.format.model_dump()
|
|
205
|
+
for key, value in format_data.items():
|
|
206
|
+
if value is not None:
|
|
207
|
+
display_key = key.replace("_", " ").title()
|
|
208
|
+
display_value = str(value)
|
|
209
|
+
|
|
210
|
+
if key == "duration" and value:
|
|
211
|
+
minutes, seconds = divmod(value, 60)
|
|
212
|
+
display_value = f"{int(minutes):02d}:{seconds:06.3f}"
|
|
213
|
+
elif key == "size" and value:
|
|
214
|
+
size_mb = value / (1024 * 1024)
|
|
215
|
+
display_value = f"{size_mb:.2f} MB ({value:,} bytes)"
|
|
216
|
+
elif key == "bit_rate" and value:
|
|
217
|
+
bit_rate_kbps = int(value) / 1000
|
|
218
|
+
display_value = f"{bit_rate_kbps:.0f} kbps"
|
|
219
|
+
|
|
220
|
+
format_table.add_row(display_key, display_value)
|
|
221
|
+
|
|
222
|
+
console.print(format_table)
|
|
223
|
+
|
|
224
|
+
# Streams table
|
|
225
|
+
if audio_info.streams:
|
|
226
|
+
for i, stream in enumerate(audio_info.streams):
|
|
227
|
+
stream_table = Table(
|
|
228
|
+
title=(
|
|
229
|
+
f"Audio Stream {i + 1}"
|
|
230
|
+
if len(audio_info.streams) > 1
|
|
231
|
+
else "Audio Stream"
|
|
232
|
+
),
|
|
233
|
+
box=box.ROUNDED,
|
|
234
|
+
)
|
|
235
|
+
stream_table.add_column("Property", style="cyan")
|
|
236
|
+
stream_table.add_column("Value", style="white")
|
|
237
|
+
|
|
238
|
+
stream_data = stream.model_dump()
|
|
239
|
+
for key, value in stream_data.items():
|
|
240
|
+
if value is not None:
|
|
241
|
+
display_key = key.replace("_", " ").title()
|
|
242
|
+
display_value = str(value)
|
|
243
|
+
|
|
244
|
+
if key == "sample_rate" and value:
|
|
245
|
+
display_value = f"{value} Hz"
|
|
246
|
+
elif key == "bit_rate" and value:
|
|
247
|
+
bit_rate_kbps = int(value) / 1000
|
|
248
|
+
display_value = f"{bit_rate_kbps:.0f} kbps"
|
|
249
|
+
elif key == "duration" and value:
|
|
250
|
+
minutes, seconds = divmod(value, 60)
|
|
251
|
+
display_value = (
|
|
252
|
+
f"{int(minutes):02d}:{seconds:06.3f}"
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
stream_table.add_row(display_key, display_value)
|
|
256
|
+
|
|
257
|
+
console.print(stream_table)
|
|
258
|
+
if i < len(audio_info.streams) - 1:
|
|
259
|
+
console.print()
|
|
260
|
+
|
|
261
|
+
def display_extra_verbose_info(self, audio_info: AudioInfo) -> None:
|
|
262
|
+
"""Display raw ffprobe output."""
|
|
263
|
+
console.print("\n[green]✓[/green] Raw FFprobe Output\n")
|
|
264
|
+
|
|
265
|
+
if audio_info.raw_data:
|
|
266
|
+
import json
|
|
267
|
+
|
|
268
|
+
from rich.syntax import Syntax
|
|
269
|
+
|
|
270
|
+
json_str = json.dumps(audio_info.raw_data, indent=2)
|
|
271
|
+
syntax = Syntax(
|
|
272
|
+
json_str, "json", theme="monokai", line_numbers=True
|
|
273
|
+
)
|
|
274
|
+
console.print(syntax)
|
|
275
|
+
|
|
276
|
+
def handle(
|
|
277
|
+
self,
|
|
278
|
+
config: Config,
|
|
279
|
+
auth_manager: AuthManager,
|
|
280
|
+
client: DeepgramClient,
|
|
281
|
+
**kwargs: Any,
|
|
282
|
+
) -> Any:
|
|
283
|
+
"""Handle audio debug command execution."""
|
|
284
|
+
audio_file = kwargs.get("file")
|
|
285
|
+
verbose = kwargs.get("verbose", False)
|
|
286
|
+
extra_verbose = kwargs.get("extra_verbose", False)
|
|
287
|
+
ffprobe_args = kwargs.get("ffprobe_args")
|
|
288
|
+
|
|
289
|
+
# Validate audio_file
|
|
290
|
+
if not audio_file:
|
|
291
|
+
return AudioDebugResult(
|
|
292
|
+
status="error",
|
|
293
|
+
message="No audio file specified",
|
|
294
|
+
audio_file="",
|
|
295
|
+
ffmpeg_installed=self.check_ffmpeg_installed(),
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
# Check if ffmpeg is installed
|
|
299
|
+
if not self.check_ffmpeg_installed():
|
|
300
|
+
console.print(
|
|
301
|
+
Panel(
|
|
302
|
+
"[red]✗ FFmpeg not found![/red]\n\n"
|
|
303
|
+
"The audio debug command requires FFmpeg to be installed "
|
|
304
|
+
"on your system.\n\n"
|
|
305
|
+
"[bold]To install FFmpeg:[/bold]\n"
|
|
306
|
+
"• Visit: [link]https://ffmpeg.org/download.html[/link]\n"
|
|
307
|
+
"• Or use your package manager:\n"
|
|
308
|
+
" - macOS: [dim]brew install ffmpeg[/dim]\n"
|
|
309
|
+
" - Ubuntu/Debian: [dim]sudo apt install ffmpeg[/dim]\n"
|
|
310
|
+
" - Windows: [dim]Download from ffmpeg.org[/dim]",
|
|
311
|
+
title="FFmpeg Required",
|
|
312
|
+
border_style="red",
|
|
313
|
+
)
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
return AudioDebugResult(
|
|
317
|
+
status="error",
|
|
318
|
+
message="FFmpeg is not installed",
|
|
319
|
+
audio_file=audio_file,
|
|
320
|
+
ffmpeg_installed=False,
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
# Process the audio file
|
|
324
|
+
try:
|
|
325
|
+
console.print(f"[blue]Analyzing audio file:[/blue] {audio_file}")
|
|
326
|
+
|
|
327
|
+
# Run ffprobe
|
|
328
|
+
probe_data = self.run_ffprobe(str(audio_file), ffprobe_args)
|
|
329
|
+
|
|
330
|
+
# Parse the data
|
|
331
|
+
audio_info = self.parse_audio_info(probe_data)
|
|
332
|
+
|
|
333
|
+
# Display results based on verbosity
|
|
334
|
+
if extra_verbose or ffprobe_args:
|
|
335
|
+
self.display_extra_verbose_info(audio_info)
|
|
336
|
+
elif verbose:
|
|
337
|
+
self.display_verbose_info(audio_info)
|
|
338
|
+
else:
|
|
339
|
+
self.display_basic_info(audio_info)
|
|
340
|
+
|
|
341
|
+
# Check for Deepgram compatibility
|
|
342
|
+
console.print("\n[bold]Deepgram Compatibility Check:[/bold]")
|
|
343
|
+
compatibility_issues = []
|
|
344
|
+
|
|
345
|
+
if audio_info.streams:
|
|
346
|
+
for stream in audio_info.streams:
|
|
347
|
+
# Check sample rate
|
|
348
|
+
if stream.sample_rate and int(stream.sample_rate) < 8000:
|
|
349
|
+
compatibility_issues.append(
|
|
350
|
+
f"⚠️ Low sample rate ({stream.sample_rate} Hz) - "
|
|
351
|
+
f"Deepgram works best with 8kHz or higher"
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
# Check channels
|
|
355
|
+
if stream.channels and stream.channels > 2:
|
|
356
|
+
compatibility_issues.append(
|
|
357
|
+
f"⚠️ Multi-channel audio ({stream.channels} "
|
|
358
|
+
f"channels) - Consider converting to mono or "
|
|
359
|
+
f"stereo"
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
if compatibility_issues:
|
|
363
|
+
for issue in compatibility_issues:
|
|
364
|
+
console.print(f" {issue}")
|
|
365
|
+
else:
|
|
366
|
+
console.print(
|
|
367
|
+
" [green]✓[/green] Audio appears to be compatible "
|
|
368
|
+
"with Deepgram"
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
return AudioDebugResult(
|
|
372
|
+
status="success",
|
|
373
|
+
message="Audio file analyzed successfully",
|
|
374
|
+
audio_file=audio_file,
|
|
375
|
+
audio_info=audio_info,
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
except Exception as e:
|
|
379
|
+
console.print(
|
|
380
|
+
Panel(
|
|
381
|
+
f"[red]✗ Error analyzing audio file[/red]\n\n"
|
|
382
|
+
f"[dim]{e!s}[/dim]",
|
|
383
|
+
title="Analysis Failed",
|
|
384
|
+
border_style="red",
|
|
385
|
+
)
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
return AudioDebugResult(
|
|
389
|
+
status="error",
|
|
390
|
+
message="Failed to analyze audio file",
|
|
391
|
+
audio_file=audio_file,
|
|
392
|
+
error_details=str(e),
|
|
393
|
+
)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Data models for audio debug command."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from deepctl_core import BaseResult
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AudioStream(BaseModel):
|
|
10
|
+
"""Audio stream information from ffprobe."""
|
|
11
|
+
|
|
12
|
+
codec_name: str | None = None
|
|
13
|
+
codec_long_name: str | None = None
|
|
14
|
+
sample_rate: str | None = None
|
|
15
|
+
channels: int | None = None
|
|
16
|
+
channel_layout: str | None = None
|
|
17
|
+
duration: float | None = None
|
|
18
|
+
bit_rate: str | None = None
|
|
19
|
+
bits_per_sample: int | None = None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class AudioFormat(BaseModel):
|
|
23
|
+
"""Audio format information from ffprobe."""
|
|
24
|
+
|
|
25
|
+
filename: str
|
|
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
|
+
nb_streams: int | None = None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class AudioInfo(BaseModel):
|
|
35
|
+
"""Detailed audio file information."""
|
|
36
|
+
|
|
37
|
+
format: AudioFormat | None = None
|
|
38
|
+
streams: list[AudioStream] = []
|
|
39
|
+
raw_data: dict[str, Any] | None = None # For verbose output
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class AudioDebugResult(BaseResult):
|
|
43
|
+
"""Result from audio debug command execution."""
|
|
44
|
+
|
|
45
|
+
message: str
|
|
46
|
+
audio_file: str | None = None
|
|
47
|
+
audio_info: AudioInfo | None = None
|
|
48
|
+
ffmpeg_installed: bool = True
|
|
49
|
+
error_details: str | None = None
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: deepctl-cmd-debug-audio
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Audio debug subcommand for deepctl
|
|
5
|
+
Author-email: Deepgram <devrel@deepgram.com>
|
|
6
|
+
Maintainer-email: Deepgram <devrel@deepgram.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Keywords: deepgram,cli,debug,audio,diagnostics
|
|
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: deepctl-core>=0.1.0
|
|
18
|
+
Requires-Dist: click>=8.0.0
|
|
19
|
+
Requires-Dist: rich>=13.0.0
|
|
20
|
+
Requires-Dist: pydantic>=2.0.0
|
|
21
|
+
Requires-Dist: ffmpeg-python>=0.2.0
|
|
22
|
+
|
|
23
|
+
# deepctl-cmd-debug-audio
|
|
24
|
+
|
|
25
|
+
Audio debug subcommand for the Deepgram CLI (deepctl). This package provides the `audio` subcommand under the `debug` command group, allowing users to debug audio-related issues using ffprobe presets.
|
|
26
|
+
|
|
27
|
+
`ffmpeg` is required.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/deepctl_cmd_debug_audio/__init__.py
|
|
4
|
+
src/deepctl_cmd_debug_audio/command.py
|
|
5
|
+
src/deepctl_cmd_debug_audio/models.py
|
|
6
|
+
src/deepctl_cmd_debug_audio.egg-info/PKG-INFO
|
|
7
|
+
src/deepctl_cmd_debug_audio.egg-info/SOURCES.txt
|
|
8
|
+
src/deepctl_cmd_debug_audio.egg-info/dependency_links.txt
|
|
9
|
+
src/deepctl_cmd_debug_audio.egg-info/entry_points.txt
|
|
10
|
+
src/deepctl_cmd_debug_audio.egg-info/requires.txt
|
|
11
|
+
src/deepctl_cmd_debug_audio.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
deepctl_cmd_debug_audio
|