Skip to content

Commit 2c36bab

Browse files
authored
Handle C++ exceptions when reading/writing metadata (#571)
Fixes #570
1 parent 3689649 commit 2c36bab

19 files changed

+1606
-1325
lines changed
Lines changed: 90 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright (c) 2006-2024 Stephen F. Booth <[email protected]>
2+
// Copyright (c) 2006-2025 Stephen F. Booth <[email protected]>
33
// Part of https://github.com/sbooth/SFBAudioEngine
44
// MIT license
55
//
@@ -58,90 +58,107 @@ + (BOOL)testFileHandle:(NSFileHandle *)fileHandle formatIsSupported:(SFBTernaryT
5858

5959
- (BOOL)readPropertiesAndMetadataReturningError:(NSError **)error
6060
{
61-
TagLib::FileStream stream(self.url.fileSystemRepresentation, true);
62-
if(!stream.isOpen()) {
63-
if(error)
64-
*error = [NSError SFB_errorWithDomain:SFBAudioFileErrorDomain
65-
code:SFBAudioFileErrorCodeInputOutput
66-
descriptionFormatStringForURL:NSLocalizedString(@"The file “%@” could not be opened for reading.", @"")
67-
url:self.url
68-
failureReason:NSLocalizedString(@"Input/output error", @"")
69-
recoverySuggestion:NSLocalizedString(@"The file may have been renamed, moved, deleted, or you may not have appropriate permissions.", @"")];
70-
return NO;
61+
try {
62+
TagLib::FileStream stream(self.url.fileSystemRepresentation, true);
63+
if(!stream.isOpen()) {
64+
if(error)
65+
*error = [NSError SFB_errorWithDomain:SFBAudioFileErrorDomain
66+
code:SFBAudioFileErrorCodeInputOutput
67+
descriptionFormatStringForURL:NSLocalizedString(@"The file “%@” could not be opened for reading.", @"")
68+
url:self.url
69+
failureReason:NSLocalizedString(@"Input/output error", @"")
70+
recoverySuggestion:NSLocalizedString(@"The file may have been renamed, moved, deleted, or you may not have appropriate permissions.", @"")];
71+
return NO;
72+
}
73+
74+
TagLib::RIFF::AIFF::File file(&stream);
75+
if(!file.isValid()) {
76+
if(error)
77+
*error = [NSError SFB_errorWithDomain:SFBAudioFileErrorDomain
78+
code:SFBAudioFileErrorCodeInvalidFormat
79+
descriptionFormatStringForURL:NSLocalizedString(@"The file “%@” is not a valid AIFF file.", @"")
80+
url:self.url
81+
failureReason:NSLocalizedString(@"Not an AIFF file", @"")
82+
recoverySuggestion:NSLocalizedString(@"The file's extension may not match the file's type.", @"")];
83+
return NO;
84+
}
85+
86+
NSMutableDictionary *propertiesDictionary = [NSMutableDictionary dictionaryWithObject:@"AIFF" forKey:SFBAudioPropertiesKeyFormatName];
87+
if(file.audioProperties()) {
88+
auto properties = file.audioProperties();
89+
SFB::Audio::AddAudioPropertiesToDictionary(properties, propertiesDictionary);
90+
91+
if(properties->bitsPerSample())
92+
propertiesDictionary[SFBAudioPropertiesKeyBitDepth] = @(properties->bitsPerSample());
93+
if(properties->sampleFrames())
94+
propertiesDictionary[SFBAudioPropertiesKeyFrameLength] = @(properties->sampleFrames());
95+
}
96+
97+
SFBAudioMetadata *metadata = [[SFBAudioMetadata alloc] init];
98+
if(file.tag())
99+
[metadata addMetadataFromTagLibID3v2Tag:file.tag()];
100+
101+
self.properties = [[SFBAudioProperties alloc] initWithDictionaryRepresentation:propertiesDictionary];
102+
self.metadata = metadata;
103+
104+
return YES;
71105
}
72-
73-
TagLib::RIFF::AIFF::File file(&stream);
74-
if(!file.isValid()) {
106+
catch(const std::exception& e) {
107+
os_log_error(gSFBAudioFileLog, "Error reading AIFF properties and metadata: %{public}s", e.what());
75108
if(error)
76-
*error = [NSError SFB_errorWithDomain:SFBAudioFileErrorDomain
77-
code:SFBAudioFileErrorCodeInvalidFormat
78-
descriptionFormatStringForURL:NSLocalizedString(@"The file “%@” is not a valid AIFF file.", @"")
79-
url:self.url
80-
failureReason:NSLocalizedString(@"Not an AIFF file", @"")
81-
recoverySuggestion:NSLocalizedString(@"The file's extension may not match the file's type.", @"")];
109+
*error = [NSError errorWithDomain:SFBAudioFileErrorDomain code:SFBAudioFileErrorCodeInternalError userInfo:nil];
82110
return NO;
83111
}
84-
85-
NSMutableDictionary *propertiesDictionary = [NSMutableDictionary dictionaryWithObject:@"AIFF" forKey:SFBAudioPropertiesKeyFormatName];
86-
if(file.audioProperties()) {
87-
auto properties = file.audioProperties();
88-
SFB::Audio::AddAudioPropertiesToDictionary(properties, propertiesDictionary);
89-
90-
if(properties->bitsPerSample())
91-
propertiesDictionary[SFBAudioPropertiesKeyBitDepth] = @(properties->bitsPerSample());
92-
if(properties->sampleFrames())
93-
propertiesDictionary[SFBAudioPropertiesKeyFrameLength] = @(properties->sampleFrames());
94-
}
95-
96-
SFBAudioMetadata *metadata = [[SFBAudioMetadata alloc] init];
97-
if(file.tag())
98-
[metadata addMetadataFromTagLibID3v2Tag:file.tag()];
99-
100-
self.properties = [[SFBAudioProperties alloc] initWithDictionaryRepresentation:propertiesDictionary];
101-
self.metadata = metadata;
102-
return YES;
103112
}
104113

105114
- (BOOL)writeMetadataReturningError:(NSError **)error
106115
{
107-
TagLib::FileStream stream(self.url.fileSystemRepresentation);
108-
if(!stream.isOpen()) {
109-
if(error)
110-
*error = [NSError SFB_errorWithDomain:SFBAudioFileErrorDomain
111-
code:SFBAudioFileErrorCodeInputOutput
112-
descriptionFormatStringForURL:NSLocalizedString(@"The file “%@” could not be opened for writing.", @"")
113-
url:self.url
114-
failureReason:NSLocalizedString(@"Input/output error", @"")
115-
recoverySuggestion:NSLocalizedString(@"The file may have been renamed, moved, deleted, or you may not have appropriate permissions.", @"")];
116-
return NO;
116+
try {
117+
TagLib::FileStream stream(self.url.fileSystemRepresentation);
118+
if(!stream.isOpen()) {
119+
if(error)
120+
*error = [NSError SFB_errorWithDomain:SFBAudioFileErrorDomain
121+
code:SFBAudioFileErrorCodeInputOutput
122+
descriptionFormatStringForURL:NSLocalizedString(@"The file “%@” could not be opened for writing.", @"")
123+
url:self.url
124+
failureReason:NSLocalizedString(@"Input/output error", @"")
125+
recoverySuggestion:NSLocalizedString(@"The file may have been renamed, moved, deleted, or you may not have appropriate permissions.", @"")];
126+
return NO;
127+
}
128+
129+
TagLib::RIFF::AIFF::File file(&stream, false);
130+
if(!file.isValid()) {
131+
if(error)
132+
*error = [NSError SFB_errorWithDomain:SFBAudioFileErrorDomain
133+
code:SFBAudioFileErrorCodeInvalidFormat
134+
descriptionFormatStringForURL:NSLocalizedString(@"The file “%@” is not a valid AIFF file.", @"")
135+
url:self.url
136+
failureReason:NSLocalizedString(@"Not an AIFF file", @"")
137+
recoverySuggestion:NSLocalizedString(@"The file's extension may not match the file's type.", @"")];
138+
return NO;
139+
}
140+
141+
SFB::Audio::SetID3v2TagFromMetadata(self.metadata, file.tag());
142+
143+
if(!file.save()) {
144+
if(error)
145+
*error = [NSError SFB_errorWithDomain:SFBAudioFileErrorDomain
146+
code:SFBAudioFileErrorCodeInputOutput
147+
descriptionFormatStringForURL:NSLocalizedString(@"The file “%@” could not be saved.", @"")
148+
url:self.url
149+
failureReason:NSLocalizedString(@"Unable to write metadata", @"")
150+
recoverySuggestion:NSLocalizedString(@"The file's extension may not match the file's type.", @"")];
151+
return NO;
152+
}
153+
154+
return YES;
117155
}
118-
119-
TagLib::RIFF::AIFF::File file(&stream, false);
120-
if(!file.isValid()) {
156+
catch(const std::exception& e) {
157+
os_log_error(gSFBAudioFileLog, "Error writing AIFF metadata: %{public}s", e.what());
121158
if(error)
122-
*error = [NSError SFB_errorWithDomain:SFBAudioFileErrorDomain
123-
code:SFBAudioFileErrorCodeInvalidFormat
124-
descriptionFormatStringForURL:NSLocalizedString(@"The file “%@” is not a valid AIFF file.", @"")
125-
url:self.url
126-
failureReason:NSLocalizedString(@"Not an AIFF file", @"")
127-
recoverySuggestion:NSLocalizedString(@"The file's extension may not match the file's type.", @"")];
159+
*error = [NSError errorWithDomain:SFBAudioFileErrorDomain code:SFBAudioFileErrorCodeInternalError userInfo:nil];
128160
return NO;
129161
}
130-
131-
SFB::Audio::SetID3v2TagFromMetadata(self.metadata, file.tag());
132-
133-
if(!file.save()) {
134-
if(error)
135-
*error = [NSError SFB_errorWithDomain:SFBAudioFileErrorDomain
136-
code:SFBAudioFileErrorCodeInputOutput
137-
descriptionFormatStringForURL:NSLocalizedString(@"The file “%@” could not be saved.", @"")
138-
url:self.url
139-
failureReason:NSLocalizedString(@"Unable to write metadata", @"")
140-
recoverySuggestion:NSLocalizedString(@"The file's extension may not match the file's type.", @"")];
141-
return NO;
142-
}
143-
144-
return YES;
145162
}
146163

147164
@end
Lines changed: 94 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright (c) 2006-2024 Stephen F. Booth <[email protected]>
2+
// Copyright (c) 2006-2025 Stephen F. Booth <[email protected]>
33
// Part of https://github.com/sbooth/SFBAudioEngine
44
// MIT license
55
//
@@ -59,95 +59,111 @@ + (BOOL)testFileHandle:(NSFileHandle *)fileHandle formatIsSupported:(SFBTernaryT
5959

6060
- (BOOL)readPropertiesAndMetadataReturningError:(NSError **)error
6161
{
62-
TagLib::FileStream stream(self.url.fileSystemRepresentation, true);
63-
if(!stream.isOpen()) {
64-
if(error)
65-
*error = [NSError SFB_errorWithDomain:SFBAudioFileErrorDomain
66-
code:SFBAudioFileErrorCodeInputOutput
67-
descriptionFormatStringForURL:NSLocalizedString(@"The file “%@” could not be opened for reading.", @"")
68-
url:self.url
69-
failureReason:NSLocalizedString(@"Input/output error", @"")
70-
recoverySuggestion:NSLocalizedString(@"The file may have been renamed, moved, deleted, or you may not have appropriate permissions.", @"")];
71-
return NO;
62+
try {
63+
TagLib::FileStream stream(self.url.fileSystemRepresentation, true);
64+
if(!stream.isOpen()) {
65+
if(error)
66+
*error = [NSError SFB_errorWithDomain:SFBAudioFileErrorDomain
67+
code:SFBAudioFileErrorCodeInputOutput
68+
descriptionFormatStringForURL:NSLocalizedString(@"The file “%@” could not be opened for reading.", @"")
69+
url:self.url
70+
failureReason:NSLocalizedString(@"Input/output error", @"")
71+
recoverySuggestion:NSLocalizedString(@"The file may have been renamed, moved, deleted, or you may not have appropriate permissions.", @"")];
72+
return NO;
73+
}
74+
75+
TagLib::DSDIFF::File file(&stream);
76+
if(!file.isValid()) {
77+
if(error)
78+
*error = [NSError SFB_errorWithDomain:SFBAudioFileErrorDomain
79+
code:SFBAudioFileErrorCodeInvalidFormat
80+
descriptionFormatStringForURL:NSLocalizedString(@"The file “%@” is not a valid DSD Interchange file.", @"")
81+
url:self.url
82+
failureReason:NSLocalizedString(@"Not a DSD Interchange file", @"")
83+
recoverySuggestion:NSLocalizedString(@"The file's extension may not match the file's type.", @"")];
84+
return NO;
85+
}
86+
87+
NSMutableDictionary *propertiesDictionary = [NSMutableDictionary dictionaryWithObject:@"DSD Interchange" forKey:SFBAudioPropertiesKeyFormatName];
88+
if(file.audioProperties()) {
89+
auto properties = file.audioProperties();
90+
SFB::Audio::AddAudioPropertiesToDictionary(properties, propertiesDictionary);
91+
92+
if(properties->bitsPerSample())
93+
propertiesDictionary[SFBAudioPropertiesKeyBitDepth] = @(properties->bitsPerSample());
94+
if(properties->sampleCount())
95+
propertiesDictionary[SFBAudioPropertiesKeyFrameLength] = @(properties->sampleCount());
96+
}
97+
98+
SFBAudioMetadata *metadata = [[SFBAudioMetadata alloc] init];
99+
if(file.hasDIINTag())
100+
[metadata addMetadataFromTagLibTag:file.DIINTag()];
101+
102+
if(file.hasID3v2Tag())
103+
[metadata addMetadataFromTagLibID3v2Tag:file.ID3v2Tag()];
104+
105+
self.properties = [[SFBAudioProperties alloc] initWithDictionaryRepresentation:propertiesDictionary];
106+
self.metadata = metadata;
107+
108+
return YES;
72109
}
73-
74-
TagLib::DSDIFF::File file(&stream);
75-
if(!file.isValid()) {
110+
catch(const std::exception& e) {
111+
os_log_error(gSFBAudioFileLog, "Error reading DSD Interchange properties and metadata: %{public}s", e.what());
76112
if(error)
77-
*error = [NSError SFB_errorWithDomain:SFBAudioFileErrorDomain
78-
code:SFBAudioFileErrorCodeInvalidFormat
79-
descriptionFormatStringForURL:NSLocalizedString(@"The file “%@” is not a valid DSD Interchange file.", @"")
80-
url:self.url
81-
failureReason:NSLocalizedString(@"Not a DSD Interchange file", @"")
82-
recoverySuggestion:NSLocalizedString(@"The file's extension may not match the file's type.", @"")];
113+
*error = [NSError errorWithDomain:SFBAudioFileErrorDomain code:SFBAudioFileErrorCodeInternalError userInfo:nil];
83114
return NO;
84115
}
85-
86-
NSMutableDictionary *propertiesDictionary = [NSMutableDictionary dictionaryWithObject:@"DSD Interchange" forKey:SFBAudioPropertiesKeyFormatName];
87-
if(file.audioProperties()) {
88-
auto properties = file.audioProperties();
89-
SFB::Audio::AddAudioPropertiesToDictionary(properties, propertiesDictionary);
90-
91-
if(properties->bitsPerSample())
92-
propertiesDictionary[SFBAudioPropertiesKeyBitDepth] = @(properties->bitsPerSample());
93-
if(properties->sampleCount())
94-
propertiesDictionary[SFBAudioPropertiesKeyFrameLength] = @(properties->sampleCount());
95-
}
96-
97-
SFBAudioMetadata *metadata = [[SFBAudioMetadata alloc] init];
98-
if(file.hasDIINTag())
99-
[metadata addMetadataFromTagLibTag:file.DIINTag()];
100-
101-
if(file.hasID3v2Tag())
102-
[metadata addMetadataFromTagLibID3v2Tag:file.ID3v2Tag()];
103-
104-
self.properties = [[SFBAudioProperties alloc] initWithDictionaryRepresentation:propertiesDictionary];
105-
self.metadata = metadata;
106-
return YES;
107116
}
108117

109118
- (BOOL)writeMetadataReturningError:(NSError **)error
110119
{
111-
TagLib::FileStream stream(self.url.fileSystemRepresentation);
112-
if(!stream.isOpen()) {
113-
if(error)
114-
*error = [NSError SFB_errorWithDomain:SFBAudioFileErrorDomain
115-
code:SFBAudioFileErrorCodeInputOutput
116-
descriptionFormatStringForURL:NSLocalizedString(@"The file “%@” could not be opened for writing.", @"")
117-
url:self.url
118-
failureReason:NSLocalizedString(@"Input/output error", @"")
119-
recoverySuggestion:NSLocalizedString(@"The file may have been renamed, moved, deleted, or you may not have appropriate permissions.", @"")];
120-
return NO;
121-
}
122-
123-
TagLib::DSDIFF::File file(&stream, false);
124-
if(!file.isValid()) {
125-
if(error)
126-
*error = [NSError SFB_errorWithDomain:SFBAudioFileErrorDomain
127-
code:SFBAudioFileErrorCodeInvalidFormat
128-
descriptionFormatStringForURL:NSLocalizedString(@"The file “%@” is not a valid DSD Interchange file.", @"")
129-
url:self.url
130-
failureReason:NSLocalizedString(@"Not a DSD Interchange file", @"")
131-
recoverySuggestion:NSLocalizedString(@"The file's extension may not match the file's type.", @"")];
132-
return NO;
120+
try {
121+
TagLib::FileStream stream(self.url.fileSystemRepresentation);
122+
if(!stream.isOpen()) {
123+
if(error)
124+
*error = [NSError SFB_errorWithDomain:SFBAudioFileErrorDomain
125+
code:SFBAudioFileErrorCodeInputOutput
126+
descriptionFormatStringForURL:NSLocalizedString(@"The file “%@” could not be opened for writing.", @"")
127+
url:self.url
128+
failureReason:NSLocalizedString(@"Input/output error", @"")
129+
recoverySuggestion:NSLocalizedString(@"The file may have been renamed, moved, deleted, or you may not have appropriate permissions.", @"")];
130+
return NO;
131+
}
132+
133+
TagLib::DSDIFF::File file(&stream, false);
134+
if(!file.isValid()) {
135+
if(error)
136+
*error = [NSError SFB_errorWithDomain:SFBAudioFileErrorDomain
137+
code:SFBAudioFileErrorCodeInvalidFormat
138+
descriptionFormatStringForURL:NSLocalizedString(@"The file “%@” is not a valid DSD Interchange file.", @"")
139+
url:self.url
140+
failureReason:NSLocalizedString(@"Not a DSD Interchange file", @"")
141+
recoverySuggestion:NSLocalizedString(@"The file's extension may not match the file's type.", @"")];
142+
return NO;
143+
}
144+
145+
SFB::Audio::SetTagFromMetadata(self.metadata, file.tag());
146+
SFB::Audio::SetID3v2TagFromMetadata(self.metadata, file.ID3v2Tag());
147+
148+
if(!file.save()) {
149+
if(error)
150+
*error = [NSError SFB_errorWithDomain:SFBAudioFileErrorDomain
151+
code:SFBAudioFileErrorCodeInputOutput
152+
descriptionFormatStringForURL:NSLocalizedString(@"The file “%@” could not be saved.", @"")
153+
url:self.url
154+
failureReason:NSLocalizedString(@"Unable to write metadata", @"")
155+
recoverySuggestion:NSLocalizedString(@"The file's extension may not match the file's type.", @"")];
156+
return NO;
157+
}
158+
159+
return YES;
133160
}
134-
135-
SFB::Audio::SetTagFromMetadata(self.metadata, file.tag());
136-
SFB::Audio::SetID3v2TagFromMetadata(self.metadata, file.ID3v2Tag());
137-
138-
if(!file.save()) {
161+
catch(const std::exception& e) {
162+
os_log_error(gSFBAudioFileLog, "Error writing DSD Interchange metadata: %{public}s", e.what());
139163
if(error)
140-
*error = [NSError SFB_errorWithDomain:SFBAudioFileErrorDomain
141-
code:SFBAudioFileErrorCodeInputOutput
142-
descriptionFormatStringForURL:NSLocalizedString(@"The file “%@” could not be saved.", @"")
143-
url:self.url
144-
failureReason:NSLocalizedString(@"Unable to write metadata", @"")
145-
recoverySuggestion:NSLocalizedString(@"The file's extension may not match the file's type.", @"")];
164+
*error = [NSError errorWithDomain:SFBAudioFileErrorDomain code:SFBAudioFileErrorCodeInternalError userInfo:nil];
146165
return NO;
147166
}
148-
149-
150-
return YES;
151167
}
152168

153169
@end

0 commit comments

Comments
 (0)