(* ::Package:: *) (* ::Title:: *) (*Flickr*) (* ::Author:: *) (*Rob Raguet-Schofield*) (*ragfield@gmail.com*) (* ::Section:: *) (*Initialization*) BeginPackage["Flickr`", {"JLink`"}]; FlickrAuthorize::usage = "FlickrAuthorize[(\"read\"|\"write\"|\"delete\")] explicitly (re)authorizes this package to access photos from a specific account on Flickr."; FlickrDeauthorize::usage = "FlickrDeauthorize[] deauthorizes this package to access photos from a specific account on Flickr."; FlickrAuthorizedQ::usage = "FlickrAuthorizedQ[(\"read\"|\"write\"|\"delete\")] returns true of this package is currently authorized to access photos from a Flickr account with the (optionally) specified permissions."; FlickrPhotosets::usage = "FlickrPhotosets[] returns a list of photosetID->photosetName rules, one for each photoset of the currently authorzied user."; FlickrPhotosetPhotos::usage = "FlickrPhotosetPhotos[photoset (, startDate, endDate)] returns a list of photoID values, one for each photo in the photoset, optionally filtering by date."; FlickrPhotosetImport::usage = "FlickrPhotosetImport[photoset, size] imports the list of photos from the specified photoset at the specified size."; FlickrPhotoSizes::usage = "FlickrPhotoSizes[photo] returns a list of size->url rules, one for each size of the image available for download."; FlickrPhotoImport::usage = "FlickPhotoImport[photo, size] imports the specified size of the specified photo from flickr"; FlickrPhotoInfo::usage = "FlickrPhotoInfo[photo] returns a list of rules containing metadata info about the photo, including title, description, tags, urls, date_taken, date_posted, location, etc."; Begin["`Private`"]; (* ::Section:: *) (*Implementation*) $FlickrKey = CompressedData[ "1:eJxTTMoPClZgYGAwNU9NsUg0NzU2MzQ0MDRJMk42SAZSacYpSQamBslGAMvTCi0="]; $FlickrSecret = CompressedData[ "1:eJxTTMoPChZgYGCwTDZMskxMTTOzTDM2szA3AQBNvgYd"]; $FlickrFrob = ""; $FlickrToken = ""; stringHash[string_String, type_:"MD5"] := Module[ {stream, file, hash}, stream = OpenWrite[]; WriteString[stream, string]; file = Close[stream]; hash = FileHash[file, type]; DeleteFile[file]; hash ]; (* ::Input:: *) (*stringHash["foo"]*) (* ::Text:: *) (*(* This should work but DateList does not handle DST correctly *)*) (*utcToLocal[{year_, mon_, day_, h_, m_, s_}] := *) (* DateList[{year, mon, day, h, m, s}, TimeZone -> 0];*) utcToLocal[{year_, mon_, day_, h_, m_, s_}] := JavaBlock@Module[ {utcZone, localZone, utcCal, localCal, date}, LoadJavaClass["java.util.TimeZone"]; LoadJavaClass["java.util.GregorianCalendar"]; utcZone = TimeZone`getTimeZone["UTC"]; localZone = TimeZone`getDefault[]; utcCal = JavaNew["java.util.GregorianCalendar", utcZone]; localCal = JavaNew["java.util.GregorianCalendar", localZone]; utcCal@set[year, mon - 1, day, h, m, Round[s]]; localCal@setTime[utcCal@getTime[]]; (localCal@get[#]& /@ { Calendar`YEAR, Calendar`MONTH, Calendar`DATE, Calendar`HOURUOFUDAY, Calendar`MINUTE, Calendar`SECOND }) + {0, 1, 0, 0, 0, 0.} (* convert seconds to Real *) ]; getSignature[args_List] := Module[ {sorted = Sort[args]}, IntegerString[stringHash[StringJoin[$FlickrSecret, Sequence[StringJoin[First@#, Last@#]& /@ sorted]], "MD5"], 16, 32] ]; (* ::Input:: *) (*getSignature[{*) (*"api_key"->$FlickrKey,*) (*"method"->"flickr.auth.getFrob"*) (*}]*) getMethodURL[url_String, args_List] := Module[ {sig = getSignature[args]}, StringJoin[url, "?", StringJoin@@Riffle[StringJoin[First@#, "=", Last@#]& /@ args, "&"], "&api_sig=", sig ] ]; (* ::Input:: *) (*getMethodURL["http://api.flickr.com/services/rest/",{*) (*"api_key"->$FlickrKey,*) (*"method"->"flickr.auth.getFrob"*) (*}]*) getFrob[] := Module[ {url, xml, frobs}, url = getMethodURL["http://api.flickr.com/services/rest/",{ "api_key"->$FlickrKey, "method"->"flickr.auth.getFrob" }]; xml = Import[url, "XML"]; frobs = Cases[xml, XMLElement["frob", {}, {x_}]:>x, \[Infinity]]; If[Length[frobs] =!= 0, First[frobs], $Failed] ]; (* ::Input:: *) (*getFrob[]*) getToken[] := Module[ {url, xml, tokens}, url = getMethodURL["http://api.flickr.com/services/rest/",{ "api_key"->$FlickrKey, "method"->"flickr.auth.getToken", "frob"->$FlickrFrob }]; xml = Import[url, "XML"]; tokens = Cases[xml, XMLElement["token", {}, {x_}]:>x, \[Infinity]]; If[Length[tokens] =!= 0, First[tokens], $Failed] ]; (* ::Input:: *) (*getToken[]*) authorize[perms_:"read"] := Module[ {res, authURL}, $FlickrFrob = getFrob[]; If[$FlickrFrob === $Failed, Return[$Failed]]; res = ChoiceDialog[Column[{ TextCell["This program requires your authorization before it can read or modify your photos and data on Flickr", FontSize->CurrentValue[{"ControlsFontSize", Large}]], TextCell["Authorizing is a simple process which takes place in your web browser. When you're finished, return to this window to complete authorization.", FontSize->CurrentValue[{"ControlsFontSize",Small}]] }]]; authURL = getMethodURL["http://flickr.com/services/auth/",{ "api_key"->$FlickrKey, "perms"->perms, "frob"->$FlickrFrob }]; If[res, SystemOpen[authURL], Return[$Canceled]]; res = ChoiceDialog[Column[{ TextCell["Return to this window after you have finished the authorization process on Flickr.com", FontSize->CurrentValue[{"ControlsFontSize", Large}]], TextCell["Once you're done, click the 'OK' button below to continue.", FontSize->CurrentValue[{"ControlsFontSize",Small}]], TextCell["(You can revoke this program's authorization at any time in your account page on Flickr.com.)", FontSize->CurrentValue[{"ControlsFontSize",Small}]] }]]; If[res, $FlickrToken = getToken[], Return[$Canceled]]; $FlickrToken ]; (* ::Input:: *) (*authorize[]*) checkToken[token_String, perms_:"read"] := Module[ {url, xml}, url = getMethodURL["http://api.flickr.com/services/rest/",{ "api_key"->$FlickrKey, "method"->"flickr.auth.checkToken", "frob"->$FlickrFrob, "auth_token"->token }]; xml = Import[url, "XML"]; Unequal[Length@Cases[xml, XMLElement["perms", _, {perms}], \[Infinity]], 0] ]; (* ::Input:: *) (*checkToken[$FlickrToken]*) checkAuthorization[perms_:"read"] := Module[ {}, If[!checkToken[$FlickrToken, perms], authorize[perms] ]; checkToken[$FlickrToken, perms] ]; (* ::Input:: *) (*$FlickrToken = "";*) (*checkAuthorization[]*) callMethod[args_List] := Module[ {fullargs, url, xml, resp}, fullargs = Join[{"api_key"->$FlickrKey, "auth_token"->$FlickrToken}, args]; url = getMethodURL["http://api.flickr.com/services/rest/", fullargs]; xml = Import[url, "XML"]; If[Length@Cases[xml, XMLElement["rsp", {___, "stat"->"ok", ___}, _], \[Infinity]] =!= 0, Return[xml] ]; If[Length@Cases[xml, XMLElement["err", {___, "code"->("1"|"98"), ___}, _], \[Infinity]] =!= 0, If[checkAuthorization[], fullargs = Join[{"api_key"->$FlickrKey, "auth_token"->$FlickrToken}, args]; url = getMethodURL["http://api.flickr.com/services/rest/", fullargs]; xml = Import[url, "XML"]; If[Length@Cases[xml, XMLElement["rsp", {___, "stat"->"ok", ___}, _], \[Infinity]] =!= 0, Return[xml] ]; ]; ]; $Failed ]; (* ::Input:: *) (*callMethod[{*) (*"api_key"->$FlickrKey,*) (*"auth_token"->$FlickrToken,*) (*"method"->"flickr.photosets.getList"*) (*}]*) (* ::Subsection:: *) (*Authorization*) FlickrAuthorize[perms_:"read"] := (Head[authorize[perms]]===String); FlickrDeauthorize[] := ($FlickToken = "";); FlickrAuthorizedQ[perms_:"read"] := checkToken[$FlickrToken, perms]; (* ::Subsection:: *) (*Photosets*) FlickrPhotosets[] := Module[ {xml}, xml = callMethod[{"method"->"flickr.photosets.getList"}]; Cases[xml, XMLElement["photoset", {___, "id"->id_, ___}, {___, XMLElement["title", _, {title_}], ___} ] :> Rule[id, title], \[Infinity]] ]; (* ::Input:: *) (*FlickrPhotosets[]*) FlickrPhotosetPhotos[Rule[id_String, name_String], start_:0, end_:$MaxMachineNumber] := FlickrPhotosetPhotos[id, start, end]; FlickrPhotosetPhotos[photoset_String, start_List, end_List] := FlickrPhotosetPhotos[photoset, AbsoluteTime[start], AbsoluteTime[end]]; FlickrPhotosetPhotos[photoset_String, absoluteStart_:0, absoluteEnd_:$MaxMachineNumber ]/;(NumericQ[absoluteStart] && NumericQ[absoluteEnd]) := Module[ {xml}, xml = callMethod[{ "method"->"flickr.photosets.getPhotos", "photoset_id"->photoset, "extras"->"date_taken" }]; xml = Cases[xml, XMLElement["photo", {___, "datetaken"->x_, ___}, _] /; absoluteStart <= AbsoluteTime[DateList[x]] <= absoluteEnd, \[Infinity]]; Cases[xml, XMLElement["photo", {___, "id"->id_, ___}, _]:>id, \[Infinity]] ]; (* ::Input:: *) (*Clear@FlickrPhotosetPhotos*) (* ::Input:: *) (*FlickrPhotosetPhotos["72157610959331733"->"Self Portraits"]*) (* ::Input:: *) (*FlickrPhotosetPhotos["72157610959331733"->"Self Portraits",{2009,2,1,0,0,0},{2009,3,1,0,0,0}]*) (* ::Input:: *) (*FlickrPhotosetPhotos["72157610959331733", {2009,2,1,0,0,0},{2009,3,1,0,0,0}]*) (* ::Input:: *) (*FlickrPhotosetPhotos["72157610959331733", AbsoluteTime@{2009,2,1,0,0,0},AbsoluteTime@{2009,3,1,0,0,0}]*) FlickrPhotosetImport[photoset_, size_String] := Module[ {photos = FlickrPhotosetPhotos[photoset]}, Monitor[ Table[FlickrPhotoImport[photos[[i]], size], {i, Length[photos]}], ProgressIndicator[Dynamic[i/Length[photos]]] ] ]; (* ::Input:: *) (*FlickrPhotosetImport["72157612695833734","Small"]*) (* ::Subsection:: *) (*Photos*) FlickrPhotoSizes[photo_String] := Module[ {xml, sizes}, xml = callMethod[{ "method"->"flickr.photos.getSizes", "photo_id"->photo }]; sizes = Cases[xml, XMLElement["size", _, _], \[Infinity]]; (Rule["label", "source"] /. #[[2]])& /@ sizes ]; (* ::Input:: *) (*FlickrPhotoSizes["2567968103"]*) FlickrPhotoImport[photo_String, size_String] := Module[ {sizes, url}, sizes = FlickrPhotoSizes[photo]; url = size /. sizes; If[url =!= size, Import[url], $Failed] ]; (* ::Input:: *) (*FlickrPhotoImport["2567968103", "Square"]*) (* ::Input:: *) (*FlickrPhotoImport["2567968103", "Thumbnail"]*) (* ::Input:: *) (*FlickrPhotoImport["2567968103", "Small"]*) (* ::Input:: *) (*FlickrPhotoImport["2567968103", "Medium"]*) firstOr[list_List, or_] := If[Length[list] =!= 0, First[list], or]; FlickrPhotoInfo[photo_String] := Module[ {xml}, xml = callMethod[{ "method"->"flickr.photos.getInfo", "photo_id"->photo }]; { "title" -> firstOr[Cases[xml, XMLElement["title", _, {s_}]:>s, \[Infinity]], ""], "description" -> firstOr[Cases[xml, XMLElement["description", _, {s_}]:>s, \[Infinity]], ""], "tags" -> Cases[xml, XMLElement["tag", {___, "raw"->s_, ___}, _]:>s, \[Infinity]], "urls" -> Cases[xml, XMLElement["url", _, {s_}]:>s, \[Infinity]], "notes" -> Cases[xml, XMLElement["note", _, {s_}]:>s, \[Infinity]], "date_taken" -> firstOr[Cases[xml, XMLElement["dates", {___, "taken"->s_, ___}, _]:> DateList[s], \[Infinity]], Date[]], "date_posted" -> firstOr[Cases[xml, XMLElement["dates", {___, "posted"->s_, ___}, _]:> utcToLocal[DateList[2208988800 + ToExpression@s]], \[Infinity]], Date[]], "location" -> firstOr[Cases[xml, XMLElement["location", Alternatives[ {___, "latitude"->lat_, ___, "longitude"->lon_, ___}, {___, "longitude"->lon_, ___, "latitude"->lat_, ___}], _]:> GeoPosition[{ToExpression@lat, ToExpression@lon}, "WGS84Original"], \[Infinity]], Null] } ]; (* ::Input:: *) (*FlickrPhotoInfo["3221140625"]*) (* ::Input:: *) (*FlickrPhotoInfo["2567968103"]*) (* ::Section:: *) (*Finalization*) End[]; (*`Private`*) EndPackage[]; (* Flickr` *)