Last active
December 30, 2024 00:00
-
-
Save thesanjeetc/f7b88b814ddac8f8117dc4102704102f to your computer and use it in GitHub Desktop.
Generate a VLC playlist from Panopto lectures for a better watching experience.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import re | |
import json | |
import requests | |
HOSTNAME = "https://imperial.cloud.panopto.eu" | |
HOME_FOLDER = "{HOME_FOLDER_ID}" | |
PLAYLIST_FILE = "panopto.xspf" | |
COOKIE_FILE = "cookie.txt" | |
OUTPUT_FILE = "videos.json" | |
def getFolderContents(folderID, session): | |
url = HOSTNAME + "/Panopto/Services/Data.svc/GetSessions" | |
headers = { | |
"content-type": "application/json; charset=UTF-8", | |
"accept": "*/*" | |
} | |
payload = { | |
"queryParameters": { | |
"folderID": folderID, | |
"getFolderData": True | |
} | |
} | |
cookies = {".ASPXAUTH": session} | |
response = requests.post(url, headers=headers, | |
cookies=cookies, json=payload) | |
contents = response.json()["d"] | |
if contents["Subfolders"] is None and not contents["Results"]: | |
return None | |
return contents | |
def fetchVideosData(session, folderID=HOME_FOLDER, store=False): | |
data = {"tree": [], "dict": {}} | |
stack = [("home", folderID, data["tree"], 0)] | |
parentMap = {"home": []} | |
def log(name, depth): | |
print("|--" * depth + "> " + name) | |
def getParents(parent): | |
parents = [] | |
while parent != "home": | |
parents.append(parent) | |
parent = parentMap[parent] | |
return parents | |
while stack: | |
name, folderID, parent, depth = stack.pop(0) | |
parent.append({ | |
"name": name, | |
"id": folderID, | |
"content": [], | |
}) | |
contents = getFolderContents(folderID, session) | |
log(name, depth) | |
if contents["Subfolders"]: | |
for folder in contents["Subfolders"]: | |
parentMap[folder["Name"]] = parent[-1]["name"] | |
stack.append((folder["Name"], folder["ID"], | |
parent[-1]["content"], depth + 1)) | |
if contents["Results"]: | |
parents = getParents(name)[::-1] | |
for video in contents["Results"]: | |
log(video["SessionName"], depth + 1) | |
timestamp = int(re.findall(r'\d+', video["StartTime"])[0])/1000 | |
data["dict"][video["DeliveryID"]] = { | |
"name": video["SessionName"], | |
"timestamp": timestamp, | |
"url": video["IosVideoUrl"], | |
"parents": parents | |
} | |
parent[-1]["content"].append(video["DeliveryID"]) | |
if store: | |
with open(OUTPUT_FILE, 'w') as file: | |
json.dump(data, file, indent=4) | |
return data["dict"].values() | |
def generatePlaylist(data, output=PLAYLIST_FILE): | |
res = "" | |
res += """<?xml version="1.0" encoding="UTF-8"?> | |
<playlist xmlns="http://xspf.org/ns/0/" xmlns:vlc="http://www.videolan.org/vlc/playlist/ns/0/" version="1"> | |
<title>Panopto</title> | |
<trackList> | |
""" | |
videos = sorted(data, key=lambda v: (*v["parents"], str(v["timestamp"]))) | |
for video in videos: | |
url = video["url"] | |
title = video["name"] | |
creator = "Panopto" | |
album = ", ".join(sorted(video["parents"])) | |
res += """ | |
<track> | |
<location>{}</location> | |
<title>{}</title> | |
<creator>{}</creator> | |
<album>{}</album> | |
<extension application="http://www.videolan.org/vlc/playlist/0"> | |
<vlc:option>sub-track=0</vlc:option> | |
</extension> | |
</track> | |
""".format(url, title, creator, album) | |
res += """ | |
</trackList> | |
</playlist>""" | |
res = res.replace("&", "and") | |
with open(output, 'w') as file: | |
file.write(res) | |
print("\nFound: {} videos".format(len(data))) | |
print("Output: {}".format(output)) | |
def getAuthCookie(): | |
session = "xxx" | |
try: | |
with open(COOKIE_FILE, 'r') as file: | |
session = file.read().strip() | |
except: | |
pass | |
while not getFolderContents(HOME_FOLDER, session): | |
session = input("Enter an ASPXAUTH cookie:") | |
with open(COOKIE_FILE, 'w+') as file: | |
file.write(session) | |
return session | |
def greet(): | |
print("______________________PANOPTO 2 PLAYLIST______________________\n") | |
print("This program will fetch all video information, using an internal Panopto API.") | |
print("A VLC compatible XML playlist will be generated.\n") | |
print("- A Panopto ASPXAUTH cookie is required.") | |
print("- Get it from DevTools -> Application -> Cookies.") | |
print("Enjoy :) - Sanjeet.\n") | |
def main(): | |
session = getAuthCookie() | |
data = fetchVideosData(session) | |
generatePlaylist(data) | |
if __name__ == "__main__": | |
greet() | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment