In-memory mime detection with Cocoa (OS X)?

My application is a viewer for a custom format, zip file with a well-defined XML manifest and resources such as images and movies. I use zlib to open a zip file in memory, and then go on to display the specified resources.

One problem I am facing is that I cannot display the video correctly, apparently because QTMovie cannot determine the mime type. A movie loaded from a file ([QTMovie movieWithFile]) works fine. Loaded from memory ([QTMovie movieWithData]) refuses to work.

This makes sense, since there is no file extension, QTMovie cannot determine mime type information. After a little search, I applied QTDataReference in the following mannner:

NSData *movieData = ...read from memory...; QTDataReference *movieDataReference = [[QTDataReference alloc] initWithReferenceToData:movieData name:fileName MIMEType:@"video/x-m4v"]; QTMovie *mov = [QTMovie movieWithDataReference:movieDataReference error:&err]; 

This works well, however hardcoding MIMEType is far from ideal. I have access to the file name and extension, so I tried to find the mime type using UTI (thanks to the good people at #macdev):

 - (NSString*)mimeTypeForExtension:(NSString*)ext { CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension,(CFStringRef)ext,NULL); return NSMakeCollectable(UTTypeCopyPreferredTagWithClass((CFStringRef)UTI,kUTTagClassMIMEType)); } 

This, however, does not work. Obviously, there is an internal database of OS X extensions and corresponding mime types. Otherwise, movies from the disc will not work. How do I access it?

Thanks!

+4
source share
3 answers

The problem is that m4v and m4p do not have mime types registered with Launch Services (probably because the mime type for m4v and m4p is not standard). In any case, what you should probably do is handle extreme cases like this, and then check for null when returning the function (if the extension is not registered and not processed by you).

Another thing is that you lose memory with your current use. I assume that you are using garbage collection, but the first call creates a CFString, which is never released. Here is a suggested implementation of your method:

 -(NSString*)mimeTypeForExtension:(NSString*)ext { NSAssert( ext, @"Extension cannot be nil" ); NSString* mimeType = nil; CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (CFStringRef)ext, NULL); if( !UTI ) return nil; CFStringRef registeredType = UTTypeCopyPreferredTagWithClass(UTI, kUTTagClassMIMEType); if( !registeredType ) // check for edge case { if( [ext isEqualToString:@"m4v"] ) mimeType = @"video/x-m4v"; else if( [ext isEqualToString:@"m4p"] ) mimeType = @"audio/x-m4p"; // handle anything else here that you know is not registered } else { mimeType = NSMakeCollectable(registeredType); } CFRelease(UTI); return mimeType; } 
+8
source

You can use NSWorkspace to make the system read the UTI file.

 -(NSString *)mimeTypeForFileAtPath:(NSString *)path error:(NSError **)err { NSString *uti, *mimeType = nil; if (!(uti = [[NSWorkspace sharedWorkspace] typeOfFile:path error:err])) return nil; if (err) *err = nil; if ((mimeType = (NSString *)UTTypeCopyPreferredTagWithClass((CFStringRef)uti, kUTTagClassMIMEType))) mimeType = NSMakeCollectable(mimeType); return mimeType; } 
+5
source

Invite people to change the return to [mimeType autorelease]; - some of us still use ancient ways!

And thanks! That was a big help.

+1
source

All Articles