spatelier 0.3.0__py3-none-any.whl

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.
Files changed (59) hide show
  1. analytics/__init__.py +1 -0
  2. analytics/reporter.py +497 -0
  3. cli/__init__.py +1 -0
  4. cli/app.py +147 -0
  5. cli/audio.py +129 -0
  6. cli/cli_analytics.py +320 -0
  7. cli/cli_utils.py +282 -0
  8. cli/error_handlers.py +122 -0
  9. cli/files.py +299 -0
  10. cli/update.py +325 -0
  11. cli/video.py +823 -0
  12. cli/worker.py +615 -0
  13. core/__init__.py +1 -0
  14. core/analytics_dashboard.py +368 -0
  15. core/base.py +303 -0
  16. core/base_service.py +69 -0
  17. core/config.py +345 -0
  18. core/database_service.py +116 -0
  19. core/decorators.py +263 -0
  20. core/error_handler.py +210 -0
  21. core/file_tracker.py +254 -0
  22. core/interactive_cli.py +366 -0
  23. core/interfaces.py +166 -0
  24. core/job_queue.py +437 -0
  25. core/logger.py +79 -0
  26. core/package_updater.py +469 -0
  27. core/progress.py +228 -0
  28. core/service_factory.py +295 -0
  29. core/streaming.py +299 -0
  30. core/worker.py +765 -0
  31. database/__init__.py +1 -0
  32. database/connection.py +265 -0
  33. database/metadata.py +516 -0
  34. database/models.py +288 -0
  35. database/repository.py +592 -0
  36. database/transcription_storage.py +219 -0
  37. modules/__init__.py +1 -0
  38. modules/audio/__init__.py +5 -0
  39. modules/audio/converter.py +197 -0
  40. modules/video/__init__.py +16 -0
  41. modules/video/converter.py +191 -0
  42. modules/video/fallback_extractor.py +334 -0
  43. modules/video/services/__init__.py +18 -0
  44. modules/video/services/audio_extraction_service.py +274 -0
  45. modules/video/services/download_service.py +852 -0
  46. modules/video/services/metadata_service.py +190 -0
  47. modules/video/services/playlist_service.py +445 -0
  48. modules/video/services/transcription_service.py +491 -0
  49. modules/video/transcription_service.py +385 -0
  50. modules/video/youtube_api.py +397 -0
  51. spatelier/__init__.py +33 -0
  52. spatelier-0.3.0.dist-info/METADATA +260 -0
  53. spatelier-0.3.0.dist-info/RECORD +59 -0
  54. spatelier-0.3.0.dist-info/WHEEL +5 -0
  55. spatelier-0.3.0.dist-info/entry_points.txt +2 -0
  56. spatelier-0.3.0.dist-info/licenses/LICENSE +21 -0
  57. spatelier-0.3.0.dist-info/top_level.txt +7 -0
  58. utils/__init__.py +1 -0
  59. utils/helpers.py +250 -0
cli/audio.py ADDED
@@ -0,0 +1,129 @@
1
+ """
2
+ Audio processing CLI commands.
3
+
4
+ This module provides command-line interfaces for audio processing operations.
5
+ """
6
+
7
+ from pathlib import Path
8
+ from typing import Optional
9
+
10
+ import typer
11
+ from rich.console import Console
12
+ from rich.panel import Panel
13
+ from rich.table import Table
14
+
15
+ from cli.error_handlers import handle_cli_error, handle_file_not_found
16
+ from core.config import Config
17
+ from core.decorators import handle_errors, time_operation
18
+ from core.logger import get_logger
19
+ from core.service_factory import ServiceFactory
20
+ from modules.audio.converter import AudioConverter
21
+
22
+ # Create the audio CLI app
23
+ app = typer.Typer(
24
+ name="audio",
25
+ help="Audio processing commands",
26
+ rich_markup_mode="rich",
27
+ )
28
+
29
+ console = Console()
30
+
31
+
32
+ @app.command()
33
+ @handle_errors(context="audio convert", verbose=True)
34
+ @time_operation(verbose=True)
35
+ def convert(
36
+ input_file: Path = typer.Argument(..., help="Input audio file"),
37
+ output_file: Path = typer.Argument(..., help="Output audio file"),
38
+ bitrate: int = typer.Option(320, "--bitrate", "-b", help="Audio bitrate (kbps)"),
39
+ format: str = typer.Option("mp3", "--format", "-f", help="Output format"),
40
+ verbose: bool = typer.Option(
41
+ False, "--verbose", "-v", help="Enable verbose output"
42
+ ),
43
+ ):
44
+ """
45
+ Convert audio to different format.
46
+
47
+ Supports various input and output formats including MP3, FLAC, WAV, etc.
48
+ """
49
+ config = Config()
50
+ logger = get_logger("audio-convert", verbose=verbose)
51
+
52
+ # Validate input file
53
+ if not input_file.exists():
54
+ handle_file_not_found(input_file, "convert")
55
+
56
+ try:
57
+ with ServiceFactory(config, verbose=verbose) as services:
58
+ # Create audio converter
59
+ converter = AudioConverter(config, verbose=verbose)
60
+
61
+ # Perform conversion
62
+ result = converter.convert(
63
+ input_file=input_file,
64
+ output_file=output_file,
65
+ format=format,
66
+ bitrate=bitrate,
67
+ )
68
+
69
+ # Display success message
70
+ console.print(
71
+ Panel(
72
+ f"[green]✓[/green] Conversion successful!\n"
73
+ f"Input: {input_file.name}\n"
74
+ f"Output: {output_file.name}\n"
75
+ f"Format: {format.upper()}\n"
76
+ f"Bitrate: {bitrate}kbps\n"
77
+ f"Size: {result.metadata.get('input_size', 0):,} -> {result.metadata.get('output_size', 0):,} bytes",
78
+ title="Conversion Complete",
79
+ border_style="green",
80
+ )
81
+ )
82
+
83
+ except Exception as e:
84
+ handle_cli_error(e, "audio conversion")
85
+
86
+
87
+ @app.command()
88
+ @handle_errors(context="audio info", verbose=True)
89
+ @time_operation(verbose=True)
90
+ def info(
91
+ file_path: Path = typer.Argument(..., help="Audio file to analyze"),
92
+ verbose: bool = typer.Option(
93
+ False, "--verbose", "-v", help="Enable verbose output"
94
+ ),
95
+ ):
96
+ """
97
+ Display detailed information about an audio file.
98
+ """
99
+ config = Config()
100
+ logger = get_logger("audio-info", verbose=verbose)
101
+
102
+ with ServiceFactory(config, verbose=verbose) as services:
103
+ try:
104
+ if not file_path.exists():
105
+ handle_file_not_found(file_path, "analyze")
106
+
107
+ # Create audio converter to get detailed info
108
+ converter = AudioConverter(config, verbose=verbose)
109
+ audio_info = converter.get_audio_info(file_path)
110
+
111
+ # Create info table
112
+ table = Table(title=f"Audio Information: {file_path.name}")
113
+ table.add_column("Property", style="cyan")
114
+ table.add_column("Value", style="magenta")
115
+
116
+ table.add_row("File Path", str(file_path))
117
+ table.add_row("File Size", f"{file_path.stat().st_size:,} bytes")
118
+ table.add_row("Format", audio_info.get("format", "unknown"))
119
+ table.add_row("Codec", audio_info.get("codec", "unknown"))
120
+ table.add_row("Duration", f"{audio_info.get('duration', 0):.2f} seconds")
121
+ table.add_row("Bitrate", f"{audio_info.get('bitrate', 0):,} bps")
122
+ table.add_row("Sample Rate", f"{audio_info.get('sample_rate', 0):,} Hz")
123
+ table.add_row("Channels", str(audio_info.get("channels", 0)))
124
+ table.add_row("Channel Layout", audio_info.get("channel_layout", "unknown"))
125
+
126
+ console.print(table)
127
+
128
+ except Exception as e:
129
+ handle_cli_error(e, "audio analysis")
cli/cli_analytics.py ADDED
@@ -0,0 +1,320 @@
1
+ """
2
+ Analytics CLI commands.
3
+
4
+ This module provides command-line interfaces for analytics and reporting operations.
5
+ """
6
+
7
+ from pathlib import Path
8
+ from typing import Optional
9
+
10
+ import typer
11
+ from rich.console import Console
12
+ from rich.panel import Panel
13
+ from rich.progress import Progress, SpinnerColumn, TextColumn
14
+ from rich.table import Table
15
+ from sqlalchemy import func
16
+
17
+ from analytics.reporter import AnalyticsReporter
18
+ from core.config import Config
19
+ from core.decorators import handle_errors, time_operation
20
+ from core.logger import get_logger
21
+ from core.service_factory import ServiceFactory
22
+
23
+ # Create the analytics CLI app
24
+ app = typer.Typer(
25
+ name="analytics",
26
+ help="Analytics and reporting commands",
27
+ rich_markup_mode="rich",
28
+ )
29
+
30
+ console = Console()
31
+
32
+
33
+ @app.command()
34
+ @handle_errors(context="analytics report", verbose=True)
35
+ @time_operation(verbose=True)
36
+ def report(
37
+ days: int = typer.Option(30, "--days", "-d", help="Number of days to analyze"),
38
+ output: Optional[Path] = typer.Option(
39
+ None, "--output", "-o", help="Output file path"
40
+ ),
41
+ format: str = typer.Option(
42
+ "json", "--format", "-f", help="Output format (json, csv, excel)"
43
+ ),
44
+ verbose: bool = typer.Option(
45
+ False, "--verbose", "-v", help="Enable verbose output"
46
+ ),
47
+ ):
48
+ """
49
+ Generate comprehensive analytics report.
50
+ """
51
+ config = Config()
52
+ logger = get_logger("analytics-report", verbose=verbose)
53
+
54
+ with ServiceFactory(config, verbose=verbose) as services:
55
+ try:
56
+ with Progress(
57
+ SpinnerColumn(),
58
+ TextColumn("[progress.description]{task.description}"),
59
+ console=console,
60
+ ) as progress:
61
+ task = progress.add_task("Generating analytics report...", total=None)
62
+
63
+ # Create analytics reporter with database service
64
+ from analytics.reporter import AnalyticsReporter
65
+
66
+ reporter = AnalyticsReporter(
67
+ config, verbose=verbose, db_service=services.database
68
+ )
69
+
70
+ # Generate reports
71
+ progress.update(task, description="Generating media report...")
72
+ media_report = reporter.generate_media_report(days)
73
+
74
+ progress.update(task, description="Generating processing report...")
75
+ processing_report = reporter.generate_processing_report(days)
76
+
77
+ progress.update(task, description="Generating usage report...")
78
+ usage_report = reporter.generate_usage_report(days)
79
+
80
+ # Combine reports
81
+ combined_report = {
82
+ "period_days": days,
83
+ "generated_at": reporter.session.query(func.now()).scalar(),
84
+ "media_report": media_report,
85
+ "processing_report": processing_report,
86
+ "usage_report": usage_report,
87
+ }
88
+
89
+ # Display summary
90
+ table = Table(title=f"Analytics Summary (Last {days} days)")
91
+ table.add_column("Metric", style="cyan")
92
+ table.add_column("Value", style="magenta")
93
+
94
+ table.add_row("Total Files", str(media_report["total_files"]))
95
+ table.add_row("Total Size (MB)", f"{media_report['total_size_mb']:.2f}")
96
+ table.add_row("Total Jobs", str(processing_report["total_jobs"]))
97
+ table.add_row("Success Rate", f"{processing_report['success_rate']:.2%}")
98
+ table.add_row(
99
+ "Avg Processing Time",
100
+ f"{processing_report['avg_processing_time_seconds']:.2f}s",
101
+ )
102
+ table.add_row("Total Events", str(usage_report["total_events"]))
103
+
104
+ console.print(table)
105
+
106
+ # Save to file if requested
107
+ if output:
108
+ progress.update(task, description="Saving report...")
109
+ reporter.export_data(output, format)
110
+ console.print(
111
+ Panel(
112
+ f"[green]✓[/green] Report saved to: {output}",
113
+ title="Report Saved",
114
+ border_style="green",
115
+ )
116
+ )
117
+
118
+ except Exception as e:
119
+ logger.error(f"Analytics report failed: {e}")
120
+ console.print(
121
+ Panel(
122
+ f"[red]✗[/red] Analytics report failed: {str(e)}",
123
+ title="Error",
124
+ border_style="red",
125
+ )
126
+ )
127
+ raise typer.Exit(1)
128
+
129
+
130
+ @app.command()
131
+ def visualize(
132
+ output_dir: Path = typer.Argument(..., help="Output directory for visualizations"),
133
+ days: int = typer.Option(30, "--days", "-d", help="Number of days to analyze"),
134
+ verbose: bool = typer.Option(
135
+ False, "--verbose", "-v", help="Enable verbose output"
136
+ ),
137
+ ):
138
+ """
139
+ Create visualization charts and dashboards.
140
+ """
141
+ config = Config()
142
+ logger = get_logger("analytics-visualize", verbose=verbose)
143
+
144
+ try:
145
+ with Progress(
146
+ SpinnerColumn(),
147
+ TextColumn("[progress.description]{task.description}"),
148
+ console=console,
149
+ ) as progress:
150
+ task = progress.add_task("Creating visualizations...", total=None)
151
+
152
+ reporter = AnalyticsReporter(config, verbose=verbose)
153
+
154
+ progress.update(task, description="Generating charts...")
155
+ created_files = reporter.create_visualizations(output_dir, days)
156
+
157
+ console.print(
158
+ Panel(
159
+ f"[green]✓[/green] Created {len(created_files)} visualization files\n"
160
+ f"Output directory: {output_dir}",
161
+ title="Visualizations Created",
162
+ border_style="green",
163
+ )
164
+ )
165
+
166
+ # List created files
167
+ if created_files:
168
+ table = Table(title="Created Files")
169
+ table.add_column("File", style="cyan")
170
+ table.add_column("Type", style="magenta")
171
+
172
+ for file_path in created_files:
173
+ file_type = "Chart" if file_path.suffix == ".png" else "Dashboard"
174
+ table.add_row(str(file_path.name), file_type)
175
+
176
+ console.print(table)
177
+
178
+ except Exception as e:
179
+ logger.error(f"Visualization creation failed: {e}")
180
+ console.print(
181
+ Panel(
182
+ f"[red]✗[/red] Visualization creation failed: {str(e)}",
183
+ title="Error",
184
+ border_style="red",
185
+ )
186
+ )
187
+ raise typer.Exit(1)
188
+
189
+
190
+ @app.command()
191
+ def stats(
192
+ days: int = typer.Option(30, "--days", "-d", help="Number of days to analyze"),
193
+ verbose: bool = typer.Option(
194
+ False, "--verbose", "-v", help="Enable verbose output"
195
+ ),
196
+ ):
197
+ """
198
+ Display quick statistics overview.
199
+ """
200
+ config = Config()
201
+ logger = get_logger("analytics-stats", verbose=verbose)
202
+
203
+ try:
204
+ reporter = AnalyticsReporter(config, verbose=verbose)
205
+
206
+ # Get quick stats
207
+ media_report = reporter.generate_media_report(days)
208
+ processing_report = reporter.generate_processing_report(days)
209
+ usage_report = reporter.generate_usage_report(days)
210
+
211
+ # Create stats table
212
+ table = Table(title=f"Quick Stats (Last {days} days)")
213
+ table.add_column("Category", style="cyan")
214
+ table.add_column("Metric", style="yellow")
215
+ table.add_column("Value", style="magenta")
216
+
217
+ # Media stats
218
+ table.add_row("Media", "Total Files", str(media_report["total_files"]))
219
+ table.add_row(
220
+ "Media", "Total Size (MB)", f"{media_report['total_size_mb']:.2f}"
221
+ )
222
+ table.add_row(
223
+ "Media",
224
+ "Avg File Size (MB)",
225
+ f"{media_report['avg_file_size_bytes'] / (1024 * 1024):.2f}",
226
+ )
227
+
228
+ # Processing stats
229
+ table.add_row("Processing", "Total Jobs", str(processing_report["total_jobs"]))
230
+ table.add_row(
231
+ "Processing", "Success Rate", f"{processing_report['success_rate']:.2%}"
232
+ )
233
+ table.add_row(
234
+ "Processing",
235
+ "Avg Time (s)",
236
+ f"{processing_report['avg_processing_time_seconds']:.2f}",
237
+ )
238
+
239
+ # Usage stats
240
+ table.add_row("Usage", "Total Events", str(usage_report["total_events"]))
241
+ table.add_row(
242
+ "Usage", "Most Active Day", usage_report.get("most_active_day", "N/A")
243
+ )
244
+ table.add_row(
245
+ "Usage", "Trend", usage_report.get("trend_analysis", {}).get("trend", "N/A")
246
+ )
247
+
248
+ console.print(table)
249
+
250
+ # Show files by type
251
+ if media_report["files_by_type"]:
252
+ type_table = Table(title="Files by Type")
253
+ type_table.add_column("Type", style="cyan")
254
+ type_table.add_column("Count", style="magenta")
255
+
256
+ for file_type, count in media_report["files_by_type"].items():
257
+ type_table.add_row(file_type, str(count))
258
+
259
+ console.print(type_table)
260
+
261
+ except Exception as e:
262
+ logger.error(f"Stats display failed: {e}")
263
+ console.print(
264
+ Panel(
265
+ f"[red]✗[/red] Stats display failed: {str(e)}",
266
+ title="Error",
267
+ border_style="red",
268
+ )
269
+ )
270
+ raise typer.Exit(1)
271
+
272
+
273
+ @app.command()
274
+ def export(
275
+ output_path: Path = typer.Argument(..., help="Output file path"),
276
+ format: str = typer.Option(
277
+ "json", "--format", "-f", help="Export format (json, csv, excel)"
278
+ ),
279
+ days: int = typer.Option(30, "--days", "-d", help="Number of days to export"),
280
+ verbose: bool = typer.Option(
281
+ False, "--verbose", "-v", help="Enable verbose output"
282
+ ),
283
+ ):
284
+ """
285
+ Export analytics data to file.
286
+ """
287
+ config = Config()
288
+ logger = get_logger("analytics-export", verbose=verbose)
289
+
290
+ try:
291
+ with Progress(
292
+ SpinnerColumn(),
293
+ TextColumn("[progress.description]{task.description}"),
294
+ console=console,
295
+ ) as progress:
296
+ task = progress.add_task("Exporting analytics data...", total=None)
297
+
298
+ reporter = AnalyticsReporter(config, verbose=verbose)
299
+
300
+ progress.update(task, description="Generating reports...")
301
+ exported_file = reporter.export_data(output_path, format)
302
+
303
+ console.print(
304
+ Panel(
305
+ f"[green]✓[/green] Data exported to: {exported_file}",
306
+ title="Export Complete",
307
+ border_style="green",
308
+ )
309
+ )
310
+
311
+ except Exception as e:
312
+ logger.error(f"Data export failed: {e}")
313
+ console.print(
314
+ Panel(
315
+ f"[red]✗[/red] Data export failed: {str(e)}",
316
+ title="Error",
317
+ border_style="red",
318
+ )
319
+ )
320
+ raise typer.Exit(1)