mirror of
https://github.com/movixcorp/MovixOpenSource.git
synced 2026-07-27 13:22:14 +00:00
336 lines
13 KiB
Python
336 lines
13 KiB
Python
import builtins
|
|
import json
|
|
import threading
|
|
from json import JSONDecodeError
|
|
from os.path import join
|
|
|
|
import requests
|
|
|
|
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.cdm import get_pssh_from_cenc_pssh
|
|
from utils.tools.common import get_valid_filename, clean_url
|
|
|
|
|
|
class vix_com(BaseService):
|
|
DEMO_URLS = [
|
|
"https://vix.com/es-es/video/video-4564160",
|
|
"https://vix.com/es-es/video/video-4326106",
|
|
"https://vix.com/es-es/video/video-4561911",
|
|
"https://vix.com/es-es/detail/series-502",
|
|
"https://vix.com/es-es/detail/series-4715",
|
|
"https://vix.com/es-es/detail/series-4027",
|
|
]
|
|
|
|
PLAY_URL = "https://nxs.mp.lura.live/v1/play/{play_id}"
|
|
TOKEN_URL = 'https://vix.com/api/video/token'
|
|
ANON_URL = 'https://vix.com/api/proxy/user/create-anon-user'
|
|
GRAPHQL_URL = 'https://client-api.vix.com/gql/v2'
|
|
VIDEO_URL = "https://vix.com/video/video-{video_id}"
|
|
|
|
MANIFEST_PRIORITY = {"application/dash+xml": 1, 'application/x-mpegurl': 0}
|
|
BEARER_TOKEN = None
|
|
LOCK = None
|
|
PAGE_SIZE = 50
|
|
APP_PLATFORM = 'web'
|
|
APP_VERSION = 'v0.0.0'
|
|
APP_DEVICE = 'desktop'
|
|
|
|
@staticmethod
|
|
def test_service():
|
|
main_service.run_service(vix_com)
|
|
|
|
@staticmethod
|
|
def credentials_needed():
|
|
return {"NONE_NEEDED": True}
|
|
|
|
@staticmethod
|
|
def initialize_service():
|
|
if vix_com.LOCK is None:
|
|
vix_com.LOCK = threading.Lock()
|
|
return vix_com
|
|
|
|
@staticmethod
|
|
def get_keys(challenge, additional):
|
|
licence = requests.post(additional["license_url"], data=challenge)
|
|
licence.raise_for_status()
|
|
return licence.content
|
|
|
|
@staticmethod
|
|
def get_video_data(source_element):
|
|
play_id = source_element.url.split("/video-")[-1]
|
|
response = requests.get(
|
|
vix_com.TOKEN_URL,
|
|
params={'videoId': play_id},
|
|
headers={'x-video-type': 'VOD'}
|
|
)
|
|
response = response.content.decode()
|
|
try:
|
|
response = json.loads(response)
|
|
except JSONDecodeError:
|
|
raise CustomException(ERR_MSG.format(
|
|
type=f'{USER_ERROR}',
|
|
url=source_element.url,
|
|
reason="Need Colombian IP to access content",
|
|
solution="Use a VPN"
|
|
))
|
|
|
|
response = requests.post(
|
|
vix_com.PLAY_URL.format(play_id=play_id),
|
|
data={'token': response["token"]}
|
|
)
|
|
status_code = response.status_code
|
|
response = response.content.decode()
|
|
message = response.lower()
|
|
|
|
if status_code in [404] and "not found" in message:
|
|
raise CustomException(ERR_MSG.format(
|
|
type=f'{USER_ERROR}',
|
|
url=source_element.url,
|
|
reason="The content isn't available",
|
|
solution="Do not attempt to download it"
|
|
))
|
|
response = json.loads(response)
|
|
response = response["content"]
|
|
|
|
if source_element.element is None:
|
|
title = response.get("title", None)
|
|
if title in ["", None]:
|
|
title = f"Video_{play_id}"
|
|
source_element.element = get_valid_filename(title)
|
|
if source_element.collection is None:
|
|
source_element.collection = join(
|
|
str(builtins.CONFIG["DOWNLOAD_COMMANDS"]["OUTPUT_MEDIA_PATH"]),
|
|
vix_com.__name__
|
|
)
|
|
|
|
manifest = []
|
|
media = response.get("media", [])
|
|
if media is None:
|
|
media = []
|
|
for m in media:
|
|
try:
|
|
m["type"] = m["type"].lower()
|
|
except:
|
|
continue
|
|
|
|
if m["type"] in vix_com.MANIFEST_PRIORITY.keys():
|
|
manifest.append(m)
|
|
|
|
manifest = sorted(manifest, key=lambda mn: vix_com.MANIFEST_PRIORITY[mn["type"]], reverse=True)
|
|
manifest = manifest[0]
|
|
license_url = manifest.get('licenseUrl', None)
|
|
manifest = manifest["url"]
|
|
|
|
manifest_content = requests.get(manifest).content.decode()
|
|
try:
|
|
pssh_value = get_pssh_from_cenc_pssh(manifest_content, xml_node=":pssh")
|
|
except:
|
|
pssh_value = None
|
|
|
|
if pssh_value is None:
|
|
license_url = None
|
|
return manifest, pssh_value, {"license_url": license_url}
|
|
|
|
@staticmethod
|
|
def get_collection_elements(collection_url):
|
|
collection_url = clean_url(collection_url)
|
|
if "/video/" in collection_url and "/video-" in collection_url:
|
|
return [BaseElement(url=collection_url)]
|
|
if "/canales/" in collection_url:
|
|
return None
|
|
|
|
if "/detail/" not in collection_url or "/series-" not in collection_url:
|
|
return None
|
|
if vix_com.BEARER_TOKEN is None:
|
|
with vix_com.LOCK:
|
|
if vix_com.BEARER_TOKEN is None:
|
|
response = requests.post(
|
|
vix_com.ANON_URL, json={'installationId': 'installationId'}
|
|
)
|
|
response = response.content.decode()
|
|
|
|
try:
|
|
response = json.loads(response)
|
|
vix_com.BEARER_TOKEN = response["accessToken"]
|
|
assert len(vix_com.BEARER_TOKEN) > 0
|
|
except (JSONDecodeError, KeyError, AssertionError):
|
|
raise CustomException(ERR_MSG.format(
|
|
type=f'{USER_ERROR}',
|
|
url=collection_url,
|
|
reason="Need Colombian IP to access content",
|
|
solution="Use a VPN"
|
|
))
|
|
|
|
show_id = collection_url.split("/series-")[-1]
|
|
series_id = f'series:mcp:{show_id}'
|
|
collection_title = None
|
|
collection = []
|
|
|
|
season_after_id = None
|
|
season_index = 0
|
|
while True:
|
|
seasons_response = requests.post(
|
|
vix_com.GRAPHQL_URL,
|
|
headers={
|
|
'authorization': f'Bearer {vix_com.BEARER_TOKEN}',
|
|
'x-vix-platform': vix_com.APP_PLATFORM,
|
|
'x-vix-app-version': vix_com.APP_VERSION,
|
|
'x-vix-device-type': vix_com.APP_DEVICE
|
|
},
|
|
json={
|
|
'operationName': 'DetailData',
|
|
'variables': {
|
|
'id': series_id, 'navigationSection': {'urlPath': ''},
|
|
'pagination': {'first': vix_com.PAGE_SIZE, 'after': season_after_id}
|
|
},
|
|
'query': '''
|
|
query DetailData($id: ID!, $pagination: PaginationParams!) {
|
|
videoById(id: $id) {
|
|
...VideoContentFullFragment videoTypeData {
|
|
... on VideoTypeSeriesData { ...SeasonsConnectionBasicFragment }
|
|
}
|
|
}
|
|
}
|
|
fragment VideoContentFullFragment on VideoContent { ...VideoContentBasicFragment }
|
|
fragment VideoContentBasicFragment on VideoContent { title videoType }
|
|
fragment SeasonsConnectionBasicFragment on VideoTypeSeriesData {
|
|
seasonsConnection(pagination: $pagination) { edges { node { id title } } }
|
|
}
|
|
'''
|
|
}
|
|
)
|
|
|
|
seasons_response = seasons_response.content.decode()
|
|
seasons_response = json.loads(seasons_response)
|
|
if collection_title is None:
|
|
try:
|
|
collection_title = seasons_response["data"]["videoById"]["title"]
|
|
collection_title = get_valid_filename(collection_title)
|
|
assert len(collection_title) > 0
|
|
except:
|
|
collection_title = f"Series {show_id}"
|
|
collection_title = join(
|
|
join(
|
|
str(builtins.CONFIG["DOWNLOAD_COMMANDS"]["OUTPUT_MEDIA_PATH"]),
|
|
vix_com.__name__
|
|
),
|
|
get_valid_filename(collection_title)
|
|
)
|
|
|
|
try:
|
|
seasons = seasons_response["data"]["videoById"]["videoTypeData"]["seasonsConnection"]["edges"]
|
|
except:
|
|
seasons = []
|
|
if len(seasons) == 0:
|
|
break
|
|
season_after_id = None
|
|
|
|
for season in seasons:
|
|
season = season.get("node", None)
|
|
if season is None:
|
|
continue
|
|
season_id = season.get("id", "")
|
|
if season_id == "":
|
|
continue
|
|
season_after_id = season_id
|
|
|
|
season_index += 1
|
|
check = check_range(True, season_index, None)
|
|
if check is True:
|
|
continue
|
|
elif check is False:
|
|
return collection
|
|
|
|
season_title = season.get("title", "")
|
|
season_title = join(
|
|
collection_title,
|
|
get_valid_filename(f'Season_{season_index} {season_title}')
|
|
)
|
|
|
|
episode_after_id = None
|
|
while True:
|
|
episodes_response = requests.post(
|
|
vix_com.GRAPHQL_URL,
|
|
headers={
|
|
'authorization': f'Bearer {vix_com.BEARER_TOKEN}',
|
|
'x-vix-platform': vix_com.APP_PLATFORM,
|
|
'x-vix-app-version': vix_com.APP_VERSION,
|
|
'x-vix-device-type': vix_com.APP_DEVICE
|
|
},
|
|
json={
|
|
'operationName': 'SeasonById',
|
|
'variables': {
|
|
'seriesId': series_id, 'seasonId': season_id,
|
|
'episodePagination': {
|
|
'first': vix_com.PAGE_SIZE,
|
|
'after': episode_after_id
|
|
},
|
|
'navigationSection': {'urlPath': ''}
|
|
},
|
|
'query': '''
|
|
query SeasonById(
|
|
$seriesId: ID!, $seasonId: ID!, $episodePagination: PaginationParams!
|
|
) {
|
|
seasonById(seriesId: $seriesId, seasonId: $seasonId) { ...SeasonByIdFragment }
|
|
}
|
|
fragment SeasonByIdFragment on Season {
|
|
episodesConnection(pagination: $episodePagination) {
|
|
edges { node { ...EpisodeFullFragment } }
|
|
}
|
|
}
|
|
fragment EpisodeFullFragment on VideoContent {
|
|
id title videoTypeData { ...VideoTypeEpisodeFullFragment }
|
|
}
|
|
fragment VideoTypeEpisodeFullFragment on VideoTypeEpisodeData { episodeNumber }
|
|
'''
|
|
}
|
|
)
|
|
|
|
episodes_response = episodes_response.content.decode()
|
|
episodes_response = json.loads(episodes_response)
|
|
try:
|
|
episodes = episodes_response["data"]["seasonById"]["episodesConnection"]["edges"]
|
|
except:
|
|
episodes = []
|
|
if len(episodes) == 0:
|
|
break
|
|
episode_after_id = None
|
|
|
|
for episode in episodes:
|
|
episode = episode.get("node", None)
|
|
if episode is None:
|
|
continue
|
|
episode_id = episode.get("id", "").split(":")[-1]
|
|
if episode_id == "":
|
|
continue
|
|
episode_after_id = episode["id"]
|
|
|
|
episode_index = episode["videoTypeData"]["episodeNumber"]
|
|
check = check_range(False, season_index, episode_index)
|
|
if check is True:
|
|
continue
|
|
elif check is False:
|
|
return collection
|
|
|
|
episode_url = vix_com.VIDEO_URL.format(video_id=episode_id)
|
|
episode_title = episode.get("title", None)
|
|
if episode_title in ["", None]:
|
|
episode_title = f"Id_{episode_id}"
|
|
episode_title = f'Episode_{episode_index} {episode_title}'
|
|
episode_title = get_valid_filename(episode_title)
|
|
|
|
collection.append(BaseElement(
|
|
url=episode_url,
|
|
collection=season_title,
|
|
element=episode_title
|
|
))
|
|
|
|
if episode_after_id is None:
|
|
break
|
|
|
|
if season_after_id is None:
|
|
break
|
|
return collection
|