27e03cc56c
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
984 lines
47 KiB
HTML
984 lines
47 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Дорожная карта видеофич — Техническая консультация v1</title>
|
||
<style>
|
||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap');
|
||
|
||
:root {
|
||
--bg: #0f1117;
|
||
--bg-card: #161922;
|
||
--bg-card-hover: #1c2030;
|
||
--border: #2a2f3e;
|
||
--text: #e4e6ed;
|
||
--text-dim: #8b8fa3;
|
||
--text-heading: #f0f2f7;
|
||
--accent: #6c5ce7;
|
||
--accent-light: #a29bfe;
|
||
--accent-bg: rgba(108, 92, 231, 0.1);
|
||
--green: #00cec9;
|
||
--green-bg: rgba(0, 206, 201, 0.1);
|
||
--yellow: #fdcb6e;
|
||
--yellow-bg: rgba(253, 203, 110, 0.1);
|
||
--red: #ff6b6b;
|
||
--red-bg: rgba(255, 107, 107, 0.1);
|
||
--blue: #74b9ff;
|
||
--blue-bg: rgba(116, 185, 255, 0.1);
|
||
--orange: #e17055;
|
||
--orange-bg: rgba(225, 112, 85, 0.1);
|
||
}
|
||
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
|
||
body {
|
||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
||
background: var(--bg);
|
||
color: var(--text);
|
||
line-height: 1.7;
|
||
font-size: 15px;
|
||
-webkit-font-smoothing: antialiased;
|
||
}
|
||
|
||
.container {
|
||
max-width: 960px;
|
||
margin: 0 auto;
|
||
padding: 60px 32px 120px;
|
||
}
|
||
|
||
/* Header */
|
||
.hero {
|
||
text-align: center;
|
||
margin-bottom: 72px;
|
||
padding: 64px 0;
|
||
position: relative;
|
||
}
|
||
.hero::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: -60px;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
width: 600px;
|
||
height: 600px;
|
||
background: radial-gradient(circle, rgba(108,92,231,0.12) 0%, transparent 70%);
|
||
pointer-events: none;
|
||
z-index: 0;
|
||
}
|
||
.hero * { position: relative; z-index: 1; }
|
||
.hero h1 {
|
||
font-size: 2.6rem;
|
||
font-weight: 800;
|
||
color: var(--text-heading);
|
||
letter-spacing: -0.03em;
|
||
margin-bottom: 16px;
|
||
background: linear-gradient(135deg, var(--text-heading) 0%, var(--accent-light) 100%);
|
||
-webkit-background-clip: text;
|
||
-webkit-text-fill-color: transparent;
|
||
background-clip: text;
|
||
}
|
||
.hero .meta {
|
||
color: var(--text-dim);
|
||
font-size: 0.9rem;
|
||
line-height: 1.8;
|
||
}
|
||
.hero .meta strong { color: var(--text); font-weight: 500; }
|
||
|
||
/* Sections */
|
||
h2 {
|
||
font-size: 1.6rem;
|
||
font-weight: 700;
|
||
color: var(--text-heading);
|
||
margin: 64px 0 24px;
|
||
padding-bottom: 12px;
|
||
border-bottom: 2px solid var(--border);
|
||
letter-spacing: -0.02em;
|
||
}
|
||
h3 {
|
||
font-size: 1.15rem;
|
||
font-weight: 600;
|
||
color: var(--accent-light);
|
||
margin: 32px 0 12px;
|
||
}
|
||
|
||
p { margin: 12px 0; color: var(--text); }
|
||
p.dim { color: var(--text-dim); font-size: 0.9rem; }
|
||
strong { font-weight: 600; color: var(--text-heading); }
|
||
em { font-style: italic; color: var(--yellow); }
|
||
|
||
/* Feature cards */
|
||
.feature-card {
|
||
background: var(--bg-card);
|
||
border: 1px solid var(--border);
|
||
border-radius: 16px;
|
||
padding: 36px;
|
||
margin: 24px 0;
|
||
transition: border-color 0.2s;
|
||
}
|
||
.feature-card:hover { border-color: var(--accent); }
|
||
.feature-card h2 {
|
||
margin-top: 0;
|
||
border: none;
|
||
padding: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
}
|
||
.feature-num {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 36px;
|
||
height: 36px;
|
||
border-radius: 10px;
|
||
font-size: 0.9rem;
|
||
font-weight: 700;
|
||
flex-shrink: 0;
|
||
}
|
||
.feature-num.f1 { background: var(--green-bg); color: var(--green); }
|
||
.feature-num.f2 { background: var(--blue-bg); color: var(--blue); }
|
||
.feature-num.f3 { background: var(--red-bg); color: var(--red); }
|
||
.feature-num.f4 { background: var(--orange-bg); color: var(--orange); }
|
||
|
||
/* Tags */
|
||
.tag {
|
||
display: inline-block;
|
||
padding: 3px 10px;
|
||
border-radius: 6px;
|
||
font-size: 0.75rem;
|
||
font-weight: 600;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.05em;
|
||
}
|
||
.tag.easy { background: var(--green-bg); color: var(--green); }
|
||
.tag.medium { background: var(--yellow-bg); color: var(--yellow); }
|
||
.tag.hard { background: var(--red-bg); color: var(--red); }
|
||
.status-badge {
|
||
display: inline-block;
|
||
padding: 4px 12px;
|
||
border-radius: 20px;
|
||
font-size: 0.8rem;
|
||
font-weight: 500;
|
||
background: var(--green-bg);
|
||
color: var(--green);
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
/* Tables */
|
||
table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
margin: 16px 0;
|
||
font-size: 0.88rem;
|
||
}
|
||
thead th {
|
||
background: rgba(108, 92, 231, 0.08);
|
||
color: var(--accent-light);
|
||
font-weight: 600;
|
||
text-align: left;
|
||
padding: 12px 16px;
|
||
border-bottom: 2px solid var(--border);
|
||
font-size: 0.8rem;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.04em;
|
||
}
|
||
tbody td {
|
||
padding: 10px 16px;
|
||
border-bottom: 1px solid var(--border);
|
||
color: var(--text);
|
||
}
|
||
tbody tr:hover { background: var(--bg-card-hover); }
|
||
tbody tr:last-child td { border-bottom: none; }
|
||
.table-wrap {
|
||
background: var(--bg-card);
|
||
border: 1px solid var(--border);
|
||
border-radius: 12px;
|
||
overflow: hidden;
|
||
margin: 16px 0;
|
||
}
|
||
.table-wrap table { margin: 0; }
|
||
|
||
/* Code */
|
||
code {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
background: rgba(108, 92, 231, 0.1);
|
||
color: var(--accent-light);
|
||
padding: 2px 7px;
|
||
border-radius: 5px;
|
||
font-size: 0.85em;
|
||
}
|
||
pre {
|
||
background: var(--bg-card);
|
||
border: 1px solid var(--border);
|
||
border-radius: 12px;
|
||
padding: 20px 24px;
|
||
overflow-x: auto;
|
||
margin: 16px 0;
|
||
font-size: 0.85rem;
|
||
line-height: 1.6;
|
||
}
|
||
pre code {
|
||
background: none;
|
||
padding: 0;
|
||
color: var(--text);
|
||
}
|
||
.keyword { color: var(--accent-light); }
|
||
.type-name { color: var(--green); }
|
||
.string-val { color: var(--yellow); }
|
||
.comment { color: var(--text-dim); }
|
||
|
||
/* Lists */
|
||
ul, ol {
|
||
margin: 12px 0;
|
||
padding-left: 24px;
|
||
}
|
||
li {
|
||
margin: 6px 0;
|
||
color: var(--text);
|
||
}
|
||
li::marker { color: var(--accent-light); }
|
||
|
||
/* Callout */
|
||
.callout {
|
||
border-left: 3px solid;
|
||
padding: 16px 20px;
|
||
margin: 20px 0;
|
||
border-radius: 0 10px 10px 0;
|
||
font-size: 0.93rem;
|
||
}
|
||
.callout.highlight {
|
||
border-color: var(--accent);
|
||
background: var(--accent-bg);
|
||
}
|
||
.callout.warning {
|
||
border-color: var(--yellow);
|
||
background: var(--yellow-bg);
|
||
}
|
||
.callout.danger {
|
||
border-color: var(--red);
|
||
background: var(--red-bg);
|
||
}
|
||
.callout.info {
|
||
border-color: var(--blue);
|
||
background: var(--blue-bg);
|
||
}
|
||
.callout.success {
|
||
border-color: var(--green);
|
||
background: var(--green-bg);
|
||
}
|
||
|
||
/* Overview grid */
|
||
.overview-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 16px;
|
||
margin: 24px 0;
|
||
}
|
||
.overview-item {
|
||
background: var(--bg-card);
|
||
border: 1px solid var(--border);
|
||
border-radius: 12px;
|
||
padding: 24px;
|
||
}
|
||
.overview-item .label {
|
||
font-size: 0.75rem;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.06em;
|
||
color: var(--text-dim);
|
||
margin-bottom: 4px;
|
||
}
|
||
.overview-item .value {
|
||
font-size: 1.5rem;
|
||
font-weight: 700;
|
||
color: var(--text-heading);
|
||
}
|
||
.overview-item .value.accent { color: var(--accent-light); }
|
||
.overview-item .value.green { color: var(--green); }
|
||
.overview-item .value.yellow { color: var(--yellow); }
|
||
|
||
/* Timeline */
|
||
.timeline {
|
||
margin: 24px 0;
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 0.82rem;
|
||
}
|
||
.timeline-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
padding: 8px 0;
|
||
}
|
||
.timeline-label {
|
||
width: 200px;
|
||
text-align: right;
|
||
color: var(--text-dim);
|
||
flex-shrink: 0;
|
||
}
|
||
.timeline-bar {
|
||
height: 28px;
|
||
border-radius: 6px;
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 0 12px;
|
||
font-weight: 500;
|
||
font-size: 0.75rem;
|
||
color: #fff;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
/* Section divider */
|
||
.divider {
|
||
height: 1px;
|
||
background: linear-gradient(to right, transparent, var(--border), transparent);
|
||
margin: 48px 0;
|
||
}
|
||
|
||
/* Risks */
|
||
.risk-item {
|
||
display: flex;
|
||
gap: 12px;
|
||
padding: 12px 0;
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
.risk-item:last-child { border-bottom: none; }
|
||
.risk-icon {
|
||
width: 24px;
|
||
height: 24px;
|
||
border-radius: 6px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 0.75rem;
|
||
flex-shrink: 0;
|
||
margin-top: 2px;
|
||
background: var(--yellow-bg);
|
||
color: var(--yellow);
|
||
}
|
||
|
||
/* MVP comparison */
|
||
.mvp-compare {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 16px;
|
||
margin: 20px 0;
|
||
}
|
||
.mvp-box {
|
||
background: var(--bg-card);
|
||
border: 1px solid var(--border);
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
}
|
||
.mvp-box h4 {
|
||
font-size: 0.85rem;
|
||
font-weight: 600;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.04em;
|
||
margin-bottom: 10px;
|
||
}
|
||
.mvp-box.mvp h4 { color: var(--green); }
|
||
.mvp-box.full h4 { color: var(--accent-light); }
|
||
.mvp-box p { font-size: 0.88rem; margin: 0; }
|
||
|
||
/* Scrollbar */
|
||
::-webkit-scrollbar { width: 6px; height: 6px; }
|
||
::-webkit-scrollbar-track { background: transparent; }
|
||
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
|
||
|
||
/* Print */
|
||
@media print {
|
||
body { background: #fff; color: #1a1a2e; }
|
||
.feature-card, .table-wrap, pre, .callout, .overview-item, .mvp-box {
|
||
background: #f8f9fa;
|
||
border-color: #dee2e6;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 640px) {
|
||
.container { padding: 32px 16px 80px; }
|
||
.hero h1 { font-size: 1.8rem; }
|
||
.overview-grid { grid-template-columns: 1fr; }
|
||
.mvp-compare { grid-template-columns: 1fr; }
|
||
.timeline-label { width: 120px; font-size: 0.7rem; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
|
||
<!-- Hero -->
|
||
<div class="hero">
|
||
<h1>Дорожная карта видеофич</h1>
|
||
<p class="meta">
|
||
Техническая консультация v1<br>
|
||
<strong>22 марта 2026</strong><br><br>
|
||
ML/AI-инженер · Backend-архитектор · Remotion-инженер<br>
|
||
Frontend-архитектор · DevOps-инженер · Инженер по производительности
|
||
</p>
|
||
</div>
|
||
|
||
<!-- Overview -->
|
||
<h2>Общая картина</h2>
|
||
|
||
<div class="overview-grid">
|
||
<div class="overview-item">
|
||
<div class="label">Всего фич</div>
|
||
<div class="value accent">4</div>
|
||
</div>
|
||
<div class="overview-item">
|
||
<div class="label">MVP все фичи</div>
|
||
<div class="value green">26–34 дня</div>
|
||
</div>
|
||
<div class="overview-item">
|
||
<div class="label">Полные версии</div>
|
||
<div class="value yellow">44–65 дней</div>
|
||
</div>
|
||
<div class="overview-item">
|
||
<div class="label">Один разработчик</div>
|
||
<div class="value">6–8 недель</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>#</th>
|
||
<th>Фича</th>
|
||
<th>Сложность</th>
|
||
<th>MVP</th>
|
||
<th>Полная версия</th>
|
||
<th>Доп. инфраструктура</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td><span class="feature-num f1" style="width:28px;height:28px;font-size:0.8rem;">1</span></td>
|
||
<td><strong>Продвинутые шаблоны Remotion</strong></td>
|
||
<td><span class="tag easy">Легко</span></td>
|
||
<td>3–4 дня</td>
|
||
<td>3–4 дня</td>
|
||
<td style="color:var(--green)">Ничего</td>
|
||
</tr>
|
||
<tr>
|
||
<td><span class="feature-num f2" style="width:28px;height:28px;font-size:0.8rem;">2</span></td>
|
||
<td><strong>Детекция вирусных моментов</strong></td>
|
||
<td><span class="tag medium">Средне</span></td>
|
||
<td>5–7 дней</td>
|
||
<td>8–12 дней</td>
|
||
<td>API-ключ для LLM</td>
|
||
</tr>
|
||
<tr>
|
||
<td><span class="feature-num f3" style="width:28px;height:28px;font-size:0.8rem;">3</span></td>
|
||
<td><strong>Авто-монтаж и трекинг лица</strong></td>
|
||
<td><span class="tag hard">Сложно</span></td>
|
||
<td>12–15 дней</td>
|
||
<td>30–45 дней</td>
|
||
<td>Фаза 2: GPU-воркер</td>
|
||
</tr>
|
||
<tr>
|
||
<td><span class="feature-num f4" style="width:28px;height:28px;font-size:0.8rem;">4</span></td>
|
||
<td><strong>Конвертация в Shorts (9:16)</strong></td>
|
||
<td><span class="tag medium">Средне</span></td>
|
||
<td>6–8 дней</td>
|
||
<td>+3–4 дня</td>
|
||
<td style="color:var(--green)">Ничего</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<p class="dim">Реалистичный прогноз для одного разработчика: <strong>6–8 недель</strong> (все MVP) или <strong>3–4 месяца</strong> (полные версии).</p>
|
||
|
||
<div class="divider"></div>
|
||
|
||
<!-- Feature 1 -->
|
||
<div class="feature-card">
|
||
<h2><span class="feature-num f1">1</span> Продвинутые шаблоны Remotion</h2>
|
||
<span class="status-badge">Спецификация и план готовы</span>
|
||
|
||
<p><strong>Что делаем:</strong> Расширяем <code>CaptionStyleSchema</code> четырьмя новыми стилями подсветки слов (<code>pop_in</code>, <code>karaoke</code>, <code>bounce</code>, <code>glow_pulse</code>), двумя переходами (<code>zoom_in</code>, <code>drop_in</code>), тремя полями (<code>word_entrance</code>, <code>highlight_rotation_deg</code>, <code>text_transform</code>). Добавляем два системных пресета: «Shorts» и «Podcast».</p>
|
||
|
||
<p><strong>Где трогаем код:</strong> Расширение схемы в Remotion + бэкенде, логика рендеринга в <code>Captions.tsx</code>, Alembic-миграция для пресетов, контролы в StyleEditor на фронте.</p>
|
||
|
||
<div class="callout success">
|
||
Особый интерес специалистов не требуется — всё спроектировано, новой инфраструктуры нет. Самая безрисковая фича в этом списке.
|
||
</div>
|
||
|
||
<p class="dim">
|
||
Спецификация: <code>docs/superpowers/specs/2026-03-21-advanced-remotion-templates-design.md</code><br>
|
||
План: <code>docs/superpowers/plans/2026-03-21-advanced-remotion-templates.md</code>
|
||
</p>
|
||
</div>
|
||
|
||
<!-- Feature 2 -->
|
||
<div class="feature-card">
|
||
<h2><span class="feature-num f2">2</span> Детекция вирусных моментов</h2>
|
||
|
||
<div class="callout highlight">
|
||
За <strong>$0.005 за видео</strong> мы можем автоматически находить самые цепляющие фрагменты в подкастах и интервью. Пять копеек — и AI выкладывает тебе на блюдце моменты, которые зрители пересылают друг другу.
|
||
</div>
|
||
|
||
<h3>Архитектура</h3>
|
||
<p><strong>LLM API:</strong> Gemini 2.5 Flash — лучшая поддержка русского языка, $0.15/$0.60 за 1М токенов. Альтернатива: GPT-4o-mini. Стоимость анализа одного 30-минутного видео: ~$0.005.</p>
|
||
<p><strong>Аудио-подкрепление:</strong> <code>librosa</code> для кривых RMS-энергии — уточняет границы клипов до естественных пауз, повышает скор для энергичных сегментов. ~20МБ, обработка 30 мин аудио <10 секунд.</p>
|
||
|
||
<h3>Пайплайн</h3>
|
||
<ol>
|
||
<li>Берём транскрипцию из БД</li>
|
||
<li><code>librosa</code> считает огибающую энергии (разрешение 100мс)</li>
|
||
<li>LLM анализирует текст через промпт со structured JSON output</li>
|
||
<li>Постобработка: привязка границ к точкам низкой энергии, расчёт energy-скоров</li>
|
||
<li>Сохраняем клипы в новую таблицу <code>clips</code></li>
|
||
</ol>
|
||
|
||
<h3>Бэкенд</h3>
|
||
<p><strong>Новый модуль:</strong> <code>clips</code> — хранит найденные клипы со связями project / file / job.</p>
|
||
|
||
<p><strong>Модель клипа:</strong></p>
|
||
<pre><code><span class="type-name">Clip</span> {
|
||
project_id: <span class="type-name">UUID</span> (FK projects)
|
||
source_file_id: <span class="type-name">UUID</span> (FK files)
|
||
job_id: <span class="type-name">UUID?</span> (FK jobs)
|
||
title: <span class="type-name">str</span>
|
||
start_ms: <span class="type-name">int</span>
|
||
end_ms: <span class="type-name">int</span>
|
||
score: <span class="type-name">float</span>
|
||
source_type: <span class="string-val">"viral_detected"</span> | <span class="string-val">"user_created"</span> | <span class="string-val">"auto_generated"</span>
|
||
status: <span class="string-val">"pending"</span> | <span class="string-val">"approved"</span> | <span class="string-val">"rejected"</span> | <span class="string-val">"exported"</span>
|
||
meta: <span class="type-name">JSON?</span> <span class="comment">(рассуждения LLM, теги, хэштеги)</span>
|
||
}</code></pre>
|
||
|
||
<p><strong>Новый тип джоба:</strong> <code>VIRAL_DETECT</code> в <code>JobTypeEnum</code>. Актор вызывает LLM API через <code>httpx</code> из Dramatiq-воркера.</p>
|
||
|
||
<h3>Фронтенд</h3>
|
||
<ul>
|
||
<li>Новый <code>ViralClipsStep</code> в визарде проекта</li>
|
||
<li>Список клипов с превьюшками, скорами, кнопками принять/отклонить</li>
|
||
<li>Модалка редактирования клипа с видео-превью</li>
|
||
<li>Новый тип джоба <code>VIRAL_DETECT</code> в обработке нотификаций</li>
|
||
</ul>
|
||
|
||
<h3>Ключевые цифры</h3>
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead><tr><th>Метрика</th><th>Значение</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>Точность (precision)</td><td>50–70%</td></tr>
|
||
<tr><td>Полнота (recall)</td><td>60–80%</td></tr>
|
||
<tr><td>Время обработки</td><td>10–20 секунд</td></tr>
|
||
<tr><td>Стоимость за видео</td><td style="color:var(--green)">~$0.005</td></tr>
|
||
<tr><td>1 000 видео/месяц</td><td style="color:var(--green)">~$5</td></tr>
|
||
<tr><td>Новые зависимости</td><td>~30 МБ</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="callout info">
|
||
10–20 секунд и пять долларов за тысячу видео. Вдумайтесь в эти цифры.
|
||
</div>
|
||
|
||
<h3>Риски</h3>
|
||
<div class="risk-item"><div class="risk-icon">!</div><div><strong>Качество промпт-инжиниринга</strong> определяет ценность фичи — придётся итерировать по фидбеку</div></div>
|
||
<div class="risk-item"><div class="risk-icon">!</div><div><strong>Визуальные моменты</strong> (мимика, физическая комедия) из текста не ловятся — ~20–30% проходят мимо</div></div>
|
||
<div class="risk-item"><div class="risk-icon">!</div><div><strong>Качество транскрипции критично</strong> — Whisper <code>tiny</code> даёт ~25% WER; для вирусной детекции минимум <code>small</code></div></div>
|
||
<div class="risk-item"><div class="risk-icon">!</div><div><strong>LLM галлюцинирует таймстемпы</strong> — обязательно валидировать метки времени</div></div>
|
||
|
||
<h3>MVP vs Полная версия</h3>
|
||
<div class="mvp-compare">
|
||
<div class="mvp-box mvp">
|
||
<h4>MVP (5–7 дней)</h4>
|
||
<p>Только текстовый анализ через LLM, без аудио-энергии. Возвращает клипы со скорами. Пользователь ревьюит и принимает/отклоняет.</p>
|
||
</div>
|
||
<div class="mvp-box full">
|
||
<h4>Полная (8–12 дней)</h4>
|
||
<p>Добавляем librosa-анализ энергии, few-shot примеры из принятых клипов, пакетную обработку, прямой экспорт в 9:16.</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Feature 3 -->
|
||
<div class="feature-card">
|
||
<h2><span class="feature-num f3">3</span> Авто-монтаж и трекинг лица</h2>
|
||
|
||
<div class="callout danger">
|
||
Самая амбициозная фича. Самая сложная. Загружаете подкаст с двумя спикерами — на выходе динамичное вертикальное видео, где камера сама «следит» за говорящим.
|
||
</div>
|
||
|
||
<h3>Архитектура</h3>
|
||
<p><strong>Детекция лиц:</strong> MediaPipe BlazeFace (Apache 2.0, ~2МБ модель, 30–60 FPS на CPU). Сэмплируем на 3 FPS. Зависимость: <code>mediapipe</code> (~30МБ).</p>
|
||
<p><strong>Диаризация спикеров:</strong> pyannote.audio 3.1 (MIT, ~10% DER, self-hosted). CPU: 0.17–0.33x реального времени. GPU: 1–2 мин на 30 мин аудио. Зависимости: <code>pyannote-audio</code> (~200МБ) + <code>torchaudio</code> (~50–80МБ).</p>
|
||
|
||
<p><strong>Маппинг лицо-спикер:</strong></p>
|
||
<ul>
|
||
<li><strong>Фаза 1:</strong> Эвристика по временной корреляции. 70–85% точности для двух спикеров. ~100 строк Python.</li>
|
||
<li><strong>Фаза 2:</strong> TalkNet-ASD — анализ губ + аудио. 92.3% точности. Нужен GPU.</li>
|
||
</ul>
|
||
|
||
<h3>Видео-композитинг (Remotion)</h3>
|
||
<p>Динамический кроп через CSS <code>transform: scale() translate()</code> на <code><Video></code> внутри контейнера с <code>overflow: hidden</code>. GPU-ускоренная браузерная операция — бесплатная по производительности.</p>
|
||
|
||
<h3>Новые Remotion-композиции</h3>
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead><tr><th>Композиция</th><th>Назначение</th><th>Фаза</th></tr></thead>
|
||
<tbody>
|
||
<tr><td><code>CaptionedVideo</code></td><td>Наложение субтитров (существует)</td><td>Текущая</td></tr>
|
||
<tr><td><code>ShortsVideo</code></td><td>Статический кроп + субтитры в 9:16</td><td>Фича 4</td></tr>
|
||
<tr><td><code>AutoEditVideo</code></td><td>Кроп с трекингом лица + монтаж + субтитры</td><td>Фича 3</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<h3>Формат данных кропа</h3>
|
||
<pre><code><span class="keyword">type</span> <span class="type-name">FaceKeyframe</span> = {
|
||
time: <span class="type-name">number</span>; <span class="comment">// секунды</span>
|
||
x: <span class="type-name">number</span>; <span class="comment">// центр лица, 0.0–1.0</span>
|
||
y: <span class="type-name">number</span>; <span class="comment">// центр лица, 0.0–1.0</span>
|
||
width: <span class="type-name">number</span>; <span class="comment">// ширина bbox, 0.0–1.0</span>
|
||
height: <span class="type-name">number</span>; <span class="comment">// высота bbox, 0.0–1.0</span>
|
||
speakerId?: <span class="type-name">string</span>;
|
||
};
|
||
|
||
<span class="keyword">type</span> <span class="type-name">CropTrack</span> = {
|
||
keyframes: <span class="type-name">FaceKeyframe</span>[];
|
||
interpolation: <span class="string-val">"linear"</span> | <span class="string-val">"ease"</span> | <span class="string-val">"smooth"</span>;
|
||
zoom: <span class="type-name">number</span>; <span class="comment">// базовый множитель зума</span>
|
||
safeMargin: <span class="type-name">number</span>; <span class="comment">// отступ вокруг лица (0.1 = 10%)</span>
|
||
};</code></pre>
|
||
|
||
<h3>Бэкенд</h3>
|
||
<p><strong>Новые типы джобов:</strong> <code>FACE_DETECT</code>, <code>SPEAKER_DIARIZE</code>. Результаты хранятся в <code>Job.output_data</code> (JSON).</p>
|
||
<p><strong>Отделение ML-сервиса:</strong></p>
|
||
<ul>
|
||
<li><strong>Фаза 1:</strong> В Dramatiq-воркерах. MediaPipe + pyannote добавляют ~280МБ к образу.</li>
|
||
<li><strong>Фаза 2:</strong> Отдельный контейнер <code>ml-worker</code> на выделенных очередях Dramatiq.</li>
|
||
</ul>
|
||
|
||
<h3>Время обработки (30-мин 1080p видео)</h3>
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead><tr><th>Шаг</th><th>CPU</th><th>GPU</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>Извлечение аудио (FFmpeg)</td><td>10–20 сек</td><td>10–20 сек</td></tr>
|
||
<tr><td>Детекция лиц (MediaPipe, 3 FPS)</td><td>1–2 мин</td><td>10–15 сек</td></tr>
|
||
<tr><td>Диаризация спикеров (pyannote)</td><td style="color:var(--red);font-weight:600">15–30 мин</td><td style="color:var(--green)">1–2 мин</td></tr>
|
||
<tr><td>Маппинг лицо-спикер</td><td>< 1 сек</td><td>< 1 сек</td></tr>
|
||
<tr><td>Рендер Remotion</td><td>10–30 мин</td><td>10–30 мин</td></tr>
|
||
<tr><td><strong>Итого</strong></td><td><strong>35–80 мин</strong></td><td><strong style="color:var(--green)">16–40 мин</strong></td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<h3>Требования к памяти</h3>
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead><tr><th>Конфигурация</th><th>Пиковое потребление RAM</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>Whisper base + pyannote (параллельно)</td><td>8–12 ГБ</td></tr>
|
||
<tr><td>Whisper medium + pyannote (параллельно)</td><td>12–16 ГБ</td></tr>
|
||
<tr><td>Рекомендуемый лимит ML-воркера</td><td style="color:var(--yellow)">16 ГБ, <code>--threads 1</code></td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<h3>Фронтенд</h3>
|
||
<ul>
|
||
<li>Превью трекинга лица: видеоплеер с наложением bounding box через canvas</li>
|
||
<li>Трек спикеров в TimelinePanel</li>
|
||
<li>Контролы: слайдер зума, скорость перехода, выбор спикера</li>
|
||
<li>Переключатель «до/после»</li>
|
||
</ul>
|
||
|
||
<h3>Ключевые цифры</h3>
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead><tr><th>Метрика</th><th>Значение</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>Точность детекции лиц</td><td>~90%</td></tr>
|
||
<tr><td>DER диаризации</td><td>~10%</td></tr>
|
||
<tr><td>Маппинг Фаза 1</td><td>70–85%</td></tr>
|
||
<tr><td>Маппинг Фаза 2 (TalkNet)</td><td style="color:var(--green)">~92%</td></tr>
|
||
<tr><td>Новые зависимости</td><td>~280 МБ</td></tr>
|
||
<tr><td>GPU обязателен?</td><td>Нет для Фазы 1</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<h3>Риски</h3>
|
||
<div class="risk-item"><div class="risk-icon">!</div><div><strong>Маппинг лицо-спикер</strong> — каждое пятое назначение может быть неверным. Нужна ручная корректировка.</div></div>
|
||
<div class="risk-item"><div class="risk-icon">!</div><div><strong>Диаризация на CPU</strong> — бутылочное горлышко. 15–30 мин на 30-мин видео.</div></div>
|
||
<div class="risk-item"><div class="risk-icon">!</div><div><strong>Конфликты PyTorch</strong> между Whisper и pyannote.</div></div>
|
||
<div class="risk-item"><div class="risk-icon">!</div><div><strong>Потеря качества</strong> при кропе 16:9 → 9:16 — остаётся ~31.6% ширины. Минимум 1080p.</div></div>
|
||
<div class="risk-item"><div class="risk-icon">!</div><div><strong>Скачивание моделей</strong> pyannote (~100МБ) требует принятия лицензии HF. Обрабатывать в Dockerfile.</div></div>
|
||
|
||
<h3>MVP vs Полная версия</h3>
|
||
<div class="mvp-compare">
|
||
<div class="mvp-box mvp">
|
||
<h4>MVP (12–15 дней)</h4>
|
||
<p>Детекция лиц. Пользователь выбирает лицо вручную. Статический кроп. Без диаризации. Один спикер.</p>
|
||
</div>
|
||
<div class="mvp-box full">
|
||
<h4>Полная (30–45 дней)</h4>
|
||
<p>Диаризация + маппинг. Динамический кроп за активным спикером. Spring()-переходы. Сплит-скрин. Несколько спикеров.</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Feature 4 -->
|
||
<div class="feature-card">
|
||
<h2><span class="feature-num f4">4</span> Конвертация в вертикальные Shorts (9:16)</h2>
|
||
|
||
<h3>Архитектура</h3>
|
||
<p>Сначала кроп, потом субтитры — всегда. Один проход рендеринга через новую композицию <code>ShortsVideo</code>.</p>
|
||
|
||
<p><strong>Спецификация кропа:</strong></p>
|
||
<pre><code><span class="keyword">type</span> <span class="type-name">CropConfig</span> = {
|
||
mode: <span class="string-val">"static"</span> | <span class="string-val">"keyframe"</span>;
|
||
staticCrop?: { x: <span class="type-name">number</span>; y: <span class="type-name">number</span>; zoom: <span class="type-name">number</span> };
|
||
keyframes?: <span class="type-name">Array</span><{ time: <span class="type-name">number</span>; x: <span class="type-name">number</span>; y: <span class="type-name">number</span>; zoom: <span class="type-name">number</span> }>;
|
||
interpolation?: <span class="string-val">"linear"</span> | <span class="string-val">"ease"</span> | <span class="string-val">"smooth"</span>;
|
||
};</code></pre>
|
||
|
||
<h3>Бэкенд</h3>
|
||
<ul>
|
||
<li><strong>Новый тип джоба:</strong> <code>ASPECT_CONVERT</code></li>
|
||
<li><strong>Новый тип артефакта:</strong> <code>VERTICAL_VIDEO</code></li>
|
||
<li>Функция <code>crop_to_vertical()</code> в <code>media/service.py</code></li>
|
||
</ul>
|
||
|
||
<h3>Фронтенд</h3>
|
||
<ul>
|
||
<li>Превью кропа: перетаскиваемый прямоугольник 9:16 поверх видеоплеера</li>
|
||
<li>Side-by-side: оригинал 16:9 vs обрезанное 9:16</li>
|
||
<li>Интеграция с Фичей 2: кнопка «Конвертировать в Short» на каждом клипе</li>
|
||
<li>Интеграция с Фичей 3: авто-кроп из данных детекции лица</li>
|
||
</ul>
|
||
|
||
<h3>Время обработки</h3>
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead><tr><th>Подход</th><th>30-мин видео</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>FFmpeg кроп (без субтитров)</td><td>12–36 мин</td></tr>
|
||
<tr><td>Remotion кроп + субтитры</td><td>11–45 мин</td></tr>
|
||
<tr><td>FFmpeg с NVENC</td><td style="color:var(--green);font-weight:600">3–5 мин</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<h3>MVP vs Полная версия</h3>
|
||
<div class="mvp-compare">
|
||
<div class="mvp-box mvp">
|
||
<h4>MVP (6–8 дней)</h4>
|
||
<p>Ручной выбор кропа. Перетаскиваемый прямоугольник. <code>ShortsVideo</code> рендерит кроп + субтитры.</p>
|
||
</div>
|
||
<div class="mvp-box full">
|
||
<h4>Полная (+3–4 дня)</h4>
|
||
<p>Авто-кроп из трекинга лица. Конвертация в один клик. Пакетная обработка.</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="divider"></div>
|
||
|
||
<!-- Timeline -->
|
||
<h2>Рекомендуемый порядок разработки</h2>
|
||
|
||
<div class="timeline">
|
||
<div class="timeline-row">
|
||
<div class="timeline-label">Неделя 1–2</div>
|
||
<div class="timeline-bar" style="width:15%;background:var(--green);">Шаблоны</div>
|
||
</div>
|
||
<div class="timeline-row">
|
||
<div class="timeline-label">Неделя 2–4</div>
|
||
<div class="timeline-bar" style="width:30%;background:var(--blue);">Вирусная детекция</div>
|
||
</div>
|
||
<div class="timeline-row">
|
||
<div class="timeline-label">Неделя 4–6</div>
|
||
<div class="timeline-bar" style="width:30%;background:var(--orange);">9:16 кроп MVP</div>
|
||
</div>
|
||
<div class="timeline-row">
|
||
<div class="timeline-label">Неделя 6–14</div>
|
||
<div class="timeline-bar" style="width:80%;background:linear-gradient(90deg, var(--red), #c0392b);">Трекинг лица</div>
|
||
</div>
|
||
<div class="timeline-row">
|
||
<div class="timeline-label">Неделя 14–15</div>
|
||
<div class="timeline-bar" style="width:15%;background:var(--orange);">9:16 апгрейд</div>
|
||
</div>
|
||
</div>
|
||
|
||
<h3>Почему именно так</h3>
|
||
<ol>
|
||
<li><strong>Шаблоны первыми</strong> — готовы к реализации, нулевой риск, моментальная польза</li>
|
||
<li><strong>Вирусная детекция второй</strong> — лучшее соотношение пользы к трудозатратам ($0.005/видео)</li>
|
||
<li><strong>9:16 MVP третьим</strong> — создаёт <code>ShortsVideo</code>, которую расширит Фича 3</li>
|
||
<li><strong>Трекинг лица последним</strong> — самая сложная; к этому моменту спрос уже валидирован</li>
|
||
<li><strong>Апгрейд 9:16</strong> — тривиален, когда трекинг лица уже даёт позиции</li>
|
||
</ol>
|
||
|
||
<div class="divider"></div>
|
||
|
||
<!-- Cost Analysis -->
|
||
<h2>Анализ стоимости</h2>
|
||
|
||
<h3>Стоимость обработки одного видео</h3>
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead><tr><th>Уровень</th><th>Состав</th><th>Вычисления</th><th>LLM API</th><th>Итого</th><th>Время</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>Только CPU</td><td>Всё на CPU</td><td>$0.05</td><td>$0.06</td><td style="color:var(--green);font-weight:700">$0.11</td><td>35–80 мин</td></tr>
|
||
<tr><td>GPU (T4)</td><td>ML на GPU</td><td>$0.11</td><td>$0.06</td><td>$0.17</td><td>16–40 мин</td></tr>
|
||
<tr><td>GPU + NVENC</td><td>Всё на GPU</td><td>$0.13</td><td>$0.06</td><td>$0.19</td><td style="color:var(--green)">10–15 мин</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="callout highlight">
|
||
Одиннадцать центов на CPU. Девятнадцать с GPU. Меньше двадцати центов за полный пайплайн с AI-анализом, трекингом лица и кодированием видео.
|
||
</div>
|
||
|
||
<h3>Месячная стоимость инфраструктуры (100 видео/мес)</h3>
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead><tr><th>Сценарий</th><th>Стоимость</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>Только CPU (текущая инфра)</td><td>~$11 + сервер</td></tr>
|
||
<tr><td>Modal serverless GPU</td><td>~$21/мес</td></tr>
|
||
<tr><td>Spot GPU (g4dn.xlarge)</td><td>~$115/мес</td></tr>
|
||
<tr><td>Постоянный GPU</td><td>~$380/мес</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="callout info">
|
||
<strong>Рекомендация:</strong> Начинаем на CPU. Переходим на Modal serverless GPU, когда время ожидания в очереди превышает 15 минут. При 500+ видео/день — смотрим на spot-инстансы.
|
||
</div>
|
||
|
||
<h3>Предлагаемые тарифы SaaS</h3>
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead><tr><th>Тариф</th><th>Цена</th><th>Ограничения</th><th>Себестоимость</th><th>Маржа</th></tr></thead>
|
||
<tbody>
|
||
<tr><td><strong>Free</strong></td><td>$0</td><td>Видео до 10 мин, низкий приоритет</td><td>~$0.04/видео</td><td style="color:var(--text-dim)">Маркетинг</td></tr>
|
||
<tr><td><strong>Pro</strong></td><td>$15–30/мес</td><td>Видео до 30 мин, GPU ML</td><td>~$0.17 при 50 видео</td><td style="color:var(--green)">60–80%</td></tr>
|
||
<tr><td><strong>Business</strong></td><td>$50–100/мес</td><td>Видео до 60 мин, приоритет, NVENC</td><td>~$0.38/видео</td><td style="color:var(--green)">70–85%</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="divider"></div>
|
||
|
||
<!-- Infrastructure -->
|
||
<h2>Инфраструктурные решения</h2>
|
||
|
||
<h3>Отделение ML-сервиса</h3>
|
||
<div class="mvp-compare">
|
||
<div class="mvp-box mvp">
|
||
<h4>Фаза 1</h4>
|
||
<p>ML в Dramatiq-воркерах. MediaPipe + pyannote добавляют ~280МБ. PyTorch уже установлен через Whisper.</p>
|
||
</div>
|
||
<div class="mvp-box full">
|
||
<h4>Фаза 2</h4>
|
||
<p>Отдельный <code>ml-worker</code> контейнер. Тот же код, другой образ (<code>Dockerfile.ml</code>), другие лимиты ресурсов.</p>
|
||
</div>
|
||
</div>
|
||
|
||
<pre><code>docker-compose up <span class="comment"># По умолчанию: без ML-воркера</span>
|
||
docker-compose --profile ml up <span class="comment"># С ML-воркером</span></code></pre>
|
||
|
||
<div class="callout warning">
|
||
<strong>НЕ строить отдельный HTTP-микросервис.</strong> Dramatiq уже обеспечивает очередь джобов, ретраи, прогресс и отмену. HTTP service discovery — оверхед с нулевой пользой для асинхронных нагрузок.
|
||
</div>
|
||
|
||
<h3>Немедленные оптимизации</h3>
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead><tr><th>Действие</th><th>Эффект</th><th>Трудозатраты</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>PyTorch на CPU-only индекс</td><td style="color:var(--green)">-800МБ образ</td><td>1 час</td></tr>
|
||
<tr><td>Исправить <code>REMOTION_SERVICE_URL</code></td><td>Баг-фикс</td><td>5 мин</td></tr>
|
||
<tr><td>Лимиты ресурсов docker-compose</td><td>Предотвращение каскадных OOM</td><td>30 мин</td></tr>
|
||
<tr><td>Пулы очередей Dramatiq</td><td>Предотвращение голодания воркеров</td><td>2–3 часа</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<p class="dim">Четыре задачи. Суммарно полдня. Экономия: 800МБ, один баг, и страховка от OOM.</p>
|
||
|
||
<div class="divider"></div>
|
||
|
||
<!-- Tech Stack Summary -->
|
||
<h2>Сводка по технологическому стеку</h2>
|
||
|
||
<h3>Новые зависимости</h3>
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead><tr><th>Пакет</th><th>Размер</th><th>Назначение</th><th>Фича</th></tr></thead>
|
||
<tbody>
|
||
<tr><td><code>google-generativeai</code> / <code>openai</code></td><td>~10 МБ</td><td>LLM API клиент</td><td><span class="feature-num f2" style="width:22px;height:22px;font-size:0.7rem;">2</span></td></tr>
|
||
<tr><td><code>librosa</code></td><td>~20 МБ</td><td>Анализ энергии аудио</td><td><span class="feature-num f2" style="width:22px;height:22px;font-size:0.7rem;">2</span></td></tr>
|
||
<tr><td><code>mediapipe</code></td><td>~30 МБ</td><td>Детекция лиц</td><td><span class="feature-num f3" style="width:22px;height:22px;font-size:0.7rem;">3</span></td></tr>
|
||
<tr><td><code>pyannote-audio</code></td><td>~200 МБ</td><td>Диаризация спикеров</td><td><span class="feature-num f3" style="width:22px;height:22px;font-size:0.7rem;">3</span></td></tr>
|
||
<tr><td><code>torchaudio</code></td><td>~50–80 МБ</td><td>Обработка аудио</td><td><span class="feature-num f3" style="width:22px;height:22px;font-size:0.7rem;">3</span></td></tr>
|
||
<tr><td><strong>Итого</strong></td><td style="color:var(--yellow);font-weight:700">~310–340 МБ</td><td></td><td></td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<h3>Новые модули, композиции, типы джобов</h3>
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead><tr><th>Элемент</th><th>Назначение</th><th>Фича</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>Модуль <code>clips</code></td><td>CRUD клипов, ревью</td><td><span class="feature-num f2" style="width:22px;height:22px;font-size:0.7rem;">2</span></td></tr>
|
||
<tr><td>Композиция <code>ShortsVideo</code></td><td>Статический кроп + субтитры 9:16</td><td><span class="feature-num f4" style="width:22px;height:22px;font-size:0.7rem;">4</span></td></tr>
|
||
<tr><td>Композиция <code>AutoEditVideo</code></td><td>Динамический кроп + субтитры</td><td><span class="feature-num f3" style="width:22px;height:22px;font-size:0.7rem;">3</span></td></tr>
|
||
<tr><td>Джоб <code>VIRAL_DETECT</code></td><td>LLM-анализ транскрипции</td><td><span class="feature-num f2" style="width:22px;height:22px;font-size:0.7rem;">2</span></td></tr>
|
||
<tr><td>Джоб <code>ASPECT_CONVERT</code></td><td>9:16 кроп</td><td><span class="feature-num f4" style="width:22px;height:22px;font-size:0.7rem;">4</span></td></tr>
|
||
<tr><td>Джоб <code>FACE_DETECT</code></td><td>Детекция лиц</td><td><span class="feature-num f3" style="width:22px;height:22px;font-size:0.7rem;">3</span></td></tr>
|
||
<tr><td>Джоб <code>SPEAKER_DIARIZE</code></td><td>Диаризация</td><td><span class="feature-num f3" style="width:22px;height:22px;font-size:0.7rem;">3</span></td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="divider"></div>
|
||
|
||
<!-- Cross-cutting Issues -->
|
||
<h2>Сквозные проблемы</h2>
|
||
<p class="dim">Шесть специалистов — шесть взглядов на одну кодовую базу.</p>
|
||
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead><tr><th>Проблема</th><th>Кто</th><th>Приоритет</th><th>Действие</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>PyTorch тащит CUDA (+800МБ)</td><td>DevOps</td><td><span class="tag hard" style="font-size:0.65rem;">Высокий</span></td><td>CPU-only PyTorch индекс</td></tr>
|
||
<tr><td>Воркер упадёт по OOM на ML-джобах</td><td>Performance</td><td><span class="tag hard" style="font-size:0.65rem;">Высокий</span></td><td>Пулы очередей, <code>--threads 1</code></td></tr>
|
||
<tr><td><code>_get_job_status_sync()</code> течёт соединениями</td><td>Performance</td><td><span class="tag hard" style="font-size:0.65rem;">Высокий</span></td><td>Починить до новых акторов</td></tr>
|
||
<tr><td>Нет очистки <code>/tmp</code> при OOM</td><td>Performance</td><td><span class="tag medium" style="font-size:0.65rem;">Средний</span></td><td>Периодическая очистка / cron</td></tr>
|
||
<tr><td><code>tasks/service.py</code> — 1 674 строки</td><td>Backend</td><td><span class="tag medium" style="font-size:0.65rem;">Средний</span></td><td>Декоратор/контекст-менеджер</td></tr>
|
||
<tr><td><code>REMOTION_SERVICE_URL</code> неверный</td><td>DevOps</td><td><span class="tag medium" style="font-size:0.65rem;">Средний</span></td><td>Исправить на <code>http://remotion:3001</code></td></tr>
|
||
<tr><td>Нет лимитов ресурсов Docker</td><td>DevOps</td><td><span class="tag medium" style="font-size:0.65rem;">Средний</span></td><td>Добавить memory/CPU лимиты</td></tr>
|
||
<tr><td>Whisper в ML-сервис</td><td>Backend</td><td><span class="tag easy" style="font-size:0.65rem;">Низкий</span></td><td>Запланировать при Фазе 2</td></tr>
|
||
<tr><td><code>isCurrent</code> в Captions.tsx</td><td>Remotion</td><td><span class="tag easy" style="font-size:0.65rem;">Низкий</span></td><td>Сравнивать по индексу</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="divider"></div>
|
||
|
||
<!-- Specialists -->
|
||
<h2>Отчёты специалистов</h2>
|
||
<p class="dim">Ключевые файлы, которые изучал каждый:</p>
|
||
<ul>
|
||
<li><strong>ML-инженер:</strong> <code>transcription/service.py</code>, <code>tasks/service.py</code>, <code>pyproject.toml</code></li>
|
||
<li><strong>Backend-архитектор:</strong> <code>tasks/service.py</code>, <code>jobs/schemas.py</code>, <code>media/service.py</code>, <code>captions/service.py</code>, <code>docker-compose.yml</code></li>
|
||
<li><strong>Remotion-инженер:</strong> <code>Composition.tsx</code>, <code>Captions.tsx</code>, <code>Root.tsx</code>, <code>useCaptions.ts</code>, все типы</li>
|
||
<li><strong>Frontend-архитектор:</strong> <code>TimelinePanel/</code>, <code>FragmentsStep/</code>, <code>WizardContext.tsx</code>, <code>notifications/</code></li>
|
||
<li><strong>DevOps-инженер:</strong> <code>docker-compose.yml</code>, <code>Dockerfile</code>, <code>pyproject.toml</code>, <code>uv.lock</code></li>
|
||
<li><strong>Инженер по производительности:</strong> <code>tasks/service.py</code>, <code>media/service.py</code>, <code>transcription/service.py</code>, <code>docker-compose.yml</code></li>
|
||
</ul>
|
||
|
||
</div>
|
||
</body>
|
||
</html> |