SalexAI commited on
Commit
4e555a8
·
verified ·
1 Parent(s): 63a8204

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +51 -22
app.py CHANGED
@@ -5,7 +5,7 @@ import json
5
 
6
  app = FastAPI()
7
 
8
- # Allow all CORS so browser apps can call this
9
  app.add_middleware(
10
  CORSMiddleware,
11
  allow_origins=["*"],
@@ -13,22 +13,24 @@ app.add_middleware(
13
  allow_headers=["*"],
14
  )
15
 
 
16
  BASE_62_CHAR_SET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
17
-
18
  def base62_to_int(e):
19
  t = 0
20
- for n in range(len(e)):
21
- t = t * 62 + BASE_62_CHAR_SET.index(e[n])
22
  return t
23
 
24
- def get_base_url(token):
25
- t = token[0]
26
- n = base62_to_int(token[1]) if t == "A" else base62_to_int(token[1:3])
27
- server_partition = n
28
- base_url = f"https://p{server_partition:02d}-sharedstreams.icloud.com/{token}/sharedstreams/"
 
29
  return base_url
30
 
31
- def get_redirected_base_url(base_url, token):
 
32
  url = base_url + "webstream"
33
  headers = {
34
  'Origin': 'https://www.icloud.com',
@@ -36,14 +38,14 @@ def get_redirected_base_url(base_url, token):
36
  }
37
  data = '{"streamCtag":null}'
38
  response = requests.post(url, headers=headers, data=data, allow_redirects=False)
39
-
40
  if response.status_code == 330:
41
  body = response.json()
42
  new_base_url = f"https://{body['X-Apple-MMe-Host']}/{token}/sharedstreams/"
43
  return new_base_url
44
  return base_url
45
 
46
- def get_metadata(base_url):
 
47
  url = base_url + "webstream"
48
  headers = {
49
  'Origin': 'https://www.icloud.com',
@@ -53,7 +55,8 @@ def get_metadata(base_url):
53
  response = requests.post(url, headers=headers, data=data)
54
  return response.json().get("photos", [])
55
 
56
- def get_asset_urls(base_url, guids):
 
57
  url = base_url + "webasseturls"
58
  headers = {
59
  'Origin': 'https://www.icloud.com',
@@ -63,28 +66,54 @@ def get_asset_urls(base_url, guids):
63
  response = requests.post(url, headers=headers, data=data)
64
  return response.json().get("items", {})
65
 
 
66
  @app.get("/album/{token}")
67
  def get_album(token: str):
68
  try:
69
  base_url = get_base_url(token)
70
  base_url = get_redirected_base_url(base_url, token)
71
  metadata = get_metadata(base_url)
72
- guids = [p["photoGuid"] for p in metadata]
73
- urls = get_asset_urls(base_url, guids)
74
 
75
  video_list = []
 
76
  for photo in metadata:
 
 
 
77
  for derivative in photo.get("derivatives", {}).values():
 
78
  if derivative.get("mediaAssetType") == "video" or derivative.get("type", "").startswith("video"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
 
80
- checksum = derivative.get("checksum")
81
- if checksum in urls:
82
- video_url = f"https://{urls[checksum]['url_location']}{urls[checksum]['url_path']}"
83
- video_list.append({
84
- "caption": photo.get("caption", ""),
85
- "url": video_url
86
- })
87
  return {"videos": video_list}
 
 
88
 
 
 
 
 
 
 
 
 
 
 
89
  except Exception as e:
90
  return {"error": str(e)}
 
5
 
6
  app = FastAPI()
7
 
8
+ # Allow all CORS so browser apps can call this API
9
  app.add_middleware(
10
  CORSMiddleware,
11
  allow_origins=["*"],
 
13
  allow_headers=["*"],
14
  )
15
 
16
+ # Base62 conversion helper
17
  BASE_62_CHAR_SET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
 
18
  def base62_to_int(e):
19
  t = 0
20
+ for char in e:
21
+ t = t * 62 + BASE_62_CHAR_SET.index(char)
22
  return t
23
 
24
+ # Construct the base URL for the shared album
25
+ def get_base_url(token: str) -> str:
26
+ first_char = token[0]
27
+ # If first char is "A", use only the next character; otherwise use the next two characters
28
+ n = base62_to_int(token[1]) if first_char == "A" else base62_to_int(token[1:3])
29
+ base_url = f"https://p{n:02d}-sharedstreams.icloud.com/{token}/sharedstreams/"
30
  return base_url
31
 
32
+ # Handle 330 redirect by calling the webstream endpoint with allow_redirects=False
33
+ def get_redirected_base_url(base_url: str, token: str) -> str:
34
  url = base_url + "webstream"
35
  headers = {
36
  'Origin': 'https://www.icloud.com',
 
38
  }
39
  data = '{"streamCtag":null}'
40
  response = requests.post(url, headers=headers, data=data, allow_redirects=False)
 
41
  if response.status_code == 330:
42
  body = response.json()
43
  new_base_url = f"https://{body['X-Apple-MMe-Host']}/{token}/sharedstreams/"
44
  return new_base_url
45
  return base_url
46
 
47
+ # Fetch metadata from the webstream endpoint
48
+ def get_metadata(base_url: str):
49
  url = base_url + "webstream"
50
  headers = {
51
  'Origin': 'https://www.icloud.com',
 
55
  response = requests.post(url, headers=headers, data=data)
56
  return response.json().get("photos", [])
57
 
58
+ # Fetch asset URLs for a list of photoGuids from the webasseturls endpoint
59
+ def get_asset_urls(base_url: str, guids: list):
60
  url = base_url + "webasseturls"
61
  headers = {
62
  'Origin': 'https://www.icloud.com',
 
66
  response = requests.post(url, headers=headers, data=data)
67
  return response.json().get("items", {})
68
 
69
+ # Main endpoint to extract videos
70
  @app.get("/album/{token}")
71
  def get_album(token: str):
72
  try:
73
  base_url = get_base_url(token)
74
  base_url = get_redirected_base_url(base_url, token)
75
  metadata = get_metadata(base_url)
76
+ guids = [photo["photoGuid"] for photo in metadata]
77
+ asset_urls = get_asset_urls(base_url, guids)
78
 
79
  video_list = []
80
+ # Loop over each photo in the album
81
  for photo in metadata:
82
+ best_video = None
83
+ best_size = 0
84
+ # Check all derivatives for potential video files
85
  for derivative in photo.get("derivatives", {}).values():
86
+ # Check if it's a video based on mediaAssetType or type field
87
  if derivative.get("mediaAssetType") == "video" or derivative.get("type", "").startswith("video"):
88
+ try:
89
+ file_size = int(derivative.get("fileSize", 0))
90
+ except ValueError:
91
+ file_size = 0
92
+ if file_size > best_size:
93
+ best_size = file_size
94
+ best_video = derivative
95
+ # If a best video candidate is found, try to build the video URL
96
+ if best_video and best_video.get("checksum") in asset_urls:
97
+ url_info = asset_urls[best_video["checksum"]]
98
+ video_url = f"https://{url_info['url_location']}{url_info['url_path']}"
99
+ video_list.append({
100
+ "caption": photo.get("caption", ""),
101
+ "url": video_url
102
+ })
103
 
 
 
 
 
 
 
 
104
  return {"videos": video_list}
105
+ except Exception as e:
106
+ return {"error": str(e)}
107
 
108
+ # Debug endpoint to return raw metadata from iCloud
109
+ @app.get("/album/{token}/raw")
110
+ def get_album_raw(token: str):
111
+ try:
112
+ base_url = get_base_url(token)
113
+ base_url = get_redirected_base_url(base_url, token)
114
+ metadata = get_metadata(base_url)
115
+ guids = [photo["photoGuid"] for photo in metadata]
116
+ asset_urls = get_asset_urls(base_url, guids)
117
+ return {"metadata": metadata, "asset_urls": asset_urls}
118
  except Exception as e:
119
  return {"error": str(e)}