Как с помощью Python перевернуть видео

Цифровые медиа, такие как видео, открыли множество возможностей для творчества, когда при обработке видео и аудио файлов можно создавать потрясающие эффекты. Многие программы и инструменты для обработки мультимедиа предлагают функцию реверса.

В этом уроке вы узнаете, как можно перевернуть видео на Python с помощью библиотеки MoviePy.

Основная идея кода, который вы здесь увидите, заключается в том, что мы извлекаем все кадры из видео с помощью настраиваемого параметра fps, а затем монтируем эти кадры в обратном порядке.

Для начала установим необходимые библиотеки:

$ pip install tqdm moviepy numpy

Начнем с импорта наших модулей:

from moviepy.editor import VideoFileClip, ImageSequenceClip
import numpy as np
import os
from datetime import timedelta, datetime
from glob import glob
from tqdm import tqdm
import shutil

Затем, чтобы избежать избыточности, я привожу приведенный ниже код из этого руководства:

# то есть, если видео длительностью 30 секунд, сохраняется с частотой 10 кадров в секунду,
# то всего сохраняется 300 кадров
SAVING_FRAMES_PER_SECOND = 30

def format_timedelta(td):
    """Служебная функция для классного форматирования объектов timedelta (например, 00: 00: 20.05)
     исключая микросекунды и сохраняя миллисекунды"""
    result = str(td)
    try:
        result, ms = result.split(".")
    except ValueError:
        return result + ".00".replace(":", "-")
    ms = int(ms)
    ms = round(ms / 1e4)
    return f"{result}.{ms:02}".replace(":", "-")

def extract_frames(video_file, verbose=1):
    # загрузить видеоклип
    video_clip = VideoFileClip(video_file)
    # создаем папку по названию видео файла
    filename, _ = os.path.splitext(video_file)
    if not os.path.isdir(filename):
        os.mkdir(filename)
    # если SAVING_FRAMES_PER_SECOND больше FPS видео, то установите минимум из них
    saving_frames_per_second = min(video_clip.fps, SAVING_FRAMES_PER_SECOND)
    # если SAVING_FRAMES_PER_SECOND установлен в 0, шаг равен 1/fps, иначе
    step = 1 / video_clip.fps if saving_frames_per_second == 0 else 1 / saving_frames_per_second
    iteration = np.arange(0, video_clip.duration, step)
    if verbose:
        iteration = tqdm(iteration, desc="\nИзвлечен видеокадр")
    # перебираем каждый возможный кадр
    for current_duration in iteration:
        # форматируем имя файла и сохраняем его
        frame_duration_formatted = format_timedelta(timedelta(seconds=current_duration)).replace(":", "-")
        frame_filename = os.path.join(filename, f"frame{frame_duration_formatted}.jpg")
        # сохраняем кадр с текущей длительностью
        video_clip.save_frame(frame_filename, current_duration)
    return filename, video_clip.fps

Вы узнаете много подробностей в записке об извлечению кадров. Однако вкратце, функция extract_frames() принимает путь к видеофайлу в качестве параметра и извлекает кадры с соответствующей продолжительностью в папку, названную по имени исходного видеофайла. Наконец, она возвращает имя этой папки.

Теперь, создадим функцию, которая считывает эти кадры извлечения в обратном порядке и сохраняет их как перевернутое видео:

def reverse_video(frames_path, video_fps, remove_extracted_frames=True):
    frame_files = glob(os.path.join(frames_path, "*"))
    # сортируем по длительности в порядке возростания
    frame_files = sorted(frame_files, key=lambda d: datetime.strptime(d.split("frame")[1], "%H-%M-%S.%f.jpg"), reverse=True)
    # вычисляем FPS, получая минимум между исходным FPS и установленным нами параметром
    saving_frames_per_second = min(video_fps, SAVING_FRAMES_PER_SECOND)
    if saving_frames_per_second == 0:
        # если параметр установлен на 0, автоматически установить его на исходное fps видео
        saving_frames_per_second = video_fps
    print("Сохраненение видео с FPS:", saving_frames_per_second)
    # загружаем в клип кадры последовательности изображений (MoviePy)
    image_sequence_clip = ImageSequenceClip(frame_files, fps=saving_frames_per_second)
    # записываем видеофайл на диск
    output_filename = f"{frames_path}-inverted.mp4"
    image_sequence_clip.write_videofile(output_filename)
    if remove_extracted_frames:
        # если установлено значение True, то удалить папку, содержащую извлеченные кадры
        shutil.rmtree(frames_path)

Функция reverse_video() ожидает в качестве аргумента имя папки с извлеченными предыдущей функцией видеокадрами (фреймами). Используем функцию glob() из модуля glob, чтобы получить все имена файлов с фреймами.

Затем сортируем эти файлы по длительности в порядке убывания. После этого, передаем эти кадры в обратном порядке объекту ImageSequenceClip() из MoviePy и устанавливаем FPS на минимум между SAVING_FRAMES_PER_SECOND, который был использован в процессе извлечения кадров, и исходным FPS видео. Причина в том, что при установке более высокого FPS, чем в исходном видео, результирующее видео будет ускоренным. Затем используем метод write_videofile() для сохранения перевернутого видео в видеофайл на диске.

Если вы установите для параметра remove_extracted_frames значение True (по умолчанию), временная папка, в которой находятся извлеченные кадры, будет удалена вместе с её содержимым.

Наконец, воспользуемся этими функциями для решения нашей задачи:

