Автоматическая ферма видеоконтента на основе Sora 2. chatgpt.. chatgpt. github.. chatgpt. github. python.. chatgpt. github. python. sora 2.. chatgpt. github. python. sora 2. tiktok.. chatgpt. github. python. sora 2. tiktok. youtube.. chatgpt. github. python. sora 2. tiktok. youtube. автоматизация.. chatgpt. github. python. sora 2. tiktok. youtube. автоматизация. боты.. chatgpt. github. python. sora 2. tiktok. youtube. автоматизация. боты. ИИ.. chatgpt. github. python. sora 2. tiktok. youtube. автоматизация. боты. ИИ. нейросети.. chatgpt. github. python. sora 2. tiktok. youtube. автоматизация. боты. ИИ. нейросети. Софт.. chatgpt. github. python. sora 2. tiktok. youtube. автоматизация. боты. ИИ. нейросети. Софт. социальные сети.. chatgpt. github. python. sora 2. tiktok. youtube. автоматизация. боты. ИИ. нейросети. Софт. социальные сети. соцсети.. chatgpt. github. python. sora 2. tiktok. youtube. автоматизация. боты. ИИ. нейросети. Софт. социальные сети. соцсети. Управление медиа.

Недавно OpenAi выпустила новую модель Sora 2, которая взорвала интернет благодаря бесплатному доступу и большим лимитом на генерации. Каждый день пользователю доступна генерация 30 видеороликов длительностью по 10 секунд или 15 видеороликов по 15 секунд.

Это привело меня к мысли, что на её основе можно начать раскрутку соцсетей органическим трафиком. Но как выделиться среди других?

Меняем Watermark

Первое, что меня озадачило – это наличие во всех роликах вотермарки Sora. Её наличие отпугивает потенциальных зрителей, демонстрируя низкое качество и потоковость контента.

Пример вотермарки

Пример вотермарки

На Github уже присутствовали решения, скрывающие вотермарку, но я решил написать своё. Для начала я просто написал сравнение по картинке с поиском зоны вотермарки с её последующим перекрытием.

def detect_watermark_zone(frame, watermark_template, threshold=0.3):
    result = cv2.matchTemplate(frame, watermark_template, cv2.TM_CCOEFF_NORMED)
    _, max_val, _, max_loc = cv2.minMaxLoc(result)
    if max_val >= threshold:
        h, w = watermark_template.shape[:2]
        return (*max_loc, w, h)
    return None

К обнаруженной зоне применялось две функции – blur + overlay. В результате вотермарка соры менялась на вотермарку моего канала.

Замененная вотермарка

Замененная вотермарка

В процессе тестирования на разных видеороликах выяснилось, что Sora использует 6 разных размеров вотермарки. Для эффективного обнаружения потребовалось добавить в программу все 6 примеров, а также функцию автоматического определения типа вотермарки.

все типы вотермарок
все типы вотермарок
def detect_best_watermark_type(input_path):
    cap = cv2.VideoCapture(input_path)
    total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
    sample_count = min(15, total_frames)  # анализируем первые 15 кадров
    scores = {t: [] for t in W_TYPES}

    for i in range(sample_count):
        ret, frame = cap.read()
        if not ret:
            break
        for t, path in W_TYPES.items():
            tmpl = cv2.imread(path)
            result = cv2.matchTemplate(frame, tmpl, cv2.TM_CCOEFF_NORMED)
            _, max_val, _, _ = cv2.minMaxLoc(result)
            scores[t].append(max_val)

    cap.release()
    avg_scores = {t: np.mean(v) if len(v) > 0 else 0 for t, v in scores.items()}
    best_type = max(avg_scores, key=avg_scores.get)

    if all(val < 0.5 for val in avg_scores.values()):
        best_type = 1

    print(f"[AutoDetect] Selected: {best_type} ({W_TYPES[best_type]}), score={avg_scores[best_type]:.3f}")
    return best_type

Итоговая точность получилась довольно высокая, но для большей эффективности были применены дополнительные оптимизации. В ходе исследования было определено, что позиция вотермарки меняется каждые 67 кадров, поэтому в алгоритм был внедрен автоматический пропуск кадров с простановкой предыдущей зоны, при совпадении зоны на пяти кадрах подрят.

