Как скачать главы видео с YouTube в виде отдельных видеофайлов?

Вопрос или проблема

Новая функция YouTube

YouTube недавно включил возможность добавлять главы в видео на YouTube.

вставьте описание изображения здесь

Это делается просто добавлением временных меток глав (время начала) в описании видео.

вставьте описание изображения здесь

Связанные темы

Используя youtube-dl/yt-dlp, мы можем:

Вопрос

Существует ли одна команда, чтобы скачать главы видео на YouTube в виде отдельных файлов видео без указания времени начала и продолжительности, а просто автоматически извлечь временные метки из .description?

yt-dlp

Нет необходимости в произвольных скриптах. yt-dlp является форком youtube-dl с дополнительными функциями и исправлениями, такими как --split-chapters.

Использование:

yt-dlp --split-chapters  [OPTIONS] URL [URL...]

Установка на Linux или Mac, с помощью pip python3 -m pip install -U "yt-dlp[default]" или brew install yt-dlp.


Связано: Как получить список названий глав из видео на YouTube с помощью youtube-dl.

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

Использование – если вы сохраните это в файл с именем “yt_chapters.py”, затем выполните

yt_chapters.py --split "https://www.youtube.com/watch?v=m5lp8S-YgrQ"

Вы можете передать несколько ссылок на скачивание, и он будет обрабатывать их по очереди.

Запись главы будет добавлена в оригинальный скачанный MP4.

Требования: youtubedl, ffmpeg и MP4Box (GPAC). Если вы не хотите сохранять оригинальный MP4, или если вам не важно, чтобы он содержал главы, вы можете закомментировать этот раздел. youtubedl и ffmpeg должны находиться в вашем системном пути, иначе измените код ниже, чтобы включить полный путь к исполняемым файлам.

#! /usr/bin/env python
# -*- coding: utf-8 -*-

import subprocess, os, argparse, datetime, re, sys

def get_source_name(download_source:str)->str:

    # Получить ожидаемое имя файла для дальнейшей обработки
    result = subprocess.run(["youtube-dl", "--get-filename",
                            "-o", "%(title)s", download_source],
                            capture_output=True, encoding='UTF8')
    return result.stdout.rstrip('\n')

def get_best_mp4_single_file(download_source:str, description_file:str,
                             fix_required:bool=False)->list:

    # Скачать файл в формате mp4 (для использования MP4)
    subprocess.run(["youtube-dl", "--write-description", "-f",
                    "bestvideo[ext=mp4]+bestaudio[ext=m4a]/mp4",
                    "-o", "%(title)s.%(ext)s", link_to_download])
    if fix_required:
        input("Исправьте файл описания, чтобы главы "
              "были на отдельных строках в формате временная метка имя "
              "и затем нажмите Enter, чтобы продолжить...")
    return read_description_file(description_file=description_file)

def get_worst_mp4_single_file(download_source:str, description_file:str,
                              fix_required:bool=False)->list:

    # Скачать файл в формате mp4 (для использования MP4)
    subprocess.run(["youtube-dl", "--write-description", "-f",
                    "worstvideo[ext=mp4]+worstaudio[ext=m4a]/mp4",
                    "-o", "%(title)s.%(ext)s", link_to_download])
    if fix_required:
        input("Исправьте файл описания, чтобы главы "
              "были на отдельных строках в формате временная метка имя "
              "и затем нажмите Enter, чтобы продолжить...")
    return read_description_file(description_file=description_file)

def add_chapters_to_mp4(chapter_file_name:str, name_for_download:str)->None:

    # Использовать MP4Box для объединения файла глав с mp4
    subprocess.run(["MP4Box", "-chap", chapter_file_name, name_for_download])

def read_description_file(description_file:str)->list:

    # Читать файл описания
    # Разделить на время и название главы

    list_of_chapters = []

    with open(description_file, 'r') as f:
        # увеличивать номер главы только на строке главы
        # строки глав начинаются с временной метки
        line_counter = 1
        for line in f:
            result = re.search(r"\(?(\d?[:]?\d+[:]\d+)\)?", line)
            try:
                time_count = datetime.datetime.strptime(result.group(1), '%H:%M:%S')
            except:
                try:
                    time_count = datetime.datetime.strptime(result.group(1), '%M:%S')
                except:
                    continue
            chap_name = line.replace(result.group(0),"").rstrip(' :\n')
            chap_pos = datetime.datetime.strftime(time_count, '%H:%M:%S')
            list_of_chapters.append((str(line_counter).zfill(2), chap_pos, chap_name))
            line_counter += 1

    return list_of_chapters

def write_chapters_file(chapter_file:str, chapter_list:tuple)->None:

            with open(chapter_file, 'w') as fo:
                for current_chapter in chapter_list:
                    fo.write(f'CHAPTER{current_chapter[0]}='
                            f'{current_chapter[1]}\n'
                            f'CHAPTER{current_chapter[0]}NAME='
                            f'{current_chapter[2]}\n')

