Banging My Head With Swift So You Don’t Have To

Hello my friends,

I’ve been keeping busy trying to learn Swift, I have a media tagger project that I will be developing using Swift exclusively.

I have to say that coming from Objective-C, C and C++ environments Swift has the best of these languages without the hassle they often bring to the table. Swift is smart and knows exactly what I mean when I say something like this:

var someArray = ["one", "two", "three"] 

Without the complexity of this:

NSArray *someArray = [[NSArray alloc] initWithObjects:@"one", @"two", @"three", nil];

Works like a low level language with the expressiveness of a high level scripting language. Nonetheless there are some hiccups like:

// This code works in Obj-C
if ([someArray containsObject:@"one"]) {
// Do something
}
// The above does not works in Swift, this is a workaround
if (contains(someArray, "one")) {
// Do something
}

Another thing to consider is that Swift is not yet an ISO standard, for some people that’s important, programmers want to make sure that the time invested in learning a new language is well spent. ISO standards assure programmers that all APIs and language will not change, but that hasn’t stop people from learning HTML5. I’m sure that will happen in a year or two when all the developer community is happy with the language and has provided all feedback necessary to make that language where it needs to be. So far is looking good, now to my MediaTagger app.

MediaTagger is an app that will run in OS X and will give you the ability to tag all your video files, sort of like how Genres works in the iTunes library where you can filter and get for example all Jazz songs. In this app you will be able to create your custom tags and as well as adding multiple tags for a video file; if you have a video of your son playing basketball you will be able to create tags for that video, you can tag the video as “sports” and “family”. Those tags can be reusable and you can filter videos based on them. For that app we’ll need a Table View and CoreData along with custom classes for the “Media” entity. Since Drag and Drop functionality will also be added we’ll try to not use bindings excessively, my experience is that the more bindings you use in your app the more difficult it is to add custom functionality. The first thing we’ll do is deal with the “Media” entity. This entity will hold all the metadata for the respective media file in a NSDictionary, attributes like name, url, size, extension, dateAdded will be extracted from the dictionary during initialization to make them more accesible. If we do that we can do this:

videoObject.name
// instead of this:
videoObject.mediaItemDictionary.valueForKey(kMDItemDisplayName)!.stringByDeletingPathExtension! as String

It saves time not having to type all that just to retrieve the name of the video file from the dictionary. I will also write a simple command line driver program to test our class before we implement everything with a TableView. I’m going to create a class named “Video” to hold our media item metadata. When extracting metadata I use the CoreServices MDItem class. There are some classes in the AVFoundation library but I think that the MDItem class is a more robust approach and simple enough, in just three steps we can create our dictionary and be ready to go. Without further ado the Video class.

import Foundation
class Video {
    // Instance variables here for easy access
    let name: String
    let ext: String
    let url: NSURL
    let size: NSNumber
    let dateAdded: NSDate
    // This dictionary holds all the metadata, is kept just in case we need more data later.
    let mediaItemDictionary: NSDictionary
    // This bool is here so to help our tags later on, we can retrieve all videos that are not tagged.
    var isTagged: Bool
    // I want to retrieve all dictionary data when I write someVideo.description,
    // for this we override the object description.
    var description: String {
        return mediaItemDictionary.description
    }
    init(fileURL: NSURL) {
    /* CoreServices MDItemCreate creates our mediaItem, this is still C and we need to allocate memory for our mediaItem, we use a CFAllocator for this, the second parameter is the fileURL as a string, we use the .path so that the URL is returned as a string. This mediaItem contains all the metadata we need. */
    let mediaItem = MDItemCreate(kCFAllocatorDefault, fileURL.path)
    // MDItemCopyAttributeNames returns a CFArray with data from the mediaItem object.
    let mediaItemAtributeNames = MDItemCopyAttributeNames(mediaItem)
    // Since we don't want to have to deal with CFArrays we create a dictionary with the attributes values
    // and attribute names.
    mediaItemDictionary = MDItemCopyAttributes(mediaItem, mediaItemAttributeNames)
    // You pretty much done, all this code does is copy some basic data to our variables for accessibility.
    name = mediaItemDictionary.valueForKey(kMDItemDisplayName)!.stringByDeletingPathExtension! as String
    ext = mediaItemDictionary.valueForKey(kMDItemDisplayName)!.pathExtension! as String
    url = fileURL
    size = mediaItemDictionary.valueForKey(kMDItemFSSize)! as NSNumber
    dateAdded = mediaItemDictionary.valueForKey(kMDItemDateAdded)! as NSDate
    isTagged = false
    }
}

Our driver program is very simple, I have a file on my desktop and I will retrieve all the metadata from it. This is a console application, make sure you don’t create a Cocoa Application, we’ll do that later, for now we just want to make sure our class is retrieving all the file metadata.


import Foundation
// We create a NSURL with a file path from my computer desktop
let mediaFileURL = NSURL.fileURLWithPath("/Users/LAROD/Desktop/video_file.mp4")!
/* Now, using the mediaFileURL url we create a new "Video" object, we use let because we don't want to make changes to our object, we want to read only. After that we use the custom description var to print our dictionary. */
let mediaFile = Video(fileURL: mediaFileURL)
println(mediaFile.description) 

You should now see all the file metadata as shown below.


{
kMDItemAudioBitRate = 73;
kMDItemAudioChannelCount = 2;
kMDItemCodecs = (
"H.264",
AAC
);
kMDItemContentCreationDate = "2014-09-26 15:03:35 +0000";
kMDItemContentModificationDate = "2014-09-26 15:13:18 +0000";
kMDItemContentType = "public.mpeg-4";
kMDItemContentTypeTree = (
"public.mpeg-4",
"public.movie",
"public.audiovisual-content",
"public.data",
"public.item",
"public.content"
);
kMDItemDateAdded = "2014-10-21 13:34:49 +0000";
kMDItemDisplayName = "video_file.mp4";
kMDItemDurationSeconds = "1406.722";
kMDItemFSContentChangeDate = "2014-09-26 15:13:18 +0000";
kMDItemFSCreationDate = "2014-09-26 15:03:35 +0000";
kMDItemFSCreatorCode = 0;
kMDItemFSFinderFlags = 0;
kMDItemFSInvisible = 0;
kMDItemFSIsExtensionHidden = 0;
kMDItemFSLabel = 0;
kMDItemFSName = "video_file.mp4";
kMDItemFSOwnerGroupID = 20;
kMDItemFSOwnerUserID = 501;
kMDItemFSSize = 188905412;
kMDItemFSTypeCode = 0;
kMDItemKind = "MPEG-4 File";
kMDItemLogicalSize = 188905412;
kMDItemMediaTypes = (
Video,
Sound
);
kMDItemPhysicalSize = 188907520;
kMDItemPixelHeight = 404;
kMDItemPixelWidth = 718;
kMDItemStreamable = 0;
kMDItemTotalBitRate = 1065;
kMDItemVideoBitRate = 992;
}
Program ended with exit code: 0

1 Comment

  1. Works for me. Thanks!

    Is there an equivalent for iOS to do the above? How about updating an attribute?

    Reply

Leave a Reply