Indexando Vídeos Longos com a API Gemini: Timestamps, Recursos Visuais e Auditoria de Lacunas
Um fluxo de trabalho de engenharia para indexar gravações de vídeo longas com a API Gemini: upload pela Files API, geração de rascunhos agênticos na Interactions API, validação programática de linhas estruturadas e inspeção direcionada de lacunas de cobertura.
Conteúdo

Gravações de vídeo longas — como palestras técnicas, workshops, chamadas de revisão de arquitetura e screencasts — concentram uma densidade alta de informações distribuídas entre o áudio da fala e os elementos visuais na tela. Resumos convencionais de alto nível capturam somente temas gerais, forçando os engenheiros a avançar e retroceder manualmente pela gravação sempre que precisam encontrar um comando específico de terminal ou um parâmetro de configuração.
Os modelos multimodais Gemini conseguem analisar fluxos de áudio e vídeo de forma simultânea, gerando índices estruturados de eventos com timestamps. Contudo, o resultado bruto da inferência inicial do modelo representa apenas um conjunto preliminar de candidatos, e não um índice de referência pronto para produção. Construir um índice confiável exige um pipeline rigoroso: upload único via Files API e reutilização de URI, extração de intervalos preliminares, validação estrita do esquema de saída, auditoria de lacunas suspeitas de cobertura e calibração direcionada.
Arquitetura: Métodos de Entrega e Modos de Processamento
Na documentação atual do Google Video Understanding, as implementações principais giram em torno da Interactions API e da biblioteca google-genai. Embora o método clássico generate_content permaneça suportado para compatibilidade retroativa, a Interactions API oferece um controle mais claro sobre os parâmetros de processamento multimodal.
1. Métodos de Entrega de Vídeo
- Files API (Recomendada para vídeos longos): Mais indicada para gravações com duração de vários minutos a várias horas. O arquivo é enviado uma única vez, indexado no servidor e referenciado por URI em requisições consecutivas sem o reenvio de bytes brutos.
- Google Cloud Storage (GCS): Ideal para acervos de vídeo que já residem na infraestrutura do Google Cloud.
- Dados Embutidos (Inline Data): Transmissão direta dos bytes brutos no payload da requisição. A documentação aponta restrições de payload variadas conforme o ambiente; para uma manipulação estável de gravações de uma hora ou mais, convém apoiar-se na Files API ou no Cloud Storage, evitando a transmissão do fluxo de vídeo inline.
2. Modos de Processamento: Estático vs. Agêntico
- Processamento Estático (Static Processing):
Por padrão, o modelo realiza uma amostragem discreta de quadros a uma taxa de 1 quadro por segundo (1 FPS). Cada segundo se converte em tokens, acumulando uma carga substancial de contexto ao longo de 60 minutos de gravação. Tenha em mente: a amostragem a 1 FPS pode perder eventos visuais rápidos (como alternâncias de janelas em frações de segundo ou tooltips passageiros) e não garante a captura de cada microinteração. - Compreensão de Vídeo Agêntica (Agentic Video Understanding):
O modelo navega pelo vídeo de forma dinâmica, recuperando quadros específicos e trechos da faixa de áudio sob demanda. Isso reduz consideravelmente o volume de tokens de contexto processados.
Limitação: A heurística agêntica apoia-se em pistas de áudio e gatilhos semânticos. Se o apresentador executar ações silenciosamente na tela (como digitar um comando ou analisar um diagrama) sem falar nada, a heurística pode tratar esse intervalo como ocioso em segundo plano e deixar de solicitar quadros visuais detalhados.
Passo 1. Upload do Vídeo e Consulta de Status
Arquivos enviados para a Files API não ficam disponíveis para inferência imediatamente; o servidor precisa descompactar o contêiner e indexar as faixas de áudio e vídeo. Sua aplicação precisa consultar o recurso periodicamente (polling) até que o estado se torne ACTIVE.
import time
from google import genai
client = genai.Client()
video_path = "tech_workshop_60min.mp4"
video_file = client.files.upload(file=video_path)
while not video_file.state or video_file.state.name != "ACTIVE":
if video_file.state and video_file.state.name == "FAILED":
error_info = getattr(video_file, "error", None)
raise RuntimeError(
f"Файл перешел в статус FAILED. Детали: {error_info}. "
"Проверьте кодеки, целостность контейнера или повторите попытку."
)
time.sleep(5)
video_file = client.files.get(name=video_file.name)
print("Видео готово к обработке.")
Passo 2. Solicitação de um Rascunho de Índice via Interactions API
Para gravações extensas, acione client.interactions.create especificando "processing": "agentic". O prompt estabelece uma saída tabular estrita: intervalo de timestamps MM:SS - MM:SS, tipo de evento (visual, speech, hybrid), uma declaração concisa da fala (CLAIM) e a ação executada na tela (ACTION).
index_prompt = """
Ты — инструмент технической индексации видеозаписей.
Сформируй хронологический индекс событий для всей 60-минутной записи от 00:00 до 60:00.
Требования к структуре ответа:
1. Выведи результат построчно в формате TSV с разделителем |.
2. Каждая строка должна содержать ровно 5 полей:
START_TIME (MM:SS) | END_TIME (MM:SS) | TYPE (visual/speech/hybrid) | CLAIM | ACTION
3. Не объединяй слишком длинные интервалы в одну запись; фиксируй смену слайдов, терминал, ошибки и выводы спикера.
4. Если речи не было, в поле CLAIM укажи NONE. Если на экране не было динамики, в ACTION укажи STATIC.
5. Выводи исключительно строки данных без вводных слов и пояснений.
"""
interaction = client.interactions.create(
model="gemini-3.8-flash",
input=[
{
"type": "video",
"uri": video_file.uri,
"mime_type": video_file.mime_type,
"processing": "agentic"
},
{
"type": "text",
"text": index_prompt
}
]
)
raw_index_output = interaction.output_text
Sobre as Etapas de Navegação Agêntica:
A presença de entradasprocessing_calldentro deinteraction.stepsconfirma que o modelo navegou dinamicamente pela linha do tempo do vídeo em vez de ingerir um fluxo contínuo. No entanto, observar essas chamadas atesta apenas a ocorrência da navegação — não garante a completude da saída em relação a todos os eventos relevantes da cronologia.
Passo 3. Exemplo de Estrutura de Saída (Rascunho Hipotético)
Veja a seguir um trecho hipotético ilustrativo da saída do modelo demonstrando o esquema de dados esperado:
00:00 | 02:15 | hybrid | Вводное слово, обзор повестки миграции на шардированный кластер | Титульный слайд доклада, окно спикера
02:16 | 05:40 | visual | NONE | Переключение на схему архитектуры сервиса заказов в Miro
05:41 | 09:12 | speech | Пояснение причин отказа от распределенных транзакций в пользу Saga | Статичная схема Miro, курсор неподвижен
27:30 | 29:10 | visual | NONE | Открытие консоли, запуск сценария развертывания реплик БД
29:11 | 32:45 | hybrid | Разбор сценария split-brain при потере сетевой связности | Вывод логов etcd в терминале, подсветка таймаутов
54:10 | 57:25 | speech | Ответ на вопрос о допустимой задержке репликации данных | Финальный слайд с контактами спикера
57:26 | 60:00 | hybrid | Подведение итогов и демонстрация ссылки на репозиторий | Показ QR-кода на экране, завершение созвона
Passo 4. Validação da Marcação e Busca Local
Respostas brutas de LLMs não devem ser tratadas como conjuntos estruturados confiáveis sem validação. Um analisador resiliente não deve descartar registros corrompidos de forma silenciosa; em vez disso, deve isolá-los para correção manual. O validador confere: presença exata de cinco campos, tipos de eventos permitidos (visual, speech, hybrid) e timestamps MM:SS válidos e contidos na duração total do vídeo.
import csv
import re
from typing import List, Dict, Tuple
TIME_PATTERN = re.compile(r"^(\d{2}):([0-5]\d)$")
ALLOWED_TYPES = {"visual", "speech", "hybrid"}
def time_to_seconds(t_str: str) -> int:
match = TIME_PATTERN.match(t_str)
if not match:
raise ValueError(f"Некорректный формат времени: {t_str}")
m, s = map(int, match.groups())
return m * 60 + s
def parse_and_validate_tsv(
raw_text: str,
max_duration_sec: int = 3600
) -> Tuple[List[Dict[str, str]], List[Dict[str, str]]]:
valid_rows = []
malformed_rows = []
lines = [line.strip() for line in raw_text.strip().splitlines() if line.strip()]
for idx, line in enumerate(lines, start=1):
cols = [c.strip() for c in line.split("|")]
if len(cols) != 5:
malformed_rows.append({
"line": idx,
"content": line,
"error": f"Ожидалось 5 полей, получено {len(cols)}"
})
continue
start_str, end_str, event_type, claim, action = cols
if event_type.lower() not in ALLOWED_TYPES:
malformed_rows.append({
"line": idx,
"content": line,
"error": f"Недопустимый тип события: {event_type}"
})
continue
try:
s_sec = time_to_seconds(start_str)
e_sec = time_to_seconds(end_str)
except ValueError as err:
malformed_rows.append({
"line": idx,
"content": line,
"error": str(err)
})
continue
if s_sec > e_sec:
malformed_rows.append({
"line": idx,
"content": line,
"error": f"Время начала ({start_str}) больше времени окончания ({end_str})"
})
continue
if e_sec > max_duration_sec:
malformed_rows.append({
"line": idx,
"content": line,
"error": f"Таймкод {end_str} выходит за хронометраж ({max_duration_sec} сек)"
})
continue
valid_rows.append({
"start": start_str,
"end": end_str,
"start_sec": s_sec,
"end_sec": e_sec,
"type": event_type.lower(),
"claim": claim,
"action": action
})
return valid_rows, malformed_rows
def search_index(
rows: List[Dict[str, str]],
keyword: str = None,
event_type: str = None
) -> List[Dict[str, str]]:
results = []
for r in rows:
if event_type and r["type"] != event_type.lower():
continue
if keyword:
kw = keyword.lower()
if kw not in r["claim"].lower() and kw not in r["action"].lower():
continue
results.append(r)
return results
valid_index, errors = parse_and_validate_tsv(raw_index_output, max_duration_sec=3600)
if errors:
print(f"Обнаружено некорректных строк: {len(errors)}. Требуется ручная правка:")
for err in errors:
print(f"Строка {err['line']}: {err['error']} -> {err['content']}")
else:
print(f"Все строки валидны. Записей в индексе: {len(valid_index)}")
Passo 5. Auditoria de Cobertura e Identificação de Lacunas
Antes de publicar o índice, inspecione a linha do tempo em busca de pontos cegos:
- Faça inspeções por amostragem em zonas de referência:
Verifique manualmente o início do vídeo (introdução e slides iniciais), o ponto intermediário (onde demonstrações ao vivo ou debates de arquitetura atingem o pico) e a conclusão (sessão de perguntas e respostas e encerramento). - Analise os intervalos de timestamps:
Não há um limite universal para o espaçamento ideal entre entradas do índice; a tolerância varia com o caso de uso. Em um screencast detalhado, uma lacuna de 90 segundos pode significar uma etapa de configuração omitida. Em uma exposição teórica panorâmica, uma única tese estendendo-se por 5 minutos pode ser inteiramente aceitável. Caso um intervalo pareça desalinhado ao ritmo da apresentação, marque-o como suspeito. - Inspecione eventos visuais silenciosos:
Se o apresentador realizou demonstrações na tela sem acompanhamento em voz alta, o modo agêntico pode ter ignorado o trecho. Direcione essas janelas para revisão estática pontual.
Passo 6. Inspeção Direcionada de Zonas Suspeitas com Clipe Estático
Reavaliar um segmento suspeito não requer reprocessar a íntegra dos 60 minutos de vídeo. O recorte de clipes delimita a análise no modo estático a pontos exatos em segundos (start_offset e end_offset).
Limitação do Método:
O modo estático a 1 FPS disponibiliza uma grade regular de amostragem, facilitando o resgate de ações negligenciadas durante a análise agêntica ampla. No entanto, ele não garante recuperação absoluta (recall): variações visuais que ocorram mais rápido do que um segundo ainda podem passar despercebidas entre os quadros capturados.
start_sec = 1200
end_sec = 1380
targeted_inspection = client.interactions.create(
model="gemini-3.8-flash",
input=[
{
"type": "video",
"uri": video_file.uri,
"mime_type": video_file.mime_type,
"processing": {
"type": "static",
"start_offset": start_sec,
"end_offset": end_sec,
"fps": 1.0
}
},
{
"type": "text",
"text": (
"Хронологически опиши изменения на экране. "
"Зафиксируй команды в терминале, смену окон и системные сообщения."
)
}
]
)
print("Результат точечного досмотра отрезка:")
print(targeted_inspection.output_text)
Incorpore as novas entradas resgatadas ao índice validado manualmente ou por meio de uma etapa de revisão semiautomatizada.
Após corrigir todas as ocorrências em errors e verificar os timestamps correspondentes na gravação original, exporte o índice final. O trecho de código abaixo aborta propositalmente caso o validador ainda aponte inconsistências; certifique-se de que valid_index contenha os dados verificados e corrigidos antes da execução.
if errors:
raise ValueError("Исправьте строки из errors и повторите проверку перед экспортом")
with open("video_index.csv", "w", newline="", encoding="utf-8") as output_file:
writer = csv.DictWriter(
output_file,
fieldnames=["start", "end", "type", "claim", "action"],
extrasaction="ignore",
)
writer.writeheader()
writer.writerows(valid_index)
O arquivo CSV resultante possibilita aos usuários buscar por afirmações ou ações de tela específicas e saltar diretamente para os momentos correspondentes na gravação original. O arquivo permanece como rascunho até que um editor humano confirme tanto quais eventos foram selecionados quanto seus limites de eventos, e não meramente limites de amostragem.
Diagnóstico de Problemas e Casos de Borda
- Status
FAILEDdurante o upload na Files API:
Evite presumir o motivo da falha sem checar os diagnósticos da API. Erros podem decorrer de contêineres não suportados, cabeçalhos corrompidos ou instabilidades temporárias de infraestrutura. Inspecione o atributofile.errorvia SDK, teste a reprodução local do arquivo comffprobe, padronize os fluxos comffmpeg(-c:v libx264 -c:a aac) se indispensável e refaça o envio. - Erro
401 Unauthorizedou quedas de rede:
O código 401 aponta expressamente para falha de autenticação (chave inválida ou ausente, ou problemas na variável de ambienteGEMINI_API_KEY), e não para término da sessão de processamento. Para evitar timeouts nas conexões HTTP do cliente durante requisições demoradas, habilite o streaming comstream=True. - Timestamps que ultrapassam a duração total do vídeo:
Esse desvio pode se manifestar conforme a complexidade do contexto se eleva. Neutralize o comportamento com regras firmes no prompt e validação programática (e_sec > max_duration_sec) no analisador. - Desvio estrutural no formato TSV:
Se a formatação quebrar, encaminhe as linhas defeituosas paramalformed_rowse inclua de 1 a 2 linhas de referência (few-shot) nas diretrizes de sistema do prompt.
Fluxo de Trabalho de Verificação Pré-Publicação
- Confirmar prontidão do arquivo: Certifique-se de que o recurso alcançou o status
ACTIVEna Files API. - Gerar rascunho inicial: Obtenha o índice base configurando o processamento
agenticna Interactions API. - Executar validação programática: Verifique se todas as linhas possuem os cinco campos mandatórios, tipos aceitos e a formatação
MM:SS. Isole registros inválidos para tratamento. - Auditar cobertura: Inspecione as zonas de referência (começo, meio e fim) e contraste a frequência dos timestamps com a dinâmica da fala.
- Realizar inspeção direcionada: Reavalie intervalos suspeitos ou trechos silenciosos com clipes estáticos (
start_offset/end_offset). - Executar calibração manual por amostragem: Verifique o horário de início real de cada evento em relação ao áudio/vídeo de origem relevante conforme o caso de uso exigir; não exija uma alteração visual correspondente, já que pode ocorrer um evento apenas de áudio.
Para detalhes sobre esquemas de requisição, modos de processamento e restrições de amostragem, consulte a documentação oficial do Gemini Video Understanding.