Мои скрипты: различия между версиями
Владимир (обсуждение | вклад) |
Владимир (обсуждение | вклад) мНет описания правки |
||
| Строка 223: | Строка 223: | ||
</syntaxhighlight> | </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> | |||
Версия от 16:13, 19 октября 2025
Тут я буду аккумулировать мои наработки и автоматизации, в данном случае на python.
Автоматизация выравнивания громкости звуковых дорожек.
Что это из себя представляет. Представим, что мы скачали сериала или фильм, с закадровым переводом. Так как закадровый перевод всегда поверх оригинальной дорожки, получается, что оригинальная дорожка, простыми словами становится тише.
Есть студии которые заглушают дорожку только в местах где идет пере озвучка. Я не разу не встречал случаев когда кто то бы специально заглушал оригинальную озвучку чтобы не снижать громкость остальных звуков. Всё же живём в век технологий.
Для это существует множество методов, но самый популярный и мощный, основании конечно же на ИИ, это
YT-DLP Downloader Script
Простой и мощный Python-скрипт для автоматизации скачивания видео с помощью утилиты yt-dlp. Скрипт автоматически определяет наилучший доступный HLS-поток и загружает его в несколько потоков для максимальной скорости.
Назначение
Скрипт предназначен для упрощения процесса загрузки видео. Его ключевые возможности:
- Автоматический выбор качества: Самостоятельно находит HLS-поток с самым высоким разрешением.
- Многопоточная загрузка: Ускоряет скачивание, используя несколько потоков одновременно.
- Пакетная обработка: Может обрабатывать список ссылок из файла
links.txt. - Гибкая настройка: Позволяет через аргументы командной строки указать папку для сохранения, количество потоков и режим постобработки.
- Опциональная постобработка: По умолчанию отключен вызов
ffmpegдля исправления контейнера, что ускоряет процесс, но его можно включить при необходимости.
Установка
Перед использованием скрипта убедитесь, что в вашей системе установлены необходимые компоненты.
- Python: Требуется Python версии 3.6 или выше.
- yt-dlp: Основная утилита для скачивания. Установить можно через pip:
pip install yt-dlp
- FFmpeg (Опционально): Необходим, если вы планируете использовать функцию исправления видеофайлов (аргумент
--use-fixup). - Сам скрипт: Сохраните исходный код, представленный ниже, в файл с именем
downloader.py.
Использование
Скрипт запускается из командной строки. Основной способ работы — через аргументы.
Источник ссылок
Скрипт сначала ищет файл links.txt в той же директории.
- Если файл найден и не пуст, скрипт обработает все ссылки из него по очереди. Каждая ссылка должна быть на новой строке.
- Если файл пуст или отсутствует, скрипт запросит ввести одну ссылку для скачивания в консоли.
Аргументы командной строки
Вы можете настроить работу скрипта с помощью следующих аргументов:
-h, --help: Показать справочное сообщение и выйти.-p PATH, --path PATH: Указать путь к папке для сохранения файлов. Если не указан, файлы сохраняются рядом со скриптом.-t THREADS, --threads THREADS: Задать количество потоков для скачивания. По умолчанию: 6.--use-fixup: Включить постобработку видео с помощьюffmpeg. По умолчанию отключена.
Примеры команд
- Простой запуск (6 потоков, сохранение в текущую папку, без постобработки):
python downloader.py
- Скачать в 12 потоков в указанную папку:
python downloader.py -t 12 -p "D:\Видео\Новинки"
- Скачать с включенной постобработкой
ffmpeg:
python downloader.py --use-fixup
Исходный код
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()
|
Advanced Video Trimmer
Скрипт для быстрой и точной обрезки видеофайлов без перекодирования.
Назначение
Advanced Video Trimmer — это утилита командной строки на Python, предназначенная для "безболезненной" обрезки видео. Она идеально подходит для удаления рекламных заставок, пустых фрагментов в начале или конце файла и других подобных задач.
Ключевые особенности:
- Обрезка без перекодирования: Используется режим
-c copyв FFmpeg, что делает процесс практически мгновенным и сохраняет исходное качество видео. - Точность до ключевого кадра: Скрипт автоматически находит ближайшие к заданному времени ключевые кадры (I-frames) для начала и конца обрезки. Это гарантирует отсутствие рассинхронизации аудио и видео.
- Сохранение метаданных: В обрезанный файл записывается подробная информация об операции: исходная длительность, запрошенное время обрезки и фактическое смещение. Если файл обрабатывается повторно, создается новая запись в истории изменений.
- Гибкое использование: Поддерживается как интерактивный режим (с вопросами пользователю), так и работа через аргументы командной строки для автоматизации.
Установка
Скрипт требует наличия на компьютере Python и FFmpeg.
- Python 3: Убедитесь, что у вас установлен Python. Вы можете скачать его с официального сайта.
- FFmpeg: Утилита FFmpeg должна быть установлена и доступна в системной переменной PATH. Скачать её можно с официального сайта FFmpeg.
- Сохраните скрипт: Сохраните исходный код скрипта в файл, например,
advanced_trim_video.py. Никаких дополнительных библиотек Python устанавливать не нужно.
Использование
Скрипт можно запускать как в интерактивном режиме, так и с указанием всех параметров через аргументы командной строки.
Интерактивный режим
Это самый простой способ. Просто запустите скрипт без аргументов. Он последовательно задаст вопросы:
py advanced_trim_video.py
Скрипт спросит, сколько секунд отрезать от начала и от конца, после чего обработает все видеофайлы в текущем каталоге и сохранит результат в подпапку out.
Аргументы командной строки
Для автоматизации и более сложного использования можно передавать параметры при запуске.
py advanced_trim_video.py -s <СТАРТ> -e <КОНЕЦ> -i <ВХОДНОЙ_КАТАЛОГ> -o <ВЫХОДНОЙ_КАТАЛОГ>
-s, --start: Сколько секунд отрезать от начала видео.-e, --end: Сколько секунд отрезать от конца видео.-i, --input: Путь к каталогу с исходными файлами (по умолчанию: текущий каталог).-o, --output: Путь для сохранения результатов (по умолчанию: подкаталогout).
Примеры
- Обрезать 15.5 секунд в начале и 5 в конце у видео в текущей папке:*
py advanced_trim_video.py -s 15.5 -e 5
- Взять видео из "D:\Movies", обрезать 10 секунд в начале и сохранить в "F:\Trimmed Videos":*
py advanced_trim_video.py -s 10 -i "D:\Movies" -o "F:\Trimmed Videos"
Просмотр метаданных
Чтобы проверить, что информация об обрезке записалась в файл, используйте команду ffprobe:
ffprobe -v error -show_format "путь/к/выходному/файлу.mp4"
В секции [TAG] вы увидите всю историю изменений.
Исходный код
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()