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
@@ -0,0 +1,59 @@
1
+ analytics/__init__.py,sha256=7RfAxqoEDZhAoTLgS8U_Kg86Uk7Cp6mqu7Oq6sXM0Ws,39
2
+ analytics/reporter.py,sha256=H7Tg1ylZlZXq0QMY52CiPyMHVI0W_5D3Bx26Zgftt-Y,17184
3
+ cli/__init__.py,sha256=ax7OsAuN6blcwrdT-ZFYt4At_XVHX5I2bTnnyOY5nX4,38
4
+ cli/app.py,sha256=WCoKHaxnpcNqir9lGz2o5ngFNTzZ3kaTxSFD4cdrlZ4,4611
5
+ cli/audio.py,sha256=TLfajWFxbVnmAuKVtBzPm_1DcKpGZPMIwdTjH_lhr6w,4479
6
+ cli/cli_analytics.py,sha256=i0GCOOTkAvBkH2PVGika4Ueo7TQ7XizqpsGUQNJaSxY,10835
7
+ cli/cli_utils.py,sha256=NK7Ka-Tg0fV1PIKrSokgqJE7gT4IbVnq8XKSGhfBRpw,8582
8
+ cli/error_handlers.py,sha256=069trR-KCPNs7uhkeUtqqi2KWofqUX_bdy4smHBzVgE,3382
9
+ cli/files.py,sha256=GmsZUCUQhZlsTOrSzbuqluHE8qGxcQKdL4eaNbhHtB8,9174
10
+ cli/update.py,sha256=dIFFWzsZudTyMFtb3mbJz4e2QsWw9z32Zy9yDZZpEj8,10727
11
+ cli/video.py,sha256=UofcWa92PmKgJY3A7qoX9lvOqc1hnflyW77Q_7E510U,31038
12
+ cli/worker.py,sha256=JlQJuWZ-OK9RVWm_zEgxBatpmpj8MlSgXhBqVtPQxzE,19534
13
+ core/__init__.py,sha256=TzcmHJcrbThDnkRDYggGCdG51i4NxHVbQfwfiHqYtFk,43
14
+ core/analytics_dashboard.py,sha256=IPyay9JYXHYsn34atbkQuKCTpQJcOT1-MY5AwyneG1o,11622
15
+ core/base.py,sha256=fxc9xjP_plOs1V89gYGpJ3zQXZZvqb_w0MRAEkdwTYQ,9311
16
+ core/base_service.py,sha256=QMfZqxk00C3S7rWmDGr1CrmSKpJMqtYG59z2SlOX7HM,2160
17
+ core/config.py,sha256=K0YPj83BG766HCbtn7G8kaG3Zi91ZSEcG_VhtxhBLG0,11390
18
+ core/database_service.py,sha256=DJMArLkp2HSBeUuHmoZDx4xzULBj9AJ82mD3PpZXumk,3564
19
+ core/decorators.py,sha256=7NqCDzUX0gdIokb3PhIJmqzYsR7e-CDpVki7EXgrj4I,7705
20
+ core/error_handler.py,sha256=cBMUkEbuE2U8OpiXM1VilrjRJ0NbFSDQlDpdr5By_KE,7268
21
+ core/file_tracker.py,sha256=iq_zNfwDtXaiftqCPCOC3E1p_H1vHY3bxl5CfsokQHs,7984
22
+ core/interactive_cli.py,sha256=x7iiE2gMxWgQzfsHmqgJdAQSEUXNvVJJCLyCGvSAq80,13078
23
+ core/interfaces.py,sha256=eZvjN0EqeJJCc4V_843e3zcSfkGjHzi3d5lNcLBwRkk,4149
24
+ core/job_queue.py,sha256=WC9IfW4VLkyP7q8Hjs1_kG8VR80ns0AQzNo2x7JNvUA,15058
25
+ core/logger.py,sha256=1ZK2p6UFXzRUHmZcjNZnTIY6VvejtFWhqyafJwzcyhk,1937
26
+ core/package_updater.py,sha256=j5V0XBD5uRmfgtiZ6s_hdmHre2AZHNiWyswDo8CIRcg,16420
27
+ core/progress.py,sha256=e15Na5VFeUpr3oX5AxipfJU2AFYzA3cMLAcYYvqH6bg,7257
28
+ core/service_factory.py,sha256=iiu6z9e94cmYALDzQM-CQz4rWtYgqgUymGaUL8CpWj8,10889
29
+ core/streaming.py,sha256=ehyzX-WagsfpwQXMIw7CYqtJ-SCEgB1aA-k52TXNKXA,9365
30
+ core/worker.py,sha256=80EXiAQxRdepQzlrdZk26DFOqNAQK6_n4-FS0l2ML2c,28855
31
+ database/__init__.py,sha256=EKePuR9hs0oWIJRxme40UMYcWF0tz1cJ9DrbfURqjnA,55
32
+ database/connection.py,sha256=_Py1t_a-iSiPpj3qzVpcOWvLuo_nzOXoRZkz4SBvZtI,9184
33
+ database/metadata.py,sha256=FoeHKrAilWS8gLSuTTTiE26TexgRcOJ22BWsn9YfbxA,18590
34
+ database/models.py,sha256=INTVwH4elPjITmmPO8hpgeX8KgPZ2XMypWvSTzXpa_Q,10914
35
+ database/repository.py,sha256=5S5pWqHBEtVMVhmhgJMTpODKAX6a05JhsyHTKJMVw3g,19287
36
+ database/transcription_storage.py,sha256=tej6rK4W-JLNZfNyYGEFVht5DMiK2ji1GX4MxJYYGgo,7441
37
+ modules/__init__.py,sha256=Q9VgejWnHX56SWlSSNzg337WmEhcd5luHIdEhnRIX3w,52
38
+ modules/audio/__init__.py,sha256=jB3nMX_RuAgfPJl-jaRJ3IaAYvdXln5E-iiBHgncH0c,101
39
+ modules/audio/converter.py,sha256=m96pG69dWyAI4nLARrfR7ByuJlMt1D656Jbc5WA2VwM,6646
40
+ modules/video/__init__.py,sha256=lji7Iv0fUxmFbYMQo_WPCZgQ5TtD544LdpOmaXuq3P8,478
41
+ modules/video/converter.py,sha256=DocIPlPsJLBIM9sLOTmMfYkX9L_BIQyi4wX554ZzhPk,6095
42
+ modules/video/fallback_extractor.py,sha256=KSDllZo_mkr0D3QQql1rZT-VXDJEtF9zpt260PtRZ0Y,11540
43
+ modules/video/transcription_service.py,sha256=DpsLL3I7_7qWNXTbNuLcHTq5cJARWAfJ7Xf9q90oZlc,13447
44
+ modules/video/youtube_api.py,sha256=j-UnEqJ6zjl39JA0MNxWgAc7EKifM2fBmKwDZ8zKQ2c,13069
45
+ modules/video/services/__init__.py,sha256=TyFqoZtJZ9eGG2DHYYple64pcEwwQTiVnvKN-MzFFkw,458
46
+ modules/video/services/audio_extraction_service.py,sha256=_qJLFhawzALVropGfMy8GozUXNuPH7OQCr71ogBhtAs,10283
47
+ modules/video/services/download_service.py,sha256=MdKauUW3G4kloL2PcBc1ATrMJdkRiwEud0Hwc6IbFvs,33902
48
+ modules/video/services/metadata_service.py,sha256=JmxNaaphF1HnEhFRfksG1Ln-KLVenXRkCoQJJ77t33w,6428
49
+ modules/video/services/playlist_service.py,sha256=S4jTcsZcHVrrWVmsXJt3bKBpOxNn4sIFX0KrSxnCMbM,18562
50
+ modules/video/services/transcription_service.py,sha256=WNK5BZbcH6tXezL7-V2mnefS3EuptJFinbsljXGmX4k,18350
51
+ spatelier/__init__.py,sha256=-045ZSA66Hm8CsjvgXLkBAdqVYEiuVcroGw40j3khpY,620
52
+ spatelier-0.3.0.dist-info/licenses/LICENSE,sha256=JZNX7Ai0RiL2tABxcMJ-_3LzKtn4zQn1IiSJE7Gr6LA,1069
53
+ utils/__init__.py,sha256=tqyNdgGqZrcISSg2vBtMlVxsOvwaLo3zjqIk5f3QkhM,37
54
+ utils/helpers.py,sha256=_mljn-GqmpGFi9V4E_yXvibitvVixmqJXkJMwQ2x5qc,5924
55
+ spatelier-0.3.0.dist-info/METADATA,sha256=wQn_8pGNbg_OMOLpdtd17aTsqbAaCsKhDjtdWIFGBmA,8532
56
+ spatelier-0.3.0.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
57
+ spatelier-0.3.0.dist-info/entry_points.txt,sha256=krNsqTmR3258mlYSFxk722DCk9imaEuqCq7Lq3Ikzdk,59
58
+ spatelier-0.3.0.dist-info/top_level.txt,sha256=Ho8f_iFLJBn58qzRo5xWMDWGgBjuwjrY_5p_K9rpVlA,52
59
+ spatelier-0.3.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.10.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ spatelier = spatelier.cli.app:main_entry
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Galen Spikes
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,7 @@
1
+ analytics
2
+ cli
3
+ core
4
+ database
5
+ modules
6
+ spatelier
7
+ utils
utils/__init__.py ADDED
@@ -0,0 +1 @@
1
+ """Utility functions and helpers."""
utils/helpers.py ADDED
@@ -0,0 +1,250 @@
1
+ """
2
+ General utility functions.
3
+
4
+ This module contains helper functions used throughout the application.
5
+ """
6
+
7
+ import hashlib
8
+ import mimetypes
9
+ import shutil
10
+ from pathlib import Path
11
+ from typing import List, Optional, Union
12
+
13
+ from rich.console import Console
14
+ from rich.progress import (
15
+ BarColumn,
16
+ Progress,
17
+ SpinnerColumn,
18
+ TaskProgressColumn,
19
+ TextColumn,
20
+ )
21
+
22
+
23
+ def get_file_hash(file_path: Union[str, Path], algorithm: str = "sha256") -> str:
24
+ """
25
+ Calculate hash of a file.
26
+
27
+ Args:
28
+ file_path: Path to file
29
+ algorithm: Hash algorithm to use
30
+
31
+ Returns:
32
+ Hexadecimal hash string
33
+ """
34
+ hash_obj = hashlib.new(algorithm)
35
+ file_path = Path(file_path)
36
+
37
+ with open(file_path, "rb") as f:
38
+ for chunk in iter(lambda: f.read(4096), b""):
39
+ hash_obj.update(chunk)
40
+
41
+ return hash_obj.hexdigest()
42
+
43
+
44
+ def get_file_size(file_path: Union[str, Path]) -> int:
45
+ """
46
+ Get file size in bytes.
47
+
48
+ Args:
49
+ file_path: Path to file
50
+
51
+ Returns:
52
+ File size in bytes
53
+ """
54
+ return Path(file_path).stat().st_size
55
+
56
+
57
+ def format_file_size(size_bytes: int) -> str:
58
+ """
59
+ Format file size in human-readable format.
60
+
61
+ Args:
62
+ size_bytes: Size in bytes
63
+
64
+ Returns:
65
+ Formatted size string
66
+ """
67
+ if size_bytes == 0:
68
+ return "0 B"
69
+
70
+ size_names = ["B", "KB", "MB", "GB", "TB"]
71
+ i = 0
72
+ while size_bytes >= 1024 and i < len(size_names) - 1:
73
+ size_bytes /= 1024.0
74
+ i += 1
75
+
76
+ return f"{size_bytes:.1f} {size_names[i]}"
77
+
78
+
79
+ def get_file_type(file_path: Union[str, Path]) -> str:
80
+ """
81
+ Get MIME type of a file.
82
+
83
+ Args:
84
+ file_path: Path to file
85
+
86
+ Returns:
87
+ MIME type string
88
+ """
89
+ mime_type, _ = mimetypes.guess_type(str(file_path))
90
+ return mime_type or "application/octet-stream"
91
+
92
+
93
+ def is_video_file(file_path: Union[str, Path]) -> bool:
94
+ """
95
+ Check if file is a video file.
96
+
97
+ Args:
98
+ file_path: Path to file
99
+
100
+ Returns:
101
+ True if file is a video, False otherwise
102
+ """
103
+ mime_type = get_file_type(file_path)
104
+ return mime_type.startswith("video/")
105
+
106
+
107
+ def is_audio_file(file_path: Union[str, Path]) -> bool:
108
+ """
109
+ Check if file is an audio file.
110
+
111
+ Args:
112
+ file_path: Path to file
113
+
114
+ Returns:
115
+ True if file is audio, False otherwise
116
+ """
117
+ mime_type = get_file_type(file_path)
118
+ return mime_type.startswith("audio/")
119
+
120
+
121
+ def find_files(
122
+ directory: Union[str, Path],
123
+ pattern: str = "*",
124
+ recursive: bool = True,
125
+ file_types: Optional[List[str]] = None,
126
+ ) -> List[Path]:
127
+ """
128
+ Find files matching pattern in directory.
129
+
130
+ Args:
131
+ directory: Directory to search
132
+ pattern: File pattern to match
133
+ recursive: Whether to search recursively
134
+ file_types: Optional list of file extensions to filter by
135
+
136
+ Returns:
137
+ List of matching file paths
138
+ """
139
+ directory = Path(directory)
140
+ files = []
141
+
142
+ if recursive:
143
+ files = list(directory.rglob(pattern))
144
+ else:
145
+ files = list(directory.glob(pattern))
146
+
147
+ if file_types:
148
+ file_types = [ext.lower().lstrip(".") for ext in file_types]
149
+ files = [f for f in files if f.suffix.lower().lstrip(".") in file_types]
150
+
151
+ return files
152
+
153
+
154
+ def safe_filename(filename: str, max_length: int = 255) -> str:
155
+ """
156
+ Create a safe filename by removing/replacing invalid characters.
157
+
158
+ Args:
159
+ filename: Original filename
160
+ max_length: Maximum length of filename
161
+
162
+ Returns:
163
+ Safe filename
164
+ """
165
+ # Characters not allowed in filenames
166
+ invalid_chars = '<>:"/\\|?*'
167
+
168
+ # Replace invalid characters
169
+ for char in invalid_chars:
170
+ filename = filename.replace(char, "_")
171
+
172
+ # Remove leading/trailing spaces and dots
173
+ filename = filename.strip(" .")
174
+ # Replace multiple spaces with single space and strip again
175
+ import re
176
+
177
+ filename = re.sub(r"\s+", " ", filename).strip()
178
+ # Remove trailing spaces before extension
179
+ if "." in filename:
180
+ name, ext = filename.rsplit(".", 1)
181
+ filename = name.strip() + "." + ext
182
+
183
+ # Truncate if too long
184
+ if len(filename) > max_length:
185
+ name, ext = filename.rsplit(".", 1) if "." in filename else (filename, "")
186
+ max_name_length = max_length - len(ext) - 1 if ext else max_length
187
+ filename = name[:max_name_length] + ("." + ext if ext else "")
188
+
189
+ return filename
190
+
191
+
192
+ def copy_file_with_progress(
193
+ src: Union[str, Path], dst: Union[str, Path], description: str = "Copying file"
194
+ ) -> bool:
195
+ """
196
+ Copy file with progress bar.
197
+
198
+ Args:
199
+ src: Source file path
200
+ dst: Destination file path
201
+ description: Description for progress bar
202
+
203
+ Returns:
204
+ True if successful, False otherwise
205
+ """
206
+ src = Path(src)
207
+ dst = Path(dst)
208
+
209
+ if not src.exists():
210
+ return False
211
+
212
+ # Ensure destination directory exists
213
+ dst.parent.mkdir(parents=True, exist_ok=True)
214
+
215
+ file_size = src.stat().st_size
216
+
217
+ with Progress(
218
+ SpinnerColumn(),
219
+ TextColumn("[progress.description]{task.description}"),
220
+ BarColumn(),
221
+ TaskProgressColumn(),
222
+ console=Console(),
223
+ ) as progress:
224
+ task = progress.add_task(description, total=file_size)
225
+
226
+ with open(src, "rb") as f_src, open(dst, "wb") as f_dst:
227
+ while True:
228
+ chunk = f_src.read(8192)
229
+ if not chunk:
230
+ break
231
+ f_dst.write(chunk)
232
+ progress.update(task, advance=len(chunk))
233
+
234
+ return True
235
+
236
+
237
+ def cleanup_temp_files(temp_dir: Union[str, Path]) -> None:
238
+ """
239
+ Clean up temporary files in directory.
240
+
241
+ Args:
242
+ temp_dir: Directory containing temporary files
243
+ """
244
+ temp_path = Path(temp_dir)
245
+
246
+ if temp_path.exists():
247
+ try:
248
+ shutil.rmtree(temp_path)
249
+ except Exception:
250
+ pass # Ignore cleanup errors