AutoCat/AutoCatCore/Services/VehicleRecordService/VehicleRecordService.swift
2025-03-16 22:23:03 +03:00

143 lines
4.8 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// VehicleRecordService.swift
// AutoCatCore
//
// Created by Selim Mustafaev on 16.03.2025.
// Copyright © 2025 Selim Mustafaev. All rights reserved.
//
import Foundation
public actor VehicleRecordService {
let recordService: AudioRecordServiceProtocol
let locationService: LocationServiceProtocol
let settingsService: SettingsServiceProtocol
let validLetters = Constants.pnLettersMap.keys.map(String.init)
var url: URL?
var date = Date()
var location: VehicleEventDto?
@AutoCancellable
var locationTask: Task<Void,Error>?
public init(
recordService: AudioRecordServiceProtocol,
locationService: LocationServiceProtocol,
settingsService: SettingsServiceProtocol
) {
self.recordService = recordService
self.locationService = locationService
self.settingsService = settingsService
}
func getPlateNumber(from recognizedText: String?) -> String? {
guard let recognizedText else {
return nil
}
let trimmed = recognizedText
.replacingOccurrences(of: " ", with: "")
.uppercased()
.replacingOccurrences(of: "Ф", with: "В")
.replacingOccurrences(of: "НОЛЬ", with: "0")
.replacingOccurrences(of: "Э", with: "")
var result = ""
if let range = trimmed.range(of: #"\S\d\d\d\S\S\d\d\d?"#, options: .regularExpression) {
result = String(trimmed[range])
} else if let range = trimmed.range(of: #"\S\S\S\d\d\d\d\d\d?"#, options: .regularExpression), settingsService.recognizeAlternativeOrder {
let n = String(trimmed[range])
result = String(n.prefix(1)) + n.substring(with: 3..<6) + n.substring(with: 1..<3) + n.substring(from: 6)
} else if let range = trimmed.range(of: #"\S\d\d\d\S\S"#, options: .regularExpression), settingsService.recognizeShortenedNumbers {
result = String(trimmed[range]) + settingsService.defaultRegion
} else if let range = trimmed.range(of: #"\S\S\S\d\d\d"#, options: .regularExpression), settingsService.recognizeAlternativeOrder && settingsService.recognizeShortenedNumbers {
let n = String(trimmed[range])
result = String(n.prefix(1)) + n.substring(with: 3..<6) + n.substring(with: 1..<3) + settingsService.defaultRegion
}
if !result.isEmpty && valid(number: result) {
return result
} else {
return nil
}
}
func valid(number: String) -> Bool {
guard number.count >= 8 else { return false }
let first = String(number.prefix(1))
let second = number.substring(with: 4..<5)
let third = number.substring(with: 5..<6)
let digits = Int(number.substring(with: 1..<4))
let region = Int(number.substring(from: 6))
return self.validLetters.contains(first)
&& self.validLetters.contains(second)
&& self.validLetters.contains(third)
&& digits != nil
&& region != nil
&& region! < 1000
}
}
extension VehicleRecordService: VehicleRecordServiceProtocol {
public func requestPermissionsIfNeeded() async {
await recordService.requestRecordPermissions()
await recordService.requestRecognitionAuthorization()
}
public func startRecording() async throws {
date = Date()
let fileName = "recording-\(date.timeIntervalSince1970).m4a"
let url = try FileManager.default.url(for: fileName, in: "recordings")
self.url = url
try await recordService.startRecording(to: url)
locationTask = Task {
location = try await locationService.getRecentLocation()
}
}
public func stopRecording() async throws -> AudioRecordDto {
guard let url else {
await recordService.cancelRecording()
throw VehicleRecordError.emptyUrl
}
await recordService.stopRecording()
async let recognitionTask = recordService.recognizeText(from: url)
async let durationTask = recordService.getDuration(from: url)
let (text, duration) = await (recognitionTask, try? durationTask)
locationTask?.cancel()
locationTask = nil
self.url = nil
let record = AudioRecordDto(
path: url.lastPathComponent,
number: getPlateNumber(from: text),
raw: text ?? "",
duration: duration ?? 0,
event: location
)
return record
}
public func cancelRecording() async {
await recordService.cancelRecording()
locationTask?.cancel()
locationTask = nil
url = nil
}
}