diff --git a/OpenSuperWhisper/ContentView.swift b/OpenSuperWhisper/ContentView.swift index 1cc9ad7..95ddd97 100644 --- a/OpenSuperWhisper/ContentView.swift +++ b/OpenSuperWhisper/ContentView.swift @@ -68,29 +68,33 @@ class ContentViewModel: ObservableObject { // Capture the current recording duration let duration = await MainActor.run { self.recordingDuration } - // Create a new Recording instance + // Create a new Recording and save audio + metadata let timestamp = Date() let fileName = "\(Int(timestamp.timeIntervalSince1970)).wav" - let finalURL = Recording( + let recording = Recording( id: UUID(), timestamp: timestamp, fileName: fileName, transcription: text, duration: duration // Use tracked duration - ).url - + ) + // Ensure recordings folder exists + try FileManager.default.createDirectory( + at: Recording.recordingsDirectory, + withIntermediateDirectories: true + ) // Move the temporary recording to final location - try recorder.moveTemporaryRecording(from: tempURL, to: finalURL) - + let audioURL = recording.url + try recorder.moveTemporaryRecording(from: tempURL, to: audioURL) + // Write metadata JSON alongside audio + let metadataURL = audioURL.deletingPathExtension().appendingPathExtension("json") + let encoder = JSONEncoder() + encoder.outputFormatting = [.prettyPrinted, .sortedKeys] + let metadataData = try encoder.encode(recording) + try metadataData.write(to: metadataURL) // Save the recording to store await MainActor.run { - self.recordingStore.addRecording(Recording( - id: UUID(), - timestamp: timestamp, - fileName: fileName, - transcription: text, - duration: self.recordingDuration // Use tracked duration - )) + self.recordingStore.addRecording(recording) } print("Transcription result: \(text)") diff --git a/OpenSuperWhisper/FileDropHandler.swift b/OpenSuperWhisper/FileDropHandler.swift index a441193..54b1b8b 100644 --- a/OpenSuperWhisper/FileDropHandler.swift +++ b/OpenSuperWhisper/FileDropHandler.swift @@ -84,29 +84,32 @@ class FileDropHandler: ObservableObject { url: url, settings: Settings() ) - // Create a new Recording instance + // Create a new Recording and save audio + metadata let timestamp = Date() let fileName = "\(Int(timestamp.timeIntervalSince1970)).wav" - let finalURL = Recording( + let recording = Recording( id: UUID(), timestamp: timestamp, fileName: fileName, transcription: text, duration: fileDuration - ).url - - // Copy the file for playback - try FileManager.default.copyItem(at: url, to: finalURL) - - // Save the recording to store - self.recordingStore.addRecording( - Recording( - id: UUID(), - timestamp: timestamp, - fileName: fileName, - transcription: text, - duration: self.fileDuration - )) + ) + // Ensure recordings folder exists + try FileManager.default.createDirectory( + at: Recording.recordingsDirectory, + withIntermediateDirectories: true + ) + // Copy audio file + let audioURL = recording.url + try FileManager.default.copyItem(at: url, to: audioURL) + // Write metadata JSON alongside audio + let metadataURL = audioURL.deletingPathExtension().appendingPathExtension("json") + let encoder = JSONEncoder() + encoder.outputFormatting = [.prettyPrinted, .sortedKeys] + let metadataData = try encoder.encode(recording) + try metadataData.write(to: metadataURL) + // Save the recording to the store + self.recordingStore.addRecording(recording) } catch { print("Error processing dropped audio file: \(error)") diff --git a/OpenSuperWhisper/Indicator/IndicatorWindow.swift b/OpenSuperWhisper/Indicator/IndicatorWindow.swift index 9f87fda..39444ba 100644 --- a/OpenSuperWhisper/Indicator/IndicatorWindow.swift +++ b/OpenSuperWhisper/Indicator/IndicatorWindow.swift @@ -51,29 +51,33 @@ class IndicatorViewModel: ObservableObject { print("start decoding...") let text = try await transcription.transcribeAudio(url: tempURL, settings: Settings()) - // Create a new Recording instance + // Create a new Recording and save audio + metadata let timestamp = Date() let fileName = "\(Int(timestamp.timeIntervalSince1970)).wav" - let finalURL = Recording( + let recording = Recording( id: UUID(), timestamp: timestamp, fileName: fileName, transcription: text, duration: 0 // TODO: Get actual duration - ).url - + ) + // Ensure recordings folder exists + try FileManager.default.createDirectory( + at: Recording.recordingsDirectory, + withIntermediateDirectories: true + ) // Move the temporary recording to final location - try recorder.moveTemporaryRecording(from: tempURL, to: finalURL) - + let audioURL = recording.url + try recorder.moveTemporaryRecording(from: tempURL, to: audioURL) + // Write metadata JSON alongside audio + let metadataURL = audioURL.deletingPathExtension().appendingPathExtension("json") + let encoder = JSONEncoder() + encoder.outputFormatting = [.prettyPrinted, .sortedKeys] + let metadataData = try encoder.encode(recording) + try metadataData.write(to: metadataURL) // Save the recording to store await MainActor.run { - self.recordingStore.addRecording(Recording( - id: UUID(), - timestamp: timestamp, - fileName: fileName, - transcription: text, - duration: 0 // TODO: Get actual duration - )) + self.recordingStore.addRecording(recording) } insertTextUsingPasteboard(text) diff --git a/OpenSuperWhisper/Models/Recording.swift b/OpenSuperWhisper/Models/Recording.swift index ef11da9..92de7c6 100644 --- a/OpenSuperWhisper/Models/Recording.swift +++ b/OpenSuperWhisper/Models/Recording.swift @@ -12,13 +12,18 @@ struct Recording: Identifiable, Codable, FetchableRecord, PersistableRecord, Equ return lhs.id == rhs.id } - var url: URL { + /// Directory where recording files are stored. + static var recordingsDirectory: URL { let applicationSupport = FileManager.default.urls( for: .applicationSupportDirectory, in: .userDomainMask ).first! let appDirectory = applicationSupport.appendingPathComponent(Bundle.main.bundleIdentifier!) - let recordingsDirectory = appDirectory.appendingPathComponent("recordings") - return recordingsDirectory.appendingPathComponent(fileName) + return appDirectory.appendingPathComponent("recordings") + } + + /// File URL for this recording. + var url: URL { + Self.recordingsDirectory.appendingPathComponent(fileName) } // MARK: - Database Table Definition