Hi,
no error on my side (Windows 10)
Code:python widefrog.py https://www.tf1.fr/tfx/la-villa-des-coeurs-brises/videos/la-villa-saison-08-episode-01-14350616.html?playlist=la-saison-08-de-la-villa-09810740 [INFO] Current app version: 3.2.0 [INFO] Running on: windows/64bit/python 3.10.11 [INFO] Starting the service initialization stage. [INFO] Current progress: [1/1] [INFO] Starting the collections extraction stage. [INFO] Current progress: [1/1] [INFO] Starting the media data extraction stage. [INFO] Current progress: [1/1] [INFO] Finished generating the download commands. [INFO] Saved the generated download commands to app_files\cmds.txt [INFO] If you used a VPN, turn it off to avoid wasting data for the downloading stage. Also you may edit the app_files\cmds.txt file if necessary. Type yes when ready: yes [INFO] Starting the downloading stage.
Support our site by donate $5 directly to us Thanks!!!
Try StreamFab Downloader and download streaming video from Netflix, Amazon!
Try StreamFab Downloader and download streaming video from Netflix, Amazon!
+ Reply to Thread
Results 481 to 510 of 528
-
-
Hi guys!

First off, thanks for making this tool @2nHxWW6GkN1l916N3ayz8HQoi! It seems very comprehensive and capable of getting keys for practically any MPD, but I can't actually test that since I'm getting:I'm not asking you to implement such a service; I'd just like some help in doing so myself please. I don't even know where to start, since you mention nothing about it in this thread (at least as far as I can tell through searching and scanning).Code:[USER_ERROR] Failed to download: https://cdn.bitmovin.com/content/assets/art-of-motion_drm/mpds/11331.mpd. Reason: No service available for cdn.bitmovin.com. Solution: Implement it.
Apologies if this has been posted about before, but I really don't want to spend hours reading all 11 pages of this massive thread if it can be avoided, especially when most of you are just talking about bugs and already-supported services, judging from what I have seen (I've read the first three pages and scanned the rest briefly).
So just a basic rundown of what's needed to implement a custom service would be awesome, and very much appreciated! Many thanks in advance
PS: I'm a pretty proficient Python dev as is, so no need to assume the usual n00b level of programming-based intelligence that is shown across this forum lol
Last edited by CoopMeisterFresh; 24th Aug 2025 at 03:37.
-
-
-
-
It looks like TF1 ramped up their encryption since the beginning of the month, every media downloaded is now corrupted visually with no sound.
-
[Attachment 88490 - Click to enlarge]
cmds.txt
N_m3u8DL-RE "https://vod-das.cdn-0.diff.tf1.fr/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjaXAiOiI5M i4xMjguMTEzLjkwIiwiY21jZCI6IiIsImV4cCI6MTc1NjY5MDM 4NiwiZ2lkIjoiYzU2NjJkNGQ5ZmQzNGYxZGIwNjFjYmE1NGMyN TIzYjkiLCJpYXQiOjE3NTY2NzU5ODYsImlzcyI6ImRlbGl2ZXJ 5IiwibWF4YiI6NTc0OTAwMCwic3RlbSI6Ii8yL1VTUC0weDAvM DcvOTYvMTQzNTA3OTYvc3NtLzg0YzQzOGQzOWRmOWFjZTliNDQ zNWNhOGY1Zjc5YzA0Njg2NGViZTJkZTc3YzQ5NGY0OTBkOTMxZ WMwM2Q5ZTMuaXNtLzE0MzUwNzk2Lm1wZCIsInN1YiI6ImM1NjY yZDRkOWZkMzRmMWRiMDYxY2JhNTRjMjUyM2I5In0.VyeYHIR7W 7qisH3rqhQVTeI_vssg_tZyoDOhGRTTml0/2/USP-0x0/07/96/14350796/ssm/84c438d39df9ace9b4435ca8f5f79c046864ebe2de77c494f4 90d931ec03d9e3.ism/14350796.mpd" --tmp-dir "media\tmp\Snhyyur" --save-dir "media\tf1_fr\Avant-premiere_-_La_Villa_-_Saison_10_Episode_18_du_2_septembre_2025" --save-name "content" -sv res="1024x576":for=best -sa best -da id=audio_div -ss all -mt --check-segments-count false --no-log -M format=mkv & "terminal\mkvmerge.bat" "media\tf1_fr\Avant-premiere_-_La_Villa_-_Saison_10_Episode_18_du_2_septembre_2025.mkv" "media\tf1_fr\Avant-premiere_-_La_Villa_-_Saison_10_Episode_18_du_2_septembre_2025" true & rd /s /q "media\tmp\Snhyyur"
no detect keys -
It's a User agent problem. Replace your tf1_fr.py by this. In your config file use this user agent for instance.
"USER_AGENT": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:142.0) Gecko/20100101 Firefox/142.0",Last edited by pleg33; 2nd Sep 2025 at 23:24.
-
sa ne fonctonne pas ce que ta donnez sa met sa
mon tf1.py
import json
import re
from os.path import join
import requests
from utils.constants.macros import ERR_MSG, USER_ERROR, APP_ERROR
from utils.main_service import main_service
from utils.structs import BaseElement, CustomException, BaseService
from utils.tools.args import check_range
from utils.tools.common import get_valid_filename
class tf1_fr(BaseService):
DEMO_URLS = [
"https://www.tf1.fr/tf1/gladiators/videos/extract",
"https://www.tf1.fr/tf1/les-freres-scott/videos",
"https://www.tf1.fr/tf1/les-bracelets-rouges/videos/extract",
"https://www.tf1.fr/tmc/les-mysteres-de-l-amour/videos",
"https://www.tf1.fr/tf1/gladiators/videos",
"https://www.tf1.fr/tmc/quotidien-avec-yann-barthes/videos/invites-fanny-ardant-et-thierry-klifa-rois-de-la-piste-et-de-larnaque-85679919.html",
"https://www.tf1.fr/tmc/la-mode-by-loic-prigent/videos/5-minutes-de-mode-by-loic-prigent-du-8-mars-2024-73931950.html",
"https://www.tf1.fr/tf1/double-zero/videos/double-zero-70592950.html",
]
LOGIN_URL = 'https://compte.tf1.fr/accounts.login'
TOKEN_URL = 'https://www.tf1.fr/token/gigya/web'
MEDIAINFO_URL = 'https://mediainfo.tf1.fr/mediainfocombo/{media_id}'
PLAYER_URL = 'https://prod-player.tf1.fr'
BASE_URL = 'https://www.tf1.fr'
LICENSE_URL = "https://widevine-proxy-m.prod.p.tf1.fr/proxy"
EMAIL = "YOUR_EMAIL"
PASSWORD = "YOUR_PASSWORD"
BEARER_TOKEN = None
API_KEY, CONSENT_IDS, PLAYER_VERSION = None, None, None
USER_AGENT = builtins.CONFIG["USER_AGENT"]
@staticmethod
def test_service():
main_service.run_service(tf1_fr)
@staticmethod
def credentials_needed():
return {
"EMAIL": tf1_fr.EMAIL,
"PASSWORD": tf1_fr.PASSWORD
}
@staticmethod
def format_version(version):
major, minor, patch = map(int, version.split('.'))
return str(major * 1000000 + minor * 1000 + patch)
@staticmethod
def get_tf1_info():
response = requests.get(
tf1_fr.BASE_URL,
headers={'User-Agent': tf1_fr.USER_AGENT}).content.decode().replace('\\"' , '"')
api_key = re.findall(r'"apiKey":"([^"]+)"', response)[0]
consent_ids = re.findall(r'neededConsentIds":\[(.*?)]', response)[0].replace("\"", "").split(",")
player_version = re.findall(
rf'"playerEndpoint":"{tf1_fr.PLAYER_URL}/","version":"([^"]+)"', response
)[0]
return api_key, consent_ids, tf1_fr.format_version(player_version)
@staticmethod
def get_bearer_token():
class_name = tf1_fr.__name__.replace("_", ".")
credentials = builtins.CONFIG["SERVICE_CREDENTIALS"][class_name]
try:
assert type(credentials["EMAIL"]) is str
assert type(credentials["PASSWORD"]) is str
except:
return None
response = json.loads(requests.post(
tf1_fr.LOGIN_URL,
data={
"loginID": credentials["EMAIL"],
"password": credentials["PASSWORD"],
"APIKey": tf1_fr.API_KEY
}, headers={'User-Agent': tf1_fr.USER_AGENT}
).content.decode())
status_code = response.get('statusCode', None)
if status_code != 200:
if status_code == 403:
return None
raise CustomException(ERR_MSG.format(
type=f'{APP_ERROR}',
url=f"from the {tf1_fr.__name__} service",
reason=f"Unknown error encountered: {str(response)}",
solution="Debug the service"
))
return json.loads(requests.post(
tf1_fr.TOKEN_URL,
json={
'uid': response["UID"], 'signature': response["UIDSignature"],
'timestamp': int(response["signatureTimestamp"]),
'consent_ids': tf1_fr.CONSENT_IDS
}
,
headers={'User-Agent': tf1_fr.USER_AGENT}).content.decode())["token"]
@staticmethod
def initialize_service():
if tf1_fr.API_KEY is None or tf1_fr.CONSENT_IDS is None or tf1_fr.PLAYER_VERSION is None:
tf1_fr.API_KEY, tf1_fr.CONSENT_IDS, tf1_fr.PLAYER_VERSION = tf1_fr.get_tf1_info()
if tf1_fr.BEARER_TOKEN is None:
tf1_fr.BEARER_TOKEN = tf1_fr.get_bearer_token()
if tf1_fr.BEARER_TOKEN is None:
return None
return tf1_fr
@staticmethod
def get_keys(challenge, additional):
licence = requests.post(
tf1_fr.LICENSE_URL, data=challenge, headers={'User-Agent': tf1_fr.USER_AGENT})
licence.raise_for_status()
return licence.content
@staticmethod
def get_video_data(source_element):
matches = re.findall(
r'"embedUrl":"([^"]+)"', requests.get(
source_element.url, headers={'User-Agent': tf1_fr.USER_AGENT}).content.decode()
)
match = [m for m in matches if "/player/" in m]
if len(match) == 0:
raise CustomException(ERR_MSG.format(
type=f'{USER_ERROR}',
url=source_element.url,
reason="Content isn't available anymore",
solution="Do not attempt to download it"
))
media_id = re.findall(r'/player/([^/]+)', match[0])[0]
response = requests.get(
tf1_fr.MEDIAINFO_URL.format(media_id=media_id),
params={'pver': tf1_fr.PLAYER_VERSION, 'context': 'context'},
headers={'authorization': f'Bearer {tf1_fr.BEARER_TOKEN}','User-Agent': tf1_fr.USER_AGENT}
)
response = response.content.decode()
if "geoblocked" in response.lower():
raise CustomException(ERR_MSG.format(
type=f'{USER_ERROR}',
url=source_element.url,
reason="Need French IP to access content",
solution="Use a VPN"
))
response = json.loads(response)
if (response.get("delivery", {}).get("code", None) == 4034 or
"permission_denied" in response.get("media", {}).get("error_code", "").lower()):
raise CustomException(ERR_MSG.format(
type=f'{USER_ERROR}',
url=source_element.url,
reason="Premium content can't be downloaded",
solution="Do not attempt to download it"
))
if source_element.element is None:
source_element.element = get_valid_filename(response.get("media", {}).get("title", media_id))
if source_element.collection is None:
source_element.collection = join(
str(builtins.CONFIG["DOWNLOAD_COMMANDS"]["OUTPUT_MEDIA_PATH"]),
tf1_fr.__name__
)
manifest = response["delivery"]["url"]
try:
pssh_value = str(min(re.findall(
r'<cencssh>(.+?)</cencssh>',
requests.get(
manifest, headers={'User-Agent': tf1_fr.USER_AGENT}).content.decode()
), key=len))
except:
pssh_value = None
if builtins.CONFIG.get("BASIC", False) is True:
manifest = re.sub(r'/eyJ[^/]*/', '/<TOKEN>/', manifest)
return manifest, pssh_value, {}
@staticmethod
def get_episode_index(label):
if label is None or len(label) == 0:
return None
label = label.lower()
label = re.sub(r'\s+', ' ', label)
label = label.replace(' ', "_")
for s in ["e", "pisode_", "pisodes_", "part_", "emission_"]:
try:
return int(re.findall(fr'{s}(\d+)', label)[0])
except:
if s == "emission_" and "emission_" in label:
return 1
return None
@staticmethod
def get_collection_elements(collection_url):
if "/playlist" in collection_url:
return None
if "/videos/" in collection_url and ".html" in collection_url:
return [BaseElement(url=collection_url)]
if "/videos" in collection_url:
collection = []
try:
section = re.search(r"/videos/([^/?]*)", collection_url).group(1)
except:
section = None
response = requests.get(
collection_url, headers={'User-Agent': tf1_fr.USER_AGENT}).content.decode()
collection_name = re.search(r"([^/]+)/videos", collection_url).group(1)
collection_name = get_valid_filename(collection_name)
re_href = 'href="'
re_href += re.search(fr"{tf1_fr.BASE_URL}(.*?)/videos", collection_url).group(1)
re_href += "/videos/"
if section is not None and len(section) > 0:
collection_name = collection_name + "_" + section
collection_name = get_valid_filename(collection_name)
matches = re.findall(fr'({re_href}.*?\.html)"[^>]*><div[^>]*>([^<]+)</div>', response)
if len(matches) == 0:
matches = re.findall(fr'({re_href}.*?\.html)"[^>]*>([^<]+)<', response)
matches = [(
tf1_fr.BASE_URL + m[0].split('"')[1], m[1]
) for m in matches]
matches = [(*value, index + 1) for index, value in enumerate(matches)]
for content_url, content_name, content_index in matches:
check = check_range(False, None, content_index)
if check is True:
continue
elif check is False:
return collection
collection.append(BaseElement(
url=content_url,
collection=join(join(
str(builtins.CONFIG["DOWNLOAD_COMMANDS"]["OUTPUT_MEDIA_PATH"]),
tf1_fr.__name__
), collection_name),
element=f'{content_index}'
f'_'
f'{get_valid_filename(content_name)}'
))
return collection
re_href += "saison-"
matches = re.findall(fr'{re_href}\d+"', response)
matches = [m.split('"')[1] for m in matches]
matches = [(
f'{tf1_fr.BASE_URL}{m}', int(m.split("/videos/saison-")[1])
) for m in matches]
matches = sorted(matches, key=lambda m: m[1])
re_href = re_href.replace("saison-", "")
for season_url, season_index in matches:
check = check_range(True, season_index, None)
if check is True:
continue
elif check is False:
return collection
page = 0
while True:
page += 1
if page > 1:
response = requests.get(
f'{season_url}/{page}', allow_redirects=False, headers={'User-Agent': tf1_fr.USER_AGENT})
try:
assert 300 <= response.status_code < 400
assert len(response.headers["Location"]) > 0
assert response.headers["Location"] in season_url
break
except:
pass
else:
response = requests.get(
f'{season_url}/{page}', headers={'User-Agent': tf1_fr.USER_AGENT})
response = response.content.decode()
matches = re.findall(fr'({re_href}.*?\.html)"[^>]*><div[^>]*>([^<]+)</div>', response)
if len(matches) == 0:
matches = re.findall(fr'({re_href}.*?\.html)"[^>]*>([^<]+)<', response)
matches = [(
tf1_fr.BASE_URL + m[0].split('"')[1],
m[1],
tf1_fr.get_episode_index(m[1])
) for m in matches]
matches = [m for m in matches if m[2] is not None]
for episode_url, episode_name, episode_index in matches:
check = check_range(False, season_index, episode_index)
if check in [True, False]:
continue
collection.append(BaseElement(
url=episode_url,
collection=join(
join(
str(builtins.CONFIG["DOWNLOAD_COMMANDS"]["OUTPUT_MEDIA_PATH"]),
tf1_fr.__name__
),
join(collection_name, f'Season_{season_index}')
),
element=f"Episode_{episode_index}"
f"_"
f"{get_valid_filename(episode_name)}"
))
if len(matches) == 0:
break
return collection
return None -
j'ai une question possible d'ajouter le service crunchyroll je ses pas comment ajouter des services
-
Hello, has anyone been able to use the tf1 service successfully?
I even tried this and updated the tf1 script but it did not work.
Code:[INFO] Current app version: 3.2.0 [INFO] Running on: windows/64bit/python 3.13.7 [INFO] Starting the service initialization stage. [APP_ERROR] Failed to download: https://www.tf1.fr/tmc/quotidien-avec-yann-barthes/videos/quotidien-premiere-partie-du-5-septembre-2025-02055522.html. Reason: Failed to initialize the tf1_fr service. Solution: Debug the service. [INFO] Current progress: [1/1] [INFO] Starting the collections extraction stage. [INFO] Starting the media data extraction stage. [INFO] Finished generating the download commands. [WARNING] Saved the failed URLs to app_files\cmds_failed.txt [WARNING] No download commands have been generated.
-
For Windows users download & replace widefrog.exe file
PLEASE BE CAREFUL WHEN DOWNLOADING FILES FROM NEW USERS!
Virustotal
https://www.virustotal.com/gui/file/90e1afac91ba60819435744cbc2cb1611f6d46fa722c502387...407c?nocache=1Last edited by Baldrick; 9th Sep 2025 at 12:51. Reason: add warning
-
-
OP hasn’t been active since March 26, 2025.
I hope everything is okay with him. -
-
Hi,
Since modifying the tf1_fr.py file, single downloads work again, but bulk downloads don't work via this type of URL:
https://www.tf1.fr/tf1/tout-pour-la-lumiere/videos/saison-1
[INFO] Current app version: 3.1.0
[INFO] Running on: windows/64bit/python 3.13.6
[INFO] Starting the service initialization stage.
[INFO] Current progress: [1/1]
[INFO] Starting the collections extraction stage.
[INFO] Current progress: [1/1]
[INFO] Starting the media data extraction stage.
[INFO] Finished generating the download commands.
[WARNING] No download commands have been generated. -
[INFO] Current app version: 3.2.0
[INFO] Running on: windows/64bit/python 3.11.0
[INFO] Starting the service initialization stage.
[INFO] Current progress: [1/1]
[INFO] Starting the collections extraction stage.
[INFO] Current progress: [1/1]
[INFO] Starting the media data extraction stage.
[USER_ERROR]/[APP_ERROR] Failed to download: https://www.tf1.fr/tf1/plus-belle-la-vie/videos/avant-premiere-plus-belle-la-vie-encor...-29975900.html. Reason: Something went wrong with the license call. Solution: Make sure you can play the content and/or change your VPN IP. Additionally, wait a few minutes and/or replace your CDM with a fresh one. If the issue persists, then debug the tf1_fr service.
[INFO] Finished generating the download commands.
[WARNING] Saved the failed URLs to app_files\cmds_failed.txt
[WARNING] No download commands have been generated.
Appuyez sur une touche pour continuer...
Terminer le programme de commandes (O/N)*?
TF1 on blacklisté mon CDM je fait comment -
I have the same problem. It's not your CDM that's blacklisted...
A few weeks ago, we already had a user-agent problem...
There have certainly been technical changes to the TF1 website.
The "tf1_fr.py" file in the "services" folder is therefore no longer valid without an update.
I hope someone will be able to do this, as I don't have the skills to create a new version of this file that works with the website updates. -
tf1 a changé leur syteme de login maintenant plus besoin ce login c tout dans un seul truc
👉 TF1 n’utilise plus __NEXT_DATA__.
Ils chargent les infos vidéo via un autre mécanisme (React + Apollo/GraphQL). Les pages contiennent des gros blocs comme window.__APOLLO_STATE__ ou des script inline avec des clés contentId, videoId, Product::TF1_xxx.
C’est pour ça que mon code échoue : il cherche encore le vieux bloc.
✅ Ce qu’il faut faire maintenant
Scanner tous les <script> à la recherche d’un JSON contenant "contentId" ou "Product::".
Extraire ce champ → ça nous donne l’ID vidéo.
Utiliser cet ID avec l’API https://player.tf1.fr/{contentId}?device=browser (ça, ça n’a pas changé). -
I managed to fix the latest tf1_fr service issue the following way:
1. Modify the get_video_data(source_element) method:
Replace the last line:
with this block:Code:return manifest, pssh_value, {}
2. Modify the get_keys(challenge, additional) method:Code:additional = {} if "drms" in response["delivery"]: drm = response["delivery"]["drms"] for k in drm: if "widevine" in k["name"].lower(): additional["license_url"] = k["url"] break return manifest, pssh_value, additional
Replace the first line:
with:Code:licence = requests.post(tf1_fr.LICENSE_URL, data=challenge, headers={"User-Agent": tf1_fr.USER_AGENT})
Code:licence = requests.post(additional["license_url"], data=challenge, headers={"User-Agent": tf1_fr.USER_AGENT})Last edited by gibolin; 25th Sep 2025 at 04:39. Reason: tf1_fr.zip file attached
-
Thank you!
It's OK again for a video with a URL that points to the video itself.
However, we're still having an issue with bulk downloads for this type of URL:
https://www.tf1.fr/tf1/tout-pour-la-lumiere/videos/saison-1 -
@Sardine1508, the two issues are unrelated.
The collection download is handled in the get_collection_elements method, and the issue you pointed out came from the regex not matching the episode URLs anymore.
Parsing HTML with regex is tricky, and it would probably be more robust to rewrite this method using BeautifulSoup...
For now, I made a quick and dirty fix that should solve the problem, see attachment. -
Hi, anyone got fix for m6.fr ? also how to setup to download audio eng by default not fr ?
widefrog_v3_2_0_python_source_code(1)\services\m6_ fr.py", line 397, in get_series_handler
adapter = HTTPAdapter(max_retries=Retry(
TypeError: Retry.__init__() got an unexpected keyword argument 'backoff_max'
Press any key to continue . . .
Last edited by munez7; 30th Sep 2025 at 09:34.
-
Hi everyone! I'm trying to download a video from mediasetinfinity.mediaset.it but I get this error. How can I fix it?
-
Hi everyone, Widefrog worked great for months, but for a few days now it hasn't been able to work on Mediaset Infinity. Is there a solution?
failed to initialize the mediasetinfinity_mediaset_it serviceLast edited by esamax2000; 3rd Oct 2025 at 02:20.
-
quelquin peux me corriger pour rmcbfmplay car il a etait mis a jour il fonctionne plus
import base64
import builtins
import json
import re
from os.path import join
from urllib.parse import urlparse, parse_qs
import requests
from bs4 import BeautifulSoup
from utils.constants.macros import USER_ERROR, ERR_MSG
from utils.main_service import main_service
from utils.structs import BaseElement, CustomException, BaseService
from utils.tools.args import check_range
from utils.tools.common import get_valid_filename
class rmcbfmplay_com(BaseService):
DEMO_URLS = [
"https://www.rmcbfmplay.com/info-programme/rmc-bfm-play/australie-la-ruee-vers-lor?contentId=Product::NEUF_BFMAVOD_BAV_6232189865 27&universe=PROVIDER",
"https://www.rmcbfmplay.com/info-programme/bfm-tv/michel-edouard-leclerc-a-tout-prix?contentId=Product::NEUF_BFMTV_BFM989735110527 &universe=PROVIDER",
"https://www.rmcbfmplay.com/info-programme/rmc-decouverte/seuls-face-a-lalaska?contentId=Product::NEUF_RMCDEC_RMCP4341198 &universe=PROVIDER",
"https://www.rmcbfmplay.com/video/rmc-story/alien-theory/s6e10-lere-technologique?contentId=Product::NEUF_NUM23_N23410 89&universe=PROVIDER",
"https://www.rmcbfmplay.com/video/rmc-story/le-pal-au-coeur-du-zoo-le-plus-insolite-de-france?contentId=Product::NEUF_NUM23_N235446923275 27&universe=PROVIDER",
"https://www.rmcbfmplay.com/video/rmc-bfm-play/alexandre-le-grand-la-vraie-histoire-dun-conquerant-de-legende?contentId=Product::NEUF_BFMAVOD_BAV9842248 66527&universe=PROVIDER",
]
CONFIG_URL = 'https://www.rmcbfmplay.com/assets/configs/config.json'
CAS_AUTH_URL = 'https://sso.rmcbfmplay.com/cas/oidc/authorize'
CONTENT_URL = 'https://ws-backendtv.rmcbfmplay.com/gaia-core/rest/api/web/v3/content/{content_id}/options'
DETAIL_URL = 'https://ws-cdn.tv.sfr.net/gaia-core/rest/api/web/v1/content/{content_id}/detail'
EPISODES_URL = 'https://ws-cdn.tv.sfr.net/gaia-core/rest/api/web/v1/content/{content_id}/episodes'
VIDEO_URL = 'https://www.rmcbfmplay.com/video?contentId={content_id}&universe={universe}'
LICENSE_URL = None
EMAIL = 'YOUR_EMAIL'
PASSWORD = 'YOUR_PASSWORD'
BFM_TOKEN = None
APP_VALUE = None
CONFIG_CONTENT = None
PAGE_LIMIT = 50
RES_PRIORITY = {"sd": 0, "hd": 1}
@staticmethod
def test_service():
main_service.run_service(rmcbfmplay_com)
@staticmethod
def is_content_livestream(content, additional):
return "/direct-tv/" in content
@staticmethod
def credentials_needed():
return {
"EMAIL": rmcbfmplay_com.EMAIL,
"PASSWORD": rmcbfmplay_com.PASSWORD
}
@staticmethod
def get_token():
class_name = rmcbfmplay_com.__name__.replace("_", ".")
credentials = builtins.CONFIG["SERVICE_CREDENTIALS"][class_name]
try:
assert type(credentials["EMAIL"]) is str
assert type(credentials["PASSWORD"]) is str
except:
return None
with requests.session() as login_session:
response = login_session.get(
rmcbfmplay_com.CAS_AUTH_URL, allow_redirects=False, params={
'client_id': rmcbfmplay_com.CONFIG_CONTENT["auth"]["OIDC_CLIENT_ID"],
'scope': 'openid', 'response_type': 'token',
'redirect_uri': 'https://www.rmcbfmplay.com'
}
)
redirect = response.headers["location"]
response = login_session.get(redirect)
soup_login = BeautifulSoup(response.content, 'html5lib')
form_data = {}
for element in soup_login.find('form').find_all('input'):
if element.has_attr('value'):
form_data[element['name']] = element['value']
form_data['username'] = credentials["EMAIL"]
form_data['password'] = credentials["PASSWORD"]
form_data['remember-me'] = 'on'
response = login_session.post(redirect, allow_redirects=False, data=form_data)
redirect = response.headers["location"]
response = login_session.get(redirect, allow_redirects=False)
redirect = response.headers["location"]
token = parse_qs(urlparse(redirect).query)["access_token"][0]
for b64 in token.split("."):
try:
return json.loads(base64.b64decode(b64 + "==").decode())["tu"]
except:
pass
return None
@staticmethod
def initialize_service():
if rmcbfmplay_com.CONFIG_CONTENT is None:
rmcbfmplay_com.CONFIG_CONTENT = json.loads(requests.get(rmcbfmplay_com.CONFIG_URL) .content.decode())
rmcbfmplay_com.APP_VALUE = rmcbfmplay_com.CONFIG_CONTENT["application"]["app"]
if rmcbfmplay_com.BFM_TOKEN is None:
rmcbfmplay_com.BFM_TOKEN = rmcbfmplay_com.get_token()
if rmcbfmplay_com.BFM_TOKEN is None:
return None
rmcbfmplay_com.LICENSE_URL = rmcbfmplay_com.CONFIG_CONTENT[
"player"
]["shaka"]["drm"]["servers"]["com.widevine.alpha"]
return rmcbfmplay_com
@staticmethod
def get_keys(challenge, additional):
licence = requests.post(
rmcbfmplay_com.LICENSE_URL, data=challenge,
headers={
'customdata': '&'.join([f"{key}={value}" for key, value in {
"description": "description",
"deviceName": "deviceName",
"deviceType": "PC",
"tokenType": "castoken",
"tokenSSO": rmcbfmplay_com.BFM_TOKEN
}.items()])
}
)
if licence.status_code == 520:
raise CustomException(ERR_MSG.format(
type=f'{USER_ERROR}',
url=additional["URL"],
reason="Need French IP to access content",
solution="Use a VPN"
))
licence.raise_for_status()
return licence.content
@staticmethod
def get_video_data(source_element):
query_params = parse_qs(urlparse(source_element.url).query)
content_id = query_params["contentId"][0]
universe = query_params["universe"][0]
response = json.loads(requests.get(
rmcbfmplay_com.CONTENT_URL.format(content_id=conte nt_id),
params={
'app': rmcbfmplay_com.APP_VALUE, 'device': 'browser',
'token': rmcbfmplay_com.BFM_TOKEN, 'universe': universe
}
).content.decode())
manifest = []
element_name = None
for product in response:
if product["productId"] != content_id:
continue
element_name = product.get("title", None)
for offer in product["offers"]:
for stream in offer["streams"]:
if stream["drm"] != "WIDEVINE":
continue
manifest += [(offer["definition"].lower(), stream["url"])]
if len(manifest) == 0:
raise CustomException(ERR_MSG.format(
type=f'{USER_ERROR}',
url=source_element.url,
reason="Content isn't available anymore",
solution="Do not attempt to download it"
))
manifest = sorted(manifest, key=lambda m: rmcbfmplay_com.RES_PRIORITY[m[0]], reverse=True)[0][1]
if source_element.element is None:
if element_name is None:
element_name = content_id
source_element.element = get_valid_filename(element_name)
if source_element.collection is None:
source_element.collection = join(
str(builtins.CONFIG["DOWNLOAD_COMMANDS"]["OUTPUT_MEDIA_PATH"]),
rmcbfmplay_com.__name__
)
try:
pssh_value = str(min(re.findall(
r'<cenc
ssh>(.+?)</cenc
ssh>',
requests.get(manifest).content.decode()
), key=len))
except:
return manifest, None, {}
return manifest, pssh_value, {"URL": source_element.url}
@staticmethod
def get_collection_elements(collection_url):
if "/video/" in collection_url and "contentId=" in collection_url:
return [BaseElement(url=collection_url)]
if "/info-programme/" in collection_url:
collection = []
query_params = parse_qs(urlparse(collection_url).query)
content_id = query_params["contentId"][0]
universe = query_params["universe"][0]
response = json.loads(requests.get(
rmcbfmplay_com.DETAIL_URL.format(content_id=conten t_id),
params={"universe": universe}
).content.decode())
collection_name = response.get("title", None)
if collection_name is None:
collection_name = content_id
collection_name = get_valid_filename(collection_name)
is_series = response.get("seasons", None) is not None
collection_key = "episodes"
if is_series:
collection_key = "seasons"
index = 0
for collection_object in response.get(collection_key, []):
if not is_series:
index += 1
episode = collection_object
check = check_range(False, None, index)
if check is True:
continue
elif check is False:
return collection
collection.append(BaseElement(
url=rmcbfmplay_com.VIDEO_URL.format(content_id=epi sode["id"], universe=universe),
collection=join(
join(
str(builtins.CONFIG["DOWNLOAD_COMMANDS"]["OUTPUT_MEDIA_PATH"]),
rmcbfmplay_com.__name__
),
collection_name
),
element=f'{episode["sequence"]}'
f'_'
f'{get_valid_filename(episode.get("title", episode["id"]))}'
))
else:
season = collection_object
check = check_range(True, season["sequence"], None)
if check is True:
continue
elif check is False:
return collection
page = -1
while True:
page += 1
episode_response = json.loads(requests.get(
rmcbfmplay_com.EPISODES_URL.format(content_id=seas on["id"]),
params={
'app': rmcbfmplay_com.APP_VALUE, 'device': 'browser',
'universe': universe, 'page': page,
'size': rmcbfmplay_com.PAGE_LIMIT
}
).content.decode())
for episode in episode_response.get("content", []):
check = check_range(False, season["sequence"], episode['episodeNumber'])
if check is True:
continue
elif check is False:
return collection
collection.append(BaseElement(
url=rmcbfmplay_com.VIDEO_URL.format(content_id=epi sode["id"], universe=universe),
collection=join(
join(
str(builtins.CONFIG["DOWNLOAD_COMMANDS"]["OUTPUT_MEDIA_PATH"]),
rmcbfmplay_com.__name__
),
join(collection_name, f'Season_{season["sequence"]}')
),
element=f'Episode_{episode["episodeNumber"]}'
f'_'
f'{get_valid_filename(episode.get("title", episode["id"]))}'
))
if len(episode_response.get("content", [])) < rmcbfmplay_com.PAGE_LIMIT:
break
return collection
return None -
Same here, think that mediaset has changed some internal code.
Here is an examble to debug, i use widefrog.exe (windows). As long as this problem didn't exist it would have been enough to write this in the command prompt:
widefrog https://mediasetinfinity.mediaset.it/video/drittoerovescio/puntata-del-2-ottobre_F314087201000401
Any solutions?
Similar Threads
-
Batch IMDB Image Downloader
By Jay123210599 in forum ComputerReplies: 3Last Post: 11th Jan 2024, 13:02 -
Help downloading DRM protected content
By edenshapira in forum Video Streaming DownloadingReplies: 0Last Post: 28th Oct 2023, 15:59 -
Downloading DRM protected content from mewatch.sg
By notred in forum Video Streaming DownloadingReplies: 4Last Post: 25th Nov 2022, 05:34 -
Weird Behaviour Of DRM Protected Content
By portalie in forum Video Streaming DownloadingReplies: 1Last Post: 6th Feb 2022, 16:10



Quote
Thanks mate