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.
- analytics/__init__.py +1 -0
- analytics/reporter.py +497 -0
- cli/__init__.py +1 -0
- cli/app.py +147 -0
- cli/audio.py +129 -0
- cli/cli_analytics.py +320 -0
- cli/cli_utils.py +282 -0
- cli/error_handlers.py +122 -0
- cli/files.py +299 -0
- cli/update.py +325 -0
- cli/video.py +823 -0
- cli/worker.py +615 -0
- core/__init__.py +1 -0
- core/analytics_dashboard.py +368 -0
- core/base.py +303 -0
- core/base_service.py +69 -0
- core/config.py +345 -0
- core/database_service.py +116 -0
- core/decorators.py +263 -0
- core/error_handler.py +210 -0
- core/file_tracker.py +254 -0
- core/interactive_cli.py +366 -0
- core/interfaces.py +166 -0
- core/job_queue.py +437 -0
- core/logger.py +79 -0
- core/package_updater.py +469 -0
- core/progress.py +228 -0
- core/service_factory.py +295 -0
- core/streaming.py +299 -0
- core/worker.py +765 -0
- database/__init__.py +1 -0
- database/connection.py +265 -0
- database/metadata.py +516 -0
- database/models.py +288 -0
- database/repository.py +592 -0
- database/transcription_storage.py +219 -0
- modules/__init__.py +1 -0
- modules/audio/__init__.py +5 -0
- modules/audio/converter.py +197 -0
- modules/video/__init__.py +16 -0
- modules/video/converter.py +191 -0
- modules/video/fallback_extractor.py +334 -0
- modules/video/services/__init__.py +18 -0
- modules/video/services/audio_extraction_service.py +274 -0
- modules/video/services/download_service.py +852 -0
- modules/video/services/metadata_service.py +190 -0
- modules/video/services/playlist_service.py +445 -0
- modules/video/services/transcription_service.py +491 -0
- modules/video/transcription_service.py +385 -0
- modules/video/youtube_api.py +397 -0
- spatelier/__init__.py +33 -0
- spatelier-0.3.0.dist-info/METADATA +260 -0
- spatelier-0.3.0.dist-info/RECORD +59 -0
- spatelier-0.3.0.dist-info/WHEEL +5 -0
- spatelier-0.3.0.dist-info/entry_points.txt +2 -0
- spatelier-0.3.0.dist-info/licenses/LICENSE +21 -0
- spatelier-0.3.0.dist-info/top_level.txt +7 -0
- utils/__init__.py +1 -0
- 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,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.
|
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
|