Desde el primer poblado hasta hoy: la ciudad nace y crece en 3D, con año, población, bandera de cada época y tarjetas con fuente. Sin IA de video y sin costo de render.

historia-timelapse es un conjunto de scripts que convierte la historia de una ciudad en un video en 3D, en timelapse. Usa el relieve real del terreno y los edificios de OpenStreetMap: cada edificio "nace" en el año en que se ocupó su barrio, y la ciudad crece en pantalla junto con el contador de población del IBGE. Los acontecimientos aparecen en tarjetas con número, explicación y fuente. Para usarlo necesitas una computadora con tarjeta de video, Python y Node, y una investigación histórica de la ciudad.
Hechos con este motor, en el canal Aventuras INEMA (en portugués). Cada ciudad tiene versión sin narración y narrada.
6 min: sambaquíes, Dias Velho, fortalezas, azorianos, la invasión española de 1777, los puentes y los rellenos. Narrada: aquí. Abrir en YouTube
7 min: tupinambás, Francia Antártica, la Corte en 1808, Imperio y República. Narrada: aquí. Abrir en YouTube
6 min: de la aldea jesuita a la hacienda de Santa Cruz y al puerto. Narrada: aquí. Abrir en YouTube
5 min, con la cámara en las calles y los edificios históricos. Narrada: aquí. Abrir en YouTube
1min35, narrado: el mismo Porto Alegre con relieve y edificios iluminados por path tracing en la GPU — sombras reales y edificios que suben en el año del barrio. Abrir en YouTube
1min35, narrado (el piloto): fundación, sitios farrapos, la inundación de 1941, el Muro de Mauá y la de 2024. Abrir en YouTube
Datos reales en lugar de imágenes inventadas.
Relieve Copernicus de 30 m y edificios de OpenStreetMap (con la base abierta de Microsoft donde el OSM tiene poco). Cada edificio recibe un año según su barrio y la curva de población.
Cada evento tiene título, número grande, explicación y fuente. Año, población (censos del IBGE) y bandera del régimen cambian en la esquina de la pantalla.
Globo con zoom hasta la ciudad, línea de tiempo, flotas navegando, sitios, fortalezas, puentes dibujados, rellenos que crecen en color e inundaciones que suben.
Todo corre en tu máquina: deck.gl en Chrome con la GPU, cerca de 0,1 s por cuadro en 1080p.




