Skip to content

Instantly share code, notes, and snippets.

@saiki-k
Last active April 23, 2025 17:35
Show Gist options
  • Select an option

  • Save saiki-k/311c304a47fc954d320c4976282876c7 to your computer and use it in GitHub Desktop.

Select an option

Save saiki-k/311c304a47fc954d320c4976282876c7 to your computer and use it in GitHub Desktop.
A Python script for downloading videos (and corresponding information) from tldv.io meeting pages
import os
import re
import sys
import requests
import json
import subprocess
import tkinter as tk
from tkinter import filedialog
class TLDVDownloader:
def __init__(self, url, auth_token=None, download_dir=None):
self.url = url
self.meeting_id = self.extract_meeting_id()
self.auth_token = auth_token
self.download_dir = download_dir
self.meeting_info = None
self.video_url = None
self.total_duration_in_seconds = None
self.total_duration_str = None
def extract_meeting_id(self):
"""Extract the tldv.io meeting ID from the given tldv.io URL."""
try:
return self.url.split("/meetings/")[1].strip("/")
except IndexError:
print(
"\nInvalid URL format. Please ensure the URL is in the correct format."
)
sys.exit(1)
def request_meeting_info(self, include_transcript=False):
"""Send a request to get the meeting information."""
api_url = f"https://gw.tldv.io/v1/meetings/{self.meeting_id}/watch-page?noTranscript={'false' if include_transcript else 'true'}"
headers = (
{"Authorization": f"Bearer {self.auth_token}"} if self.auth_token else {}
)
# print(f"\nMaking a request to: {api_url}")
response = requests.get(api_url, headers=headers)
if response.status_code != 200:
print(
f"\nFailed to get meeting information. Status code: {response.status_code}"
)
print(f"Response content: {response.content}")
sys.exit(1)
self.meeting_info = response.json()
if "video" in self.meeting_info and "source" in self.meeting_info["video"]:
self.video_url = self.meeting_info["video"]["source"]
# print(f"\nVideo URL: {self.video_url}")
else:
print("\nUnable to get the video source from the meeting info.")
sys.exit(1)
def select_download_dir(self):
"""Prompt user to select a directory or use the provided one."""
if self.download_dir is None:
root = tk.Tk()
root.withdraw()
self.download_dir = filedialog.askdirectory()
print(f"\nThe selected download directory is {self.download_dir}.")
def time_to_seconds(self, time_str):
"""Convert time in the format H:M:S.ms to seconds."""
h, m, s = time_str.split(":")
return int(h) * 3600 + int(m) * 60 + float(s)
def format_duration(self, time_str):
"""Format time in the format H:M:S.ms into hours, minutes, and seconds."""
h, m, s = map(float, time_str.split(":"))
parts = []
if h > 0:
parts.append(f"{int(h)} hour{'s' if h != 1 else ''}")
if m > 0:
parts.append(f"{int(m)} minute{'s' if m != 1 else ''}")
parts.append(f"{s:.2f} second{'s' if s != 1 else ''}")
return ", ".join(parts)
def save_meeting_info(self, filename):
"""Save the requested meeting info as JSON in the directory specified by the user."""
json_file_name = f"{filename}.json"
json_file_path = os.path.join(self.download_dir, json_file_name)
with open(json_file_path, "w") as json_file:
json.dump(self.meeting_info, json_file, indent=2)
print(f"\nMeeting info saved as {json_file_name}, inside {self.download_dir}.")
def download_video(self, filename):
"""Download the video using ffmpeg in the directory specified by the user."""
mp4_file_path = os.path.join(self.download_dir, f"{filename}.mp4")
command = [
"ffmpeg",
"-protocol_whitelist",
"file,http,https,tcp,tls,crypto",
"-i",
self.video_url,
"-c",
"copy",
mp4_file_path,
]
process = subprocess.Popen(
command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
universal_newlines=True,
)
# Regex patterns for matching ffmpeg output
time_pattern = re.compile(r"time=(\d+:\d+:\d+.\d+)")
duration_pattern = re.compile(r"Duration: (\d+:\d+:\d+.\d+),")
for line in process.stderr:
# Only print ffmpeg stderr if something goes wrong
if process.poll() is not None and process.returncode != 0:
sys.stdout.write(line)
if self.total_duration_in_seconds is None:
duration_match = duration_pattern.search(line)
if duration_match:
duration_str = duration_match.group(1)
self.total_duration_in_seconds = self.time_to_seconds(duration_str)
self.total_duration_str = self.format_duration(duration_str)
print(
f"\nTotal video duration is {self.total_duration_str}. Downloading the video...\n"
)
time_match = time_pattern.search(line)
if time_match and self.total_duration_in_seconds:
current_time = self.time_to_seconds(time_match.group(1))
download_percent = (current_time / self.total_duration_in_seconds) * 100
print(f"\rVideo download progress: {download_percent:.2f}% ", end="")
process.wait()
if os.path.exists(mp4_file_path):
print(f"\rVideo download progress: 100% ", end="")
print(
f"\n\nVideo successfully downloaded as {filename}.mp4, inside {self.download_dir}.\n"
)
else:
print("\n\nFailed to convert the video. See above for errors.\n")
def run(self):
"""Run the downloader by setting up the necessary inputs and executing the download."""
self.request_meeting_info()
print("\nPlease choose a filename and the directory for the download(s).")
filename = input(
f"\nEnter a filename for the download(s) (press Enter to use {self.meeting_id} as the default): "
)
filename = filename if filename else self.meeting_id
print(
"\nSelect the download directory from the external file dialog that has opened."
)
self.select_download_dir()
action = input(
"\nWhat would you like to download? (1: Meeting video, 2: Meeting information (as JSON), 3: Both of the above): "
)
if action in ["2", "3"]:
self.save_meeting_info(filename)
if action in ["1", "3"]:
self.download_video(filename)
if __name__ == "__main__":
url = input(
"\nPlease paste the URL of the tldv.io meeting that you want to download: "
)
auth_token = input(
"\nPlease paste the tldv.io auth token (press Enter to skip for public meeting URLs): "
)
downloader = TLDVDownloader(url, auth_token)
downloader.run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment