2016-02-25 1 views
0

Я пытаюсь сгенерировать файлы .aiff с помощью NSSpeechSynthesizer.startSpeakingString(), и я использую GCd с помощью последовательной очереди, так как NSSpeechSynthesizer берет строку и создает файл aiff по указанному адресу NSURL. Я использовал стандарт для метода loop для списка строк в [String: [String]], но это создает некоторые файлы с 0 байтами.swift OSX: серийный генератор файлов с использованием GCD

Вот функция для генерации речи:

func createSpeech(type: String, name: String) { 
     if !NSFileManager.defaultManager().fileExistsAtPath("\(dataPath)\(type)/\(name)/\(name).aiff"){ 
      do{ 
      try NSFileManager().createDirectoryAtPath("\(dataPath)\(type)/\(name)/", withIntermediateDirectories: true, attributes: nil) 
let URL = NSURL(fileURLWithPath: "\(dataPath)\(type)/\(name)/\(name).aiff") 
          print("Attempting to save speech \(name).aiff") 
self.synth.startSpeakingString(name, toURL: URL) 
      }catch{ 
       print("error occured") 
      } 
     } 
    } 

А вот функция, которая пересекает словарь для создания файлов:

 for key in self.nodeLibrary.keys{ 
           dispatch_sync(GlobalBackgroundQueue){ 
       let type = self.nodeLibrary[key]?.0 
      let name = key.componentsSeparatedByString("_")[0] 
            if !speechCheck.contains(name){ 
mixer.createSpeech(type!, name: name) 
            } 
      } 
     } 

globalBackgroundQueue является псевдонимом очереди НОД звоните _T для удобства чтения.

Обычная программа работает нормально, создает папки и подпапки, как требуется другой внешней функцией, затем синтезирует речь, но в моем случае я всегда получаю один или несколько, которые не загружаются должным образом, давая 0 байт или слишком малое количество байтов что делает файл непригодным.

Я прочитал следующую запись и используют эти методы НОД на некоторое время, но я не уверен, где я неправильно здесь:

http://www.raywenderlich.com/60749/grand-central-dispatch-in-depth-part-1

Любая помощь очень ценится как обычно

Редактировать: Обновлено с завершением закрытия и обнаружено, возможно, ошибка

Я создал функцию закрытия, как показано ниже, и использую ее в другом вспомогательном методе, который проверяет любые ошибки, такие как sourceFile.length, равный 0 после загрузки. Тем не менее, все файлы имеют длину 0, что невозможно, так как я проверял свойства звука каждого файла с помощью команды свойства finder + i.

func synthesise(type: String, name: String, completion: (success: Bool)->()) { 
     if !NSFileManager.defaultManager().fileExistsAtPath("\(dataPath)\(type)/\(name)/\(name).aiff"){ 
      do{ 
      try NSFileManager().createDirectoryAtPath("\(dataPath)\(type)/\(name)/", withIntermediateDirectories: true, attributes: nil) 
       let URL = NSURL(fileURLWithPath: "\(dataPath)\(type)/\(name)/\(name).aiff") 
       let success = self.synth.startSpeakingString(name, toURL: URL) 
       completion(success: success) 
      }catch{ 
       print("error occured") 
      } 
     } 
    } 

    func loadSpeech(type: String, name: String){ 
      synthesise(type, name: name, completion: {(success: Bool)->Void in 
       if success{ 
    print("File \(name) created successfully with return \(self.synthSuccess), checking file integrity") 
        let URL = NSURL(fileURLWithPath: "\(self.dataPath)\(type)/\(name)/\(name).aiff") 
        do{ 
        let source = try AVAudioFile(forReading: URL) 
         print("File has length: \(source.)") 
        }catch{ 
         print("error loading file") 
        } 
       }else{ 
        print("creation unsuccessful, trying again") 
    self.loadSpeech(type, name: name) 
       } 
      }) 
     } 

файлы генерируются с их папки и как метод startSpeakingString-> Bool и функции делегата у меня в классе, который обновляет synthSuccess свойство показывать верно. Поэтому я загружаю AVAudioFile, чтобы проверить его длину. Все длины файлов равны 0. Какие они не за исключением одного.

Когда я говорю об ошибке, это из другой части приложения, где я загружаю AVAudioEngine и начинаю загрузку буферов с аргументом frameCount, установленным в sourceAudioFile.length, который дает диагностическую ошибку, но сейчас это вне контекста.

+0

