777 lines
52 KiB
Markdown
777 lines
52 KiB
Markdown
# TODO — Brasiliana → S3
|
||
|
||
> **Handoff em 2026-09-10 ~15h UTC**: sessão `fix-image-pipeline-extras` perdeu
|
||
> conexão com a extensão Claude-in-Chrome e encerrou a pedido do usuário.
|
||
> Trabalho passado pra sessão `chrome-extension-connection` (tem o navegador
|
||
> conectado). Ver mensagem de handoff enviada via SendMessage pro contexto
|
||
> completo do estado no momento da troca — resumo abaixo já reflete isso.
|
||
>
|
||
> **Atualização ~17h UTC (mesma sessão)**: aba antiga do coletor não é
|
||
> acessível por esta sessão (grupo de abas é por sessão) mas continuou
|
||
> rodando sozinha o tempo todo — confirmado por progresso do
|
||
> `bras_baixados` no localStorage (compartilhado por origem). Por isso o
|
||
> download principal (`BAIXA`) **não foi reiniciado nesta aba** (evitar
|
||
> duplicar ~8 mil downloads pesados em paralelo); só os 78 itens do achado
|
||
> 2.11 foram baixados cirurgicamente aqui. `BAIXA_THUMB` v2 foi iniciado
|
||
> numa aba nova (chave `bras_thumbs_v2`, começa do zero, ~13.059 itens).
|
||
> Durante a sessão houve uma queda de rede/extensão de alguns minutos
|
||
> (mesmo tipo de falha que derrubou a sessão anterior) — gerou ~1.280 erros
|
||
> transitórios no thumb v2 que precisam de uma segunda passada
|
||
> (`BAIXA_THUMB.retomar()`) depois que a primeira terminar.
|
||
|
||
> Ver `tasks/PLANO.md` pro histórico/achados/runbook. Itens concluídos saem
|
||
> daqui e vão pra `tasks/done/` (ex.: `tasks/done/TODO-2026-09-10.md`).
|
||
> Scripts e estado em `pipeline/` (reorganização de 2026-09-10).
|
||
|
||
## Em andamento (não mexer, só monitorar)
|
||
|
||
- [x] **Upload pro S3 — CONCLUÍDO.** A fila de `~/Downloads` está em **0**; tudo
|
||
que foi baixado está no bucket. Total no ledger: **41.444 objetos**.
|
||
O `pipeline/vigia-30min.sh` segue de pé: a cada 30 min sobe o que aparecer
|
||
e apaga do disco só depois do HEAD confirmar no bucket.
|
||
**Não editar `vigia-30min.sh` nem `rodar-paralelo.sh` enquanto rodam**
|
||
(o bash lê o script aos poucos) e **não relançar o `subidor.py` antigo**
|
||
(achado 2.22). Threads: 6, não 16 — ver achado 2.24.
|
||
|
||
- [ ] **Download principal** (1 imagem de preservação por item) — navegador. **2026-09-11 20:45 UTC**: **12.986/13.059 (99,44%), faltam 73** — medido pela fonte autoritativa (bucket ∪ fila local), não pelo `bras_baixados`. Parado há ~9h (último arquivo em `~/Downloads` às 13:19 local = 11:19 UTC). Lista exata dos 73 em **`pipeline/faltantes-preservacao.jsonl`** (`{handle, url}`). **Script `pipeline/baixar-faltantes-73.js` colado e rodado pelo usuário ~22:52 local (20:52 UTC)** — lista embutida, chave própria `bras_faltantes_73`, imune ao achado 2.16. **Resultado parcial: 2/73** (`10993`, `10995`, 31 MB). Os outros 71 estão falhando — ver achado 2.17. **Recontagem de 12/09: faltam 211, não 73 — ver achado 2.23.**
|
||
|
||
### Achado 2.17 — os 71 restantes falham, e não é malformação de URL (2026-09-11 21:25 UTC)
|
||
|
||
`bras_faltantes_73` = 2 e só 2 arquivos de preservação novos apareceram em
|
||
`~/Downloads` (22:52 local). O disco e a chave concordam — aqui o achado 2.16
|
||
não se aplica, o número é real.
|
||
|
||
Os 71 que falham têm URL estruturalmente idêntica às 2 que passaram
|
||
(mesmo host, mesmo `/brasiliana/bitstream/PREFIX/ID/SEQ/arquivo`, 21 dos 71 com
|
||
`%20` no nome, igual às que funcionaram). Diferenças observadas, nenhuma
|
||
conclusiva: as 2 que passaram são `SEQ=1`; entre as 71 há `SEQ` 1 (58), 4 (11),
|
||
3 (1) e 5 (1).
|
||
|
||
Hipótese principal: **as 2 que passaram estavam em cache de borda do Cloudflare**
|
||
— exatamente o caso do item 6066 descrito no cabeçalho do
|
||
`baixador-navegador.js` (`cf-cache-status: HIT`), e não a regra geral. Se for
|
||
isso, o `fetch` de aba não basta para estes 71 e o problema é de origem, não de
|
||
script.
|
||
|
||
**Próximo passo (precisa do console da aba)**: rodar `FALTA.status()` e
|
||
`FALTA.erros()` — o script guarda `{handle, url, erro}` de cada falha, e o
|
||
código HTTP decide o caminho:
|
||
- **404** → bitstream não existe nesse `SEQ`; reparar `preservation_url` no
|
||
catálogo (mesma família dos achados 2.9/2.11/2.13) e regerar o script.
|
||
- **403** → bloqueio de origem/Cloudflare; nem a aba resolve, precisa de outra saída.
|
||
- **500** → transitório; `FALTA.retomar()` mais tarde deve pegar.
|
||
|
||
Enquanto isso o script pode ainda estar rodando: cada erro dorme 60 s de backoff,
|
||
então 71 falhas levam ~71 min a partir das 22:52 local (fim previsto ~00:05).
|
||
|
||
### Achado 2.18 — é recusa da origem, não URL errada; e a v1 do script ajudou a causar (2026-09-12)
|
||
|
||
`FALTA.status()` do usuário: `atual: "43/73"`, `erros: 101`, `feito: 0`,
|
||
`fase: "baixando"`.
|
||
|
||
Três leituras, em ordem de importância:
|
||
|
||
1. **`feito: 0` no item 43** — `10993` e `10995` são os itens 1 e 2 da lista e
|
||
baixaram com sucesso às 22:52; nesta passada **falharam**. Mesma URL, uma hora
|
||
depois. Logo **não é URL errada, é a origem recusando**.
|
||
2. **`erros: 101` com `atual: 43/73`** — mais erros que itens tentados só é
|
||
possível com **dois ou mais loops concorrentes** somando no mesmo `S`. Cada
|
||
`iniciar()`/`retomar()` sobre um script já rodando cria outro loop. Isso
|
||
quebrou a pausa de 3 s combinada com a BN e provavelmente disparou rate-limit.
|
||
3. `total: 73` (e não 71) — o `FEITOS` foi lido vazio na largada dessa passada.
|
||
|
||
**Hipóteses mortas** (não perder tempo com elas de novo):
|
||
- *Sequência errada no `preservation_url`*: os 2 que funcionaram têm exatamente
|
||
a mesma forma dos 13 "divergentes" (catálogo `seq=1`, thumb em `seq=3`), e 58
|
||
dos 71 têm `seq` coerente com o thumb e falham igual.
|
||
- *Bloqueio geral do Cloudflare*: **71/71 desses itens têm thumb baixado com
|
||
sucesso** — o item responde; só o bitstream de preservação não.
|
||
|
||
**`pipeline/baixar-faltantes-v2.js`** corrige os defeitos da v1 (que eram meus):
|
||
guarda contra `iniciar()` duplo, **aborta depois de 5 erros seguidos** em vez de
|
||
moer os 71 com 60 s cada, pausa de 5 s, backoff de 30 s, e registra o código HTTP
|
||
separado (`FALTA.resumo()` devolve a tabela de códigos). Recarregar a aba antes
|
||
de colar — `parar()` não alcança closures de colagens anteriores.
|
||
- [x] **Thumbnails** — ✅ concluído 100% (13.059/13.059). `retomar()` já foi rodado uma vez (08h local); **não re-rodar**.
|
||
- [x] **Fase 4 (legado objdigital)** — ✅ concluído (2ª passada terminou): 2.137 ok / 565 erro (2.702/2.707 processados; os 565 são 404 permanentes confirmados via curl direto, atrito real da fonte legada, não bug). Confirmado em 2026-09-11 ~20h UTC: `select erro,count(*)` devolve **só `HTTP 404` (565)** — nenhum `HTTP 0` restante, então a 3ª passada não tem o que reprocessar; **não reiniciar de novo**.
|
||
- [x] ~~`subidor.py`~~ **substituído pelo `subidor-paralelo.py` em 2026-09-12 (achado 2.22); o texto a seguir é histórico.** (`pipeline/`, `--vigiar`, PID 97295) — vivo há 1d8h, vigiando `~/Downloads`. **2026-09-11 ~20h UTC**: fila local de **19.897 arquivos `BRAS__` / ~77 GB** (12.511 thumb, 1.660 `__imagem`, 5.726 com nome original). Bucket: 33.388 objetos / 60,3 GB, 8.080 itens com `metadata.json` (61,9% dos 13.059). **2026-09-12 09:45 UTC**: 16.207 enviados, fila de 18.286. Taxa nas horas acordadas ≈ **650–740 arq/h ≈ 2,0–2,4 GB/h**.
|
||
|
||
⚠️ **O relógio é de horas ACORDADAS, não de horas corridas.** Os envios pararam
|
||
às 22:30 UTC de 11/set e só voltaram às 09:00 UTC de 12/set — **10,5 h de
|
||
silêncio**, que é a máquina dormindo (00:30–11:00 local), não falha do subidor
|
||
(o processo seguiu vivo e voltou sozinho). Por isso:
|
||
- fila de 18.286 ÷ ~700 arq/h = **~26 h de máquina acordada**
|
||
- em dias normais (~14 h acordado), isso vira **~2 dias de calendário**
|
||
- com `caffeinate -is` segurando a máquina acordada, ~26 h corridas
|
||
|
||
Vale decidir isso conscientemente: é a diferença entre terminar em ~1 dia ou ~2.
|
||
|
||
### Achado 2.15 — aba do coletor inalcançável, e storage da origem aparece vazio (2026-09-11 ~20h UTC)
|
||
|
||
Tentativa de retomar a trilha 1 sozinho falhou, mas rendeu dois fatos novos:
|
||
|
||
1. Sobraram instâncias **headless do Chrome** de uma sessão anterior, uma delas
|
||
lançada com o **perfil padrão** (`--user-data-dir` default). Mortas (PIDs
|
||
89329, 89327, 14818); o Chrome real do usuário (PID 57263, aberto desde
|
||
9/set) foi preservado. **Nunca subir headless sem `--user-data-dir` próprio**
|
||
— disputa o perfil onde mora todo o estado do coletor.
|
||
2. Numa aba nova em `https://brasilianafotografica.bn.gov.br` (mesmo perfil,
|
||
mesma origem), `localStorage` devolve **0 chaves** e `indexedDB.databases()`
|
||
devolve **[]** — enquanto `ler-localstorage.js`, lendo o leveldb do disco do
|
||
mesmo perfil/origem, devolve `bras_baixados: 12.974`. Ou seja: o contexto vivo
|
||
e o disco discordam. Não dá pra `BAIXA.retomar()` numa aba nova: sem o
|
||
IndexedDB `bras`, `alvos()` devolveria `[]`.
|
||
|
||
**Não colar `baixador-navegador.js` numa aba nesse estado**: se o storage
|
||
estiver mesmo zerado, `FEITOS` começa vazio e a fila vira os ~13 mil itens
|
||
inteiros de novo (centenas de GB, e carga indevida na fonte). O caminho seguro
|
||
é o usuário voltar na **aba original do coletor** (a que já tem `BAIXA` no
|
||
`window`) e rodar `BAIXA.status()` / `BAIXA.retomar()` lá.
|
||
|
||
### Achado 2.16 — `bras_baixados` anda PRA TRÁS; não usar como medida (2026-09-11 20:45 UTC)
|
||
|
||
O contador caiu de **12.974 → 12.111** entre 20:14 e 20:43 UTC (três leituras
|
||
seguidas devolvem 12.111; o leveldb foi escrito pelo Chrome às 22:24 local, 19
|
||
min antes da leitura — é gravação real, não cache velho).
|
||
|
||
Causa provável: `marcar()` reescreve o **array inteiro** a partir do `FEITOS`
|
||
carregado quando o script foi colado. Qualquer aba com snapshot velho que ainda
|
||
marque um item sobrescreve a lista boa por uma mais curta. Com duas abas vivas
|
||
(v1 e v2 conviveram), a lista é sempre a do último escritor, não a união.
|
||
|
||
**Consequência prática**: `bras_baixados` **não** mede progresso — subestima. A
|
||
medida autoritativa é *bucket ∪ fila local em `~/Downloads`*:
|
||
|
||
```
|
||
# handles com preservação no bucket (do aws s3 ls --recursive)
|
||
awk '... itens/<handle>/<arquivo!=metadata.json, sem "thumb">' → 8.080
|
||
# handles de preservação ainda na fila local
|
||
ls ~/Downloads | grep '^BRAS__' | grep -v thumb | sed -E 's/^BRAS__(.+)__.*/\1/' → 7.271
|
||
# união → 12.986 de 13.059 (faltam 73, listados em pipeline/faltantes-preservacao.jsonl)
|
||
```
|
||
|
||
Isso também refaz a conta do achado 2.15: o risco de recolar o script não é
|
||
"85 itens", é **re-baixar ~950+** que o contador esqueceu (ou tudo, se o
|
||
storage estiver mesmo zerado).
|
||
|
||
## Concluído agora
|
||
|
||
- [x] **Thumbnails contaminados com bitstream de LICENÇA** (achado 2.8b): 2.653 arquivos locais apagados de `~/Downloads`, 146 já subidos removidos do bucket, `subidor.py` blindado com validação de magic bytes (rejeita e apaga automaticamente qualquer coisa que não comece com JPEG/PNG, mesmo que o nome diga o contrário). `baixador-thumb.js` v2 escrito com validação + fallback — falta só reconectar o navegador pra trocar o script rodando.
|
||
- [x] **WordPress → .md**: 636/637 posts colhidos (1 erro), exportados e convertidos — `wordpress/{id}-{slug}.md` no repo (front-matter YAML: id/url/título/autor/data/categorias/handles referenciados) + `wordpress-posts.jsonl` e a pasta `wordpress/` subindo pro bucket agora.
|
||
- [x] **Bug de corrida em arquivo temporário**: `baixador.py` e `subidor.py` moram na mesma pasta e usavam o mesmo nome fixo (`.tmp/metadata.json`) — rodando ao mesmo tempo, um apagava o arquivo que o outro tinha acabado de criar. Derrubou o `baixador.py` de verdade (crash, não só log de erro). Corrigido: nome agora inclui o PID. 3 itens (`1081,1082,1084`) já tinham subido um HTML de visualizador como se fosse "legado" antes da correção — limpo do bucket, metadata regenerado.
|
||
- [x] **PDFs de álbum perdidos no objdigital**: 44 itens (achado 2.9 do PLANO.md) tinham PDF disponível mas a extração pegava um `.htm` de landing page em vez do PDF de verdade. Corrigido em `catalogo.jsonl` e no IndexedDB antes do `baixador.py --somente-legado` alcançar esses handles (confirmado: nada contaminado no bucket). `baixador.py` reiniciado (PID 37909) com o catálogo corrigido.
|
||
|
||
## Concluído nesta sessão (chrome-extension-connection, ~17h UTC)
|
||
|
||
- [x] `pipeline/reparo-hash-url.js` colado e rodado — 78/78 corrigidos no IndexedDB
|
||
- [x] **Achado 2.13 (novo)**: mesmo bug do `#` reaparecia no redirect do DSpace (forma antiga → canônica). Fix: usar `/bitstream/handle/PREFIX/ID/arquivo` direto. `preservation_url` dos 78 reescrito de novo com a forma canônica.
|
||
- [x] Os 78 itens baixados cirurgicamente — **concluído 78/78, 0 erros** (script à parte, não o `BAIXA.retomar()` geral — aba antiga com o loop principal continua rodando sozinha, ver nota de handoff acima)
|
||
- [x] `BAIXA_THUMB` v2 iniciado (aba nova, não foi possível parar a v1 na aba antiga — ver nota acima; sem risco de dado ruim no bucket graças à validação de magic bytes já no `subidor.py`)
|
||
- [x] **Thumbnails v2: CONCLUÍDO 100%** — `BAIXA_THUMB.retomar()` rodado (2026-09-11 ~08h local) recuperou todos os 1.281 erros da queda de rede (13.059/13.059 ok, 0 erro permanente)
|
||
- [x] `baixador.py --somente-legado` reiniciado uma vez (2026-09-11 ~08h local, PID 88387) depois que a passada anterior terminou (2.113 ok, 589 erro de 2.702 processados) — idempotente, só reprocessa não-'ok'
|
||
- [x] Checagem de status a cada 30 min retomada nesta sessão (ver `/loop`)
|
||
|
||
## Mural (`web/wall.html`) — sessão `fix-mobile-wall-click-layout`, 2026-09-12
|
||
|
||
No ar em <https://xn--2dk.xyz/brasiliana/>. Commits em `rafapolo/xyz`:
|
||
`d8c3364` e `a6085fa` (empurrados pela sessão paralela `brasiliano-ae`, contêm
|
||
trabalho das duas) e `f645008`. O mural é publicado copiando
|
||
`web/wall.html` → `xyz/brasiliana/index.html` e `web/atlas/atlas-map.json` →
|
||
`xyz/brasiliana/atlas/atlas-map.json`.
|
||
|
||
### Achado 3.1 — a metade descritiva do catálogo nunca chegou ao mural
|
||
|
||
Buscar "indigenista" não achava nada, mesmo com 42 peças classificadas
|
||
`dc.subject = Política indigenista`. A busca livre estava certa (varre todo o
|
||
`meta` recursivamente); quem descartava era o **`summarize()` do
|
||
`web/build-atlas.js`**, que projetava 8 campos e jogava fora assunto,
|
||
descrição, lugar, fonte, editor, relação e código de arquivo.
|
||
|
||
Havia um segundo bug atrás: `pickDC()` devolve só a **primeira** ocorrência do
|
||
campo. Como o catálogo repete `dc.subject` (78.835 linhas para 13.060
|
||
registros, ~6 por item), mesmo incluindo o campo sobreviveria um assunto por
|
||
peça. O `pickAll()` novo coleta todas, deduplicadas, na ordem.
|
||
|
||
Regras que ficaram (espelhadas em `pipeline/reprojeta-meta-mapa.mjs`):
|
||
|
||
- entram: `dc.subject`, `dc.description`, `dc.coverage`, `dc.source`,
|
||
`dc.publisher`, `dc.relation`, `dc.identifier`
|
||
- ficam de fora: `dc.language` (é sempre `"por"` — casaria ~2.000 itens por
|
||
acidente, já que a busca é por substring) e `dc.format` (`"JPG"`)
|
||
- `dc.identifier` e `dc.relation` entram **sem as URLs**: era 1,1 MB de texto
|
||
que ninguém busca, escondendo o que presta — os códigos de arquivo
|
||
(`013RJ012036`) e os nomes de coleção (`Gilberto Ferrez`, `Diários
|
||
Associados`)
|
||
- `handle` e `source_url` **saíram** do `meta`: o primeiro é a chave sob a qual
|
||
o item já está arquivado no mapa, o segundo é o mesmo endereço para todos com
|
||
o handle no meio. O mural remonta a URL numa linha (`sourceUrl()`) e injeta o
|
||
handle na palha da busca, então buscar `5546` continua achando a peça.
|
||
|
||
Verificado no navegador: `indigenista` → 42, `Gilberto Ferrez` → 2.120,
|
||
`013RJ012036` → 2, `amazônia` → 156, `5546` → 1. E o ruído sumiu: `show=full`
|
||
e `brasilianafotografica` saíram de 12.552 para 0.
|
||
|
||
### Achado 3.2 — não vale pré-comprimir o `atlas-map.json`
|
||
|
||
Medido, não chutado:
|
||
|
||
- **No fio já vem gzipado.** `curl -sI` em <https://xn--2dk.xyz/brasiliana/atlas/atlas-map.json>
|
||
responde `content-encoding: gzip`. Pré-comprimir só moveria os mesmos bytes,
|
||
ao custo de um `DecompressionStream` no carregador e de um arquivo ilegível.
|
||
(O host não oferece brotli, e `DecompressionStream` não decodifica brotli.)
|
||
- **No repositório também.** O git comprime todo blob com zlib no pack, então
|
||
um JSON de 8 MB não ocupa 8 MB lá dentro — ocupa aproximadamente o que o
|
||
`.gz` ocuparia, e ainda dá diff.
|
||
|
||
Encolher o **conteúdo** foi o que valeu, e só até certo ponto — o gzip come
|
||
quase todo o ganho estrutural:
|
||
|
||
| variante | cru | gzip |
|
||
|---|---|---|
|
||
| com os campos descritivos | 9,61 MB | 1,11 MB |
|
||
| **sem `source_url` e `meta.handle`** (feito) | **8,00 MB** | **1,05 MB** |
|
||
| + dicionário nos campos de vocabulário pequeno | 6,96 MB | 0,99 MB |
|
||
| + dicionário de assuntos/lugares/descrições | 5,14 MB | 0,93 MB |
|
||
|
||
As duas últimas linhas quase dobram a economia crua e rendem 30-60 KB no fio
|
||
cada, em troca de uma camada de dicionário no carregador — troca ruim. Onde o
|
||
tamanho cru ainda pesa é o `JSON.parse` no celular, não o download. Referência:
|
||
antes de tudo isso o mapa era 5,52 MB / 0,73 MB.
|
||
|
||
### Consertos de interação (todos verificados por CDP num Chrome real)
|
||
|
||
- **Toque numa peça abria e fechava a ficha no mesmo gesto** (metade de cima da
|
||
tela). Um toque dispara `pointerup` e depois um `click` de compatibilidade,
|
||
que caía no backdrop recém-aparecido; a guarda de 400 ms usava
|
||
`stopPropagation()`, que **não alcança listeners do mesmo nó** — e quem fecha
|
||
a ficha é um listener do próprio `#card-wrap`. Agora é
|
||
`stopImmediatePropagation()`.
|
||
- **Tela inicial "sem estilo"**: `#title-screen` era irmão flex do `<p>` e,
|
||
invisível, ainda tomava metade da linha; e a regra `#loading p` pegava também
|
||
o subtítulo, jogando-o em maiúsculas com tracking. Virou `position:absolute` e
|
||
`#loading-msg`.
|
||
- **Painel de filtros fugia do dedo** no arrasto da década: o chip do filtro
|
||
aparecia e inseria uma linha inteira acima do corpo; o badge de contagem
|
||
crescia o header. Chips agora só com o painel fechado, badge reserva a caixa
|
||
com `visibility`.
|
||
- **Movimento da parede**: a câmera mirava o *alvo* enquanto estava atrás dele
|
||
(inclinava a vista a cada arrasto — era isso que fazia a parede "nadar"); o
|
||
easing era fração por frame (a 120 Hz convergia 2× mais rápido que a 60).
|
||
Agora constante de tempo, `lookAt` removido, e soltar em movimento joga a
|
||
parede com desaceleração. Zoom do scroll virou multiplicativo.
|
||
- **Back/forward do navegador** andam pelas peças abertas, com `#handle` na URL
|
||
(link direto reabre a peça). Mudar um filtro recolhe a ficha com **um** passo
|
||
de histórico, mesmo com `apply()` rodando a cada frame do slider.
|
||
- **Filtros alcançáveis com a peça aberta**: o painel estava em `z-index: 15`
|
||
debaixo do `#card-wrap` (20). Agora 25 (scrim 24), e no celular a ficha
|
||
estaciona acima do peek da sheet para não cobrir ‹ anterior / próxima ›.
|
||
|
||
### Achado 3.5 — o mural entrava em loop de carregamento no celular
|
||
|
||
Relato do usuário (14/09): no celular a tela inicial ficou em loop. Causa, lida
|
||
no carregador: o celular **decodificava cada página em 8192×8192** (~268 MB) e
|
||
só então reduzia para 4096 num canvas, retendo os canvases além das texturas —
|
||
pico de ~800 MB. O iOS não lança erro por falta de memória: mata a aba e a
|
||
**recarrega sozinho**, e a página recomeçava o mesmo carregamento. Não havia
|
||
tratamento de erro nenhum: fetch falho ou decodificação recusada deixavam a
|
||
barra parada para sempre.
|
||
|
||
Correção (commit `xyz` e `brasiliano` de 14/09):
|
||
- **Páginas leves** `atlas-m-0..3.webp` (3072², peça de 48 px), geradas pelo
|
||
`build-atlas.js` (`--so-mobile` regera só elas) e registradas em
|
||
`atlas-map.json` no campo `mobile`. Mesma posição de cada peça, então a conta de
|
||
uv não muda. 6,8 MB no total contra 42 MB; 36 MB decodificados por página.
|
||
- **Modo leve** em toque, `maxTextureSize` < 8192 ou `deviceMemory` ≤ 4, ou
|
||
forçado com `localStorage.mural_leve = '1'`.
|
||
- **Guarda contra loop**: marca `mural_carregando` em `sessionStorage` antes da
|
||
parte pesada, apagada só depois de dois quadros renderizados. Se a página abre
|
||
com a marca, a tentativa anterior morreu → vai direto para o modo leve e
|
||
mostra "modo leve — a tentativa anterior não terminou".
|
||
- **Nunca recarrega sozinho**: HTTP de erro, decodificação recusada e
|
||
`webglcontextlost` mostram aviso com botão "tentar de novo" (que recarrega uma
|
||
vez, já no modo leve). Progresso parado por 30 s mostra "o carregamento parou
|
||
de avançar" sem derrubar o que ainda pode estar vivo.
|
||
|
||
Verificado com Chrome real via `puppeteer-core` (perfil próprio, WebGL
|
||
swiftshader), emulando celular com toque — roteiros em
|
||
`scratchpad/teste-mural/testa.mjs` e `testa2.mjs`:
|
||
|
||
| cenário | resultado |
|
||
|---|---|
|
||
| A — celular carrega | só as 4 páginas leves, capa some em 8,3 s, marca apagada |
|
||
| C — marca de crash no desktop | aviso de modo leve, nenhuma página pesada, sem recarga |
|
||
| D — página falha no download | aviso com botão, **nenhuma recarga em 10 s** |
|
||
| E — botão tentar de novo | recarrega uma vez, termina no modo leve, sem 2ª recarga |
|
||
| F — perda de contexto WebGL | aviso com botão, sem recarga |
|
||
| G — download travado | aviso "parou de avançar" em 33 s, sem recarga |
|
||
|
||
**Verificado em produção** (14/09, após o deploy do GitHub Pages levar 62 s):
|
||
o cenário A contra `https://xn--2dk.xyz/brasiliana/` baixou só as 4 páginas
|
||
leves, a capa sumiu em 9 s pela rede real, sem falha, e a marca foi apagada.
|
||
|
||
O card no modo leve recorta a peça certa: a mesma peça tirada do atlas normal e
|
||
do leve difere 4/255 em média; a vizinha, 41,5/255.
|
||
|
||
**Limite do teste**: não há iPhone de verdade aqui — o limite de memória do iOS
|
||
não se emula no Chrome. O que está provado é que o celular não decodifica mais
|
||
8192² e que nenhum caminho de falha recarrega sozinho.
|
||
|
||
**Armadilha de teste**: `framenavigated` do Puppeteer conta a `about:blank`
|
||
inicial e mudanças de histórico do mural, então contar navegações dá falso
|
||
positivo de recarga. Usar uma marca em `window`, que só some numa recarga real.
|
||
|
||
## Miniaturas derivadas + reconciliação do ledger — 2026-09-12
|
||
|
||
Mural no ar com **13.059 peças** (antes 12.552): commits `bba32f6`,
|
||
`6a82a5e`, `1ecded1` e `e930158` em `rafapolo/xyz`. Desta vez o `atlas/` inteiro foi junto, não só o
|
||
`atlas-map.json` — o formato do map mudou (ver achado 3.4).
|
||
|
||
**Números fechados em 13/09 (medidos contra catálogo × ledger):**
|
||
|
||
| | |
|
||
|---|--:|
|
||
| itens no catálogo | 13.060 |
|
||
| com imagem de preservação | 12.991 |
|
||
| com thumbnail do DSpace | 13.059 |
|
||
| com miniatura derivada (`derivados/thumb256/`) | 13.059 |
|
||
| **no mural** | **13.059** |
|
||
| fora do mural | **1** — só `7523` |
|
||
|
||
Os 8 que faltavam (`7802`, `7814`, `7817`, `7848`, `7852`, `7861`, `7875`,
|
||
`8075`) entraram: eles **têm** `thumbnail/thumb_3.jpg` de imagem real no
|
||
bucket (109–250 KB). A afirmação anterior de que o thumbnail deles era texto
|
||
de licença estava errada — o texto era o `thumb_2` do material antigo do
|
||
finland, não o objeto do bucket.
|
||
|
||
Os 73 faltantes de preservação (achado 2.17/2.18) **não** ficam de fora do
|
||
mural: têm thumbnail do DSpace, que basta para o tile de 128px. Faltar
|
||
preservação e faltar no mural são coisas diferentes.
|
||
|
||
- [x] **Miniaturas derivadas — 13.051 itens (99,93%)**
|
||
em `s3://brasiliana/derivados/thumb256/` (256px WebP q80, ~8 KB cada,
|
||
100 MB no total). Prefixo `derivados/` deliberadamente fora de `itens/`:
|
||
`itens/` é material de origem, `derivados/` é reconstruível do zero.
|
||
Misturar os dois apagaria a procedência que o MANIFEST garante.
|
||
Composição final (medida, igual à do MANIFEST): **12.849 derivadas da
|
||
imagem de preservação** + **202 derivadas do `thumbnail/` do DSpace**
|
||
(itens com miniatura mas sem preservação coletada). Caminho até lá:
|
||
6.338 no finland a partir do bucket, 6.483 locais a partir de
|
||
`~/Downloads`, 164 do DSpace, e +66 do material do achado 2.21.
|
||
- [x] **MANIFEST.md atualizado e subido** — seção `derivados/`, os achados
|
||
2.19 e 2.20 abaixo, e a nota de que `thumbnail/` do DSpace não é
|
||
miniatura (achado 3.3). Re-subido em 12/09 com 13.051 / 99,93%, os 9
|
||
ausentes nomeados e a divergência 13.058 × 13.060 anotada.
|
||
- [x] **`build-atlas.js` parou de descartar item sem título** — a guarda
|
||
`if (!o.title) continue` escondia do mural `11749` (Museu Aeroespacial) e
|
||
`6241` (Instituto Moreira Salles), que têm imagem; o card já cai pra
|
||
"(sem título)" sozinho. 12.983 → 12.985.
|
||
- [x] **Wordmark "Acervo Brasiliana" ganhou fundo preto** — só tinha
|
||
`text-shadow`, que some sobre peça clara, e peça clara é metade do
|
||
acervo. Mesmo preto da dica do rodapé.
|
||
- [x] **Rótulo de contagem deixou de dizer "no acervo"** — dizia
|
||
"N peças no acervo" como se o mural fosse o acervo inteiro; agora diz
|
||
"peças com imagem". Foi essa frase que levantou a dúvida de que faltavam
|
||
miniaturas.
|
||
- [x] **`web/build-atlas.js` ganhou `--thumbs=DIR`** — a fonte antiga
|
||
(`~/Downloads`) é varrida pelo `subidor.py` conforme sobe, então nunca
|
||
tem o acervo inteiro ao mesmo tempo. Agora a fonte é reconstruível do
|
||
bucket.
|
||
- [x] **Ledger `subidor.sqlite` reconciliado com o bucket**: 28.553 linhas =
|
||
28.553 objetos, zero divergência nos dois sentidos. Backups em
|
||
`/private/tmp/claude-501/.../scratchpad/subidor.sqlite.bak-*`.
|
||
- 145 linhas de `thumbnail` sem objeto no bucket — são os bitstreams de
|
||
LICENÇA do achado 2.8b, removidos do bucket mas deixados no ledger
|
||
(426–815 bytes, só **5 sha256 distintos** entre os 145). Apagadas.
|
||
- 3 objetos no bucket sem linha nenhuma — inseridos, sha256 recalculado
|
||
do próprio objeto.
|
||
- 2.137 objetos `legado/` que **nenhum** ledger de upload conhecia: o
|
||
`baixador.py` só anota no `baixador.sqlite`. Importados, para que
|
||
`subidor.sqlite` seja ledger único.
|
||
- 13.051 derivados registrados como `tipo='derivado-thumb256'`.
|
||
- 63 "órfãos" de preservação/thumbnail eram **falso positivo**: gravados
|
||
durante a própria listagem do bucket. Conferidos no S3, estão lá.
|
||
|
||
### Achado 2.24 — 16 threads estouram o OpenSSL do macOS (SIGSEGV)
|
||
|
||
O `subidor-paralelo.py` morreu com **segmentation fault** no arranque da
|
||
passada das 10:56 de 13/09, e por isso **nada subiu durante horas** (o
|
||
`vigia-30min` só tentaria de novo meia hora depois). O relatório do macOS
|
||
aponta a pilha dentro do `libcrypto`:
|
||
|
||
```
|
||
libcrypto.3.dylib ossl_x509_store_ctx_get_by_subject
|
||
libcrypto.3.dylib X509_STORE_CTX_get1_issuer
|
||
libssl.3.dylib ssl_verify_internal / tls_post_process_server_certificate
|
||
```
|
||
|
||
É corrida de thread na verificação de certificado TLS quando muitas threads
|
||
fazem handshake ao mesmo tempo — bug do OpenSSL, não do nosso código. Com 4
|
||
threads subiu 50 arquivos a 19 GB/h sem problema. **Default baixado para 6
|
||
threads** no `subidor-paralelo.py`, no `vigia-30min.sh` e no
|
||
`rodar-paralelo.sh`. Não vale voltar a subir: acima de ~8 o ganho é nulo (o
|
||
link satura) e o risco de crash volta.
|
||
|
||
### Achado 2.25 — 75 itens com HTTP 500 persistente na origem (não é só o 7523)
|
||
|
||
Passada de 13/09 com a aba conectada: **108 imagens novas baixaram**, e **74
|
||
falharam com erro que não passa** — 45 × HTTP 500, 26 de rede e 3 de blob
|
||
curto, com retry sem recuperar nenhuma. Somando o `7523`, são **75 itens**
|
||
cujos bitstreams de preservação a origem não serve. Lista em
|
||
`pipeline/falhas-500-2026-09-13.txt`.
|
||
|
||
Isso substitui o achado 2.23 (que falava em 211 faltantes): os outros 136
|
||
baixaram. Nenhum dos 75 fica fora do mural — todos têm thumbnail do DSpace.
|
||
|
||
Diagnóstico que vale registrar: os 500 são **do item, não do ritmo**. Com 3
|
||
requisições simultâneas, `6545` e `6555` davam 500 enquanto `1014`, `8128`,
|
||
`10039`, `14358` e `6520` davam 200 no mesmo instante, inclusive em série com
|
||
3 s de espaçamento. E os itens ruins são vizinhos de id, então em ordem
|
||
crescente eles davam erros seguidos e disparavam o aborto automático — por
|
||
isso o motor passou a **embaralhar** a lista.
|
||
|
||
### Achado 2.26 — nova rodada sobre os 75 (13/09 noite): o que recupera e a armadilha da sequência
|
||
|
||
**Recuperados (3):** `6433` e `6520` por curl — a origem serve a ~11 KB/s, e o
|
||
"erro de rede" do navegador era estouro de tempo; `6656` no navegador na
|
||
**sequência 3** (5,4 MB, 1604×2612) — o catálogo apontava a sequência 2, que é
|
||
o texto de licença.
|
||
|
||
**Curl não escala:** depois dos 2 primeiros o Cloudflare devolveu `403`
|
||
(5.484 bytes) para 23 seguidos. Parado para não piorar o bloqueio.
|
||
|
||
**Armadilha — não aceitar "qualquer imagem em qualquer sequência".** A 1ª
|
||
versão do motor que varria as sequências 1–6 trouxe 8 "sucessos"; **7 eram o
|
||
`thumbnail/thumb_3.jpg` que já estava no bucket** (sha256 idêntico, 27–170 KB,
|
||
~800 px). Retidos antes de subir e descartados. A estrutura típica de um item
|
||
DSpace deste acervo:
|
||
|
||
| sequência | conteúdo |
|
||
|--:|---|
|
||
| 1 | imagem de preservação (nos itens com defeito: `HTTP 500`) |
|
||
| 2 | bitstream de licença (texto, 426–821 bytes) |
|
||
| 3 | thumbnail gerado pelo DSpace |
|
||
| 4–6 | inexistente |
|
||
|
||
A versão corrigida calcula SHA-256 no navegador e rejeita qualquer candidato
|
||
cujo hash já esteja no ledger como `thumbnail` ou `legado`, e grava com prefixo
|
||
`HOLD__` (o `subidor-paralelo.py` ignora) para conferência antes de subir.
|
||
|
||
### Achado 2.27 — os itens do IMS morrem num corte de 300 s; o `500` é o mesmo por sequência e por nome
|
||
|
||
Terceira rodada sobre os 75 (13/09, 21h), com um motor que registra o tempo de
|
||
cada tentativa. Três comportamentos distintos, todos da origem:
|
||
|
||
- **Corte em ~300 s sem resposta** (`rede@301s`), em 12 itens do Instituto
|
||
Moreira Salles (`8145`–`8316`). A requisição não recebe nem cabeçalho: um
|
||
teste com `Range: bytes=0-1023` estourou 18 s sem resposta, então não é
|
||
transferência lenta que cai no meio — o servidor trava antes de responder e
|
||
algo no caminho corta em 5 min. Retomar em pedaços não ajuda.
|
||
- **Corte em ~32 s** em 3 itens (`8271`, `8275`, `8277`): mesma família, limite
|
||
diferente.
|
||
- **`500` imediato** (0 s) em 11 itens (`8739`–`9148`). A forma canônica
|
||
`/bitstream/handle/20.500.12156.1/<id>/<arquivo>` — que busca pelo nome em
|
||
vez da sequência — **também devolve `500` em 0,2 s** (`8739`, `6545`). O
|
||
defeito está no servidor localizar o bitstream, não no endereçamento.
|
||
|
||
Estrutura das sequências nos itens do IMS: **1 = preservação, 2 = thumbnail**
|
||
(sem bitstream de licença, ao contrário dos do Museu Aeroespacial/Arquivo
|
||
Nacional/Fiocruz, onde 2 é licença e 3 é thumbnail). Varrer 1–6 nesses itens só
|
||
gasta tempo: 3–6 não existem.
|
||
|
||
Recuperado nesta rodada: `8396` (Biblioteca Nacional, sequência 3, 13,5 MB,
|
||
3361×4453) — mesmo padrão de `6656` e `7939`, onde o catálogo aponta a
|
||
sequência 2 (licença) e a imagem está na 3.
|
||
|
||
### Achado 2.28 — a cópia legada: só existe para a Biblioteca Nacional, e 25 "cópias" são páginas HTML
|
||
|
||
Medido em 14/09. A cópia legada vem do `objdigital.bn.br` e **só existe para
|
||
itens da Biblioteca Nacional**: 2.702 dos 13.060 têm `legacy_url`, todos da BN.
|
||
Os 16% de cobertura não são falta — os outros 10.358 itens não têm cópia legada
|
||
na origem.
|
||
|
||
| | |
|
||
|---|--:|
|
||
| elegíveis (`legacy_url` no catálogo) | 2.702 |
|
||
| no bucket como `legado/` | 2.137 |
|
||
| — imagens JPG | 1.899 (859 MB) |
|
||
| — PDF de álbum | 213 (8,3 GB) |
|
||
| — **páginas HTML** | **25** (~2,4 KB cada) |
|
||
| HTTP 404 permanente | 565 (499 jpg, 41 html, 22 pdf, 2 htm, 1 sid) |
|
||
|
||
**As 25 páginas HTML não são imagem.** São o visualizador Seadragon do
|
||
objdigital (`viewer.openDzi("GeneratedImages/dzc_output.xml")`): a imagem mora
|
||
numa pirâmide de tiles Deep Zoom ao lado da página. Itens: `586`, `587`, `630`,
|
||
`802`–`806`, `808`, `809`, `811`, `812`, `814`, `816`, `817`, `870`, `1080`,
|
||
`1083`, `1085`, `1086`, `3198`, `3545`, `3546`, `3866`, `6655`. **Todos têm
|
||
imagem de preservação no bucket**, então nenhum ficou sem imagem. Não foram
|
||
apagados — é decisão do usuário (ver pendência). Mesma família do bug de
|
||
`1081`/`1082`/`1084` (HTML de visualizador subido como legado), que foi limpo
|
||
na época; estes 25 escaparam.
|
||
|
||
**O objdigital não saiu do ar — mudou de endereço.** Em 14/09 a porta 80
|
||
fechou nos dois IPs (`200.9.175.46`, `177.223.196.160`), mas a 443 está aberta
|
||
e o site responde em **`https://objdigital.bn.br/objdigital2/acervo_digital/...`**
|
||
(o próprio visualizador Seadragon carrega o script desse prefixo). Prova de que
|
||
é a mesma fonte: 3 cópias legadas sorteadas do bucket, baixadas pelo endereço
|
||
novo, voltaram com **sha256 idêntico**. Correção de uma afirmação anterior
|
||
desta sessão: o `icon573843.jpg` usado como "controle" **não** tinha baixado
|
||
antes — ele é um dos 565 erros (item `10`), e responde `200` no endereço novo.
|
||
|
||
Consequência: **a maior parte dos 565 "404 permanentes" existe no endereço
|
||
novo.** Sondagem completa por `HEAD` em 14/09 (`pipeline/legado-caminho-novo.tsv`):
|
||
|
||
| no endereço novo | itens |
|
||
|---|--:|
|
||
| `200` com `image/jpeg` | 330 |
|
||
| `200` com `application/pdf` | 18 (o maior, 52 MB) |
|
||
| `200` com `text/html` (visualizador Deep Zoom, não arquivo) | 41 |
|
||
| `404` de verdade | 176 |
|
||
|
||
Recuperáveis como arquivo: **348** (~0,3 GB). As 41 páginas HTML somam-se às 25
|
||
já no bucket: **66 itens** cuja imagem só existe como pirâmide de tiles. A pirâmide Deep Zoom das 25 páginas HTML
|
||
também responde (item `3545`: `dzc_output.xml`, 5744×4245, tiles de 256 px).
|
||
**O `baixador.py` usa o endereço antigo (`http://…/acervo_digital/`) e precisa
|
||
ser atualizado antes de qualquer nova passada.
|
||
|
||
### Achado 2.22 — o gargalo do upload era o `subidor.py`, não o link; troca pelo paralelo
|
||
|
||
Eu tinha escrito aqui que o gargalo era o link de casa. **Errado.** Medido em
|
||
12/09 (`networkQuality`): **69 Mbps de upload (~31 GB/h)**, contra ~1,3 GB/h
|
||
reais. O `subidor.py` sobe um arquivo por vez, espera 1,5 s de "estabilidade" e
|
||
dispara **três** `aws s3 cp` por arquivo (arquivo, `metadata.json`, manifest),
|
||
cada um pagando a partida do aws-cli: ~5 s por arquivo, seja miniatura de 74 KB
|
||
ou preservação de 40 MB.
|
||
|
||
Além de lento, ele estava estragando duas coisas:
|
||
- **873 manifests com linha errada**, `derivado-thumb256/<handle>.webp`. É
|
||
consequência de eu ter registrado os derivados no ledger: ele lista toda
|
||
linha do handle, e esse caminho não existe em `itens/<handle>/`.
|
||
- **Duplicatas `(N)` voltando ao bucket** depois da limpeza do achado 2.20
|
||
(ex.: `5304`, `thumb_2.jpg` e `thumb_2 (1).jpg` com o mesmo sha256).
|
||
|
||
Substituto: `pipeline/subidor-paralelo.py` (boto3, threads, HEAD antes de apagar
|
||
o local, manifest uma vez por item, sem linha de derivado, duplicata e rejeito
|
||
registrados) + `pipeline/rodar-paralelo.sh`. Teste com boto3 contra o bucket:
|
||
PUT de 20 MB a 27 Mbps num único stream.
|
||
|
||
Testes antes de soltar, com o que cada um mostrou:
|
||
1. Lote de 30: todos eram rebaixa já presente no bucket e foram **apagados do
|
||
disco sem HEAD e sem registro**. Foi o que motivou a conferência por HEAD e a
|
||
tabela `descartados`. Para esses 30, a garantia é só o ledger, reconciliado
|
||
com o bucket às 12:50.
|
||
2. Lote de 150: 150 descartes, 150 conferidos no bucket.
|
||
3. Lote de 800: 319 enviados (319 conferidos, `image/jpeg`) + 481 descartes
|
||
conferidos; manifests dos 319 sem linha de derivado, sha do metadata batendo.
|
||
4. Primeira execução real (18:4x UTC) **interrompida**: o log mostrava TIFF
|
||
indo para `rejeitados/` (achado 2.19, correção). Assinaturas corrigidas; os 21
|
||
da quarentena (19 TIFF + 2 BMP) voltaram para a fila; relançado às 18:59.
|
||
|
||
Antes do relançamento, **180 arquivos locais (0,25 GB) já presentes no S3** foram
|
||
apagados do disco a pedido, cada um com MD5 local = ETag do objeto e registro em
|
||
`descartados`. Os outros 13.922 não estavam no bucket.
|
||
|
||
### Achado 2.23 — faltam 211 preservações depois da fila, não 73
|
||
|
||
Cruzando catálogo × (ledger ∪ fila) em 12/09: **12.849 / 13.060** itens terão
|
||
imagem de preservação quando a fila acabar (thumbnail: 13.059). Os 211:
|
||
- **71** da lista conhecida (`faltantes-preservacao.jsonl`, recusa da origem);
|
||
- **1** sem `preservation_url` no catálogo;
|
||
- **139** fora de qualquer lista. Exemplo: `10039`–`10049`, que no bucket têm só
|
||
`thumbnail/` e `metadata.json`. Desses: 8 rejeitados no upload antigo, 12 com só
|
||
`legado/`, ~119 sem rastro (nem bucket, nem fila, nem log).
|
||
|
||
Hipóteses, nenhuma confirmada: (a) o "12.986/13.059" do achado 2.16 contou
|
||
objetos `legado/` e `manifest` como preservação, porque o `awk` só excluía
|
||
`metadata.json` e thumbnail; (b) parte dos ~119 pode ser TIFF apagado pelo
|
||
`subidor.py` antigo antes do trecho que o `subidor.log` cobre.
|
||
|
||
### Achado 2.21 — a sessão de 11/09 já tinha as miniaturas no finland, e eu não olhei
|
||
|
||
`/root/brasiliana-wall/` no finland tinha, desde 11/09 13:17–13:41:
|
||
`downloads-flat/` (20.411 arquivos, todos `THUMB__`, 2,2 GB), `s3-thumbs/`
|
||
(5.168 handles do bucket, 851 MB) e o `web/atlas/` cujos quatro `.webp` batem
|
||
byte a byte com o que estava publicado no `xyz` até 12/09. **O mural de 12.552
|
||
peças foi construído lá**, a partir das miniaturas do DSpace — não de
|
||
`~/Downloads`, como o comentário do `build-atlas.js` leva a crer.
|
||
|
||
A união desses dois diretórios cobre **13.041 handles**, praticamente todos
|
||
JPEG válido. Eu puxei ~63 GB de imagens de preservação do bucket para derivar
|
||
12.985 miniaturas sem ver que 3 GB de miniatura já estavam no disco da mesma
|
||
máquina, cobrindo mais itens que o meu resultado. Aproveitados depois: +66
|
||
peças (74 no delta, 8 eram licença).
|
||
|
||
**Lição de runbook**: antes de gerar qualquer coisa no finland, `ls /root` e
|
||
`cat /root/.bash_history`. Sessões anteriores deixam material lá e nada no
|
||
projeto local registra isso.
|
||
|
||
Limpeza feita em 12/09: `/root/brasiliana-wall` e `/root/thumbs` apagados
|
||
(3,4 GB; disco 27 → 31 GB livres), depois de verificar que os 13.041 handles
|
||
estavam **todos** cobertos pelo bucket (7.038) ou pela fila do Mac (6.785) —
|
||
zero cópias exclusivas. Ficaram de pé, de propósito: `/root/.s3env` (600, de
|
||
08/09, anterior às nossas sessões — pode ser setup permanente da máquina) e
|
||
`/root/brasiliana/` (44 MB, `baixador.py` + catálogo + sqlite com 13 linhas).
|
||
|
||
### Achado 2.19 — a contaminação de LICENÇA também atinge a preservação, não só a miniatura
|
||
|
||
O achado 2.8b tratou isso como problema da trilha de thumbnail. Não é: os
|
||
handles **`20.500.12156.1/6656`** e **`20.500.12156.1/7939`** têm, em
|
||
`~/Downloads`, um "imagem.jpg" que é na verdade o bitstream de LICENÇA do
|
||
DSpace — 777 bytes de texto UTF-8 com CRLF e extensão `.jpg`. A validação de
|
||
magic bytes do `subidor.py` barra no upload, então **nada disso chegou ao
|
||
bucket**; o efeito é que esses dois itens não têm imagem coletada e ninguém
|
||
percebeu, porque a fila local os contava como prontos.
|
||
|
||
Implicação: qualquer contagem de "preservação pronta" que leia `~/Downloads`
|
||
pelo nome do arquivo está 2 itens otimista. A contagem pelo bucket está certa.
|
||
|
||
**Atualização 12/09**: não são 2, são 10. Ao derivar as miniaturas do material
|
||
do finland (achado 2.21) apareceram mais **oito** com o mesmo defeito, todos
|
||
na trilha de thumbnail: `7802`, `7814`, `7817`, `7848`, `7852`, `7861`,
|
||
`7875`, `8075`. São exatamente os itens que sobram fora do mural. O bloco
|
||
`78xx` sugere que a contaminação vem em lote, por faixa de handle — vale
|
||
varrer essa faixa inteira antes de dar o acervo por fechado.
|
||
|
||
**Correção 12/09 (noite) — a regra de validação estava errada.** O `subidor.py`
|
||
só aceitava JPEG/PNG e **apagava** o resto como "bitstream de licença". Pelo
|
||
próprio `subidor.log`, dos **40** arquivos que ele apagou assim, **só 1 era
|
||
texto de licença**: 36 eram **TIFF** (`II*\0`, ~8 MB, com extensão `.jpg`),
|
||
2 **BMP** e 1 **ZIP** (`001AAN014011.jpg.zip`, item `2384`). Resultado, item a
|
||
item (lista com URL em `pipeline/apagados-pelo-subidor-antigo.jsonl`):
|
||
- **8 itens ficaram sem nenhuma imagem de preservação**: `14358`, `14359`,
|
||
`14360`, `14361`, `14362`, `14363`, `14364`, `14368` (TIFF, série "Alb 0261").
|
||
- **18 itens** perderam só bitstreams extras (`extra_seqN`, TIFF), mas têm a
|
||
imagem principal no bucket ou na fila. `10993`/`10995` (BMP) e `2384` (ZIP)
|
||
voltaram por rebaixa e estão na fila.
|
||
O `subidor-paralelo.py` aceita TIFF/BMP/GIF/JP2/WebP pela assinatura e manda
|
||
qualquer outra coisa para `pipeline/rejeitados/` com linha na tabela
|
||
`rejeitados` — nunca apaga. Os 8 casos `78xx` de thumbnail com texto de licença
|
||
(acima) foram detectados pelo `vipsthumbnail`, não por essa regra, e continuam
|
||
valendo.
|
||
|
||
### Achado 2.20 — 3.511 duplicatas `(N)` no bucket, do Chrome rebaixando por cima
|
||
|
||
Objetos `arquivo (1).jpg` convivendo com `arquivo.jpg` byte a byte idêntico —
|
||
sufixo que o Chrome põe ao rebaixar um arquivo que já existe em `~/Downloads`,
|
||
e que o `subidor.py` subiu como se fosse nome legítimo. Confirmadas idênticas
|
||
por **ETag + tamanho** (não só tamanho) antes de apagar: 3.511 objetos,
|
||
0,39 GB, removidos do bucket e do ledger na mesma passada.
|
||
|
||
**Não** foram tocados, e precisam de decisão:
|
||
- **30 objetos** cujo gêmeo tem tamanho **diferente** — não são duplicata,
|
||
são versões distintas do mesmo nome. Precisam de conferência individual.
|
||
- **43 objetos** cujo nome tem `(N)` mas que são o **único** arquivo do item —
|
||
candidatos a renomear, nunca a apagar.
|
||
|
||
`aws s3api list-objects-v2` com `--query "Contents[].[Key,Size,ETag]"` resolve
|
||
isso sem baixar nada; o ETag é o MD5 pra upload simples (só os 2.612 objetos
|
||
multipart do bucket não servem pra comparação direta).
|
||
|
||
### Achado 3.3 — o `thumbnail/` do DSpace não é uma miniatura
|
||
|
||
Medido em amostra de 300: **721 a 3.709 px no maior lado, média 1.087**; menor
|
||
lado nunca abaixo de 462. São derivados de resolução média, não miniaturas de
|
||
listagem. Duas consequências:
|
||
|
||
1. Servem tão bem quanto a imagem de preservação pro tile de 128px do mural —
|
||
por isso deu pra cobrir os 164 itens sem preservação sem perda visível.
|
||
2. A linha "Miniatura | ~13.005 elegíveis" do MANIFEST descreve ~1 MB por
|
||
arquivo, não ~8 KB. Quem ler esperando miniatura vai baixar 100x mais do
|
||
que imagina.
|
||
|
||
### Achado 3.4 — publicar o mural agora exige mandar o `atlas/` junto
|
||
|
||
O `wall.html` de 12:32 passou a derivar `handle` (chave do map) e `source_url`
|
||
(mesmo prefixo pra todo item) em JS, em vez de lê-los de cada peça. O
|
||
`summarize()` do `build-atlas.js` acompanhou. Resultado: **8,2 MB de map com
|
||
12.983 peças contra 10 MB com 12.552** — menos peso com 431 peças a mais.
|
||
|
||
Mas isso quebra a nota antiga de que "o `atlas/` já está commitado lá e não
|
||
muda a cada alteração do mural": map e `wall.html` agora são um par acoplado
|
||
por formato. Publicar um sem o outro deixa o mural sem link e sem busca por
|
||
handle.
|
||
|
||
## Pendente
|
||
|
||
- [ ] **`dc.rights` ausente em 5.833 itens (44,7%)** — confirmado como característica real da fonte, não bug. Só precisa ficar documentado no MANIFEST final, sem ação de reparo.
|
||
- [ ] **`web/build-atlas.js` não está versionado em lugar nenhum** — `brasiliano/` não é repo git e o `xyz` só recebe o `index.html` e o `atlas/`. A correção do `summarize()`/`pickAll()` (achado 3.1) vive só no disco desta máquina; um `rm` leva junto. Mesma exposição vale para `pipeline/` inteiro.
|
||
- [ ] **Encolher mais o `atlas-map.json` só com dicionário** (achado 3.2): 8,00 → 5,14 MB crus por ~120 KB no fio. Só vale se o `JSON.parse` no celular virar problema medido — não antes.
|
||
- [ ] **30 objetos `(N)` com tamanho diferente do gêmeo** (achado 2.20) — conferência individual, não dá pra automatizar com segurança.
|
||
- [ ] **43 objetos `(N)` que são o único arquivo do item** (achado 2.20) — renomear tirando o sufixo, pra o nome não mentir sobre haver um original ao lado.
|
||
- [ ] **Handles `6656` e `7939` sem imagem de preservação** (achado 2.19) — rebaixar; o que há hoje na fila local é o bitstream de licença.
|
||
- [ ] **Conferir os códigos HTTP que nunca foram tabulados** — o runbook do achado 2.18 manda rodar `FALTA.erros()` no console e rotear por código (404 → reparar URL, 403 → bloqueio de origem, 500 → transitório, retomar). **Esse passo nunca foi executado**: o achado 2.18 concluiu "é recusa da origem" a partir do `status()` agregado, não da tabela de erros por handle. Sem isso não dá pra saber quantos dos 71 são reparáveis.
|
||
- [ ] **Reconferir o handle `7523` (HTTP 500)** — registrado uma vez e tratado como falha permanente da origem desde então, mas 500 costuma ser transitório e nunca houve segunda tentativa. **Não dá pra testar por `curl`, e agora está provado com controle** (12/09): com User-Agent, Accept, Accept-Language e Referer de Chrome, o `7523` devolve `403` de 5.484 bytes (página do Cloudflare) — e o item `10`, que sabidamente funciona e está no bucket, devolve **exatamente o mesmo `403` de 5.484 bytes**. É bloqueio geral a requisição sem sessão real, nada específico do `7523`. Precisa da aba; a extensão Claude-in-Chrome seguia desconectada em duas tentativas no mesmo dia.
|
||
- [ ] **Reconciliar 13.058 vs 13.060** — o MANIFEST fala em 13.058 itens reais, o `catalogo.jsonl` tem 13.060 handles distintos. Diferença de 2 nunca explicada; anotada no MANIFEST como pendência.
|
||
- [ ] **Depois que os 212 faltantes baixarem e subirem** (pedido do usuário, 13/09):
|
||
1. conferir se **todos** os itens têm miniatura em `derivados/thumb256/` e gerar
|
||
as que faltarem (as novas preservações não têm derivado ainda);
|
||
2. **regerar o mural 3D** com o conjunto completo — de preferência derivando
|
||
no finland direto do bucket (`itens/*/preservacao/*`), que é tráfego
|
||
intra-Hetzner, em vez de puxar pro Mac;
|
||
3. republicar `wall.html` + `atlas/` no `rafapolo/xyz` (par acoplado, achado 3.4).
|
||
- [ ] **Quando o `rodar-paralelo.sh` terminar** (log termina em `===== ... FIM`):
|
||
1. conferir no log as três fases sem `!!!`, a linha `FIM da fila` (enviados,
|
||
duplicatas, rejeitados, falhas) e os `pendente:` que ficaram no disco;
|
||
2. `ls pipeline/rejeitados/` + `select * from rejeitados` — só deve ter texto
|
||
de licença; imagem ali é bug de assinatura;
|
||
3. reconciliar ledger × bucket (mesmo diff do achado de 12/09: 0 divergências
|
||
nos dois sentidos) e amostrar manifests (`grep derivado` = 0);
|
||
4. varrer `~/Downloads`: apagar o que tiver MD5 = ETag no bucket (mesmo
|
||
método dos 180 de 12/09), manter o resto;
|
||
5. atualizar a tabela de contagens do MANIFEST com os números fechados.
|
||
- [ ] **Rebaixar o que o `subidor.py` antigo apagou** (achado 2.19, correção): 8 itens sem preservação (`14358`–`14364`, `14368`) + ~20 `extra_seqN` em TIFF — lista com URL em `pipeline/apagados-pelo-subidor-antigo.jsonl`. Precisa da aba do navegador.
|
||
- [x] ~~Recontar os faltantes de preservação (211)~~ — feito em 13/09: 136 baixaram, sobraram **75** com defeito de origem (achado 2.25), listados em `pipeline/falhas-500-2026-09-13.txt`. Script usado: `pipeline/baixar-faltantes-v3.js` (ou o motor `F5` colado direto na aba).
|
||
- [ ] **`subidor.py` antigo: aposentar ou corrigir a regra de assinatura** antes de qualquer uso futuro — do jeito que está, apaga TIFF.
|
||
- [ ] **Rebaixar os 69 que sobraram** (achados 2.25–2.27; 6 recuperados em 13/09 à noite) quando a origem se recuperar — todos têm thumbnail, falta a preservação. Um `FALTA3.retomar()`/`F5.iniciar()` na aba basta; o que já baixou fica marcado no `localStorage` (`bras_faltantes_v3`).
|
||
- [ ] **Handle `8396` sem imagem e sem diagnóstico** — não está em `pipeline/faltantes-preservacao.jsonl` nem nas exceções conhecidas do MANIFEST; apareceu ao cruzar catálogo × miniaturas. Precisa de uma tentativa avulsa pra saber se é recusa da origem (achado 2.18) ou outra coisa.
|
||
- [ ] **`.git` do `rafapolo/xyz` está em 2,5 GB** e cada publicação do atlas soma ~51 MB permanentes (binário não faz delta). Se o mural for republicar atlas com frequência, tirar os `.webp` do git — LFS, ou servir do próprio bucket, que já tem os thumbs em `derivados/thumb256/`.
|
||
- [ ] **Crawl da Fase 1 pode ter mais buracos** (achado 2.10 do PLANO.md): 2 itens reais (`1799`, `4798`) foram achados faltando no catálogo só porque o WordPress os citava — já corrigidos, mas não existe verificação exaustiva de que a enumeração por `browse?type=title` pegou 100% de cada comunidade. Sem ideia barata de auditar isso por completo agora.
|
||
|
||
- [ ] **Baixar os 348 legados recuperáveis pelo endereço novo** (achado 2.28) — atualizar antes o `baixador.py` para `https://objdigital.bn.br/objdigital2/acervo_digital/`.
|
||
- [ ] **Decidir o que fazer com as 66 páginas HTML de visualizador** (25 já em `legado/` + 41 no endereço novo; achado 2.28): apagar do bucket e do ledger (não são imagem), ou montar a imagem a partir dos tiles Deep Zoom quando o objdigital voltar. Todos os 25 itens já têm preservação.
|
||
|
||
## Ao final (não fazer antes do download terminar)
|
||
|
||
- [x] Gerar `MANIFEST.md` (o que é, como foi coletado, quando) — feito; atualizado em 2026-09-12 com `derivados/` e os achados 2.19/2.20/3.3
|
||
- [ ] Confirmar no bucket que a contagem de itens/checksums bate com os 13.058 itens reais esperados
|
||
|
||
## Notas técnicas úteis pra retomar
|
||
|
||
- Downloads automáticos do Chrome exigem permissão manual em `chrome://settings/content/automaticDownloads` (site adicionado: `https://brasilianafotografica.bn.gov.br`) — já concedida, vale pra novos scripts também, não só o original.
|
||
- `BRAS`, `BAIXA`, `BAIXA_THUMB`, `BAIXA_EXTRA` — todos no `window` da mesma aba, mesmo IndexedDB (`bras`). Todos sobrevivem a fechar/reabrir a aba (dado fica salvo), mas o *loop* para e precisa recolar o script certo pra retomar.
|
||
- **Regerar as miniaturas derivadas** (tudo reconstruível, nada precisa ser
|
||
guardado): no finland, `apt install libvips-tools parallel` + s5cmd; pra cada
|
||
chave de `itens/*/preservacao/*`, `s5cmd cat` num tmp e
|
||
`vipsthumbnail "$TMP" --size 256 --output "$OUT[Q=80,strip]"`, com
|
||
`parallel -j 10`. ~640 thumbs/min, tráfego intra-Hetzner (bucket é `hel1`, o
|
||
finland também) — não passa pelo link de casa. O que ainda está só em
|
||
`~/Downloads` sai mais barato derivado localmente com `magick`: mandar os
|
||
71 GB pro finland custaria os mesmos dias do upload pendente.
|
||
- **Cuidado ao parsear `s5cmd ls`**: nomes de arquivo do acervo têm espaço
|
||
**duplo** (`48094 Álbum 311 - ...jpg`). `awk '{$1="";$2="";$3=""; print}'
|
||
recolapsa esses espaços e produz chaves que não existem — 31 downloads
|
||
falharam assim antes de eu perceber. Use
|
||
`sed -E 's|^[0-9/]+ +[0-9:]+ +[0-9]+ ||'`, que preserva o resto literal.
|
||
- **`sqlite3` desta máquina tem `.sqliterc` forçando modo box**: qualquer
|
||
script que leia a saída precisa de `sqlite3 -init /dev/null -noheader
|
||
-separator $'\t'`, senão vem borda de tabela no lugar dos dados (me custou
|
||
um diff inteiro de falso positivo).
|
||
- **`until ! pgrep -f X; do sleep; done` nunca termina**: a linha de comando do
|
||
próprio loop contém `X`, então o `pgrep -f` se acha e a condição nunca vira
|
||
falsa. Um loop desses ficou girando horas depois do trabalho acabar (12/09,
|
||
morto à mão). Esperar por PID (`while kill -0 $PID`) ou usar `pgrep -f
|
||
'[t]humb-local'` para o padrão não casar consigo mesmo.
|
||
- **Parar processo lançado em background: SIGTERM, não SIGINT.** Processo
|
||
iniciado com `&` num shell não interativo herda o SIGINT ignorado (o
|
||
`subidor.py` ficou 60 s vivo depois do `kill -INT`). Com SIGTERM, o `aws s3
|
||
cp` filho termina órfão, o que é inofensivo: sem linha no ledger e sem
|
||
apagar o local, o próximo uploader sobe de novo a mesma chave.
|
||
- **Upload de casa: 69 Mbps** (`networkQuality -s`, 12/09); download 312 Mbps.
|
||
- **Assinaturas aceitas como imagem** (`subidor-paralelo.py`): JPEG `FFD8`, PNG,
|
||
TIFF `II*\0`/`MM\0*`, GIF, JP2, BMP `BM`, WebP. Content-Type sai do conteúdo,
|
||
não da extensão (TIFF chega com nome `.jpg`).
|
||
- **`vipsthumbnail --output` é relativo à pasta do arquivo de entrada**, não ao
|
||
diretório corrente: `vipsthumbnail tmp/x.img --output out/y.webp` tenta
|
||
gravar em `tmp/out/y.webp` e falha. Usar caminho absoluto na saída. (Custou
|
||
uma miniatura "refeita" que não foi refeita, em 13/09.)
|
||
- Scripts-fonte em `pipeline/`: `coletor.js` / `coletor-resume.js` (metadados), `baixador-navegador.js` (preservação, todos os itens), `baixador-thumb.js` (miniaturas), `baixador-extras.js` (2a+ imagem de itens multi-bitstream), `reparo-multibitstream.js` (enumera bitstreams reais de um item).
|