SOURCE RECORD · S04

S04 · 制作视频 / Vidma

Apple speech request and local-only versus automatic attempts

公开摘录 · 截取原件于 2026-09-12 / Captured 2026-09-12

保留原行号。个人/账户信息如有删除,已用 REDACTED 标识。原文件 SHA-256 只用于识别截取时的本机原文件;不能证明编写或提交时间,也不等于脱敏下载文件的哈希。

Original line numbers are retained. Redactions are explicitly marked. The original full-file hash identifies a captured local file, not its creation/submission time or the redacted download. Public-file hashes are listed separately. This is selected source, not a whole-repository or binary absence finding.

Project key: vidma
Original relative path: Vidma/Services/SpeechSubtitleService.swift
Captured: 2026-09-12T18:57:15+08:00
Original full-file SHA256: 1f55567b14fb54e0699df7709137a264da703dd162d7a5b550e944670f628220
Evidence level: local source excerpt
Build correspondence: not yet established with an Apple-received binary.
Line numbers refer to the captured original file. Gaps are explicitly marked. No source code was changed.

Original lines 46 to 63

0046      init(recognizer: SFSpeechRecognizer) {
0047          self.recognizer = recognizer
0048      }
0049  
0050      func recognize(
0051          url: URL,
0052          requiresOnDeviceRecognition: Bool,
0053          audioDuration: Double,
0054          progress: @escaping @Sendable (Double) async -> Void
0055      ) async throws -> [RecognizedSpeechSegment] {
0056          try Task.checkCancellation()
0057          let request = SFSpeechURLRecognitionRequest(url: url)
0058          request.shouldReportPartialResults = true
0059          request.requiresOnDeviceRecognition = requiresOnDeviceRecognition
0060          request.taskHint = .dictation
0061          request.addsPunctuation = true
0062  
0063          return try await withCheckedThrowingContinuation { continuation in

Original lines 259 to 286

0259          guard let recognizerBox = await LegacySpeechRecognizerBox(localeIdentifier: localeIdentifier), recognizerBox.recognizer.isAvailable else {
0260              throw AppError.speechRecognitionFailure(context: "speech recognizer unavailable for \(localeIdentifier)")
0261          }
0262          let recognizer = recognizerBox.recognizer
0263          if mode == .onDeviceOnly, !recognizer.supportsOnDeviceRecognition {
0264              throw AppError.speechRecognitionFailure(messageKey: "error.speech.onDevice.message", context: "on-device recognition unavailable for \(localeIdentifier)")
0265          }
0266  
0267          let attempts = Self.recognitionAttempts(mode: mode, supportsOnDeviceRecognition: recognizer.supportsOnDeviceRecognition)
0268          let chunks = try await AudioPreprocessingService.shared.legacyChunks(from: prepared, outputDirectory: outputDirectory)
0269          await SubtitleDiagnosticsStore.shared.recordChunks(chunks)
0270          var recognized: [RecognizedSpeechSegment] = []
0271          var lastFailure: Error?
0272          var completedRequest = false
0273  
0274          for (chunkIndex, chunk) in chunks.enumerated() {
0275              var chunkCompleted = false
0276              for (attemptIndex, requiresOnDeviceRecognition) in attempts.enumerated() {
0277                  try Task.checkCancellation()
0278                  let session = SpeechRecognitionSession(recognizer: recognizer)
0279                  do {
0280                      let segments = try await withTaskCancellationHandler {
0281                          try await session.recognize(
0282                              url: chunk.url,
0283                              requiresOnDeviceRecognition: requiresOnDeviceRecognition,
0284                              audioDuration: chunk.duration
0285                          ) { fraction in
0286                              let attemptFraction = (Double(attemptIndex) + min(1, max(0, fraction))) / Double(attempts.count)

Original lines 434 to 445

0434      nonisolated static func recognitionAttempts(
0435          mode: SpeechRecognitionMode,
0436          supportsOnDeviceRecognition: Bool
0437      ) -> [Bool] {
0438          switch mode {
0439          case .automatic:
0440              supportsOnDeviceRecognition ? [true, false] : [false]
0441          case .onDeviceOnly:
0442              [true]
0443          }
0444      }
0445