Estrutura (pastas em português, numeradas pra ordenar no Finder; a mídia em data/ é toda hardlink, não duplica espaço):

lula-dataset/

├── dados_lula.xlsx ← um arquivo com tudo (abas em PT): vídeos, destaques, cortes, categorias, canais, edições, campanha

├── README.md

├── scripts/ ← .py do pipeline + queries*.txt (logs em work/logs/)

└── data/

├── 01_destaques/ melhores cortes (gem_score alto)

├── 02_cartazes/ cartazes / pôsteres

├── 03_por_tema/ cortes agrupados por assunto (hardlinks)

├── 04_cortes_rosto/ cortes curtos com o rosto do Lula (ArcFace)

├── 05_cortes_continuos/ blocos contínuos de fala

├── 06_transcricoes_cortes/ transcrição por corte (Whisper), pros vídeos sem legenda

├── 07_transcricoes_videos/ transcrição por vídeo (legenda do YouTube), <video_id>.txt

├── 08_edicoes_fas/ edits de fã com música

├── 09_campanha2026/ sub-dataset da campanha de 2026 (DATASET=campaign2026)

└── 99_indices/ jsonl/csv crus (leitura por máquina, com transcript_segments)

Os índices .jsonl/.csv (inclusive campos que não cabem no Excel, como transcript_segments) ficam em data/99_indices/.

Nomes de arquivo .jsonl citados abaixo estão lá. Caminhos são relativos à raiz quando não dito o contrário.

Vídeos do YouTube com footage antiga do Lula (foco: antes de 1990) e transcript das legendas do próprio YouTube.

Construído em 14/09/2026 a partir de 172 buscas (queries.txt), 5.589 vídeos únicos encontrados.

Ordenação: pré-1990 primeiro, depois menção a Lula, depois ano estimado, depois views.

- video_id,- url,- title,- channel,- channel_url,- upload_date(YYYYMMDD),- duration_s,- view_count,- like_count,- comment_count,- description,- tags,- categories,- chapters,- thumbnail,- language

- mentions_lula(título ou descrição),- lula_in_title

- footage_year_guess,- era_source(- title_year|- description_year|- keywords),- years_mentioned,- era_keywords,- pre_1990_guess

- transcript_text(junção dos segmentos),- transcript_segments(- start,- dur,- text),- transcript_lang,- transcript_type(- manual|- auto),- transcript_source(- youtube-transcript-api|- yt-dlp),- transcript_available_langs,- transcript_error,- transcript_chars

- found_by_queries

Ano no título manda (Roda Viva | Lula | 1986 → 1986). Sem ano no título, um ano entre 1975 e 1989 na descrição.

1945 (nascimento) e 1964 em bio genérica são ignorados. Sem ano algum, palavras-chave de época

(greve, ABC, Vila Euclides, DOPS, Diretas, Constituinte, Collor, Roda Viva…) marcam pre_1990_guess com era_source = keywords,

desde que não haja marcador pós-1990 (Bolsonaro, Dilma, Lava Jato, ano ≥ 1990…); é a fatia mais ruidosa (165 vídeos). upload_date é a data do upload, não da gravação.

Quase tudo é legenda automática do YouTube (transcript_type = auto): erros de reconhecimento em nomes e em áudio antigo são comuns.

Os vídeos sem legenda no YouTube (documentários inteiros, ex.: ABC da Greve) foram transcritos por corte com Whisper: 301 cortes

via API da OpenAI (whisper-1, com timestamps), em 06_transcricoes_cortes/ (scripts/whisper_api.py; whisper_clips.py faz o mesmo local com faster-whisper).

- youtube-transcript-api e o web client do yt-dlp levam bloqueio de IP (RequestBlocked/ "confirm you're not a bot" / 429) depois de algumas centenas de requests.

- Metadados: --extractor-args youtube:player_client=mweb --ignore-no-formats-errorpassa.

- Legendas: player_client=web_embeddedexpõe as auto captions; pedir só a língua original (pt, ou.*-origpara vídeo em outra língua). Pedir tradução (ptde vídeo em espanhol,pt-PT) dá 429.

videos_pre1990.jsonl ainda tinha muito vídeo sobre o Lula. clip.py run baixa cada candidato em 360p, varre um frame a cada 2 s com

reconhecimento facial (insightface buffalo_l, ArcFace) contra work/lula_ref.npy (3.484 rostos dele extraídos do Roda Viva 1986 e do

Canal Livre 1981; separação dos jornalistas: Lula p5 = 0,63, outros p99 = 0,36; limiar 0,45) e corta só os trechos em que ele aparece.

Os dois perfis saem do mesmo download. Regras do perfil cont: junta sumidas do rosto de até 45 s (pergunta do entrevistador, corte de câmera), exige 10 s e presença do Lula em pelo menos 30% do trecho (lula_frac), acolchoa 2 s e encosta o corte na mudança de cena mais próxima (até 4 s). Parâmetros por env (GAP, MIN_RUN, PAD, MIN_FRAC); mudar e rodar de novo recorta os vídeos já varridos sem refazer a varredura (baixa de novo a fonte). Fonte baixada é apagada após o corte

(KEEP_SRC=1 mantém). Só rosto conta: voz em off do Lula sem imagem dele fica de fora. Fotos dele mostradas por um youtuber entram (trechos curtos, mean_sim alto).

./clip.py stats resume; a fila é resumível.

Download exige PO token: plugin bgutil-ytdlp-pot-provider + servidor node em ~/tools/bgutil-ytdlp-pot-provider/server (o script sobe sozinho).

Downloads sequenciais com 20 s de pausa (PACE) e espera de 10/20/40 min quando o YouTube bloqueia.

Mesmo pipeline com QUERIES_FILE=queries_edits.txt OUT=edits_ (70 buscas: edit, phonk, 4K, sigma, tribute, remix, mashup, melhores momentos… + uploads do canal do "Lula | Gangsta's Paradise").

edits_videos.jsonl/csv é o bruto (1.760 vídeos, muito Lula Molusco e receita de lula). edits_filter.py gera edits_fan.jsonl/csv (363 vídeos):

Lula no título, sem molusco/receita/jornal, título com marcador de edit, até 15 min, ordenado por views, com fan_edit_score. Transcripts não foram puxados para essa leva (é música).

Cada bloco contínuo com transcript passa por um modelo (claude -p, sonnet) que devolve categories (1 a 3 de uma taxonomia fixa: greve_sindicato,

ditadura_prisao_anistia, eleicao_1989_collor, eleicoes_82_86_88, constituinte_congresso, fome_pobreza_salario, trabalhador_patrao_classe,

democracia_diretas_voto, economia_inflacao_divida, religiao_igreja, infancia_familia_pessoal, humor_carisma, imprensa_midia, internacional,

pt_partido, comicio_discurso, entrevista_estudio, musica_jingle, outro), gem_score 0-5, gem_type, summary, quote e lula_speaks.

Resultado em clips_enriched.jsonl e cache em work/gems/. Blocos sem transcript ficam em sem_transcript.

Rerodar scripts/export_xlsx.py regenera a planilha da raiz a partir dos 99_indices.

Nota: export_xlsx.py já aponta pros nomes novos; build.py/clip.py/gems.py/posters.py ainda referenciam os nomes antigos (data/clips_continuous…) e precisariam de ajuste de path pra re-rodar o scrape.

Mesmo pipeline, outro alvo: QUERIES_FILE=queries_campaign2026.txt OUT=campaign2026_ build.py all (buscas por relevância e por data, date: usa ytsearchdate),

campaign_filter.py (upload >= jun/2026, Lula de verdade, ordenado por views). Cortes em 09_campanha2026/cortes_continuos/ e 09_campanha2026/cortes_rosto/.

Depois DATASET=campaign2026 clip.py ref <ids> (referência facial do Lula de 2026, o de 1981 não serve), DATASET=campaign2026 clip.py run,

DATASET=campaign2026 gems.py (taxonomia de 2026: economia, programas sociais, isenção do IR, Bolsonaro/adversários, 8 de janeiro, Trump/tarifas, soberania…).

As abas campanha_videos, campanha_cortes e campanha_rostos de dados_lula.xlsx saem daí. Nome dos cortes começa pela data do upload (AAAAMMDD).

Busca imagens em Bing/Google (icrawler), mantém só alta resolução (menor lado >= 800 px e área >= 1,2 MP), converte tudo pra PNG e deduplica por hash perceptual.

Um teste de achatamento de cor separa cartaz de foto de comício: retrato ou muita cor chapada fica em data/02_cartazes/, o resto vai pra data/02_cartazes/_maybe_photo/ pra revisão.

02_cartazes/index.jsonl tem width, height, megapixels, orientation, poster_score, kind. Afrouxar limiar: MIN_SIDE=600 MIN_AREA=700000 posters.py filter.

- Downloads last month

- 715