noblebarkrr commited on
Commit
3a22254
·
verified ·
1 Parent(s): 2f4895f

Upload 4 files

Browse files
mvsepless/app.py CHANGED
The diff for this file is too large to render. See raw diff
 
mvsepless/gradio_helper.py CHANGED
@@ -1,471 +1,471 @@
1
- import os
2
- import gradio as gr
3
- import zipfile
4
- from datetime import timezone, timedelta
5
- import platform
6
- from functools import wraps
7
- try:
8
- import spaces
9
- except:
10
- spaces = None
11
- import torch
12
- from tqdm import tqdm
13
- import urllib.request
14
- import time
15
- import yt_dlp
16
- from typing import List, Optional, Tuple, Union, Any, Dict
17
- from i18n import _i18n
18
-
19
- tz = timezone(timedelta(hours=3))
20
-
21
- cuda_available: bool = torch.cuda.is_available()
22
- mps_available: bool = False # torch.mps.is_available()
23
- device_count: int = torch.cuda.device_count() if cuda_available else 0
24
- all_ids: List[int] = list(range(device_count))
25
-
26
- script_dir: str = os.path.dirname(os.path.abspath(__file__))
27
- DOWNLOAD_DIR: str = os.environ.get(
28
- "MVSEPLESS_DOWNLOAD_DIR", os.path.join(os.getcwd(), "downloaded")
29
- )
30
-
31
-
32
- def dw_file(url_model: str, local_path: str, retries: int = 180) -> None:
33
- """
34
- Скачать файл с поддержкой повторных попыток
35
-
36
- Args:
37
- url_model: URL файла
38
- local_path: Локальный путь для сохранения
39
- retries: Количество попыток
40
- """
41
- dir_name = os.path.dirname(local_path)
42
- if dir_name != "":
43
- os.makedirs(dir_name, exist_ok=True)
44
-
45
- class TqdmUpTo(tqdm):
46
- def update_to(self, b: int = 1, bsize: int = 1, tsize: Optional[int] = None) -> None:
47
- if tsize is not None:
48
- self.total = tsize
49
- self.update(b * bsize - self.n)
50
-
51
- for attempt in range(retries):
52
- try:
53
- with TqdmUpTo(
54
- unit="B",
55
- unit_scale=True,
56
- unit_divisor=1024,
57
- miniters=1,
58
- desc=os.path.basename(local_path),
59
- ) as t:
60
- urllib.request.urlretrieve(
61
- url_model, local_path, reporthook=t.update_to
62
- )
63
- break
64
- except Exception as e:
65
- print(_i18n("download_attempt_failed", attempt=attempt + 1, retries=retries, error=str(e)))
66
- if attempt < retries - 1:
67
- print(_i18n("retrying"))
68
- time.sleep(2)
69
- else:
70
- print(_i18n("all_download_attempts_failed"))
71
- raise
72
-
73
-
74
- def dw_yt_dlp(
75
- url: str,
76
- output_dir: Optional[str] = None,
77
- cookie: Optional[str] = None,
78
- output_format: str = "mp3",
79
- output_bitrate: str = "320",
80
- title: Optional[str] = None,
81
- ) -> Optional[str]:
82
- """
83
- Скачать аудио с YouTube с помощью yt-dlp
84
-
85
- Args:
86
- url: URL видео
87
- output_dir: Директория для сохранения
88
- cookie: Путь к файлу с cookies
89
- output_format: Формат выходного файла
90
- output_bitrate: Битрейт
91
- title: Название файла
92
-
93
- Returns:
94
- Путь к скачанному файлу или None
95
- """
96
- outtmpl = "%(title)s.%(ext)s" if title is None else f"{title}.%(ext)s"
97
-
98
- ydl_opts: Dict[str, Any] = {
99
- "format": "bestaudio/best",
100
- "outtmpl": os.path.join(
101
- DOWNLOAD_DIR if not output_dir else output_dir, outtmpl
102
- ),
103
- "postprocessors": [
104
- {
105
- "key": "FFmpegExtractAudio",
106
- "preferredcodec": output_format,
107
- "preferredquality": output_bitrate,
108
- }
109
- ],
110
- "noplaylist": True,
111
- "quiet": True,
112
- "no_warnings": True,
113
- }
114
-
115
- if cookie and os.path.exists(cookie):
116
- ydl_opts["cookiefile"] = cookie
117
-
118
- with yt_dlp.YoutubeDL(ydl_opts) as ydl:
119
- try:
120
- info = ydl.extract_info(url, download=True)
121
- if "_type" in info and info["_type"] == "playlist":
122
- entry = info["entries"][0]
123
- filename = ydl.prepare_filename(entry)
124
- else:
125
- filename = ydl.prepare_filename(info)
126
-
127
- base, _c = os.path.splitext(filename)
128
- audio_file = base + f".{output_format}"
129
-
130
- return os.path.join(DOWNLOAD_DIR, audio_file)
131
- except Exception as e:
132
- print(_i18n("download_error", error=str(e)))
133
- return None
134
-
135
-
136
- def str2bool(value: Union[str, bool, int, None]) -> bool:
137
- """
138
- Преобразовать строку в булево значение
139
-
140
- Args:
141
- value: Входное значение
142
-
143
- Returns:
144
- Булево значение
145
- """
146
- true_values = ['true', '1', 'yes', 'y', 't', 'on']
147
- false_values = ['false', '0', 'no', 'n', 'f', 'off']
148
-
149
- if isinstance(value, str):
150
- value_lower = value.lower().strip()
151
- if value_lower in true_values:
152
- return True
153
- elif value_lower in false_values:
154
- return False
155
- else:
156
- raise ValueError(_i18n("str2bool_error", value=value))
157
- elif isinstance(value, bool):
158
- return value
159
- else:
160
- return bool(value)
161
-
162
-
163
- def set_device(*args: Any, prefer_gpu: bool = True) -> str:
164
- """
165
- Установить устройство для вычислений
166
-
167
- Args:
168
- *args: Аргументы (могут содержать ID устройств)
169
- prefer_gpu: Предпочитать GPU
170
-
171
- Returns:
172
- Строка с указанием устройства
173
- """
174
- prefer_cuda_flag = prefer_gpu
175
-
176
- if args:
177
- if len(args) == 1 and isinstance(args[0], bool):
178
- prefer_cuda_flag = args[0]
179
- ids = None
180
- else:
181
- ids = []
182
- for arg in args:
183
- if isinstance(arg, list):
184
- ids.extend(arg)
185
- elif isinstance(arg, int):
186
- ids.append(arg)
187
- elif isinstance(arg, tuple):
188
- ids.extend(list(arg))
189
-
190
- ids = sorted(set(ids))
191
- prefer_cuda_flag = prefer_gpu if ids else prefer_cuda_flag
192
- else:
193
- ids = None
194
-
195
- if ids is not None:
196
- if cuda_available and prefer_cuda_flag:
197
- valid_ids = [i for i in ids if i < device_count]
198
- if valid_ids:
199
- if len(valid_ids) == 1:
200
- return f"cuda:{valid_ids[0]}"
201
- else:
202
- return f"cuda:{','.join(map(str, valid_ids))}"
203
- else:
204
- return "cuda:0"
205
- elif mps_available and prefer_cuda_flag:
206
- return "mps"
207
- else:
208
- return "cpu"
209
- else:
210
- if cuda_available and prefer_cuda_flag:
211
- if device_count == 1:
212
- return "cuda:0"
213
- elif device_count > 1:
214
- return f"cuda:{','.join(map(str, all_ids))}"
215
- else:
216
- return "cpu"
217
- elif mps_available and prefer_cuda_flag:
218
- return "mps"
219
- else:
220
- return "cpu"
221
-
222
-
223
- def easy_check_is_colab() -> bool:
224
- """
225
- Проверить, выполняется ли код в Google Colab
226
-
227
- Returns:
228
- True если в Colab
229
- """
230
- if platform.machine() == "x86_64" and "Linux" in platform.platform():
231
- try:
232
- import google.colab
233
- module_path: str = google.colab.__file__
234
- if module_path.startswith("/usr/local/lib/python") and module_path.endswith("/dist-packages/google/colab/__init__.py"):
235
- return True
236
- else:
237
- return False
238
- except ImportError:
239
- return False
240
- else:
241
- return False
242
-
243
- zerogpu_is = str2bool(os.getenv('SPACES_ZERO_GPU', 'False'))
244
- zerogpu_available = False
245
- if not spaces:
246
- def hf_spaces_gpu(*args, **kwargs):
247
- if len(args) == 1 and callable(args[0]):
248
- return args[0]
249
- return lambda f: f
250
- else:
251
- if zerogpu_is:
252
- zerogpu_available = True
253
- hf_spaces_gpu = spaces.GPU
254
- else:
255
- def hf_spaces_gpu(*args, **kwargs):
256
- if len(args) == 1 and callable(args[0]):
257
- return args[0]
258
- return lambda f: f
259
-
260
-
261
- class GradioHelper:
262
- """Вспомогательный класс для Gradio интерфейса"""
263
-
264
- def return_list(self, lst: List[Any], none: bool = False, **kwargs) -> gr.update:
265
- """
266
- Вернуть обновление для списка
267
-
268
- Args:
269
- lst: Список значений
270
- none: Добавить пустое значение
271
- **kwargs: Дополнительные аргументы
272
-
273
- Returns:
274
- Обновление Gradio
275
- """
276
- if lst:
277
- return gr.update(choices=lst, value=lst[0] if not none else None, **kwargs)
278
- else:
279
- return gr.update(choices=[], value=None, **kwargs)
280
-
281
- def return_audio(self, label: str, path: str) -> gr.update:
282
- """
283
- Вернуть обновление для аудио
284
-
285
- Args:
286
- label: Метка
287
- path: Путь к файлу
288
-
289
- Returns:
290
- Обновление Gradio
291
- """
292
- return gr.update(label=label, value=path)
293
-
294
- def get_file_size(self, path: Optional[str]) -> str:
295
- """
296
- Получить размер файла в человекочитаемом формате
297
-
298
- Args:
299
- path: Путь к файлу
300
-
301
- Returns:
302
- Строка с размером
303
- """
304
- if path:
305
- if os.path.exists(path):
306
- size_bytes = os.path.getsize(path)
307
- else:
308
- return _i18n("file_not_exists")
309
- else:
310
- return ""
311
-
312
- units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']
313
-
314
- if size_bytes == 0:
315
- return "[0 B]"
316
-
317
- i = 0
318
- size_float = float(size_bytes)
319
- while size_float >= 1024 and i < len(units) - 1:
320
- size_float /= 1024
321
- i += 1
322
-
323
- return f"[{size_float:.1f} {units[i]}]" if i > 0 else f"[{int(size_float)} {units[i]}]"
324
-
325
- def return_audio_with_size(self, *args: Any, **kwargs) -> gr.update:
326
- """
327
- Вернуть аудио с размером в метке
328
-
329
- Args:
330
- *args: Позиционные аргументы
331
- **kwargs: Именованные аргументы
332
-
333
- Returns:
334
- Обновление Gradio
335
- """
336
- if "label" in kwargs and "value" in kwargs:
337
- kwargs["label"] = f"{self.get_file_size(kwargs['value'])} {kwargs['label']}"
338
- elif "label" not in kwargs and "value" in kwargs:
339
- kwargs["label"] = f"{self.get_file_size(kwargs['value'])}"
340
- return gr.update(**kwargs)
341
-
342
- def define_audio_with_size(self, *args: Any, **kwargs) -> gr.Audio:
343
- """
344
- Создать аудио компонент с размером в метке
345
-
346
- Args:
347
- *args: Позиционные аргументы
348
- **kwargs: Именованные аргументы
349
-
350
- Returns:
351
- Компонент Audio
352
- """
353
- if "label" in kwargs and "value" in kwargs:
354
- kwargs["label"] = f"{self.get_file_size(kwargs['value'])} {kwargs['label']}"
355
- elif "label" not in kwargs and "value" in kwargs:
356
- kwargs["label"] = f"{self.get_file_size(kwargs['value'])}"
357
- return gr.Audio(**kwargs)
358
-
359
- def create_archive_advanced(self, file_list: List[Tuple[str, List[Tuple[str, str]]]], archive_name: str = "archive.zip") -> str:
360
- """
361
- Создать ZIP архив из списка файлов
362
-
363
- Args:
364
- file_list: Список файлов для архивации
365
- archive_name: Имя архива
366
-
367
- Returns:
368
- Путь к созданному архиву
369
- """
370
- try:
371
- print(_i18n("creating_zip_archive"))
372
- with zipfile.ZipFile(
373
- archive_name, "w", zipfile.ZIP_DEFLATED
374
- ) as zipf:
375
- successful_files = 0
376
-
377
- for basename, stems in file_list:
378
- for stem_name, stem_path in stems:
379
- try:
380
- if os.path.exists(stem_path) and os.path.isfile(
381
- stem_path
382
- ):
383
- basename_ = os.path.basename(stem_path)
384
- zipf.write(stem_path, basename_)
385
- successful_files += 1
386
- print(
387
- _i18n("file_added_to_zip", path=stem_path, name=basename)
388
- )
389
- else:
390
- print(
391
- _i18n("file_not_found_for_zip", path=stem_path)
392
- )
393
-
394
- except Exception as e:
395
- print(
396
- _i18n("error_adding_to_zip", path=stem_path, error=str(e))
397
- )
398
-
399
- print(_i18n("zip_created", path=archive_name))
400
- print(_i18n("files_added_count", count=successful_files))
401
- return os.path.abspath(archive_name)
402
-
403
- except Exception as e:
404
- print(_i18n("zip_creation_error", error=str(e)))
405
- return ""
406
-
407
- def extract_zip(self, zip_file_path: str, output_dir: Optional[str] = None) -> List[str]:
408
- """
409
- Распаковать ZIP архив
410
-
411
- Args:
412
- zip_file_path: Путь к ZIP архиву
413
- output_dir: Директория для распаковки
414
-
415
- Returns:
416
- Список распакованных файлов
417
- """
418
- if output_dir is None:
419
- output_dir = os.path.splitext(zip_file_path)[0]
420
-
421
- os.makedirs(output_dir, exist_ok=True)
422
-
423
- try:
424
- with zipfile.ZipFile(zip_file_path, 'r') as zip_ref:
425
- if zip_ref.testzip() is not None:
426
- print(_i18n("zip_corrupted_warning"))
427
-
428
- zip_ref.extractall(output_dir)
429
-
430
- file_count = len(zip_ref.namelist())
431
- print(_i18n("zip_extracted", count=file_count, dir=output_dir))
432
-
433
- except zipfile.BadZipFile:
434
- print(_i18n("zip_bad_file"))
435
- return []
436
- except PermissionError:
437
- print(_i18n("zip_permission_error"))
438
- return []
439
- except Exception as e:
440
- print(_i18n("zip_unknown_error", error=str(e)))
441
- return []
442
- finally:
443
- input_files: List[str] = []
444
- for root, dirs, files in os.walk(output_dir):
445
- for file in files:
446
- input_files.append(os.path.join(root, file))
447
- return input_files
448
-
449
-
450
- if __name__ == "__main__":
451
- import argparse
452
- parser = argparse.ArgumentParser(
453
- description=_i18n("download_audio_cli_description")
454
- )
455
-
456
- parser.add_argument("--url", type=str, required=True, help=_i18n("url_help"))
457
- parser.add_argument("--output_dir", type=str, default=None, help=_i18n("output_dir_help"))
458
- parser.add_argument("--cookie", type=str, default=None, help=_i18n("cookie_help"))
459
- parser.add_argument("--output_format", type=str, default="mp3", choices=["mp3", "wav", "flac", "ogg", "opus", "m4a", "aac"], help=_i18n("output_format_help"))
460
- parser.add_argument("--title", type=str, default=None, help=_i18n("title_help"))
461
- args = parser.parse_args()
462
-
463
- from audio import output_formats
464
- dw_yt_dlp(
465
- args.url,
466
- args.output_dir,
467
- args.cookie,
468
- args.output_format,
469
- "320",
470
- args.title,
471
  )
 
