Play images and video from Synology PhotoStation server

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. # -*- coding: utf-8 -*-
  2. import os,os.path
  3. import requests, urllib, urlparse
  4. import json
  5. headers2dict = lambda h: dict([l.strip().split(": ") for l in h.strip().splitlines()])
  6. class PhotoStationAPI():
  7. def __init__(self, ps_url):
  8. self.url = "%s/webapi/"%ps_url
  9. self.sid = ""
  10. self.user = ""
  11. self.headers = headers2dict("""
  12. User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:51.0) Gecko/20100101 Firefox/51.0
  13. Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
  14. Accept-Language: en-US,en;q=0.5
  15. Referer: %s/photo/
  16. """%ps_url)
  17. self.api = self._call2("query.php", "api=SYNO.API.Info&method=query&version=1&query=all")
  18. def login(self,user,password):
  19. data = "api=SYNO.PhotoStation.Auth&method=login&version=1&username=%s&password=%s&remember_me=on"%(user,password)
  20. js = self._call2("auth.php",data)
  21. self.sid=js["sid"]
  22. self.user = user
  23. return self.sid
  24. def logout(self):
  25. pass
  26. def get_album_list( self, album_id="",
  27. offset=0,limit=-1,
  28. additional="album_permission,photo_exif,video_codec,video_quality,thumb_size,file_location,item_count,album_sorting", #album_permission,photo_exif,video_codec,video_quality,thumb_size,file_location
  29. sort_by="preference", # 'filename', 'takendate', 'createdate', 'preference', 'default'
  30. sort_direction="asc"
  31. ):
  32. data = "api=SYNO.PhotoStation.Album&method=list&version=1&offset=%s&limit=%s&recursive=false&type=album,photo,video&additional=%s&sort_direction=%s&sort_by=%s&ignore=thumbnail&id=%s"%(
  33. offset,limit,additional,sort_direction,sort_by,album_id)
  34. return self._call2("album.php",data)
  35. def get_album_list2( self, album_id="",
  36. offset=0,limit=-1,
  37. additional="album_permission,photo_exif,video_codec,video_quality,thumb_size,file_location,item_count,album_sorting", #album_permission,photo_exif,video_codec,video_quality,thumb_size,file_location
  38. sort_by="preference", # filename,
  39. sort_direction="asc"
  40. ):
  41. lst = []
  42. js = self.get_album_list(album_id,offset,limit,additional,sort_by,sort_direction)
  43. for f in js["items"]:
  44. if f["type"] == u"photo":
  45. name = f["info"]["name"]
  46. desc = [
  47. f["info"]["description"],
  48. f["additional"]["file_location"],
  49. f["info"]["takendate"],
  50. "%sx%s"%(f["info"]["resolutionx"],f["info"]["resolutiony"]),
  51. "%s %s"%(f["additional"]["photo_exif"]["camera"],f["additional"]["photo_exif"]["camera_model"]),
  52. "%s %s %s"%(f["additional"]["photo_exif"]["aperture"],f["additional"]["photo_exif"]["exposure"],f["additional"]["photo_exif"]["focal_length"])
  53. ]
  54. elif f["type"] == u"video":
  55. name = f["info"]["name"]
  56. desc = [
  57. f["info"]["description"],
  58. f["additional"]["file_location"],
  59. ]
  60. elif f["type"] == u"album":
  61. name = f["info"]["name"]
  62. desc = [
  63. f["info"]["description"],
  64. f["additional"]["file_location"],
  65. "%s photos, %s videos"%(f["additional"]["item_count"]["photo"],f["additional"]["item_count"]["video"])
  66. ]
  67. desc = filter(None, desc)
  68. desc = "\n".join(desc)
  69. img = self.get_thumb_url(f["id"],"small")
  70. lst.append([
  71. f["info"]["name"].encode("utf8"),
  72. f["id"].encode("utf8"), #id
  73. img.encode("utf8"), # thumburl
  74. desc.encode("utf8"), # desc
  75. #f["type"].encode("utf8")
  76. ])
  77. return lst
  78. def get_categories(self):
  79. return self.call_api("Category", "list", offset=0, limit=120)
  80. def get_album_info(self,album_id=""):
  81. data = "id=%s&additional=album_sorting,item_count&api=SYNO.PhotoStation.Album&method=getinfo&version=1&ps_username=%s"%(
  82. album_id,self.user)
  83. return self._call2("album.php",data)
  84. def get_category_list2(self):
  85. return self.call_api("Category", "list", offset=0, limit=-1)
  86. def get_category_list(self, category_id=""):
  87. if category_id:
  88. # api=SYNO.PhotoStation.Category&method=list&version=1&offset=0&limit=1000
  89. data = "id=%s&api=SYNO.PhotoStation.Category&method=listitem&version=1&offset=0&limit=-1&additional=album_permission,photo_exif,video_codec,video_quality,thumb_size,file_location,item_count,album_sorting" % category_id
  90. else:
  91. data = "api=SYNO.PhotoStation.Category&method=list&version=1&offset=0&limit=-1"
  92. return self._call2("category.php",data)
  93. def get_smart_album(self, id=""):
  94. if id:
  95. #TODO
  96. data = "id=%s&api=SYNO.PhotoStation.Category&method=listitem&version=1&offset=0&limit=500&additional=album_permission,thumb_size" % id
  97. else:
  98. #sort_by=title&sort_direction=desc&api=SYNO.PhotoStation.SmartAlbum&method=list&version=1&offset=0&limit=500&additional=thumb_size
  99. data = "sort_by=title&sort_direction=desc&api=SYNO.PhotoStation.SmartAlbum&method=list&version=1&offset=0&limit=500&additional=thumb_size"
  100. return self._call2("smart_album.php",data)
  101. def get_photo_info(self,id):
  102. data = "id=%s&version=1&api=SYNO.PhotoStation.Photo&method=getinfo&ps_username=%s&additional=album_permission,photo_exif,video_codec,video_quality,thumb_size,file_location,item_count,album_sorting"%(id,self.user)
  103. js = self._call2("photo.php",data)
  104. if isinstance(js, list):
  105. return js[0]
  106. else:
  107. return js
  108. def get_thumb(self,id,size="peview",rotate=0 ):
  109. # siz = preview|small|large
  110. data = "api=SYNO.PhotoStation.Thumb&method=get&version=1&size=%s&id=%s&rotate_version=%s"%(size,id,rotate)
  111. content = self._call2("thumb.php",data)
  112. return content
  113. def get_thumb_url(self,id,size="small",rotate=0 ):
  114. # small (M),preview (PREVIEW),large (XL)
  115. # http://home.blue.lv/photo/webapi/thumb.php?api=SYNO.PhotoStation.Thumb&method=get&version=1&size=large&id=photo_323031372f323031372d30312d787820537475626169_53353032303031372e4a5047&rotate_version=0&thumb_sig=&PHPSESSID=p51g2ssj3b2o3legsoqfqdc8r3
  116. url = "%sthumb.php?api=SYNO.PhotoStation.Thumb&method=get&version=1&size=%s&id=%s&rotate_version=%s&thumb_sig=&PHPSESSID=%s"%(
  117. self.url, size, id, rotate, self.sid )
  118. return url
  119. def get_photo_url(self,id):
  120. url = "%sdownload.php?api=SYNO.PhotoStation.Download&method=getphoto&version=1&id=%s&PHPSESSID=%s" % (
  121. self.url, id, self.sid )
  122. return url
  123. def get_video_url(self,id, quality_id=""):
  124. if not quality_id:
  125. url = "%sdownload.php?api=SYNO.PhotoStation.Download&method=getvideo&version=1&id=%s&PHPSESSID=%s" % (
  126. self.url, id, self.sid )
  127. else:
  128. url = "%sdownload.php?api=SYNO.PhotoStation.Download&method=getvideo&version=1&id=%s&quality_id=%s&PHPSESSID=%s" % (
  129. self.url, id, quality_id, self.sid )
  130. return url
  131. def get_video_streams(self,id):
  132. urls = []
  133. js = self.get_photo_info(id)
  134. if not js: return urls
  135. name = js["info"]["name"]
  136. for q in js["additional"]["video_quality"]:
  137. s = {}
  138. quality_id = q["id"]
  139. data = "api=SYNO.PhotoStation.Download&method=getvideo&version=1&id=%s&quality_id=%s"%(id,quality_id)
  140. url = "%s/download.php/%s?%s&PHPSESSID=%s"%(self.url,name,data,self.sid)
  141. s["name"] = name
  142. s["url"] = url
  143. s["img"] = self.get_thumb_url(id, size='small')
  144. s["quality"] = "%sx%s"%(q["resolutionx"],q["resolutiony"])
  145. s["bitrate"] = q["video_bitrate"]
  146. s["desc"] = "%s %s\n%s\n%sx%s %s/%s"%(js["info"]["name"],js["info"]["description"],
  147. js["info"]["takendate"],
  148. js["additional"]["video_codec"]["resolutionx"],js["additional"]["video_codec"]["resolutiony"],js["additional"]["video_codec"]["vcodec"],js["additional"]["video_codec"]["acodec"])
  149. urls.append(s)
  150. return urls
  151. def _call(self,path,data):
  152. url = self.url+path
  153. if isinstance(data,basestring):
  154. data = urlparse.parse_qs(data)
  155. if self.sid:
  156. self.headers["Cookie"] = "PHPSESSID=%s;"%self.sid
  157. try:
  158. r = requests.post(url,data,headers=self.headers)
  159. js = json.loads(r.content)
  160. except Exception as e:
  161. raise Exception(e)
  162. if not (js["success"] and "data" in js):
  163. raise Exception("PhotoStation API %s error %s\n%s" % ( path, js["error"]["code"], data))
  164. else:
  165. return js["data"]
  166. def _call2(self,path,data):
  167. "GET request to PhotoStation API"
  168. if isinstance(data,dict):
  169. data = urllib.urlencode(data)
  170. if self.sid:
  171. if not "PHPSESSID" in data:
  172. data = data +"&PHPSESSID=%s"%self.sid
  173. #self.headers["Cookie"] = "PHPSESSID=%s;"%self.sid
  174. try:
  175. url = self.url + path +"?"+data
  176. print url
  177. r = requests.get(url,headers=self.headers)
  178. js = json.loads(r.content)
  179. except Exception as e:
  180. raise Exception(e)
  181. if not (js["success"] and "data" in js):
  182. raise Exception("PhotoStation API %s error %s\n%s" % ( path, js["error"]["code"], data))
  183. else:
  184. return js["data"]
  185. def _check_url(self,url):
  186. if self.sid:
  187. if not "PHPSESSID" in url:
  188. self.headers["Cookie"] = "PHPSESSID=%s; photo_remember_me=1"%self.sid
  189. pass
  190. r = requests.get(url,headers=self.headers)
  191. if r.status_code <> 200 or "text/plain" in r.headers["Content-Type"] and "error" in r.content:
  192. return False
  193. else:
  194. return True
  195. def call_api(self, obj, method, **kwargs):
  196. proc = "SYNO.PhotoStation." + obj
  197. if proc not in self.api:
  198. raise Exception("Not valid API object name ", obj)
  199. path = self.api[proc]["path"]
  200. data = {}
  201. data["api"] = proc
  202. data["method"] = method
  203. data["version"] = 1
  204. data.update(kwargs)
  205. return self._call2(path, data)
  206. API = {
  207. "SYNO.PhotoStation.Auth": {
  208. "path": "auth.php",
  209. "methods": {
  210. "1": ["login", "logout", "checkauth"]
  211. }
  212. },
  213. "SYNO.PhotoStation.Info": {
  214. "path": "info.php",
  215. "methods": {
  216. "1": ["getinfo"]
  217. }
  218. },
  219. "SYNO.PhotoStation.Album": {
  220. "path": "album.php",
  221. "methods": {
  222. "1": ["list", "getinfo", "create", "edit", "delete", "arrangeitem", "move", "cleararrangeitem", "cancel"]
  223. }
  224. },
  225. "SYNO.PhotoStation.Permission": {
  226. "path": "permission.php",
  227. "methods": {
  228. "1": ["getalbum", "editalbum", "editgroup", "list_public_share", "edit_public_share"]
  229. }
  230. },
  231. "SYNO.PhotoStation.Photo": {
  232. "path": "photo.php",
  233. "methods": {
  234. "1": ["list", "listexif", "listfeatureditem", "listgpsgroup", "listgpsgroupeditem", "getinfo", "getexif", "edit", "delete", "copy", "cancel"]
  235. }
  236. },
  237. "SYNO.PhotoStation.Thumb": {
  238. "path": "thumb.php",
  239. "methods": {
  240. "1": ["get", "get_dsm_thumb"]
  241. }
  242. },
  243. "SYNO.PhotoStation.Cover": {
  244. "path": "cover.php",
  245. "methods": {
  246. "1": ["set"]
  247. }
  248. },
  249. "SYNO.PhotoStation.SmartAlbum": {
  250. "path": "smart_album.php",
  251. "methods": {
  252. "1": ["list", "getinfo", "create", "edit", "delete"]
  253. }
  254. },
  255. "SYNO.PhotoStation.File": {
  256. "path": "file.php",
  257. "methods": {
  258. "1": ["uploadphoto", "uploadvideo"]
  259. }
  260. },
  261. "SYNO.PhotoStation.Download": {
  262. "path": "download.php",
  263. "methods": {
  264. "1": ["getphoto", "getvideo", "getitem"]
  265. }
  266. },
  267. "SYNO.PhotoStation.ory": {
  268. "path": "ory.php",
  269. "methods": {
  270. "1": ["list", "getinfo", "create", "edit", "delete", "arrangeory", "listitem", "additem", "removeitem", "arrangeitem"]
  271. }
  272. },
  273. "SYNO.PhotoStation.About": {
  274. "path": "about.php",
  275. "methods": {
  276. "1": ["get", "set", "set_visibility"]
  277. }
  278. },
  279. "SYNO.PhotoStation.Tag": {
  280. "path": "tag.php",
  281. "methods": {
  282. "1": ["list", "getinfo", "create", "edit", "delete", "searchplace", "delete_unconfirmed_tag"]
  283. }
  284. },
  285. "SYNO.PhotoStation.PhotoTag": {
  286. "path": "photo_tag.php",
  287. "methods": {
  288. "1": ["list", "people_tag", "geo_tag", "desc_tag", "delete", "people_tag_confirm"]
  289. }
  290. },
  291. "SYNO.PhotoStation.Comment": {
  292. "path": "comment.php",
  293. "methods": {
  294. "1": ["list", "create", "delete"]
  295. }
  296. },
  297. "SYNO.PhotoStation.Timeline": {
  298. "path": "timeline.php",
  299. "methods": {
  300. "1": ["getindex"]
  301. }
  302. },
  303. "SYNO.PhotoStation.Group": {
  304. "path": "group.php",
  305. "methods": {
  306. "1": ["list", "get", "get_dsm_group", "getmember", "create", "edit", "editmember", "delete"]
  307. }
  308. },
  309. "SYNO.PhotoStation.Rotate": {
  310. "path": "rotate.php",
  311. "methods": {
  312. "1": ["set"]
  313. }
  314. },
  315. "SYNO.PhotoStation.SlideshowMusic": {
  316. "path": "slideshow_music.php",
  317. "methods": {
  318. "1": ["list", "get", "add", "edit", "delete"]
  319. }
  320. },
  321. "SYNO.PhotoStation.DsmShare": {
  322. "path": "dsm_share.php",
  323. "methods": {
  324. "1": ["list", "copy", "copymusic"]
  325. }
  326. },
  327. "SYNO.PhotoStation.SharedAlbum": {
  328. "path": "shared_album.php",
  329. "methods": {
  330. "1": ["list", "getinfo", "getinfo_public", "create", "edit", "delete", "add_items", "remove_items", "edit_public_share", "get_single_item", "set_single_item"]
  331. }
  332. },
  333. "SYNO.PhotoStation.PhotoLog": {
  334. "path": "log.php",
  335. "methods": {
  336. "1": ["list", "clear", "export"]
  337. }
  338. },
  339. "SYNO.PhotoStation.Path": {
  340. "path": "path.php",
  341. "methods": {
  342. "1": [" ", "checkpath"]
  343. }
  344. }
  345. }
  346. if __name__ == "__main__":
  347. ps = PhotoStationAPI("http://home.blue.lv/photo")
  348. print ps.login("user","Kaskade7")
  349. js = ps.call_api("Category", "list", offset=0, limit=120)
  350. js = ps.get_category_list("category_1")
  351. #js = ps.get_category("category_1")
  352. video_id = "video_323031372f323031372d31322d3234205a69656d6173737665746b692f636c697073_30303133332e4d5453"
  353. quality_id = "2f766f6c756d65312f70686f746f2f323031372f323031372d31322d3234205a69656d6173737665746b692f636c6970732f4065614469722f30303133332e4d54532f53594e4f50484f544f5f46494c4d5f4d2e6d7034"
  354. url = ps.get_video_url(video_id, quality_id)
  355. url0 = ps.get_video_url(video_id, "")
  356. album_id = u'album_323031372f323031372d30312d313320536c69646f73616e61'
  357. js = ps.get_album_info(album_id)
  358. js = ps.get_album_list(album_id)
  359. album_id = "album_323031372f323031372d30312d313320536c69646f73616e612f766964656f"
  360. js = ps.get_album_list(album_id)
  361. photo_id = "photo_323031372f323031372d30312d787820537475626169_53353032303031372e4a5047"
  362. js = ps.get_photo_info(photo_id)
  363. url = ps.get_thumb_url(photo_id,"large")
  364. print url
  365. print ps._check_url(url)
  366. video_id = "video_323031372f323031372d30312d7878205374756261692f636c697073_30303136342e4d5453"
  367. js = ps.get_photo_info(video_id)
  368. urls = ps.get_video_streams(video_id)
  369. print urls[0]["url"]
  370. a=1