Недавно 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)
В результате программа начала стабильно накладывать субтитры поверх каждого видеоролика.

Склейка видео
Алгоритмы 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 секунд каждый, с неповторяющимися уникальными сюжетами.

Создание всех видеороликов занимает примерно 30 минут. Основное время уходит на ожидание генераций от Sora2. При желании это можно масштабировать и параллелить, используя больше аккаунтов и проксей.
Автоматическая загрузка на YouTube
Последним шагом для полной автоматизации напрашивается загрузка видеороликов на Youtube и Tiktok. Это позволит запустить систему на удаленном сервере и забыть о ней на пол года, пока аккаунты набирают аудиторию без участия человека.
В автоматизации Youtube есть две методики. Первая это использование официального Api. Есть официальный репозиторий с подобным кодом. У этого метода есть недостаток - все видео попадают в черновик, а попытка публикации через Api приводит к автоматической блокировке видео. Это можно обойти договорившись с техподдержкой, но делать это я не пробовал.
Второй метод это автоматизация средствами Selenium или Playwright, в настоящее время я ещё не написал этот модуль, но он планируется к внедрению в ближайшие недели.
Итоги
В данный момент я веду каналы на Youtube и Tiktok, загружая в них по 10 видеороликов в день. Проект существует с 5 октября, поэтому качественной статистики пока нет. Алгоритм ютуб в среднем рекомендует 3 видео из 10, обеспечивая им примерно тысячу просмотров. Некоторые видео начинают рекомендоваться после задержки в несколько недель, повышая общие просмотры. В тикток рекомендуется почти каждое видео, но средние просмотры держатся в районе 300.
Проект пока недоступен на github, каждый день в него вносятся сильные корректировки. Также меняется системный промпт для тематики видеороликов, в поисках более качественных тем для повышения просмотров.
Комментарии (5)

SvetlanaDen
20.10.2025 08:44Интересно, но разве в пользовательском соглашении Sora не стоит запрет на удаление ватермарки?

Ryav
20.10.2025 08:44У хабровчан появилась уникальная возможность высказать всё, что они думают, тому, кто «загрязняет» интернет.
blacksan
По просмотрам чет кажется не все хорошо, но в целом интересно.