SalexAI commited on
Commit
f0efb1b
·
verified ·
1 Parent(s): 34299d1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +58 -46
app.py CHANGED
@@ -1,3 +1,4 @@
 
1
  from fastapi import FastAPI
2
  from fastapi.middleware.cors import CORSMiddleware
3
  import httpx
@@ -15,116 +16,127 @@ def configure_cors(app: FastAPI):
15
  allow_methods=["*"],
16
  allow_headers=["*"],
17
  )
 
18
  configure_cors(app)
19
 
20
- # Precompute Base62 index mapping for O(1) lookup\BASE_62_MAP = {c: i for i, c in enumerate("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")}
 
 
 
21
 
22
  async def get_client() -> httpx.AsyncClient:
23
- if not hasattr(app.state, 'client'):
24
  app.state.client = httpx.AsyncClient(timeout=10.0)
25
  return app.state.client
26
 
27
- # Convert base62 to int using dict lookup
28
  def base62_to_int(token: str) -> int:
29
  result = 0
30
  for ch in token:
31
  result = result * 62 + BASE_62_MAP[ch]
32
  return result
33
 
34
- # Get base URL from token
35
  async def get_base_url(token: str) -> str:
36
  first = token[0]
37
- n = base62_to_int(token[1]) if first == 'A' else base62_to_int(token[1:3])
 
 
 
38
  return f"https://p{n:02d}-sharedstreams.icloud.com/{token}/sharedstreams/"
39
 
40
- # Static request details
41
- ICLOUD_HEADERS = {'Origin': 'https://www.icloud.com', 'Content-Type': 'text/plain'}
42
  ICLOUD_PAYLOAD = '{"streamCtag":null}'
43
 
44
- # Handle possible 330 redirect
45
  async def get_redirected_base_url(base_url: str, token: str) -> str:
46
  client = await get_client()
47
- resp = await client.post(base_url + 'webstream', headers=ICLOUD_HEADERS, data=ICLOUD_PAYLOAD, follow_redirects=False)
 
 
48
  if resp.status_code == 330:
49
  body = resp.json()
50
- host = body.get('X-Apple-MMe-Host')
51
  return f"https://{host}/{token}/sharedstreams/"
52
  return base_url
53
 
54
- # Generic function to POST and parse JSON
55
  async def post_json(path: str, base_url: str, payload: str) -> dict:
56
  client = await get_client()
57
- resp = await client.post(f"{base_url}{path}", headers=ICLOUD_HEADERS, data=payload)
 
 
58
  resp.raise_for_status()
59
  return resp.json()
60
 
61
- # Fetch metadata
62
  async def get_metadata(base_url: str) -> list:
63
- return (await post_json('webstream', base_url, ICLOUD_PAYLOAD)).get('photos', [])
 
64
 
65
- # Fetch asset URL mappings
66
  async def get_asset_urls(base_url: str, guids: list) -> dict:
67
- data = json.dumps({"photoGuids": guids})
68
- return (await post_json('webasseturls', base_url, data)).get('items', {})
 
69
 
70
- # Main endpoint\@app.get('/album/{token}')
71
  async def get_album(token: str):
72
  try:
73
  base_url = await get_base_url(token)
74
  base_url = await get_redirected_base_url(base_url, token)
75
 
76
  metadata = await get_metadata(base_url)
77
- guids = [p['photoGuid'] for p in metadata]
78
  asset_map = await get_asset_urls(base_url, guids)
79
 
80
  videos = []
81
- for p in metadata:
82
- if p.get('mediaAssetType', '').lower() != 'video':
 
83
  continue
84
 
85
- # select the largest derivative (exclude poster)
86
- derivatives = p.get('derivatives', {})
87
- best = max((d for k, d in derivatives.items() if k.lower() != 'posterframe'),
88
- key=lambda d: int(d.get('fileSize') or 0),
89
- default=None)
 
90
  if not best:
 
91
  continue
92
 
93
- checksum = best.get('checksum')
94
- if checksum not in asset_map:
 
95
  logging.info(f"Missing asset for checksum {checksum}")
96
  continue
97
-
98
- info = asset_map[checksum]
99
  video_url = f"https://{info['url_location']}{info['url_path']}"
100
 
101
  poster = None
102
- pf = derivatives.get('PosterFrame')
103
- if pf and pf.get('checksum') in asset_map:
104
- pi = asset_map[pf['checksum']]
105
- poster = f"https://{pi['url_location']}{pi['url_path']}"
 
106
 
107
  videos.append({
108
- 'caption': p.get('caption', ''),
109
- 'url': video_url,
110
- 'poster': poster
111
  })
112
 
113
- return {'videos': videos}
114
-
115
  except Exception as e:
116
- logging.error(f"Error fetching album {token}: {e}")
117
- return {'error': str(e)}
118
 
119
- # Debug endpoint
120
- @app.get('/album/{token}/raw')
121
  async def get_album_raw(token: str):
122
  try:
123
  base_url = await get_base_url(token)
124
  base_url = await get_redirected_base_url(base_url, token)
125
  metadata = await get_metadata(base_url)
126
- guids = [p['photoGuid'] for p in metadata]
127
  asset_map = await get_asset_urls(base_url, guids)
128
- return {'metadata': metadata, 'asset_urls': asset_map}
129
  except Exception as e:
130
- return {'error': str(e)}
 
 
 