if __name__ == "__main__":
    import sys
    video_file = sys.argv[1]

    import time
    # замерим время для раскадровки видеоролика
    begtime = time.perf_counter()
    frames_folder_path, video_fps = extract_frames(video_file)
    endtime = time.perf_counter()
    print(f"\nНа раскадровку потребовалось {endtime - begtime} с")

    # замерим время для сборки реверсивного ролика
    begtime = time.perf_counter()
    reverse_video(frames_folder_path, video_fps=video_fps)
    endtime = time.perf_counter()
    print(f"\nНа сборку видео потребовалось {endtime - begtime} с")

Готово, мастер! Попробуем с видео заставки сайта студентов бизнес-информатики:

$ python reverse_video.py is42.mp4

В консоле можно увидеть что-то подобное:

Сообщения при реверсе видео
Сообщения при реверсе видео

И перевернутое видео в файле is42-inverted.mp4 появилось в текущем каталоге!

Заключение

Если ваше видео довольно длинное, обязательно уменьшите FPS (параметр SAVING_FRAMES_PER_SECOND), я ууменьшал его до 10, вы можете увеличить его, если чувствуете задержку в выходном видео, что увеличит размер видео, а также время выполнения программы по извлечению кадров и их загрузке в обратном порядке.

Очевидно, что в выходном видео не будет звука, вы можете использовать AudioClip(), загруженный из аудио (возможно, аудио, извлеченное из исходного видео), и просто установить image_sequence_clip.audio для этого вновь созданного объекта AudioClip(), а затем продолжить тот-же процесс сохранения видео.

Полный код приведен ниже, а видео можете забраь с Yuotube:

from moviepy.editor import VideoFileClip, ImageSequenceClip
import numpy as np
import os
from datetime import timedelta, datetime
from glob import glob
from tqdm import tqdm
import shutil

# то есть, если видео длительностью 30 секунд, сохраняется с частотой 10 кадров в секунду,
# то всего сохраняется 300 кадров
SAVING_FRAMES_PER_SECOND = 30

def format_timedelta(td):
    """Служебная функция для классного форматирования объектов timedelta (например, 00: 00: 20.05)
     исключая микросекунды и сохраняя миллисекунды"""
    result = str(td)
    try:
        result, ms = result.split(".")
    except ValueError:
        return result + ".00".replace(":", "-")
    ms = int(ms)
    ms = round(ms / 1e4)
    return f"{result}.{ms:02}".replace(":", "-")

def extract_frames(video_file, verbose=1):
    # загрузить видеоклип
    video_clip = VideoFileClip(video_file)
    # создаем папку по названию видео файла
    filename, _ = os.path.splitext(video_file)
    if not os.path.isdir(filename):
        os.mkdir(filename)
    # если SAVING_FRAMES_PER_SECOND больше FPS видео, то установите минимум из них
    saving_frames_per_second = min(video_clip.fps, SAVING_FRAMES_PER_SECOND)
    # если SAVING_FRAMES_PER_SECOND установлен в 0, шаг равен 1/fps, иначе
    step = 1 / video_clip.fps if saving_frames_per_second == 0 else 1 / saving_frames_per_second
    iteration = np.arange(0, video_clip.duration, step)
    if verbose:
        iteration = tqdm(iteration, desc="\nИзвлечен видеокадр")
    # перебираем каждый возможный кадр
    for current_duration in iteration:
        # форматируем имя файла и сохраняем его
        frame_duration_formatted = format_timedelta(timedelta(seconds=current_duration)).replace(":", "-")
        frame_filename = os.path.join(filename, f"frame{frame_duration_formatted}.jpg")
        # сохраняем кадр с текущей длительностью
        video_clip.save_frame(frame_filename, current_duration)
    return filename, video_clip.fps

def reverse_video(frames_path, video_fps, remove_extracted_frames=True):
    frame_files = glob(os.path.join(frames_path, "*"))
    # сортируем по длительности в порядке возростания
    frame_files = sorted(frame_files, key=lambda d: datetime.strptime(d.split("frame")[1], "%H-%M-%S.%f.jpg"), reverse=True)
    # вычисляем FPS, получая минимум между исходным FPS и установленным нами параметром
    saving_frames_per_second = min(video_fps, SAVING_FRAMES_PER_SECOND)
    if saving_frames_per_second == 0:
        # если параметр установлен на 0, автоматически установить его на исходное fps видео
        saving_frames_per_second = video_fps
    print("Сохраненение видео с FPS:", saving_frames_per_second)
    # загружаем в клип кадры последовательности изображений (MoviePy)
    image_sequence_clip = ImageSequenceClip(frame_files, fps=saving_frames_per_second)
    # записываем видеофайл на диск
    output_filename = f"{frames_path}-inverted.mp4"
    image_sequence_clip.write_videofile(output_filename)
    if remove_extracted_frames:
        # если установлено значение True, то удалить папку, содержащую извлеченные кадры
        shutil.rmtree(frames_path)

if __name__ == "__main__":
    import sys
    video_file = sys.argv[1]

    import time
    # замерим время для раскадровки видеоролика
    begtime = time.perf_counter()
    frames_folder_path, video_fps = extract_frames(video_file)
    endtime = time.perf_counter()
    print(f"\nНа раскадровку потребовалось {endtime - begtime} с")

    # замерим время для сборки реверсивного ролика
    begtime = time.perf_counter()
    reverse_video(frames_folder_path, video_fps=video_fps)
    endtime = time.perf_counter()
    print(f"\nНа сборку видео потребовалось {endtime - begtime} с")

По мотивам How to Reverse Videos in Python

Print Friendly, PDF & Email

CC BY-NC 4.0 Как с помощью Python перевернуть видео, опубликовано К ВВ, лицензия — Creative Commons Attribution-NonCommercial 4.0 International.


Респект и уважуха

Добавить комментарий