Есть еще вопросы в отредактированный код: 1 В вашем методе 'synhesize' вы не завершаете _when_ асинхронная задача была завершена. Вместо этого вы немедленно завершите его после того, как вы запустили асинхронную задачу. Исправлено: вам нужно использовать _delegate_ 'NSSpeechSynthesizerDelegate', чтобы получить уведомление _when_ задача завершена. Затем и только тогда вызовите обработчик завершения. Во-вторых, вам необходимо убедиться, что обработчик завершения в конечном итоге будет вызван. То есть, это ДОЛЖНО быть вызвано в конечном итоге. В вашем методе 'synhesize' вы не выполняете это требование. – CouchDeveloper

+0

2. 'loadSpeech' не работает так, как вы ожидаете. Из-за 1. он не будет вызван, когда синтез завершится неудачей. 3. Вам по-прежнему нужен способ обеспечения последовательного вызова нескольких входов.У вашего метода 'loadSpeech' есть уже базовая структура для выполнения этого. Создайте новый с параметром массива входов и обработчиком завершения. Внутренняя функция имеет inout параметр 'Generator' и обработчик завершения. Вызовите 'gen.next' во внутренней функции, чтобы получить следующий элемент в массиве. Когда задача завершена, вызовите внутреннюю функцию, передающую ей генератор. – CouchDeveloper

+0

Да, у меня есть как didFinishSpeaking, так и didEncounterError в моем файле класса. Я сделал 3 разных подхода, один с использованием блоков, другой с GCD и простой, проверяя свойство, которое я называю finalSpeak: Bool. Нет. Цикл while продолжается вечно. Однако я также протестировал один startSpeakingString без toURL, и сообщения печатаются на консоль. Когда я использую toURL, сообщение печатает, но файл поврежден, хотя сообщение об ошибке не распечатывается (которое я установил как print («\ (message) является ошибкой»). Я вижу, что другие пользователи имеют такую ​​же проблему с поврежденными файлами NSSpeechSynthesizer, – triple7

ответ

1

startSpeakingString(_:toURL:) запустит асинхронный задача в фоновом режиме. Фактически, ваш код запускает несколько асинхронных задач, которые запускаются одновременно. Это может быть причиной проблемы, которую вы испытываете.

Решение должно гарантировать, что только одна задача активна одновременно.

Проблема startSpeakingString(_:toURL:) в том, что он начинает асинхронную задачу - но функция сама предоставляет никаких средств, чтобы получить уведомление, когда задача выполнена.

Однако, есть делегат, который необходимо настроить для получения уведомлений.

Таким образом, для вашего решения потребуется определить NSSpeechSynthesizerDelegate.

Вы можете создать свой собственный класс помощника, который предоставляет асинхронную функцию, которая имеет обработчик завершения:

func exportSpeakingString(string: String, url: NSURL, 
    completion: (NSURL?, ErrorType?) ->()) 

Внутренне класс создает экземпляр NSSpeechSynthesizerиNSSpeechSynthesizerDelegate и реализует методы делегата, соответственно, ,

Чтобы выполнить задачу, вам необходимо найти подход для запуска нескольких асинхронных функций последовательно. На SO уже есть решения.

Edit:

настроить мой собственный проект, чтобы подтвердить или пренебрежение возможный вопрос в рамках NSSpeechSynthesizer системы. До сих пор могут владеть испытаниями, подтверждающими, что NSSpeechSynthesizer работает должным образом.

Однако, есть несколько тонкостей стоит упомянуть:

  1. Убедитесь, что вы создать действительный URL файла, который вы передаете в качестве аргумента параметр URL в методе startSpeakingString(:toURL:).

  2. Убедитесь, что вы выбрали расширение для выходного файла, который известен NSSpeechSynthesizer и системные рамки, воспроизводящие этот файл, например .aiff. К сожалению, документации здесь недостаточно, поэтому мне пришлось пройти пробную версию и ошибку. Список поддерживаемых форматов аудио файлов QuickTime может помочь здесь. Тем не менее, я понятия не имею, как NSSpeechSynthesizer выбирает формат вывода.

Следующие два класса составляют простой простой в использовании библиотеки:

import Foundation 
import AppKit 


enum SpeechSynthesizerError: ErrorType { 
    case ErrorActive 
    case ErrorURL(message: String) 
    case ErrorUnknown 
} 

internal class InternalSpeechSynthesizer: NSObject, NSSpeechSynthesizerDelegate { 

    typealias CompletionFunc = (NSURL?, ErrorType?) ->() 

    private let synthesizer = NSSpeechSynthesizer(voice: nil)! 
    private var _completion: CompletionFunc? 
    private var _url: NSURL? 

    override init() { 
     super.init() 
     synthesizer.delegate = self 
    } 

    // CAUTION: This call is not thread-safe! Ensure that multiple method invocations 
    // will be called from the same thread! 
    // Only _one_ task can be active at a time. 
    internal func synthesize(input: String, output: NSURL, completion: CompletionFunc) { 
     guard _completion == nil else { 
      dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) { 
       completion(nil, SpeechSynthesizerError.ErrorActive) 
      } 
      return 
     } 
     guard output.path != nil else { 
      dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) { 
       completion(nil, SpeechSynthesizerError.ErrorURL(message: "The URL must be a valid file URL.")) 
      } 
      return 
     } 
     _completion = completion 
     _url = output 
     if !synthesizer.startSpeakingString(input, toURL: output) { 
      fatalError("Could not start speeaking") 
     } 
    } 


    internal func speechSynthesizer(sender: NSSpeechSynthesizer, 
            willSpeakWord characterRange: NSRange, 
            ofString string: String) 
    { 
     NSLog("willSpeakWord") 
    } 

    internal func speechSynthesizer(sender: NSSpeechSynthesizer, 
            willSpeakPhoneme phonemeOpcode: Int16) 
    { 
     NSLog("willSpeakPhoneme") 
    } 

    internal func speechSynthesizer(sender: NSSpeechSynthesizer, 
            didEncounterErrorAtIndex characterIndex: Int, 
            ofString string: String, 
            message: String) 
    { 
     NSLog("didEncounterErrorAtIndex") 
    } 

    internal func speechSynthesizer(sender: NSSpeechSynthesizer, 
            didFinishSpeaking finishedSpeaking: Bool) 
    { 
     assert(self._url != nil) 
     assert(self._url!.path != nil) 
     assert(self._completion != nil) 
     var error: ErrorType?    
     if !finishedSpeaking { 
      do { 
       error = try self.synthesizer.objectForProperty(NSSpeechErrorsProperty) as? NSError 
      } catch let err { 
       error = err 
      } 
     } 
     let url: NSURL? = NSFileManager.defaultManager().fileExistsAtPath(self._url!.path!) ? self._url : nil   
     let completion = self._completion! 
     dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) { 
      if url == nil && error == nil { 
       error = SpeechSynthesizerError.ErrorUnknown 
      } 
      completion(url, error) 
     } 
     _completion = nil 
     _url = nil 
    } 

} 