1
+ import os
2
+ import gradio as gr
3
+ import zipfile
4
+ from datetime import timezone, timedelta
5
+ import platform
6
+ from functools import wraps
7
+ try:
8
+ import spaces
9
+ except:
10
+ spaces = None
11
+ import torch
12
+ from tqdm import tqdm
13
+ import urllib.request
14
+ import time
15
+ import yt_dlp
16
+ from typing import List, Optional, Tuple, Union, Any, Dict
17
+ from i18n import _i18n
18
+
19
+ tz = timezone(timedelta(hours=3))
20
+
21
+ cuda_available: bool = torch.cuda.is_available()
22
+ mps_available: bool = False # torch.mps.is_available()
23
+ device_count: int = torch.cuda.device_count() if cuda_available else 0
24
+ all_ids: List[int] = list(range(device_count))
25
+
26
+ script_dir: str = os.path.dirname(os.path.abspath(__file__))
27
+ DOWNLOAD_DIR: str = os.environ.get(
28
+ "MVSEPLESS_DOWNLOAD_DIR", os.path.join(os.getcwd(), "downloaded")
29
+ )
30
+
31
+
32
+ def dw_file(url_model: str, local_path: str, retries: int = 180) -> None:
33
+ """
34
+ Скачать файл с поддержкой повторных попыток
35
+
36
+ Args:
37
+ url_model: URL файла
38
+ local_path: Локальный путь для сохранения
39
+ retries: Количество попыток
40
+ """
41
+ dir_name = os.path.dirname(local_path)
42
+ if dir_name != "":
43
+ os.makedirs(dir_name, exist_ok=True)
44
+
45
+ class TqdmUpTo(tqdm):
46
+ def update_to(self, b: int = 1, bsize: int = 1, tsize: Optional[int] = None) -> None:
47
+ if tsize is not None:
48
+ self.total = tsize
49
+ self.update(b * bsize - self.n)
50
+
51
+ for attempt in range(retries):
52
+ try:
53
+ with TqdmUpTo(
54
+ unit="B",
55
+ unit_scale=True,
56
+ unit_divisor=1024,
57
+ miniters=1,
58
+ desc=os.path.basename(local_path),
59
+ ) as t:
60
+ urllib.request.urlretrieve(
61
+ url_model, local_path, reporthook=t.update_to
62
+ )
63
+ break
64
+ except Exception as e:
65
+ print(_i18n("download_attempt_failed", attempt=attempt + 1, retries=retries, error=str(e)))
66
+ if attempt < retries - 1:
67
+ print(_i18n("retrying"))
68
+ time.sleep(2)
69
+ else:
70
+ print(_i18n("all_download_attempts_failed"))
71
+ raise
72
+
73
+
74
+ def dw_yt_dlp(
75
+ url: str,
76
+ output_dir: Optional[str] = None,
77
+ cookie: Optional[str] = None,
78
+ output_format: str = "mp3",
79
+ output_bitrate: str = "320",
80
+ title: Optional[str] = None,
81
+ ) -> Optional[str]:
82
+ """
83
+ Скачать аудио с YouTube с помощью yt-dlp
84
+
85
+ Args:
86
+ url: URL видео
87
+ output_dir: Директория для сохранения
88
+ cookie: Путь к файлу с cookies
89
+ output_format: Формат выходного файла
90
+ output_bitrate: Битрейт
91
+ title: Название файла
92
+
93
+ Returns:
94
+ Путь к скачанному файлу или None
95
+ """
96
+ outtmpl = "%(title)s.%(ext)s" if title is None else f"{title}.%(ext)s"
97
+
98
+ ydl_opts: Dict[str, Any] = {
99
+ "format": "bestaudio/best",
100
+ "outtmpl": os.path.join(
101
+ DOWNLOAD_DIR if not output_dir else output_dir, outtmpl
102
+ ),
103
+ "postprocessors": [
104
+ {
105
+ "key": "FFmpegExtractAudio",
106
+ "preferredcodec": output_format,
107
+ "preferredquality": output_bitrate,
108
+ }
109
+ ],
110
+ "noplaylist": True,
111
+ "quiet": True,
112
+ "no_warnings": True,
113
+ }
114
+
115
+ if cookie and os.path.exists(cookie):
116
+ ydl_opts["cookiefile"] = cookie
117
+
118
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
119
+ try:
120
+ info = ydl.extract_info(url, download=True)
121
+ if "_type" in info and info["_type"] == "playlist":
122
+ entry = info["entries"][0]
123
+ filename = ydl.prepare_filename(entry)
124
+ else:
125
+ filename = ydl.prepare_filename(info)
126
+
127
+ base, _c = os.path.splitext(filename)
128
+ audio_file = base + f".{output_format}"
129
+
130
+ return os.path.join(DOWNLOAD_DIR, audio_file)
131
+ except Exception as e:
132
+ print(_i18n("download_error", error=str(e)))
133
+ return None
134
+
135
+
136
+ def str2bool(value: Union[str, bool, int, None]) -> bool:
137
+ """
138
+ Преобразовать строку в булево значение
139
+
140
+ Args:
141
+ value: Входное значение
142
+
143
+ Returns:
144
+ Булево значение
145
+ """
146
+ true_values = ['true', '1', 'yes', 'y', 't', 'on']
147
+ false_values = ['false', '0', 'no', 'n', 'f', 'off']
148
+
149
+ if isinstance(value, str):
150
+ value_lower = value.lower().strip()
151
+ if value_lower in true_values:
152
+ return True
153
+ elif value_lower in false_values:
154
+ return False
155
+ else:
156
+ raise ValueError(_i18n("str2bool_error", value=value))
157
+ elif isinstance(value, bool):
158
+ return value
159
+ else:
160
+ return bool(value)
161
+
162
+
163
+ def set_device(*args: Any, prefer_gpu: bool = True) -> str:
164
+ """
165
+ Установить устройство для вычислений
166
+
167
+ Args:
168
+ *args: Аргументы (могут содержать ID устройств)
169
+ prefer_gpu: Предпочитать GPU
170
+
171
+ Returns:
172
+ Строка с указанием устройства
173
+ """
174
+ prefer_cuda_flag = prefer_gpu
175
+
176
+ if args:
177
+ if len(args) == 1 and isinstance(args[0], bool):
178
+ prefer_cuda_flag = args[0]
179
+ ids = None
180
+ else:
181
+ ids = []
182
+ for arg in args:
183
+ if isinstance(arg, list):
184
+ ids.extend(arg)
185
+ elif isinstance(arg, int):
186
+ ids.append(arg)
187
+ elif isinstance(arg, tuple):
188
+ ids.extend(list(arg))
189
+
190
+ ids = sorted(set(ids))
191
+ prefer_cuda_flag = prefer_gpu if ids else prefer_cuda_flag
192
+ else:
193
+ ids = None
194
+
195
+ if ids is not None:
196
+ if cuda_available and prefer_cuda_flag:
197
+ valid_ids = [i for i in ids if i < device_count]
198
+ if valid_ids:
199
+ if len(valid_ids) == 1:
200
+ return f"cuda:{valid_ids[0]}"
201
+ else:
202
+ return f"cuda:{','.join(map(str, valid_ids))}"
203
+ else:
204
+ return "cuda:0"
205
+ elif mps_available and prefer_cuda_flag:
206
+ return "mps"
207
+ else:
208
+ return "cpu"
209
+ else:
210
+ if cuda_available and prefer_cuda_flag:
211
+ if device_count == 1:
212
+ return "cuda:0"
213
+ elif device_count > 1:
214
+ return f"cuda:{','.join(map(str, all_ids))}"
215
+ else:
216
+ return "cpu"
217
+ elif mps_available and prefer_cuda_flag:
218
+ return "mps"
219
+ else:
220
+ return "cpu"
221
+
222
+
223
+ def easy_check_is_colab() -> bool:
224
+ """
225
+ Проверить, выполняется ли код в Google Colab
226
+
227
+ Returns:
228
+ True если в Colab
229
+ """
230
+ if platform.machine() == "x86_64" and "Linux" in platform.platform():
231
+ try:
232
+ import google.colab
233
+ module_path: str = google.colab.__file__
234
+ if module_path.startswith("/usr/local/lib/python") and module_path.endswith("/dist-packages/google/colab/__init__.py"):
235
+ return True
236
+ else:
237
+ return False
238
+ except ImportError:
239
+ return False
240
+ else:
241
+ return False
242
+
243
+ zerogpu_is = str2bool(os.getenv('SPACES_ZERO_GPU', 'False'))
244
+ zerogpu_available = False
245
+ if not spaces:
246
+ def hf_spaces_gpu(*args, **kwargs):
247
+ if len(args) == 1 and callable(args[0]):
248
+ return args[0]
249
+ return lambda f: f
250
+ else:
251
+ if zerogpu_is:
252
+ zerogpu_available = True
253
+ hf_spaces_gpu = spaces.GPU
254
+ else:
255
+ def hf_spaces_gpu(*args, **kwargs):
256
+ if len(args) == 1 and callable(args[0]):
257
+ return args[0]
258
+ return lambda f: f
259
+
260
+
261
+ class GradioHelper:
262
+ """Вспомогательный класс для Gradio интерфейса"""
263
+
264
+ def return_list(self, lst: List[Any], none: bool = False, **kwargs) -> gr.update:
265
+ """
266
+ Вернуть обновление для списка
267
+
268
+ Args:
269
+ lst: Список значений
270
+ none: Добавить пустое значение
271
+ **kwargs: Дополнительные аргументы
272
+
273
+ Returns:
274
+ Обновление Gradio
275
+ """
276
+ if lst:
277
+ return gr.update(choices=lst, value=lst[0] if not none else None, **kwargs)
278
+ else:
279
+ return gr.update(choices=[], value=None, **kwargs)
280
+
281
+ def return_audio(self, label: str, path: str) -> gr.update:
282
+ """
283
+ Вернуть обновление для аудио
284
+
285
+ Args:
286
+ label: Метка
287
+ path: Путь к файлу
288
+
289
+ Returns:
290
+ Обновление Gradio
291
+ """
292
+ return gr.update(label=label, value=path)
293
+
294
+ def get_file_size(self, path: Optional[str]) -> str:
295
+ """
296
+ Получить размер файла в человекочитаемом формате
297
+
298
+ Args:
299
+ path: Путь к файлу
300
+
301
+ Returns:
302
+ Строка с размером
303
+ """
304
+ if path:
305
+ if os.path.exists(path):
306
+ size_bytes = os.path.getsize(path)
307
+ else:
308
+ return _i18n("file_not_exists")
309
+ else:
310
+ return ""
311
+
312
+ units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']
313
+
314
+ if size_bytes == 0:
315
+ return "[0 B]"
316
+
317
+ i = 0
318
+ size_float = float(size_bytes)
319
+ while size_float >= 1024 and i < len(units) - 1:
320
+ size_float /= 1024
321
+ i += 1
322
+
323
+ return f"[{size_float:.1f} {units[i]}]" if i > 0 else f"[{int(size_float)} {units[i]}]"
324
+
325
+ def return_audio_with_size(self, *args: Any, **kwargs) -> gr.update:
326
+ """
327
+ Вернуть аудио с размером в метке
328
+
329
+ Args:
330
+ *args: Позиционные аргументы
331
+ **kwargs: Именованные аргументы
332
+
333
+ Returns:
334
+ Обновление Gradio
335
+ """
336
+ if "label" in kwargs and "value" in kwargs:
337
+ kwargs["label"] = f"{self.get_file_size(kwargs['value'])} {kwargs['label']}"
338
+ elif "label" not in kwargs and "value" in kwargs:
339
+ kwargs["label"] = f"{self.get_file_size(kwargs['value'])}"
340
+ return gr.update(**kwargs)
341
+
342
+ def define_audio_with_size(self, *args: Any, **kwargs) -> gr.Audio:
343
+ """
344
+ Создать аудио компонент с размером в метке
345
+
346
+ Args:
347
+ *args: Позиционные аргументы
348
+ **kwargs: Именованные аргументы
349
+
350
+ Returns:
351
+ Компонент Audio
352
+ """
353
+ if "label" in kwargs and "value" in kwargs:
354
+ kwargs["label"] = f"{self.get_file_size(kwargs['value'])} {kwargs['label']}"
355
+ elif "label" not in kwargs and "value" in kwargs:
356
+ kwargs["label"] = f"{self.get_file_size(kwargs['value'])}"
357
+ return gr.Audio(**kwargs)
358
+
359
+ def create_archive_advanced(self, file_list: List[Tuple[str, List[Tuple[str, str]]]], archive_name: str = "archive.zip") -> str:
360
+ """
361
+ Создать ZIP архив из списка файлов
362
+
363
+ Args:
364
+ file_list: Список файлов для архивации
365
+ archive_name: Имя архива
366
+
367
+ Returns:
368
+ Путь к созданному архиву
369
+ """
370
+ try:
371
+ print(_i18n("creating_zip_archive"))
372
+ with zipfile.ZipFile(
373
+ archive_name, "w", zipfile.ZIP_DEFLATED
374
+ ) as zipf:
375
+ successful_files = 0
376
+
377
+ for basename, stems in file_list:
378
+ for stem_name, stem_path in stems:
379
+ try:
380
+ if os.path.exists(stem_path) and os.path.isfile(
381
+ stem_path
382
+ ):
383
+ basename_ = os.path.basename(stem_path)
384
+ zipf.write(stem_path, basename_)
385
+ successful_files += 1
386
+ print(
387
+ _i18n("file_added_to_zip", path=stem_path, name=basename)
388
+ )
389
+ else:
390
+ print(
391
+ _i18n("file_not_found_for_zip", path=stem_path)
392
+ )
393
+
394
+ except Exception as e:
395
+ print(
396
+ _i18n("error_adding_to_zip", path=stem_path, error=str(e))
397
+ )
398
+
399
+ print(_i18n("zip_created", path=archive_name))
400
+ print(_i18n("files_added_count", count=successful_files))
401
+ return os.path.abspath(archive_name)
402
+
403
+ except Exception as e:
404
+ print(_i18n("zip_creation_error", error=str(e)))
405
+ return ""
406
+
407
+ def extract_zip(self, zip_file_path: str, output_dir: Optional[str] = None) -> List[str]:
408
+ """
409
+ Распаковать ZIP архив
410
+
411
+ Args:
412
+ zip_file_path: Путь к ZIP архиву
413
+ output_dir: Директория для распаковки
414
+
415
+ Returns:
416
+ Список распакованных файлов
417
+ """
418
+ if output_dir is None:
419
+ output_dir = os.path.splitext(zip_file_path)[0]
420
+
421
+ os.makedirs(output_dir, exist_ok=True)
422
+
423
+ try:
424
+ with zipfile.ZipFile(zip_file_path, 'r') as zip_ref:
425
+ if zip_ref.testzip() is not None:
426
+ print(_i18n("zip_corrupted_warning"))
427
+
428
+ zip_ref.extractall(output_dir)
429
+
430
+ file_count = len(zip_ref.namelist())
431
+ print(_i18n("zip_extracted", count=file_count, dir=output_dir))
432
+
433
+ except zipfile.BadZipFile:
434
+ print(_i18n("zip_bad_file"))
435
+ return []
436
+ except PermissionError:
437
+ print(_i18n("zip_permission_error"))
438
+ return []
439
+ except Exception as e:
440
+ print(_i18n("zip_unknown_error", error=str(e)))
441
+ return []
442
+ finally:
443
+ input_files: List[str] = []
444
+ for root, dirs, files in os.walk(output_dir):
445
+ for file in files:
446
+ input_files.append(os.path.join(root, file))
447
+ return input_files
448
+
449
+
450
+ if __name__ == "__main__":
451
+ import argparse
452
+ parser = argparse.ArgumentParser(
453
+ description=_i18n("download_audio_cli_description")
454
+ )
455
+
456
+ parser.add_argument("--url", type=str, required=True, help=_i18n("url_help"))
457
+ parser.add_argument("--output_dir", type=str, default=None, help=_i18n("output_dir_help"))
458
+ parser.add_argument("--cookie", type=str, default=None, help=_i18n("cookie_help"))
459
+ parser.add_argument("--output_format", type=str, default="mp3", choices=["mp3", "wav", "flac", "ogg", "opus", "m4a", "aac"], help=_i18n("output_format_help"))
460
+ parser.add_argument("--title", type=str, default=None, help=_i18n("title_help"))
461
+ args = parser.parse_args()
462
+
463
+ from audio import output_formats
464
+ dw_yt_dlp(
465
+ args.url,
466
+ args.output_dir,
467
+ args.cookie,
468
+ args.output_format,
469
+ "320",
470
+ args.title,
471
  )
mvsepless/separator.py CHANGED
The diff for this file is too large to render. See raw diff
 
mvsepless/vbach.py CHANGED
The diff for this file is too large to render. See raw diff