SalexAI commited on
Commit
4fc602f
·
verified ·
1 Parent(s): 84208f9

made shit faster

Browse files
Files changed (1) hide show
  1. app.py +111 -125
app.py CHANGED
@@ -1,144 +1,130 @@
1
  from fastapi import FastAPI
2
  from fastapi.middleware.cors import CORSMiddleware
3
- import requests
4
  import json
5
  import logging
6
 
7
  app = FastAPI()
8
-
9
- # Set up logging for debugging
10
  logging.basicConfig(level=logging.INFO)
11
 
12
  # Allow all CORS so browser apps can call this API
13
- app.add_middleware(
14
- CORSMiddleware,
15
- allow_origins=["*"],
16
- allow_methods=["*"],
17
- allow_headers=["*"],
18
- )
19
-
20
- # Base62 conversion helper
21
- BASE_62_CHAR_SET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
22
- def base62_to_int(e):
23
- t = 0
24
- for char in e:
25
- t = t * 62 + BASE_62_CHAR_SET.index(char)
26
- return t
27
-
28
- # Construct the base URL for the shared album from the token
29
- def get_base_url(token: str) -> str:
30
- first_char = token[0]
31
- n = base62_to_int(token[1]) if first_char == "A" else base62_to_int(token[1:3])
32
- base_url = f"https://p{n:02d}-sharedstreams.icloud.com/{token}/sharedstreams/"
33
- return base_url
 
34
 
35
- # Handle 330 redirect from iCloud
36
- def get_redirected_base_url(base_url: str, token: str) -> str:
37
- url = base_url + "webstream"
38
- headers = {
39
- 'Origin': 'https://www.icloud.com',
40
- 'Content-Type': 'text/plain',
41
- }
42
- data = '{"streamCtag":null}'
43
- response = requests.post(url, headers=headers, data=data, allow_redirects=False)
44
- if response.status_code == 330:
45
- body = response.json()
46
- new_base_url = f"https://{body['X-Apple-MMe-Host']}/{token}/sharedstreams/"
47
- return new_base_url
 
 
 
 
 
48
  return base_url
49
 
50
- # Fetch metadata (the "webstream" data)
51
- def get_metadata(base_url: str):
52
- url = base_url + "webstream"
53
- headers = {
54
- 'Origin': 'https://www.icloud.com',
55
- 'Content-Type': 'text/plain',
56
- }
57
- data = '{"streamCtag":null}'
58
- response = requests.post(url, headers=headers, data=data)
59
- return response.json().get("photos", [])
60
-
61
- # Fetch asset URLs mapping for a list of photoGuids
62
- def get_asset_urls(base_url: str, guids: list):
63
- url = base_url + "webasseturls"
64
- headers = {
65
- 'Origin': 'https://www.icloud.com',
66
- 'Content-Type': 'text/plain',
67
- }
68
  data = json.dumps({"photoGuids": guids})
69
- response = requests.post(url, headers=headers, data=data)
70
- return response.json().get("items", {})
71
 
72
- # Main endpoint to extract video URLs and include the poster (first frame)
73
- @app.get("/album/{token}")
74
- def get_album(token: str):
75
  try:
76
- base_url = get_base_url(token)
77
- base_url = get_redirected_base_url(base_url, token)
78
- metadata = get_metadata(base_url)
79
- guids = [photo["photoGuid"] for photo in metadata]
80
- asset_urls = get_asset_urls(base_url, guids)
81
-
82
- video_list = []
83
- # Process each photo in the album
84
- for photo in metadata:
85
- # Check if the overall photo is a video
86
- if photo.get("mediaAssetType", "").lower() == "video":
87
- best_video = None
88
- best_size = 0
89
- # Look at each derivative except the poster frame
90
- derivatives = photo.get("derivatives", {})
91
- for key, derivative in derivatives.items():
92
- if key.lower() == "posterframe":
93
- continue
94
- try:
95
- file_size = int(derivative.get("fileSize", 0))
96
- except (ValueError, TypeError):
97
- file_size = 0
98
- if file_size > best_size:
99
- best_size = file_size
100
- best_video = derivative
101
-
102
- # Also get the poster frame derivative if available
103
- poster_url = None
104
- poster_derivative = photo.get("derivatives", {}).get("PosterFrame")
105
- if poster_derivative:
106
- poster_checksum = poster_derivative.get("checksum")
107
- if poster_checksum in asset_urls:
108
- poster_info = asset_urls[poster_checksum]
109
- poster_url = f"https://{poster_info['url_location']}{poster_info['url_path']}"
110
-
111
- # Build the video URL if found
112
- if best_video:
113
- checksum = best_video.get("checksum")
114
- if checksum in asset_urls:
115
- url_info = asset_urls[checksum]
116
- video_url = f"https://{url_info['url_location']}{url_info['url_path']}"
117
- video_list.append({
118
- "caption": photo.get("caption", ""),
119
- "url": video_url,
120
- "poster": poster_url
121
- })
122
- else:
123
- logging.info(f"Checksum {checksum} not found in asset_urls for photo {photo.get('photoGuid')}")
124
- else:
125
- logging.info(f"No video derivative found for photo {photo.get('photoGuid')}")
126
- else:
127
- logging.info(f"Photo {photo.get('photoGuid')} is not a video. mediaAssetType: {photo.get('mediaAssetType')}")
128
-
129
- return {"videos": video_list}
130
  except Exception as e:
131
- return {"error": str(e)}
 
132
 
133
- # Debug endpoint to return raw metadata for further inspection
134
- @app.get("/album/{token}/raw")
135
- def get_album_raw(token: str):
136
  try:
137
- base_url = get_base_url(token)
138
- base_url = get_redirected_base_url(base_url, token)
139
- metadata = get_metadata(base_url)
140
- guids = [photo["photoGuid"] for photo in metadata]
141
- asset_urls = get_asset_urls(base_url, guids)
142
- return {"metadata": metadata, "asset_urls": asset_urls}
143
  except Exception as e:
144
- return {"error": str(e)}
 
1
  from fastapi import FastAPI
2
  from fastapi.middleware.cors import CORSMiddleware
3
+ import httpx
4
  import json
5
  import logging
6
 
7
  app = FastAPI()
 
 
8
  logging.basicConfig(level=logging.INFO)
9
 
10
  # Allow all CORS so browser apps can call this API
11
+ def configure_cors(app: FastAPI):
12
+ app.add_middleware(
13
+ CORSMiddleware,
14
+ allow_origins=["*"],
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)}