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:
@@ -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.');
|
||||
})();
|
||||
})();
|
||||
Reference in New Issue
Block a user