def process_video(input_path, watermark_path, overlay_path, output_path):
    cap = cv2.VideoCapture(input_path)
    video_clip = VideoFileClip(input_path)
    frames = []
    audio = video_clip.audio  # может быть None
    fps = cap.get(cv2.CAP_PROP_FPS)
    watermark_template = cv2.imread(watermark_path)
    overlay_img = cv2.imread(overlay_path, cv2.IMREAD_UNCHANGED)  # RGBA
    frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
    positions = {}

    last_pos = (0, 0, 0, 0)
    ident_count = 0
    skip_frames = 0
    for i in range(frame_count):
        ret, frame = cap.read()
        if not ret:
            break
        if skip_frames > 0:
            positions[i + 1] = last_pos
            skip_frames -= 1
            continue
        zone = detect_watermark_zone(frame, watermark_template)
        if not zone:
            positions[i+1] = (0, 0, 0, 0)
            continue
        positions[i+1] = zone
        if last_pos != zone:
            last_pos = zone
            ident_count = 0
        else:
            ident_count += 1
        if ident_count >= 5:
            next_multiple = ((i + 67) // 67) * 67
            skip_frames = min(next_multiple - i, frame_count - i)
            ident_count = 0

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

Наложение субтитров поверх видео

Современная молодежь часто плохо фокусирует внимание и постоянно переключается между разными источниками контента. Для удержания внимания было решено внедрить поверх видео субтитры. В качестве основы использовалась библиотека с Github auto-subtitle . Она автоматически генерирует субтитры из голоса с помощью OpenAi Whisper.

Базовые настройки этой библиотеки не удовлетворяли требованиям, которые обычно используются в Tiktok и Shorts, в результате было применено несколько модификаций. Для субтитров был выбран определенный стиль и размер, распространенные у других создателей контента.

        style = (
            "Fontname=Bahnschrift SemiCondensed,"
            'Fontsize=14,'
            "ScaleX=0.8,"
            "Bold=1,"
            "MarginV=100,"
            "MarginL=50,"
            "MarginR=50,"
            "Outline=1.5,"
            'Shadow=0,'
            'BorderStyle=1'
        )

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

def color_srt(input_file: str, output_file: str):
    colors = ['lime', 'yellow']

    def colorize_text(text):
        words = text.split()
        n = len(words)
        if n <= 1:
            return text
        elif n == 2:
            words[1] = f'<font color="{random.choice(colors)}">{words[1]}</font>'
        elif n == 3:
            # красим центральное слово
            words[1] = f'<font color="{random.choice(colors)}">{words[1]}</font>'
        else:
            indices = list(range(1, n))
            to_color = random.sample(indices, random.randint(1, 2) if n >= 5 else 1)
            prev_i = None
            prev_color = None
            for i in to_color:
                if prev_i is not None and ((i == prev_i + 1) or (i == prev_i - 1)):
                    color = prev_color
                else:
                    color = random.choice([c for c in colors if c != prev_color]) if prev_color else random.choice(
                        colors)
                words[i] = f'<font color="{color}">{words[i]}</font>'
                prev_i = i
                prev_color = color
        return ' '.join(words)

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

Автоматическая ферма видеоконтента на основе Sora 2 - 4

Склейка видео

Алгоритмы youtube shorts и tiktok отдают предпочтения видеороликам продолжительностью 15-30 секунд. Было решено делать видео длительностью 30 секунд на основе двух Sora генераций. Для этого был написан простой модуль на основе Moviepy.

from moviepy import VideoFileClip, concatenate_videoclips


def concat_videos(video_paths, output_path="output.mp4"):
    clips = []
    for path in video_paths:
        clip = VideoFileClip(path)
        if clip.duration > 1:
            clip = clip.subclipped(0, clip.duration)
        clips.append(clip)

    final_clip = concatenate_videoclips(clips)
    final_clip.write_videofile(output_path, codec="libx264", audio_codec="aac")
    for c in clips:
        c.close()
    final_clip.close()

После этого была написана функция, объединяющая эти три операции в одну.

def mounting_video(video_name: str, subt_off=False):
    video_count = VIDEO_COUNT
    w_types = [0, 0, 0, 0]
    fix_only = (False, 3)
    for i in range(1, video_count+1):
        if fix_only[0]:
            i = fix_only[1]
        print(f'{i} видео начало обработку')
        if w_types[i-1] != -1:
            hide_watermark(PATHS.input + f'{i}.mp4', w_types[i-1])
        else:
            shutil.copy(PATHS.input + f'{i}.mp4', f'./input/m_{i}.mp4')
        if not subt_off:
            sys.argv = ['mounting_video.py', PATHS.tmp + f'm_{i}.mp4', '-o', PATHS.tmp]
            main()
        else:
            shutil.copy(PATHS.tmp + f'm_{i}.mp4', PATHS.tmp + f'sm_{i}.mp4')
        if fix_only[0]:
            break

    videos = [PATHS.tmp + f'sm_{i}.mp4' for i in range(1, video_count+1)]
    concat_videos(videos, PATHS.output + video_name)


if __name__ == '__main__':
    timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M")
    filename = f"file_{timestamp}.mp4"
    mounting_video(filename)

Изначально эта функция была рассчитана на склейку четырех видеороликов по 10 секунд, в процессе оптимизаций было решено заменить их количество на 2 по 15 секунд.

Автоматическая генерация сюжетов

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

def get_sora_prompts(pr_text):
    # new_text = prompt_modifications(pr_text)
    new_text = pr_text

    good_providers = [
        OIVSCodeSer0501
    ]
    print('Generate new prompt')
    response = client.chat.completions.create(
        model=g4f.models.default,
        provider=random.choice(good_providers),
        messages=[{"role": "user", "content": new_text}],
        web_search=False
    )
    print(f'Selected provider: {response.provider}')
    answer = response.choices[0].message.content
    start = answer.find('{')
    end = answer.rfind('}') + 1
    if start == -1 or end <= 0:
        return False
    js_text = answer[start:end]
    try:
        data = json.loads(js_text)
    except json.JSONDecodeError:
        return False
    required_keys = {"prompts", "title", "description", "tags"}
    if not required_keys.issubset(data.keys()):
        return False
    if not isinstance(data["prompts"], list) or len(data["prompts"]) != 2:
        return False
    for item in data["prompts"]:
        if not isinstance(item, str):
            return False
    print('Prompt successfully validated')
    save_correct_prompt(data)
    return True

Промпт составлен так, чтобы нейросеть возвращала ответ в следующем формате:

{
  "prompts": [
    "текст первого промпта",
    "текст второго промпта"
  ],
  "title": "кликбейтное название для видео + 3 тега через #",
  "description": "описание видео",
  "tags": "теги через запятую без #"
}

Это позволяет легко распарсить полученный json и провести несколько тестов на валидацию, что нейросеть не прислала в ответ отсебятину. Результат сохраняется в файл для дальнейшего использования.

Автоматическая генерация Sora

После автоматизации промптов напрашивалось автоматическое управление сайтом Sora, чтобы не тратить время на ручной ввод и загрузку результатов. Автоматизация проводилась средствами Python Playwright с аддоном stelth для обхода защиты cloudflare. Код получился довольно громоздким, посмотреть его вы можете на моем github. В программу заложена отказоустойчивость на случай перегрузки сервера или блокировки генерации за нарушение правил.

Написанный модуль использует два аккаунта параллельно через куки файлы, вводит в них промпты и качает полученные видео. Таким образом он делает 4 видеоролика по 15 секунд каждые 5 минут. Возможно дальнейшее масштабирование через увеличение количества аккаунтов.

Объединение модулей

После написания всех модулей я объединил их одной общей функцией, которая генерирует 10 видеороликов на одну кнопку.

import asyncio
import os
import time
from datetime import datetime
import shutil
from constants import PATHS, VIDEO_COUNT, FOLDER_VID_COUNT
from gpt_module import create_new_prompts
from sora_module import create_sora_videos
from mounting_video import mounting_video


def generate_video():
    subt_off = False

    create_new_prompts()
    is_all_videos = False
    while not is_all_videos:
        is_all_videos = asyncio.run(create_sora_videos())
        time.sleep(5)
    cur_date = datetime.now().strftime("%d%m")
    folder_name = cur_date
    folder_path = os.path.join(PATHS.sora_videos, folder_name)
    for i in range(1, FOLDER_VID_COUNT+1):
        result_name = f'{i}_{cur_date}.mp4'
        result_file = os.path.join(PATHS.output, result_name)
        if os.path.exists(result_file):
            print(f'Видео {i} уже создано')
            continue
        videos_path = os.path.join(folder_path, str(i))
        if len(os.listdir(videos_path)) < VIDEO_COUNT:
            print(f'Пропуск видео {i}, нехватает кусков')
            continue
        for filename in os.listdir(videos_path):
            source_file = os.path.join(videos_path, filename)
            destination_file = os.path.join(PATHS.input, filename)
            shutil.copy(source_file, destination_file)
        mounting_video(result_name, subt_off)


if __name__ == '__main__':
    generate_video()

В результате всего одно нажатие приводит к созданию 10 видеороликов длительность по 30 секунд каждый, с неповторяющимися уникальными сюжетами.

Автоматическая ферма видеоконтента на основе Sora 2 - 5

Создание всех видеороликов занимает примерно 30 минут. Основное время уходит на ожидание генераций от Sora2. При желании это можно масштабировать и параллелить, используя больше аккаунтов и проксей.

Автоматическая загрузка на YouTube

Последним шагом для полной автоматизации напрашивается загрузка видеороликов на Youtube и Tiktok. Это позволит запустить систему на удаленном сервере и забыть о ней на пол года, пока аккаунты набирают аудиторию без участия человека.

В автоматизации Youtube есть две методики. Первая это использование официального Api. Есть официальный репозиторий с подобным кодом. У этого метода есть недостаток – все видео попадают в черновик, а попытка публикации через Api приводит к автоматической блокировке видео. Это можно обойти договорившись с техподдержкой, но делать это я не пробовал.

Второй метод это автоматизация средствами Selenium или Playwright, в настоящее время я ещё не написал этот модуль, но он планируется к внедрению в ближайшие недели.

Итоги

В данный момент я веду каналы на Youtube и Tiktok, загружая в них по 10 видеороликов в день. Проект существует с 5 октября, поэтому качественной статистики пока нет. Алгоритм ютуб в среднем рекомендует 3 видео из 10, обеспечивая им примерно тысячу просмотров. Некоторые видео начинают рекомендоваться после задержки в несколько недель, повышая общие просмотры. В тикток рекомендуется почти каждое видео, но средние просмотры держатся в районе 300.

Автоматическая ферма видеоконтента на основе Sora 2 - 6

Проект пока недоступен на github, каждый день в него вносятся сильные корректировки. Также меняется системный промпт для тематики видеороликов, в поисках более качественных тем для повышения просмотров.

Автор: Mashinow

Источник

Rambler's Top100