def split_mp4(chapters:list, download_filename:str, download_name:str)->None:

    current_duration_pretext = subprocess.run(['ffprobe', '-i', download_filename,
                                       '-show_entries', 'format=duration',
                                       '-v', 'quiet'],
                                        capture_output=True, encoding='UTF8')
    current_duration = float(current_duration_pretext.stdout[18:-13])
    m, s = divmod(current_duration, 60)
    h, m = divmod(m, 60)
    current_dur=":".join([str(int(h)),str(int(m)),str(s)])
    for current_index, current_chapter in enumerate(chapters):
        next_index = current_index + 1
        start_time = current_chapter[1]
        try:
            end_time = chapters[next_index][1]
        except:
            end_time = current_dur
        output_name = f'{download_name} - ({current_chapter[2]}).mp4'
        subprocess.run(["ffmpeg", "-ss", start_time, "-to", end_time,
                        "-i", download_filename, "-acodec", "copy",
                        "-vcodec", "copy", output_name])

if __name__ == '__main__':

    parser = argparse.ArgumentParser(description='Скачать видео YouTube с главами')

    parser.add_argument('links', metavar="N", type=str, nargs="+",
                        help='Список ссылок для скачивания')

    parser.add_argument("--split", help="Разделить главы на отдельные файлы",
                        action="store_true")
    parser.add_argument("--test", help="Скачать худшую версию для ускорения тестирования",
                        action="store_true")
    parser.add_argument("--fix", help="Пауза для ручного исправления файла описания",
                        action="store_true")
    args = parser.parse_args()

    for link_to_download in args.links:

        download_name = get_source_name(download_source=link_to_download)
        download_filename = f'{download_name}.mp4'
        description_file = f'{download_name}.description'
        chapter_file = f'{download_name}_chapter.txt'

        if not args.test:
            chapters = get_best_mp4_single_file(download_source=link_to_download,
                                                description_file=description_file,
                                                fix_required=args.fix)
        else:
            chapters = get_worst_mp4_single_file(download_source=link_to_download,
                                                description_file=description_file,
                                                fix_required=args.fix)

        if not chapter_file:
            print("Главы не найдены")
            sys.exit(1)

        write_chapters_file(chapter_file=chapter_file, chapter_list=chapters)

        if args.split:
            split_mp4(chapters=chapters, download_filename=download_filename,
                      download_name=download_name)

        add_chapters_to_mp4(chapter_file_name=chapter_file,
                              name_for_download=download_filename)

        # Удалить текстовые файлы
        os.remove(description_file)
        os.remove(chapter_file)

Используйте эту команду yt-dlp в терминале

Установив yt-dlp, используя $ python3 -m pip install -U yt-dlp согласно документации здесь.

cd в ваш выбранный каталог и выполните команду ниже.

yt-dlp  -f 'best[height=720]' [URL...]  -o '%(title)s/%(title)s.%(ext)s'  --split-chapters  -o "chapter:%(title)s/[%(section_number)s] - %(section_title)s.%(ext)s" 

Не забудьте обновить [URL...] на URL видео (уберите квадратные скобки). Вам также может понадобиться установить FFmpeg.

Ответ или решение

Чтобы загрузить главы видео с YouTube в виде отдельных видеофайлов, вы можете использовать программу yt-dlp, являющуюся форком youtube-dl с дополнительными функциональными возможностями, включая опцию --split-chapters. В этом руководстве я подробно опишу, как это сделать, обеспечив удобочитаемость и полноту ответа.

Установка необходимые инструментов

Перед началом работы убедитесь, что у вас установлены следующие программы:

  1. Python: требуется для установки yt-dlp через pip.
  2. yt-dlp: выполните следующую команду в терминале:
    python3 -m pip install -U yt-dlp
  3. FFmpeg: необходим для обработки видеофайлов. Установите его следующими способами:
    • На Linux (Debian/Ubuntu):
      sudo apt update
      sudo apt install ffmpeg
    • На macOS:
      brew install ffmpeg

Загрузка глав видео

Теперь, когда ваши инструменты установлены, перейдем к процессу загрузки глав видео. Используйте следующую команду в терминале:

yt-dlp -f 'best[height=720]' [URL...] --split-chapters -o "chapter:%(title)s/[%(section_number)s] - %(section_title)s.%(ext)s"

Где:

  • [URL…]: замените на ссылку на видео YouTube, главы которого вы хотите загрузить.
  • Параметр -f 'best[height=720]' указывает на загрузку лучшего видео с высотой 720 пикселей.
  • Опция --split-chapters обеспечивает разделение глав на отдельные файлы.
  • Параметр -o "chapter:%(title)s/[%(section_number)s] - %(section_title)s.%(ext)s" задает формат именования файлов для каждой главы.

Пример использования

Если вы хотите загрузить видео с данной ссылкой:

https://www.youtube.com/watch?v=b1Fo_M_tj6w

Выполните команду:

yt-dlp -f 'best[height=720]' https://www.youtube.com/watch?v=b1Fo_M_tj6w --split-chapters -o "chapter:%(title)s/[%(section_number)s] - %(section_title)s.%(ext)s"

Заключение

Теперь вы знаете, как загружать главы видео с YouTube как отдельные видеофайлы с помощью yt-dlp. Этот процесс универсален и может применяться ко всем видео, которые имеют разделы. Надеюсь, это руководство было полезным. Если возникнут дополнительные вопросы, не стесняйтесь спрашивать!

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

Капча загружается...