Hi folks,
rencently i've been watching this podcast from Spotify
Code:https://open.spotify.com/episode/0EBTCiYZl535zGYjbC6LXo?si=sNytZqqnTjes6MKP1wrSpA&nd=1
[Attachment 67013 - Click to enlarge]
It is protected, but I couldn't find any mpd or m3u8 file.
Could you please help me find the so-called playback file? It must have a list in which each video segment is listed.
Thank you~
+ Reply to Thread
Results 1 to 22 of 22
-
-
Interestingly, I've got the key but I don't know how to download this.
-
I couldn't find an mpd or m3u8 either, few months ago when I needed, so I resorted to sort of a manual way of downloading the init, the chunks and then using type command in Windows to concatenate them.
-
-
The webm podcasts are more tricky than the music tracks.
You can get the pssh by searching for 'drm' in the network tab and finding the pssh in the response (choose the widevine one). I suspect that response also describes the step size for the webm parts to download and the podcast duration.
The webm parts are numbered something like (this is from memory and probably not completely accurate, apart from the numbers):
https://****0web.webm*****
https://****4web.webm*****
https://****8web.webm*****
I guess that's a step size of 4.
Someone who knows how to use yt-dlp properly will probably know how to download all the parts with a single yt-dlp command, otherwise a script might be needed.
However...
1. I got an error using N_m3u8DL-RE with the keys and the webm urls (I didn't try hard, I could have just made a mistake on the command line, so this might still be the right way to go for webm)
2. yt-dlp downloaded the webm sections I asked it to without any issues, but mp4decrypt probably isn't the right tool for decrypting them, because it didn't decrypt them successfully.
Summary: the webm podcasts are more tricky than the music tracks. -
By the way, just now I checked that link I posted in last September, and found that "We couldn't find the page you want".
Is it because I changed my account region (US -> Pakistan) so Podcast videos are not available in the current location, or is it because that video is not availavle anymore??
[Attachment 70636 - Click to enlarge] -
There is no MPD with Spotify, and the PSSH is found inside 'supports_drm' or with EME logger extension. In 'supports_drm', you will also find a manifest of sorts where you can see all quality profiles. Not all podcast videos are encrypted, though, so the PSSH might be missing for a reason.
As for avoiding the thousands of files, I found that the best way is to use something like wget or curl to feed all the tiny files directly into two big video.mp4 and audio.mp4 and then merge it. FFmpeg do not like it when there are literally thousands of tiny files. This will also make it easier to use mp4decrypt on those videos that need it. -
I checked the link to the podcast you shared, and it appears to be a protected episode on Spotify. Unfortunately, finding a specific playback file or video clips may not be possible because Spotify uses its own streaming infrastructure. However, if you're looking for ways to access the content, I'd suggest contacting the creator of the podcast or Spotify support for help. They may be able to offer alternatives or clarify any restrictions on accessing content. Or you could try buying Spotify promo packages at songlifty.com to access quality broadcasts. I hope you find a solution, and happy listening to podcasts!
Last edited by iannzhjab; 13th Jun 2023 at 03:03.
-
Here is the downloader for Spotify Video Podcasts. Only free podcasts. Email and password are needed as well as CDM in wvd format (only for DRM content).
Code:import base64 import json import os from urllib.parse import urlparse import requests from librespot import util from librespot.core import Session from librespot.metadata import EpisodeId from pywidevine.cdm import Cdm from pywidevine.device import Device from pywidevine.pssh import PSSH EMAIL = "YOUR_EMAIL" PASSWORD = "YOUR_PASSWORD" WVD_FILE = "device_wvd_file.wvd" OUTPUT_FOLDER = os.path.join(".", "spotify_output") APP_RESOLVE_URL = 'https://apresolve.spotify.com/' MIME_INCLUDE = ["video/mp4", "audio/mp4"] def get_spotify_base_url(): t = "spclient" return json.loads(requests.get( APP_RESOLVE_URL, params={'type': [t]} ).content.decode())[t][0].split(":")[0] BASE_URL = get_spotify_base_url() MANIFEST_URL = 'https://' + BASE_URL + '/manifests/v7/json/sources/{manifest_id}/options/supports_drm' LICENSE_URL = f'https://{BASE_URL}/widevine-license/v1/video/license' def get_access_token(): session = Session.Builder().user_pass( EMAIL, PASSWORD ).create() return session, session.tokens().get("playlist-read") SESSION, ACCESS_TOKEN = get_access_token() def get_pssh_from_init(init_url): content = requests.get(init_url).content offsets = [] offset = 0 while True: offset = content.find(b'pssh', offset) if offset == -1: break size = int.from_bytes(content[offset - 4:offset], byteorder='big') pssh_offset = offset - 4 offsets.append(content[pssh_offset:pssh_offset + size]) offset += size pssh_list = [base64.b64encode(wv_offset).decode() for wv_offset in offsets] for pssh in pssh_list: if 70 < len(pssh) < 190: return pssh return None def get_keys(pssh_value): if pssh_value is None: return [] try: device = Device.load(WVD_FILE) except: return [] pssh_value = PSSH(pssh_value) cdm = Cdm.from_device(device) cdm_session_id = cdm.open() challenge = cdm.get_license_challenge(cdm_session_id, pssh_value) licence = requests.post( LICENSE_URL, data=challenge, headers={'authorization': f'Bearer {ACCESS_TOKEN}'} ) licence.raise_for_status() cdm.parse_license(cdm_session_id, licence.content) keys = [] for key in cdm.get_keys(cdm_session_id): if "CONTENT" in key.type: keys += [f"{key.kid.hex}:{key.key.hex()}"] cdm.close(cdm_session_id) return keys def get_base_url(base_urls, test_url): for base_url in base_urls: if 200 <= requests.get(f"{base_url}{test_url}").status_code < 300: return base_url def generate_segm_m3u8(output_path, content, profile, manifest): init_url = manifest["initialization_template"] init_url = init_url.replace("{{profile_id}}", str(profile["id"])) init_url = init_url.replace("{{file_type}}", str(profile["file_type"])) segm_url = manifest["segment_template"] segm_url = segm_url.replace("{{profile_id}}", str(profile["id"])) segm_url = segm_url.replace("{{file_type}}", str(profile["file_type"])) base_url = get_base_url(manifest["base_urls"], init_url) if base_url is None: return None init_url = f"{base_url}{init_url}" segm_url = f"{base_url}{segm_url}" segm_incr = content["segment_length"] last_segm = int(content["end_time_millis"] / 1000) m3u8_content = "#EXTM3U\n#EXT-X-VERSION:3\n\n" m3u8_content += f"#EXT-X-MAP:URI=\"{init_url}\"\n" for i in range(0, last_segm, segm_incr): segment = segm_url.replace("{{segment_timestamp}}", str(i)) m3u8_content += f"#EXTINF:{segm_incr:.3f},\n{segment}\n" m3u8_content += "#EXT-X-ENDLIST\n" with open(output_path, "w") as f: f.write(m3u8_content) return init_url def generate_master_m3u8(name, manifest): output_path = os.path.join(OUTPUT_FOLDER, name) if not os.path.exists(output_path): os.makedirs(output_path) m3u8_content = "#EXTM3U\n#EXT-X-VERSION:3\n\n" pssh = None for content in manifest["contents"]: max_res = max([p["video_resolution"] for p in content["profiles"] if "video_bitrate" in p]) for profile in content["profiles"]: p_id = profile["id"] mime_type = profile["mime_type"] if mime_type not in MIME_INCLUDE: star = True for check in ["video", "audio"]: if f"{check}/*" not in MIME_INCLUDE and f"{check}/" in mime_type: star = False break if not star: continue title = f"video_{p_id}.m3u8" if "video_bitrate" in profile else f"audio_{p_id}.m3u8" init_url = generate_segm_m3u8(os.path.join(output_path, title), content, profile, manifest) if init_url is None: continue if "video_bitrate" in profile: if profile["video_resolution"] == max_res and pssh is None: pssh = get_pssh_from_init(init_url) video_bitrate = profile["video_bitrate"] video_width = profile["video_width"] video_height = profile["video_height"] video_codec = profile["video_codec"] m3u8_content += f"#EXT-X-STREAM-INF:BANDWIDTH={video_bitrate},RESOLUTION={video_width}x{video_height},CODECS=\"{video_codec}\",TYPE=VIDEO,MIME-TYPE=\"{mime_type}\",AUDIO=\"Audio\"\n" m3u8_content += f"{title}\n" elif "audio_bitrate" in profile: audio_bitrate = profile["audio_bitrate"] audio_codec = profile["audio_codec"] m3u8_content += f'#EXT-X-MEDIA:TYPE=AUDIO,AUTOSELECT=YES,DEFAULT=YES,CHANNELS="2",GROUP-ID="Audio",URI="{title}\"\n' # m3u8_content += f"#EXT-X-STREAM-INF:BANDWIDTH={audio_bitrate},CODECS=\"{audio_codec}\",TYPE=AUDIO,MIME-TYPE=\"{mime_type}\"\n" # m3u8_content += f"{title}\n" for srt_code in manifest.get("subtitle_language_codes", []): srt_url = manifest["subtitle_template"].replace("{{language_code}}", srt_code) base_url = get_base_url(manifest["subtitle_base_urls"], srt_url) srt_url = f"{base_url}{srt_url}" _, srt_ext = os.path.splitext(os.path.basename(urlparse(srt_url).path)) srt_path = os.path.join(output_path, f"subtitle_{srt_code}{srt_ext}") with open(srt_path, 'w') as f: f.write(requests.get(srt_url).content.decode()) output_path = os.path.join(output_path, "master.m3u8") with open(output_path, "w") as f: f.write(m3u8_content) return output_path, pssh def get_video_data(episode_id): content_uri = f'spotify:episode:{episode_id}' episode = EpisodeId.from_uri(content_uri) manifest_id = util.bytes_to_hex( SESSION.api().get_metadata_4_episode(episode).video[0].file_id ) manifest = json.loads(requests.get( MANIFEST_URL.format(manifest_id=manifest_id), headers={'authorization': f'Bearer {ACCESS_TOKEN}'} ).content.decode()) return generate_master_m3u8(episode_id, manifest) def get_download_command(source_url): name = source_url.split("/")[-1] download_path, pssh = get_video_data(name) keys = get_keys(pssh) if len(keys) == 0: if pssh is not None: return f"Need local CDM (in WVD format) for {name}" return f'N_m3u8DL-RE.exe "{download_path}" -M format=mkv --save-name "{name}"' return f'N_m3u8DL-RE.exe "{download_path}" {" ".join([f"--key {k}" for k in keys])} -M format=mkv --save-name "{name}"' SOURCE_URLS = [ "https://open.spotify.com/episode/4dbjxVECWt1p2CcGrs7Wen", "https://open.spotify.com/episode/0OzuSfwPTCoPcjhnGsXOJa", "https://open.spotify.com/episode/3ppOLVKTWPsZeTk6r0l9Gg", "https://open.spotify.com/episode/0EBTCiYZl535zGYjbC6LXo", ] for s in SOURCE_URLS: print(get_download_command(s))
Code:N_m3u8DL-RE.exe ".\spotify_output\4dbjxVECWt1p2CcGrs7Wen\master.m3u8" -M format=mkv --save-name "4dbjxVECWt1p2CcGrs7Wen" N_m3u8DL-RE.exe ".\spotify_output\0OzuSfwPTCoPcjhnGsXOJa\master.m3u8" -M format=mkv --save-name "0OzuSfwPTCoPcjhnGsXOJa" N_m3u8DL-RE.exe ".\spotify_output\3ppOLVKTWPsZeTk6r0l9Gg\master.m3u8" --key 2dc54356f02f0aad15b5837eac4bad8b:6f08b5c8499e8f9328e5ca8266d7c949 -M format=mkv --save-name "3ppOLVKTWPsZeTk6r0l9Gg" N_m3u8DL-RE.exe ".\spotify_output\0EBTCiYZl535zGYjbC6LXo\master.m3u8" --key 690e36993c307d3cbec9b7863a6304ff:4b6c623daa65807ce0bbb31db8417b95 -M format=mkv --save-name "0EBTCiYZl535zGYjbC6LXo"
[Attachment 78167 - Click to enlarge]
[Attachment 78020 - Click to enlarge]
It is meant only for podcasts that are videos with or without DRM. Audio podcasts do not work with this script. Initially, I wanted to add support for them as well, but this has already been done and there are already existing tools for this type of content (DRM isn't even relevant for the audio ones because it can be skipped). But I haven't seen tools for video. If any subtitles are found, those are downloaded as well in the m3u8 folder and can be remuxed later manually.
An interesting problem was encountered. There is no m3u8 or MPD for this content. Spotify API returns a JSON that contains the necessary information to build one, but no m3u8/mpd. So the easy solution was to build an m3u8 and use existing tools on it. The generated m3u8 may show some weird information about bitrate, but at least the resolution is perfect and you can deduce which one is audio/video. Be sure to download it right away because the URL fragments contain query parameters that can expire. I've chosen m3u8 because it is easier to generate it by following a text based specification, rather than XML. Don't forget to delete the output folder at the end.
Edit: Changed the m3u8 to properly separate the audio and video in the N_m3u8 media selection. Thanks @snake for explaining the m3u8 format to achieve this.
Edit2: Modified the m3u8 format to auto select the audio track using n_m3u8. Thanks again @snake
Edit3: This script doesn't work anymore cause of outdated librespot. Also need real cdms.Last edited by 2nHxWW6GkN1l916N3ayz8HQoi; 22nd Oct 2024 at 02:39.
--[----->+<]>.++++++++++++.---.--------.
[*drm mass downloader: widefrog*]~~~~~~~~~~~[*how to make your own mass downloader: guide*] -
You're welcome
No problemAs I said to that person, I never solve private problems however I'm always willing to help directly (or indirectly) people I learned a lot from. I believe all problems and solutions should be publicly available so people can learn since this is a forum. And if I get more people interested in coding, that's a bonus.
--[----->+<]>.++++++++++++.---.--------.
[*drm mass downloader: widefrog*]~~~~~~~~~~~[*how to make your own mass downloader: guide*] -
Forgive my ignorance.
but how am I to use this code? looks like python but ofc I'm missing modules? Ive not used pywidevine before but i did install it using pip.
(Thus far I've been able to get keys I need through CDRM-Project and then download with from mpd + keys with N_m3u8DL-RE)
However for spotify, I just don't understand how they've parsed the files at all. If I attempt to grab what I believe to be manifest I get errored or a json file lol
indeed a baby at all of this I most sincerely appreciate any/all guidance. -
Noob Starter Pack. Just download every Widevine mpd! Not kidding!.
https://files.videohelp.com/u/301890/hellyes6.zip -
Hi so right now I'm just trying to use as-is with his example urls
Code:SOURCE_URLS = [ "https://open.spotify.com/episode/4dbjxVECWt1p2CcGrs7Wen", "https://open.spotify.com/episode/0OzuSfwPTCoPcjhnGsXOJa", "https://open.spotify.com/episode/3ppOLVKTWPsZeTk6r0l9Gg", "https://open.spotify.com/episode/0EBTCiYZl535zGYjbC6LXo", ]
-
-
Okay, I'm an idiot. Thank you.
I should've known better to try again once rested.
Initially I had caught the error: " ModuleNotFoundError: No module named 'librespot' "
ofc that just a matter of installing. But what happened is I had installed pywidevine afterwards which caused it to break. Now reading logs I can see that librespot "requires protobuf==3.20.1" & "requires requests==2.30.0" which conflicts with pywidevine which "requires protobuf<5.0.0,>=4.25.1" & "requires requests<3.0.0,>=2.31.0" so I just had to downgrade my requests and protobuf modules to match librespot requirements and it works as intended.
Ofc this means I can't utilize pywidevine, which in turn means I wouldn't be able to get keys for anything DRM protected. Right now it isn't much of an issue for me as it seems what the couple podcast episodes I was after weren't protected. My guess then is I'd just have to downgrade pywidevine to a lower version that is compatible with the current requirements by librespot.
Sorry I wasted peoples time.
I'm trying to use these tools the best I can, ofc I should always be mindful that more often than not the weakest link is me and sometimes a bit of rest and some coffee does wonders to the obvious solutions to trivial problems. Oh well, bound to make many such mistakes as I learn I suppose best I get comfortable becoming the fool -
You didn't waste anyone's time, no worries. I had that same inconvenience when I first installed librespot, which is where python virtual environments come in handy: https://forum.videohelp.com/threads/411862-Beyond-WKS-KEYS
-
Ofc why didnt I think of virtual envs. This is where pipx would come in handy then (I knew I'd eventually have a need for this- good bookmark) https://github.com/pypa/pipx for anyone interested if I understand correctly, this will be simpler way to manage my 'environments' without the need to call on the activation every time.
Awesome community, thanks for your help white_snake!
Similar Threads
-
spotify ripper
By lomero in forum AudioReplies: 7Last Post: 1st Aug 2023, 14:08 -
Download Spotify ad-style videos - ie Takeovers?
By fireplayer in forum Video Streaming DownloadingReplies: 33Last Post: 2nd Jun 2022, 00:29 -
Is there any video option like Spotify or Google Access?
By mikehende in forum Newbie / General discussionsReplies: 9Last Post: 24th Dec 2019, 09:44 -
PodCast
By biferi in forum Newbie / General discussionsReplies: 1Last Post: 23rd Apr 2019, 00:23 -
Spotify video - how to do it
By kevinrm17 in forum Video Streaming DownloadingReplies: 1Last Post: 13th Apr 2019, 08:22