Jay Tuley

Posts tagged with programming

My Sunday Project - Reusable Cocoa Script Menu by Jay

The Sunday Project

So last sunday I started on something new, really it’s a feature for NicePlayer, but also a feature in a lot of other existing apps out there, and could be useful in a lot of cocoa programs that don’t have this feature yet, so I wrote my implementation as an embedable framework, and am releasing under the MPL/LGPL/GPL (my latest preferred OSI approved license for those who notice what license I release under).

So here’s a riddle, what do iTunes, DVD Player, Xcode, FlySketch, NetNewsWire, MarsEdit and BBEdit all have in common?

Give up?

They all have one of these (more or less):

Their own script menu.

Uses in NicePlayer

Having a script menu in NicePlayer has been something in the back of my mind for a while. There are some features requests, while really simple, are very specific to individual user needs and we can’t justify adding a feature. Sometimes there are features requests that just don’t fit into Robert’s or my idea of NicePlayer, and we barely have enough time to add the features we want to add, so in the next release users can add their own menu commands in this script menu.

One of the features introduced in 0.92 of NicePlayer was an option to remove the fixed aspect ratio. Maybe you want to distort the movie, maybe the aspect ratio is just slightly off, this feature works in those cases, and only adds one more menu to the window and is inline with adding basic window options in the Window menu that we did before. However this isn’t useful when someone has a lot of media that is consistently using non-square pixels. In the case of standard media formats, the proper behavior of NicePlayer should be to automatically adjust (this feature has been added for the DV codec when using the CoreVideo plugin in the next version of NicePlayer 0.93). However there are people out there for some reason, how have media that is encoded with non-square pixels for no standard reason, and there isn’t a way to detect the correct aspect ratio. It turns out writing an AppleScript with the current NicePlayer dictionary to set a Window to a different aspect ratio is pretty trivial, so this is one of the scripts we’ll likely include in the next release (since its one the scripts i’ve been using to test anyway).

I think the main benefit of having the script menu, will be nicer integration with other apps. Whether having it integrate with a cataloging app, Toast, or just organizing with the finder, there seem to be many potential uses in this respect.

CocoaScriptMenu.Framework in Your Own Program

The framework is called CocoaScriptMenu.Framework and available on my software page. It’s not my favorite name of the software I’ve written, but I think it’ll help in being google-able for those wanting it’s feature. I’m releasing it as version 1.0, and as I said before under the MPL/LGPL/GPL license. The basic way to use it is to:

  1. Add the framework binary to your project
  2. Add the framework to a copy phase that puts it into the Frameworks folder of your app bundle
  3. Add this line [[CSMScriptMenu sharedMenuGenerator] updateScriptMenu]; somewhere right after the nib loads (while including <CocoaScriptMenu/CocoaScriptMenu.h> for that file of course)

And that will give you a typical script menu once you compile and run your app.

Features

I wrote typical, as not all script menus are the same. I tried to add what I felt were the best features of all the script menus, while being able to behave, depending on how you use it, like 80%25 of the script menus I’ve seen with out any extra customization. Some of the features are

  • It looks for scripts in Application Support/AppName/Scripts in all domains.
  • You can make submenus by nesting folders.
  • You can add a menu separator by adding a file or folder with a dash as it’s name.
  • You can order scripts, separators, and folders using a two digit prefix on the file name (two digit prefix not required).
  • It runs AppleScripts, Automator Files (.workflow), Application Bundles, and Shell Scripts (sh/python/perl/whatever but remember you need executable permission for shell scripts).
  • It automatically updates the menu, without relaunching the application, as you add items or folders nested inside the script folder, unless that folder didn’t exist at Application Launch.

Extending w/o Modifying

