Communitygithub.com

kinopico-zhang/trip-video-skill

Fluxo completo para produzir vídeos longos de viagem geográfica (múltiplos trechos de transporte + rotas aéreas + renderização em nuvem Earth Studio + composição final).

O que é trip-video-skill?

Este skill (um repositório git chamado `kinopico-zhang/trip-video-skill`) implementa um pipeline completo para criar vídeos de viagem no Earth Studio, cobrindo múltiplos modos de transporte: rotas de carro (via OSRM), rotas aéreas reais (via Tavily), travessias de balsa, e caminhadas. O fluxo inclui pesquisa de rota, geração de arquivos .esp (Earth Studio Project), automação de renderização na nuvem via Chrome DevTools Protocol (CDP), composição de frames com Python/Pillow (sem numpy), controle de qualidade (QC, MAD de flicker), codificação final com ffmpeg e entrega para NAS. O skill é especialmente útil para criadores de conteúdo de viagem que desejam produzir vídeos com aparência profissional, com transições suaves entre trechos, sobreposição de waypoints, ícones de veículos proporcionais à altitude, e animações de caminhada com linha tracejada. Suporta principalmente Earth Studio (baseado em nuvem) e ferramentas de linha de comando; o exemplo já executado é a rota Qinghu North Station → Shenzhen Bay Port → HKG → CEB → Siquijor (v17). Os usuários devem ler o case-study (v17) e as regras de apresentação antes de começar.

Funciona com✓Claude Code~Codex CLI~Cursor
npx skills add kinopico-zhang/trip-video-skill

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Trip Video (Earth Studio 行程长视频) 制作流水线

Este skill é um repositório git (kinopico-zhang/trip-video-skill). Abaixo, $T = raiz do repositório (neste computador: ~/.claude/skills/trip-video → link simbólico ~/workspace/trip-video-skill). Convenção: cada rota em um diretório de trabalho independente (json / esp / frames ficam lá), scripts são chamados a partir de $T. Detalhes aprofundados em $T/references/: case-study-v17.md (análise completa de v7→v17, leia antes de começar), presentation-rules.md (regras de apresentação, confirme com o usuário para não regredir). Exemplo completo: $T/examples/sz-hkg-ceb-siquijor/.

0. Visão geral do pipeline

Pesquisa de rota (OSRM para carro / Tavily para rotas aéreas / travessias de balsa)          ← §1–§2
  ↓  Escrever script de geração por rota (copiar de examples/) → .esp por trecho + flight_data.json   ← §3
Renderização em nuvem Earth Studio via CDP (Chrome headless Windows)   ← §4
  ↓  siq_dl.mjs baixa mp4 → ffmpeg decodifica em frames base
src_frames/ (indexado a partir de 1 f%05d.jpg)
  ↓  compose20.py composição (linha de rota/veículo/linha tracejada de caminhada/rótulo/ícone)
comp_frames/ (indexado a partir de 0) → QC + verificação de flicker MAD            ← §5
  ↓  ffmpeg codifica (crf 18)
scp nas:/share/Public/earth/ + md5 em ambos os lados                 ← §6

Arquivo de dados flight_data.json (schema veja flight_data30.json nos exemplos): route vértices (lat,lon,alt,rt) + flag walk, samples amostras de câmera a cada 0.1s, frames tipo/posição/vetor tangente do veículo por frame, walks janelas de caminhada, emoji ângulo de orientação do sprite de cada veículo, pontos de ancoragem nomeados (aeroporto/porto/porto de entrada/ponto de partida…), fps.

Ambiente: node≥22 (scripts cdp zero dependências); Pillow via PYTHONPATH (neste computador aponta para ~/workspace/google-earth/pylibs, a máquina não tem PIL do sistema); ffmpeg em /home/admin/.local/bin/ffmpeg; sem numpy (tudo com PIL); NAS usa alias ssh nas, diretório de entrega /share/Public/earth/.


1. Consulta de navegação terrestre (OSRM)

Geometria de trechos de carro usa demo público do OSRM (Google Maps Directions RPC não é acessível em modo headless; OSRM segue a mesma malha viária):

# Rota de carro entre dois pontos (overview=full para geometria completa); pontos múltiplos separados por ponto e vírgula forçam passagem. A máquina não tem jq, use python para obter coordenadas
curl -s 'https://router.project-osrm.org/route/v1/driving/114.0354,22.6748;113.9350,22.5101?overview=full&geometries=geojson' \
  | python3 -c 'import json,sys; d=json.load(sys.stdin); json.dump(d["routes"][0]["geometry"]["coordinates"], open("route_SZ_raw.json","w"))'
# Resultado [[lon,lat],...] (nota: longitude vem primeiro!), salvar como route_<trecho>_raw.json
  • Fixação de extremidades: OSRM prende os pontos à superfície da estrada, não no ponto de ancoragem exato → _load_forced (implementado em trip_v10.py nos exemplos) fixa o início e fim no ponto de ancoragem exato: se distância >25m do ponto, insere; caso contrário, substitui.
  • Verificação de trechos sobre água: Pontes sobre o mar são viadutos legítimos sobre água; usa polígonos de terra do Natural Earth ne_50m_land para verificar se o segmento está sobre água, distinguindo "viaduto" de "erro do OSRM contornando água".
  • Trechos a pé (passagem na fronteira/saguão → pista/posição de estacionamento → saída): 1–2s, medir distância no mapa, não consultar malha viária; deslocamento usa curva hermite na corda ECEF (_walk_ckpt, velocidade zero nos dois extremos, τ=0.3s).
  • Balsa / rota marítima: OSRM não tem rotas de barco → Tavily consulta empresa de balsa/rota/porto, desenha rota manualmente no mar.

2. Consulta de rota aérea (Tavily → WPTS)

Rota aérea deve ser modelada com dados reais, não pode usar arco de círculo máximo. Use tvly (seção 7) para esclarecer cinco coisas:

  1. Pista de decolagem e rumo inicial (exemplo: HKG 07L/07R, track 073° para leste)
  2. Sequência de waypoints de saída (073°→BREAM vire à direita→ATTOL→SIKOU para evitar pico de 934m da Ilha Lantau)
  3. Altitude de cruzeiro e distância total (FL350=10668m, ~1790km)
  4. Direção da rota (ao longo do lado oeste de Luzon→Estreito de Tablas→Oeste de Negros→Estreito de Tanon)
  5. Pista de chegada e direção de aproximação (CEB pouso 04, vento predominante de nordeste)

Palavras-chave de busca: "<DEP> <ARR> flight route waypoints", "<ICAO> departure chart SID"; cruzar com página de rota do flightradar24, diagrams de aeroporto. Saída = lista WPTS no script de geração (exemplo flight10.py: (lat, lon) + comentários para cabeceira/ponto de rotação/waypoints), linha de rota = spline Catmull-Rom pelos waypoints. Conclusão da pesquisa vai no docstring do script com data.

3. Design de câmera e geração de .esp (script por rota)

Padrão generator (copie de $T/examples/sz-hkg-ceb-siquijor/trip_v10.py): cada trecho gera um .esp + flight_data.json consolidado, bibliotecas em $T/compose/ (flight.py/flight_v5.py: ll2ecef, tabela CAL, hermite, build_esp). Matemática principal (tudo verificado experimentalmente, não altere):

  • Semântica de interpolação do ES: renderização em nuvem = interpolação linear no espaço numérico dos keyframes; altitude interpola no espaço CAL value (logarítmico), escreva keyframes com alt_to_value(); trechos de grande escala (≥Mm de distância) devem interpolar no espaço v_write.
  • Vetores base da câmera (look-at calculado manualmente, alinhamento ≤1px com renderização do ES): f=norm(poi−cam), r=norm(cross(f,norm(cam))), u=cross(r,f) (u=cross(f,r) causaria inversão vertical).
  • Visão superior com norte para cima: câmera diretamente acima do alvo, look-at = ponto no solo deslocado 0.007·H para norte (≈0.4° de inclinação, heading constante 0, sem oscilação).
  • Densidade de keyframes: amostragem começa a cada 0.1s; eixos de tempo com movimento denso devem ter keyframe a cada frame (interpolação linear a cada 0.1s+ achata o zoom H em uma linha quebrada, pulso de velocidade aparente 1.6× a 10Hz).
  • Movimento suave = zoom H: deslocamento toma du/df = c·s(f)·H(f) (s=easing nos extremos com velocidade zero) → velocidade na tela independente da altitude; trecho de cruzeiro usa método envelope v(t)=COEF·H(t) (resolve COEF da equação de distância) para velocidade constante na tela ~700px/s.
  • Grande escala interpola no espaço log + sinease; múltiplos pontos de ancoragem usam Hermite cúbico + tangentes manuais (smoothstep com velocidade zero em cada ponto causa engasgos).
  • Junta bit-exata entre trechos: estado da câmera do primeiro frame do trecho seguinte é copiado byte a byte do ponto final do trecho anterior via camera_state(); o generator tem asserção _seam_diff < 1e-12 (diferença de keyframe deve ser = 0; diferença medida de frame 1–16 é apenas ruído de renderização em nuvem).
  • Armadilha de contagem de frames: sempre use int(round(T*FPS)) (int(4.1*30)=122≠123).

4. Renderização em nuvem Earth Studio (automação CDP)

Pré-requisitos: Chrome no Windows --headless=new --remote-debugging-port=9222 --user-data-dir=C:\Users\admin\ChromeESAuto (Chrome 136+ precisa de user-data-dir independente) já logado no ES; WSL2 rede mirrored conecta diretamente a http://localhost:9222.

node $T/cdp/nav_es.mjs                      # Recuperar/abrir ES + ajustar janela 1920×1080 (viewport reinicia para 985px a cada import/refresh, precisa reexecutar!)
node $T/cdp/siq_import.mjs 'C:\Users\admin\ChromeESAuto\seg0.esp'   # Importar .esp (caminho Windows)
node $T/cdp/siq_scrub_batch.mjs 45 45 15 …  # Avançar frame por frame (ArrowRight) lendo painel de propriedades (longitude/latitude/altitude) comparar com valores de design
node $T/cdp/render_submit.mjs "Seg0"        # DOM click abre diálogo de renderização + Submit (renderização em nuvem 1080p mp4, ~2 minutos/trecho)
node $T/cdp/siq_poll.mjs "Seg0" 15          # Polling fila até delivered (API leva alguns segundos para registrar após submit, "NONE" ≠ falha)
node $T/cdp/siq_dl.mjs get "Seg0"           # Baixar da fila modal; depois use `siq_dl.mjs close`

Três grandes armadilhas do CDP (scripts já têm contramedidas,原理 veja case-study §4):

  • Import .esp: DOM.setFileInputFiles precisa disparar manualmente evento change, senão ES falha silenciosamente; import só vai para memória, não para disco → Submit direto do estado live.
  • Cliques do mouse na síntese falham aleatoriamente → botão de renderização/Submit usam DOM el.click() (render_submit.mjs).
  • Inputs controlados pelo React às vezes não aceitam nome de job injetado → submeter com nome padrão do .esp, fazer polling e download pelo nome padrão (siq_dl.mjs get precisa do nome do job, senão baixa arquivo errado); link direto bigstore 404, não usar.
  • Painel de propriedades >350km de altitude mostra valor 8–17% menor (bug de exibição, caminho de renderização é preciso, não se engane).
  • Credenciais não são salvas em disco: login com node $T/cdp/login2.mjs <password> (passado via argv).

5. Montagem de frames, composição e regras de apresentação

# Decodificar (mp4 f00001 = t=0, indexado a partir de 1)
/home/admin/.local/bin/ffmpeg -i seg0.mp4 -qscale:v 1 seg0_frames/f%05d.jpg
# Renomear por timeline para src_frames/ (contínuo, indexado a partir de 1)

# Composição (KEEP_WM=1 = manter logo Google, única forma de entrega)
cd <diretório de trabalho>
FLIGHT_DATA=flight_data.json SRC=src_frames OUT=comp_frames KEEP_WM=1 \
  PYTHONPATH=<caminho Pillow> python3 $T/compose/compose20.py
FLIGHT_DATA=flight_data.json SRC=src_frames OUT=comp_frames \
  PYTHONPATH=<caminho Pillow> python3 $T/compose/compose20.py qc