public struct SpeechSynthesizer { 
    public init() {}  
    private let _synthesizer = InternalSpeechSynthesizer() 

    public func synthesize(input: String, output: NSURL, completion: (NSURL?, ErrorType?) ->()) { 
     _synthesizer.synthesize(input, output: output) { (url, error) in 
      completion(url, error) 
     } 
    } 
} 

Вы можете использовать его, как показано ниже:

func testExample() { 
    let expect = self.expectationWithDescription("future should be fulfilled") 

    let synth = SpeechSynthesizer() 
    let url = NSURL(fileURLWithPath: "/Users/me/Documents/speech.aiff") 

    synth.synthesize("Hello World!", output: url) { (url, error) in 
     if let url = url { 
      print("URL: \(url)") 
     } 
     if let error = error { 
      print("Error: \(error)") 
     } 
     expect.fulfill() 
    } 


    self.waitForExpectationsWithTimeout(1000, handler: nil) 
    // Test: output file should exist. 
} 
+0

Привет, разработчик couch, я обновил код, чтобы учесть t он выводит результаты, но все еще имеет эти файлы с 0 AVFrameCount. Все они печатаются как 0, хотя 5 из 6 действительно имеют контент. Я что-то забыл? – triple7

+0

Hi Couch разработчик, работает очень хорошо. Я сделаю ссылку на эту должность на другие вопросы, поскольку она попадает в ту же категорию. Спасибо, я многому научился о создании собственной делегации внутри страны. – triple7

1

В приведенном выше коде проверьте результат звонка на synth.startSpeakingString(name, toURL: URL), который может вернуть false, если синтезатор не смог начать разговор. Если это не удается, узнайте, почему, или просто повторите попытку.

Плюс, добавьте [NSSpeechSynthesiserDelegate][1], и найдите обратные вызовы speechSynthesizer:didFinishSpeaking:. Когда синтезатор думает, что он закончил говорить, проверьте размер файла. Если он равен нулю, повторите операцию.

+0

Спасибо за предложение. У меня есть методы делегата, которые я использую, чтобы проверить, был ли синтез успешным, но не выглядел в него больше для того, чтобы разоблачить какой-либо конкретный тип ошибки на нем. Я заставлю голову над блоками завершения после этого :) – triple7

Смежные вопросы