The singleton [CSMScriptMenu sharedMenuGenerator] has 4 optional delegate methods that allow you to keep the core functionality but make some slightly different script menus without having to modify the source (although modification is certainly an option. (warning most of this is untested as I use only the default implementation of each of these in NicePlayer)

-(NSMenuItem*)showScriptFolderMenuItem;

If your delegate implements this method, it needs to return a NSMenuItem that should be used instead of the Open Scripts Folder menu item. An example would be a menuitem with a submenu with an open command for each each script folder in the domains. The default behavior is a menuitem to open the first location returned by the delegate method -(NSArray*)scriptLocations; in the finder.

-(NSArray*)argumentsForShellScripts;
The default implementation returns nil. Implement this method in your delegate if you want to pass string arguments to any shell script. (is not used for AppleScripts, Application Bundles or Workflows)

-(id)scriptMenuItemOrItems;
This should return something that has a subMenu or menu getter for an NSMenu. This NSMenu is what the script menu items get added too. You may also return an array to add the script menu in multiple places in your app. The default implementation adds a Script Menu in-front of the Help menu and returns it.

-(NSArray*)scriptLocations;
The default implementation, returns the paths to Application Support/MainBundleName/Scripts for every domain that the path exists, with the User domain guaranteed first. Also creates the folder for the user domain if it doesn’t exist.

Extending by Modifying

So in version 1.0 the script running implementations are very basic. They are setup as a Class Cluster, with the CSMCommand class providing the public interface and several subclasses that implement script running for various types of scripts or executables.

Diagram of class hierarchy:

alloc on CSMCommand returns a singleton instance of CSMPlaceholderCommand. CSMPlaceholderCommand’s initWithScriptPath: works as parameterized factory method and depending on the path passed in returns the correct concrete implementation allocated and initialized.

So to extend the implementation of script execution for an existing file type, you would just modify one of the subclasses. To add a new filetype you would modify the initWithScriptPath: factory method and add a new subclass.

initWithScriptPath: uses Apple’s Uniform Type Identifiers to determine which concrete class to instantiate, so order does matter, make sure you add the more specific type checks in the beginning of the method and the more general towards the end.

There are a lot of ways that I can think of that the concrete script execution classes could be improved, however for NicePlayer these all work well, thus I figured for version 1.0 it was better to stick with the basic implementation and release it now, rather than try and over engineer. So I’ll wait and see if people need more or not, not to mention they have the option of contributing code.

Updated at Cocoa Script Menu Revised 1.01

iTunes Music Library.xml by Jay

So someone asked me a question, and I’ve had this question before, so I decided to answer it in my blog.

Tom asks:

One thing that’s been confusing me for awhile is locating the XML file for the iTunes library. The iTunes library can be in an arbitrary location, so relying on it being in ~/Music/iTunes/ is bound to fail for anyone who relocates their library.

My question is, in iEatBrainz, how do you extract the location of the iTunes library from the iTunes preferences, which are guaranteed to be in a defined location. From looking at it, iTunes seems to encode a Classic-style ‘alis’ resource into the data fork, but I can’t figure out how to turn that back into something I can manipulate from Cocoa.

It’s a pretty good question, when I originally looked into locating the iTunes Music Library.xml file (for the Music Recommendation System), I also was under the impression that the file could be in arbitrary places just like Tom. However, I soon discoved that there are only specific places that file can exist. It had only seemed like this wasn’t the case because the default location for iTunes Music Folder is the same as where your library files need to be. So if you look for the library file at ~/Music/iTunes/iTunes Music Library.xml, you’ll be okay even if the Music Folder is set to be somewhere else.

So that’s easy enough, but wait, you may now be wondering did If I really mean “places” when I wrote “specific places” and indeed there is another place your library could be located, ~/Documents/iTunes/iTunes Music Library.xml. iTunes checks in precedence for the iTunes folder, first ~/Music, if it doesn’t find it there it checks in /Documents. If all you have is the iTunes folder /Documents it will use the library there and save and update it, if you have both, it will use ~/Music. If you have neither it create a folder in ~/Music. Another thing you should do to make sure that you find the iTunes Music Library.xml file, is because some people tend to movie things around with aliases, you need to resolve aliases in that path, you can do that with this code from Apple.

So that’s basically all you have to do to find the iTunes Music Library.xml file. And if for some reason you program still can’t find the xml file you can pop up an open dialog box.

There are some other notes about the xml file worth noting, it’s periodically updated, and seems to be in place for Cocoa apps like iMovie, iPhoto, and iDVD, however none of them check for the file in ~/Documents (so if you look in the the secondary location, you and iTunes are already one step in sync better than Apple’s own iApps). The tracks are also numbered inside the XML with a Track ID, and in AppleScript they are referred to as “Database ID”, these values aren’t constant, and every so often they get renumbered, it’s worth noting as this has been the bane of iEatBrainz in the past and present.

Finally, while it doesn’t concern most, in my opinion there is a major bug regarding the dates in the XML file. The XML file uses the GMT offset for the daylight savings time that you are currently observing, not the offset for when that date occurred in time.

So on this day of posting I’m currently not observing daylight savings time. So that’s 6 hours behind GMT where my thinks it’s located.

Song A was added in November (not DTS, thus 6 hours behind)
2001-11-27T01:35:27Z (-600 = 7:35:00 PM) in XML
Monday, November 26, 2001 7:35:27 PM in Applescript and GUI

Which is right, everything matches, however:

Song B with a Data added in July, thus in DTS
(5 hours behind GMT in my location)
2001-07-18T20:24:29Z (-500 = 3:24:29 PM) in XML
Wednesday, July 18, 2001 2:24:29 PM in Applescript and GUI

That’s wrong! the XML doesn’t match up with the GUI and AppleScript!

If I change my current time to a day in daylight savings time:
2001-07-18T19:24:29Z (-500 = 2:24:29 PM) in XML

iTunes rewrote the date, and now the time matches the GUI, however all the non DTS dates now don’t match!

It’s just frustrating, I filed a bug report with Apple last June, but I’m not sure if they care as iMovie, iDVD, and iPhoto, don’t care about dates.

So yeah, I guess that’s the iTunes Music Library.xml file in a nutshell.

iEatBrainz -- the status, Introducing iEatMeta by Jay

So it feels like with each new version of iTunes, the hackish applescript retagging gets less effective. It motivated me to dig deeper in finding out about how meta information is stored in the M4A files. I found out it was pretty easy to parse (ATOMS are amazing) and I had a test app, and figured out all the atoms that I would need to know to read the equivalent of what iTunes allows you to edit. After that i decided I’d try getting mp3 id3 tags using a library. So I wanted something that i could dynamically link to (with out making my code GPL) and low level to get iTunes specific tags, thus I used id3lib, which seems work really well. I’m a little concerned that projects seem to moving from this to libid3tag, however, the only complaints I’ve read are that it’s “over engineered” which makes me feel a little better. I have a library now, iEatMeta.Framework, that reads metadata, and I’m currently working on writing metadata, and hopefully it won’t be too long before I get it how i want it, and I release it under MPL/LGPL. This framework would be the foundation for redesigning iEatBrianz.

iEatBrainz applescript to add iTunes selection by Jay

I received an email a little earlier asking what iEatBrainz applescript commands would allow you to add songs from an iTunes selection. iEatBrainz only has a applescript dictionary because it’s an Apple Script Studio app with a lot of Obj-C and just a small bit of applescript. However, since the applescript dictionary encompasses almost all of cocoa, it wasn’t too hard to write a script that takes the currently selected song in iEatBrainz and add it to the tagging list (As long as you know what methods to call).

Open this script in Script Editor

on addSongWithDatabaseID(aDatabaseID, aLibrary, aTaggingController)
    
tell application “iEatBrainz”
        
set aTrack to call method “trackForTrackID:” of aLibrary with parameter (aDatabaseID as string)
        
call method “addiTunesTrack:” of aTaggingController with parameter aTrack
    
end tell
end
addSongWithDatabaseID

tell application “iEatBrainz”
    
set myLibrary to call method “musicLibrary” of call method “mediator” of call method “delegate” of application
    
    set
taggingController to call method “musicMatchings” of call method “mediator” of call method “delegate” of application
end tell

tell
application
“iTunes”
    
set these_tracks to the selection of browser window 1
    —
if no selection
    
if these_tracks is {} then error “No tracks are selected in the front window.”
end tell

repeat with
i from 1 to number of items in these_tracks
    
set this_item to item i of these_tracks
    
tell application “iTunes”
        
set dbID to database ID of this_item
    
end tell
    
addSongWithDatabaseID(dbID, myLibrary, taggingController)
end repeat

This still will have problems on occasion when iEatBrainz gets out of sync with iTunes, due to iTunes renumbering its database.

Oh for Joy! For Joy! Pie Menus! by Jay

So I was looking through the cocoa blogs on Cocoa Dev and I found this post Apple Pie . This guy is actually implementing pie menus, and they look rather nice too. I don’t know how many MacWarriors meetings someone would someone would bring up pie menus (Robert or Mark as I recall, one or both of them are obsessed more than I). Anyway VERY COOL!!!!