Skip to content

Instantly share code, notes, and snippets.

@Gottfr1ed
Last active May 28, 2025 04:34
Show Gist options
  • Select an option

  • Save Gottfr1ed/835465601f0dbcd5d4611bd151ba903f to your computer and use it in GitHub Desktop.

Select an option

Save Gottfr1ed/835465601f0dbcd5d4611bd151ba903f to your computer and use it in GitHub Desktop.
RUTUBE video/playlist downloader
#!/usr/bin/python3
import argparse
import subprocess
import urllib.request
import urllib.error
import json
import re
import os
import shutil
API_URL = "https://rutube.ru/api"
REQUEST_HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:131.0) Gecko/20100101 Firefox/131.0"
}
def url_open(url):
req = urllib.request.Request(url, headers=REQUEST_HEADERS)
with urllib.request.urlopen(req) as res:
return res.read()
def url_open_utf8(url):
return url_open(url).decode("utf-8")
def url_open_json(url):
return json.loads(url_open(url))
def download_video(video_id, dest_dir):
video_info_url = f"{API_URL}/play/options/{video_id}/"
video_info = url_open_json(video_info_url)
video_title = video_info["title"]
ts_path = f"{dest_dir}/{video_title}.ts"
mp4_path = f"{dest_dir}/{video_title}.mp4"
video_list_url = video_info["video_balancer"]["m3u8"]
video_list = url_open_utf8(video_list_url)
video_urls = re.findall(r"https:\S+", video_list)
video_url_highest_quality = video_urls[-1]
seg_base_url = video_url_highest_quality.split(".m3u8")[0]
seg_list = url_open_utf8(video_url_highest_quality)
seg_names = re.findall(r"segment-\S+", seg_list)
if not os.path.isdir(dest_dir):
os.makedirs(dest_dir)
with open(ts_path, "wb") as ts:
print(f'Downloading "{video_title}" ...')
for seg_idx, seg_name in enumerate(seg_names):
print(f"{seg_name} [{seg_idx}/{len(seg_names)}]")
seg_url = f"{seg_base_url}/{seg_name}"
seg_data = url_open(seg_url)
ts.write(seg_data)
print(f'Converting "{ts_path}" to "{mp4_path}" with FFmpeg ...')
subprocess.run(["ffmpeg", "-i", ts_path, mp4_path])
os.remove(ts_path)
if shutil.which("ffmpeg") is None:
print(f"FFmpeg is required to run this script. Please install it and try again.")
exit(1)
arg_parser = argparse.ArgumentParser(prog=os.path.basename(__file__))
arg_parser.add_argument("url", nargs="+")
arg_parser.add_argument("-d", "--destination", help="destination directory")
args = arg_parser.parse_args()
dest_dir = args.destination if args.destination else "rutube-videos"
for url in args.url:
video_pattern = r"^\s*(https:\/\/rutube\.ru)\/(video)\/(\w+)\/?\s*$"
video_match = re.fullmatch(video_pattern, url)
if video_match:
video_id = video_match.group(3)
download_video(video_id, dest_dir)
else:
playlist_pattern = r"^\s*(https:\/\/rutube\.ru)\/(plst)\/(\d+)\/?\s*$"
playlist_match = re.fullmatch(playlist_pattern, url)
if playlist_match:
playlist_id = playlist_match.group(3)
playlist_info_url = f"{API_URL}/playlist/custom/{playlist_id}"
playlist_info = url_open_json(playlist_info_url)
playlist_title = playlist_info["title"]
playlist_dest_dir = f"{dest_dir}/{playlist_title}"
video_infos = url_open_json(playlist_info_url + "/videos/")["results"]
for video_info in video_infos:
video_id = video_info["playlist_id"]
download_video(video_id, playlist_dest_dir)
else:
print(f'"{url}" is not a valid RUTUBE video/playlist URL, skipping...')
@Gottfr1ed
Copy link
Author

Gottfr1ed commented Nov 26, 2024

FFmpeg is required to run this script.

Usage example:
python3 downloader.py -d ~/rutube_videos https://rutube.ru/video/1q2w3e4r5t6y/ https://rutube.ru/plst/123456/

@squituit
Copy link

squituit commented Mar 10, 2025

The script works well. But it won't start as shown. To run, change the following lines:

91 video_infos = url_open_json(plst_info_url + "/videos/")["results"] ---> video_infos = url_open_json(playlist_info_url + "/videos/")["results"]

93 video_id = video_info["plst_id"] ---> video_id = video_info["id"]

94 download_video(video_id, plst_dest_dir) ---> download_video(video_id, playlist_dest_dir)

P.S - For Windows machines include ffmpeg/bin folder in Path.

@Gottfr1ed
Copy link
Author

Gottfr1ed commented May 21, 2025

@squituit Thank you. I decided to change the names of some variables and forgot to test the script :) Everything should be fine now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment