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)}