Files
jhonny-editor/code/engine/scanner/descoberta/descoberta_de_arquivos.py
T
João Henrique b9bf3b2863 feat: criado repositório jhonny-editor no Gitea
criado repositório jhonny-editor no Gitea
adicionado script admin/deploy.command com commit automático
atualizado admin/DEV-NOTES.md com template limpo

Resumo:
- 48 arquivos alterados
- 26 novos
- 19 modificados
- 3 removidos

 22 files changed, 658 insertions(+), 568 deletions(-)

Arquivos:
  - AGENTS.md
  - admin/DEV-NOTES.md
  - admin/OpenCut.command
  - admin/commit.command
  - admin/deploy.command
  - admin/update.command
  - code/cep-plugin/index.html
  - code/cep-plugin/main.js
  - code/cep-plugin/styles.css
  - code/engine/ARQUITETURA.md
  - code/engine/arquitetura/README.md
  - code/engine/gerar_relatorio_timeline.py
  - code/engine/integracoes/apple_speech/apple_speech_transcriber.swift
  - code/engine/integracoes/apple_speech/provider_de_transcricao_apple.py
  - code/engine/integracoes/midia/__init__.py
  - code/engine/integracoes/whisper/provider_de_transcricao_local.py
  - code/engine/scanner/__init__.py
  - code/engine/scanner/coordenacao/__init__.py
  - code/engine/scanner/descoberta/__init__.py
  - code/engine/scanner/modelos.py
  - code/engine/scanner/transcricao_da_timeline.py
  - code/src/tools/discovery.ts
  - :memory:.ses
  - admin/Jhonny.command
  - admin/inativos/OpenCut.command
  - admin/inativos/update.command
  - code/docs/glossario-analise-emocional.md
  - code/docs/levantamento-apple-vision-e-apple-intelligence.md
  - code/docs/levantamento-ferramentas-analise-visual-local.md
  - code/engine/arquitetura/biblioteca-inteligente-de-videos.md
  - code/engine/executar_scanner.py
  - code/engine/integracoes/huggingface/
  - code/engine/integracoes/midia/extracao_de_metadados.py
  - code/engine/integracoes/visual/
  - code/engine/requirements-visual.txt
  - code/engine/scanner/configuracao_visual.py
  - code/engine/scanner/descoberta/descoberta_de_arquivos.py
  - code/engine/scanner/metadados.py
  - code/engine/scanner/relatorio_visual.py
  - code/engine/scanner/retakes/
  - code/engine/scanner/visual.py
  - code/engine/testes/test_analise_visual_local.py
  - code/engine/testes/test_arquivos_e_metadados.py
  - code/engine/testes/test_provider_de_transcricao_apple.py
  - code/relatorios/analise-brools/
  - code/relatorios/analise-visual/
  - code/relatorios/audio/arquivos/
  - code/relatorios/transcricao-timeline.md
2026-09-08 16:13:27 -04:00

98 lines
3.5 KiB
Python

from dataclasses import dataclass
from pathlib import Path
from typing import Protocol
from ...dominio import Clipe
from ...scanner.modelos import ErroDeAnalise
from ..coordenacao import ContextoDeAnalise
@dataclass(frozen=True)
class ArquivoDescoberto:
"""Resultado da validação do arquivo associado a um clipe."""
caminho: Path
existe: bool
acessivel: bool
tipo_de_midia: str | None = None
tamanho_em_bytes: int | None = None
@property
def offline(self) -> bool:
return not self.existe or not self.acessivel
class LeitorDeArquivos(Protocol):
def descobrir(self, caminho: str | Path) -> ArquivoDescoberto: ...
class LeitorLocalDeArquivos:
"""Adapter que resolve e valida caminhos no sistema de arquivos local."""
def descobrir(self, caminho: str | Path) -> ArquivoDescoberto:
caminho_resolvido = Path(caminho).expanduser().resolve(strict=False)
existe = caminho_resolvido.is_file()
acessivel = existe
tamanho = None
if existe:
try:
tamanho = caminho_resolvido.stat().st_size
with caminho_resolvido.open("rb"):
pass
except (OSError, PermissionError):
acessivel = False
return ArquivoDescoberto(
caminho=caminho_resolvido,
existe=existe,
acessivel=acessivel,
tipo_de_midia=caminho_resolvido.suffix.lower().lstrip(".") or None,
tamanho_em_bytes=tamanho,
)
class DescobertaDeArquivos:
"""Relaciona os clipes da timeline aos arquivos físicos de origem."""
nome = "descoberta_de_arquivos"
def __init__(self, leitor: LeitorDeArquivos | None = None) -> None:
self.leitor = leitor or LeitorLocalDeArquivos()
def executar(self, contexto: ContextoDeAnalise) -> ContextoDeAnalise:
if contexto.timeline is None:
return contexto
vistos: dict[Path, str] = {}
for clipe in self._clipes(contexto):
if not clipe.arquivo:
clipe.offline = True
self._registrar_erro(contexto, "arquivo_ausente", "Clipe sem sourceFile.", clipe)
continue
try:
descoberto = self.leitor.descobrir(clipe.arquivo)
except (OSError, ValueError, TypeError) as exc:
clipe.offline = True
self._registrar_erro(contexto, "arquivo_inacessivel", str(exc), clipe)
continue
clipe.arquivo = str(descoberto.caminho)
clipe.offline = descoberto.offline
contexto.arquivos[clipe.identificador] = descoberto
if descoberto.offline:
self._registrar_erro(contexto, "arquivo_inacessivel", "Arquivo não encontrado ou sem permissão de leitura.", clipe)
elif descoberto.caminho in vistos:
contexto.avisos.append(
f"Arquivo duplicado entre os clipes {vistos[descoberto.caminho]} e {clipe.identificador}."
)
else:
vistos[descoberto.caminho] = clipe.identificador
return contexto
@staticmethod
def _clipes(contexto: ContextoDeAnalise) -> list[Clipe]:
return [clipe for faixa in contexto.timeline.faixas for clipe in faixa.clipes]
@staticmethod
def _registrar_erro(contexto: ContextoDeAnalise, codigo: str, mensagem: str, clipe: Clipe) -> None:
contexto.erros.append(str(ErroDeAnalise(codigo, mensagem, f"clipe[{clipe.identificador}].sourceFile")))