1
+ ```python
2
  from fastapi import FastAPI
3
  from fastapi.middleware.cors import CORSMiddleware
4
  import httpx
 
16
  allow_methods=["*"],
17
  allow_headers=["*"],
18
  )
19
+
20
  configure_cors(app)
21
 
22
+ # Precompute Base62 index mapping for O(1) lookup
23
+ BASE_62_MAP = {c: i for i, c in enumerate(
24
+ "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
25
+ )}
26
 
27
  async def get_client() -> httpx.AsyncClient:
28
+ if not hasattr(app.state, "client"):
29
  app.state.client = httpx.AsyncClient(timeout=10.0)
30
  return app.state.client
31
 
32
+ # Convert base62 string to integer
33
  def base62_to_int(token: str) -> int:
34
  result = 0
35
  for ch in token:
36
  result = result * 62 + BASE_62_MAP[ch]
37
  return result
38
 
 
39
  async def get_base_url(token: str) -> str:
40
  first = token[0]
41
+ if first == "A":
42
+ n = base62_to_int(token[1])
43
+ else:
44
+ n = base62_to_int(token[1:3])
45
  return f"https://p{n:02d}-sharedstreams.icloud.com/{token}/sharedstreams/"
46
 
47
+ # Common headers and payload for iCloud requests
48
+ ICLOUD_HEADERS = {"Origin": "https://www.icloud.com", "Content-Type": "text/plain"}
49
  ICLOUD_PAYLOAD = '{"streamCtag":null}'
50
 
 
51
  async def get_redirected_base_url(base_url: str, token: str) -> str:
52
  client = await get_client()
53
+ resp = await client.post(
54
+ f"{base_url}webstream", headers=ICLOUD_HEADERS, data=ICLOUD_PAYLOAD, follow_redirects=False
55
+ )
56
  if resp.status_code == 330:
57
  body = resp.json()
58
+ host = body.get("X-Apple-MMe-Host")
59
  return f"https://{host}/{token}/sharedstreams/"
60
  return base_url
61
 
 
62
  async def post_json(path: str, base_url: str, payload: str) -> dict:
63
  client = await get_client()
64
+ resp = await client.post(
65
+ f"{base_url}{path}", headers=ICLOUD_HEADERS, data=payload
66
+ )
67
  resp.raise_for_status()
68
  return resp.json()
69
 
 
70
  async def get_metadata(base_url: str) -> list:
71
+ data = await post_json("webstream", base_url, ICLOUD_PAYLOAD)
72
+ return data.get("photos", [])
73
 
 
74
  async def get_asset_urls(base_url: str, guids: list) -> dict:
75
+ payload = json.dumps({"photoGuids": guids})
76
+ data = await post_json("webasseturls", base_url, payload)
77
+ return data.get("items", {})
78
 
79
+ @app.get("/album/{token}")
80
  async def get_album(token: str):
81
  try:
82
  base_url = await get_base_url(token)
83
  base_url = await get_redirected_base_url(base_url, token)
84
 
85
  metadata = await get_metadata(base_url)
86
+ guids = [photo["photoGuid"] for photo in metadata]
87
  asset_map = await get_asset_urls(base_url, guids)
88
 
89
  videos = []
90
+ for photo in metadata:
91
+ if photo.get("mediaAssetType", "").lower() != "video":
92
+ logging.info(f"Photo {photo.get('photoGuid')} is not a video.")
93
  continue
94
 
95
+ derivatives = photo.get("derivatives", {})
96
+ best = max(
97
+ (d for k, d in derivatives.items() if k.lower() != "posterframe"),
98
+ key=lambda d: int(d.get("fileSize") or 0),
99
+ default=None
100
+ )
101
  if not best:
102
+ logging.info(f"No video derivative for photo {photo.get('photoGuid')}")
103
  continue
104
 
105
+ checksum = best.get("checksum")
106
+ info = asset_map.get(checksum)
107
+ if not info:
108
  logging.info(f"Missing asset for checksum {checksum}")
109
  continue
 
 
110
  video_url = f"https://{info['url_location']}{info['url_path']}"
111
 
112
  poster = None
113
+ pf = derivatives.get("PosterFrame")
114
+ if pf:
115
+ pf_info = asset_map.get(pf.get("checksum"))
116
+ if pf_info:
117
+ poster = f"https://{pf_info['url_location']}{pf_info['url_path']}"
118
 
119
  videos.append({
120
+ "caption": photo.get("caption", ""),
121
+ "url": video_url,
122
+ "poster": poster
123
  })
124
 
125
+ return {"videos": videos}
 
126
  except Exception as e:
127
+ logging.error(f"Error in get_album: {e}")
128
+ return {"error": str(e)}
129
 
130
+ @app.get("/album/{token}/raw")
 
131
  async def get_album_raw(token: str):
132
  try:
133
  base_url = await get_base_url(token)
134
  base_url = await get_redirected_base_url(base_url, token)
135
  metadata = await get_metadata(base_url)
136
+ guids = [photo["photoGuid"] for photo in metadata]
137
  asset_map = await get_asset_urls(base_url, guids)
138
+ return {"metadata": metadata, "asset_urls": asset_map}
139
  except Exception as e:
140
+ logging.error(f"Error in get_album_raw: {e}")
141
+ return {"error": str(e)}
142
+ ```