feat(voz): legenda por ênfase, forced align, IA local e correções de zoom/revisão
Trabalho da branch feat/revisao-enfases: pipeline de edição por voz ganha alinhamento forçado (whisperx), roteirização por LLM local (Ollama), e a etapa 5 (revisão de frases) passa a refletir de verdade o que é aplicado. - generate_subtitles_by_emphasis: legenda comum cobre o clipe inteiro, legenda dinâmica só nas frases de ênfase, e a comum é desativada (enabled="0") onde a dinâmica cobre, em vez de nunca ser gerada ali. - validate_subtitle_layout ignora títulos com enabled="0" — corrige falso positivo de colisão contra o que está desativado no lugar dele. - Corrige zoom/marcador sendo descartado quando a borda encosta exatamente no início de um corte. - Etapa 5 do Assistente: recarrega quando as decisões da IA mudam (com fresh=true, ignorando a revisão salva antiga) — resolve a dessincronia entre "ativa" na tela e o que já foi cortado no FCPXML. - Etapa "Processar" reaplica as decisões da revisão (_phrase_actions.json) antes da cadeia de remoção de silêncio/legendas — antes, desativar uma frase na etapa 5 não tinha efeito nenhum no vídeo final. - Etapa "Concluído" fundida em "Processar" — abrir no Final Cut/Finder aparece assim que termina, sem slide extra. - Palavra clicável na etapa 5 agora funciona como toggle (clique de novo desfaz) e mostra a própria ênfase (sublinhado colorido + peso da fonte). - fcpxml/forced_align.py, fcpxml/llm_local.py, ai_edit.py: alinhamento fonético via whisperx e roteirização local via Ollama/Gemma. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
711c397dfe
commit
7b5aed79ee
@@ -31,15 +31,31 @@ struct CaptionsView: View {
|
||||
@AppStorage("capSampleAfter") private var sampleAfter = "sua legenda"
|
||||
@AppStorage("capShowGuides") private var showsGuides = true
|
||||
|
||||
private let fontChoices = [
|
||||
"Helvetica Neue", "Helvetica", "Arial", "Avenir Next",
|
||||
"Futura", "SF Pro Display", "Georgia", "Impact",
|
||||
]
|
||||
/// Todas as famílias de fonte instaladas no macOS (sistema + usuário), as
|
||||
/// usadas por padrão primeiro, para o seletor listar tudo sem hardcode.
|
||||
private static let installedFontFamilies: [String] = {
|
||||
var families = NSFontManager.shared.availableFontFamilies
|
||||
.sorted { $0.localizedCaseInsensitiveCompare($1) == .orderedAscending }
|
||||
let preferred = ["Helvetica Neue", "Playfair Display", "Georgia", "Didot"]
|
||||
for family in preferred.reversed() {
|
||||
if let idx = families.firstIndex(of: family) {
|
||||
families.remove(at: idx)
|
||||
families.insert(family, at: 0)
|
||||
}
|
||||
}
|
||||
return families
|
||||
}()
|
||||
|
||||
private let emphasisFontChoices = [
|
||||
"Playfair Display", "Georgia", "Didot", "Futura",
|
||||
"Avenir Next", "Times New Roman", "Helvetica Neue", "Impact",
|
||||
]
|
||||
/// Lista para um picker: todas as famílias instaladas e, se o valor salvo
|
||||
/// não estiver entre elas (ex.: fonte de outro Mac), ele entra no topo
|
||||
/// para o seletor continuar exibindo a escolha atual.
|
||||
private func fontChoices(for current: String) -> [String] {
|
||||
var list = Self.installedFontFamilies
|
||||
if !list.contains(current) {
|
||||
list.insert(current, at: 0)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
private let emphasisFaceChoices = [
|
||||
"Medium Italic", "Italic", "Bold Italic", "Bold", "Regular", "Light Italic",
|
||||
@@ -211,7 +227,7 @@ struct CaptionsView: View {
|
||||
private var bodySection: some View {
|
||||
Section("Linhas de apoio") {
|
||||
Picker("Fonte", selection: bound(\.font)) {
|
||||
ForEach(fontChoices, id: \.self) { Text($0).tag($0) }
|
||||
ForEach(fontChoices(for: config.font), id: \.self) { Text($0).tag($0) }
|
||||
}
|
||||
slider(
|
||||
"Tamanho",
|
||||
@@ -226,7 +242,7 @@ struct CaptionsView: View {
|
||||
private var emphasisSection: some View {
|
||||
Section("Palavra de ênfase") {
|
||||
Picker("Fonte", selection: bound(\.emphasisFont)) {
|
||||
ForEach(emphasisFontChoices, id: \.self) { Text($0).tag($0) }
|
||||
ForEach(fontChoices(for: config.emphasisFont), id: \.self) { Text($0).tag($0) }
|
||||
}
|
||||
Picker("Estilo", selection: bound(\.emphasisFace)) {
|
||||
ForEach(emphasisFaceChoices, id: \.self) { Text($0).tag($0) }
|
||||
@@ -244,7 +260,7 @@ struct CaptionsView: View {
|
||||
private var plainSubtitleSection: some View {
|
||||
Section("Legenda comum") {
|
||||
Picker("Fonte", selection: plainBound(\.font)) {
|
||||
ForEach(fontChoices, id: \.self) { Text($0).tag($0) }
|
||||
ForEach(fontChoices(for: plainConfig.font), id: \.self) { Text($0).tag($0) }
|
||||
}
|
||||
slider(
|
||||
"Tamanho",
|
||||
|
||||
Reference in New Issue
Block a user