Spaces:
Running
Running
File size: 4,659 Bytes
5cc97a0 30a787a b7d0fd9 30a787a 5cc97a0 921564c 5cc97a0 921564c 5cc97a0 2b76608 b7d0fd9 5cc97a0 b7d0fd9 921564c 5cc97a0 b7d0fd9 5cc97a0 2b76608 b7d0fd9 5cc97a0 b7d0fd9 5cc97a0 2b76608 5cc97a0 2b76608 b7d0fd9 5cc97a0 b7d0fd9 2b76608 5cc97a0 b7d0fd9 2b76608 5cc97a0 9243edd 5cc97a0 eb8307e 5cc97a0 eb8307e 5cc97a0 eb8307e 5cc97a0 eb8307e 5cc97a0 eb8307e 5cc97a0 eb8307e 5cc97a0 eb8307e 5cc97a0 eb8307e 5cc97a0 eb8307e 5cc97a0 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 |
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
import httpx
import json
import logging
app = FastAPI()
logging.basicConfig(level=logging.INFO)
# Enable CORS for all origins
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
BASE_62_MAP = {c: i for i, c in enumerate("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")}
async def get_client() -> httpx.AsyncClient:
if not hasattr(app.state, "client"):
app.state.client = httpx.AsyncClient(timeout=15.0)
return app.state.client
def base62_to_int(token: str) -> int:
result = 0
for ch in token:
result = result * 62 + BASE_62_MAP[ch]
return result
async def get_base_url(token: str) -> str:
first = token[0]
if first == "A":
n = base62_to_int(token[1])
else:
n = base62_to_int(token[1:3])
return f"https://p{n:02d}-sharedstreams.icloud.com/{token}/sharedstreams/"
ICLOUD_HEADERS = {
"Origin": "https://www.icloud.com",
"Content-Type": "text/plain"
}
ICLOUD_PAYLOAD = '{"streamCtag":null}'
async def get_redirected_base_url(base_url: str, token: str) -> str:
client = await get_client()
resp = await client.post(
f"{base_url}webstream", headers=ICLOUD_HEADERS, data=ICLOUD_PAYLOAD, follow_redirects=False
)
if resp.status_code == 330:
try:
body = resp.json()
host = body.get("X-Apple-MMe-Host")
if not host:
raise ValueError("Missing X-Apple-MMe-Host in 330 response")
logging.info(f"Redirected to {host}")
return f"https://{host}/{token}/sharedstreams/"
except Exception as e:
logging.error(f"Redirect parsing failed: {e}")
raise
elif resp.status_code == 200:
return base_url
else:
resp.raise_for_status()
async def post_json(path: str, base_url: str, payload: str) -> dict:
client = await get_client()
resp = await client.post(f"{base_url}{path}", headers=ICLOUD_HEADERS, data=payload)
resp.raise_for_status()
return resp.json()
async def get_metadata(base_url: str) -> list:
data = await post_json("webstream", base_url, ICLOUD_PAYLOAD)
return data.get("photos", [])
async def get_asset_urls(base_url: str, guids: list) -> dict:
payload = json.dumps({"photoGuids": guids})
data = await post_json("webasseturls", base_url, payload)
return data.get("items", {})
@app.get("/album/{token}")
async def get_album(token: str):
try:
base_url = await get_base_url(token)
base_url = await get_redirected_base_url(base_url, token)
metadata = await get_metadata(base_url)
guids = [photo["photoGuid"] for photo in metadata]
asset_map = await get_asset_urls(base_url, guids)
videos = []
for photo in metadata:
if photo.get("mediaAssetType", "").lower() != "video":
continue
derivatives = photo.get("derivatives", {})
best = max(
(d for k, d in derivatives.items() if k.lower() != "posterframe"),
key=lambda d: int(d.get("fileSize") or 0),
default=None
)
if not best:
continue
checksum = best.get("checksum")
info = asset_map.get(checksum)
if not info:
continue
video_url = f"https://{info['url_location']}{info['url_path']}"
poster = None
pf = derivatives.get("PosterFrame")
if pf:
pf_info = asset_map.get(pf.get("checksum"))
if pf_info:
poster = f"https://{pf_info['url_location']}{pf_info['url_path']}"
videos.append({
"caption": photo.get("caption", ""),
"url": video_url,
"poster": poster or ""
})
return {"videos": videos}
except Exception as e:
logging.exception("Error in get_album")
return {"error": str(e)}
@app.get("/album/{token}/raw")
async def get_album_raw(token: str):
try:
base_url = await get_base_url(token)
base_url = await get_redirected_base_url(base_url, token)
metadata = await get_metadata(base_url)
guids = [photo["photoGuid"] for photo in metadata]
asset_map = await get_asset_urls(base_url, guids)
return {"metadata": metadata, "asset_urls": asset_map}
except Exception as e:
logging.exception("Error in get_album_raw")
return {"error": str(e)} |