|
|
| Строка 9: |
Строка 9: |
| Есть студии которые заглушают дорожку только в местах где идет пере озвучка. Я не разу не встречал случаев когда кто то бы специально заглушал оригинальную озвучку чтобы не снижать громкость остальных звуков. Всё же живём в век технологий. | | Есть студии которые заглушают дорожку только в местах где идет пере озвучка. Я не разу не встречал случаев когда кто то бы специально заглушал оригинальную озвучку чтобы не снижать громкость остальных звуков. Всё же живём в век технологий. |
|
| |
|
| Для это существует множество методов, но самый популярный и мощный, основании конечно же на ИИ, это | | Для это существует множество методов, но самый популярный и мощный, основании конечно же на ИИ, это |
| | |
| = YT-DLP Downloader Script =
| |
| Простой и мощный Python-скрипт для автоматизации скачивания видео с помощью утилиты [[yt-dlp]]. Скрипт автоматически определяет наилучший доступный HLS-поток и загружает его в несколько потоков для максимальной скорости.
| |
| | |
| == Назначение ==
| |
| Скрипт предназначен для упрощения процесса загрузки видео. Его ключевые возможности:
| |
| | |
| * '''Автоматический выбор качества:''' Самостоятельно находит HLS-поток с самым высоким разрешением.
| |
| * '''Многопоточная загрузка:''' Ускоряет скачивание, используя несколько потоков одновременно.
| |
| * '''Пакетная обработка:''' Может обрабатывать список ссылок из файла <code>links.txt</code>.
| |
| * '''Гибкая настройка:''' Позволяет через аргументы командной строки указать папку для сохранения, количество потоков и режим постобработки.
| |
| * '''Опциональная постобработка:''' По умолчанию отключен вызов <code>ffmpeg</code> для исправления контейнера, что ускоряет процесс, но его можно включить при необходимости.
| |
| | |
| == Установка ==
| |
| Перед использованием скрипта убедитесь, что в вашей системе установлены необходимые компоненты.
| |
| | |
| # '''[[Python]]''': Требуется Python версии 3.6 или выше.
| |
| # '''[[yt-dlp]]''': Основная утилита для скачивания. Установить можно через pip:
| |
| # <pre>pip install yt-dlp</pre>
| |
| # '''[[FFmpeg]]''' (Опционально): Необходим, если вы планируете использовать функцию исправления видеофайлов (аргумент <code>--use-fixup</code>).
| |
| # '''Сам скрипт:''' Сохраните исходный код, представленный ниже, в файл с именем <code>downloader.py</code>.
| |
| | |
| == Использование ==
| |
| Скрипт запускается из командной строки. Основной способ работы — через аргументы.
| |
| | |
| === Источник ссылок ===
| |
| Скрипт сначала ищет файл <code>links.txt</code> в той же директории.
| |
| | |
| * '''Если файл найден и не пуст''', скрипт обработает все ссылки из него по очереди. Каждая ссылка должна быть на новой строке.
| |
| * '''Если файл пуст или отсутствует''', скрипт запросит ввести одну ссылку для скачивания в консоли.
| |
| | |
| === Аргументы командной строки ===
| |
| Вы можете настроить работу скрипта с помощью следующих аргументов:
| |
| | |
| * <code>-h, --help</code>: Показать справочное сообщение и выйти.
| |
| * <code>-p PATH, --path PATH</code>: Указать путь к папке для сохранения файлов. Если не указан, файлы сохраняются рядом со скриптом.
| |
| * <code>-t THREADS, --threads THREADS</code>: Задать количество потоков для скачивания. По умолчанию: 6.
| |
| * <code>--use-fixup</code>: Включить постобработку видео с помощью <code>ffmpeg</code>. По умолчанию отключена.
| |
| | |
| === Примеры команд ===
| |
| | |
| * '''Простой запуск (6 потоков, сохранение в текущую папку, без постобработки):'''
| |
| <pre>python downloader.py</pre>
| |
| | |
| * '''Скачать в 12 потоков в указанную папку:'''
| |
| <pre>python downloader.py -t 12 -p "D:\Видео\Новинки"</pre>
| |
| | |
| * '''Скачать с включенной постобработкой <code>ffmpeg</code>:'''
| |
| <pre>python downloader.py --use-fixup</pre>
| |
| == Исходный код ==
| |
| | |
| {| <div class="mw-collapsible mw-collapsed">
| |
| '''Нажмите, чтобы развернуть или свернуть исходный код скрипта'''
| |
| <div class="mw-collapsible-content">
| |
| <syntaxhighlight lang="python">
| |
| import subprocess
| |
| import os
| |
| import re
| |
| import argparse
| |
| | |
| # --- НАСТРОЙКИ ---
| |
| LINKS_FILE = "links.txt" # Имя файла со ссылками
| |
| # -----------------
| |
| | |
| def get_best_format(url: str) -> str | None:
| |
| """
| |
| Получает список форматов, находит последний ID типа 'hls-<число>' и возвращает его.
| |
| """
| |
| print(f"\n🔎 Получаю список форматов для видео: {url}")
| |
| try:
| |
| command = ["yt-dlp", "-F", url]
| |
| result = subprocess.run(command, capture_output=True, text=True, check=True, encoding='utf-8')
| |
|
| |
| lines = result.stdout.split('\n')
| |
| hls_format_ids = []
| |
| format_regex = re.compile(r"^(?P<id>hls-\d+)\s")
| |
| | |
| for line in lines:
| |
| match = format_regex.match(line.strip())
| |
| if match:
| |
| hls_format_ids.append(match.group('id'))
| |
| | |
| if not hls_format_ids:
| |
| print("❌ Форматы 'hls-<число>' не найдены для этой ссылки.")
| |
| return None
| |
| | |
| best_format_id = hls_format_ids[-1]
| |
| print(f"✅ Найдено лучшее качество (последний в списке 'hls-'): {best_format_id}")
| |
| return best_format_id
| |
| | |
| except FileNotFoundError:
| |
| print("❌ ОШИБКА: `yt-dlp` не найден. Убедитесь, что он установлен и доступен в PATH.")
| |
| return None
| |
| except subprocess.CalledProcessError as e:
| |
| print(f"❌ ОШИБКА: yt-dlp вернул ошибку при получении форматов. Возможно, ссылка неверна.")
| |
| print(e.stderr)
| |
| return None
| |
| except Exception as e:
| |
| print(f"❌ Произошла непредвиденная ошибка: {e}")
| |
| return None
| |
| | |
| def download_video(url: str, format_id: str, threads: int, output_path: str | None, use_fixup: bool):
| |
| """
| |
| Запускает скачивание видео с указанным ID формата, количеством потоков и путем сохранения.
| |
| """
| |
| print(f"🚀 Начинаю загрузку с ID '{format_id}' в {threads} потоков...")
| |
| if use_fixup:
| |
| print("🛠️ Постобработка ffmpeg (fixup) ВКЛЮЧЕНА.")
| |
| else:
| |
| print("🛠️ Постобработка ffmpeg (fixup) ОТКЛЮЧЕНА.")
| |
|
| |
| try:
| |
| command = [
| |
| "yt-dlp",
| |
| "-f", format_id,
| |
| "-N", str(threads),
| |
| url
| |
| ]
| |
|
| |
| if not use_fixup:
| |
| command.extend(["--fixup", "never"])
| |
|
| |
| if output_path:
| |
| if not os.path.exists(output_path):
| |
| print(f"📁 Создаю папку для сохранения: {output_path}")
| |
| os.makedirs(output_path)
| |
| command.extend(["-P", output_path])
| |
| | |
| subprocess.run(command, check=True)
| |
| print("✅ Загрузка успешно завершена!")
| |
| except FileNotFoundError:
| |
| print("❌ ОШИБКА: `yt-dlp` не найден. Убедитесь, что он установлен.")
| |
| except subprocess.CalledProcessError:
| |
| print("❌ ОШИБКА: Произошла ошибка во время скачивания.")
| |
| except Exception as e:
| |
| print(f"❌ Произошла непредвиденная ошибка при скачивании: {e}")
| |
| | |
| def process_url(url: str, threads: int, output_path: str | None, use_fixup: bool):
| |
| """
| |
| Полный процесс обработки одной ссылки: получение формата и скачивание.
| |
| """
| |
| url = url.strip()
| |
| if not url:
| |
| return
| |
| | |
| best_format_id = get_best_format(url)
| |
| if best_format_id:
| |
| download_video(url, best_format_id, threads, output_path, use_fixup)
| |
| | |
| | |
| def main():
| |
| """
| |
| Главная функция скрипта.
| |
| """
| |
| parser = argparse.ArgumentParser(
| |
| description="""
| |
| Скрипт для автоматического скачивания видео с помощью yt-dlp.
| |
| | |
| Основная логика работы:
| |
| 1. Автоматически находит HLS-поток ('hls-<число>') с самым высоким качеством.
| |
| 2. Скачивает видео в несколько потоков для ускорения процесса.
| |
| 3. Позволяет гибко настраивать параметры через аргументы командной строки.
| |
| 4. Источник ссылок: файл 'links.txt' или прямой ввод, если файл пуст.
| |
| """,
| |
| formatter_class=argparse.RawTextHelpFormatter
| |
| )
| |
| parser.add_argument(
| |
| '-p', '--path',
| |
| type=str,
| |
| help='Полный путь к папке для сохранения скачанных файлов.\nПример: "C:\\Users\\User\\Downloads"\nЕсли аргумент не указан, файлы сохраняются в ту же папку,\nгде находится скрипт.'
| |
| )
| |
| parser.add_argument(
| |
| '-t', '--threads',
| |
| type=int,
| |
| default=6,
| |
| help='Количество одновременных потоков для скачивания каждого файла.\nБольше потоков может ускорить загрузку, но увеличивает нагрузку на сеть.\n(По умолчанию: 6)'
| |
| )
| |
| parser.add_argument(
| |
| '--use-fixup',
| |
| action='store_true',
| |
| help='Флаг для принудительного включения постобработки видео с помощью ffmpeg.\nЭто может исправить ошибки в контейнере (например, MPEG-TS в MP4),\nно замедляет процесс. По умолчанию эта функция отключена.'
| |
| )
| |
| args = parser.parse_args()
| |
| | |
| urls_to_download = []
| |
| if os.path.exists(LINKS_FILE) and os.path.getsize(LINKS_FILE) > 0:
| |
| print(f"📖 Найден файл '{LINKS_FILE}'. Читаю ссылки из него.")
| |
| with open(LINKS_FILE, 'r', encoding='utf-8') as f:
| |
| urls_to_download = [line for line in f if line.strip()]
| |
|
| |
| if not urls_to_download:
| |
| print(f"📝 Файл '{LINKS_FILE}' пуст или не найден.")
| |
| user_url = input("➡️ Введите ссылку на видео для скачивания: ")
| |
| if user_url:
| |
| urls_to_download.append(user_url)
| |
| | |
| if not urls_to_download:
| |
| print(" ссылок для скачивания нет. Завершаю работу.")
| |
| return
| |
| | |
| print(f"\nНачинаю обработку {len(urls_to_download)} ссылок...")
| |
| print(f"Параметры: Потоков = {args.threads}, Путь = '{args.path or 'Папка со скриптом'}'")
| |
| print("-" * 30)
| |
|
| |
| for url in urls_to_download:
| |
| process_url(url, threads=args.threads, output_path=args.path, use_fixup=args.use_fixup)
| |
| print("-" * 30)
| |
|
| |
| print("🎉 Все задачи выполнены!")
| |
| | |
| | |
| if __name__ == "__main__":
| |
| main()
| |
| </syntaxhighlight>
| |
| |}
| |
| | |
| = Advanced Video Trimmer =
| |
| Скрипт для быстрой и точной обрезки видеофайлов без перекодирования.
| |
| | |
| == Назначение ==
| |
| '''Advanced Video Trimmer''' — это утилита командной строки на Python, предназначенная для "безболезненной" обрезки видео. Она идеально подходит для удаления рекламных заставок, пустых фрагментов в начале или конце файла и других подобных задач.
| |
| | |
| Ключевые особенности:
| |
| | |
| * '''Обрезка без перекодирования:''' Используется режим <code>-c copy</code> в FFmpeg, что делает процесс практически мгновенным и сохраняет исходное качество видео.
| |
| * '''Точность до ключевого кадра:''' Скрипт автоматически находит ближайшие к заданному времени ключевые кадры (I-frames) для начала и конца обрезки. Это гарантирует отсутствие рассинхронизации аудио и видео.
| |
| * '''Сохранение метаданных:''' В обрезанный файл записывается подробная информация об операции: исходная длительность, запрошенное время обрезки и фактическое смещение. Если файл обрабатывается повторно, создается новая запись в истории изменений.
| |
| * '''Гибкое использование:''' Поддерживается как интерактивный режим (с вопросами пользователю), так и работа через аргументы командной строки для автоматизации.
| |
| | |
| == Установка ==
| |
| Скрипт требует наличия на компьютере '''Python''' и '''FFmpeg'''.
| |
| | |
| # '''Python 3:''' Убедитесь, что у вас установлен Python. Вы можете скачать его с [https://www.python.org/downloads/ официального сайта].
| |
| # '''FFmpeg:''' Утилита FFmpeg должна быть установлена и доступна в системной переменной PATH. Скачать её можно с [https://ffmpeg.org/download.html официального сайта FFmpeg].
| |
| # '''Сохраните скрипт:''' Сохраните исходный код скрипта в файл, например, <code>advanced_trim_video.py</code>. Никаких дополнительных библиотек Python устанавливать не нужно.
| |
| | |
| == Использование ==
| |
| Скрипт можно запускать как в интерактивном режиме, так и с указанием всех параметров через аргументы командной строки.
| |
| | |
| === Интерактивный режим ===
| |
| Это самый простой способ. Просто запустите скрипт без аргументов. Он последовательно задаст вопросы:<syntaxhighlight lang="bash">
| |
| py advanced_trim_video.py
| |
| </syntaxhighlight>Скрипт спросит, сколько секунд отрезать от начала и от конца, после чего обработает все видеофайлы в текущем каталоге и сохранит результат в подпапку <code>out</code>.
| |
| | |
| === Аргументы командной строки ===
| |
| Для автоматизации и более сложного использования можно передавать параметры при запуске.<syntaxhighlight lang="bash">
| |
| py advanced_trim_video.py -s <СТАРТ> -e <КОНЕЦ> -i <ВХОДНОЙ_КАТАЛОГ> -o <ВЫХОДНОЙ_КАТАЛОГ>
| |
| </syntaxhighlight>
| |
| | |
| * <code>-s, --start</code>: Сколько секунд отрезать от '''начала''' видео.
| |
| * <code>-e, --end</code>: Сколько секунд отрезать от '''конца''' видео.
| |
| * <code>-i, --input</code>: Путь к каталогу с исходными файлами (по умолчанию: текущий каталог).
| |
| * <code>-o, --output</code>: Путь для сохранения результатов (по умолчанию: подкаталог <code>out</code>).
| |
| | |
| ==== Примеры ====
| |
| | |
| * Обрезать 15.5 секунд в начале и 5 в конце у видео в текущей папке:*
| |
| <syntaxhighlight lang="bash">
| |
| py advanced_trim_video.py -s 15.5 -e 5
| |
| </syntaxhighlight>
| |
| | |
| * Взять видео из "D:\Movies", обрезать 10 секунд в начале и сохранить в "F:\Trimmed Videos":*
| |
| <syntaxhighlight lang="bash">
| |
| py advanced_trim_video.py -s 10 -i "D:\Movies" -o "F:\Trimmed Videos"
| |
| </syntaxhighlight>
| |
| | |
| === Просмотр метаданных ===
| |
| Чтобы проверить, что информация об обрезке записалась в файл, используйте команду <code>ffprobe</code>:<syntaxhighlight lang="bash">
| |
| ffprobe -v error -show_format "путь/к/выходному/файлу.mp4"
| |
| </syntaxhighlight>В секции <code>[TAG]</code> вы увидите всю историю изменений.
| |
| | |
| == Исходный код ==
| |
| <div class="mw-collapsible mw-collapsed">
| |
| '''Нажмите, чтобы развернуть или свернуть исходный код скрипта'''<div class="mw-collapsible-content"><syntaxhighlight lang="python">
| |
| import os
| |
| import subprocess
| |
| import json
| |
| import shlex
| |
| import time
| |
| import argparse
| |
| | |
| def get_metadata(filepath):
| |
| """Считывает существующие метаданные из файла."""
| |
| try:
| |
| command = [
| |
| "ffprobe", "-v", "error", "-print_format", "json",
| |
| "-show_format", filepath
| |
| ]
| |
| result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True, text=True, encoding='utf-8')
| |
| data = json.loads(result.stdout)
| |
| return data.get('format', {}).get('tags', {})
| |
| except (subprocess.CalledProcessError, json.JSONDecodeError, FileNotFoundError):
| |
| return {}
| |
| | |
| def get_video_info_final(filepath):
| |
| """Мгновенно получает длительность и ключевые кадры, читая индекс пакетов."""
| |
| try:
| |
| probe_command = ["ffprobe", "-v", "error", "-print_format", "json", "-show_format", filepath]
| |
| result = subprocess.run(probe_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True, text=True, encoding='utf-8')
| |
| data = json.loads(result.stdout)
| |
| duration = float(data.get('format', {}).get('duration', 0.0))
| |
| if duration == 0.0: return None, None
| |
| except Exception:
| |
| return None, None
| |
| | |
| try:
| |
| keyframes_command = ["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries", "packet=pts_time,flags", "-of", "csv=p=0", filepath]
| |
| result = subprocess.run(keyframes_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True, text=True, encoding='utf-8')
| |
| keyframes = [float(parts[0]) for line in result.stdout.strip().split('\n') if (parts := line.split(',')) and len(parts) >= 2 and parts[1].strip().startswith('K')]
| |
| return duration, keyframes
| |
| except Exception:
| |
| return None, None
| |
| | |
| def find_keyframe_before(keyframes, target_time):
| |
| """Находит ближайший ключевой кадр до указанного времени."""
| |
| if not keyframes: return 0.0
| |
| best_keyframe = 0.0
| |
| for kf_time in keyframes:
| |
| if kf_time <= target_time:
| |
| best_keyframe = kf_time
| |
| else:
| |
| break
| |
| return best_keyframe
| |
| | |
| def main():
| |
| """Основная функция для запуска скрипта."""
| |
| parser = argparse.ArgumentParser(
| |
| description="""Скрипт для быстрой и точной обрезки видеофайлов без перекодирования.
| |
| Он находит ближайшие ключевые кадры для начала и конца обрезки,
| |
| чтобы избежать рассинхронизации аудио и видео.
| |
| Также записывает подробные метаданные об операции в выходной файл.""",
| |
| formatter_class=argparse.RawTextHelpFormatter
| |
| )
| |
| parser.add_argument('-s', '--start', type=float, help='Сколько секунд отрезать от НАЧАЛА видео.')
| |
| parser.add_argument('-e', '--end', type=float, help='Сколько секунд отрезать от КОНЦА видео.')
| |
| parser.add_argument('-i', '--input', type=str, help='Путь к каталогу с исходными видеофайлами.\nПо умолчанию: текущий каталог.')
| |
| parser.add_argument('-o', '--output', type=str, help='Путь к каталогу для сохранения результатов.\nПо умолчанию: подкаталог "out" в исходном каталоге.')
| |
|
| |
| args = parser.parse_args()
| |
| | |
| # --- Интерактивный ввод, если аргументы не указаны ---
| |
| start_trim = args.start
| |
| if start_trim is None:
| |
| try:
| |
| raw_input = input("▶️ Сколько секунд отрезать от НАЧАЛА? (по умолчанию: 0): ")
| |
| start_trim = float(raw_input) if raw_input else 0.0
| |
| except ValueError:
| |
| print("❗ Некорректный ввод. Используется значение 0.")
| |
| start_trim = 0.0
| |
| | |
| end_trim = args.end
| |
| if end_trim is None:
| |
| try:
| |
| raw_input = input("◀️ Сколько секунд отрезать от КОНЦА? (по умолчанию: 0): ")
| |
| end_trim = float(raw_input) if raw_input else 0.0
| |
| except ValueError:
| |
| print("❗ Некорректный ввод. Используется значение 0.")
| |
| end_trim = 0.0
| |
| | |
| # --- Определение путей ---
| |
| source_dir = args.input or os.getcwd()
| |
| if not os.path.isdir(source_dir):
| |
| print(f"❌ Ошибка: Указанный входной каталог не существует: {source_dir}")
| |
| return
| |
|
| |
| output_dir = args.output or os.path.join(source_dir, "out")
| |
| os.makedirs(output_dir, exist_ok=True)
| |
|
| |
| print(f"\n▶️ Начинаю работу в каталоге: {source_dir}")
| |
| print(f"✂️ Настройки: отрезать ~{start_trim} сек. в начале и ~{end_trim} сек. в конце.")
| |
| print(f"📂 Результаты будут сохранены в: {output_dir}\n")
| |
| | |
| video_extensions = ('.mp4', '.mkv', '.avi', '.mov', '.flv', '.webm', '.wmv', '.mpeg', '.mpg')
| |
| files_in_dir = [f for f in os.listdir(source_dir) if os.path.isfile(os.path.join(source_dir, f))]
| |
| | |
| if not any(f.lower().endswith(video_extensions) for f in files_in_dir):
| |
| print("❌ В текущем каталоге не найдено видеофайлов для обработки.")
| |
| return
| |
| | |
| for filename in files_in_dir:
| |
| if filename.lower().endswith(video_extensions):
| |
| source_path = os.path.join(source_dir, filename)
| |
| output_path = os.path.join(output_dir, filename)
| |
| | |
| print(f"--- 🎬 Обработка файла: {filename} ---")
| |
|
| |
| duration, keyframes = get_video_info_final(source_path)
| |
|
| |
| if duration is None or not keyframes:
| |
| print(f"🔴 Не удалось проанализировать файл или найти ключевые кадры. Пропускаю.\n")
| |
| continue
| |
|
| |
| print(f"🕒 Исходная длительность: {duration:.3f} секунд.")
| |
| | |
| actual_start_time = find_keyframe_before(keyframes, start_trim)
| |
| desired_end_time = duration - end_trim
| |
| actual_end_time = find_keyframe_before(keyframes, desired_end_time)
| |
|
| |
| if actual_start_time >= actual_end_time:
| |
| print(f"🟡 Внимание: Видео слишком короткое для обрезки. Пропускаю.\n")
| |
| continue
| |
|
| |
| new_duration = actual_end_time - actual_start_time
| |
| print(f" ⏱️ Новая длительность: {new_duration:.3f} сек.")
| |
| print(f" Желаемый старт: {start_trim:.3f} сек. -> ✅ Реальный старт (keyframe): {actual_start_time:.3f} сек.")
| |
| print(f" Желаемый конец: {desired_end_time:.3f} сек. -> ✅ Реальный конец (keyframe): {actual_end_time:.3f} сек.")
| |
| | |
| # --- Формирование метаданных ---
| |
| existing_meta = get_metadata(source_path)
| |
| history_index = 1
| |
| while f"trim_history_{history_index}_source_duration" in existing_meta:
| |
| history_index += 1
| |
| | |
| metadata_args = [
| |
| "-metadata", f"comment=Trimmed with advanced_trim_video.py",
| |
| "-metadata", f"trim_history_{history_index}_source_duration={duration:.6f}",
| |
| "-metadata", f"trim_history_{history_index}_start_requested={start_trim:.3f}",
| |
| "-metadata", f"trim_history_{history_index}_start_actual={actual_start_time:.6f}",
| |
| "-metadata", f"trim_history_{history_index}_end_requested={end_trim:.3f}",
| |
| "-metadata", f"trim_history_{history_index}_end_actual={(duration - actual_end_time):.6f}",
| |
| ]
| |
| | |
| command = ["ffmpeg", "-ss", str(actual_start_time), "-i", source_path, "-t", str(new_duration), "-fflags", "+genpts", "-c", "copy"]
| |
|
| |
| if output_path.lower().endswith('.mp4'):
| |
| command.extend(["-movflags", "use_metadata_tags"])
| |
|
| |
| command.extend(metadata_args)
| |
| command.extend(["-y", output_path])
| |
|
| |
| print(f"🚀 Выполняю команду обрезки...")
| |
| try:
| |
| subprocess.run(command, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
| |
| print(f"✅ Успешно! Файл сохранен: {output_path}\n")
| |
| except subprocess.CalledProcessError as e:
| |
| print(f"❗ Произошла ошибка при обработке файла: {e}\n")
| |
| | |
| print("🏁 === Обработка всех видеофайлов завершена! ===")
| |
| | |
| if __name__ == "__main__":
| |
| main()
| |
| </syntaxhighlight></div></div>
| |