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
98 lines
3.5 KiB
Python
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")))
|