import Foundation import Speech /// Encapsula o ciclo de vida do Apple Speech para a Engine. /// /// A classe não conhece o scanner, o catálogo ou a persistência. Sua única /// responsabilidade é converter um arquivo de mídia em JSON temporal. O /// processo é Foundation-only para poder ser executado como CLI sem iniciar /// AppKit ou NSApplication. final class TranscritorDeFalaApple { private let semaforo = DispatchSemaphore(value: 0) private var payload: [String: Any] = ["segmentos": []] private var codigoDeSaida: Int32 = 0 private var finalizado = false func executar(argumentos: [String]) -> Int32 { guard argumentos.count >= 3 else { return finalizar("uso: apple-speech-transcriber arquivo locale [somente_no_dispositivo]", 2) } let arquivo = URL(fileURLWithPath: argumentos[1]) let locale = Locale(identifier: argumentos[2]) let somenteNoDispositivo = argumentos.count > 3 && argumentos[3] == "true" guard let recognizer = SFSpeechRecognizer(locale: locale), recognizer.isAvailable else { return finalizar("Apple Speech indisponível para o locale solicitado", 3) } SFSpeechRecognizer.requestAuthorization { [weak self] status in guard let self else { return } guard status == .authorized else { self.finalizar("Permissão do Apple Speech não concedida", 4) return } let request = SFSpeechURLRecognitionRequest(url: arquivo) request.shouldReportPartialResults = false if somenteNoDispositivo && recognizer.supportsOnDeviceRecognition { request.requiresOnDeviceRecognition = true } recognizer.recognitionTask(with: request) { [weak self] resultado, erro in guard let self else { return } if let resultado, resultado.isFinal { self.payload["segmentos"] = resultado.bestTranscription.segments.map { ["inicio": $0.timestamp, "fim": $0.timestamp + $0.duration, "texto": $0.substring, "confianca": $0.confidence] as [String: Any] } self.finalizar(nil, 0) } else if let erro { self.finalizar("Falha no Apple Speech: \(erro.localizedDescription)", 5) } } } semaforo.wait() imprimirResultado() return codigoDeSaida } @discardableResult private func finalizar(_ mensagem: String?, _ codigo: Int32) -> Int32 { guard !finalizado else { return codigoDeSaida } finalizado = true if let mensagem { fputs(mensagem + "\n", stderr) } codigoDeSaida = codigo semaforo.signal() return codigo } private func imprimirResultado() { guard let dados = try? JSONSerialization.data(withJSONObject: payload), let texto = String(data: dados, encoding: .utf8) else { fputs("Não foi possível serializar o resultado do Apple Speech\n", stderr) codigoDeSaida = 6 return } print(texto) } } let transcritor = TranscritorDeFalaApple() exit(transcritor.executar(argumentos: CommandLine.arguments))