iPhone sweetness and Web Importer not-so-sweetness

March 15th, 2008

So, first about this supposed I-Phonographer SDK thing.

Ahhh, it is so cool! I was able to get it working on my non-intel iMac thanks to this. I haven’t looked at it that much, but from what I have seen and tried, it looks really amazing. I am thinking I want to make an iPhone app this summer. It might be a PhotoBook Touch that behaves just like the default Photos application. It would also have upload support, so you can snap a photo with the iPhone camera, and upload it instantly to Facebook.

It might also be something totally different. If you have any cool ideas for an application you want to see on the iPhone, let me know, and I might just make it.

Web Importer is coming along, just not as quickly as I would have liked. I have found very little time recently to work on it. I am also going to Eastern Europe for a couple weeks, which should be really fun, but will delay it even longer. So, hold tight.

I have gotten more than enough people who have asked to beta test it. I really appreciate all the support.

Since my last post, I have written an early version of the Facebook Friend plugin, and I have added plugin preferences (See a screenshot).

Also, I can confirm that “Web Importer” will not be the final name. Some other better and more creative names are coming down the pipeline :).

However, there are still just too many little things left to fix, so I don’t feel comfortable releasing a beta yet. Sorry, but I promise it will be worth it.

Web Importer Teaser

February 10th, 2008

The next generation (to be dramatic) of Google Importer is coming along nicely.
Right now, it is code named Web Importer. Whether or not that becomes the final name is dependent solely on if some sudden stroke of creativity hits me (or if anyone else has suggestions, in the spirit of its future fate of being open source).

So, here’s what is done:

  • The backend (minus a few bugs)- Spotlight menu and Finder searches work, the plugin system is ready, results are saved to disk, Spotlight finds them and imports the metadata, etc.
  • The Yahoo! Web Search Plugin
  • The Quick Look Generator- you can use Cover Flow to see thumbnails of all the results, including the logo for the service, the title, and a description. Hit space and you get all the detailed information about the result. Then, double click on the file and it opens instantly in your web browser.

And here is what is left:

  • Preferences. I have only worked on the basics, but all plugins will be fully customizable and extendable.
  • Plugins. Aside from the Yahoo web search plugin, I am planning on shipping it with at least Yahoo image search and Facebook friend plugins
  • All the polish and little details…

So, I am hoping to have a near feature complete beta available soon. Like really soon. Probably. :)
If you are interested in being among the first to try it out, just send me an email (support [at] caffeinatedcocoa [.dot] com)

And what would a teaser post be without some screenshots? (Click for full size)

Web Importer Teaser- Spotlight Menu

Web Importer Teaser- Finder Search

Web Importer Teaser- Quick Look

BREAKING: Caffeinated Cocoa Endorses Barack Obama

February 4th, 2008

After Daniel Jalkut’s endorsement of Barack Obama and its appearance on the front page of the New York Times, I thought I would follow up with my own opinion. On behalf of everyone at Caffeinated Cocoa (i.e., me) I strongly believe Barack Obama is the best candidate for president.

Obama can bring about real change in this country. He is not talking only about policy change (which I’m sure Hillary could do quite well). He will also change how politics works in Washington. No other candidate has, in recent memory, talked about change like he does.

Even Steve Jobs secretly supports Obama. Seriously, he decided to hide clues all around Apple products. For example, try typing this into Terminal:

echo -e '\0126\0117\0124\0105\n\0117\0102\0101\0115\0101'

(the numbers correspond to the serial of the yet unreleased Apple Tablet I am typing/tapping this on.)

I guess every bit helps….

I’ll be waiting for a call from Obama inviting me to travel on the campaign trail with him and Kennedy…..

Accessibility for The Rest Of Us

January 26th, 2008

So, in addition to giving you an update on Google Importer, it is about time for another Cocoa post.

Google(Web) Importer for Leopard makes extensive use of the Accessibility API. This may seem a bit weird. Most people would think it would be for things like screen readers and speech recognition, but it can be used for a lot more. For example, the Watch Me Do feature in Automator that lets you record and replay every action you do in an application uses the Accessibility API. Google(Web) Importer will use it to get the search terms from Spotlight and the Finder.

The Accessibility API is great for getting the state of the GUI, such as the location of the mouse, the text typed into a field, or the button a user clicks. It is also great for controlling the GUI, like actually clicking a button or entering text. So, it can really be used in a lot of applications.

(By the way, the front end to the Accessibility API in Applescript is called UI Scripting. Same idea.)