Linux con tarjeta de video, Python 3, Node 20+ y ffmpeg. Los datos son archivos públicos descargados por un script; no hay API ni costo.
Python, Node, ffmpeg y git.
sudo apt install python3-venv ffmpeg git # Node 20+: con nvm (nvm install 20) o NodeSource — el nodejs de apt es antiguo
El render usa Chrome con la GPU (Vulkan). Sin Vulkan, ejecuta con GL="--use-angle=gl" antes del comando (más lento).
# ~0,1 s por cuadro en 1080p en una GPU NVIDIAEl recorte regional del OSM pesa cientos de MB (el Sur de Brasil, ~430 MB). Cada bloque de relieve pesa 20–50 MB.
# todo queda en la carpeta dl/ (fuera de git)Cada ciudad vive en cidades/<id>/. Los scripts leen la ciudad mediante la variable CIDADE. Los ejemplos usan Florianópolis (floripa): cámbialo por el id de tu ciudad en todos los comandos y mantén export CIDADE=<id> en la terminal (sin él los scripts usan poa).
Crea el entorno Python (venv = una carpeta con las bibliotecas solo de este proyecto) e instala Playwright, que abre Chrome para grabar los cuadros.
git clone https://github.com/inematds/historia-timelapse && cd historia-timelapse python3 -m venv .venv .venv/bin/pip install osmium tifffile imagecodecs \ shapely numpy pyproj scipy pillow cd motor && npm i && npx playwright install --with-deps chromium && cd ..
bbox es el rectángulo del mapa en grados: oeste, sur, este, norte (puedes dibujarlo en bboxfinder.com). municipio_osm es el nombre exacto del municipio en OpenStreetMap. centro_antigo es el punto donde empezó la ciudad.
# cidades/floripa/config.json { "nome": "Florianópolis", "bbox": [-48.80, -27.92, -48.33, -27.33], "pbf": "dl/sul.osm.pbf", "pbf_url": "https://download.geofabrik.de/south-america/brazil/sul-latest.osm.pbf", "municipio_osm": "Florianópolis", "centro_antigo": [-48.5490, -27.5965], "pais_microsoft": "Brazil" } # opcionales: "efeitos" (puentes, fuertes, rellenos), "tex" (tamaño de la textura), # "agua_baixa_m", "pop_modelo_extra" — mira cidades/floripa/config.json
El script descarga el recorte del OSM (formato .pbf), los bloques de relieve que cubren tu bbox y, con --microsoft, los edificios de Microsoft para donde el OSM tiene pocos edificios.
export CIDADE=floripa .venv/bin/python -I scripts/baixar_dados.py --microsoft
En cidades/<id>/dados/pesquisa/ van tres archivos. Copia los de Florianópolis como modelo y cambia el contenido.
# populacao.json — censos y estimaciones { "serie": [ { "ano": 1872, "pop": 25709, "fonte_url": "https://..." } ] } # eventos.json — un evento por ítem, con coordenada y fuente { "eventos": [ { "id": "ponte-hercilio", "ano": 1926, "titulo_curto_PT": "A PONTE HERCÍLIO LUZ", "legenda": "...", "detalhe": "...", "numero_impacto": "821 METROS", "local": { "lat": -27.5935, "lon": -48.5649 }, "fontes": ["https://..."] } ] } # bairros_epocas.json — año en que cada barrio empezó a ser ocupado { "bairros": [ { "bairro": "Trindade", "ano_aprox_ocupacao": 1960 } ] }
Recorta edificios, agua y barrios, arma el relieve con el mar, da a cada edificio un año de nacimiento y prepara puentes, fuertes y rellenos.
.venv/bin/python -I scripts/extrai_osm.py # usa el "pbf" del config .venv/bin/python -I scripts/extrai_msbuild.py # solo si descargaste los edificios de Microsoft .venv/bin/python -I scripts/prep_terreno.py .venv/bin/python -I scripts/prep_predios.py .venv/bin/python -I scripts/modelo_anos.py .venv/bin/python -I scripts/extrai_feicoes.py .venv/bin/python -I scripts/prep_efeitos.py
roteiro.json es un objeto con la lista "blocos" (y, si los usas, "linhas", "fortes", "areas" y "regimes"). Para una ciudad nueva escribes este archivo a mano, o copias y adaptas cidades/floripa/roteiro_floripa.py, que lo arma a partir de eventos.json. Cada bloque tiene los años (inicio, año del evento, fin), la cámara de salida y de llegada, la duración y la tarjeta. En Florianópolis lo arma roteiro_floripa.py a partir de la investigación; úsalo como modelo.
# un bloque del roteiro.json { "id": "ponte-hercilio", "anos": [1895, 1926, 1927], "pulo": 2.0, "dur": 9.5, "cam": [ {"lon": -48.552, "lat": -27.600, "zoom": 14, "pitch": 58, "bearing": -40}, {"lon": -48.570, "lat": -27.602, "zoom": 13.7, "pitch": 60, "bearing": -70} ], "cartao": { "numero": "821 METROS", "titulo": "A ponte Hercílio Luz", "legenda": "...", "detalhe": "...", "fonte": "IBGE" }, "destaca": ["linha:hercilio"] } # anos: [inicio, año del evento, fin]; "pulo": segundos en que el tiempo corre hasta el año del evento # cam: lon/lat del centro, zoom (12 = barrio, 14 = manzana), pitch (inclinación 0–60), bearing (rumbo en grados) # "destaca": "linha:<id>" | "forte:<id>" | "area:<id>" — ids definidos en "efeitos" del config y listados al inicio del guion # ajusta la cámara mirando los cuadros del paso 6 python3 cidades/floripa/roteiro_floripa.py .venv/bin/python -I scripts/gerar_cena.py # genera cidades/floripa/cena/cena.js
Graba solo los segundos indicados, para revisar encuadre, tarjetas y efectos antes del video completo.
cd motor && node render.mjs ../cidades/floripa/cena --quadros 20,60,120 && cd .. # las imágenes q_*.jpg quedan en cidades/floripa/cena/
El render se hace en tramos de 60 s (el navegador se reinicia entre ellos, para que no falte memoria). Pon tus músicas instrumentales (.mp3) en cidades/floripa/musica/; suenan en orden alfabético, en bucle.
bash motor/render_trechos.sh cidades/floripa/cena cidades/floripa/final/video.mp4 .venv/bin/python -I scripts/montar_musica.py # cidades/floripa/final/audio.wav ffmpeg -i cidades/floripa/final/video.mp4 -i cidades/floripa/final/audio.wav \ -map 0:v -map 1:a -c:v copy -c:a aac -shortest cidades/floripa/final/floripa.mp4
La narración es una capa de audio sobre el mismo video: un texto por bloque en cidades/<id>/narracao.json, encajado al inicio de cada evento. La grabación usa un motor de voz local del autor (cardshorts, fuera de este repositorio); con otra voz, graba un archivo por bloque y completa narracao/tempos.json.
# narracao/tempos.json, si grabas con otra voz: {"<bloque>": {"dur": 6.2, "wav": "ruta.wav", "texto": "...", "ouvido": "..."}} python3 scripts/narrar.py # solo con cardshorts; con otra voz, sáltalo y llena tempos.json python3 scripts/montar_narrada.py # final/audio-narrado.wav ffmpeg -i cidades/floripa/final/video.mp4 -i cidades/floripa/final/audio-narrado.wav -map 0:v -map 1:a -c:v copy -c:a aac -shortest cidades/floripa/final/floripa-narrado.mp4
El mismo video con el relieve y los edificios iluminados por path tracing en Mitsuba 3: sombras entre edificios, aspecto de maqueta y edificios que suben en su año. El motor sigue dibujando encima el HUD, las tarjetas y los efectos. Unos 45 min para 95 s de video (el render normal, ~5 min). Detalles (en portugués): docs/RENDER-LUZ-REAL.md.
Los dos formatos y Blender (mismo guion, mismas tarjetas y efectos; solo cambian el relieve y los edificios):
.venv/bin/pip install mitsuba==3.9.1 drjit==1.5.0 mapbox-earcut bash motor/render_luz.sh cidades/floripa/cena cidades/floripa/final/floripa-mitsuba-video.mp4 64
Lo que ya existe y lo que viene.