# Verificação de flicker MAD (diferença de frames adjacentes em tons de cinza 192×108): mediana≤14 cena estática / 20–27 zoom logarítmico em degraus / >40 flicker
PYTHONPATH=<caminho Pillow> python3 $T/qc/mad_check.py comp_frames

Regras completas de apresentação em $T/references/presentation-rules.md (confirmado pelo usuário, não regredir), pontos principais: logo deve permanecer em uma única versão; caminhada = linha tracejada dinâmica sem desenhar pessoa; final sem veículo adicional; ícones estritamente ∝1/alt (13500/alt, piso 0.12) + imagem de fundo lado maior 150px no mesmo tamanho para mesma altitude; veículo pré-espera + parada em ponto de ancoragem exato (desenhar na ordem: espera→parada→atual).

Pontos de edição por rota no compose20.py (todo o resto orientado a dados):

  • AIRPORTS / PIERS lista de rótulos (texto / janela de tempo / fade-out) — coordenadas dos pontos de ancoragem vêm de flight_data.json;
  • FONT_PATH (fonte CJK, ajustar conforme máquina);
  • Sprites de veículos compose/emoji_*.png (novo tipo de veículo usa sprite_prep.py a partir de raw_sprites/).

QC deve passar tudo verde antes de codificar: projeção do ponto de parada/espera 0.00px, emoji totalmente dentro do quadro em todos os frames (±80px), diferença de keyframe na junta = 0, janela QC escrita com base no rt medido dos dados (não confiar no tempo de design). Orçamento de velocidade aparente: translação central ≤1.2 tela/s, veículo ≤0.8 tela/s.

6. Codificação e entrega

/home/admin/.local/bin/ffmpeg -framerate 30 -i comp_frames/f%05d.jpg \
  -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p -fps_mode passthrough \
  -movflags +faststart <rota>_trip_v17.mp4
scp <rota>_trip_v17.mp4 nas:/share/Public/earth/
md5sum <rota>_trip_v17.mp4 && ssh nas md5sum /share/Public/earth/<rota>_trip_v17.mp4   # só considera entregue se ambos os lados conferirem

Estratégia de versões: NAS mantém versões aprovadas históricas, exclui versões rejeitadas/substituídas; entrega com versão única de logo (sem duas versões com sufixo _logo).


7. Referência rápida de pesquisa Tavily (autenticado neste computador)

tvly em /home/admin/.local/bin/tvly (já tvly login). Saída JSON adequada para consumo por LLM:

tvly search "HKG CEB flight route waypoints FL350" --max-results 8 --include-answer basic
tvly search "Cebu to Siquijor OceanJet ferry schedule pier" --max-results 5 -o ferry.json
tvly extract 'https://www.flightradar24.com/...'           # Extração completa de texto
tvly map 'earth.google.com' --limit 20                     # Descoberta de URLs do site
tvly research "香港机场 07L 跑道 SID 离场航路"              # Pesquisa aprofundada (múltiplas rodadas, adequado para verificação de rota)

Uso mais completo veja a série de skills ~/.claude/skills/tavily-*.

8. Referência rápida de armadilhas

ArmadilhaContramedida
Extremidades OSRM não no ponto de ancoragem_load_forced fixa (insere se >25m / substitui caso contrário)
Import .esp falha silenciosamenteDisparar manualmente evento change (já incluso em siq_import.mjs)
Clique do mouse na síntese falhaDOM el.click() (render_submit.mjs)
Nome do job não é injetadoSubmeter com nome padrão do .esp + polling/download
Job não encontrado logo após submitAPI leva segundos, fazer poll antes de decidir
Viewport reseta para 985pxnode $T/cdp/nav_es.mjs a cada import/refresh
Altitude >350km exibe valor baixoBug do painel, confiar na renderização
numpy não existePIL ImageChops/histogram
Oscilação/flicker na timelineKeyframe a cada frame + zoom em log + movimento com zoom H + mad_check.py
Ferramenta Read não consegue ver frames renderizadosRecortar→redimensionar→microscópio ASCII ( .:-=+*#%@) inspeção visual
Botguard bloqueia loginDigitar caractere por caractere dispatchKeyEvent + interpolação de mouse (login2.mjs)
CredenciaisSempre passar via argv/variável de ambiente, nunca escrever no repositório

Habilidades Relacionadas