Also, unlike Input Managers (which Google Importer for Tiger used), using the Accessibility API:

  • Is fully supported and documented
  • Works with practically every application (not just Cocoa apps)
  • Should not break between releases, if coded carefully

Now, I don’t think it is necessary to write about how to use the API. It is a C API, but just read the documentation and look at this sample code that demonstrates just about everything.

Instead, I will focus on something else that probably will be purely for the benefit of the mighty search engines…

Aside from being really useful, the Accessibility API is also really powerful and therefore subject to abuse. For example, one could very easily write a keylogger with it. That is probably why Apple decided to disable it by default. To enable it, the user has to open System Prefrences, click on Universal Access, and check the box labeled “Enable access for assistive devices.” Now, there are a couple problems with that approach:

  • It enables access system wide (or user wide, I’m not sure). That means that any application can use it if that little box is checked. In the keylogger example, if the user checked that box to let Automator use it legitimately, the keylogger can also use it without any sort of notification to the user.
  • It is annoying. A user should not be asked to change their preferences just to run your application.

So, Apple came up with another solution in Tiger that solves these problems: the magic function AXMakeProcessTrusted. That will enable the API just for your application and needs to be called from a process running as root, so it is secure. It is also all automatic, so aside from asking the user for his/her password, there is nothing the user needs to do.
The problem is that it seems no one uses it. Every third party application I have seen and even Automator just asks the user to manually check the box in System Preferences. It is more work to implement and has one huge undocumented bug (the application must be relaunched before it is actually trusted Update: reported as #5711990), but I really think people should be using it. So, I thought I would release the code to make it easy to implement it in your app. It includes a helper agent that you should be able to just drop into your project.

Download TrustMe

Google Importer. Is it vaperware? Find out at 11.

January 26th, 2008

Yeah, so about this Google Importer for Leopard business.

I am done with college apps and I am now officially a second semester senior! Theoretically, that means more time for programming.
I have started working on it, and it seems to be progressing nicely. Once it is done, I plan to make it completely open source and hopefully have a plugin architecture so developers can make it search information from any website. I am also planning on dropping Google and switching to Yahoo, since they have a much more powerful API (that also means Google Importer will be renamed to something like Web Importer).

So, unfortunately I still cannot tell you when it will be done. I would really appreciate it if people would let me know if they like the path I am taking. If there is a lot of interest for just getting something out, I would be willing to rush a release that has none of these new features, but works on Leopard. Send pleas and candy-flavored bribes to support [at] caffeinatedcocoa[dot]com, or post a comment below.

Hurrays For Winter Break!

January 4th, 2008

So, I released PhotoBook 1.1 today. 

That’s right, I finally got some time away from school, college apps, and friends (ha) to code! This release has a few major new features, a bunch of little changes of additions, and a whole lot of bug fixes. Most notably, you can now open almost any Facebook photo or album URL in PhotoBook, and I redesigned the photo viewing window to add easier to use slideshow controls. 

The vast majority of changes in this release were based on suggestions from users like you. So, keep sending me your ideas to support  at caffeinatedcocoa dot com. 

I know a lot of people have been asking about Google Importer for Leopard. I have started working on it, but unfortunately it requires a complete rewrite and Apple didn’t add in a nice API to make it easy. You should be learning more about the project soon. 

By the way, I got an iPod Touch and it is incredible. I can’t wait until February when Apple opens it up to developers. I’m hoping that means all developers, including starving (pre-)college students.  If so, I think PhotoBook Mobile or whatever would be really cool. Let me know what you think.

Caffeinated Cocoa + Leopard

October 27th, 2007

Leopard is out!
I never got the beta, so I am pretty excited to finally have it. As Walt Mossberg put it, Leopard is a evolutionary, not a revolutionary new OS. I would consider only 10.0 and 10.4 to fall under the latter. In my opinion, though, Leopard is the best evolutionary release ever, and exactly what Apple needs. Tiger added a lot of huge features, but lacked polish and consistency. Leopard pretty much leaves no part of the OS untouched. It seems like most every detail has been rethought. John Gruber gives a good example of this. That means a lot of small features will likely be hard to find, but will make the overall experience of using a Mac considerably better. 
But enough of that, there are plenty of Leopard reviews out there. On to my software.

PhotoBook

Good news! It looks like PhotoBook 1.0.2 runs great on Leopard. I am pretty surprised that in my early testing, I did not find any new problems. I probably just need to tweak some of the colors to better match the new window style, and drop in a 512×512 icon for funness in CoverFlow. Thanks Apple for such a clean upgrade.

There are so many new features I can add to PhotoBook that are made possible in Leopard, so I intend to start requiring 10.5 pretty soon. Think better slideshows, smart folders, animations, iChat Theater sharing, etc. Therefore, I will probably put out just one more major release for Tiger before starting to add some of these new features. 

Google Importer 

Bad news (Sorta)! Google Importer 1.0.1 does not work on Leopard. The way things were set up in Tiger required that it use a pretty ugly hack, so it is no surprise it is broken in Leopard.

Google Importer used an Input Manager to “plug in” to Spotlight, get the search terms, and add the Google results to the list. Apple has decided to stop supporting Input Managers in Leopard, so Google Importer never runs for a search.Input managers were a really easy, powerful tool for developers to extend applications without special plugin support. Applications that add features to iChat and Safari, for example, used input managers. The problem is that someone can just as easily build one to take control of an application, without the user ever knowing what is going on. This poses a big potential security threat, so it is no surprise Apple decided to get rid of them all together. (UPDATE: Oops, I guess they are still loaded in Leopard, but with a bunch of added restrictions. Also, there is a release not saying they are not supported, and might not be loaded in the future)

I am confident that there will be another, better way to get Google Importer to work in Leopard. I am so busy with school and college apps that it is unlikely you will see an update soon, but I will try to get to it eventually. 

So, congrats to all the Apple developers who worked on Leopard. While it does still have some problems, I really encourage everyone to consider upgrading. 

Image Metadata

October 6th, 2007

Digital cameras are really cool in that whenever you take a picture, it saves a lot more than just the image data. Right in the image file, it adds the date it was taken, the length of the flash, the exposure time, the model of the camera, and much more. If you have an expensive camera with GPS, it will even save the location at which the photo was taken. 

Most of this information is stored with EXIF (Exchangeable image file format). This is a pretty much universally adopted standard, so most cameras and photo programs use and save the data. iPhoto and Preview, for example, allow you to view this information easily by opening the Info window.

The problem is that Cocoa does not currently have great support for EXIF. Also, the support it does have is horribly documented. If you are working with photos in a cocoa app, however, it is important to understand and support it. Even if you have no intention to work with the EXIF data at all, if you mess with the image data, you need to make sure you preserve all the metadata when you save the image. Depending on what you do, this may not happen automatically.

To work with EXIF in a cocoa app, you will need to dive into Carbon a bit, but it is really not that bad. Also, note that the code in this article requires Tiger or later. You will need to use another tool to get metadata out of images if you need to support Panther.

Finally, I hear Leopard will have a new framework for working with images, ImageKit. This might make some of this stuff easier to do. I don’t know, as I am not lucky enough to have the beta :)

Getting Metadata

So, most commonly, you just want to read the metadata from a photo and use in some manner. Magrathea, for example, would see if any photo you tried to import contained GPS EXIF properties, and if so, place the photo on the map automatically for you.

You will need to use the ImageIO framework, so first add ApplicationServices.framework to your project. Then, import it into your file.

Next, you need to create a CGImageSourceIf you are reading the image from disk (or the internet) use:

 CGImageSourceRef source = CGImageSourceCreateWithURL( (CFURLRef) aUrl, NULL);

Note that, thanks to Toll-Free Bridging, aUrl can be a NSURL, even though the function takes a CFURLRef. There is no need to convert the URL first. The cast just calms the compiler down so it doesn’t throw a warning.

If you already have the image as a NSImage, you will need to get the data out of the image first. The easiest way to do this is just to use -TIFFRepresentation, and that will give you a NSData object you can use. Then, create a CGImageSourceRef with:

 CGImageSourceRef source = CGImageSourceCreateWithData( (CFDataRef) theData, NULL);

Again, Toll-Free Bridging allows you to pass the NSData object right in.Now, you can get a NSDictionary with all the image’s metadata:

 NSDictionary* metadata = (NSDictionary *)CGImageSourceCopyPropertiesAtIndex(source,0,NULL);

Toll-Free Bridging works both ways. Yay!

Now, you are pretty much done with Carbon and you can start getting the metadata you want.

All the keys in the metadata dictionary are listed here.

So, say you want to get the date the photo was taken. You would just use the key kCGImagePropertyExifDateTimeOriginal

(Note that all the keys are CFStringRef’s, so you should cast them to an NSString * before using them with NSDictionary)

You might wonder what the format of the date is. Well, just look what the documentation says:kCGImagePropertyExifDateTimeOriginalSpecifies the original date and time.

Thanks! In fact, none of the keys have any documentation on what the hell they actually contain.

Most of it should be pretty self explanatory, but if you are unsure you can check the spec (the good stuff starts on page 17) Core Graphics uses the same terminology for the names of the keys, so it is not that hard to find.Also, something important to note, is that unless the documentation says otherwise, all the properties are returned as CFStringRef’s/NSString’s. That means you will need to do a little extra work if you want to use a property that contains a date or a number.

So, with dates for example, the spec says dates are returned in the format YYYY:MM:DD HH:MM:SSThis is slightly different than the format NSDate expects (YYYY-MM-DD HH:MM:SS ±HHMM). Here is some quick code to convert it to that format (unformattedDateAsString is the NSString returned from the metadata dictionary): 

NSDate date = nil;
*NSMutableString *unformattedDateAsMutableString = [[unformattedDateAsString mutableCopy]autorelease];
//make sure the date stored in the metadata is not nil, and contains a meaningful date
if(unformattedDateAsMutableString && ![unformattedDateAsMutableString isEqualToString:@""] && ![unformattedDateAsMutableString isEqualToString:@"0000:00:00 00:00:00"])
{

    //the date (not the time) part of the string needs to contain dashes, not colons, for NSDate to read it correctly
    [unformattedDateAsMutableString replaceOccurrencesOfString:@":" withString:@"-" options:0 range:NSMakeRange(0, 10]; //the first 10 characters are the date part

    //the EXIF spec does not allow the time zone to be saved with the date,
    // so we must assume the camera’s clock is set to the same time zone as the computer’s.
    [unformattedDateAsMutableString appendString:@" +0000"];

    date = [NSDate dateWithString: unformattedDateAsMutableString];

}

And finally, don’t forget to release the metadata dictionary and the image source when you are done.

Setting Metadata

You may also want to add metadata to an image, for instance the program that created the image. Or, in the case of PhotoBook, I add the date a photo was uploaded to facebook in the metadata before adding it to iPhoto.

It is also really important that you preserve any metadata that has already been set for an image when you add new properties.

To start, you go through the same process to get the metadata. Create a CGImageSource using one of the methods above and get the metadata as a dictionary with CGImageSourceCopyPropertiesAtIndex

Now it gets a bit more complicated.First create a mutable copy of the metadata dictionary and make any changes you’d like (add properties, override properties, etc.) You can use all the same keys listed here (just make sure you cast them to a NSString* first).

Note that in order to be useful, all the values you set must be NSStrings (unless otherwise documented) and conform to the format of the spec

Next, you create a CGImageDestination with some path or data where you want the modified image to be written. Use CGImageDestinationAddImageFromSource(destination,source,0, (CFDictionaryRef) metadata); to add the image you are working with to the destination, and override the old metadata with the modified metadata. Finally, you call CGImageDestinationFinalize(destination); to either write to data or write to file (depending on what you initialized the destination with).

Instead of showing little code samples of all of these steps, I think it is better to give one big example that demonstrates everything.

This method takes in a NSDate and a path to an image file, and sets the date as the date digitized in the metadata. It then writes the modified image back to its original path. It also demonstrates how to convert a NSDate into a string that is the right EXIF format. Enjoy!

Available for download here.

- (BOOL)setDateDigitized:(NSDate *)date forPhotoWithURL:(NSURL *)URL;
{
    CGImageSourceRef source = CGImageSourceCreateWithURL( (CFURLRef) URL,NULL);
    if (!source)
    {
        NSLog(@"***Could not create image source ***");
        return NO;
    }
    
    //get all the metadata in the image
    NSDictionary *metadata = (NSDictionary *) CGImageSourceCopyPropertiesAtIndex(source,0,NULL);
    
    //make the metadata dictionary mutable so we can add properties to it
    NSMutableDictionary *metadataAsMutable = [[metadata mutableCopy]autorelease];
    [metadata release];
    
    NSMutableDictionary *EXIFDictionary = [[[metadata objectForKey:(NSString *)kCGImagePropertyExifDictionary]mutableCopy]autorelease];
    
    if(!EXIFDictionary)
    {
        //if the image does not have an EXIF dictionary (not all images do), then create one for us to use
        EXIFDictionary = [NSMutableDictionary dictionary];
    }
    
    
    //we need to format the date so it conforms to the EXIF spec and can be read by other apps

    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc]init];
    [dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
    [dateFormatter setDateFormat:@"yyyy:MM:dd HH:mm:ss"]; //the date format for EXIF dates as from http://www.abmt.unibas.ch/dokumente/ExIF.pdf
    NSString *EXIFFormattedCreatedDate = [dateFormatter stringFromDate:date]; //use the date formatter to get a string from the date we were passed in the EXIF format
    [dateFormatter release];
    
    [EXIFDictionary setObject:EXIFFormattedCreatedDate forKey:(NSString *)kCGImagePropertyExifDateTimeDigitized];
    
    
    //add our modified EXIF data back into the image’s metadata
    [metadataAsMutable setObject:EXIFDictionary forKey:(NSString *)kCGImagePropertyExifDictionary];
    
    CFStringRef UTI = CGImageSourceGetType(source); //this is the type of image (e.g., public.jpeg)
    
    //this will be the data CGImageDestinationRef will write into
    NSMutableData *data = [NSMutableData data];
    
    CGImageDestinationRef destination = CGImageDestinationCreateWithData((CFMutableDataRef)data,UTI,1,NULL);
    
    if(!destination)
    {
        NSLog(@"***Could not create image destination ***");
        return NO;
    }
    
    //add the image contained in the image source to the destination, overidding the old metadata with our modified metadata
    CGImageDestinationAddImageFromSource(destination,source,0, (CFDictionaryRef) metadataAsMutable);
    
    //tell the destination to write the image data and metadata into our data object.
    //It will return false if something goes wrong
    BOOL success = NO;
    success = CGImageDestinationFinalize(destination);
    
    if(!success)
    {
        NSLog(@"***Could not create data from image destination ***");
        return NO;
    }
    
    
    //now we have the data ready to go, so do whatever you want with it
    //here we just write it to disk at the same path we were passed
    [data writeToURL:URL atomically:YES];
    
    //cleanup
    CFRelease(destination);
    CFRelease(source);
    
    return YES;
}   

YABFR

September 22nd, 2007

PhotoBook 1.0.2 is out!

Hurray! I hope you are excited as me!

Sorry for Yet Another Bug Fix Release, but I needed to get some things fixed, and there are a few new minor features in there.

One bug in particular bugged the hell out of me. The “back” button on your browser has been remotely disabled to ensure you will listen to this pointless story.
A very small number of users were either getting an Unknown error when logging in, or were getting stuck on loading screens. This small subset users just happened to include people reviewing apps for facebook, which is why PhotoBook is not currently in the Application Directory

It was truly a bug like Steven Frank writes about.
I, of course, could not reproduce it myself, but a patient user with the problem offered to help track it down (thanks!)

Basically, I make a request to facebook using their REST API (with their awesome pseudo-SQL language FQL) and it sends back the response in XML. I then parse this XML with NSXMLDocument.

The “unknown error” indicates the NSXMLDocument is nil, and somehow no error from the download, facebook, or the NSXMLDocument was returned.

So, going down the list, I first assumed the FQL query was bad (maybe some object used in the query was returning nil and facebook freaked out and returned no data). Nope, the query looks fine.

Next, I thought the problem might be on facebook’s side, and it was returning no data. Nope, it returns something that looks like XML.
So, I guess the XML might be invalid. I get a sample response from the user (as a NSString) and feed it into a NSXMLDocument. It is parsed fine.
Maybe there is something wrong with my download code? I stick the data hidden on a server, and try to download it with the same code. Works fine, and is parsed correctly.
Finally, through some trickery, I get facebook’s servers to return the same data. This data looks like the same, but is actually a few bytes longer. I feed it into NSXMLDocument, and yay! It returns nil, and it does not even set the NSError object I pass to it (even stranger).

So, finally, I set the option NSXMLDocumentTidyXML and everything works fine.

I still have no idea why (a)facebook is returning a few extra bytes or (b)NSXMLDocument returns nil without setting the error object I pass to it with anything. But whatever. I guess the lesson is never trust any data you do not have complete control over. Programming 101, but whatever.

I have had plenty of other problems with trusting data too much (FQL randomly not returning everything I ask from it, queries that are too long for GET and return 414-Request URI too long). It is still loads of fun.

I promise some interesting cocoa posts soon, and lots of cool features for PhotoBook 1.1.

PhotoBook

September 10th, 2007

So, two weeks ago, I released a beta of PhotoBook, a really easy to use, mac-like application for viewing facebook photos.

Since that time, I have released 1.0 and 1.0.1 (that was fast).
PhotoBook was featured on Apple Downloads and is somehow the #8 top download right now.
There is also a little post about it on TUAW
Cool! Thanks to everyone who has helped spread PhotoBook and has given me feedback.

I am in school now, so that means I will be a bit (i.e., a lot) slower in releasing updates and responding to emails. Sorry about this.

I have lots of plans for features to add, but if you have any ideas, please email me (support [at] caffeinatedcocoa [dot] com) or leave a comment here.