File size: 5,019 Bytes
a3a5495 4e555a8 a3a5495 4e555a8 a3a5495 4e555a8 a3a5495 4e555a8 a3a5495 4e555a8 a3a5495 4e555a8 a3a5495 4e555a8 a3a5495 6c3bd2c a3a5495 4e555a8 a3a5495 4e555a8 a3a5495 4e555a8 6c3bd2c a3a5495 6c3bd2c 4e555a8 6c3bd2c 4e555a8 4b36c6f a3a5495 4e555a8 a3a5495 4e555a8 a3a5495 |
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 |
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
import requests
import json
app = FastAPI()
# Allow all CORS so browser apps can call this API
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
# Base62 conversion helper
BASE_62_CHAR_SET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
def base62_to_int(e):
t = 0
for char in e:
t = t * 62 + BASE_62_CHAR_SET.index(char)
return t
# Construct the base URL for the shared album
def get_base_url(token: str) -> str:
first_char = token[0]
n = base62_to_int(token[1]) if first_char == "A" else base62_to_int(token[1:3])
base_url = f"https://p{n:02d}-sharedstreams.icloud.com/{token}/sharedstreams/"
return base_url
# Handle 330 redirect by calling the webstream endpoint with allow_redirects=False
def get_redirected_base_url(base_url: str, token: str) -> str:
url = base_url + "webstream"
headers = {
'Origin': 'https://www.icloud.com',
'Content-Type': 'text/plain',
}
data = '{"streamCtag":null}'
response = requests.post(url, headers=headers, data=data, allow_redirects=False)
if response.status_code == 330:
body = response.json()
new_base_url = f"https://{body['X-Apple-MMe-Host']}/{token}/sharedstreams/"
return new_base_url
return base_url
# Fetch metadata from the webstream endpoint
def get_metadata(base_url: str):
url = base_url + "webstream"
headers = {
'Origin': 'https://www.icloud.com',
'Content-Type': 'text/plain',
}
data = '{"streamCtag":null}'
response = requests.post(url, headers=headers, data=data)
return response.json().get("photos", [])
# Fetch asset URLs for a list of photoGuids from the webasseturls endpoint
def get_asset_urls(base_url: str, guids: list):
url = base_url + "webasseturls"
headers = {
'Origin': 'https://www.icloud.com',
'Content-Type': 'text/plain',
}
data = json.dumps({"photoGuids": guids})
response = requests.post(url, headers=headers, data=data)
return response.json().get("items", {})
# Main endpoint to extract videos with enhanced checks
@app.get("/album/{token}")
def get_album(token: str):
try:
base_url = get_base_url(token)
base_url = get_redirected_base_url(base_url, token)
metadata = get_metadata(base_url)
guids = [photo["photoGuid"] for photo in metadata]
asset_urls = get_asset_urls(base_url, guids)
video_list = []
# Loop over each photo in the album
for photo in metadata:
best_video = None
best_size = 0
# First, check the derivatives field
for derivative in photo.get("derivatives", {}).values():
if (
derivative.get("mediaAssetType") == "video" or
derivative.get("type", "").startswith("video") or
derivative.get("filename", "").endswith(".mp4")
):
try:
file_size = int(derivative.get("fileSize", 0))
except ValueError:
file_size = 0
if file_size > best_size:
best_size = file_size
best_video = derivative
# If no derivative qualifies, check a potential 'movies' field
if not best_video and "movies" in photo:
for movie in photo["movies"]:
if movie.get("filename", "").endswith(".mp4"):
try:
file_size = int(movie.get("fileSize", 0))
except ValueError:
file_size = 0
if file_size > best_size:
best_size = file_size
best_video = movie
# Build the video URL if found and if its checksum is in the asset_urls
if best_video and best_video.get("checksum") in asset_urls:
url_info = asset_urls[best_video["checksum"]]
video_url = f"https://{url_info['url_location']}{url_info['url_path']}"
video_list.append({
"caption": photo.get("caption", ""),
"url": video_url
})
return {"videos": video_list}
except Exception as e:
return {"error": str(e)}
# Debug endpoint to return raw metadata from iCloud
@app.get("/album/{token}/raw")
def get_album_raw(token: str):
try:
base_url = get_base_url(token)
base_url = get_redirected_base_url(base_url, token)
metadata = get_metadata(base_url)
guids = [photo["photoGuid"] for photo in metadata]
asset_urls = get_asset_urls(base_url, guids)
return {"metadata": metadata, "asset_urls": asset_urls}
except Exception as e:
return {"error": str(e)}
|