M4ROOT = FX3, ZVe1/, DJI Pocket 4p, DJI-1…4) сама по себе контекст: чем снято и с какого приёмника звук. Сцены собираются симлинками поверх, в проекте. И карту не чистим, пока rclone check --size-only не подтвердит на Drive имя и точный размер каждого файла → 0.2 Media Prep.Стадия scripts/01_prepare/0120_day_ingest/. Считает полностью автономно и сама себя проверяет: ночью рядом никого нет.
.LRF.| Этап | Какой день | Чем описан день | Раскладка | Человек нужен |
|---|---|---|---|---|
0120_day_ingest | выезд, «день из жизни»: много людей, сцены по месту и времени | константами в common_day.py — сделан под YTEVO03, гейты литералами | 01_Source/{NN_Сцена}/CAM-* | сцены и имена голосов — карточка 4 |
0121_studio_day | студия курса: эксперт и продюсер, урок за уроком | файлом day.json — код, дата, карта, камеры, петлички, люди, стыки, ожидаемые числа | 01_Source/{блок}/{тема}/{урок}/CAM-* | только выбор цвета на витрине лутов |
common_day считает пути YTEVO03 на самом импорте, поэтому нормировка звука и чистка расшифровки в 0121 скопированы из s2_audio/s3_transcribe с указанием источника. Новая смена того же спринта = копия day.json с другой датой.cd ~/YTAI/scripts/01_prepare/0121_studio_day python3 index.py # перепись карты → index.json, дальше карту никто не сканирует python3 audio.py --only mic # рабочий звук петличек 16 кГц, уровень по RMS python3 night.py --dry-run # что сделает ночь, ничего не запуская python3 night.py # asr_mic → … → report python3 night.py --from proxy # продолжить с шага
transcript.py решает только проникание — микрофон эксперта слышит продюсера и наоборот.takemap.py сопоставляет дубль с карточкой урока по редким словам (эксперт пересказывает, а не читает с суфлёра), screencast идёт раньше — в имени записи экрана уже стоит номер урока. Поэтому раскладка layout.py — после карты дублей, а не до.board) — до прокси, иначе в комплект уедут старые кубы.00_Setup/{CODE}_day1.html — что снято и чем доказано, {CODE}_day1_doubts.html — спорные дубли с кадром и уликами. Расшифровка внутри — 3.6 Transcription, раскладка — 3.2 Source Structure.clocks.json, а не ошибка измерения: камерный TC на этом дне не был засинхронизирован, хотя 2.2 требует Free Run.*M01.XML (CreationDate со своим смещением зоны, от часов машины не зависит). Длительность = Duration / captureFps.creation_time отстаёт от имени на 0–4 с.creation_time; поле time_reference игнорируется — в нём три несовместимые популяции (верное время / ноль / сдвиг −1:09:32)._orig.wav (32 bit float с передатчика) и _edit.wav. В разбор идёт только _orig: _edit на пяти дублях короче и к картинке не подходит.| Стадия | Что делает | Гейт — из кода, не из прозы | Жёсткость |
|---|---|---|---|
s0_index | ffprobe + Sony *M01.XML + BWF → index.json | видео == 172 и звук == 105 и список проблем пуст | HARD |
s2_audio | рабочий звук 16 кГц из исходников | петличных единиц ровно 105, брака ≤ 2 | HARD |
s3_transcribe | пословная расшифровка {w,s,e} | транскрипт у каждой петлички и слов ≥ 20 000 | HARD |
s4_diarize | pyannote + центроиды голосов | турнов ≥ 95 % петличек | HARD |
s5_voices | сквозная склейка голосов по всему дню | кластеров ≥ 2, конфликтов ≤ 2 % от cannot_link (минимум 1) | SOFT |
s6_wordsync | граф часов по речи | камер ≥ 2, стыков ≥ 3, худший дрейф ≤ 10 с | SOFT |
s7_frames | контактные листы 3×2 | листов ≥ 150 | SOFT |
s8_vlm | «что в кадре», Qwen2.5-VL | описаний ≥ 140, одна фраза ≤ 20 % клипов | SOFT |
s1b_scenes | блоки → сцены, локальной LLM из закрытого списка | сцен ≥ 8 и размещено ≥ 200 | SOFT |
s10_layout | 01_Source/{сцена}/{камера} симлинками | ссылок ≥ 50 | SOFT |
s9_day_html | страница дня + бриф на телефон | ≥ 60 КБ и свежее каждого своего источника | HARD |
s11_proxy | прокси-комплект | готовых ≥ 170 | SOFT |
verify | приёмка по артефактам, а не по журналам | day_check.ok is true | SOFT |
STAGES в run_night.py. Привязка к конкретному дню (пути, устройства, подписи сцен, известные стыки авто-сплита) живёт в common_day.py; про гейты это, к сожалению, неверно — см. карточку 2.s6 ловит развал, а не отсутствие кадра — и это осознанно. Кадровой точности на речевом шаге не бывает: речь даёт 0,6–0,9 с, поэтому порог 10 с. На этом дне все четыре стыка авто-сплита остались «не ок» — 0 из 4, максимум 3,0 с, — и это нормальный результат ночи: до кадра доводит 4.2 Audio Fine Sync (0,6–0,9 с → 1–3 мс). Требовать 0,5 с от речевого синхрона — значит заваливать гейт на исправном дне.python3 run_night.py # весь конвейер, каждая стадия резюмируемая python3 run_night.py --from s6 # с этой стадии и дальше python3 run_night.py --only s5 python3 verify.py # приёмка по артефактам
~/.cache/ytai/{CODE}/: PAUSE (встать между файлами) · STOP (выйти) · ALL_DONE (ставит сам). Смежное: 3.6 Transcription (mlx-whisper и pyannote), 4.1 Multicam Word-Sync (s6 зовёт именно его), 3.4 Proxy, 0.3 Storage (метки носителей).Ночью рядом никого нет. Значит, конвейер обязан сам заметить, что стадия соврала, и сам подняться после любого падения.
Успех = код возврата 0 И гейт пройден. Иначе пауза 60×попытка и повтор, до трёх раз, потом стадия failed и веха в Telegram. Статус done пишется только после гейта — поэтому ему можно верить при рестарте.
supervisor.shТик 600 с, живёт до 144 тиков (сутки) и сам гаснет. Жив ли прогон — если нет, поднимает. Карты нет → ждёт, а не падает. На внутреннем диске меньше 8 ГБ → ставит PAUSE, снимает при 15 ГБ. Вехи в Telegram каждый третий тик.
keepalive.sh под launchdНичего не считает: проверяет, что присмотр жив, и поднимает его. Лежит на внутреннем диске, поэтому переживает передёрг карты, смерть присмотра и перезагрузку. При STOP/PAUSE/ALL_DONE или без карты не делает ничего.
os.path.exists как гейт запрещён: файл может быть обрезком прошлой попытки — существует, весит много и показывает вчерашнюю правду. Эталон формулировки — настоящий гейт страницы дня:
страница годна, только если она СВЕЖЕЕ каждого своего источника размер ≥ 60 000 байт mtime({CODE}_day.html) > mtime(index | audio | voices | clocks | scenes | sheets_meta | scene_desc .json)
run_night.py. Числа 172, 105, 150, 140, 200, 170, 20000 вписаны литералами и настроены на день из 172 клипов. На следующем дне их придётся переписать или вынести в common_day.py. Это известный долг, а не переносимость: «стадия работает на любом дне» — пока неправда..ae.rya.ytevo03-supervisor.plist.disabled), а собственного plist у keepalive.sh нет вообще. Ночь держали слои 1–2 и человек: 34 падения и полтора часа холостого цикла, потому что run_stage верил статусу done раньше, чем спрашивал гейт. Это починено. Но прежде чем писать «у нас работает launchd-job», надо завести plist на keepalive.sh.~/Library/Logs/ytai/). Путь на /Volumes/… убивает job с EX_CONFIG 78 ещё до старта питона: агент не дождётся монтирования тома.s3, s4, s5, s15 под venv с torch, s8 под venv с VLM, s1b под venv с локальной LLM, остальное — системным python3. Запуск обёрнут в caffeinate -dimsu, иначе мак уснёт на середине.YTs/{КАНАЛ}/{КАНАЛ}_voices.json — один человек, один ID на все проекты канала.
| Группа | Кто это | Пример |
|---|---|---|
| A | ядро канала — те, кто ведёт и держит линию | YTEVO-A1 герой линии «день из жизни» |
| B | гости — приходят на конкретную съёмку | YTEVO-B1 священник, гость дня |
| C | дополнительная команда — съёмка, звук, помощь | YTEVO-C1 продюсер и оператор |
voice_cards.json → измерено_на_этом_дне, а не из константы реестра.
| Полоса | Измерено на этом дне | Константа реестра |
|---|---|---|
| «тот же человек», p95 | 0,303 — пар 8 200, медиана 0,187 | D_SAME = 0,326 |
| «разные люди», p05 | 0,334 — пар 226, медиана 0,738 | D_MAYBE = 0,377 |
same_p95, а проверка «имя собрано из далёких частей» берёт diff_p05.dominant_share — это не доля чужой речи. Это доля дорожек, где голос самый громкий, и считается она только по спорным дорожкам — тем, где слышно больше одного диктора: на дорожке, где голос один, доминировать не над кем. У человека без петлички она низкая по определению. Порог отказа: спорных ≥ 4 и dominant_share < 0,5 → имя не предлагается, это похоже на чужую речь, просочившуюся в микрофоны, а не на человека.python3 voice_registry.py --enroll # отпечатки подтверждённых голосов python3 voice_registry.py --enroll --only "Имя" # только этих, флаг повторяемый python3 voice_registry.py --enroll --hold "Имя" # всех, кроме этих python3 voice_registry.py --match # примерить, ничего не пишет python3 voice_registry.py --list
--only / --hold.Вторая половина стадии в run_night не входит и гоняется руками: ночь довозит кластеры, имя ставит человек. Всё ниже — правила, при которых он ставит его один раз и правильно.
PY=~/YTAI/environment/.venv_transcribe/bin/python python3 s12_decisions.py # лист решений decisions.json $PY s15_voice_cards.py # отрывки, кадры, улики — ТОЛЬКО под venv python3 s14_review_page.py # страница приёмки {CODE}_review.html # человек правит на странице → «Сохранить» → файл в ~/Downloads cp ~/Downloads/{CODE}_decisions_*.json {работа}/decisions.json python3 s13_apply_decisions.py --dry-run && python3 s13_apply_decisions.py python3 s16_day_passport.py python3 voice_registry.py --enroll
s13 сам пересобирает s10 → s9 → s12 → s15 → s14, и s12 с s15 в этой цепочке обязательны: переносы дорожек рождают новые кластеры — на этом дне 16 штук, #72…#87, — и лист, собранный до них, показывает голоса, которых уже нет. s15 гонять venv-питоном. Как выглядит результат — страница приёмки дня YTEVO03, слепок v22.Петличка пишет всю комнату: многодикторными оказались 111 отрывков из 135 (82 %). Выбирать кусок по длине и числу слов поэтому мало — человек слышит соседа и опознаёт не того. Отбор идёт по ступеням чистоты, и чистота стоит выше длины: rank = ступень × 10 + счёт, разрыв в 10 не перебить ни счётом, ни штрафами.
| Ступень | Что значит | Отрывков |
|---|---|---|
| solo | во всём файле слышен только он | 31 |
| clean | своя реплика, чужой речи в окне нет вовсе | 97 |
| mostly | его речи ≥ 80 % окна и начинается не с соседа | 7 |
| glued | склейка 2–3 своих реплик одной дорожки | 0 — не понадобилась |
| dirty | ничего из перечисленного — честная пометка, и не первым | 0 |
s15 — читать как порядок величин, а не как константу. Durable здесь — лестница и правило ранга.v03_02.m4a = «второй отрывок третьего голоса»): меняешь правила отбора — бампай PIPE_VER, иначе кэш подсунет к новому отрывку старый звук и кадр от другой минуты дня.extra_options живут «съёмочная группа», «священник в храме», «собеседник», «случайный прохожий», «радио / телевизор / фон», «несколько человек», «кто-то ещё», «не речь (пение, музыка, эхо)», «понять нельзя» — девять подписей, и реестр канала перебивает список дня целиком. Наружу едет подпись, а не id: PICK = "name:кто-то ещё" неотличим от имени. Поэтому s13 не склеивает кластеры по совпадению категории — иначе два разных «кто-то ещё» становятся одним голосом, а категория попадает в список людей дня наравне с героем. На этом дне категориями закрыты 28 кластеров из 86: «не речь» 21, «понять нельзя» 5, «кто-то ещё» 2.voice_far_seen.json), при повторном появлении считается принятой: человек её видел и имя не поменял. Подтверждённые расхождения живут в voice_far_ok.json. Оба файла не удалять — это ответы человека, а не кэш.decisions.json как подтверждённые_расхождения. Необязательное — хвосты у людей с уже снятым отпечатком и собственные переносы дорожек — отдельно, под «Можно пропустить». Счётчик в заголовке пересчитывается на ходу; пусто — «Решать нечего — всё подтверждено». Формулировать действием, а не числом:
«Имя»: #20 (0.5 мин) — это он или другой?
По голосу расходится с основным #1 на 0.467 — сильнее, чем обычно расходятся
разные люди (0.334). Открой #20 ниже, послушай. Это он — жми «проверил».
Не он — поставь на карточке #20 другое имя.
«Расстояние 0.558 при полосе 0.334» не говорит ничего. «Открой #86, послушай, если не он — поставь другое имя» говорит. Расхождение вдвое дальше порога идёт в «Надо решить» даже на коротком куске: грубая ошибка не становится мелкой оттого, что кусок короткий.split_track от голоса оператора: человек его видел и оставил номером. Это ответ, а не забывчивость, — и паспорт обязан показать его именно так.Два выхода: {CODE}_passport.html человеку — один лёгкий файл, открывается с телефона — и {CODE}_day_passport.json следующему этапу. Паспорт ничего не решает и не правит: только собирает уже принятое. Не назван голос — здесь он останется «Голос N», а не получит догадку.
#1 #9 #20 #22 #37 … #91 · отпечаток в реестре есть · слышен на CAM-A_FX3 CAM-B_ZVE1 CAM-C_Pocket TX00…TX03| Человек | Минут (union) | «По файлам» |
|---|---|---|
YTEVO-A1 герой дня | 194,4 | 442,8 |
YTEVO-B1 гость-священник | 74,8 | 215,6 |
YTEVO-A2 ведущая | 56,6 | 127,6 |
YTEVO-C1 продюсер и оператор | 55,0 | 105,3 |
YTEVO-A3 продюсер канала | 0,3 | 0,3 |
confidence и why: поставлено человеком или машиной. Ночь назвала 7 сцен из 14, остальные проставил человек.нерешённое. На этом дне оно пусто, и это проверяемое утверждение, а не бодрость.python3 daymap.py --project "/Volumes/…/YTEVO03_Plechko_day" # → 00_Setup/{CODE}_day_map.xmind python3 project_check.py --project "/Volumes/…/YTEVO03_Plechko_day" # → 00_Setup/{CODE}_project_check.html
daymap.py — майндкарта дня из тех же файлов, что страница дня, руками не набирается. Четыре ветки: ДЕНЬ (сцена → блоки, камеры, голоса, клипы), ЛЮДИ, ВЫПУСКИ (выпуск → главы; помечено «ЧЕРНОВИК» — замысел, положенный на фактические сцены, а не снятый с ката), ЧТО ГДЕ (проект, прокси, транскрипты, цвет, Drive). У студийного дня своя — 0121_studio_day/daymap.py --day, ветки блок → тема → урок.project_check.py — самодостаточна ли папка, числами: вес по папкам и свободное место · симлинки (битые, наружу проекта; битые .LRF — не потеря, их заменяет прокси) · медиа файлами и ссылками в 01_Source · прокси-комплект (таймкод, дорожки, битность, разрешение, fps, цветовые теги) · проявка · валидатор ингеста · есть ли карта .xmind. Параметры прокси снимаются через 16_proxy/1601_build/contract.py, своей копии нет.level=disabled у alimiter обязателен. По умолчанию лимитер сам подтягивает выход в потолок — и тот запас, ради которого он ставился, исчезает.-0.0 в Python ложно. (r.get('max_db') or -99) превращает честный ноль в −99 и прячет ровно те файлы, которые ищешь..LRF у Pocket в разборе не участвует. Звук там есть, но выигрыш 1,5 минуты за ночь, а дорожка на 21 мс короче оригинала: на пословных таймкодах это риск без выгоды.verbose=False у mlx_whisper рисует прогресс-бар. Молчит только None — иначе лог ночи вырастает до гигабайтов.MEDIAPRO.XML врёт про камеру. Карта ZV-E1 однажды побывала в FX3 и до сих пор объявляет ILME-FX3A. Модель брать только из посайдкарного modelName → 2.1.2 ZV-E1.Duration / captureFps, не через tcFps: иначе 50p-клипы удваиваются.creation_time: у Pocket контейнерное время отстаёт от имени на 0–4 с, и честное решение провалило бы гейт на ровном месте.GROUPS — встроенный массив bash (идентификаторы групп пользователя). Присваивание молча игнорируется, цикл идёт по 250 395 398…. Имя занято, брать другое.PIPE_VER, иначе кэш подсунет старый звук и кадр от другой минуты дня.s5_voices.py --samples-only пересобирает цитаты, не перекластеризовывая — полный прогон раздаёт номера заново и смывает решения человека.torchcodec в venv сломан (libavutil.56.dylib не найден). pyannote читает звук фолбэком и сыплет предупреждениями — работает, но починить стоит.