Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 66 additions & 28 deletions automated_quality_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from retry import retry

API_TOKEN = os.environ.get("SCREENLY_API_TOKEN")
SCREENLY_API_BASE_URL = "https://api.screenlyapp.com"
REQUEST_HEADERS = {
"Authorization": f"Token {API_TOKEN}",
"Content-Type": "application/json",
Expand All @@ -16,36 +17,58 @@
PLAYLIST_PREFIX = "QC"


def get_ten_random_assets():
def get_team_id() -> str:
"""
Return 10 random assets in the account.
Return the current account's team ID.
"""

response = requests.get(
'https://api.screenlyapp.com/v4/assets?select=id&type=in.("appweb","audio","edge-app","image","video","web")&status=in.("finished","processing")',
f"{SCREENLY_API_BASE_URL}/v4.1/teams?is_current=eq.true",
headers=REQUEST_HEADERS,
)
response.raise_for_status()
teams = response.json()
if not teams:
raise ValueError("No current team found for this token")
return teams[0]["id"]

asset_count = len(response.json())

# Pick 10 random assets
asset_list = []
for i in range(10):
random_index = random.randint(0, asset_count - 1)
asset_list.append(response.json()[random_index]["id"])
def get_ten_random_assets(team_id: str) -> List[str]:
"""
Return 10 random asset IDs that belong to the current team.
Filtering by team_id ensures the token has permission to add them to playlists.
"""
response = requests.get(
f'{SCREENLY_API_BASE_URL}/v4.1/assets?select=id&team_id=eq.{team_id}&type=in.("appweb","audio","edge-app","image","video","web")&status=in.("finished","processing")',
headers=REQUEST_HEADERS,
)
response.raise_for_status()

return asset_list
assets = response.json()
if not assets:
raise ValueError(f"No eligible assets found for team {team_id}")

return [random.choice(assets)["id"] for _ in range(10)]


def get_screens() -> List[Dict[str, Any]]:
"""
Return a list of screens in the account.
Return a list of screens in the account. In v4.1, status and in_sync
live in the screen_statuses view, so they are embedded and flattened
into each screen dict.
"""

response = requests.get('https://api.screenlyapp.com/v4/screens?select=id,name,hostname,status,in_sync&type=eq.hardware&is_enabled=eq.true', headers=REQUEST_HEADERS)
response = requests.get(
f'{SCREENLY_API_BASE_URL}/v4.1/screens?select=id,name,hostname,screen_statuses(status,in_sync)&type=eq.hardware&is_enabled=eq.true',
headers=REQUEST_HEADERS,
)
response.raise_for_status()
return response.json()

screens = response.json()
for screen in screens:
screen_status = screen.pop("screen_statuses") or {}
screen["status"] = screen_status.get("status", "offline")
screen["in_sync"] = screen_status.get("in_sync", False)
return screens


@retry(AssertionError, tries=10, delay=SCREEN_SYNC_THRESHOLD / 10)
Expand Down Expand Up @@ -89,7 +112,7 @@ def get_qc_playlist_ids():
Get all playlists starting with 'PLAYLIST_PREFIX'.
"""

response = requests.get("https://api.screenlyapp.com/v4/playlists", headers=REQUEST_HEADERS)
response = requests.get(f"{SCREENLY_API_BASE_URL}/v4.1/playlists", headers=REQUEST_HEADERS)
response.raise_for_status()

qc_playlists = []
Expand All @@ -102,19 +125,29 @@ def get_qc_playlist_ids():

def delete_playlist(playlist_id):
"""
Delete a playlist and its items. In v4, playlist items must be
removed before the playlist itself can be deleted.
Delete a playlist and its items. In v4.1, label associations and playlist
items must be removed before the playlist itself can be deleted.
"""
labels_response = requests.delete(
f"{SCREENLY_API_BASE_URL}/v4.1/labels/playlists?playlist_id=eq.{playlist_id}",
headers=REQUEST_HEADERS,
)
if not labels_response.ok:
print(f"Failed to delete label associations for playlist {playlist_id}: {labels_response.status_code} {labels_response.text}")
return False
items_response = requests.delete(
f"https://api.screenlyapp.com/v4/playlist-items?playlist_id=eq.{playlist_id}",
f"{SCREENLY_API_BASE_URL}/v4.1/playlist-items?playlist_id=eq.{playlist_id}",
headers=REQUEST_HEADERS,
)
if not items_response.ok:
print(f"Failed to delete items for playlist {playlist_id}: {items_response.status_code} {items_response.text}")
return False
response = requests.delete(
f"https://api.screenlyapp.com/v4/playlists?id=eq.{playlist_id}",
f"{SCREENLY_API_BASE_URL}/v4.1/playlists?id=eq.{playlist_id}",
headers=REQUEST_HEADERS,
)
if not response.ok:
print(f"Failed to delete playlist {playlist_id}: {response.status_code} {response.text}")
return response.ok


Expand All @@ -123,7 +156,7 @@ def get_all_screens_label_id():
Return the ID of the built-in 'all-screens' label.
"""
response = requests.get(
"https://api.screenlyapp.com/v4/labels?type=eq.all-screens",
f"{SCREENLY_API_BASE_URL}/v4.1/labels?type=eq.all-screens",
headers=REQUEST_HEADERS,
)
response.raise_for_status()
Expand All @@ -135,15 +168,15 @@ def get_all_screens_label_id():

def add_asset_to_playlist(playlist_id, asset_id):
"""
Add a single asset to a playlist via the v4 playlist-items endpoint.
Add a single asset to a playlist via the v4.1 playlist-items endpoint.
"""
payload = {
"playlist_id": playlist_id,
"asset_id": asset_id,
"duration": 10,
}
response = requests.post(
"https://api.screenlyapp.com/v4/playlist-items",
f"{SCREENLY_API_BASE_URL}/v4.1/playlist-items",
headers={**REQUEST_HEADERS, "Prefer": "return=representation"},
json=payload,
)
Expand All @@ -161,8 +194,8 @@ def assign_playlist_to_all_screens(playlist_id):
"playlist_id": playlist_id,
}
response = requests.post(
"https://api.screenlyapp.com/v4/labels/playlists",
headers={**REQUEST_HEADERS, "Prefer": "return=representation"},
f"{SCREENLY_API_BASE_URL}/v4.1/labels/playlists",
headers={**REQUEST_HEADERS, "Prefer": "return=representation, resolution=ignore-duplicates"},
json=payload,
)
response.raise_for_status()
Expand All @@ -184,18 +217,23 @@ def create_qc_playlist():
}

response = requests.post(
"https://api.screenlyapp.com/v4/playlists",
f"{SCREENLY_API_BASE_URL}/v4.1/playlists",
headers={**REQUEST_HEADERS, "Prefer": "return=representation"},
json=payload,
)
response.raise_for_status()

data = response.json()
playlist_id = data[0]["id"] if isinstance(data, list) else data["id"]
print(f"Playlist created: {playlist_id}")

team_id = get_team_id()

for asset_id in get_ten_random_assets():
print("Adding assets to playlist...")
for asset_id in get_ten_random_assets(team_id):
add_asset_to_playlist(playlist_id, asset_id)

print("Assigning playlist to all screens...")
assign_playlist_to_all_screens(playlist_id)


Expand Down Expand Up @@ -230,10 +268,10 @@ def main():
try:
create_qc_playlist()
except requests.HTTPError as error:
print(f"Unable to create playlist: {error.response.status_code} {error.response.text}")
print(f"QC playlist setup failed: {error.response.status_code} {error.response.text}")
sys.exit(1)
except Exception as error:
print(f"Unable to create playlist: {error}")
print(f"QC playlist setup failed: {error}")
sys.exit(1)

print("Waiting for screens to sync...")
Expand Down
Loading