feat: acervo Brasiliana Fotográfica coletado e mural 3D no ar

Captura integral do acervo da Brasiliana Fotográfica (Biblioteca Nacional)
para s3://brasiliana: 13.060 itens, 12.985 com imagem de preservação, 13.059
com miniatura derivada, 2.137 cópias legadas e 636 posts do blog. 41.444
objetos no ledger.

Os 75 itens sem preservação estão em errors.md: a página do item responde
200, mas a origem serve o bitstream com HTTP 500. Nenhum fica fora do mural,
porque todos têm thumbnail.

Não versionado, por viver no bucket ou ser reconstruível: catálogo,
posts .jsonl, atlas .webp, sqlite de estado e logs. Credenciais ficam de
fora pelo .gitignore.
This commit is contained in:
rodado
2026-09-13 20:17:02 +02:00
commit 290c4cc21d
690 changed files with 81187 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
AWS_ACCESS_KEY_ID=<preencher>
AWS_SECRET_ACCESS_KEY=<preencher>
AWS_DEFAULT_REGION=<preencher>
S3_ENDPOINT=https://hel1.your-objectstorage.com
ARCHIVE_BUCKET=brasiliana
@@ -0,0 +1,40 @@
{"handle": "20.500.12156.1/10993", "tipo": "preservacao", "nome": "Alb 0279 113 CT.jpg", "formato": "bmp", "ja_no_bucket": false, "na_fila": true, "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10993/1/Alb%200279%20113%20CT.jpg"}
{"handle": "20.500.12156.1/10995", "tipo": "preservacao", "nome": "Alb 0279 106 CT.jpg", "formato": "bmp", "ja_no_bucket": false, "na_fila": true, "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10995/1/Alb%200279%20106%20CT.jpg"}
{"handle": "20.500.12156.1/14293", "tipo": "preservacao", "nome": "extra_seq1.jpg", "formato": "tiff", "ja_no_bucket": false, "na_fila": false, "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14293/2/imagem.jpg"}
{"handle": "20.500.12156.1/14295", "tipo": "preservacao", "nome": "extra_seq1.jpg", "formato": "tiff", "ja_no_bucket": true, "na_fila": false, "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14295/2/imagem.jpg"}
{"handle": "20.500.12156.1/14357", "tipo": "preservacao", "nome": "extra_seq1.jpg", "formato": "tiff", "ja_no_bucket": false, "na_fila": false, "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14357/3/imagem.jpg"}
{"handle": "20.500.12156.1/14357", "tipo": "preservacao", "nome": "extra_seq2.jpg", "formato": "tiff", "ja_no_bucket": false, "na_fila": false, "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14357/3/imagem.jpg"}
{"handle": "20.500.12156.1/14365", "tipo": "preservacao", "nome": "extra_seq2.jpg", "formato": "tiff", "ja_no_bucket": false, "na_fila": false, "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14365/1/imagem.jpg"}
{"handle": "20.500.12156.1/14365", "tipo": "preservacao", "nome": "extra_seq3.jpg", "formato": "tiff", "ja_no_bucket": false, "na_fila": false, "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14365/1/imagem.jpg"}
{"handle": "20.500.12156.1/14365", "tipo": "preservacao", "nome": "extra_seq4.jpg", "formato": "tiff", "ja_no_bucket": false, "na_fila": false, "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14365/1/imagem.jpg"}
{"handle": "20.500.12156.1/14365", "tipo": "preservacao", "nome": "extra_seq5.jpg", "formato": "tiff", "ja_no_bucket": false, "na_fila": false, "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14365/1/imagem.jpg"}
{"handle": "20.500.12156.1/14366", "tipo": "preservacao", "nome": "extra_seq2.jpg", "formato": "tiff", "ja_no_bucket": false, "na_fila": false, "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14366/1/imagem.jpg"}
{"handle": "20.500.12156.1/14367", "tipo": "preservacao", "nome": "extra_seq2.jpg", "formato": "tiff", "ja_no_bucket": false, "na_fila": false, "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14367/1/imagem.jpg"}
{"handle": "20.500.12156.1/14408", "tipo": "preservacao", "nome": "extra_seq2.jpg", "formato": "texto/licenca", "ja_no_bucket": false, "na_fila": false, "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14408/1/imagem.jpg"}
{"handle": "20.500.12156.1/14434", "tipo": "preservacao", "nome": "extra_seq1.jpg", "formato": "tiff", "ja_no_bucket": true, "na_fila": false, "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14434/2/imagem.jpg"}
{"handle": "20.500.12156.1/14434", "tipo": "preservacao", "nome": "extra_seq3.jpg", "formato": "tiff", "ja_no_bucket": true, "na_fila": false, "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14434/2/imagem.jpg"}
{"handle": "20.500.12156.1/14434", "tipo": "preservacao", "nome": "extra_seq4.jpg", "formato": "tiff", "ja_no_bucket": true, "na_fila": false, "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14434/2/imagem.jpg"}
{"handle": "20.500.12156.1/14434", "tipo": "preservacao", "nome": "extra_seq5.jpg", "formato": "tiff", "ja_no_bucket": true, "na_fila": false, "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14434/2/imagem.jpg"}
{"handle": "20.500.12156.1/14437", "tipo": "preservacao", "nome": "extra_seq1.jpg", "formato": "tiff", "ja_no_bucket": true, "na_fila": false, "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14437/2/imagem.jpg"}
{"handle": "20.500.12156.1/14437", "tipo": "preservacao", "nome": "extra_seq3.jpg", "formato": "tiff", "ja_no_bucket": true, "na_fila": false, "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14437/2/imagem.jpg"}
{"handle": "20.500.12156.1/14514", "tipo": "preservacao", "nome": "extra_seq1.jpg", "formato": "tiff", "ja_no_bucket": false, "na_fila": false, "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14514/2/imagem.jpg"}
{"handle": "20.500.12156.1/14515", "tipo": "preservacao", "nome": "extra_seq2.jpg", "formato": "tiff", "ja_no_bucket": false, "na_fila": false, "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14515/1/imagem.jpg"}
{"handle": "20.500.12156.1/14515", "tipo": "preservacao", "nome": "extra_seq3.jpg", "formato": "tiff", "ja_no_bucket": false, "na_fila": false, "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14515/1/imagem.jpg"}
{"handle": "20.500.12156.1/14516", "tipo": "preservacao", "nome": "extra_seq2.jpg", "formato": "tiff", "ja_no_bucket": false, "na_fila": false, "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14516/1/imagem.jpg"}
{"handle": "20.500.12156.1/14518", "tipo": "preservacao", "nome": "extra_seq1.jpg", "formato": "tiff", "ja_no_bucket": false, "na_fila": false, "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14518/3/imagem.jpg"}
{"handle": "20.500.12156.1/14518", "tipo": "preservacao", "nome": "extra_seq2.jpg", "formato": "tiff", "ja_no_bucket": false, "na_fila": false, "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14518/3/imagem.jpg"}
{"handle": "20.500.12156.1/14518", "tipo": "preservacao", "nome": "extra_seq4.jpg", "formato": "tiff", "ja_no_bucket": false, "na_fila": false, "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14518/3/imagem.jpg"}
{"handle": "20.500.12156.1/14519", "tipo": "preservacao", "nome": "extra_seq1.jpg", "formato": "tiff", "ja_no_bucket": false, "na_fila": false, "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14519/2/imagem.jpg"}
{"handle": "20.500.12156.1/14520", "tipo": "preservacao", "nome": "extra_seq1.jpg", "formato": "tiff", "ja_no_bucket": false, "na_fila": false, "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14520/2/imagem.jpg"}
{"handle": "20.500.12156.1/14520", "tipo": "preservacao", "nome": "extra_seq3.jpg", "formato": "tiff", "ja_no_bucket": false, "na_fila": false, "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14520/2/imagem.jpg"}
{"handle": "20.500.12156.1/14520", "tipo": "preservacao", "nome": "extra_seq4.jpg", "formato": "tiff", "ja_no_bucket": false, "na_fila": false, "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14520/2/imagem.jpg"}
{"handle": "20.500.12156.1/14521", "tipo": "preservacao", "nome": "extra_seq2.jpg", "formato": "tiff", "ja_no_bucket": false, "na_fila": false, "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14521/1/imagem.jpg"}
{"handle": "20.500.12156.1/14358", "tipo": "preservacao", "nome": "Alb 0261 139.jpg", "formato": "tiff", "ja_no_bucket": false, "na_fila": false, "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14358/1/Alb%200261%20139.jpg"}
{"handle": "20.500.12156.1/14359", "tipo": "preservacao", "nome": "Alb 0261 136.jpg", "formato": "tiff", "ja_no_bucket": false, "na_fila": false, "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14359/1/Alb%200261%20136.jpg"}
{"handle": "20.500.12156.1/14360", "tipo": "preservacao", "nome": "Alb 0261 141.jpg", "formato": "tiff", "ja_no_bucket": false, "na_fila": false, "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14360/1/Alb%200261%20141.jpg"}
{"handle": "20.500.12156.1/14361", "tipo": "preservacao", "nome": "Alb 0261 142.jpg", "formato": "tiff", "ja_no_bucket": false, "na_fila": false, "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14361/1/Alb%200261%20142.jpg"}
{"handle": "20.500.12156.1/14362", "tipo": "preservacao", "nome": "Alb 0261 143.jpg", "formato": "tiff", "ja_no_bucket": false, "na_fila": false, "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14362/1/Alb%200261%20143.jpg"}
{"handle": "20.500.12156.1/14363", "tipo": "preservacao", "nome": "Alb 0261 144.jpg", "formato": "tiff", "ja_no_bucket": false, "na_fila": false, "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14363/1/Alb%200261%20144.jpg"}
{"handle": "20.500.12156.1/14364", "tipo": "preservacao", "nome": "Alb 0261 146.jpg", "formato": "tiff", "ja_no_bucket": false, "na_fila": false, "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14364/1/Alb%200261%20146.jpg"}
{"handle": "20.500.12156.1/14368", "tipo": "preservacao", "nome": "Alb 0261 163.jpg", "formato": "tiff", "ja_no_bucket": false, "na_fila": false, "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14368/1/Alb%200261%20163.jpg"}
{"handle": "20.500.12156.1/2384", "tipo": "preservacao", "nome": "001AAN014011.jpg.zip", "formato": "zip", "ja_no_bucket": false, "na_fila": false, "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/2384/1/001AAN014011.jpg"}
+86
View File
@@ -0,0 +1,86 @@
/* ===================================================================
Baixa as imagens EXTRAS de itens com mais de um bitstream real
(achado do reparo-multibitstream.js: 114 itens, a maioria com 2,
alguns com 3). A maior de cada item ja e o preservation_url, coberta
pelo baixador-navegador.js -- este script pega as demais.
PRE-REQUISITO: reparo-multibitstream.js ja rodado (precisa dos
campos all_preservation_urls / all_preservation_sizes no IndexedDB).
Nomeia como extra_seqN.jpg -- sobe pra preservacao/extra_seqN.jpg
junto com a imagem principal do item (subidor.py nao precisa de
ajuste, ja trata qualquer nome que nao comece com THUMB__).
=================================================================== */
(() => {
const PAUSA = 3000;
const sleep = ms => new Promise(r => setTimeout(r, ms));
const jit = () => PAUSA + Math.floor(Math.random() * 1000);
let db;
const abrir = () => new Promise((res, rej) => {
const q = indexedDB.open('bras', 1);
q.onsuccess = () => res(q.result); q.onerror = () => rej(q.error);
});
const getAll = st => new Promise((res, rej) => {
const r = db.transaction(st).objectStore(st).getAll();
r.onsuccess = () => res(r.result); r.onerror = () => rej(r.error);
});
const S = {fase: 'ocioso', feito: 0, erros: 0, total: 0, atual: '', parar: false};
const FEITOS = new Set(JSON.parse(localStorage.getItem('bras_extras') || '[]'));
const marcar = k => { FEITOS.add(k); localStorage.setItem('bras_extras', JSON.stringify([...FEITOS])); };
async function alvos() {
const itens = await getAll('itens');
const tarefas = [];
for (const o of itens) {
if (!o.all_preservation_urls || o.all_preservation_urls.length < 2) continue;
o.all_preservation_urls.forEach((u, idx) => {
if (u === o.preservation_url) return; // a maior ja e coberta pelo BAIXA principal
tarefas.push({handle: o.handle, url: u, idx});
});
}
return tarefas;
}
async function iniciar() {
S.parar = false; S.fase = 'baixando';
const lista = (await alvos()).filter(t => !FEITOS.has(`${t.handle}#${t.idx}`));
S.total = lista.length;
console.log(`${lista.length} imagens extras a baixar (itens com multiplos bitstreams)`);
for (let i = 0; i < lista.length; i++) {
if (S.parar) { S.fase = 'parado'; return; }
const o = lista[i];
S.atual = `${i + 1}/${lista.length}`;
const chave = `${o.handle}#${o.idx}`;
try {
const r = await fetch(o.url, {credentials: 'same-origin'});
if (!r.ok) throw new Error('HTTP ' + r.status);
const b = await r.blob();
const seq = o.url.match(/\/(\d+)\/[^/]+$/)?.[1] || o.idx;
const nome = `extra_seq${seq}.jpg`;
const arqNome = `BRAS__${o.handle.replace(/\//g, '_')}__${nome}`;
const a = document.createElement('a');
a.href = URL.createObjectURL(b); a.download = arqNome;
document.body.appendChild(a); a.click(); a.remove();
setTimeout(() => URL.revokeObjectURL(a.href), 30000);
marcar(chave); S.feito++;
} catch (e) {
S.erros++; console.warn(` ${chave}: ${e.message}`);
}
if (i % 20 === 0) console.log(` ${i}/${lista.length} ok=${S.feito} erro=${S.erros}`);
await sleep(jit());
}
S.fase = 'concluido';
console.log(`\nExtras concluido. ok=${S.feito} erro=${S.erros}`);
}
window.BAIXA_EXTRA = {
quantos: async () => { const a = await alvos();
return {total: a.length, jaFeitos: FEITOS.size}; },
iniciar, status: () => ({...S}),
parar: () => { S.parar = true; },
};
(async () => { db = await abrir(); })();
})();
+103
View File
@@ -0,0 +1,103 @@
/* ===================================================================
TODAS as trilhas -- baixa pelo navegador, ponto.
MUDANCA DE PLANO (2026-09-10): o achado 2.4 do PLANO.md ("bitstreams
passam por curl") nao se sustentou. Testado ao vivo: item nunca antes
acessado, buscado pelo navegador (200 OK) e IMEDIATAMENTE depois via
curl na Finlandia -- 403, pagina real do Cloudflare ("Attention
Required"). O unico item que passa por curl (6066) so passa porque
ja tinha sido baixado com sucesso antes e ficou em cache de borda
(cf-cache-status: HIT) -- nao e regra geral, e sobra de um caso.
Ou seja: SEM navegador vivo, sem imagem, pra praticamente tudo --
nao so os nomes com encoding (trilha B). baixador.py na Finlandia
fica pausado ate se achar outra saida.
Por isso esta versao pega TODO item com preservation_url, nao so os
de nome com encoding. A logica de baixar/nomear/salvar e a mesma que
ja validava a trilha B -- so mudou o filtro de quem entra na fila.
COMO USAR (depois que o coletor.js terminar)
1. Na mesma aba do coletor, cole este arquivo.
2. BAIXA.quantos() quantos itens precisam desta trilha
3. BAIXA.iniciar() comeca; os arquivos caem na pasta Downloads
4. Em outro terminal, rode python3 subidor.py --catalogo catalogo.jsonl
Ele sobe cada arquivo pro S3 e APAGA na hora, para o disco nao encher.
BAIXA.status() / BAIXA.parar() / BAIXA.retomar()
=================================================================== */
(() => {
const PAUSA = 3000; // arquivos sao grandes; va devagar
const sleep = ms => new Promise(r => setTimeout(r, ms));
const jit = () => PAUSA + Math.floor(Math.random() * 1000);
let db;
const abrir = () => new Promise((res, rej) => {
const q = indexedDB.open('bras', 1);
q.onupgradeneeded = e => { const d = e.target.result;
if (!d.objectStoreNames.contains('fila')) d.createObjectStore('fila');
if (!d.objectStoreNames.contains('itens')) d.createObjectStore('itens'); };
q.onsuccess = e => res(e.target.result); q.onerror = () => rej(q.error); });
const getAll = st => new Promise((res, rej) => {
const r = db.transaction(st).objectStore(st).getAll();
r.onsuccess = () => res(r.result); r.onerror = () => rej(r.error); });
/* curl nao e mais confiavel pra quase nada (ver nota no topo) --
qualquer item com preservation_url entra na fila, nome simples
ou com encoding, tanto faz. */
const precisaNavegador = u => !!u;
const S = {fase: 'ocioso', feito: 0, erros: 0, total: 0, atual: '', parar: false};
const FEITOS = new Set(JSON.parse(localStorage.getItem('bras_baixados') || '[]'));
const marcar = h => { FEITOS.add(h);
localStorage.setItem('bras_baixados', JSON.stringify([...FEITOS])); };
async function alvos() {
const itens = await getAll('itens');
return itens.filter(o => !o.erro && precisaNavegador(o.preservation_url));
}
async function iniciar() {
S.parar = false; S.fase = 'baixando';
const lista = (await alvos()).filter(o => !FEITOS.has(o.handle));
S.total = lista.length;
console.log(`${lista.length} arquivos por esta trilha (~${(lista.length*PAUSA/3600000).toFixed(1)} h)`);
for (let i = 0; i < lista.length; i++) {
if (S.parar) { S.fase = 'parado'; return; }
const o = lista[i];
S.atual = `${i + 1}/${lista.length}`;
try {
const r = await fetch(o.preservation_url, {credentials: 'same-origin'});
if (!r.ok) throw new Error('HTTP ' + r.status);
const b = await r.blob();
const orig = decodeURIComponent(o.preservation_url.split('/').pop());
// o prefixo carrega o handle: e assim que o subidor sabe onde guardar
const nome = `BRAS__${o.handle.replace(/\//g, '_')}__${orig}`.replace(/[\\/:*?"<>|]/g, '_');
const a = document.createElement('a');
a.href = URL.createObjectURL(b); a.download = nome;
document.body.appendChild(a); a.click(); a.remove();
setTimeout(() => URL.revokeObjectURL(a.href), 30000);
marcar(o.handle); S.feito++;
} catch (e) {
S.erros++; console.warn(` ${o.handle}: ${e.message}`);
if (/HTTP 4|HTTP 5/.test(String(e))) await sleep(60000);
}
if (i % 25 === 0) console.log(` ${i}/${lista.length} ok=${S.feito} erro=${S.erros}`);
await sleep(jit());
}
S.fase = 'concluido';
console.log(`\nConcluido. ok=${S.feito} erro=${S.erros}`);
}
window.BAIXA = {
quantos: async () => { const a = await alvos();
return {precisamDoNavegador: a.length, jaBaixados: FEITOS.size, restam: a.length - FEITOS.size}; },
iniciar, status: () => ({...S}),
parar: () => { S.parar = true; }, retomar: () => iniciar(),
limparMarcas: () => localStorage.removeItem('bras_baixados'),
};
(async () => { db = await abrir();
const q = await window.BAIXA.quantos();
console.log('BAIXA pronto:', q, '\nRode BAIXA.iniciar() para comecar.');
})();
})();
+137
View File
@@ -0,0 +1,137 @@
/* ===================================================================
v2 -- a v1 confiava cegamente que seq_master+1 e a miniatura, so
descartando se viesse GRANDE demais. Bug real (2026-09-10): pra
muitos itens (varias instituicoes -- visto em Marinha e IMS), essa
sequencia cai no bitstream de LICENCA do DSpace (texto puro, poucas
centenas de bytes) em vez da miniatura. 62% dos thumbs de um lote
sairam errados assim -- pequenos o bastante pra passar no filtro de
tamanho, mas nao sao imagem nenhuma.
FIX: valida os magic bytes (JPEG/PNG) antes de aceitar. Se a
primeira tentativa (seq+1) nao for imagem, tenta seq+2 e seq+3 (caso
a licenca esteja no meio). Se nenhuma bater, cai pro metodo confiavel
do achado 2.7 (extrai o <img src> da pagina do item).
O subidor.py TAMBEM valida antes de subir agora (defesa em camadas)
-- mas o ideal e nao gerar o lixo em primeiro lugar.
=================================================================== */
(() => {
const BASE = 'https://brasilianafotografica.bn.gov.br/brasiliana';
const PREFIX = '20.500.12156.1';
const PAUSA = 1500;
const sleep = ms => new Promise(r => setTimeout(r, ms));
const jit = () => PAUSA + Math.floor(Math.random() * 500);
let db;
const abrir = () => new Promise((res, rej) => {
const q = indexedDB.open('bras', 1);
q.onsuccess = () => res(q.result); q.onerror = () => rej(q.error);
});
const getAll = st => new Promise((res, rej) => {
const r = db.transaction(st).objectStore(st).getAll();
r.onsuccess = () => res(r.result); r.onerror = () => rej(r.error);
});
const S = {fase: 'ocioso', feito: 0, erros: 0, total: 0, atual: '', parar: false};
const FEITOS = new Set(JSON.parse(localStorage.getItem('bras_thumbs_v2') || '[]'));
const marcar = h => { FEITOS.add(h); localStorage.setItem('bras_thumbs_v2', JSON.stringify([...FEITOS])); };
function seqDoUrl(u) {
const m = u.match(/\/bitstream\/[^/]+\/[^/]+\/(\d+)\//);
return m ? +m[1] : null;
}
async function ehImagem(blob) {
const buf = new Uint8Array(await blob.slice(0, 8).arrayBuffer());
if (buf[0] === 0xFF && buf[1] === 0xD8) return true; // JPEG
if (buf[0]===0x89 && buf[1]===0x50 && buf[2]===0x4E && buf[3]===0x47) return true; // PNG
return false;
}
async function tentaSequencia(handle, seq) {
const url = `${BASE}/bitstream/${PREFIX}/${handle}/${seq}/thumb.jpg`;
const r = await fetch(url, {credentials: 'same-origin'});
if (!r.ok) return null;
const b = await r.blob();
if (b.size > 2 * 1024 * 1024) return null; // grande demais pra thumb
if (!(await ehImagem(b))) return null; // achado 2026-09-10: pode ser bitstream de licenca
return {blob: b, nomeSeq: seq};
}
async function viaImgDaPagina(handle) {
const r = await fetch(`${BASE}/handle/${PREFIX}/${handle}`, {credentials: 'same-origin'});
if (!r.ok) return null;
const html = await r.text();
const d = new DOMParser().parseFromString(html, 'text/html');
const img = d.querySelector('.file-wrapper img');
if (!img) return null;
let u;
try { u = new URL(img.getAttribute('src'), location.href); } catch(e) { return null; }
const rr = await fetch(u.href, {credentials: 'same-origin'});
if (!rr.ok) return null;
const b = await rr.blob();
if (!(await ehImagem(b))) return null;
return {blob: b, nomeSeq: 'pagina'};
}
async function alvos() {
const itens = await getAll('itens');
return itens.filter(o => {
if (o.erro || !o.preservation_url) return false;
const seqs = (o.all_preservation_urls || [o.preservation_url]).map(seqDoUrl).filter(Boolean);
return seqs.length > 0;
});
}
async function iniciar() {
S.parar = false; S.fase = 'baixando';
const lista = (await alvos()).filter(o => !FEITOS.has(o.handle));
S.total = lista.length;
console.log(`${lista.length} miniaturas a checar/baixar (v2, com validacao)`);
for (let i = 0; i < lista.length; i++) {
if (S.parar) { S.fase = 'parado'; return; }
const o = lista[i];
const h = o.handle.split('/').pop();
S.atual = `${i + 1}/${lista.length}`;
try {
const seqs = (o.all_preservation_urls || [o.preservation_url]).map(seqDoUrl).filter(Boolean);
const maxSeq = Math.max(...seqs);
let achado = null;
for (const seq of [maxSeq + 1, maxSeq + 2, maxSeq + 3]) {
achado = await tentaSequencia(h, seq);
if (achado) break;
await sleep(300);
}
if (!achado) achado = await viaImgDaPagina(h);
if (!achado) throw new Error('nenhuma miniatura valida encontrada');
const nome = `THUMB__thumb_${achado.nomeSeq}.jpg`;
const arqNome = `BRAS__${o.handle.replace(/\//g, '_')}__${nome}`;
const a = document.createElement('a');
a.href = URL.createObjectURL(achado.blob); a.download = arqNome;
document.body.appendChild(a); a.click(); a.remove();
setTimeout(() => URL.revokeObjectURL(a.href), 30000);
marcar(o.handle); S.feito++;
} catch (e) {
S.erros++; console.warn(` ${o.handle}: ${e.message}`);
}
if (i % 50 === 0) console.log(` ${i}/${lista.length} ok=${S.feito} erro=${S.erros}`);
await sleep(jit());
}
S.fase = 'concluido';
console.log(`\nThumbs v2 concluido. ok=${S.feito} erro=${S.erros}`);
}
window.BAIXA_THUMB = {
quantos: async () => { const a = await alvos();
return {total: a.length, jaFeitos: FEITOS.size, restam: a.length - FEITOS.size}; },
iniciar, status: () => ({...S}),
parar: () => { S.parar = true; }, retomar: () => iniciar(),
limparMarcas: () => localStorage.removeItem('bras_thumbs_v2'),
};
(async () => { db = await abrir();
const q = await window.BAIXA_THUMB.quantos();
console.log('BAIXA_THUMB v2 pronto:', q, '\nRode BAIXA_THUMB.iniciar() para comecar.');
})();
})();
+196
View File
@@ -0,0 +1,196 @@
#!/usr/bin/env python3
"""
Baixa os arquivos da Brasiliana e deposita no S3, um por vez, sem acumular.
Roda no servidor (Finlandia): mede 4,7 MB/s no bitstream e o bucket e Hetzner
tambem, entao a transferencia nao sai do datacenter. NUNCA guarda mais de um
arquivo em disco: baixa -> sha256 -> sobe -> apaga. Pico de uso = 1 arquivo.
Os bitstreams do DSpace passam pelo Cloudflare com curl simples (as paginas
HTML e que sao desafiadas -- essas vem do coletor.js, no navegador).
A copia legada do objdigital NAO e alcancavel da Finlandia: rode --somente-legado
na maquina local para essas.
python3 baixador.py --catalogo catalogo.jsonl
python3 baixador.py --catalogo catalogo.jsonl --somente-legado # na sua maquina
python3 baixador.py --status
"""
import argparse, hashlib, json, os, shutil, sqlite3, subprocess, sys, time
from pathlib import Path
from urllib.parse import unquote, urlparse
UA = ("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36")
CONTACT = os.environ.get("ARCHIVE_CONTACT", "")
S3_ENDPOINT = os.environ.get("S3_ENDPOINT", "https://hel1.your-objectstorage.com")
BUCKET = os.environ.get("ARCHIVE_BUCKET", "brasiliana")
ROOT = Path(__file__).resolve().parent
DB = ROOT / "baixador.sqlite"
TMP = ROOT / ".tmp"
PAUSA = 2.0
MIN_LIVRE = 2 * 1024**3 # 2 GB de folga; abaixo disso, para
def db():
con = sqlite3.connect(DB)
con.execute("PRAGMA journal_mode=WAL")
con.executescript("""
CREATE TABLE IF NOT EXISTS arquivos (
handle TEXT, tipo TEXT, url TEXT, nome TEXT,
bytes INTEGER, sha256 TEXT, status TEXT, erro TEXT,
PRIMARY KEY (handle, tipo));
""")
return con
def livre():
return shutil.disk_usage(ROOT).free
def baixar(url, destino):
"""curl -L para arquivo. Retorna (http, bytes) sem manter nada em memoria."""
r = subprocess.run(
["curl", "-sL", "-o", str(destino), "-w", "%{http_code} %{size_download}",
"-A", UA, *(["-H", f"From: {CONTACT}"] if CONTACT else []),
"--max-time", "600", "--retry", "2", "--retry-delay", "5", url],
capture_output=True, text=True)
try:
http, size = r.stdout.strip().split()
return int(http), int(size)
except Exception:
return 0, 0
def s3_put(local, key):
return subprocess.run(
["aws", "--endpoint-url", S3_ENDPOINT, "s3", "cp", str(local),
f"s3://{BUCKET}/{key}", "--only-show-errors"],
capture_output=True, text=True).returncode == 0
def nome_de(url):
n = unquote(urlparse(url).path.rsplit("/", 1)[-1]) or "arquivo.bin"
return n.replace("/", "_")[:150]
def processa(con, rec, tipo, url, args):
handle = rec["handle"]
ja = con.execute("SELECT status FROM arquivos WHERE handle=? AND tipo=?",
(handle, tipo)).fetchone()
if ja and ja[0] == "ok":
return "pulado"
if livre() < MIN_LIVRE:
sys.exit(f"\nESPACO BAIXO ({livre()/1024**3:.1f} GB livres). Parando por seguranca.")
TMP.mkdir(exist_ok=True)
nome = nome_de(url)
tmp = TMP / nome
http, size = baixar(url, tmp)
if http != 200 or size <= 0:
tmp.unlink(missing_ok=True)
con.execute("INSERT OR REPLACE INTO arquivos VALUES (?,?,?,?,NULL,NULL,'erro',?)",
(handle, tipo, url, nome, f"HTTP {http}"))
con.commit()
return f"erro HTTP {http}"
sha = hashlib.sha256()
with tmp.open("rb") as fh:
for bloco in iter(lambda: fh.read(1 << 20), b""):
sha.update(bloco)
dig = sha.hexdigest()
pasta = handle.replace("/", "_")
sub = "preservacao" if tipo == "preservacao" else "legado"
ok = s3_put(tmp, f"itens/{pasta}/{sub}/{nome}")
tmp.unlink(missing_ok=True) # apaga SEMPRE, deu certo ou nao
if not ok:
con.execute("INSERT OR REPLACE INTO arquivos VALUES (?,?,?,?,?,?,'erro','falha no upload')",
(handle, tipo, url, nome, size, dig))
con.commit()
return "erro no S3"
con.execute("INSERT OR REPLACE INTO arquivos VALUES (?,?,?,?,?,?,'ok',NULL)",
(handle, tipo, url, nome, size, dig))
con.commit()
return f"{size/1048576:.1f} MB"
def sobe_metadados(con, rec):
"""metadata.json + manifest-sha256.txt do item, a partir do catalogo.
Nome de arquivo temporario inclui o PID: baixador.py e subidor.py
moram na mesma pasta (mesmo TMP) e podem rodar ao mesmo tempo -- um
nome fixo faz um apagar o arquivo que o outro acabou de criar
(achado 2026-09-10, derrubou o baixador.py com FileNotFoundError)."""
handle = rec["handle"]; pasta = handle.replace("/", "_")
TMP.mkdir(exist_ok=True)
mj = TMP / f"metadata.{os.getpid()}.json"
mj.write_text(json.dumps(rec, ensure_ascii=False, indent=2), encoding="utf-8")
s3_put(mj, f"itens/{pasta}/metadata.json")
linhas = [f"{sha} {'preservacao' if t=='preservacao' else 'legado'}/{n}"
for t, n, sha in con.execute(
"SELECT tipo,nome,sha256 FROM arquivos WHERE handle=? AND status='ok'",
(handle,))]
linhas.append(f"{hashlib.sha256(mj.read_bytes()).hexdigest()} metadata.json")
mf = TMP / f"manifest-sha256.{os.getpid()}.txt"
mf.write_text("\n".join(linhas) + "\n", encoding="utf-8")
s3_put(mf, f"itens/{pasta}/manifest-sha256.txt")
mj.unlink(missing_ok=True); mf.unlink(missing_ok=True)
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--catalogo")
ap.add_argument("--limit", type=int, default=0)
ap.add_argument("--somente-legado", action="store_true")
ap.add_argument("--pular-legado", action="store_true")
ap.add_argument("--status", action="store_true")
args = ap.parse_args()
con = db()
if args.status:
for tipo, st, n, b in con.execute(
"SELECT tipo,status,COUNT(*),COALESCE(SUM(bytes),0) FROM arquivos GROUP BY 1,2"):
print(f" {tipo:<12} {st:<6} {n:>6} {b/1024**3:.2f} GB")
print(f" disco livre: {livre()/1024**3:.1f} GB")
return
if not args.catalogo:
sys.exit("informe --catalogo catalogo.jsonl")
if not os.environ.get("AWS_ACCESS_KEY_ID"):
sys.exit("credenciais S3 ausentes no ambiente")
recs = [json.loads(l) for l in open(args.catalogo, encoding="utf-8") if l.strip()]
recs = [r for r in recs if not r.get("erro")]
if args.limit:
recs = recs[: args.limit]
print(f"{len(recs)} itens | destino s3://{BUCKET}/ | livre {livre()/1024**3:.1f} GB\n")
for i, rec in enumerate(recs, 1):
partes = []
if not args.somente_legado and rec.get("preservation_url"):
partes.append(("preservacao", rec["preservation_url"]))
if not args.pular_legado and rec.get("legacy_url"):
# objdigital.bn.br reseta conexao na porta 80 (http simples) --
# so responde em https. O DC.identifier na fonte traz http://.
legado_url = rec["legacy_url"].replace("http://", "https://", 1)
partes.append(("legado", legado_url))
if not partes:
continue
res = []
for tipo, url in partes:
res.append(f"{tipo}:{processa(con, rec, tipo, url, args)}")
time.sleep(PAUSA)
sobe_metadados(con, rec)
print(f"[{i}/{len(recs)}] {rec['handle'].split('/')[-1]:<6} {' '.join(res)}", flush=True)
print("\nconcluido")
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\nInterrompido. Estado salvo -- rode de novo para continuar.")
+176
View File
@@ -0,0 +1,176 @@
/* ===================================================================
FALTA -- fecha a trilha 1: baixa SO os 73 itens de preservacao que
ainda nao existem nem no bucket nem na fila local de ~/Downloads.
POR QUE ESTE SCRIPT EXISTE (achado 2.16, 2026-09-11)
O contador `bras_baixados` anda pra tras: `marcar()` do
baixador-navegador.js reescreve o array inteiro a partir do FEITOS
carregado quando o script foi colado, entao qualquer aba com snapshot
velho sobrescreve a lista boa por uma mais curta (12.974 -> 12.111 em
meia hora). Recolar o baixador geral re-baixaria ~950+ arquivos que o
contador esqueceu -- ou os 13 mil, se o storage estiver zerado.
Por isso aqui a lista dos faltantes vem EMBUTIDA no script (apurada
por bucket + fila local, nao pelo localStorage) e o progresso vai pra
uma chave propria, `bras_faltantes_73`. Este script NUNCA escreve em
`bras_baixados` -- nao piora a lista que ja esta corrompida.
COMO USAR
1. Cole na aba da Brasiliana (a do coletor, ou qualquer uma da origem).
2. FALTA.iniciar()
3. Os arquivos caem em ~/Downloads com o prefixo BRAS__<handle>__,
que e como o subidor.py (ja rodando, PID 97295) sabe onde guardar.
Nao precisa mexer no subidor: ele vigia a pasta sozinho.
FALTA.status() / FALTA.parar() / FALTA.retomar() / FALTA.pendentes()
FALTA.erros() lista o que falhou, pra uma segunda passada
=================================================================== */
(() => {
/* [handle, preservation_url] -- apurados em 2026-09-11 20:45 UTC */
const FALTANTES = [
["20.500.12156.1/10993", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10993/1/Alb%200279%20113%20CT.jpg"],
["20.500.12156.1/10995", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10995/1/Alb%200279%20106%20CT.jpg"],
["20.500.12156.1/6433", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6433/1/0072430cx006-05t.jpg"],
["20.500.12156.1/6520", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6520/1/007_IMG_1008t.jpg"],
["20.500.12156.1/6545", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6545/1/CC-05-01-015-3.jpg"],
["20.500.12156.1/6555", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6555/1/BR_RJANRIO_Q0_ADM_CPA_VFE_FOT_0009_m0003de0003.jpg"],
["20.500.12156.1/6568", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6568/1/BR_RJANRIO_Q0_ADM_CPA_VFE_FOT_0019_m0001de0001.jpg"],
["20.500.12156.1/6570", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6570/1/BR_RJANRIO_Q0_ADM_CPA_VFE_FOT_0024_m0001de0001.jpg"],
["20.500.12156.1/6669", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6669/1/BP-06-TP-01-012.jpg"],
["20.500.12156.1/6670", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6670/1/BP-06-TP-01-015.jpg"],
["20.500.12156.1/6674", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6674/1/BP-06-TP-01-022.jpg"],
["20.500.12156.1/7034", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7034/1/007A6P3F08-004.jpg"],
["20.500.12156.1/7317", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7317/4/Alb%200294%20069ct.jpg"],
["20.500.12156.1/7326", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7326/4/Alb%200294%20037ct.jpg"],
["20.500.12156.1/7344", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7344/4/Alb%200294%20093ct.jpg"],
["20.500.12156.1/7353", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7353/4/Alb%200294%20097ct.jpg"],
["20.500.12156.1/7355", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7355/4/Alb%200259%20073%20ct.jpg"],
["20.500.12156.1/7358", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7358/1/BR_RJANRIO_OG_0_FOT_0001_021.jpg"],
["20.500.12156.1/7379", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7379/1/BR_RJANRIO_OG_0_FOT_0001_057.jpg"],
["20.500.12156.1/7386", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7386/1/BR_RJANRIO_OG_0_FOT_0001_067.jpg"],
["20.500.12156.1/7393", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7393/1/BR_RJANRIO_OG_0_FOT_0001_089.jpg"],
["20.500.12156.1/7438", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7438/1/BR_RJANRIO_OG_0_FOT_0001_085.jpg"],
["20.500.12156.1/7440", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7440/1/BR_RJANRIO_OG_0_FOT_0001_087.jpg"],
["20.500.12156.1/7457", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7457/4/Alb%200294%20127ct.jpg"],
["20.500.12156.1/7461", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7461/4/Alb%200259%20081ct.jpg"],
["20.500.12156.1/7463", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7463/4/Alb%200294%20106ct.jpg"],
["20.500.12156.1/7475", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7475/4/Alb%200259%20096ct.jpg"],
["20.500.12156.1/7515", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7515/4/Alb%200259%20145ct.jpg"],
["20.500.12156.1/7516", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7516/4/Alb%200259%20139ct.jpg"],
["20.500.12156.1/7517", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7517/3/Album%200035%20297%20CT.jpg"],
["20.500.12156.1/7529", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7529/5/Alb%200259%20130ct.jpg"],
["20.500.12156.1/7743", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7743/1/001ALA011011.jpg"],
["20.500.12156.1/7744", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7744/1/001ALA011012.jpg"],
["20.500.12156.1/7766", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7766/1/001ATK012010.jpg"],
["20.500.12156.1/7802", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7802/1/Alb%200036%20037%20CT%20ok.jpg"],
["20.500.12156.1/7814", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7814/1/Alb%200036%20081%20CT%20ok.jpg"],
["20.500.12156.1/7817", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7817/1/Alb%200036%20087%20CT%20ok.jpg"],
["20.500.12156.1/7848", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7848/1/Alb%200036%20013%20CT%20ok.jpg"],
["20.500.12156.1/7852", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7852/1/Album%200035%20105%20CT.jpg"],
["20.500.12156.1/7861", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7861/1/Album%200035%20088%20CT.jpg"],
["20.500.12156.1/7875", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7875/1/Album%200035%20078%20CT.jpg"],
["20.500.12156.1/8075", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8075/1/Alb%200038%20009%20CT%20ok.jpg"],
["20.500.12156.1/8128", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8128/1/014AM006009.jpg"],
["20.500.12156.1/8133", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8133/1/014AM010003.jpg"],
["20.500.12156.1/8141", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8141/1/014AM010014.jpg"],
["20.500.12156.1/8145", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8145/1/014AM012006.jpg"],
["20.500.12156.1/8161", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8161/1/014AM012058.jpg"],
["20.500.12156.1/8182", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8182/1/014AM019007.jpg"],
["20.500.12156.1/8187", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8187/1/014AM019013.jpg"],
["20.500.12156.1/8249", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8249/1/014AM002012.jpg"],
["20.500.12156.1/8254", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8254/1/014AM002019.jpg"],
["20.500.12156.1/8262", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8262/1/014AM003015.jpg"],
["20.500.12156.1/8267", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8267/1/014AM004003.jpg"],
["20.500.12156.1/8268", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8268/1/014AM005003.jpg"],
["20.500.12156.1/8271", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8271/1/014AM005005.jpg"],
["20.500.12156.1/8275", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8275/1/014AM005014.jpg"],
["20.500.12156.1/8277", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8277/1/014AM005020.jpg"],
["20.500.12156.1/8286", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8286/1/014AM006002.jpg"],
["20.500.12156.1/8313", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8313/1/001AAN005123.jpg"],
["20.500.12156.1/8316", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8316/1/001ACL019004.jpg"],
["20.500.12156.1/8374", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8374/1/002080NUS0309.jpg"],
["20.500.12156.1/8739", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8739/1/007ALA102.jpg"],
["20.500.12156.1/8761", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8761/1/007ALA015.jpg"],
["20.500.12156.1/8762", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8762/1/007ALA016.jpg"],
["20.500.12156.1/8774", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8774/1/007ALA027.jpg"],
["20.500.12156.1/8781", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8781/1/007ALA033.jpg"],
["20.500.12156.1/8785", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8785/1/007ALA037.jpg"],
["20.500.12156.1/8796", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8796/1/007ALA047.jpg"],
["20.500.12156.1/8804", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8804/1/007ALA054.jpg"],
["20.500.12156.1/8825", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8825/1/007ALA073.jpg"],
["20.500.12156.1/8872", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8872/1/A05F03P02.jpg"],
["20.500.12156.1/8912", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8912/1/pixinguinha04.jpg"],
["20.500.12156.1/9148", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/9148/1/007ALA091.jpg"]
];
const PAUSA = 3000; // mesmo ritmo cortes do baixador geral
const CHAVE = 'bras_faltantes_73'; // chave propria: nao toca em bras_baixados
const sleep = ms => new Promise(r => setTimeout(r, ms));
const jit = () => PAUSA + Math.floor(Math.random() * 1000);
const FEITOS = new Set(JSON.parse(localStorage.getItem(CHAVE) || '[]'));
const marcar = h => { FEITOS.add(h);
localStorage.setItem(CHAVE, JSON.stringify([...FEITOS])); };
const S = {fase: 'ocioso', feito: 0, erros: 0, total: 0, atual: '', parar: false};
const FALHAS = [];
async function baixarUm(handle, url) {
const r = await fetch(url, {credentials: 'same-origin'});
if (!r.ok) throw new Error('HTTP ' + r.status);
const b = await r.blob();
if (b.size < 1024) throw new Error('blob suspeito: ' + b.size + ' bytes');
const orig = decodeURIComponent(url.split('/').pop());
// o prefixo carrega o handle: e assim que o subidor sabe onde guardar
const nome = `BRAS__${handle.replace(/\//g, '_')}__${orig}`.replace(/[\\/:*?"<>|]/g, '_');
const a = document.createElement('a');
a.href = URL.createObjectURL(b); a.download = nome;
document.body.appendChild(a); a.click(); a.remove();
setTimeout(() => URL.revokeObjectURL(a.href), 30000);
return b.size;
}
async function iniciar() {
S.parar = false; S.fase = 'baixando'; FALHAS.length = 0;
const lista = FALTANTES.filter(([h]) => !FEITOS.has(h));
S.total = lista.length; S.feito = 0; S.erros = 0;
console.log(`FALTA: ${lista.length} arquivos (~${Math.ceil(lista.length * PAUSA / 60000)} min)`);
for (let i = 0; i < lista.length; i++) {
if (S.parar) { S.fase = 'parado'; console.log('parado em ' + i); return; }
const [handle, url] = lista[i];
S.atual = `${i + 1}/${lista.length} ${handle}`;
try {
const bytes = await baixarUm(handle, url);
marcar(handle); S.feito++;
console.log(` ok ${handle} ${(bytes / 1048576).toFixed(1)} MB (${i + 1}/${lista.length})`);
} catch (e) {
S.erros++; FALHAS.push({handle, url, erro: e.message});
console.warn(` ERRO ${handle}: ${e.message}`);
if (/HTTP 4|HTTP 5/.test(String(e))) await sleep(60000); // mesmo backoff do geral
}
await sleep(jit());
}
S.fase = 'concluido';
console.log(`\nFALTA concluido. ok=${S.feito} erro=${S.erros}`);
if (FALHAS.length) {
console.log('Rode FALTA.retomar() pra tentar de novo so os que falharam:');
console.table(FALHAS);
} else {
console.log('Trilha 1 fechada: 13.059/13.059. O subidor sobe o resto sozinho.');
}
}
window.FALTA = {
iniciar, status: () => ({...S}), retomar: () => iniciar(),
parar: () => { S.parar = true; },
pendentes: () => FALTANTES.filter(([h]) => !FEITOS.has(h)).map(([h]) => h),
erros: () => [...FALHAS],
limparMarcas: () => localStorage.removeItem(CHAVE),
};
console.log(`FALTA pronto: ${FALTANTES.length} no total, ${window.FALTA.pendentes().length} pendentes.`);
console.log('Rode FALTA.iniciar() para comecar.');
})();
+195
View File
@@ -0,0 +1,195 @@
/* ===================================================================
FALTA v2 -- fecha a trilha 1 (73 itens de preservacao restantes).
POR QUE V2 (achado 2.18, 2026-09-12)
A v1 tinha tres defeitos que juntos viraram rate-limit na origem:
1. `iniciar()` nao se protegia contra chamada dupla -- cada
FALTA.iniciar()/retomar() num script ja rodando somava OUTRO loop
sobre o mesmo S. Deu `erros: 101` com `atual: 43/73`, e a pausa
de 3s combinada com a BN virou 1,5s ou menos.
2. Erro nao interrompia nada: o loop moia os 71 com 60s de backoff
cada, martelando a origem por mais de uma hora sem chance de
alguem olhar o resultado.
3. Nao registrava o codigo HTTP separado da mensagem, entao o
diagnostico dependia de ler FALHAS na mao.
Evidencia de que o problema e de origem, nao de URL: 10993 e 10995
baixaram as 22:52 e FALHARAM na passada seguinte, mesma URL. E os 71
tem 71/71 thumbs baixados com sucesso -- o item responde, o bitstream
de preservacao e que nao.
COMO USAR
1. Recarregue a aba antes de colar (mata loops velhos da v1).
2. Cole. Rode FALTA.iniciar().
3. Ele PARA SOZINHO depois de 5 erros seguidos e mostra a tabela
de codigos -- e o sinal de que a origem esta recusando, e insistir
so piora. Espere e rode FALTA.retomar() mais tarde.
FALTA.status() / .parar() / .retomar() / .erros() / .pendentes()
=================================================================== */
(() => {
/* [handle, preservation_url] -- apurados em 2026-09-11 20:45 UTC */
const FALTANTES = [
["20.500.12156.1/10993", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10993/1/Alb%200279%20113%20CT.jpg"],
["20.500.12156.1/10995", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10995/1/Alb%200279%20106%20CT.jpg"],
["20.500.12156.1/6433", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6433/1/0072430cx006-05t.jpg"],
["20.500.12156.1/6520", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6520/1/007_IMG_1008t.jpg"],
["20.500.12156.1/6545", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6545/1/CC-05-01-015-3.jpg"],
["20.500.12156.1/6555", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6555/1/BR_RJANRIO_Q0_ADM_CPA_VFE_FOT_0009_m0003de0003.jpg"],
["20.500.12156.1/6568", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6568/1/BR_RJANRIO_Q0_ADM_CPA_VFE_FOT_0019_m0001de0001.jpg"],
["20.500.12156.1/6570", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6570/1/BR_RJANRIO_Q0_ADM_CPA_VFE_FOT_0024_m0001de0001.jpg"],
["20.500.12156.1/6669", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6669/1/BP-06-TP-01-012.jpg"],
["20.500.12156.1/6670", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6670/1/BP-06-TP-01-015.jpg"],
["20.500.12156.1/6674", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6674/1/BP-06-TP-01-022.jpg"],
["20.500.12156.1/7034", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7034/1/007A6P3F08-004.jpg"],
["20.500.12156.1/7317", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7317/4/Alb%200294%20069ct.jpg"],
["20.500.12156.1/7326", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7326/4/Alb%200294%20037ct.jpg"],
["20.500.12156.1/7344", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7344/4/Alb%200294%20093ct.jpg"],
["20.500.12156.1/7353", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7353/4/Alb%200294%20097ct.jpg"],
["20.500.12156.1/7355", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7355/4/Alb%200259%20073%20ct.jpg"],
["20.500.12156.1/7358", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7358/1/BR_RJANRIO_OG_0_FOT_0001_021.jpg"],
["20.500.12156.1/7379", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7379/1/BR_RJANRIO_OG_0_FOT_0001_057.jpg"],
["20.500.12156.1/7386", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7386/1/BR_RJANRIO_OG_0_FOT_0001_067.jpg"],
["20.500.12156.1/7393", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7393/1/BR_RJANRIO_OG_0_FOT_0001_089.jpg"],
["20.500.12156.1/7438", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7438/1/BR_RJANRIO_OG_0_FOT_0001_085.jpg"],
["20.500.12156.1/7440", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7440/1/BR_RJANRIO_OG_0_FOT_0001_087.jpg"],
["20.500.12156.1/7457", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7457/4/Alb%200294%20127ct.jpg"],
["20.500.12156.1/7461", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7461/4/Alb%200259%20081ct.jpg"],
["20.500.12156.1/7463", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7463/4/Alb%200294%20106ct.jpg"],
["20.500.12156.1/7475", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7475/4/Alb%200259%20096ct.jpg"],
["20.500.12156.1/7515", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7515/4/Alb%200259%20145ct.jpg"],
["20.500.12156.1/7516", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7516/4/Alb%200259%20139ct.jpg"],
["20.500.12156.1/7517", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7517/3/Album%200035%20297%20CT.jpg"],
["20.500.12156.1/7529", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7529/5/Alb%200259%20130ct.jpg"],
["20.500.12156.1/7743", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7743/1/001ALA011011.jpg"],
["20.500.12156.1/7744", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7744/1/001ALA011012.jpg"],
["20.500.12156.1/7766", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7766/1/001ATK012010.jpg"],
["20.500.12156.1/7802", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7802/1/Alb%200036%20037%20CT%20ok.jpg"],
["20.500.12156.1/7814", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7814/1/Alb%200036%20081%20CT%20ok.jpg"],
["20.500.12156.1/7817", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7817/1/Alb%200036%20087%20CT%20ok.jpg"],
["20.500.12156.1/7848", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7848/1/Alb%200036%20013%20CT%20ok.jpg"],
["20.500.12156.1/7852", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7852/1/Album%200035%20105%20CT.jpg"],
["20.500.12156.1/7861", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7861/1/Album%200035%20088%20CT.jpg"],
["20.500.12156.1/7875", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7875/1/Album%200035%20078%20CT.jpg"],
["20.500.12156.1/8075", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8075/1/Alb%200038%20009%20CT%20ok.jpg"],
["20.500.12156.1/8128", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8128/1/014AM006009.jpg"],
["20.500.12156.1/8133", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8133/1/014AM010003.jpg"],
["20.500.12156.1/8141", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8141/1/014AM010014.jpg"],
["20.500.12156.1/8145", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8145/1/014AM012006.jpg"],
["20.500.12156.1/8161", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8161/1/014AM012058.jpg"],
["20.500.12156.1/8182", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8182/1/014AM019007.jpg"],
["20.500.12156.1/8187", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8187/1/014AM019013.jpg"],
["20.500.12156.1/8249", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8249/1/014AM002012.jpg"],
["20.500.12156.1/8254", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8254/1/014AM002019.jpg"],
["20.500.12156.1/8262", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8262/1/014AM003015.jpg"],
["20.500.12156.1/8267", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8267/1/014AM004003.jpg"],
["20.500.12156.1/8268", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8268/1/014AM005003.jpg"],
["20.500.12156.1/8271", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8271/1/014AM005005.jpg"],
["20.500.12156.1/8275", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8275/1/014AM005014.jpg"],
["20.500.12156.1/8277", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8277/1/014AM005020.jpg"],
["20.500.12156.1/8286", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8286/1/014AM006002.jpg"],
["20.500.12156.1/8313", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8313/1/001AAN005123.jpg"],
["20.500.12156.1/8316", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8316/1/001ACL019004.jpg"],
["20.500.12156.1/8374", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8374/1/002080NUS0309.jpg"],
["20.500.12156.1/8739", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8739/1/007ALA102.jpg"],
["20.500.12156.1/8761", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8761/1/007ALA015.jpg"],
["20.500.12156.1/8762", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8762/1/007ALA016.jpg"],
["20.500.12156.1/8774", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8774/1/007ALA027.jpg"],
["20.500.12156.1/8781", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8781/1/007ALA033.jpg"],
["20.500.12156.1/8785", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8785/1/007ALA037.jpg"],
["20.500.12156.1/8796", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8796/1/007ALA047.jpg"],
["20.500.12156.1/8804", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8804/1/007ALA054.jpg"],
["20.500.12156.1/8825", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8825/1/007ALA073.jpg"],
["20.500.12156.1/8872", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8872/1/A05F03P02.jpg"],
["20.500.12156.1/8912", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8912/1/pixinguinha04.jpg"],
["20.500.12156.1/9148", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/9148/1/007ALA091.jpg"]
];
const PAUSA = 5000; // v1 usava 3000; mais folga depois do rate-limit
const BACKOFF = 30000; // v1 dormia 60s por erro e moia os 71: 71 min
const MAX_SEGUIDOS = 5; // desiste depois disso -- nao martela a origem
const CHAVE = 'bras_faltantes_73';
const sleep = ms => new Promise(r => setTimeout(r, ms));
const jit = () => PAUSA + Math.floor(Math.random() * 1500);
const FEITOS = new Set(JSON.parse(localStorage.getItem(CHAVE) || '[]'));
const marcar = h => { FEITOS.add(h);
localStorage.setItem(CHAVE, JSON.stringify([...FEITOS])); };
const S = {fase: 'ocioso', feito: 0, erros: 0, total: 0, atual: '', parar: false};
const FALHAS = [];
function resumo() {
const c = {};
FALHAS.forEach(f => { c[f.codigo] = (c[f.codigo] || 0) + 1; });
return c;
}
async function baixarUm(handle, url) {
const r = await fetch(url, {credentials: 'same-origin'});
if (!r.ok) { const e = new Error('HTTP ' + r.status); e.codigo = r.status; throw e; }
const b = await r.blob();
if (b.size < 1024) { const e = new Error('blob ' + b.size + 'B'); e.codigo = 'blob-curto'; throw e; }
const orig = decodeURIComponent(url.split('/').pop());
const nome = `BRAS__${handle.replace(/\//g, '_')}__${orig}`.replace(/[\\/:*?"<>|]/g, '_');
const a = document.createElement('a');
a.href = URL.createObjectURL(b); a.download = nome;
document.body.appendChild(a); a.click(); a.remove();
setTimeout(() => URL.revokeObjectURL(a.href), 30000);
return b.size;
}
async function iniciar() {
if (S.fase === 'baixando') { // <-- o defeito nº1 da v1
console.warn('FALTA ja esta rodando (' + S.atual + '). Use FALTA.parar() antes.');
return;
}
S.parar = false; S.fase = 'baixando'; FALHAS.length = 0;
const lista = FALTANTES.filter(([h]) => !FEITOS.has(h));
S.total = lista.length; S.feito = 0; S.erros = 0;
console.log(`FALTA v2: ${lista.length} pendentes, ~${Math.ceil(lista.length * PAUSA / 60000)} min se tudo der certo`);
let seguidos = 0;
for (let i = 0; i < lista.length; i++) {
if (S.parar) { S.fase = 'parado'; console.log('parado em ' + (i + 1)); return; }
const [handle, url] = lista[i];
S.atual = `${i + 1}/${lista.length} ${handle}`;
try {
const bytes = await baixarUm(handle, url);
marcar(handle); S.feito++; seguidos = 0;
console.log(` ok ${handle} ${(bytes / 1048576).toFixed(1)} MB (${i + 1}/${lista.length})`);
} catch (e) {
S.erros++; seguidos++;
FALHAS.push({handle, url, codigo: e.codigo ?? 'rede', erro: e.message});
console.warn(` ERRO ${handle}: ${e.message}`);
if (seguidos >= MAX_SEGUIDOS) { // <-- o defeito nº2 da v1
S.fase = 'abortado';
console.error(`\nFALTA abortou: ${seguidos} erros seguidos. A origem esta recusando -- insistir so piora.`);
console.table(resumo());
console.log('Espere (30+ min) e rode FALTA.retomar(). O que ja baixou esta marcado.');
return;
}
await sleep(BACKOFF);
}
await sleep(jit());
}
S.fase = 'concluido';
console.log(`\nFALTA v2 concluido. ok=${S.feito} erro=${S.erros}`);
if (FALHAS.length) { console.table(resumo()); console.table(FALHAS); }
else console.log('Trilha 1 fechada: 13.059/13.059. O subidor sobe o resto sozinho.');
}
window.FALTA = {
iniciar, status: () => ({...S}), retomar: () => iniciar(),
parar: () => { S.parar = true; },
pendentes: () => FALTANTES.filter(([h]) => !FEITOS.has(h)).map(([h]) => h),
erros: () => [...FALHAS], resumo,
limparMarcas: () => localStorage.removeItem(CHAVE),
};
console.log(`FALTA v2 pronto: ${window.FALTA.pendentes().length} pendentes de ${FALTANTES.length}.`);
console.log('Rode FALTA.iniciar()');
})();
+344
View File
@@ -0,0 +1,344 @@
/* ===================================================================
FALTA v3 -- fecha a trilha 1 (212 itens de preservacao restantes).
POR QUE V3 (2026-09-13): a lista da v2 (73) estava subestimada. Recontagem
direta contra o bucket, com a fila de ~/Downloads ja esvaziada, da 212
itens sem NENHUMA imagem de preservacao -- ver achado 2.23. Inclui os 8 que
o subidor.py antigo apagou por confundir TIFF com licenca (achado 2.19).
Motor identico ao da v2, so a lista muda.
NAO cobre os 30 bitstreams "extra_seqN" apagados junto: o nome deles e
nosso, nao da origem, entao a URL precisa ser enumerada item a item com
pipeline/reparo-multibitstream.js antes.
POR QUE V2 (achado 2.18, 2026-09-12)
A v1 tinha tres defeitos que juntos viraram rate-limit na origem:
1. `iniciar()` nao se protegia contra chamada dupla -- cada
FALTA.iniciar()/retomar() num script ja rodando somava OUTRO loop
sobre o mesmo S. Deu `erros: 101` com `atual: 43/73`, e a pausa
de 3s combinada com a BN virou 1,5s ou menos.
2. Erro nao interrompia nada: o loop moia os 71 com 60s de backoff
cada, martelando a origem por mais de uma hora sem chance de
alguem olhar o resultado.
3. Nao registrava o codigo HTTP separado da mensagem, entao o
diagnostico dependia de ler FALHAS na mao.
Evidencia de que o problema e de origem, nao de URL: 10993 e 10995
baixaram as 22:52 e FALHARAM na passada seguinte, mesma URL. E os 71
tem 71/71 thumbs baixados com sucesso -- o item responde, o bitstream
de preservacao e que nao.
COMO USAR
1. Recarregue a aba antes de colar (mata loops velhos da v1).
2. Cole. Rode FALTA.iniciar().
3. Ele PARA SOZINHO depois de 5 erros seguidos e mostra a tabela
de codigos -- e o sinal de que a origem esta recusando, e insistir
so piora. Espere e rode FALTA.retomar() mais tarde.
FALTA.status() / .parar() / .retomar() / .erros() / .pendentes()
=================================================================== */
(() => {
/* [handle, preservation_url] -- apurados em 2026-09-13 (bucket, depois da fila esvaziar) */
const FALTANTES = [
["20.500.12156.1/101", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/101/1/imagem.jpg"],
["20.500.12156.1/1004", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/1004/1/imagem.jpg"],
["20.500.12156.1/1005", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/1005/1/imagem.jpg"],
["20.500.12156.1/1006", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/1006/1/imagem.jpg"],
["20.500.12156.1/1007", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/1007/1/imagem.jpg"],
["20.500.12156.1/1008", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/1008/1/imagem.jpg"],
["20.500.12156.1/1009", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/1009/1/imagem.jpg"],
["20.500.12156.1/1010", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/1010/1/imagem.jpg"],
["20.500.12156.1/1011", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/1011/1/imagem.jpg"],
["20.500.12156.1/1012", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/1012/1/imagem.jpg"],
["20.500.12156.1/1013", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/1013/1/imagem.jpg"],
["20.500.12156.1/1014", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/1014/1/imagem.jpg"],
["20.500.12156.1/1015", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/1015/1/imagem.jpg"],
["20.500.12156.1/6433", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6433/1/0072430cx006-05t.jpg"],
["20.500.12156.1/6520", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6520/1/007_IMG_1008t.jpg"],
["20.500.12156.1/6545", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6545/1/CC-05-01-015-3.jpg"],
["20.500.12156.1/6555", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6555/1/BR_RJANRIO_Q0_ADM_CPA_VFE_FOT_0009_m0003de0003.jpg"],
["20.500.12156.1/6568", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6568/1/BR_RJANRIO_Q0_ADM_CPA_VFE_FOT_0019_m0001de0001.jpg"],
["20.500.12156.1/6570", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6570/1/BR_RJANRIO_Q0_ADM_CPA_VFE_FOT_0024_m0001de0001.jpg"],
["20.500.12156.1/6656", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6656/2/imagem.jpg"],
["20.500.12156.1/6669", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6669/1/BP-06-TP-01-012.jpg"],
["20.500.12156.1/6670", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6670/1/BP-06-TP-01-015.jpg"],
["20.500.12156.1/6674", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6674/1/BP-06-TP-01-022.jpg"],
["20.500.12156.1/7034", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7034/1/007A6P3F08-004.jpg"],
["20.500.12156.1/7317", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7317/4/Alb%200294%20069ct.jpg"],
["20.500.12156.1/7326", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7326/4/Alb%200294%20037ct.jpg"],
["20.500.12156.1/7344", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7344/4/Alb%200294%20093ct.jpg"],
["20.500.12156.1/7353", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7353/4/Alb%200294%20097ct.jpg"],
["20.500.12156.1/7355", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7355/4/Alb%200259%20073%20ct.jpg"],
["20.500.12156.1/7358", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7358/1/BR_RJANRIO_OG_0_FOT_0001_021.jpg"],
["20.500.12156.1/7379", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7379/1/BR_RJANRIO_OG_0_FOT_0001_057.jpg"],
["20.500.12156.1/7386", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7386/1/BR_RJANRIO_OG_0_FOT_0001_067.jpg"],
["20.500.12156.1/7393", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7393/1/BR_RJANRIO_OG_0_FOT_0001_089.jpg"],
["20.500.12156.1/7438", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7438/1/BR_RJANRIO_OG_0_FOT_0001_085.jpg"],
["20.500.12156.1/7440", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7440/1/BR_RJANRIO_OG_0_FOT_0001_087.jpg"],
["20.500.12156.1/7457", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7457/4/Alb%200294%20127ct.jpg"],
["20.500.12156.1/7461", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7461/4/Alb%200259%20081ct.jpg"],
["20.500.12156.1/7463", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7463/4/Alb%200294%20106ct.jpg"],
["20.500.12156.1/7475", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7475/4/Alb%200259%20096ct.jpg"],
["20.500.12156.1/7515", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7515/4/Alb%200259%20145ct.jpg"],
["20.500.12156.1/7516", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7516/4/Alb%200259%20139ct.jpg"],
["20.500.12156.1/7517", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7517/3/Album%200035%20297%20CT.jpg"],
["20.500.12156.1/7529", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7529/5/Alb%200259%20130ct.jpg"],
["20.500.12156.1/7743", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7743/1/001ALA011011.jpg"],
["20.500.12156.1/7744", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7744/1/001ALA011012.jpg"],
["20.500.12156.1/7766", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7766/1/001ATK012010.jpg"],
["20.500.12156.1/7802", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7802/1/Alb%200036%20037%20CT%20ok.jpg"],
["20.500.12156.1/7814", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7814/1/Alb%200036%20081%20CT%20ok.jpg"],
["20.500.12156.1/7817", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7817/1/Alb%200036%20087%20CT%20ok.jpg"],
["20.500.12156.1/7848", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7848/1/Alb%200036%20013%20CT%20ok.jpg"],
["20.500.12156.1/7852", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7852/1/Album%200035%20105%20CT.jpg"],
["20.500.12156.1/7861", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7861/1/Album%200035%20088%20CT.jpg"],
["20.500.12156.1/7875", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7875/1/Album%200035%20078%20CT.jpg"],
["20.500.12156.1/7939", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7939/2/imagem.jpg"],
["20.500.12156.1/8075", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8075/1/Alb%200038%20009%20CT%20ok.jpg"],
["20.500.12156.1/8128", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8128/1/014AM006009.jpg"],
["20.500.12156.1/8133", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8133/1/014AM010003.jpg"],
["20.500.12156.1/8141", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8141/1/014AM010014.jpg"],
["20.500.12156.1/8145", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8145/1/014AM012006.jpg"],
["20.500.12156.1/8161", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8161/1/014AM012058.jpg"],
["20.500.12156.1/8182", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8182/1/014AM019007.jpg"],
["20.500.12156.1/8187", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8187/1/014AM019013.jpg"],
["20.500.12156.1/8249", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8249/1/014AM002012.jpg"],
["20.500.12156.1/8254", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8254/1/014AM002019.jpg"],
["20.500.12156.1/8262", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8262/1/014AM003015.jpg"],
["20.500.12156.1/8267", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8267/1/014AM004003.jpg"],
["20.500.12156.1/8268", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8268/1/014AM005003.jpg"],
["20.500.12156.1/8271", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8271/1/014AM005005.jpg"],
["20.500.12156.1/8275", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8275/1/014AM005014.jpg"],
["20.500.12156.1/8277", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8277/1/014AM005020.jpg"],
["20.500.12156.1/8286", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8286/1/014AM006002.jpg"],
["20.500.12156.1/8313", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8313/1/001AAN005123.jpg"],
["20.500.12156.1/8316", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8316/1/001ACL019004.jpg"],
["20.500.12156.1/8374", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8374/1/002080NUS0309.jpg"],
["20.500.12156.1/8396", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8396/2/imagem.jpg"],
["20.500.12156.1/8739", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8739/1/007ALA102.jpg"],
["20.500.12156.1/8761", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8761/1/007ALA015.jpg"],
["20.500.12156.1/8762", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8762/1/007ALA016.jpg"],
["20.500.12156.1/8774", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8774/1/007ALA027.jpg"],
["20.500.12156.1/8781", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8781/1/007ALA033.jpg"],
["20.500.12156.1/8785", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8785/1/007ALA037.jpg"],
["20.500.12156.1/8796", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8796/1/007ALA047.jpg"],
["20.500.12156.1/8804", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8804/1/007ALA054.jpg"],
["20.500.12156.1/8825", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8825/1/007ALA073.jpg"],
["20.500.12156.1/8872", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8872/1/A05F03P02.jpg"],
["20.500.12156.1/8912", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8912/1/pixinguinha04.jpg"],
["20.500.12156.1/9148", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/9148/1/007ALA091.jpg"],
["20.500.12156.1/10039", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10039/1/007CX190-01.jpg"],
["20.500.12156.1/10040", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10040/1/007CX190-05.jpg"],
["20.500.12156.1/10041", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10041/1/007CX190-11.jpg"],
["20.500.12156.1/10042", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10042/1/007CX190-14.jpg"],
["20.500.12156.1/10043", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10043/1/007CX190-16.jpg"],
["20.500.12156.1/10044", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10044/1/007CX194-08.jpg"],
["20.500.12156.1/10045", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10045/1/001CC007004.jpg"],
["20.500.12156.1/10046", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10046/1/007CX194-10.jpg"],
["20.500.12156.1/10047", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10047/1/007CX196-02.jpg"],
["20.500.12156.1/10048", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10048/1/007CX196-07.jpg"],
["20.500.12156.1/10049", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10049/1/007CX197-02.jpg"],
["20.500.12156.1/10050", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10050/1/007CX197-05.jpg"],
["20.500.12156.1/10051", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10051/1/007CX197-09.jpg"],
["20.500.12156.1/10052", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10052/1/007CX197-10.jpg"],
["20.500.12156.1/10053", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10053/1/007CX198-03.jpg"],
["20.500.12156.1/10054", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10054/1/007CX199-03.jpg"],
["20.500.12156.1/10055", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10055/1/007CX199-04.jpg"],
["20.500.12156.1/10056", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10056/1/001CC007005.jpg"],
["20.500.12156.1/10057", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10057/1/007CX214-12.JPG"],
["20.500.12156.1/10058", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10058/1/007CX237-01.jpg"],
["20.500.12156.1/10059", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10059/1/007SN-12.jpg"],
["20.500.12156.1/10060", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10060/1/007_IMG_1807.jpg"],
["20.500.12156.1/10061", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10061/1/007_IMG_1811.jpg"],
["20.500.12156.1/10062", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10062/1/007_IMG_1812.jpg"],
["20.500.12156.1/10063", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10063/1/007_IMG_1813.jpg"],
["20.500.12156.1/10064", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10064/1/007_IMG_1855.jpg"],
["20.500.12156.1/10065", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10065/1/007_IMG_1859.jpg"],
["20.500.12156.1/10066", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10066/1/007_IMG_1993.jpg"],
["20.500.12156.1/10067", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10067/1/001CC007007.jpg"],
["20.500.12156.1/10068", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10068/1/007_IMG_2000.jpg"],
["20.500.12156.1/10069", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10069/1/007_IMG_2006.jpg"],
["20.500.12156.1/10070", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10070/1/007_IMG_2007.jpg"],
["20.500.12156.1/10071", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10071/1/007_IMG_2010.jpg"],
["20.500.12156.1/10072", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10072/1/007_IMG_2023.jpg"],
["20.500.12156.1/10073", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10073/1/007_IMG_2493.jpg"],
["20.500.12156.1/10074", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10074/1/007_IMG_2497.jpg"],
["20.500.12156.1/10075", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10075/1/007_IMG_2500.jpg"],
["20.500.12156.1/10076", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10076/1/007_IMG_2502.jpg"],
["20.500.12156.1/10077", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10077/1/007_IMG_2503.jpg"],
["20.500.12156.1/10078", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10078/1/001CC007014.jpg"],
["20.500.12156.1/10079", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10079/1/007_IMG_2509.jpg"],
["20.500.12156.1/10080", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10080/1/007_IMG_2511.jpg"],
["20.500.12156.1/10081", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10081/1/007_IMG_2516.jpg"],
["20.500.12156.1/10082", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10082/1/007_IMG_2519.jpg"],
["20.500.12156.1/10083", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10083/1/007_IMG_2520.jpg"],
["20.500.12156.1/10084", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10084/1/007_IMG_2522.jpg"],
["20.500.12156.1/10085", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10085/1/007_IMG_2525.jpg"],
["20.500.12156.1/10086", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10086/1/007_IMG_2526.jpg"],
["20.500.12156.1/10087", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10087/1/007_IMG_2527.jpg"],
["20.500.12156.1/10088", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10088/1/007_IMG_2531.jpg"],
["20.500.12156.1/10089", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10089/1/001AN02001006.jpg"],
["20.500.12156.1/10090", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10090/1/001CC007015.jpg"],
["20.500.12156.1/10091", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10091/1/007_IMG_2836.jpg"],
["20.500.12156.1/10092", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10092/1/007_IMG_2837.jpg"],
["20.500.12156.1/10093", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10093/1/007_IMG_2838.jpg"],
["20.500.12156.1/10094", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10094/1/007_IMG_2839.jpg"],
["20.500.12156.1/10095", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10095/1/007_IMG_2897.jpg"],
["20.500.12156.1/10096", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10096/1/007_IMG_2904.jpg"],
["20.500.12156.1/10097", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10097/1/007_IMG_2912.jpg"],
["20.500.12156.1/10098", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10098/1/007_IMG_2913.jpg"],
["20.500.12156.1/10099", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10099/1/007_IMG_2914.jpg"],
["20.500.12156.1/10100", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10100/1/007_IMG_2917.jpg"],
["20.500.12156.1/10101", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10101/1/001CC007016.jpg"],
["20.500.12156.1/10102", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10102/1/007_IMG_2934.jpg"],
["20.500.12156.1/10103", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10103/1/007_IMG_2935.jpg"],
["20.500.12156.1/10104", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10104/1/007_IMG_2944.jpg"],
["20.500.12156.1/10105", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10105/1/007_IMG_3108.jpg"],
["20.500.12156.1/10106", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10106/1/007_IMG_3110.jpg"],
["20.500.12156.1/10107", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10107/1/007_IMG_3113.jpg"],
["20.500.12156.1/10108", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10108/1/007_IMG_3114.jpg"],
["20.500.12156.1/10109", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10109/1/007_IMG_3119.jpg"],
["20.500.12156.1/10110", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10110/1/007_IMG_3165.jpg"],
["20.500.12156.1/10111", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10111/1/007_IMG_3259.jpg"],
["20.500.12156.1/10112", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10112/1/001CC007019.jpg"],
["20.500.12156.1/10113", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10113/1/007_IMG_3285.jpg"],
["20.500.12156.1/10114", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10114/1/007_IMG_3299.jpg"],
["20.500.12156.1/10115", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10115/1/007_IMG_3309.jpg"],
["20.500.12156.1/10116", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10116/1/007_IMG_3316.jpg"],
["20.500.12156.1/10117", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10117/1/007_IMG_3330.jpg"],
["20.500.12156.1/10118", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10118/1/007_IMG_3332.jpg"],
["20.500.12156.1/10119", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10119/1/007_IMG_3347.jpg"],
["20.500.12156.1/10120", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10120/1/007_IMG_3350.jpg"],
["20.500.12156.1/10121", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10121/1/007_IMG_3352.jpg"],
["20.500.12156.1/10122", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10122/1/007_IMG_3353.jpg"],
["20.500.12156.1/10123", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10123/1/001CC007020.jpg"],
["20.500.12156.1/10124", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10124/1/007_IMG_3437.jpg"],
["20.500.12156.1/10125", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10125/1/007_IMG_3469.jpg"],
["20.500.12156.1/10126", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10126/1/007_IMG_3527.jpg"],
["20.500.12156.1/10127", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10127/1/007_IMG_3528.jpg"],
["20.500.12156.1/10128", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10128/1/007_IMG_3533.jpg"],
["20.500.12156.1/10129", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10129/1/007_IMG_3534.jpg"],
["20.500.12156.1/10130", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10130/1/007_IMG_3535.jpg"],
["20.500.12156.1/10131", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10131/1/007_IMG_3536.jpg"],
["20.500.12156.1/10132", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10132/1/007_IMG_3539.jpg"],
["20.500.12156.1/10133", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10133/1/007_IMG_3540.jpg"],
["20.500.12156.1/10134", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10134/1/001CC007025.jpg"],
["20.500.12156.1/10135", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10135/1/007_IMG_3547.jpg"],
["20.500.12156.1/10136", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10136/1/007_IMG_3559.jpg"],
["20.500.12156.1/10137", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10137/1/007_IMG_3585.jpg"],
["20.500.12156.1/10138", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10138/1/007_IMG_3588.jpg"],
["20.500.12156.1/10139", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10139/1/007_IMG_3590.jpg"],
["20.500.12156.1/10140", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10140/1/007_IMG_3594.jpg"],
["20.500.12156.1/10141", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10141/1/007_IMG_3596.jpg"],
["20.500.12156.1/10142", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10142/1/007_IMG_3597.jpg"],
["20.500.12156.1/10143", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10143/1/007_IMG_3598.jpg"],
["20.500.12156.1/10144", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10144/1/007_IMG_3601.jpg"],
["20.500.12156.1/10145", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10145/1/001CC008007.jpg"],
["20.500.12156.1/10146", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10146/1/007_IMG_3603.jpg"],
["20.500.12156.1/10147", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10147/1/007_IMG_3607.jpg"],
["20.500.12156.1/10148", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10148/1/007_IMG_3631.jpg"],
["20.500.12156.1/10149", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10149/1/007_IMG_3634.jpg"],
["20.500.12156.1/10150", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10150/1/007_IMG_3636.jpg"],
["20.500.12156.1/10151", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10151/1/007_IMG_3639.jpg"],
["20.500.12156.1/10152", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10152/1/007_IMG_3642.jpg"],
["20.500.12156.1/10153", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10153/1/007_IMG_3739.jpg"],
["20.500.12156.1/10154", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10154/1/007_IMG_3740.jpg"],
["20.500.12156.1/10155", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10155/1/007_IMG_3742.jpg"],
["20.500.12156.1/14358", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14358/1/Alb%200261%20139.jpg"],
["20.500.12156.1/14359", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14359/1/Alb%200261%20136.jpg"],
["20.500.12156.1/14360", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14360/1/Alb%200261%20141.jpg"],
["20.500.12156.1/14361", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14361/1/Alb%200261%20142.jpg"],
["20.500.12156.1/14362", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14362/1/Alb%200261%20143.jpg"],
["20.500.12156.1/14363", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14363/1/Alb%200261%20144.jpg"],
["20.500.12156.1/14364", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14364/1/Alb%200261%20146.jpg"],
["20.500.12156.1/14368", "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14368/1/Alb%200261%20163.jpg"],
];
const PAUSA = 5000; // v1 usava 3000; mais folga depois do rate-limit
const BACKOFF = 30000; // v1 dormia 60s por erro e moia os 71: 71 min
const MAX_SEGUIDOS = 5; // desiste depois disso -- nao martela a origem
const CHAVE = 'bras_faltantes_73';
const sleep = ms => new Promise(r => setTimeout(r, ms));
const jit = () => PAUSA + Math.floor(Math.random() * 1500);
const FEITOS = new Set(JSON.parse(localStorage.getItem(CHAVE) || '[]'));
const marcar = h => { FEITOS.add(h);
localStorage.setItem(CHAVE, JSON.stringify([...FEITOS])); };
const S = {fase: 'ocioso', feito: 0, erros: 0, total: 0, atual: '', parar: false};
const FALHAS = [];
function resumo() {
const c = {};
FALHAS.forEach(f => { c[f.codigo] = (c[f.codigo] || 0) + 1; });
return c;
}
async function baixarUm(handle, url) {
const r = await fetch(url, {credentials: 'same-origin'});
if (!r.ok) { const e = new Error('HTTP ' + r.status); e.codigo = r.status; throw e; }
const b = await r.blob();
if (b.size < 1024) { const e = new Error('blob ' + b.size + 'B'); e.codigo = 'blob-curto'; throw e; }
const orig = decodeURIComponent(url.split('/').pop());
const nome = `BRAS__${handle.replace(/\//g, '_')}__${orig}`.replace(/[\\/:*?"<>|]/g, '_');
const a = document.createElement('a');
a.href = URL.createObjectURL(b); a.download = nome;
document.body.appendChild(a); a.click(); a.remove();
setTimeout(() => URL.revokeObjectURL(a.href), 30000);
return b.size;
}
async function iniciar() {
if (S.fase === 'baixando') { // <-- o defeito nº1 da v1
console.warn('FALTA ja esta rodando (' + S.atual + '). Use FALTA.parar() antes.');
return;
}
S.parar = false; S.fase = 'baixando'; FALHAS.length = 0;
const lista = FALTANTES.filter(([h]) => !FEITOS.has(h));
S.total = lista.length; S.feito = 0; S.erros = 0;
console.log(`FALTA v2: ${lista.length} pendentes, ~${Math.ceil(lista.length * PAUSA / 60000)} min se tudo der certo`);
let seguidos = 0;
for (let i = 0; i < lista.length; i++) {
if (S.parar) { S.fase = 'parado'; console.log('parado em ' + (i + 1)); return; }
const [handle, url] = lista[i];
S.atual = `${i + 1}/${lista.length} ${handle}`;
try {
const bytes = await baixarUm(handle, url);
marcar(handle); S.feito++; seguidos = 0;
console.log(` ok ${handle} ${(bytes / 1048576).toFixed(1)} MB (${i + 1}/${lista.length})`);
} catch (e) {
S.erros++; seguidos++;
FALHAS.push({handle, url, codigo: e.codigo ?? 'rede', erro: e.message});
console.warn(` ERRO ${handle}: ${e.message}`);
if (seguidos >= MAX_SEGUIDOS) { // <-- o defeito nº2 da v1
S.fase = 'abortado';
console.error(`\nFALTA abortou: ${seguidos} erros seguidos. A origem esta recusando -- insistir so piora.`);
console.table(resumo());
console.log('Espere (30+ min) e rode FALTA.retomar(). O que ja baixou esta marcado.');
return;
}
await sleep(BACKOFF);
}
await sleep(jit());
}
S.fase = 'concluido';
console.log(`\nFALTA v2 concluido. ok=${S.feito} erro=${S.erros}`);
if (FALHAS.length) { console.table(resumo()); console.table(FALHAS); }
else console.log('Trilha 1 fechada: 13.059/13.059. O subidor sobe o resto sozinho.');
}
window.FALTA = {
iniciar, status: () => ({...S}), retomar: () => iniciar(),
parar: () => { S.parar = true; },
pendentes: () => FALTANTES.filter(([h]) => !FEITOS.has(h)).map(([h]) => h),
erros: () => [...FALHAS], resumo,
limparMarcas: () => localStorage.removeItem(CHAVE),
};
console.log(`FALTA v2 pronto: ${window.FALTA.pendentes().length} pendentes de ${FALTANTES.length}.`);
console.log('Rode FALTA.iniciar()');
})();
+2
View File
@@ -0,0 +1,2 @@
{"handle":"20.500.12156.1/6066","title":"1. Centenário da Independência do Brasil : Exposição","community":"Biblioteca Nacional","preservation_url":"https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6066/1/imagem.jpg","legacy_url":"http://objdigital.bn.br/objdigital2/acervo_digital/div_iconografia/icon843660/icon843660.jpg","rights":"Biblioteca Nacional (Brasil)"}
{"handle":"20.500.12156.1/10377","title":"Cemitério de Pistoia","community":"Instituto Moreira Salles","preservation_url":"https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10377/1/LB.001%288%29.dvft.JPG","legacy_url":null,"rights":null}
+204
View File
@@ -0,0 +1,204 @@
/* ===================================================================
Coletor da Brasiliana Fotografica -- TRILHA PARALELA (Chrome), roda
DENTRO da pagina.
Copia de coletor.js, com duas diferencas deliberadas:
1. COM exclui '2' (Biblioteca Nacional) -- essa comunidade ja esta
sendo colhida pela outra instancia (Brave) e e a maior das duas
(2.726 itens), entao e a que menos risco de sobreposicao corre
se for deixada so pro Brave terminar.
2. exporta como brasiliana-catalogo-chrome.jsonl (nome diferente,
pra nao colidir com o download da outra aba e facilitar o merge
com dedupe por handle depois).
Ainda existe sobreposicao possivel: se o Brave terminar Biblioteca
Nacional e avancar pras comunidades que esta aba tambem esta colhendo,
ele vai re-colher itens que ja estao aqui (redundante, nao incorreto --
dedupe por handle no merge final resolve). Aceito conscientemente ao
rodar as duas trilhas em paralelo (dobra o volume de requisicoes no
servidor da BN vs. so uma trilha).
IndexedDB proprio ('bras', separado por navegador/perfil -- sem colisao
com o do Brave mesmo com o mesmo nome).
=================================================================== */
(() => {
const BASE = 'https://brasilianafotografica.bn.gov.br/brasiliana';
const PREFIX= '20.500.12156.1';
const RPP = 100;
const PAUSA = 2000; // ms entre requisicoes. Nao baixe disso.
const COM = {
'1753':'Instituto Moreira Salles',
'3540':'Arquivo Geral da Cidade do Rio de Janeiro',
'3541':'Diretoria do Patrimonio Historico e Documentacao da Marinha',
'4342':'Leibniz-Institut fuer Laenderkunde, Leipzig', '4884':'Arquivo Nacional',
'4886':'Museu da Republica', '5018':'Fiocruz', '6142':'Museu Historico Nacional',
'6660':'Fundacao Joaquim Nabuco', '7072':'Museu Aeroespacial',
'11464':'Escola de Ciencias Sociais (FGV CPDOC)',
'12796':'Instituto Historico e Geografico Brasileiro',
'13099':'Instituto Brasileiro de Geografia e Estatistica',
'14087':'Museu Paraense Emilio Goeldi',
};
const ESPERADO = 10310; // 13036 - 2726 (Biblioteca Nacional, fica com o Brave)
const sleep = ms => new Promise(r => setTimeout(r, ms));
const jit = () => PAUSA + Math.floor(Math.random() * 600); // ritmo, nao metronomo
// ---------- IndexedDB ----------
let db;
const abrir = () => new Promise((res, rej) => {
const q = indexedDB.open('bras', 1);
q.onupgradeneeded = e => { const d = e.target.result;
if (!d.objectStoreNames.contains('fila')) d.createObjectStore('fila');
if (!d.objectStoreNames.contains('itens')) d.createObjectStore('itens'); };
q.onsuccess = e => res(e.target.result);
q.onerror = () => rej(q.error);
});
const put = (st, k, v) => new Promise((res, rej) => {
const t = db.transaction(st, 'readwrite'); t.objectStore(st).put(v, k);
t.oncomplete = res; t.onerror = () => rej(t.error); });
const putMuitos = (st, pares) => new Promise((res, rej) => {
const t = db.transaction(st, 'readwrite'), o = t.objectStore(st);
for (const [k, v] of pares) o.put(v, k);
t.oncomplete = res; t.onerror = () => rej(t.error); });
const keys = st => new Promise((res, rej) => {
const r = db.transaction(st).objectStore(st).getAllKeys();
r.onsuccess = () => res(r.result); r.onerror = () => rej(r.error); });
const getAll = st => new Promise((res, rej) => {
const r = db.transaction(st).objectStore(st).getAll();
r.onsuccess = () => res(r.result); r.onerror = () => rej(r.error); });
const get = (st, k) => new Promise(res => {
const r = db.transaction(st).objectStore(st).get(k);
r.onsuccess = () => res(r.result); r.onerror = () => res(undefined); });
async function pegar(u) {
const r = await fetch(u, {credentials: 'same-origin'});
if (!r.ok) throw new Error('HTTP ' + r.status);
return r.text();
}
// ---------- extracao ----------
function extrair(html, h, com) {
const d = new DOMParser().parseFromString(html, 'text/html');
const dc = [...d.querySelectorAll('meta[name]')]
.filter(m => /^(DC|DCTERMS|citation)/.test(m.name))
.map(m => ({name: m.name, value: m.content}));
const a = n => (dc.find(x => x.name === n) || {}).value || null;
// a tabela do ?show=full traz campos que nao viram meta tag, entre eles dc.rights
const tb = [...d.querySelectorAll('table.detailtable tr, table.ds-includeSet-table tr')]
.map(tr => { const td = [...tr.querySelectorAll('td')].map(c => c.textContent.trim());
return td.length >= 2 ? {field: td[0], value: td[1]} : null; })
.filter(Boolean);
const leg = dc.filter(x => x.name === 'DC.identifier' && /objdigital/.test(x.value || ''))
.map(x => x.value)[0] || null;
return {
handle: `${PREFIX}/${h}`,
source_url: `${BASE}/handle/${PREFIX}/${h}?show=full`,
fetched_at: new Date().toISOString(),
title: a('DC.title'),
community: com,
// bitstream do DSpace = arquivo de PRESERVACAO (ate 8679x6484, 34,7 MB).
// objdigital = copia MENOR (~2600x2100, ~330 KB), so procedencia.
preservation_url: a('citation_pdf_url'),
legacy_url: leg,
rights: (tb.find(t => /^dc\.rights/i.test(t.field)) || {}).value || null,
dublin_core: dc,
full_table: tb,
};
}
const S = {fase: 'iniciando', atual: '', colhidos: 0, erros: 0, parar: false, t0: Date.now()};
// ---------- fase 1: enumerar ----------
async function descobrir() {
S.fase = 'enumerando';
const RE = new RegExp('handle/' + PREFIX.replace(/\./g, '\\.') + '/(\\d+)', 'g');
for (const [h, nome] of Object.entries(COM)) {
if (S.parar) return;
S.atual = nome;
let off = 0, total = null, vistos = new Set();
while (!S.parar) {
let html;
try { html = await pegar(`${BASE}/handle/${PREFIX}/${h}/browse?type=title&rpp=${RPP}&offset=${off}`); }
catch (e) { console.warn(` ${nome} offset=${off}: ${e.message} -- pausa de 30s`);
await sleep(30000); continue; }
if (total === null) {
const m = html.replace(/<[^>]+>/g, ' ')
.match(/no momento\D{0,24}[\d.,]+\s*-\s*[\d.,]+\s*de\s*([\d.,]+)/i);
total = m ? +m[1].replace(/[.,]/g, '') : null;
}
const ach = new Set([...html.matchAll(RE)].map(m => m[1])); ach.delete(h);
let novos = 0; for (const x of ach) if (!vistos.has(x)) { vistos.add(x); novos++; }
await putMuitos('fila', [...ach].map(x => [x, nome]));
off += RPP;
if (!novos || (total && off >= total)) break;
await sleep(jit());
}
console.log(` ${nome.padEnd(46)} ${String(vistos.size).padStart(6)}`);
await sleep(jit());
}
const n = (await keys('fila')).length;
console.log(`\nEnumerados: ${n} (esperado ${ESPERADO})`);
}
// ---------- fase 2: metadados ----------
async function coletar() {
S.fase = 'colhendo';
const fila = await keys('fila'), feitos = new Set(await keys('itens'));
const pend = fila.filter(h => !feitos.has(h));
console.log(`${pend.length} itens a colher (~${(pend.length * PAUSA / 3600000).toFixed(1)} h)`);
for (let i = 0; i < pend.length; i++) {
if (S.parar) { S.fase = 'parado'; return; }
const h = pend[i];
S.atual = `${i + 1}/${pend.length}`;
try {
const html = await pegar(`${BASE}/handle/${PREFIX}/${h}?show=full`);
await put('itens', h, extrair(html, h, await get('fila', h)));
S.colhidos++;
} catch (e) {
await put('itens', h, {handle: `${PREFIX}/${h}`, erro: String(e).slice(0, 80)});
S.erros++;
// recuou? entao respire, nao insista -- insistir e o que vira bloqueio
if (/HTTP 4|HTTP 5/.test(String(e))) { console.warn(` ${h}: ${e.message} -- pausa de 60s`);
await sleep(60000); }
}
if (i % 100 === 0) console.log(` ${i}/${pend.length} ok=${S.colhidos} erro=${S.erros}`);
await sleep(jit());
}
S.fase = 'concluido';
console.log(`\nConcluido. ok=${S.colhidos} erro=${S.erros}. Agora: await BRAS_CHROME.exportar()`);
}
// ---------- exportar ----------
async function exportar() {
const todos = await getAll('itens');
const a = document.createElement('a');
a.href = URL.createObjectURL(new Blob([todos.map(o => JSON.stringify(o)).join('\n')],
{type: 'application/x-ndjson'}));
a.download = 'brasiliana-catalogo-chrome.jsonl';
document.body.appendChild(a); a.click(); a.remove();
console.log(`exportadas ${todos.length} linhas para a pasta Downloads (brasiliana-catalogo-chrome.jsonl)`);
return todos.length;
}
window.BRAS_CHROME = {
status: async () => {
const naFila = (await keys('fila')).length, noBanco = (await keys('itens')).length;
const h = (Date.now() - S.t0) / 3600000;
return {fase: S.fase, atual: S.atual, naFila, noBanco, erros: S.erros,
restam: naFila - noBanco, horas: +h.toFixed(2)};
},
parar: () => { S.parar = true; console.log('parando apos a requisicao atual'); },
retomar: () => { S.parar = false; coletar(); },
exportar,
limpar: () => indexedDB.deleteDatabase('bras'),
};
(async () => {
db = await abrir();
if ((await keys('fila')).length < ESPERADO) await descobrir();
await coletar();
})();
console.log('BRAS_CHROME iniciado (trilha paralela, sem Biblioteca Nacional). Acompanhe com: await BRAS_CHROME.status()');
})();
+93
View File
@@ -0,0 +1,93 @@
/* ===================================================================
Reparo do coletor.js -- reprocessa itens que ficaram marcados com
erro (ex.: queda de rede no meio da colheita).
POR QUE PRECISA: coletar() grava {handle, erro} em 'itens' quando
uma requisicao falha. Isso conta como "feito" pro loop principal --
ele nao tenta de novo sozinho. Falhas de rede (TypeError: Failed to
fetch) tambem nao disparam a pausa de 60s (so HTTP 4xx/5xx disparam),
entao uma queda pode gerar uma rajada de erros em vez de so alguns.
COMO USAR
1. Rode DEPOIS que await BRAS.status() mostrar fase 'concluido'
(pra nao competir por rede com o loop principal).
2. Cole este arquivo inteiro no mesmo console (mesma aba/origem).
3. Acompanhe pelo proprio console.log -- ele avisa quantos achou
e reporta ok/erro no final.
4. Se ainda sobrar erro (rede caiu nesse meio-tempo tambem), rode
de novo -- e idempotente.
=================================================================== */
(async () => {
const BASE = 'https://brasilianafotografica.bn.gov.br/brasiliana';
const PREFIX= '20.500.12156.1';
const PAUSA = 2000;
const sleep = ms => new Promise(r => setTimeout(r, ms));
const jit = () => PAUSA + Math.floor(Math.random() * 600);
const db = await new Promise((res, rej) => {
const q = indexedDB.open('bras', 1);
q.onsuccess = () => res(q.result); q.onerror = () => rej(q.error);
});
const get = (st, k) => new Promise(res => {
const r = db.transaction(st).objectStore(st).get(k);
r.onsuccess = () => res(r.result); r.onerror = () => res(undefined); });
const put = (st, k, v) => new Promise((res, rej) => {
const t = db.transaction(st, 'readwrite'); t.objectStore(st).put(v, k);
t.oncomplete = res; t.onerror = () => rej(t.error); });
const getAll = st => new Promise((res, rej) => {
const r = db.transaction(st).objectStore(st).getAll();
r.onsuccess = () => res(r.result); r.onerror = () => rej(r.error); });
async function pegar(u) {
const r = await fetch(u, {credentials: 'same-origin'});
if (!r.ok) throw new Error('HTTP ' + r.status);
return r.text();
}
function extrair(html, h, com) {
const d = new DOMParser().parseFromString(html, 'text/html');
const dc = [...d.querySelectorAll('meta[name]')]
.filter(m => /^(DC|DCTERMS|citation)/.test(m.name))
.map(m => ({name: m.name, value: m.content}));
const a = n => (dc.find(x => x.name === n) || {}).value || null;
const tb = [...d.querySelectorAll('table.detailtable tr, table.ds-includeSet-table tr')]
.map(tr => { const td = [...tr.querySelectorAll('td')].map(c => c.textContent.trim());
return td.length >= 2 ? {field: td[0], value: td[1]} : null; })
.filter(Boolean);
const leg = dc.filter(x => x.name === 'DC.identifier' && /objdigital/.test(x.value || ''))
.map(x => x.value)[0] || null;
return {
handle: `${PREFIX}/${h}`,
source_url: `${BASE}/handle/${PREFIX}/${h}?show=full`,
fetched_at: new Date().toISOString(),
title: a('DC.title'),
community: com,
preservation_url: a('citation_pdf_url'),
legacy_url: leg,
rights: (tb.find(t => /^dc\.rights/i.test(t.field)) || {}).value || null,
dublin_core: dc,
full_table: tb,
};
}
const todos = await getAll('itens');
const falhados = todos.filter(x => x.erro);
console.log(`${falhados.length} itens com erro -- reprocessando`);
let ok = 0, erro = 0;
for (const item of falhados) {
const h = item.handle.split('/').pop();
const com = await get('fila', h); // recupera o nome da comunidade
try {
const html = await pegar(`${BASE}/handle/${PREFIX}/${h}?show=full`);
await put('itens', h, extrair(html, h, com));
ok++;
} catch (e) {
await put('itens', h, {handle: item.handle, erro: String(e).slice(0, 80)});
erro++;
if (/HTTP 4|HTTP 5/.test(String(e))) await sleep(60000);
}
await sleep(jit());
}
console.log(`\nReparo concluido. ok=${ok} ainda-com-erro=${erro}`);
})();
+202
View File
@@ -0,0 +1,202 @@
/* ===================================================================
Coletor da Brasiliana Fotografica -- RETOMADA (resume + reparo juntos).
Diferenca do coletor.js original: coletar() considera "feito" so quem
tem registro SEM campo .erro. Handles nunca tentados E handles que
falharam antes (rede caiu, HTTP 4xx/5xx) entram juntos na mesma
passada -- nao precisa colar coletor.js e depois coletor-reparo.js
separado.
COMO USAR
1. Abra https://brasilianafotografica.bn.gov.br/brasiliana/
(mesma origem de sempre -- reaproveita o IndexedDB existente)
2. Cole este arquivo inteiro no console
3. Ele pula a descoberta (fila ja esta cheia) e colhe so o que
falta: pendentes + os que tinham erro
4. await BRAS.status() pra acompanhar; await BRAS.exportar() no fim
=================================================================== */
(() => {
const BASE = 'https://brasilianafotografica.bn.gov.br/brasiliana';
const PREFIX= '20.500.12156.1';
const RPP = 100;
const PAUSA = 2000; // ms entre requisicoes. Nao baixe disso.
const COM = {
'2':'Biblioteca Nacional', '1753':'Instituto Moreira Salles',
'3540':'Arquivo Geral da Cidade do Rio de Janeiro',
'3541':'Diretoria do Patrimonio Historico e Documentacao da Marinha',
'4342':'Leibniz-Institut fuer Laenderkunde, Leipzig', '4884':'Arquivo Nacional',
'4886':'Museu da Republica', '5018':'Fiocruz', '6142':'Museu Historico Nacional',
'6660':'Fundacao Joaquim Nabuco', '7072':'Museu Aeroespacial',
'11464':'Escola de Ciencias Sociais (FGV CPDOC)',
'12796':'Instituto Historico e Geografico Brasileiro',
'13099':'Instituto Brasileiro de Geografia e Estatistica',
'14087':'Museu Paraense Emilio Goeldi',
};
const ESPERADO = 13036;
const sleep = ms => new Promise(r => setTimeout(r, ms));
const jit = () => PAUSA + Math.floor(Math.random() * 600);
// ---------- IndexedDB ----------
let db;
const abrir = () => new Promise((res, rej) => {
const q = indexedDB.open('bras', 1);
q.onupgradeneeded = e => { const d = e.target.result;
if (!d.objectStoreNames.contains('fila')) d.createObjectStore('fila');
if (!d.objectStoreNames.contains('itens')) d.createObjectStore('itens'); };
q.onsuccess = e => res(e.target.result);
q.onerror = () => rej(q.error);
});
const put = (st, k, v) => new Promise((res, rej) => {
const t = db.transaction(st, 'readwrite'); t.objectStore(st).put(v, k);
t.oncomplete = res; t.onerror = () => rej(t.error); });
const putMuitos = (st, pares) => new Promise((res, rej) => {
const t = db.transaction(st, 'readwrite'), o = t.objectStore(st);
for (const [k, v] of pares) o.put(v, k);
t.oncomplete = res; t.onerror = () => rej(t.error); });
const keys = st => new Promise((res, rej) => {
const r = db.transaction(st).objectStore(st).getAllKeys();
r.onsuccess = () => res(r.result); r.onerror = () => rej(r.error); });
const get = (st, k) => new Promise(res => {
const r = db.transaction(st).objectStore(st).get(k);
r.onsuccess = () => res(r.result); r.onerror = () => res(undefined); });
const getAll = st => new Promise((res, rej) => {
const r = db.transaction(st).objectStore(st).getAll();
r.onsuccess = () => res(r.result); r.onerror = () => rej(r.error); });
// so as chaves cujo valor NAO tem .erro -- feito de verdade
const chavesOk = () => new Promise((res, rej) => {
const s = new Set();
const req = db.transaction('itens').objectStore('itens').openCursor();
req.onsuccess = e => { const c = e.target.result;
if (c) { if (!c.value.erro) s.add(c.key); c.continue(); } else res(s); };
req.onerror = () => rej(req.error);
});
async function pegar(u) {
const r = await fetch(u, {credentials: 'same-origin'});
if (!r.ok) throw new Error('HTTP ' + r.status);
return r.text();
}
// ---------- extracao ----------
function extrair(html, h, com) {
const d = new DOMParser().parseFromString(html, 'text/html');
const dc = [...d.querySelectorAll('meta[name]')]
.filter(m => /^(DC|DCTERMS|citation)/.test(m.name))
.map(m => ({name: m.name, value: m.content}));
const a = n => (dc.find(x => x.name === n) || {}).value || null;
const tb = [...d.querySelectorAll('table.detailtable tr, table.ds-includeSet-table tr')]
.map(tr => { const td = [...tr.querySelectorAll('td')].map(c => c.textContent.trim());
return td.length >= 2 ? {field: td[0], value: td[1]} : null; })
.filter(Boolean);
const leg = dc.filter(x => x.name === 'DC.identifier' && /objdigital/.test(x.value || ''))
.map(x => x.value)[0] || null;
return {
handle: `${PREFIX}/${h}`,
source_url: `${BASE}/handle/${PREFIX}/${h}?show=full`,
fetched_at: new Date().toISOString(),
title: a('DC.title'),
community: com,
preservation_url: a('citation_pdf_url'),
legacy_url: leg,
rights: (tb.find(t => /^dc\.rights/i.test(t.field)) || {}).value || null,
dublin_core: dc,
full_table: tb,
};
}
const S = {fase: 'iniciando', atual: '', colhidos: 0, erros: 0, parar: false, t0: Date.now()};
// ---------- fase 1: enumerar (so roda se a fila estiver incompleta) ----------
async function descobrir() {
S.fase = 'enumerando';
const RE = new RegExp('handle/' + PREFIX.replace(/\./g, '\\.') + '/(\\d+)', 'g');
for (const [h, nome] of Object.entries(COM)) {
if (S.parar) return;
S.atual = nome;
let off = 0, total = null, vistos = new Set();
while (!S.parar) {
let html;
try { html = await pegar(`${BASE}/handle/${PREFIX}/${h}/browse?type=title&rpp=${RPP}&offset=${off}`); }
catch (e) { console.warn(` ${nome} offset=${off}: ${e.message} -- pausa de 30s`);
await sleep(30000); continue; }
if (total === null) {
const m = html.replace(/<[^>]+>/g, ' ')
.match(/no momento\D{0,24}[\d.,]+\s*-\s*[\d.,]+\s*de\s*([\d.,]+)/i);
total = m ? +m[1].replace(/[.,]/g, '') : null;
}
const ach = new Set([...html.matchAll(RE)].map(m => m[1])); ach.delete(h);
let novos = 0; for (const x of ach) if (!vistos.has(x)) { vistos.add(x); novos++; }
await putMuitos('fila', [...ach].map(x => [x, nome]));
off += RPP;
if (!novos || (total && off >= total)) break;
await sleep(jit());
}
console.log(` ${nome.padEnd(46)} ${String(vistos.size).padStart(6)}`);
await sleep(jit());
}
const n = (await keys('fila')).length;
console.log(`\nEnumerados: ${n} (esperado ${ESPERADO})`);
}
// ---------- fase 2: metadados (pendentes + com erro, juntos) ----------
async function coletar() {
S.fase = 'colhendo';
const fila = await keys('fila'), ok = await chavesOk();
const pend = fila.filter(h => !ok.has(h));
console.log(`${pend.length} itens a colher/reparar (~${(pend.length * PAUSA / 3600000).toFixed(1)} h)`);
for (let i = 0; i < pend.length; i++) {
if (S.parar) { S.fase = 'parado'; return; }
const h = pend[i];
S.atual = `${i + 1}/${pend.length}`;
try {
const html = await pegar(`${BASE}/handle/${PREFIX}/${h}?show=full`);
await put('itens', h, extrair(html, h, await get('fila', h)));
S.colhidos++;
} catch (e) {
await put('itens', h, {handle: `${PREFIX}/${h}`, erro: String(e).slice(0, 80)});
S.erros++;
if (/HTTP 4|HTTP 5/.test(String(e))) { console.warn(` ${h}: ${e.message} -- pausa de 60s`);
await sleep(60000); }
}
if (i % 100 === 0) console.log(` ${i}/${pend.length} ok=${S.colhidos} erro=${S.erros}`);
await sleep(jit());
}
S.fase = 'concluido';
console.log(`\nConcluido. ok=${S.colhidos} erro=${S.erros}. Agora: await BRAS.exportar()`);
}
// ---------- exportar ----------
async function exportar() {
const todos = await getAll('itens');
const a = document.createElement('a');
a.href = URL.createObjectURL(new Blob([todos.map(o => JSON.stringify(o)).join('\n')],
{type: 'application/x-ndjson'}));
a.download = 'brasiliana-catalogo.jsonl';
document.body.appendChild(a); a.click(); a.remove();
console.log(`exportadas ${todos.length} linhas para a pasta Downloads`);
return todos.length;
}
window.BRAS = {
status: async () => {
const naFila = (await keys('fila')).length, ok = await chavesOk();
const h = (Date.now() - S.t0) / 3600000;
return {fase: S.fase, atual: S.atual, naFila, noBanco: ok.size, erros: S.erros,
restam: naFila - ok.size, horas: +h.toFixed(2)};
},
parar: () => { S.parar = true; console.log('parando apos a requisicao atual'); },
retomar: () => { S.parar = false; coletar(); },
exportar,
limpar: () => indexedDB.deleteDatabase('bras'),
};
(async () => {
db = await abrir();
if ((await keys('fila')).length < ESPERADO) await descobrir();
await coletar();
})();
console.log('BRAS retomado (pendentes + erros juntos). Acompanhe com: await BRAS.status()');
})();
+142
View File
@@ -0,0 +1,142 @@
/* ===================================================================
Coletor dos posts editoriais do WordPress (raiz do dominio, separado
do /brasiliana/ DSpace). Roda igual o coletor.js -- dentro da pagina,
fetch() same-origin, grava em IndexedDB proprio ('bras_wp').
Nao baixa imagem nenhuma: as fotos dos posts ja apontam (em miniatura)
pros mesmos handles que o acervo principal ja cobre. Só o texto e o
mapeamento pra handles tem valor novo aqui.
COMO USAR
1. Cole este arquivo na mesma aba (ou qualquer aba da mesma origem)
2. await WP.descobrir() -- enumera todos os posts (categorias x paginacao)
3. await WP.colher() -- busca o conteudo de cada post
4. await WP.status()
5. await WP.exportar() -- baixa wordpress-posts.jsonl
=================================================================== */
(() => {
const BASE = 'https://brasilianafotografica.bn.gov.br';
const CATS = ["25","26","311","5","9","35","189","1"];
const PAUSA = 1500;
const sleep = ms => new Promise(r => setTimeout(r, ms));
const jit = () => PAUSA + Math.floor(Math.random() * 500);
let db;
const abrir = () => new Promise((res, rej) => {
const q = indexedDB.open('bras_wp', 1);
q.onupgradeneeded = e => { const d = e.target.result;
if (!d.objectStoreNames.contains('fila')) d.createObjectStore('fila');
if (!d.objectStoreNames.contains('posts')) d.createObjectStore('posts'); };
q.onsuccess = e => res(e.target.result); q.onerror = () => rej(q.error);
});
const put = (st, k, v) => new Promise((res, rej) => {
const t = db.transaction(st, 'readwrite'); t.objectStore(st).put(v, k);
t.oncomplete = res; t.onerror = () => rej(t.error);
});
const putMuitos = (st, pares) => new Promise((res, rej) => {
const t = db.transaction(st, 'readwrite'), o = t.objectStore(st);
for (const [k, v] of pares) o.put(v, k);
t.oncomplete = res; t.onerror = () => rej(t.error);
});
const keys = st => new Promise((res, rej) => {
const r = db.transaction(st).objectStore(st).getAllKeys();
r.onsuccess = () => res(r.result); r.onerror = () => rej(r.error);
});
const getAll = st => new Promise((res, rej) => {
const r = db.transaction(st).objectStore(st).getAll();
r.onsuccess = () => res(r.result); r.onerror = () => rej(r.error);
});
const S = {fase: 'ocioso', atual: '', colhidos: 0, erros: 0, parar: false};
async function descobrir() {
S.fase = 'descobrindo';
for (const cat of CATS) {
if (S.parar) return;
S.atual = `categoria ${cat}`;
let pagina = 1, vazias = 0;
while (vazias < 1 && pagina <= 100) {
if (S.parar) return;
const url = pagina === 1 ? `${BASE}/?cat=${cat}` : `${BASE}/?cat=${cat}&paged=${pagina}`;
let html;
try { const r = await fetch(url, {credentials:'same-origin'}); html = await r.text(); }
catch(e) { vazias++; pagina++; await sleep(2000); continue; }
const d = new DOMParser().parseFromString(html, 'text/html');
const ids = [...d.querySelectorAll('article')]
.map(a => a.id?.match(/post-(\d+)/)?.[1]).filter(Boolean);
if (!ids.length) { vazias++; pagina++; await sleep(300); continue; }
await putMuitos('fila', ids.map(id => [id, cat]));
pagina++;
await sleep(jit());
}
}
const n = (await keys('fila')).length;
console.log(`Descoberta: ${n} posts unicos`);
}
function limpaTexto(el) {
const clone = el.cloneNode(true);
clone.querySelectorAll('.sharify-container, script, style').forEach(e => e.remove());
return clone.textContent.replace(/[ \t]+/g, ' ').replace(/\n{3,}/g, '\n\n').trim();
}
async function colherUm(id) {
const r = await fetch(`${BASE}/?p=${id}`, {credentials: 'same-origin'});
if (!r.ok) throw new Error('HTTP ' + r.status);
const html = await r.text();
const d = new DOMParser().parseFromString(html, 'text/html');
const titulo = d.querySelector('.entry-title')?.textContent?.trim() || d.title;
const autor = d.querySelector('.author')?.textContent?.trim() || null;
const dataIso = d.querySelector('time')?.getAttribute('datetime') || null;
const categorias = [...new Set([...d.querySelectorAll('a[href*="?cat="]')].map(a => a.textContent.trim()).filter(Boolean))];
const contentEl = d.querySelector('.entry-content');
const corpo = contentEl ? limpaTexto(contentEl) : null;
const handles = [...new Set([...html.matchAll(/handle\/(20\.500\.12156\.1)\/(\d+)/g)].map(m => `${m[1]}/${m[2]}`))];
return {
id, url: `${BASE}/?p=${id}`, titulo, autor, data_iso: dataIso,
categorias, corpo, handles_referenciados: handles, fetched_at: new Date().toISOString(),
};
}
async function colher() {
S.fase = 'colhendo';
const fila = await keys('fila'), feitos = new Set(await keys('posts'));
const pend = fila.filter(id => !feitos.has(id));
console.log(`${pend.length} posts a colher`);
for (let i = 0; i < pend.length; i++) {
if (S.parar) { S.fase = 'parado'; return; }
const id = pend[i];
S.atual = `${i + 1}/${pend.length}`;
try {
const post = await colherUm(id);
await put('posts', id, post);
S.colhidos++;
} catch (e) {
await put('posts', id, {id, erro: String(e).slice(0, 80)});
S.erros++;
}
if (i % 25 === 0) console.log(` ${i}/${pend.length} ok=${S.colhidos} erro=${S.erros}`);
await sleep(jit());
}
S.fase = 'concluido';
console.log(`\nConcluido. ok=${S.colhidos} erro=${S.erros}`);
}
async function exportar() {
const todos = await getAll('posts');
const a = document.createElement('a');
a.href = URL.createObjectURL(new Blob([todos.map(o => JSON.stringify(o)).join('\n')],
{type: 'application/x-ndjson'}));
a.download = 'wordpress-posts.jsonl';
document.body.appendChild(a); a.click(); a.remove();
console.log(`exportados ${todos.length} posts`);
return todos.length;
}
window.WP = {
descobrir, colher, exportar,
status: async () => ({...S, naFila: (await keys('fila')).length, noBanco: (await keys('posts')).length}),
parar: () => { S.parar = true; },
};
(async () => { db = await abrir(); console.log('WP pronto. await WP.descobrir() depois await WP.colher()'); })();
})();
+207
View File
@@ -0,0 +1,207 @@
/* ===================================================================
Coletor da Brasiliana Fotografica -- roda DENTRO da pagina.
POR QUE ASSIM: o Cloudflare do site bloqueia navegador dirigido por
automacao (Playwright headless E headed levam 403), mas nao toca em
fetch() disparado de dentro de uma pagina aberta no seu Chrome --
mesma origem, sessao real, zero sinal de automacao.
Medido: >100 requisicoes seguidas sem bloqueio, a ~2s de intervalo.
ARMAZENAMENTO: IndexedDB, nao localStorage. 13.036 itens com metadados
completos dao ~39 MB; localStorage estoura em ~5 MB.
COMO USAR
1. Abra https://brasilianafotografica.bn.gov.br/brasiliana/
2. DevTools (Cmd+Option+J) > Console > cole este arquivo inteiro
3. Ele comeca sozinho: enumera as 15 comunidades e segue colhendo.
4. Deixe a aba aberta (~7 h). Pode usar o resto do Chrome normalmente.
ACOMPANHAR / CONTROLAR
await BRAS.status() onde esta
BRAS.parar() interrompe (nao perde nada)
BRAS.retomar() continua de onde parou
await BRAS.exportar() baixa brasiliana-catalogo.jsonl
Fechou a aba? Reabra a pagina, cole o arquivo de novo: ele retoma.
=================================================================== */
(() => {
const BASE = 'https://brasilianafotografica.bn.gov.br/brasiliana';
const PREFIX= '20.500.12156.1';
const RPP = 100;
const PAUSA = 2000; // ms entre requisicoes. Nao baixe disso.
const COM = {
'2':'Biblioteca Nacional', '1753':'Instituto Moreira Salles',
'3540':'Arquivo Geral da Cidade do Rio de Janeiro',
'3541':'Diretoria do Patrimonio Historico e Documentacao da Marinha',
'4342':'Leibniz-Institut fuer Laenderkunde, Leipzig', '4884':'Arquivo Nacional',
'4886':'Museu da Republica', '5018':'Fiocruz', '6142':'Museu Historico Nacional',
'6660':'Fundacao Joaquim Nabuco', '7072':'Museu Aeroespacial',
'11464':'Escola de Ciencias Sociais (FGV CPDOC)',
'12796':'Instituto Historico e Geografico Brasileiro',
'13099':'Instituto Brasileiro de Geografia e Estatistica',
'14087':'Museu Paraense Emilio Goeldi',
};
const ESPERADO = 13036;
const sleep = ms => new Promise(r => setTimeout(r, ms));
const jit = () => PAUSA + Math.floor(Math.random() * 600); // ritmo, nao metronomo
// ---------- IndexedDB ----------
let db;
const abrir = () => new Promise((res, rej) => {
const q = indexedDB.open('bras', 1);
q.onupgradeneeded = e => { const d = e.target.result;
if (!d.objectStoreNames.contains('fila')) d.createObjectStore('fila');
if (!d.objectStoreNames.contains('itens')) d.createObjectStore('itens'); };
q.onsuccess = e => res(e.target.result);
q.onerror = () => rej(q.error);
});
const put = (st, k, v) => new Promise((res, rej) => {
const t = db.transaction(st, 'readwrite'); t.objectStore(st).put(v, k);
t.oncomplete = res; t.onerror = () => rej(t.error); });
const putMuitos = (st, pares) => new Promise((res, rej) => {
const t = db.transaction(st, 'readwrite'), o = t.objectStore(st);
for (const [k, v] of pares) o.put(v, k);
t.oncomplete = res; t.onerror = () => rej(t.error); });
const keys = st => new Promise((res, rej) => {
const r = db.transaction(st).objectStore(st).getAllKeys();
r.onsuccess = () => res(r.result); r.onerror = () => rej(r.error); });
const getAll = st => new Promise((res, rej) => {
const r = db.transaction(st).objectStore(st).getAll();
r.onsuccess = () => res(r.result); r.onerror = () => rej(r.error); });
const get = (st, k) => new Promise(res => {
const r = db.transaction(st).objectStore(st).get(k);
r.onsuccess = () => res(r.result); r.onerror = () => res(undefined); });
async function pegar(u) {
const r = await fetch(u, {credentials: 'same-origin'});
if (!r.ok) throw new Error('HTTP ' + r.status);
return r.text();
}
// ---------- extracao ----------
function extrair(html, h, com) {
const d = new DOMParser().parseFromString(html, 'text/html');
const dc = [...d.querySelectorAll('meta[name]')]
.filter(m => /^(DC|DCTERMS|citation)/.test(m.name))
.map(m => ({name: m.name, value: m.content}));
const a = n => (dc.find(x => x.name === n) || {}).value || null;
// a tabela do ?show=full traz campos que nao viram meta tag, entre eles dc.rights
const tb = [...d.querySelectorAll('table.detailtable tr, table.ds-includeSet-table tr')]
.map(tr => { const td = [...tr.querySelectorAll('td')].map(c => c.textContent.trim());
return td.length >= 2 ? {field: td[0], value: td[1]} : null; })
.filter(Boolean);
const leg = dc.filter(x => x.name === 'DC.identifier' && /objdigital/.test(x.value || ''))
.map(x => x.value)[0] || null;
return {
handle: `${PREFIX}/${h}`,
source_url: `${BASE}/handle/${PREFIX}/${h}?show=full`,
fetched_at: new Date().toISOString(),
title: a('DC.title'),
community: com,
// bitstream do DSpace = arquivo de PRESERVACAO (ate 8679x6484, 34,7 MB).
// objdigital = copia MENOR (~2600x2100, ~330 KB), so procedencia.
preservation_url: a('citation_pdf_url'),
legacy_url: leg,
rights: (tb.find(t => /^dc\.rights/i.test(t.field)) || {}).value || null,
dublin_core: dc,
full_table: tb,
};
}
const S = {fase: 'iniciando', atual: '', colhidos: 0, erros: 0, parar: false, t0: Date.now()};
// ---------- fase 1: enumerar ----------
async function descobrir() {
S.fase = 'enumerando';
const RE = new RegExp('handle/' + PREFIX.replace(/\./g, '\\.') + '/(\\d+)', 'g');
for (const [h, nome] of Object.entries(COM)) {
if (S.parar) return;
S.atual = nome;
let off = 0, total = null, vistos = new Set();
while (!S.parar) {
let html;
try { html = await pegar(`${BASE}/handle/${PREFIX}/${h}/browse?type=title&rpp=${RPP}&offset=${off}`); }
catch (e) { console.warn(` ${nome} offset=${off}: ${e.message} -- pausa de 30s`);
await sleep(30000); continue; }
if (total === null) {
const m = html.replace(/<[^>]+>/g, ' ')
.match(/no momento\D{0,24}[\d.,]+\s*-\s*[\d.,]+\s*de\s*([\d.,]+)/i);
total = m ? +m[1].replace(/[.,]/g, '') : null;
}
const ach = new Set([...html.matchAll(RE)].map(m => m[1])); ach.delete(h);
let novos = 0; for (const x of ach) if (!vistos.has(x)) { vistos.add(x); novos++; }
await putMuitos('fila', [...ach].map(x => [x, nome]));
off += RPP;
if (!novos || (total && off >= total)) break;
await sleep(jit());
}
console.log(` ${nome.padEnd(46)} ${String(vistos.size).padStart(6)}`);
await sleep(jit());
}
const n = (await keys('fila')).length;
console.log(`\nEnumerados: ${n} (esperado ${ESPERADO})`);
}
// ---------- fase 2: metadados ----------
async function coletar() {
S.fase = 'colhendo';
const fila = await keys('fila'), feitos = new Set(await keys('itens'));
const pend = fila.filter(h => !feitos.has(h));
console.log(`${pend.length} itens a colher (~${(pend.length * PAUSA / 3600000).toFixed(1)} h)`);
for (let i = 0; i < pend.length; i++) {
if (S.parar) { S.fase = 'parado'; return; }
const h = pend[i];
S.atual = `${i + 1}/${pend.length}`;
try {
const html = await pegar(`${BASE}/handle/${PREFIX}/${h}?show=full`);
await put('itens', h, extrair(html, h, await get('fila', h)));
S.colhidos++;
} catch (e) {
await put('itens', h, {handle: `${PREFIX}/${h}`, erro: String(e).slice(0, 80)});
S.erros++;
// recuou? entao respire, nao insista -- insistir e o que vira bloqueio
if (/HTTP 4|HTTP 5/.test(String(e))) { console.warn(` ${h}: ${e.message} -- pausa de 60s`);
await sleep(60000); }
}
if (i % 100 === 0) console.log(` ${i}/${pend.length} ok=${S.colhidos} erro=${S.erros}`);
await sleep(jit());
}
S.fase = 'concluido';
console.log(`\nConcluido. ok=${S.colhidos} erro=${S.erros}. Agora: await BRAS.exportar()`);
}
// ---------- exportar ----------
async function exportar() {
const todos = await getAll('itens');
const a = document.createElement('a');
a.href = URL.createObjectURL(new Blob([todos.map(o => JSON.stringify(o)).join('\n')],
{type: 'application/x-ndjson'}));
a.download = 'brasiliana-catalogo.jsonl';
document.body.appendChild(a); a.click(); a.remove();
console.log(`exportadas ${todos.length} linhas para a pasta Downloads`);
return todos.length;
}
window.BRAS = {
status: async () => {
const naFila = (await keys('fila')).length, noBanco = (await keys('itens')).length;
const h = (Date.now() - S.t0) / 3600000;
return {fase: S.fase, atual: S.atual, naFila, noBanco, erros: S.erros,
restam: naFila - noBanco, horas: +h.toFixed(2)};
},
parar: () => { S.parar = true; console.log('parando apos a requisicao atual'); },
retomar: () => { S.parar = false; coletar(); },
exportar,
limpar: () => indexedDB.deleteDatabase('bras'),
};
(async () => {
db = await abrir();
if ((await keys('fila')).length < ESPERADO) await descobrir();
await coletar();
})();
console.log('BRAS iniciado. Acompanhe com: await BRAS.status()');
})();
+75
View File
@@ -0,0 +1,75 @@
#!/usr/bin/env python3
"""
Converte wordpress-posts.jsonl (saido do coletor-wordpress.js) em um .md
por post, com front-matter YAML. Imagens NAO sao baixadas -- os posts so
usam miniaturas dos mesmos handles que o acervo principal ja cobre (ver
PLANO.md); o valor novo aqui e o texto + o mapeamento pra handles.
Uso: python3 converte-wordpress-md.py [wordpress-posts.jsonl] [pasta-saida]
"""
import json, re, sys, unicodedata
from pathlib import Path
ENTRADA = Path(sys.argv[1] if len(sys.argv) > 1 else "wordpress-posts.jsonl")
SAIDA = Path(sys.argv[2] if len(sys.argv) > 2 else "../wordpress")
SAIDA.mkdir(parents=True, exist_ok=True)
def slug(txt):
txt = unicodedata.normalize("NFKD", txt).encode("ascii", "ignore").decode()
txt = re.sub(r"[^a-zA-Z0-9]+", "-", txt).strip("-").lower()
return txt[:80] or "sem-titulo"
def yaml_str(s):
if s is None:
return "null"
return '"' + str(s).replace("\\", "\\\\").replace('"', '\\"') + '"'
def yaml_lista(items):
if not items:
return "[]"
return "[" + ", ".join(yaml_str(i) for i in items) + "]"
n_ok = n_erro = 0
for linha in open(ENTRADA, encoding="utf-8"):
if not linha.strip():
continue
post = json.loads(linha)
if post.get("erro"):
n_erro += 1
continue
titulo = post.get("titulo") or f"post-{post['id']}"
nome_arquivo = f"{post['id']}-{slug(titulo)}.md"
frontmatter = "\n".join([
"---",
f"id: {post['id']}",
f"url: {yaml_str(post.get('url'))}",
f"titulo: {yaml_str(titulo)}",
f"autor: {yaml_str(post.get('autor'))}",
f"data: {yaml_str(post.get('data_iso'))}",
f"categorias: {yaml_lista(post.get('categorias') or [])}",
f"handles_referenciados: {yaml_lista(post.get('handles_referenciados') or [])}",
f"fetched_at: {yaml_str(post.get('fetched_at'))}",
"---",
])
corpo = post.get("corpo") or "*(sem conteúdo extraído)*"
# nota de referencia pros handles, com link relativo pro nosso acervo
handles = post.get("handles_referenciados") or []
nota_handles = ""
if handles:
linhas_h = "\n".join(f"- `{h}` — `itens/{h.replace('/', '_')}/`" for h in handles)
nota_handles = f"\n\n---\n\n**Itens do acervo referenciados neste post:**\n\n{linhas_h}\n"
conteudo = f"{frontmatter}\n\n# {titulo}\n\n{corpo}{nota_handles}\n"
(SAIDA / nome_arquivo).write_text(conteudo, encoding="utf-8")
n_ok += 1
print(f"{n_ok} arquivos .md gerados em {SAIDA.resolve()}")
print(f"{n_erro} posts com erro na coleta, pulados")
+74
View File
@@ -0,0 +1,74 @@
20.500.12156.1/7475
20.500.12156.1/8785
20.500.12156.1/7875
20.500.12156.1/8075
20.500.12156.1/7817
20.500.12156.1/7326
20.500.12156.1/7386
20.500.12156.1/8912
20.500.12156.1/8262
20.500.12156.1/8161
20.500.12156.1/8761
20.500.12156.1/8762
20.500.12156.1/7457
20.500.12156.1/8804
20.500.12156.1/8774
20.500.12156.1/7516
20.500.12156.1/7379
20.500.12156.1/7344
20.500.12156.1/7743
20.500.12156.1/8313
20.500.12156.1/8374
20.500.12156.1/6545
20.500.12156.1/8187
20.500.12156.1/8133
20.500.12156.1/8277
20.500.12156.1/7355
20.500.12156.1/7848
20.500.12156.1/8267
20.500.12156.1/6520
20.500.12156.1/8275
20.500.12156.1/7529
20.500.12156.1/6570
20.500.12156.1/8182
20.500.12156.1/7034
20.500.12156.1/7744
20.500.12156.1/6568
20.500.12156.1/8872
20.500.12156.1/8396
20.500.12156.1/7317
20.500.12156.1/7517
20.500.12156.1/8268
20.500.12156.1/8141
20.500.12156.1/6433
20.500.12156.1/7438
20.500.12156.1/6670
20.500.12156.1/7440
20.500.12156.1/7939
20.500.12156.1/8825
20.500.12156.1/7353
20.500.12156.1/7393
20.500.12156.1/7515
20.500.12156.1/7814
20.500.12156.1/7358
20.500.12156.1/7463
20.500.12156.1/7852
20.500.12156.1/8796
20.500.12156.1/8145
20.500.12156.1/8254
20.500.12156.1/7766
20.500.12156.1/6674
20.500.12156.1/7461
20.500.12156.1/8128
20.500.12156.1/8316
20.500.12156.1/8249
20.500.12156.1/6555
20.500.12156.1/6669
20.500.12156.1/7802
20.500.12156.1/8739
20.500.12156.1/7861
20.500.12156.1/9148
20.500.12156.1/6656
20.500.12156.1/8781
20.500.12156.1/8271
20.500.12156.1/8286
+73
View File
@@ -0,0 +1,73 @@
{"handle": "20.500.12156.1/10993", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10993/1/Alb%200279%20113%20CT.jpg"}
{"handle": "20.500.12156.1/10995", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10995/1/Alb%200279%20106%20CT.jpg"}
{"handle": "20.500.12156.1/6433", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6433/1/0072430cx006-05t.jpg"}
{"handle": "20.500.12156.1/6520", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6520/1/007_IMG_1008t.jpg"}
{"handle": "20.500.12156.1/6545", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6545/1/CC-05-01-015-3.jpg"}
{"handle": "20.500.12156.1/6555", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6555/1/BR_RJANRIO_Q0_ADM_CPA_VFE_FOT_0009_m0003de0003.jpg"}
{"handle": "20.500.12156.1/6568", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6568/1/BR_RJANRIO_Q0_ADM_CPA_VFE_FOT_0019_m0001de0001.jpg"}
{"handle": "20.500.12156.1/6570", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6570/1/BR_RJANRIO_Q0_ADM_CPA_VFE_FOT_0024_m0001de0001.jpg"}
{"handle": "20.500.12156.1/6669", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6669/1/BP-06-TP-01-012.jpg"}
{"handle": "20.500.12156.1/6670", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6670/1/BP-06-TP-01-015.jpg"}
{"handle": "20.500.12156.1/6674", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6674/1/BP-06-TP-01-022.jpg"}
{"handle": "20.500.12156.1/7034", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7034/1/007A6P3F08-004.jpg"}
{"handle": "20.500.12156.1/7317", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7317/4/Alb%200294%20069ct.jpg"}
{"handle": "20.500.12156.1/7326", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7326/4/Alb%200294%20037ct.jpg"}
{"handle": "20.500.12156.1/7344", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7344/4/Alb%200294%20093ct.jpg"}
{"handle": "20.500.12156.1/7353", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7353/4/Alb%200294%20097ct.jpg"}
{"handle": "20.500.12156.1/7355", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7355/4/Alb%200259%20073%20ct.jpg"}
{"handle": "20.500.12156.1/7358", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7358/1/BR_RJANRIO_OG_0_FOT_0001_021.jpg"}
{"handle": "20.500.12156.1/7379", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7379/1/BR_RJANRIO_OG_0_FOT_0001_057.jpg"}
{"handle": "20.500.12156.1/7386", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7386/1/BR_RJANRIO_OG_0_FOT_0001_067.jpg"}
{"handle": "20.500.12156.1/7393", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7393/1/BR_RJANRIO_OG_0_FOT_0001_089.jpg"}
{"handle": "20.500.12156.1/7438", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7438/1/BR_RJANRIO_OG_0_FOT_0001_085.jpg"}
{"handle": "20.500.12156.1/7440", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7440/1/BR_RJANRIO_OG_0_FOT_0001_087.jpg"}
{"handle": "20.500.12156.1/7457", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7457/4/Alb%200294%20127ct.jpg"}
{"handle": "20.500.12156.1/7461", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7461/4/Alb%200259%20081ct.jpg"}
{"handle": "20.500.12156.1/7463", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7463/4/Alb%200294%20106ct.jpg"}
{"handle": "20.500.12156.1/7475", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7475/4/Alb%200259%20096ct.jpg"}
{"handle": "20.500.12156.1/7515", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7515/4/Alb%200259%20145ct.jpg"}
{"handle": "20.500.12156.1/7516", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7516/4/Alb%200259%20139ct.jpg"}
{"handle": "20.500.12156.1/7517", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7517/3/Album%200035%20297%20CT.jpg"}
{"handle": "20.500.12156.1/7529", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7529/5/Alb%200259%20130ct.jpg"}
{"handle": "20.500.12156.1/7743", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7743/1/001ALA011011.jpg"}
{"handle": "20.500.12156.1/7744", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7744/1/001ALA011012.jpg"}
{"handle": "20.500.12156.1/7766", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7766/1/001ATK012010.jpg"}
{"handle": "20.500.12156.1/7802", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7802/1/Alb%200036%20037%20CT%20ok.jpg"}
{"handle": "20.500.12156.1/7814", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7814/1/Alb%200036%20081%20CT%20ok.jpg"}
{"handle": "20.500.12156.1/7817", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7817/1/Alb%200036%20087%20CT%20ok.jpg"}
{"handle": "20.500.12156.1/7848", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7848/1/Alb%200036%20013%20CT%20ok.jpg"}
{"handle": "20.500.12156.1/7852", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7852/1/Album%200035%20105%20CT.jpg"}
{"handle": "20.500.12156.1/7861", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7861/1/Album%200035%20088%20CT.jpg"}
{"handle": "20.500.12156.1/7875", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7875/1/Album%200035%20078%20CT.jpg"}
{"handle": "20.500.12156.1/8075", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8075/1/Alb%200038%20009%20CT%20ok.jpg"}
{"handle": "20.500.12156.1/8128", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8128/1/014AM006009.jpg"}
{"handle": "20.500.12156.1/8133", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8133/1/014AM010003.jpg"}
{"handle": "20.500.12156.1/8141", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8141/1/014AM010014.jpg"}
{"handle": "20.500.12156.1/8145", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8145/1/014AM012006.jpg"}
{"handle": "20.500.12156.1/8161", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8161/1/014AM012058.jpg"}
{"handle": "20.500.12156.1/8182", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8182/1/014AM019007.jpg"}
{"handle": "20.500.12156.1/8187", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8187/1/014AM019013.jpg"}
{"handle": "20.500.12156.1/8249", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8249/1/014AM002012.jpg"}
{"handle": "20.500.12156.1/8254", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8254/1/014AM002019.jpg"}
{"handle": "20.500.12156.1/8262", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8262/1/014AM003015.jpg"}
{"handle": "20.500.12156.1/8267", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8267/1/014AM004003.jpg"}
{"handle": "20.500.12156.1/8268", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8268/1/014AM005003.jpg"}
{"handle": "20.500.12156.1/8271", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8271/1/014AM005005.jpg"}
{"handle": "20.500.12156.1/8275", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8275/1/014AM005014.jpg"}
{"handle": "20.500.12156.1/8277", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8277/1/014AM005020.jpg"}
{"handle": "20.500.12156.1/8286", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8286/1/014AM006002.jpg"}
{"handle": "20.500.12156.1/8313", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8313/1/001AAN005123.jpg"}
{"handle": "20.500.12156.1/8316", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8316/1/001ACL019004.jpg"}
{"handle": "20.500.12156.1/8374", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8374/1/002080NUS0309.jpg"}
{"handle": "20.500.12156.1/8739", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8739/1/007ALA102.jpg"}
{"handle": "20.500.12156.1/8761", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8761/1/007ALA015.jpg"}
{"handle": "20.500.12156.1/8762", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8762/1/007ALA016.jpg"}
{"handle": "20.500.12156.1/8774", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8774/1/007ALA027.jpg"}
{"handle": "20.500.12156.1/8781", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8781/1/007ALA033.jpg"}
{"handle": "20.500.12156.1/8785", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8785/1/007ALA037.jpg"}
{"handle": "20.500.12156.1/8796", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8796/1/007ALA047.jpg"}
{"handle": "20.500.12156.1/8804", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8804/1/007ALA054.jpg"}
{"handle": "20.500.12156.1/8825", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8825/1/007ALA073.jpg"}
{"handle": "20.500.12156.1/8872", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8872/1/A05F03P02.jpg"}
{"handle": "20.500.12156.1/8912", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8912/1/pixinguinha04.jpg"}
{"handle": "20.500.12156.1/9148", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/9148/1/007ALA091.jpg"}
File diff suppressed because one or more lines are too long
+242
View File
@@ -0,0 +1,242 @@
{"handle": "20.500.12156.1/101", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/101/1/imagem.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/1004", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/1004/1/imagem.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/1005", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/1005/1/imagem.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/1006", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/1006/1/imagem.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/1007", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/1007/1/imagem.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/1008", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/1008/1/imagem.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/1009", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/1009/1/imagem.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/1010", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/1010/1/imagem.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/1011", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/1011/1/imagem.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/1012", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/1012/1/imagem.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/1013", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/1013/1/imagem.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/1014", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/1014/1/imagem.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/1015", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/1015/1/imagem.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/6433", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6433/1/0072430cx006-05t.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/6520", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6520/1/007_IMG_1008t.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/6545", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6545/1/CC-05-01-015-3.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/6555", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6555/1/BR_RJANRIO_Q0_ADM_CPA_VFE_FOT_0009_m0003de0003.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/6568", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6568/1/BR_RJANRIO_Q0_ADM_CPA_VFE_FOT_0019_m0001de0001.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/6570", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6570/1/BR_RJANRIO_Q0_ADM_CPA_VFE_FOT_0024_m0001de0001.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/6656", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6656/2/imagem.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/6669", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6669/1/BP-06-TP-01-012.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/6670", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6670/1/BP-06-TP-01-015.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/6674", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/6674/1/BP-06-TP-01-022.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/7034", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7034/1/007A6P3F08-004.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/7317", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7317/4/Alb%200294%20069ct.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/7326", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7326/4/Alb%200294%20037ct.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/7344", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7344/4/Alb%200294%20093ct.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/7353", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7353/4/Alb%200294%20097ct.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/7355", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7355/4/Alb%200259%20073%20ct.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/7358", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7358/1/BR_RJANRIO_OG_0_FOT_0001_021.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/7379", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7379/1/BR_RJANRIO_OG_0_FOT_0001_057.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/7386", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7386/1/BR_RJANRIO_OG_0_FOT_0001_067.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/7393", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7393/1/BR_RJANRIO_OG_0_FOT_0001_089.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/7438", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7438/1/BR_RJANRIO_OG_0_FOT_0001_085.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/7440", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7440/1/BR_RJANRIO_OG_0_FOT_0001_087.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/7457", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7457/4/Alb%200294%20127ct.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/7461", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7461/4/Alb%200259%20081ct.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/7463", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7463/4/Alb%200294%20106ct.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/7475", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7475/4/Alb%200259%20096ct.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/7515", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7515/4/Alb%200259%20145ct.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/7516", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7516/4/Alb%200259%20139ct.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/7517", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7517/3/Album%200035%20297%20CT.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/7523", "url": null, "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/7529", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7529/5/Alb%200259%20130ct.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/7743", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7743/1/001ALA011011.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/7744", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7744/1/001ALA011012.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/7766", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7766/1/001ATK012010.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/7802", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7802/1/Alb%200036%20037%20CT%20ok.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/7814", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7814/1/Alb%200036%20081%20CT%20ok.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/7817", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7817/1/Alb%200036%20087%20CT%20ok.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/7848", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7848/1/Alb%200036%20013%20CT%20ok.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/7852", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7852/1/Album%200035%20105%20CT.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/7861", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7861/1/Album%200035%20088%20CT.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/7875", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7875/1/Album%200035%20078%20CT.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/7939", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/7939/2/imagem.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/8075", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8075/1/Alb%200038%20009%20CT%20ok.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/8128", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8128/1/014AM006009.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/8133", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8133/1/014AM010003.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/8141", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8141/1/014AM010014.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/8145", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8145/1/014AM012006.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/8161", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8161/1/014AM012058.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/8182", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8182/1/014AM019007.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/8187", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8187/1/014AM019013.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/8249", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8249/1/014AM002012.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/8254", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8254/1/014AM002019.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/8262", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8262/1/014AM003015.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/8267", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8267/1/014AM004003.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/8268", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8268/1/014AM005003.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/8271", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8271/1/014AM005005.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/8275", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8275/1/014AM005014.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/8277", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8277/1/014AM005020.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/8286", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8286/1/014AM006002.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/8313", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8313/1/001AAN005123.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/8316", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8316/1/001ACL019004.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/8374", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8374/1/002080NUS0309.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/8396", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8396/2/imagem.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/8739", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8739/1/007ALA102.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/8761", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8761/1/007ALA015.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/8762", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8762/1/007ALA016.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/8774", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8774/1/007ALA027.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/8781", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8781/1/007ALA033.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/8785", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8785/1/007ALA037.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/8796", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8796/1/007ALA047.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/8804", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8804/1/007ALA054.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/8825", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8825/1/007ALA073.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/8872", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8872/1/A05F03P02.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/8912", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/8912/1/pixinguinha04.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/9148", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/9148/1/007ALA091.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10039", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10039/1/007CX190-01.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10040", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10040/1/007CX190-05.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10041", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10041/1/007CX190-11.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10042", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10042/1/007CX190-14.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10043", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10043/1/007CX190-16.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10044", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10044/1/007CX194-08.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10045", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10045/1/001CC007004.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10046", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10046/1/007CX194-10.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10047", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10047/1/007CX196-02.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10048", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10048/1/007CX196-07.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10049", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10049/1/007CX197-02.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10050", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10050/1/007CX197-05.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10051", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10051/1/007CX197-09.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10052", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10052/1/007CX197-10.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10053", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10053/1/007CX198-03.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10054", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10054/1/007CX199-03.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10055", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10055/1/007CX199-04.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10056", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10056/1/001CC007005.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10057", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10057/1/007CX214-12.JPG", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10058", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10058/1/007CX237-01.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10059", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10059/1/007SN-12.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10060", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10060/1/007_IMG_1807.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10061", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10061/1/007_IMG_1811.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10062", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10062/1/007_IMG_1812.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10063", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10063/1/007_IMG_1813.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10064", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10064/1/007_IMG_1855.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10065", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10065/1/007_IMG_1859.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10066", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10066/1/007_IMG_1993.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10067", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10067/1/001CC007007.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10068", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10068/1/007_IMG_2000.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10069", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10069/1/007_IMG_2006.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10070", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10070/1/007_IMG_2007.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10071", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10071/1/007_IMG_2010.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10072", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10072/1/007_IMG_2023.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10073", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10073/1/007_IMG_2493.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10074", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10074/1/007_IMG_2497.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10075", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10075/1/007_IMG_2500.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10076", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10076/1/007_IMG_2502.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10077", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10077/1/007_IMG_2503.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10078", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10078/1/001CC007014.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10079", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10079/1/007_IMG_2509.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10080", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10080/1/007_IMG_2511.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10081", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10081/1/007_IMG_2516.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10082", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10082/1/007_IMG_2519.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10083", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10083/1/007_IMG_2520.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10084", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10084/1/007_IMG_2522.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10085", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10085/1/007_IMG_2525.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10086", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10086/1/007_IMG_2526.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10087", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10087/1/007_IMG_2527.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10088", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10088/1/007_IMG_2531.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10089", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10089/1/001AN02001006.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10090", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10090/1/001CC007015.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10091", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10091/1/007_IMG_2836.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10092", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10092/1/007_IMG_2837.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10093", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10093/1/007_IMG_2838.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10094", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10094/1/007_IMG_2839.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10095", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10095/1/007_IMG_2897.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10096", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10096/1/007_IMG_2904.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10097", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10097/1/007_IMG_2912.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10098", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10098/1/007_IMG_2913.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10099", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10099/1/007_IMG_2914.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10100", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10100/1/007_IMG_2917.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10101", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10101/1/001CC007016.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10102", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10102/1/007_IMG_2934.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10103", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10103/1/007_IMG_2935.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10104", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10104/1/007_IMG_2944.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10105", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10105/1/007_IMG_3108.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10106", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10106/1/007_IMG_3110.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10107", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10107/1/007_IMG_3113.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10108", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10108/1/007_IMG_3114.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10109", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10109/1/007_IMG_3119.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10110", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10110/1/007_IMG_3165.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10111", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10111/1/007_IMG_3259.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10112", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10112/1/001CC007019.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10113", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10113/1/007_IMG_3285.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10114", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10114/1/007_IMG_3299.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10115", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10115/1/007_IMG_3309.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10116", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10116/1/007_IMG_3316.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10117", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10117/1/007_IMG_3330.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10118", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10118/1/007_IMG_3332.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10119", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10119/1/007_IMG_3347.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10120", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10120/1/007_IMG_3350.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10121", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10121/1/007_IMG_3352.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10122", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10122/1/007_IMG_3353.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10123", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10123/1/001CC007020.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10124", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10124/1/007_IMG_3437.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10125", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10125/1/007_IMG_3469.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10126", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10126/1/007_IMG_3527.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10127", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10127/1/007_IMG_3528.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10128", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10128/1/007_IMG_3533.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10129", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10129/1/007_IMG_3534.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10130", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10130/1/007_IMG_3535.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10131", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10131/1/007_IMG_3536.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10132", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10132/1/007_IMG_3539.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10133", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10133/1/007_IMG_3540.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10134", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10134/1/001CC007025.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10135", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10135/1/007_IMG_3547.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10136", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10136/1/007_IMG_3559.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10137", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10137/1/007_IMG_3585.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10138", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10138/1/007_IMG_3588.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10139", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10139/1/007_IMG_3590.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10140", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10140/1/007_IMG_3594.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10141", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10141/1/007_IMG_3596.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10142", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10142/1/007_IMG_3597.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10143", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10143/1/007_IMG_3598.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10144", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10144/1/007_IMG_3601.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10145", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10145/1/001CC008007.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10146", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10146/1/007_IMG_3603.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10147", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10147/1/007_IMG_3607.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10148", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10148/1/007_IMG_3631.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10149", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10149/1/007_IMG_3634.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10150", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10150/1/007_IMG_3636.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10151", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10151/1/007_IMG_3639.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10152", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10152/1/007_IMG_3642.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10153", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10153/1/007_IMG_3739.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10154", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10154/1/007_IMG_3740.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/10155", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/10155/1/007_IMG_3742.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/14358", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14358/1/Alb%200261%20139.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/14359", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14359/1/Alb%200261%20136.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/14360", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14360/1/Alb%200261%20141.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/14361", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14361/1/Alb%200261%20142.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/14362", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14362/1/Alb%200261%20143.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/14363", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14363/1/Alb%200261%20144.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/14364", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14364/1/Alb%200261%20146.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/14368", "url": "https://brasilianafotografica.bn.gov.br/brasiliana/bitstream/20.500.12156.1/14368/1/Alb%200261%20163.jpg", "nome": "imagem.jpg", "motivo": "sem preservacao"}
{"handle": "20.500.12156.1/14293", "url": null, "nome": "extra_seq1.jpg", "motivo": "extra apagado (tiff)"}
{"handle": "20.500.12156.1/14295", "url": null, "nome": "extra_seq1.jpg", "motivo": "extra apagado (tiff)"}
{"handle": "20.500.12156.1/14357", "url": null, "nome": "extra_seq1.jpg", "motivo": "extra apagado (tiff)"}
{"handle": "20.500.12156.1/14357", "url": null, "nome": "extra_seq2.jpg", "motivo": "extra apagado (tiff)"}
{"handle": "20.500.12156.1/14365", "url": null, "nome": "extra_seq2.jpg", "motivo": "extra apagado (tiff)"}
{"handle": "20.500.12156.1/14365", "url": null, "nome": "extra_seq3.jpg", "motivo": "extra apagado (tiff)"}
{"handle": "20.500.12156.1/14365", "url": null, "nome": "extra_seq4.jpg", "motivo": "extra apagado (tiff)"}
{"handle": "20.500.12156.1/14365", "url": null, "nome": "extra_seq5.jpg", "motivo": "extra apagado (tiff)"}
{"handle": "20.500.12156.1/14366", "url": null, "nome": "extra_seq2.jpg", "motivo": "extra apagado (tiff)"}
{"handle": "20.500.12156.1/14367", "url": null, "nome": "extra_seq2.jpg", "motivo": "extra apagado (tiff)"}
{"handle": "20.500.12156.1/14434", "url": null, "nome": "extra_seq1.jpg", "motivo": "extra apagado (tiff)"}
{"handle": "20.500.12156.1/14434", "url": null, "nome": "extra_seq3.jpg", "motivo": "extra apagado (tiff)"}
{"handle": "20.500.12156.1/14434", "url": null, "nome": "extra_seq4.jpg", "motivo": "extra apagado (tiff)"}
{"handle": "20.500.12156.1/14434", "url": null, "nome": "extra_seq5.jpg", "motivo": "extra apagado (tiff)"}
{"handle": "20.500.12156.1/14437", "url": null, "nome": "extra_seq1.jpg", "motivo": "extra apagado (tiff)"}
{"handle": "20.500.12156.1/14437", "url": null, "nome": "extra_seq3.jpg", "motivo": "extra apagado (tiff)"}
{"handle": "20.500.12156.1/14514", "url": null, "nome": "extra_seq1.jpg", "motivo": "extra apagado (tiff)"}
{"handle": "20.500.12156.1/14515", "url": null, "nome": "extra_seq2.jpg", "motivo": "extra apagado (tiff)"}
{"handle": "20.500.12156.1/14515", "url": null, "nome": "extra_seq3.jpg", "motivo": "extra apagado (tiff)"}
{"handle": "20.500.12156.1/14516", "url": null, "nome": "extra_seq2.jpg", "motivo": "extra apagado (tiff)"}
{"handle": "20.500.12156.1/14518", "url": null, "nome": "extra_seq1.jpg", "motivo": "extra apagado (tiff)"}
{"handle": "20.500.12156.1/14518", "url": null, "nome": "extra_seq2.jpg", "motivo": "extra apagado (tiff)"}
{"handle": "20.500.12156.1/14518", "url": null, "nome": "extra_seq4.jpg", "motivo": "extra apagado (tiff)"}
{"handle": "20.500.12156.1/14519", "url": null, "nome": "extra_seq1.jpg", "motivo": "extra apagado (tiff)"}
{"handle": "20.500.12156.1/14520", "url": null, "nome": "extra_seq1.jpg", "motivo": "extra apagado (tiff)"}
{"handle": "20.500.12156.1/14520", "url": null, "nome": "extra_seq3.jpg", "motivo": "extra apagado (tiff)"}
{"handle": "20.500.12156.1/14520", "url": null, "nome": "extra_seq4.jpg", "motivo": "extra apagado (tiff)"}
{"handle": "20.500.12156.1/14521", "url": null, "nome": "extra_seq2.jpg", "motivo": "extra apagado (tiff)"}
{"handle": "20.500.12156.1/2384", "url": null, "nome": "001AAN014011.jpg.zip", "motivo": "extra apagado (zip)"}
File diff suppressed because one or more lines are too long
+93
View File
@@ -0,0 +1,93 @@
/* ===================================================================
v2 -- a v1 usava .file-wrapper a[href*="sequence="], que nao extrai
nada (testado, retorna vazio). O que funciona: contar quantos
.file-wrapper existem (bate com o numero real de bitstreams) e
tentar as sequencias 1..N direto -- ja confirmado que o nome do
arquivo na URL nao importa nessa rota, so a sequencia.
Reparo: itens sem preservation_url que na verdade tem bitstream(s)
real(is) -- so nao geram citation_pdf_url (provavelmente por terem
mais de um bitstream no bundle ORIGINAL, o que confunde o gerador
de citacao do DSpace).
Grava all_preservation_urls / all_preservation_sizes (ordenado do
maior pro menor) e preservation_url = o maior, pra compatibilidade
com o pipeline existente. Rodar depois baixador-extras.js pra pegar
as demais imagens dos itens com mais de uma.
=================================================================== */
(async () => {
const BASE = 'https://brasilianafotografica.bn.gov.br/brasiliana';
const PREFIX = '20.500.12156.1';
const PAUSA = 2000;
const sleep = ms => new Promise(r => setTimeout(r, ms));
const jit = () => PAUSA + Math.floor(Math.random() * 600);
// preencha com os handles a reparar (ex.: os que tem preservation_url nulo)
const HANDLES = __HANDLES__;
let db;
const abrir = () => new Promise((res, rej) => {
const q = indexedDB.open('bras', 1);
q.onsuccess = () => res(q.result); q.onerror = () => rej(q.error);
});
const get = (st, k) => new Promise(res => {
const r = db.transaction(st).objectStore(st).get(k);
r.onsuccess = () => res(r.result); r.onerror = () => res(undefined);
});
const put = (st, k, v) => new Promise((res, rej) => {
const t = db.transaction(st, 'readwrite'); t.objectStore(st).put(v, k);
t.oncomplete = res; t.onerror = () => rej(t.error);
});
async function contaBitstreams(h) {
const r = await fetch(`${BASE}/handle/${PREFIX}/${h}`, {credentials: 'same-origin'});
if (!r.ok) throw new Error('HTTP ' + r.status);
const html = await r.text();
const d = new DOMParser().parseFromString(html, 'text/html');
return d.querySelectorAll('.file-wrapper').length;
}
async function tamanho(url) {
const r = await fetch(url, {credentials: 'same-origin'});
if (!r.ok) return -1;
const b = await r.blob();
return b.size;
}
const RESULT = {ok: 0, erro: 0, multiplos: 0, total: HANDLES.length, atual: 0};
window.REPARO_MULTI = RESULT;
db = await abrir();
console.log(`reparo-multibitstream v2: ${HANDLES.length} itens a checar`);
for (let i = 0; i < HANDLES.length; i++) {
const h = HANDLES[i];
RESULT.atual = i + 1;
try {
const n = await contaBitstreams(h);
if (!n) { RESULT.erro++; console.warn(` ${h}: 0 file-wrapper (sem bitstream mesmo)`); await sleep(jit()); continue; }
const comTamanho = [];
for (let seq = 1; seq <= n; seq++) {
const u = `${BASE}/bitstream/${PREFIX}/${h}/${seq}/imagem.jpg`;
const bytes = await tamanho(u);
if (bytes > 0) comTamanho.push({url: u, bytes});
await sleep(300);
}
if (!comTamanho.length) { RESULT.erro++; console.warn(` ${h}: ${n} wrapper(s) mas nenhuma sequencia respondeu`); await sleep(jit()); continue; }
comTamanho.sort((a, b) => b.bytes - a.bytes);
const item = await get('itens', h) || {};
item.preservation_url = comTamanho[0].url;
item.all_preservation_urls = comTamanho.map(c => c.url);
item.all_preservation_sizes = comTamanho.map(c => c.bytes);
await put('itens', h, item);
RESULT.ok++;
if (comTamanho.length > 1) RESULT.multiplos++;
console.log(` ${h}: ${comTamanho.length}/${n} bitstream(s), maior=${(comTamanho[0].bytes/1048576).toFixed(1)}MB`);
} catch (e) {
RESULT.erro++;
console.warn(` ${h}: ${e.message}`);
}
await sleep(jit());
}
console.log(`\nreparo-multibitstream v2 concluido. ok=${RESULT.ok} erro=${RESULT.erro} itens-com-multiplos-bitstreams=${RESULT.multiplos}`);
})();
+62
View File
@@ -0,0 +1,62 @@
// reprojeta-meta-mapa.mjs — reescreve SÓ o `meta` de cada item do
// web/atlas/atlas-map.json a partir do pipeline/catalogo.jsonl, sem re-renderizar
// os atlas .webp (posições, cores e recortes das peças ficam intactos).
//
// Use quando as regras de metadados do web/build-atlas.js mudarem e não valer a
// pena reconstruir 47 MB de imagem. As regras aqui têm de espelhar as de
// summarize()/pickAll() lá — em 2026-09-12 as duas foram escritas juntas.
//
// node pipeline/reprojeta-meta-mapa.mjs (da raiz do projeto)
import fs from 'node:fs';
import readline from 'node:readline';
const MAP = 'web/atlas/atlas-map.json';
const pickAll = (item, field, noUrls) => {
const out = [];
for (const row of item.full_table || []) {
if (row.field !== field) continue;
const v = String(row.value ?? '').trim();
if (!v || out.includes(v)) continue;
if (noUrls && /^https?:\/\//i.test(v)) continue;
out.push(v);
}
return out;
};
const some = (i, f, noUrls) => { const v = pickAll(i, f, noUrls); return v.length ? v : undefined; };
const one = (i, f) => pickAll(i, f)[0] || undefined;
const d = JSON.parse(fs.readFileSync(MAP, 'utf8'));
const wanted = new Set(Object.keys(d.map));
const extra = new Map();
const rl = readline.createInterface({ input: fs.createReadStream('pipeline/catalogo.jsonl'), crlfDelay: Infinity });
for await (const line of rl) {
if (!line.trim()) continue;
const o = JSON.parse(line);
if (!wanted.has(o.handle)) continue;
extra.set(o.handle, {
subject: some(o, 'dc.subject'),
description: some(o, 'dc.description'),
coverage: some(o, 'dc.coverage'),
source: one(o, 'dc.source'),
publisher: one(o, 'dc.publisher'),
relation: some(o, 'dc.relation', true),
identifier: some(o, 'dc.identifier', true),
});
}
let enriched = 0, missing = 0;
for (const [h, e] of Object.entries(d.map)) {
const add = extra.get(h);
if (!add) { missing++; continue; }
let touched = false;
for (const [k, v] of Object.entries(add)) if (v !== undefined) { e.meta[k] = v; touched = true; }
// deriváveis da chave: não repetir (ver build-atlas.js)
delete e.meta.handle;
delete e.meta.source_url;
if (touched) enriched++;
}
const out = JSON.stringify(d);
fs.writeFileSync(MAP, out);
console.log(`itens: ${wanted.size} | enriquecidos: ${enriched} | sem registro: ${missing}`);
console.log(`atlas-map.json: ${(out.length / 1048576).toFixed(2)} MB`);
+17
View File
@@ -0,0 +1,17 @@
#!/bin/bash
# Drena a fila de ~/Downloads pro S3 com o subidor-paralelo.py e depois arruma
# o que o subidor.py antigo deixou: duplicatas "(N)" identicas e manifests com
# linha de derivado. Log em pipeline/subidor-paralelo.log.
# O caffeinate e amarrado a este script: segura o Mac acordado so enquanto roda.
set -u
cd "$(dirname "$0")"
set -a; . ./.s3env; set +a
exec >> subidor-paralelo.log 2>&1
caffeinate -is -w $$ &
echo "===== $(date -u +%FT%TZ) inicio (pid $$)"
python3 -u subidor-paralelo.py --catalogo catalogo.jsonl --threads 6 --lote 400 || echo "!!! drenagem saiu com erro $?"
echo "===== $(date -u +%FT%TZ) limpeza de duplicatas"
python3 -u subidor-paralelo.py --catalogo catalogo.jsonl --limpar-duplicatas || echo "!!! limpeza saiu com erro $?"
echo "===== $(date -u +%FT%TZ) regravando todos os manifests"
python3 -u subidor-paralelo.py --catalogo catalogo.jsonl --so-manifestos --threads 6 || echo "!!! manifests saiu com erro $?"
echo "===== $(date -u +%FT%TZ) FIM"
+8
View File
@@ -0,0 +1,8 @@
20.500.12156.1_7802
20.500.12156.1_7814
20.500.12156.1_7817
20.500.12156.1_7848
20.500.12156.1_7852
20.500.12156.1_7861
20.500.12156.1_7875
20.500.12156.1_8075
+405
View File
@@ -0,0 +1,405 @@
#!/usr/bin/env python3
"""
subidor-paralelo.py -- substitui o subidor.py para drenar a fila de ~/Downloads.
Por que existe (medido 2026-09-12): o subidor.py sobe um arquivo por vez e, a
cada arquivo, espera 1,5 s de "estabilidade" e dispara tres `aws s3 cp`
(arquivo, metadata.json, manifest). Cada um paga a partida do aws-cli: ~5 s por
arquivo, seja thumbnail de 74 KB ou preservacao de 40 MB. Resultado: ~1,3 GB/h
num link de 69 Mbps (~31 GB/h). O gargalo era o processo, nao o link.
O que muda:
- upload em paralelo via boto3 (um cliente, N threads, sem partida de CLI),
conferindo o tamanho no bucket antes de apagar o local
- metadata.json + manifest-sha256.txt uma vez por item ao fim de cada lote
- copia "(N)" do Chrome identica (mesmo handle, tipo, sha256 e nome-base de
algo ja enviado) NAO sobe (achado 2.20)
- arquivo que nao e imagem vai pra pipeline/rejeitados/ e ganha linha na
tabela `rejeitados`, em vez de ser apagado sem rastro (achado 2.19)
- manifest do item deixa de fora `derivado-thumb256`: esses objetos moram em
derivados/thumb256/, nao dentro de itens/<handle>/
O que NAO muda: chaves no bucket, ledger `enviados`, validacao por magic bytes,
conteudo de metadata.json.
Uso:
set -a; . .s3env; set +a
python3 subidor-paralelo.py --catalogo catalogo.jsonl # drena a fila
python3 subidor-paralelo.py --limpar-duplicatas # apaga "(N)" identicas (bucket + ledger)
python3 subidor-paralelo.py --catalogo catalogo.jsonl --so-manifestos # regrava metadata+manifest de todo item
"""
import argparse, hashlib, json, mimetypes, os, re, shutil, sqlite3, sys, time
from collections import defaultdict
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
import boto3
from boto3.s3.transfer import TransferConfig
from botocore.config import Config
S3_ENDPOINT = os.environ.get("S3_ENDPOINT", "https://hel1.your-objectstorage.com")
BUCKET = os.environ.get("ARCHIVE_BUCKET", "brasiliana")
PREFIXO = "BRAS__"
ROOT = Path(__file__).resolve().parent
DB = ROOT / "subidor.sqlite"
REJ_DIR = ROOT / "rejeitados"
SUFIXO_N = re.compile(r"^(.*) \((\d+)\)(\.[^.]+)$")
TIPOS_DO_ITEM = ("preservacao", "thumbnail", "legado") # o que mora em itens/<handle>/
TRANSF = TransferConfig(multipart_threshold=64 * 2**20, multipart_chunksize=16 * 2**20,
max_concurrency=4)
def agora():
return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
def log(msg):
print(f"{time.strftime('%H:%M:%S')} {msg}", flush=True)
def base(nome):
m = SUFIXO_N.match(nome)
return m.group(1) + m.group(3) if m else nome
def cliente(threads):
return boto3.client(
"s3", endpoint_url=S3_ENDPOINT,
config=Config(max_pool_connections=threads * 5 + 8,
retries={"max_attempts": 8, "mode": "adaptive"},
request_checksum_calculation="when_required",
response_checksum_validation="when_required"))
def db():
con = sqlite3.connect(DB, timeout=60)
con.executescript("""
CREATE TABLE IF NOT EXISTS descartados (
handle TEXT, tipo TEXT, nome TEXT, bytes INTEGER, sha256 TEXT,
chave_no_bucket TEXT, quando TEXT, PRIMARY KEY (handle, tipo, nome, sha256));
CREATE TABLE IF NOT EXISTS rejeitados (
handle TEXT, tipo TEXT, nome TEXT, bytes INTEGER, sha256 TEXT,
cabecalho TEXT, quando TEXT, PRIMARY KEY (handle, tipo, nome, sha256));""")
return con
def carrega_catalogo(caminho):
d = {}
if caminho:
for linha in open(caminho, encoding="utf-8"):
if linha.strip():
r = json.loads(linha)
d[r["handle"]] = r
return d
def interpreta(p):
"""BRAS__20.500.12156.1_10377__[THUMB__]nome -> (handle, pasta, tipo, nome)"""
resto = p.name[len(PREFIXO):]
if "__" not in resto:
return None
pasta, nome = resto.split("__", 1)
m = re.fullmatch(r"(.+)_(\d+)", pasta)
if not m:
return None
tipo = "preservacao"
if nome.startswith("THUMB__"):
nome, tipo = nome[len("THUMB__"):], "thumbnail"
return f"{m.group(1)}/{m.group(2)}", pasta, tipo, nome
# assinaturas de imagem aceitas. Achado 2026-09-12: parte das "preservacao" com
# extensao .jpg e TIFF de verdade (II*\0 / MM\0*, ~8 MB, faixa 145xx); a regra
# antiga (so JPEG/PNG) do subidor.py apagava esses arquivos como se fossem o
# bitstream de licenca. O que se quer barrar e texto, nao formato de imagem.
ASSINATURAS = (
(b"\xff\xd8", "jpeg"), (b"\x89PNG\r\n\x1a\n", "png"),
(b"II*\x00", "tiff"), (b"MM\x00*", "tiff"), (b"GIF87a", "gif"), (b"GIF89a", "gif"),
(b"\x00\x00\x00\x0cjP ", "jp2"), (b"\xff\x4f\xff\x51", "j2k"), (b"BM", "bmp"),
)
def formato(cab):
for sig, nome in ASSINATURAS:
if cab.startswith(sig):
return nome
if cab[:4] == b"RIFF" and cab[8:12] == b"WEBP":
return "webp"
return None
def examina(p):
h, cab = hashlib.sha256(), None
with p.open("rb") as fh:
for b in iter(lambda: fh.read(1 << 20), b""):
if cab is None:
cab = b[:20]
h.update(b)
cab = cab or b""
return h.hexdigest(), p.stat().st_size, formato(cab) is not None, cab
MIME = {"jpeg": "image/jpeg", "png": "image/png", "tiff": "image/tiff", "gif": "image/gif",
"jp2": "image/jp2", "j2k": "image/jp2", "bmp": "image/bmp", "webp": "image/webp"}
def tipo_mime(p):
"""pelo conteudo, nao pela extensao: TIFF chega com nome .jpg"""
with open(p, "rb") as fh:
f = formato(fh.read(20))
return MIME.get(f) or mimetypes.guess_type(str(p))[0] or "binary/octet-stream"
def sobe_arquivo(s3, p, key, tam):
s3.upload_file(str(p), BUCKET, key, Config=TRANSF,
ExtraArgs={"ContentType": tipo_mime(p)})
got = s3.head_object(Bucket=BUCKET, Key=key)["ContentLength"]
if got != tam:
raise RuntimeError(f"tamanho no bucket {got} != local {tam}")
def confere_existente(s3, key, tam):
"""True se o objeto ja esta no bucket com o mesmo tamanho."""
try:
return s3.head_object(Bucket=BUCKET, Key=key)["ContentLength"] == tam
except Exception:
return False
def sobe_bytes(s3, dados, key, ctype):
s3.put_object(Bucket=BUCKET, Key=key, Body=dados, ContentType=ctype)
def refaz_metadados(con, s3, cat, handles, threads):
"""metadata.json + manifest-sha256.txt, mesmo formato do subidor.py."""
trabalhos = []
for h in sorted(handles):
rec = cat.get(h)
if not rec:
continue
mj = json.dumps(rec, ensure_ascii=False, indent=2).encode("utf-8")
linhas = [f"{sha} {tipo}/{nome}" for nome, sha, tipo in con.execute(
f"SELECT nome, sha256, tipo FROM enviados WHERE handle=? AND tipo IN "
f"({','.join('?' * len(TIPOS_DO_ITEM))}) ORDER BY tipo, nome", (h, *TIPOS_DO_ITEM))]
linhas.append(f"{hashlib.sha256(mj).hexdigest()} metadata.json")
mf = ("\n".join(linhas) + "\n").encode("utf-8")
pasta = h.replace("/", "_")
trabalhos.append((mj, f"itens/{pasta}/metadata.json", "application/json"))
trabalhos.append((mf, f"itens/{pasta}/manifest-sha256.txt", "text/plain"))
falhas = 0
with ThreadPoolExecutor(threads) as ex:
for f in as_completed([ex.submit(sobe_bytes, s3, *t) for t in trabalhos]):
try:
f.result()
except Exception as e:
falhas += 1
log(f"FALHA metadado: {e}")
return len(trabalhos) // 2, falhas
def drena(con, s3, cat, pasta, threads, lote, max_lotes=0):
REJ_DIR.mkdir(exist_ok=True)
env = dup = rej = 0
bytes_env, t0 = 0, time.time()
falhou = set()
n_lotes = 0
while True:
if max_lotes and n_lotes >= max_lotes:
log(f"parando depois de {n_lotes} lote(s) (--max-lotes)")
break
n_lotes += 1
corte = time.time() - 10
fila = []
for p in pasta.glob(PREFIXO + "*"):
if (not p.is_file() or p.suffix == ".crdownload" or p.name in falhou
or p.stat().st_mtime > corte or Path(str(p) + ".crdownload").exists()):
continue
info = interpreta(p)
if info:
fila.append((p, *info))
else:
log(f"ignorado (nome inesperado): {p.name}")
falhou.add(p.name)
if not fila:
break
# nome sem "(N)" primeiro: entre duas copias identicas, fica o nome original
fila.sort(key=lambda a: (SUFIXO_N.match(a[4]) is not None, a[0].name))
pedaco = fila[:lote]
with ThreadPoolExecutor(threads) as ex:
exames = list(zip(pedaco, ex.map(lambda a: examina(a[0]), pedaco)))
no_ledger = defaultdict(dict) # (handle, tipo, sha) -> {nome-base: nome ja enviado}
for h in {a[1] for a, _ in exames}:
for tipo, sha, nome in con.execute(
"SELECT tipo, sha256, nome FROM enviados WHERE handle=?", (h,)):
no_ledger[(h, tipo, sha)].setdefault(base(nome), nome)
neste_lote = set() # (handle, tipo, sha, nome-base) que ainda vai subir agora
subir, conferir = [], []
for (p, handle, pasta_h, tipo, nome), (sha, tam, eh_img, cab) in exames:
if not eh_img:
shutil.move(str(p), REJ_DIR / p.name)
con.execute("INSERT OR REPLACE INTO rejeitados VALUES (?,?,?,?,?,?,?)",
(handle, tipo, nome, tam, sha, cab.hex(), agora()))
rej += 1
log(f"REJEITADO (nao e imagem, {tam} bytes, inicio {cab[:12]!r}): {p.name} -> rejeitados/")
continue
k = (handle, tipo, sha)
existente = no_ledger[k].get(base(nome))
if existente:
# so apaga o local depois do HEAD confirmar o objeto no bucket
conferir.append((p, handle, pasta_h, tipo, nome, sha, tam, existente))
continue
if k + (base(nome),) in neste_lote:
continue # copia identica sobe neste lote; esta fica pro proximo
neste_lote.add(k + (base(nome),))
subir.append((p, handle, pasta_h, tipo, nome, sha, tam))
con.commit()
tocados = set()
with ThreadPoolExecutor(threads) as ex:
futs = {ex.submit(sobe_arquivo, s3, a[0], f"itens/{a[2]}/{a[3]}/{a[4]}", a[6]): a
for a in subir}
for f in as_completed(futs):
p, handle, pasta_h, tipo, nome, sha, tam = futs[f]
try:
f.result()
except Exception as e:
falhou.add(p.name)
log(f"FALHA no upload (mantido em disco): {p.name}: {e}")
continue
con.execute("INSERT OR REPLACE INTO enviados (handle,nome,bytes,sha256,quando,tipo) "
"VALUES (?,?,?,?,?,?)", (handle, nome, tam, sha, agora(), tipo))
con.commit()
p.unlink()
tocados.add(handle)
env += 1
bytes_env += tam
with ThreadPoolExecutor(threads) as ex:
futs = {ex.submit(confere_existente, s3, f"itens/{a[2]}/{a[3]}/{a[7]}", a[6]): a
for a in conferir}
for f in as_completed(futs):
p, handle, pasta_h, tipo, nome, sha, tam, existente = futs[f]
chave = f"itens/{pasta_h}/{tipo}/{existente}"
if f.result():
con.execute("INSERT OR REPLACE INTO descartados VALUES (?,?,?,?,?,?,?)",
(handle, tipo, nome, tam, sha, chave, agora()))
con.commit()
p.unlink()
dup += 1
else:
# ledger dizia que estava la, o bucket discorda: sobe de novo
log(f"ledger x bucket divergem, reenviando: {chave}")
try:
sobe_arquivo(s3, p, chave, tam)
except Exception as e:
falhou.add(p.name)
log(f"FALHA no reenvio (mantido em disco): {p.name}: {e}")
continue
con.execute("INSERT OR REPLACE INTO enviados (handle,nome,bytes,sha256,quando,tipo) "
"VALUES (?,?,?,?,?,?)", (handle, existente, tam, sha, agora(), tipo))
con.commit()
p.unlink()
tocados.add(handle)
env += 1
bytes_env += tam
n_meta, f_meta = refaz_metadados(con, s3, cat, tocados, threads)
horas = (time.time() - t0) / 3600
log(f"lote ok: {len(tocados)} itens | total {env} arq, {bytes_env/2**30:.1f} GB "
f"({bytes_env/2**30/horas:.1f} GB/h) | dup {dup} | rej {rej} | falha {len(falhou)} "
f"| meta {n_meta} (falha {f_meta}) | fila ~{len(fila) - len(pedaco)}")
log(f"FIM da fila: {env} enviados, {bytes_env/2**30:.1f} GB, {dup} duplicatas descartadas, "
f"{rej} rejeitados, {len(falhou)} falhas mantidas em disco")
for n in sorted(falhou):
log(f" pendente: {n}")
def limpa_duplicatas(con, s3):
grupos = defaultdict(list)
for h, tipo, nome, sha in con.execute(
"SELECT handle, tipo, nome, sha256 FROM enviados WHERE tipo IN ('preservacao','thumbnail')"):
grupos[(h, tipo, sha)].append(nome)
apagar = []
for (h, tipo, sha), nomes in grupos.items():
if len(nomes) < 2:
continue
for b, iguais in _por_base(nomes).items():
if len(iguais) < 2:
continue
fica = sorted(iguais, key=lambda n: (SUFIXO_N.match(n) is not None, n))[0]
apagar += [(h, tipo, n, fica) for n in iguais if n != fica]
log(f"duplicatas identicas no ledger: {len(apagar)}")
feitos, tocados = 0, set()
def um(a):
h, tipo, n, fica = a
pasta = h.replace("/", "_")
k_dup, k_fica = f"itens/{pasta}/{tipo}/{n}", f"itens/{pasta}/{tipo}/{fica}"
t1 = s3.head_object(Bucket=BUCKET, Key=k_dup)["ContentLength"]
t2 = s3.head_object(Bucket=BUCKET, Key=k_fica)["ContentLength"]
if t1 != t2:
raise RuntimeError(f"tamanhos divergem no bucket: {k_dup}")
s3.delete_object(Bucket=BUCKET, Key=k_dup)
return a
with ThreadPoolExecutor(16) as ex:
for f in as_completed([ex.submit(um, a) for a in apagar]):
try:
h, tipo, n, _ = f.result()
except Exception as e:
log(f" nao apagado: {e}")
continue
con.execute("DELETE FROM enviados WHERE handle=? AND tipo=? AND nome=?", (h, tipo, n))
tocados.add(h)
feitos += 1
con.commit()
log(f"duplicatas apagadas: {feitos}")
return tocados
def _por_base(nomes):
d = defaultdict(list)
for n in nomes:
d[base(n)].append(n)
return d
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--downloads", default=str(Path.home() / "Downloads"))
ap.add_argument("--catalogo")
# 6 e nao 16: com muitas threads o OpenSSL do macOS estoura na verificacao
# de certificado (SIGSEGV em X509_STORE_CTX_get1_issuer, 13/09) -- corrida
# dentro do libcrypto, nao do nosso codigo. 6 threads ja dao ~19 GB/h.
ap.add_argument("--threads", type=int, default=6)
ap.add_argument("--lote", type=int, default=400)
ap.add_argument("--max-lotes", type=int, default=0, help="0 = ate esvaziar a fila")
ap.add_argument("--limpar-duplicatas", action="store_true")
ap.add_argument("--so-manifestos", action="store_true")
args = ap.parse_args()
if not os.environ.get("AWS_ACCESS_KEY_ID"):
sys.exit("credenciais S3 ausentes. Rode: set -a; . .s3env; set +a")
con, s3 = db(), cliente(args.threads)
cat = carrega_catalogo(args.catalogo)
if args.limpar_duplicatas:
tocados = limpa_duplicatas(con, s3)
if cat and tocados:
log("regravando manifest dos itens tocados: %d (falha %d)" %
refaz_metadados(con, s3, cat, tocados, args.threads))
elif args.so_manifestos:
if not cat:
sys.exit("--so-manifestos precisa de --catalogo")
hs = {r[0] for r in con.execute("SELECT DISTINCT handle FROM enviados WHERE tipo IN (?,?,?)",
TIPOS_DO_ITEM)}
log(f"regravando metadata+manifest de {len(hs)} itens")
log("feito: %d itens (falha %d)" % refaz_metadados(con, s3, cat, hs, args.threads))
else:
log(f"drenando {args.downloads} -> s3://{BUCKET}/ ({args.threads} threads, lotes de {args.lote})")
drena(con, s3, cat, Path(args.downloads), args.threads, args.lote, args.max_lotes)
if __name__ == "__main__":
main()
+198
View File
@@ -0,0 +1,198 @@
#!/usr/bin/env python3
"""
Trilha B, segunda metade: leva da pasta Downloads para o S3 e apaga.
O baixador-navegador.js despeja os arquivos em Downloads com o handle no
proprio nome (BRAS__20.500.12156.1_10377__arquivo.jpg). Este script recolhe,
confere SHA-256, sobe pro bucket e APAGA na hora -- o disco nunca acumula.
Rode em paralelo com o navegador, em modo --vigiar:
set -a; . .s3env; set +a
python3 subidor.py --vigiar --catalogo catalogo.jsonl
Uma passada so, sem vigiar:
python3 subidor.py --catalogo catalogo.jsonl
"""
import argparse, hashlib, json, os, sqlite3, subprocess, sys, time
from pathlib import Path
S3_ENDPOINT = os.environ.get("S3_ENDPOINT", "https://hel1.your-objectstorage.com")
BUCKET = os.environ.get("ARCHIVE_BUCKET", "brasiliana")
PREFIXO = "BRAS__"
ROOT = Path(__file__).resolve().parent
DB = ROOT / "subidor.sqlite"
def db():
con = sqlite3.connect(DB)
con.executescript("""
CREATE TABLE IF NOT EXISTS enviados (
handle TEXT, nome TEXT, bytes INTEGER, sha256 TEXT, quando TEXT,
PRIMARY KEY (handle, nome));""")
cols = [r[1] for r in con.execute("PRAGMA table_info(enviados)")]
if "tipo" not in cols:
# tipo entra na migracao, mas a PK antiga (handle,nome) colidiria
# entre preservacao/thumbnail com o mesmo nome de arquivo -- refaz
# a tabela com PK composta, preservando as linhas ja existentes
# (todas eram 'preservacao', de antes do thumbnail existir).
con.executescript("""
ALTER TABLE enviados RENAME TO enviados_old;
CREATE TABLE enviados (
handle TEXT, nome TEXT, bytes INTEGER, sha256 TEXT, quando TEXT,
tipo TEXT DEFAULT 'preservacao',
PRIMARY KEY (handle, tipo, nome));
INSERT INTO enviados (handle,nome,bytes,sha256,quando,tipo)
SELECT handle,nome,bytes,sha256,quando,'preservacao' FROM enviados_old;
DROP TABLE enviados_old;
""")
con.commit()
return con
def s3_put(local, key):
return subprocess.run(
["aws", "--endpoint-url", S3_ENDPOINT, "s3", "cp", str(local),
f"s3://{BUCKET}/{key}", "--only-show-errors"],
capture_output=True, text=True).returncode == 0
def sha256(p):
h = hashlib.sha256()
with p.open("rb") as fh:
for b in iter(lambda: fh.read(1 << 20), b""):
h.update(b)
return h.hexdigest()
def estavel(p):
"""Chrome ainda escrevendo? Compara o tamanho com um intervalo curto."""
try:
a = p.stat().st_size
time.sleep(1.5)
return a == p.stat().st_size and a > 0
except FileNotFoundError:
return False
def carrega_catalogo(caminho):
if not caminho:
return {}
d = {}
for linha in open(caminho, encoding="utf-8"):
if linha.strip():
r = json.loads(linha)
d[r["handle"]] = r
return d
def escreve_metadados(con, handle, cat):
"""metadata.json + manifest-sha256.txt do item."""
rec = cat.get(handle)
if not rec:
return
pasta = handle.replace("/", "_")
# nome com PID: baixador.py roda na mesma pasta e pode escrever no
# mesmo .tmp ao mesmo tempo -- nome fixo faz um processo apagar o
# arquivo que o outro acabou de criar (achado 2026-09-10, crash real).
tmp = ROOT / ".tmp"; tmp.mkdir(exist_ok=True)
mj = tmp / f"metadata.{os.getpid()}.json"
mj.write_text(json.dumps(rec, ensure_ascii=False, indent=2), encoding="utf-8")
s3_put(mj, f"itens/{pasta}/metadata.json")
linhas = [f"{sha} {tipo}/{nome}" for nome, sha, tipo in
con.execute("SELECT nome,sha256,tipo FROM enviados WHERE handle=?", (handle,))]
linhas.append(f"{sha256(mj)} metadata.json")
mf = tmp / f"manifest-sha256.{os.getpid()}.txt"
mf.write_text("\n".join(linhas) + "\n", encoding="utf-8")
s3_put(mf, f"itens/{pasta}/manifest-sha256.txt")
mj.unlink(missing_ok=True); mf.unlink(missing_ok=True)
def uma_passada(con, pasta_downloads, cat, verboso=True):
n = 0
for p in sorted(pasta_downloads.glob(PREFIXO + "*")):
if p.suffix == ".crdownload" or not p.is_file():
continue
if not estavel(p):
continue
resto = p.name[len(PREFIXO):]
if "__" not in resto:
print(f" ignorado (nome inesperado): {p.name}")
continue
pasta_handle, nome = resto.split("__", 1)
handle = pasta_handle.replace("_", "/", 1) if "/" not in pasta_handle else pasta_handle
# 20.500.12156.1_10377 -> 20.500.12156.1/10377
if "_" in pasta_handle:
base, num = pasta_handle.rsplit("_", 1)
handle = f"{base}/{num}"
# THUMB__ no nome roteia pra subpasta thumbnail/ em vez de preservacao/
eh_thumb = nome.startswith("THUMB__")
if eh_thumb:
nome = nome[len("THUMB__"):]
subpasta = "thumbnail" if eh_thumb else "preservacao"
# valida que e imagem de verdade antes de subir -- achado 2026-09-10:
# o palpite de sequencia do achado 2.8 (seq+1 = thumbnail) as vezes
# cai no bitstream de LICENCA do DSpace em vez da miniatura (62% dos
# thumbs de alguns lotes!). Rejeita qualquer coisa que nao comece com
# os magic bytes de JPEG/PNG -- mais barato que confiar no nome.
with p.open("rb") as fh:
cabecalho = fh.read(8)
eh_jpeg = cabecalho[:2] == b"\xff\xd8"
eh_png = cabecalho[:8] == b"\x89PNG\r\n\x1a\n"
if not (eh_jpeg or eh_png):
print(f" REJEITADO (nao e imagem, primeiros bytes: {cabecalho[:20]!r}): {p.name}", flush=True)
p.unlink()
continue
dig, tam = sha256(p), p.stat().st_size
if s3_put(p, f"itens/{pasta_handle}/{subpasta}/{nome}"):
con.execute("INSERT OR REPLACE INTO enviados VALUES (?,?,?,?,?,?)",
(handle, nome, tam, dig,
time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), subpasta))
con.commit()
escreve_metadados(con, handle, cat)
p.unlink() # apaga SEMPRE que subiu
n += 1
if verboso:
print(f" {handle:<24} {tam/1048576:6.1f} MB -> S3, local apagado", flush=True)
else:
print(f" FALHA no upload: {p.name} (mantido em disco)", flush=True)
return n
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--downloads", default=str(Path.home() / "Downloads"))
ap.add_argument("--catalogo")
ap.add_argument("--vigiar", action="store_true")
ap.add_argument("--intervalo", type=int, default=20)
args = ap.parse_args()
if not os.environ.get("AWS_ACCESS_KEY_ID"):
sys.exit("credenciais S3 ausentes. Rode: set -a; . .s3env; set +a")
con = db()
pasta = Path(args.downloads)
cat = carrega_catalogo(args.catalogo)
print(f"vigiando {pasta} -> s3://{BUCKET}/ ({len(cat)} itens no catalogo)\n"
if args.vigiar else f"passada unica em {pasta}\n")
total = 0
try:
while True:
total += uma_passada(con, pasta, cat)
if not args.vigiar:
break
time.sleep(args.intervalo)
except KeyboardInterrupt:
pass
enviados, bytes_ = con.execute(
"SELECT COUNT(*), COALESCE(SUM(bytes),0) FROM enviados").fetchone()
print(f"\nnesta sessao: {total} | acumulado: {enviados} arquivos, {bytes_/1024**3:.2f} GB")
if __name__ == "__main__":
main()
+61
View File
@@ -0,0 +1,61 @@
#!/usr/bin/env node
/*
* ler-localstorage.js — lê o localStorage do Chrome direto do disco, sem
* depender da extensão Claude-in-Chrome estar conectada.
*
* Achado 2026-09-11: o progresso de BAIXA/BAIXA_THUMB (arrays de handles em
* localStorage.bras_baixados / bras_thumbs_v2) sobrevive em
* "~/Library/Application Support/Google/Chrome/Default/Local Storage/leveldb"
* mesmo com a extensão desconectada — é só um arquivo leveldb no disco.
* Copiamos antes de ler pra não brigar com o LOCK do Chrome (que está com o
* diretório aberto o tempo todo).
*
* Uso: node ler-localstorage.js [chave1] [chave2] ...
* sem argumentos, lê as chaves conhecidas do projeto.
*/
const { ClassicLevel } = require('classic-level');
const { execSync } = require('child_process');
const os = require('os');
const path = require('path');
const ORIGIN = 'https://brasilianafotografica.bn.gov.br';
const SRC = path.join(os.homedir(),
'Library/Application Support/Google/Chrome/Default/Local Storage/leveldb');
const COPY = '/tmp/ls-copy-' + Date.now();
const KEYS_PADRAO = ['bras_baixados', 'bras_thumbs', 'bras_thumbs_v2'];
function mk(name) {
return Buffer.concat([
Buffer.from('_' + ORIGIN, 'latin1'),
Buffer.from([0x00, 0x01]),
Buffer.from(name, 'latin1'),
]);
}
async function main() {
const keys = process.argv.slice(2);
const alvo = keys.length ? keys : KEYS_PADRAO;
execSync(`cp -r "${SRC}" "${COPY}"`);
const db = new ClassicLevel(COPY, { valueEncoding: 'buffer', keyEncoding: 'buffer' });
await db.open({ errorIfExists: false, createIfMissing: false });
const resultado = {};
for (const label of alvo) {
try {
const v = await db.get(mk(label));
const flag = v[0];
const str = flag === 0 ? v.slice(1).toString('utf16le') : v.slice(1).toString('utf8');
const arr = JSON.parse(str);
resultado[label] = Array.isArray(arr) ? arr.length : str.length;
} catch (e) {
resultado[label] = null; // chave não existe ou não é JSON — ok, só ausente
}
}
await db.close();
execSync(`rm -rf "${COPY}"`);
console.log(JSON.stringify(resultado, null, 2));
}
main().catch(e => { console.error('ERRO', e.message); process.exit(1); });
+189
View File
@@ -0,0 +1,189 @@
{
"name": "tools",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"classic-level": "^3.0.0"
}
},
"node_modules/abstract-level": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/abstract-level/-/abstract-level-3.1.1.tgz",
"integrity": "sha512-CW2gKbJFTuX1feMvOrvsVMmijAOgI9kg2Ie9Dq3gOcMt/dVVoVmqNlLcEUCT13NxHFMEajcUcVBIplbyDroDiw==",
"license": "MIT",
"dependencies": {
"buffer": "^6.0.3",
"is-buffer": "^2.0.5",
"level-supports": "^6.2.0",
"level-transcoder": "^1.0.1",
"maybe-combine-errors": "^1.0.0",
"module-error": "^1.0.1"
},
"engines": {
"node": ">=18"
}
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.2.1"
}
},
"node_modules/classic-level": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/classic-level/-/classic-level-3.0.0.tgz",
"integrity": "sha512-yGy8j8LjPbN0Bh3+ygmyYvrmskVita92pD/zCoalfcC9XxZj6iDtZTAnz+ot7GG8p9KLTG+MZ84tSA4AhkgVZQ==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"abstract-level": "^3.1.0",
"module-error": "^1.0.1",
"napi-macros": "^2.2.2",
"node-gyp-build": "^4.3.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "BSD-3-Clause"
},
"node_modules/is-buffer": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz",
"integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/level-supports": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/level-supports/-/level-supports-6.2.0.tgz",
"integrity": "sha512-QNxVXP0IRnBmMsJIh+sb2kwNCYcKciQZJEt+L1hPCHrKNELllXhvrlClVHXBYZVT+a7aTSM6StgNXdAldoab3w==",
"license": "MIT",
"engines": {
"node": ">=16"
}
},
"node_modules/level-transcoder": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/level-transcoder/-/level-transcoder-1.0.1.tgz",
"integrity": "sha512-t7bFwFtsQeD8cl8NIoQ2iwxA0CL/9IFw7/9gAjOonH0PWTTiRfY7Hq+Ejbsxh86tXobDQ6IOiddjNYIfOBs06w==",
"license": "MIT",
"dependencies": {
"buffer": "^6.0.3",
"module-error": "^1.0.1"
},
"engines": {
"node": ">=12"
}
},
"node_modules/maybe-combine-errors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/maybe-combine-errors/-/maybe-combine-errors-1.0.0.tgz",
"integrity": "sha512-eefp6IduNPT6fVdwPp+1NgD0PML1NU5P6j1Mj5nz1nidX8/sWY7119WL8vTAHgqfsY74TzW0w1XPgdYEKkGZ5A==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/module-error": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/module-error/-/module-error-1.0.2.tgz",
"integrity": "sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/napi-macros": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.2.2.tgz",
"integrity": "sha512-hmEVtAGYzVQpCKdbQea4skABsdXW4RUh5t5mJ2zzqowJS2OyXZTU1KhDVFhx+NlWZ4ap9mqR9TcDO3LTTttd+g==",
"license": "MIT"
},
"node_modules/node-gyp-build": {
"version": "4.8.4",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
"integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
"license": "MIT",
"bin": {
"node-gyp-build": "bin.js",
"node-gyp-build-optional": "optional.js",
"node-gyp-build-test": "build-test.js"
}
}
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"dependencies": {
"classic-level": "^3.0.0"
},
"allowScripts": {
"classic-level@3.0.0": true
}
}
+17
View File
@@ -0,0 +1,17 @@
#!/bin/bash
# A cada 30 min: sobe pro S3 o que o navegador tiver deixado em ~/Downloads e
# apaga o local do que subiu (o subidor-paralelo.py so apaga depois do HEAD
# confirmar o objeto no bucket). Passada vazia nao faz nada.
set -u
cd "$(dirname "$0")"
set -a; . ./.s3env; set +a
exec >> subidor-paralelo.log 2>&1
echo "===== $(date -u +%FT%TZ) vigia-30min iniciado (pid $$)"
while true; do
n=$(ls ~/Downloads 2>/dev/null | grep -c '^BRAS__' || true)
if [ "${n:-0}" -gt 0 ]; then
echo "----- $(date -u +%FT%TZ) vigia: $n arquivo(s) na fila"
python3 -u subidor-paralelo.py --catalogo catalogo.jsonl --threads 6 --lote 400 || echo "!!! vigia: erro $?"
fi
sleep 1800
done