Skip to content

Instantly share code, notes, and snippets.

@thesanjeetc
Last active December 30, 2024 00:00
Show Gist options
  • Save thesanjeetc/f7b88b814ddac8f8117dc4102704102f to your computer and use it in GitHub Desktop.
Save thesanjeetc/f7b88b814ddac8f8117dc4102704102f to your computer and use it in GitHub Desktop.
Generate a VLC playlist from Panopto lectures for a better watching experience.
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