In the itunes search api doc there is an example of searching for an artist called maroon and the url is like so:
https://itunes.apple.com/search?term=maroon&entity=allArtist&attribute=allArtistTerm
This returns over 50 results that start like this:
{
"resultCount": 50,
"results": [
{
"wrapperType": "artist",
"artistType": "Artist",
"artistName": "Maroon 5",
"artistLinkUrl": "https://itunes.apple.com/us/artist/maroon-5/id1798556?uo=4",
"artistId": 1798556,
"amgArtistId": 529962,
"primaryGenreName": "Pop",
"primaryGenreId": 14,
"radioStationUrl": "https://itunes.apple.com/station/idra.1798556"
},
{
"wrapperType": "artist",
"artistType": "Software Artist",
"artistName": "MaroonEntertainment",
"artistLinkUrl": "https://itunes.apple.com/us/artist/maroonentertainment/id537029262?uo=4",
"artistId": 537029262,
"radioStationUrl": "https://itunes.apple.com/station/idra.537029262"
},
Which is nice. However here is my problem: I would like to create a search query that is as specific as possible by combining the search for both an artist and a song name and an album name..
So for example I got this song:
I can search for the artist name only:
https://itunes.apple.com/search?term=Semisonic&entity=allArtist&attribute=allArtistTerm
I can search for the song term only:
https://itunes.apple.com/search?term=Across the Great Divide&entity=song&attribute=songTerm
I can search for the album name only:
https://itunes.apple.com/search?term=Great Divide&entity=album&attribute=albumTerm
However none of these guys give me the result i want (i can find the result i'm looking for amongst maybe 50 others.. but i just want the search query to be specific enough to avoid any client side filtering kind of thing).
How can I combine these searches? if I simply add two searches together (in this example i'm searching for both song and artist):
https://itunes.apple.com/search?term=Across the Great Divide&entity=song&attribute=songTerm&term=Semisonic&entity=allArtist&attribute=allArtistTerm
then apple will simply ignore the first search type (ie song) and return the results for artist only).
ideas?
Well this is more of a "workaround" answer.. but it's the solution I'm using.. so might as well spread the love eh?
This is a 100% client side solution (ie the entire database of itunes music can be downloaded into my own server.. then I can create all sots of search wrappers around it.. but that's a project in itself).
this is what i got:
// this is just a wrapper around the apple search api.. it makes your
// average joe http get request
[[AppleServer shared] searchForSongWithTitle:track.title andAlbumName:track.albumName completion:^(NSArray *results, NSError *error){
if ([results count] >0) {
NSLog(@"[%d] unfiltered songs retrieved from apple search api", [results count]);
NSDictionary *filteredResult = [[self class] filterResults:results ToMatchTrack:track];
if (!filteredResult) {
NSLog(@"Filtering may be too strict, we got [%d] results from apple search api but none past our filter", [results count]);
return;
}
.. process results
+ (NSDictionary *)filterResults:(NSArray *)results ToMatchTrack:(VBSong *)track
{
NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(NSDictionary *evaluatedTrack, NSDictionary *bindings){
BOOL result =
([track.title isLooselyEqualToString:evaluatedTrack[@"trackName"]] &&
[track.artistName isLooselyEqualToString:evaluatedTrack[@"artistName"]] &&
[track.albumName isLooselyEqualToString:evaluatedTrack[@"collectionName"]]);
NSLog(@"match?[%d]", result);
return result;
}];
return [[results filteredArrayUsingPredicate:predicate] firstObject];
}
the key method here is isLooselyEqualToString
.. it's defined in an NSString category like so:
/**
* Tests if one string equals another substring, relaxing the following contraints
* - one string can be a substring of another
* - it's a case insensitive comparison
* - all special characters are removed from both strings
*
* ie this should return true for this comparison:
* - comparing self:"Circus One (Presented By Doctor P and Flux Pavilion)"
and str:"Circus One presented by Doctor P"
*
* @param str string to compare self against
* @return if self is the same as str, relaxing the contraints described above
*/
- (BOOL)isLooselyEqualToString:(NSString *)str
{
return [[self removeSpecialCharacters] containSubstringBothDirections:[str removeSpecialCharacters]];
}
/**
* Tests if one string is a substring of another
* ie this should return true for both these comparisons:
* - comparing self:"Doctor P & Flux Pavilion" and substring:"Flux Pavilion"
* - comparing self:"Flux Pavilion" and substring:"Doctor P & Flux Pavilion"
*
* @param substring to compare self against
* @return if self is a substring of substring
*/
-(BOOL)containSubstringBothDirections:(NSString*)substring
{
if (substring == nil) return self.length == 0;
if ([self rangeOfString:substring options:NSCaseInsensitiveSearch].location == NSNotFound) {
if ([substring rangeOfString:self options:NSCaseInsensitiveSearch].location == NSNotFound) {
return NO;
} else {
return YES;
}
} else {
return YES;
}
}
- (NSString *)removeSpecialCharacters
{
NSMutableCharacterSet *specialCharsSet = [[NSCharacterSet letterCharacterSet] mutableCopy];
[specialCharsSet formUnionWithCharacterSet:[NSCharacterSet whitespaceCharacterSet]];
return [[self componentsSeparatedByCharactersInSet:[specialCharsSet invertedSet]] componentsJoinedByString:@""];
}
Bonus This is the solution we are currently using.. I'm fully aware that some terms may come up that break this algorithm.. so we have a unit test for this that we incrementally add terms to ensure we keep on improving our algorithm while not causing regression bugs.. i'll post it if i get enough votes on this answer heh.