Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6f518b0a9 |
@@ -276,7 +276,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 34;
|
CURRENT_PROJECT_VERSION = 35;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"meetingnotes/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"meetingnotes/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = G9LVHZAJNX;
|
DEVELOPMENT_TEAM = G9LVHZAJNX;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
@@ -290,7 +290,7 @@
|
|||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 15.0;
|
MACOSX_DEPLOYMENT_TARGET = 15.0;
|
||||||
MARKETING_VERSION = 1.1.22;
|
MARKETING_VERSION = 1.1.23;
|
||||||
ONLY_ACTIVE_ARCH = NO;
|
ONLY_ACTIVE_ARCH = NO;
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) -D ENABLE_TCC_SPI";
|
OTHER_SWIFT_FLAGS = "$(inherited) -D ENABLE_TCC_SPI";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = net.jamesbone.meetingnotes;
|
PRODUCT_BUNDLE_IDENTIFIER = net.jamesbone.meetingnotes;
|
||||||
@@ -312,7 +312,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 34;
|
CURRENT_PROJECT_VERSION = 35;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"meetingnotes/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"meetingnotes/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = G9LVHZAJNX;
|
DEVELOPMENT_TEAM = G9LVHZAJNX;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
@@ -326,7 +326,7 @@
|
|||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 15.0;
|
MACOSX_DEPLOYMENT_TARGET = 15.0;
|
||||||
MARKETING_VERSION = 1.1.22;
|
MARKETING_VERSION = 1.1.23;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) -D ENABLE_TCC_SPI";
|
OTHER_SWIFT_FLAGS = "$(inherited) -D ENABLE_TCC_SPI";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = net.jamesbone.meetingnotes;
|
PRODUCT_BUNDLE_IDENTIFIER = net.jamesbone.meetingnotes;
|
||||||
|
|||||||
@@ -87,10 +87,11 @@ final class AudioManager: NSObject, ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
repairHalfDurationSystemWAVIfNeeded(in: files)
|
repairHalfDurationSystemWAVIfNeeded(in: files)
|
||||||
if let mismatch = captureDurationMismatch(in: files) {
|
let completedFiles = files.compactMap { $0 }
|
||||||
let completedFiles = files.compactMap { $0 }
|
let audioFolder = preserveAudioFiles(completedFiles, meetingID: completedMeetingID)
|
||||||
let audioFolder = preserveAudioFiles(completedFiles, meetingID: completedMeetingID)
|
let transcriptionFiles = preservedAudioFiles(files, in: audioFolder)
|
||||||
lastRecoveryAudioFolderName = audioFolder?.lastPathComponent
|
lastRecoveryAudioFolderName = audioFolder?.lastPathComponent
|
||||||
|
if let mismatch = captureDurationMismatch(in: transcriptionFiles) {
|
||||||
let recoveryMessage = audioFolder == nil
|
let recoveryMessage = audioFolder == nil
|
||||||
? " The audio remains in the app's temporary folder."
|
? " The audio remains in the app's temporary folder."
|
||||||
: " Audio was kept so it can be recovered."
|
: " Audio was kept so it can be recovered."
|
||||||
@@ -99,8 +100,8 @@ final class AudioManager: NSObject, ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let model = UserDefaultsManager.shared.transcriptionModel
|
let model = UserDefaultsManager.shared.transcriptionModel
|
||||||
async let micResult = transcribe(files[0], model: model, diarization: false)
|
async let micResult = transcribe(transcriptionFiles[0], model: model, diarization: false)
|
||||||
async let systemResult = transcribe(files[1], model: model, diarization: true)
|
async let systemResult = transcribe(transcriptionFiles[1], model: model, diarization: true)
|
||||||
let (micTranscription, systemTranscription) = await (micResult, systemResult)
|
let (micTranscription, systemTranscription) = await (micResult, systemResult)
|
||||||
let results = [micTranscription, systemTranscription]
|
let results = [micTranscription, systemTranscription]
|
||||||
|
|
||||||
@@ -110,9 +111,6 @@ final class AudioManager: NSObject, ObservableObject {
|
|||||||
existingChunks: transcriptChunks.filter(\.isFinal)
|
existingChunks: transcriptChunks.filter(\.isFinal)
|
||||||
)
|
)
|
||||||
transcriptChunks = updated
|
transcriptChunks = updated
|
||||||
let completedFiles = files.compactMap { $0 }
|
|
||||||
let audioFolder = preserveAudioFiles(completedFiles, meetingID: completedMeetingID)
|
|
||||||
lastRecoveryAudioFolderName = audioFolder?.lastPathComponent
|
|
||||||
if !failures.isEmpty {
|
if !failures.isEmpty {
|
||||||
let retentionDays = UserDefaultsManager.shared.audioRetentionDays
|
let retentionDays = UserDefaultsManager.shared.audioRetentionDays
|
||||||
let retentionUnit = retentionDays == 1 ? "day" : "days"
|
let retentionUnit = retentionDays == 1 ? "day" : "days"
|
||||||
@@ -572,6 +570,15 @@ final class AudioManager: NSObject, ObservableObject {
|
|||||||
LocalStorageManager.shared.preserveAudioFiles(urls, for: meetingID)
|
LocalStorageManager.shared.preserveAudioFiles(urls, for: meetingID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func preservedAudioFiles(_ urls: [URL?], in folder: URL?) -> [URL?] {
|
||||||
|
urls.map { sourceURL in
|
||||||
|
guard let sourceURL else { return nil }
|
||||||
|
guard let folder else { return sourceURL }
|
||||||
|
let preservedURL = folder.appendingPathComponent(sourceURL.lastPathComponent)
|
||||||
|
return FileManager.default.fileExists(atPath: preservedURL.path) ? preservedURL : sourceURL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func captureDurationMismatch(in files: [URL?]) -> String? {
|
private func captureDurationMismatch(in files: [URL?]) -> String? {
|
||||||
guard files.count >= 2,
|
guard files.count >= 2,
|
||||||
let micDuration = audioDuration(at: files[0]),
|
let micDuration = audioDuration(at: files[0]),
|
||||||
|
|||||||
@@ -256,50 +256,12 @@ class LocalStorageManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func findRecoveryAudioFolder(for meeting: Meeting) -> URL? {
|
func findRecoveryAudioFolder(for meeting: Meeting) -> URL? {
|
||||||
if let name = meeting.recoveryAudioFolderName,
|
let canonicalName = meeting.id.uuidString
|
||||||
let folder = recoveryAudioFolder(named: name) {
|
guard meeting.recoveryAudioFolderName == nil
|
||||||
return folder
|
|| meeting.recoveryAudioFolderName?.caseInsensitiveCompare(canonicalName) == .orderedSame else {
|
||||||
}
|
|
||||||
|
|
||||||
let claimedFolderNames = Set(
|
|
||||||
loadMeetings()
|
|
||||||
.filter { $0.id != meeting.id }
|
|
||||||
.compactMap(\.recoveryAudioFolderName)
|
|
||||||
)
|
|
||||||
guard let folders = try? FileManager.default.contentsOfDirectory(
|
|
||||||
at: recoveryDirectory,
|
|
||||||
includingPropertiesForKeys: [.isDirectoryKey, .creationDateKey, .contentModificationDateKey],
|
|
||||||
options: [.skipsHiddenFiles]
|
|
||||||
) else {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
return recoveryAudioFolder(named: canonicalName)
|
||||||
let candidates = folders.compactMap { folder -> (url: URL, distance: TimeInterval)? in
|
|
||||||
let folderValues = try? folder.resourceValues(
|
|
||||||
forKeys: [.isDirectoryKey, .creationDateKey, .contentModificationDateKey]
|
|
||||||
)
|
|
||||||
let files = recoveryAudioFiles(in: folder)
|
|
||||||
guard folderValues?.isDirectory == true,
|
|
||||||
!claimedFolderNames.contains(folder.lastPathComponent),
|
|
||||||
!files.isEmpty else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
let dates = files.compactMap { file -> Date? in
|
|
||||||
let values = try? file.url.resourceValues(forKeys: [.creationDateKey, .contentModificationDateKey])
|
|
||||||
return values?.creationDate ?? values?.contentModificationDate
|
|
||||||
}
|
|
||||||
let referenceDate = dates.min()
|
|
||||||
?? folderValues?.creationDate
|
|
||||||
?? folderValues?.contentModificationDate
|
|
||||||
guard let referenceDate else { return nil }
|
|
||||||
return (folder, abs(referenceDate.timeIntervalSince(meeting.date)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// This fallback links recovery files created by older app versions.
|
|
||||||
return candidates
|
|
||||||
.filter { $0.distance <= 12 * 60 * 60 }
|
|
||||||
.min(by: { $0.distance < $1.distance })?
|
|
||||||
.url
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteRecoveryAudioFolder(_ folder: URL) {
|
func deleteRecoveryAudioFolder(_ folder: URL) {
|
||||||
|
|||||||
@@ -124,6 +124,9 @@ class RecordingSessionManager: ObservableObject {
|
|||||||
// Find and update the active meeting
|
// Find and update the active meeting
|
||||||
if let index = meetings.firstIndex(where: { $0.id == meetingId }) {
|
if let index = meetings.firstIndex(where: { $0.id == meetingId }) {
|
||||||
meetings[index].transcriptChunks = chunks
|
meetings[index].transcriptChunks = chunks
|
||||||
|
if let recoveryAudioFolderName = lastRecoveryAudioFolderName {
|
||||||
|
meetings[index].recoveryAudioFolderName = recoveryAudioFolderName
|
||||||
|
}
|
||||||
|
|
||||||
// Save the updated meeting
|
// Save the updated meeting
|
||||||
let success = LocalStorageManager.shared.saveMeeting(meetings[index])
|
let success = LocalStorageManager.shared.saveMeeting(meetings[index])
|
||||||
|
|||||||
@@ -254,9 +254,7 @@ class MeetingViewModel: ObservableObject {
|
|||||||
|
|
||||||
private func refreshRecoveryAudioFolder() {
|
private func refreshRecoveryAudioFolder() {
|
||||||
recoveryAudioFolderURL = LocalStorageManager.shared.findRecoveryAudioFolder(for: meeting)
|
recoveryAudioFolderURL = LocalStorageManager.shared.findRecoveryAudioFolder(for: meeting)
|
||||||
if let recoveryAudioFolderURL {
|
meeting.recoveryAudioFolderName = recoveryAudioFolderURL?.lastPathComponent
|
||||||
meeting.recoveryAudioFolderName = recoveryAudioFolderURL.lastPathComponent
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func showAudioInFinder() {
|
func showAudioInFinder() {
|
||||||
|
|||||||
Reference in New Issue
Block a user