initial commit
This commit is contained in:
commit
d4055ac0c1
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
__pycache__
|
||||
venv/
|
||||
**/*.mp4
|
14
README.md
Normal file
14
README.md
Normal file
@ -0,0 +1,14 @@
|
||||
# FAU-TV Video Downloader
|
||||
|
||||
Downloads all videos of a given course (even if the download has been restricted) to a local folder.
|
||||
This software is provided without warranty. Usage is discouraged! Usage of this software is at your own risk!
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
./dl '<fau course id>' '[output directory]' '[starter url]'
|
||||
```
|
||||
|
||||
# Procedure
|
||||
|
||||
Upon starting, a firefox instance is opened, prompting you to log in using IDM SSO. As soon as the url changes to something starting with `https://www.fau.tv`, download will be starting. If you do not want to provide your credentials, you can provide `https://www.fau.tv` as a starter url.
|
21
dl
Executable file
21
dl
Executable file
@ -0,0 +1,21 @@
|
||||
from .lib import *
|
||||
import sys
|
||||
import os
|
||||
|
||||
|
||||
def main():
|
||||
course_id = sys.argv[1]
|
||||
out_dir = sys.argv[2] if len(sys.argv) >= 3 else './out'
|
||||
auth_url = sys.argv[3] if len(sys.argv) >= 4 else None
|
||||
|
||||
load_token(auth_url) if auth_url is not None else load_token()
|
||||
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
|
||||
for index, clip_id in enumerate(get_course_clip_ids(course_id)):
|
||||
print(f'downloading clip {clip_id}')
|
||||
download_clip(clip_id, f'{out_dir}/{index+1: 04d}_{clip_id}.mp4')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
154
lib.py
Normal file
154
lib.py
Normal file
@ -0,0 +1,154 @@
|
||||
from selenium import webdriver
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
import requests
|
||||
import re
|
||||
import subprocess
|
||||
import shutil
|
||||
|
||||
|
||||
_token: "Token" = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class Token():
|
||||
auth_token: str
|
||||
session_id: str
|
||||
session_ci: str
|
||||
|
||||
def cookies(self):
|
||||
return {
|
||||
"SimpleSAMLAuthToken": self.auth_token,
|
||||
"SimpleSAMLSessionID": self.session_id,
|
||||
"session_ci": self.session_ci,
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class ClipDetails():
|
||||
combined_media_id: str = None
|
||||
combined_playlist_url: str = None
|
||||
camera_media_id: str = None
|
||||
camera_playlist_url: str = None
|
||||
slides_media_id: str = None
|
||||
slides_playlist_url: str = None
|
||||
|
||||
|
||||
def load_token(auth_url: str = "https://www.fau.tv/auth/sso"):
|
||||
global _token
|
||||
|
||||
driver = webdriver.Firefox()
|
||||
|
||||
driver.get(auth_url)
|
||||
|
||||
while not driver.current_url.startswith('https://www.fau.tv/'):
|
||||
time.sleep(0.5)
|
||||
|
||||
def get_value(cookie):
|
||||
if cookie is None:
|
||||
return None
|
||||
return cookie.get("value")
|
||||
|
||||
_token = Token(
|
||||
auth_token=get_value(driver.get_cookie("SimpleSAMLAuthToken")),
|
||||
session_id=get_value(driver.get_cookie("SimpleSAMLSessionID")),
|
||||
session_ci=get_value(driver.get_cookie("session_ci")),
|
||||
)
|
||||
|
||||
print(_token)
|
||||
|
||||
driver.close()
|
||||
|
||||
|
||||
def get_course_clip_ids(course_id: str) -> list[str]:
|
||||
global _token
|
||||
|
||||
regex = re.compile(r'(/clip/id/)([0-9]+)(\"\s*class=\"preview\")')
|
||||
|
||||
urls = []
|
||||
|
||||
url = f'https://www.fau.tv/course/id/{course_id}'
|
||||
with requests.get(url, cookies=_token.cookies()) as r:
|
||||
clip_matches = regex.findall(r.text)
|
||||
for match in clip_matches:
|
||||
urls.append(match[1])
|
||||
|
||||
return urls
|
||||
|
||||
|
||||
def get_clip_details(clip_id: str) -> ClipDetails:
|
||||
global _token
|
||||
|
||||
url = f'https://www.fau.tv/clip/id/{clip_id}'
|
||||
details = ClipDetails()
|
||||
|
||||
with requests.get(url, cookies=_token.cookies()) as r:
|
||||
def get_details(keyword: str):
|
||||
mediaid_re = re.compile(
|
||||
r'(' + keyword + r'Sources[^,]*,\s+mediaid\:\s+\")([0-9]+)'
|
||||
)
|
||||
|
||||
playlist_url_re = re.compile(
|
||||
r'(file\:\s+\")([^\"]*' + keyword + r'\.smil[^\"]*)(\")'
|
||||
)
|
||||
|
||||
mediaid_matches = mediaid_re.findall(r.text)
|
||||
if len(mediaid_matches) > 0:
|
||||
setattr(details, keyword + "_media_id", mediaid_matches[0][1])
|
||||
|
||||
playlist_url_matches = playlist_url_re.findall(r.text)
|
||||
if len(playlist_url_matches) > 0:
|
||||
setattr(details, keyword + "_playlist_url",
|
||||
playlist_url_matches[0][1])
|
||||
|
||||
get_details("combined")
|
||||
get_details("camera")
|
||||
get_details("slides")
|
||||
|
||||
return details
|
||||
|
||||
|
||||
def download_media(media_id: str, outfile_path: str):
|
||||
global _token
|
||||
|
||||
url = f'https://itunes.video.uni-erlangen.de/get/file/' + \
|
||||
str(media_id) + '?download=1'
|
||||
|
||||
with requests.get(url, stream=True, cookies=_token.cookies()) as r:
|
||||
if (r.status_code != 200):
|
||||
return False
|
||||
|
||||
with open(outfile_path, 'wb') as f:
|
||||
shutil.copyfileobj(r.raw, f)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def download_playlist(playlist_url: str, outfile_path: str):
|
||||
subprocess.run([
|
||||
'ffmpeg',
|
||||
'-i', playlist_url,
|
||||
'-c', 'copy',
|
||||
outfile_path,
|
||||
])
|
||||
|
||||
|
||||
def download_clip(clip_id: str, outfile_path: str):
|
||||
details = get_clip_details(clip_id)
|
||||
|
||||
media_id = next(id for id in [
|
||||
details.combined_media_id,
|
||||
details.camera_media_id,
|
||||
details.slides_media_id,
|
||||
] if id is not None)
|
||||
|
||||
if download_media(media_id, outfile_path):
|
||||
return
|
||||
|
||||
url = next(url for url in [
|
||||
details.combined_playlist_url,
|
||||
details.camera_playlist_url,
|
||||
details.slides_playlist_url,
|
||||
] if url is not None)
|
||||
|
||||
download_playlist(url, outfile_path)
|
5
pyvenv.cfg
Normal file
5
pyvenv.cfg
Normal file
@ -0,0 +1,5 @@
|
||||
home = /usr/bin
|
||||
include-system-site-packages = false
|
||||
version = 3.12.4
|
||||
executable = /usr/bin/python3.12
|
||||
command = /usr/bin/python -m venv /home/ludwig/git/fau-tv-dl
|
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@ -0,0 +1,2 @@
|
||||
selenium
|
||||
requests
|
Loading…
x
Reference in New Issue
Block a user