From 0bedb1bf057f6a90b012f81f3a98de96a0d76b7e Mon Sep 17 00:00:00 2001 From: Lovi <62809003+Lovi-0@users.noreply.github.com> Date: Sun, 8 Dec 2024 19:00:37 +0100 Subject: [PATCH] Improve site SC --- README.md | 26 ++++- .../Api/Player/Helper/Vixcloud/util.py | 99 ++++++++----------- StreamingCommunity/Api/Player/vixcloud.py | 52 +++++++++- .../Api/Site/ddlstreamitaly/costant.py | 2 +- .../Api/Site/streamingcommunity/series.py | 17 ++-- .../streamingcommunity/util/ScrapeSerie.py | 36 ++++--- config.json | 4 +- 7 files changed, 149 insertions(+), 87 deletions(-) diff --git a/README.md b/README.md index a9be866..d4422c6 100644 --- a/README.md +++ b/README.md @@ -314,6 +314,24 @@ forced-ita hin - Hindi pol - Polish tur - Turkish
+ +# COMMAND + + +- Download a specific season by entering its number. + * **Example:** `1` will download *Season 1* only. + +- Use the wildcard `*` to download every available season. + * **Example:** `*` will download all seasons in the series. + +- Specify a range of seasons using a hyphen `-`. + * **Example:** `1-2` will download *Seasons 1 and 2*. + +- Enter a season number followed by `-*` to download from that season to the end. + * **Example:** `3-*` will download from *Season 3* to the final season. + +
+ # Docker You can run the script in a docker container, to build the image just run @@ -371,7 +389,13 @@ The `run-container` command mounts also the `config.json` file, so any change to # To Do -- Create website API +- Create website API -> https://github.com/Lovi-0/StreamingCommunity/tree/test_gui_1 + +# SUPPORT + +If you'd like to support this project, consider making a donation! + +[![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/donate/?hosted_button_id=UXTWMT8P6HE2C) # Contributing diff --git a/StreamingCommunity/Api/Player/Helper/Vixcloud/util.py b/StreamingCommunity/Api/Player/Helper/Vixcloud/util.py index 4c29ee1..cccf4b7 100644 --- a/StreamingCommunity/Api/Player/Helper/Vixcloud/util.py +++ b/StreamingCommunity/Api/Player/Helper/Vixcloud/util.py @@ -1,17 +1,22 @@ # 23.11.24 -import re -import logging from typing import Dict, Any, List, Union class Episode: def __init__(self, data: Dict[str, Any]): - self.id: int = data.get('id', '') - self.number: int = data.get('number', '') - self.name: str = data.get('name', '') - self.plot: str = data.get('plot', '') - self.duration: int = data.get('duration', '') + self.images = None + self.data = data + + self.id: int = data.get('id') + self.scws_id: int = data.get('scws_id') + self.number: int = data.get('number') + self.name: str = data.get('name') + self.plot: str = data.get('plot') + self.duration: int = data.get('duration') + + def collect_image(self, SITE_NAME, domain): + self.image = f"https://cdn.{SITE_NAME}.{domain}/images/{self.data.get('images')[0]['filename']}" def __str__(self): return f"Episode(id={self.id}, number={self.number}, name='{self.name}', plot='{self.plot}', duration={self.duration} sec)" @@ -20,7 +25,7 @@ class EpisodeManager: def __init__(self): self.episodes: List[Episode] = [] - def add_episode(self, episode_data: Dict[str, Any]): + def add(self, episode_data: Dict[str, Any]): """ Add a new episode to the manager. @@ -29,8 +34,20 @@ class EpisodeManager: """ episode = Episode(episode_data) self.episodes.append(episode) + + def get(self, index: int) -> Episode: + """ + Retrieve an episode by its index in the episodes list. + + Parameters: + - index (int): The zero-based index of the episode to retrieve. + + Returns: + Episode: The Episode object at the specified index. + """ + return self.episodes[index] - def get_length(self) -> int: + def length(self) -> int: """ Get the number of episodes in the manager. @@ -54,61 +71,23 @@ class EpisodeManager: class Season: def __init__(self, season_data: Dict[str, Union[int, str, None]]): + self.images = {} + self.season_data = season_data + self.id: int = season_data.get('id') + self.scws_id: int = season_data.get('scws_id') + self.imdb_id: int = season_data.get('imdb_id') self.number: int = season_data.get('number') self.name: str = season_data.get('name') + self.slug: str = season_data.get('slug') self.plot: str = season_data.get('plot') - self.episodes_count: int = season_data.get('episodes_count') - - def __str__(self): - return f"Season(id={self.id}, number={self.number}, name='{self.name}', plot='{self.plot}', episodes_count={self.episodes_count})" - -class SeasonManager: - def __init__(self): - self.seasons: List[Season] = [] - - def add_season(self, season_data: Dict[str, Union[int, str, None]]): - """ - Add a new season to the manager. - - Parameters: - season_data (Dict[str, Union[int, str, None]]): A dictionary containing data for the new season. - """ - season = Season(season_data) - self.seasons.append(season) - - def get(self, index: int) -> Season: - """ - Get a season item from the list by index. - - Parameters: - index (int): The index of the seasons item to retrieve. - - Returns: - Season: The media item at the specified index. - """ - return self.media_list[index] - - def get_length(self) -> int: - """ - Get the number of seasons in the manager. - - Returns: - int: Number of seasons. - """ - return len(self.seasons) - - def clear(self) -> None: - """ - This method clears the seasons list. - - Parameters: - self: The object instance. - """ - self.seasons.clear() - - def __str__(self): - return f"SeasonManager(num_seasons={len(self.seasons)})" + self.type: str = season_data.get('type') + self.seasons_count: int = season_data.get('seasons_count') + self.episodes: EpisodeManager = EpisodeManager() + + def collect_images(self, SITE_NAME, domain): + for dict_image in self.season_data.get('images'): + self.images[dict_image.get('type')] = f"https://cdn.{SITE_NAME}.{domain}/images/{dict_image.get('filename')}" class Stream: diff --git a/StreamingCommunity/Api/Player/vixcloud.py b/StreamingCommunity/Api/Player/vixcloud.py index 6bc4bd8..4351467 100644 --- a/StreamingCommunity/Api/Player/vixcloud.py +++ b/StreamingCommunity/Api/Player/vixcloud.py @@ -169,6 +169,56 @@ class VideoSource: # Construct the new URL with updated query parameters return urlunparse(parsed_url._replace(query=query_string)) + def get_mp4(self, url_to_download: str, scws_id: str) -> list: + """ + Generate download links for the specified resolutions from StreamingCommunity. + + Args: + url_to_download (str): URL of the video page. + scws_id (str): SCWS ID of the title. + + Returns: + list: A list of video download URLs. + """ + headers = { + 'referer': url_to_download, + 'user-agent': get_headers(), + } + + # API request to get video details + video_api_url = f'https://{self.base_name}.{self.domain}/api/video/{scws_id}' + response = httpx.get(video_api_url, headers=headers) + + if response.status_code == 200: + response_json = response.json() + + video_tracks = response_json.get('video_tracks', []) + track = video_tracks[-1] + console.print(f"[cyan]Available resolutions: [red]{[str(track['quality']) for track in video_tracks]}") + + # Request download link generation for each track + download_response = httpx.post( + url=f'https://{self.base_name}.{self.domain}/api/download/generate_link?scws_id={track["video_id"]}&rendition={track["quality"]}', + headers={ + 'referer': url_to_download, + 'user-agent': get_headers(), + 'x-xsrf-token': config_manager.get("SITE", self.base_name)['extra']['x-xsrf-token'] + }, + cookies={ + 'streamingcommunity_session': config_manager.get("SITE", self.base_name)['extra']['streamingcommunity_session'] + } + ) + + if download_response.status_code == 200: + return {'url': download_response.text, 'quality': track["quality"]} + + else: + logging.error(f"Failed to generate link for resolution {track['quality']} (HTTP {download_response.status_code}).") + + else: + logging.error(f"Error fetching video API URL (HTTP {response.status_code}).") + return [] + class VideoSourceAnime(VideoSource): def __init__(self, site_name: str): @@ -221,4 +271,4 @@ class VideoSourceAnime(VideoSource): except Exception as e: logging.error(f"Error fetching embed URL: {e}") - return None + return None \ No newline at end of file diff --git a/StreamingCommunity/Api/Site/ddlstreamitaly/costant.py b/StreamingCommunity/Api/Site/ddlstreamitaly/costant.py index e2f7a5e..858bfd8 100644 --- a/StreamingCommunity/Api/Site/ddlstreamitaly/costant.py +++ b/StreamingCommunity/Api/Site/ddlstreamitaly/costant.py @@ -10,7 +10,7 @@ from StreamingCommunity.Util._jsonConfig import config_manager SITE_NAME = os.path.basename(os.path.dirname(os.path.abspath(__file__))) ROOT_PATH = config_manager.get('DEFAULT', 'root_path') DOMAIN_NOW = config_manager.get_dict('SITE', SITE_NAME)['domain'] -COOKIE = config_manager.get_dict('SITE', SITE_NAME)['cookie'] +COOKIE = config_manager.get_dict('SITE', SITE_NAME)['extra'] SERIES_FOLDER = config_manager.get('DEFAULT', 'serie_folder_name') MOVIE_FOLDER = config_manager.get('DEFAULT', 'movie_folder_name') diff --git a/StreamingCommunity/Api/Site/streamingcommunity/series.py b/StreamingCommunity/Api/Site/streamingcommunity/series.py index 7be3b97..f32c837 100644 --- a/StreamingCommunity/Api/Site/streamingcommunity/series.py +++ b/StreamingCommunity/Api/Site/streamingcommunity/series.py @@ -42,13 +42,13 @@ def download_video(tv_name: str, index_season_selected: int, index_episode_selec start_message() # Get info about episode - obj_episode = scrape_serie.obj_episode_manager.episodes[index_episode_selected - 1] + obj_episode = scrape_serie.episode_manager.get(index_episode_selected - 1) console.print(f"[yellow]Download: [red]{index_season_selected}:{index_episode_selected} {obj_episode.name}") print() # Define filename and path for the downloaded video mp4_name = f"{map_episode_title(tv_name, index_season_selected, index_episode_selected, obj_episode.name)}.mp4" - mp4_path = os.path.join(ROOT_PATH, SITE_NAME, SERIES_FOLDER, tv_name, f"S{index_season_selected}") + mp4_path = os.path.join(ROOT_PATH, SITE_NAME, SERIES_FOLDER, tv_name, f"S{index_season_selected}") # Retrieve scws and if available master playlist video_source.get_iframe(obj_episode.id) @@ -84,13 +84,12 @@ def download_episode(tv_name: str, index_season_selected: int, scrape_serie: Scr """ # Clean memory of all episodes and get the number of the season - scrape_serie.obj_episode_manager.clear() - season_number = scrape_serie.obj_season_manager.seasons[index_season_selected - 1].number + scrape_serie.episode_manager.clear() # Start message and collect information about episodes start_message() - scrape_serie.collect_title_season(season_number) - episodes_count = scrape_serie.obj_episode_manager.get_length() + scrape_serie.collect_info_season(index_season_selected) + episodes_count = scrape_serie.episode_manager.length() if download_all: @@ -137,8 +136,8 @@ def download_series(select_season: MediaItem, version: str) -> None: video_source.setup(select_season.id) # Collect information about seasons - scrape_serie.collect_info_seasons() - seasons_count = scrape_serie.obj_season_manager.get_length() + scrape_serie.collect_info_title() + seasons_count = scrape_serie.season_manager.seasons_count # Prompt user for season selection and download episodes console.print(f"\n[green]Seasons found: [red]{seasons_count}") @@ -188,7 +187,7 @@ def display_episodes_list(scrape_serie) -> str: table_show_manager.add_column(column_info) # Populate the table with episodes information - for i, media in enumerate(scrape_serie.obj_episode_manager.episodes): + for i, media in enumerate(scrape_serie.episode_manager.episodes): table_show_manager.add_tv_show({ 'Index': str(media.number), 'Name': media.name, diff --git a/StreamingCommunity/Api/Site/streamingcommunity/util/ScrapeSerie.py b/StreamingCommunity/Api/Site/streamingcommunity/util/ScrapeSerie.py index 1f73f3e..3dc2fd5 100644 --- a/StreamingCommunity/Api/Site/streamingcommunity/util/ScrapeSerie.py +++ b/StreamingCommunity/Api/Site/streamingcommunity/util/ScrapeSerie.py @@ -10,7 +10,7 @@ import httpx # Internal utilities from StreamingCommunity.Util.headers import get_headers from StreamingCommunity.Util._jsonConfig import config_manager -from StreamingCommunity.Api.Player.Helper.Vixcloud.util import SeasonManager, EpisodeManager +from StreamingCommunity.Api.Player.Helper.Vixcloud.util import Season, EpisodeManager # Variable @@ -46,10 +46,10 @@ class ScrapeSerie: if series_name is not None: self.is_series = True self.series_name = series_name - self.obj_season_manager: SeasonManager = SeasonManager() - self.obj_episode_manager: EpisodeManager = EpisodeManager() + self.season_manager = None + self.episode_manager: EpisodeManager = EpisodeManager() - def collect_info_seasons(self) -> None: + def collect_info_title(self) -> None: """ Retrieve season information for a TV series from the streaming site. @@ -72,17 +72,22 @@ class ScrapeSerie: response.raise_for_status() # Extract seasons from JSON response - json_response = response.json().get('props', {}).get('title', {}).get('seasons', []) - - # Add each season to the season manager - for dict_season in json_response: - self.obj_season_manager.add_season(dict_season) + json_response = response.json().get('props') + # Collect info about season + self.season_manager = Season(json_response.get('title')) + self.season_manager.collect_images(self.base_name, self.domain) + + # Collect first episode info + for i, ep in enumerate(json_response.get('loadedSeason').get('episodes')): + self.season_manager.episodes.add(ep) + self.season_manager.episodes.get(i).collect_image(self.base_name, self.domain) + except Exception as e: logging.error(f"Error collecting season info: {e}") raise - def collect_title_season(self, number_season: int) -> None: + def collect_info_season(self, number_season: int) -> None: """ Retrieve episode information for a specific season. @@ -92,8 +97,13 @@ class ScrapeSerie: Raises: Exception: If there's an error fetching episode information """ - try: + self.headers = { + 'user-agent': get_headers(), + 'x-inertia': 'true', + 'x-inertia-version': self.version, + } + try: response = httpx.get( url=f'https://{self.base_name}.{self.domain}/titles/{self.media_id}-{self.series_name}/stagione-{number_season}', headers=self.headers, @@ -102,11 +112,11 @@ class ScrapeSerie: response.raise_for_status() # Extract episodes from JSON response - json_response = response.json().get('props', {}).get('loadedSeason', {}).get('episodes', []) + json_response = response.json().get('props').get('loadedSeason').get('episodes') # Add each episode to the episode manager for dict_episode in json_response: - self.obj_episode_manager.add_episode(dict_episode) + self.episode_manager.add(dict_episode) except Exception as e: logging.error(f"Error collecting title season info: {e}") diff --git a/config.json b/config.json index 287471a..75103e9 100644 --- a/config.json +++ b/config.json @@ -62,7 +62,7 @@ }, "SITE": { "streamingcommunity": { - "domain": "asia" + "domain": "family" }, "altadefinizione": { "domain": "now" @@ -75,7 +75,7 @@ }, "ddlstreamitaly": { "domain": "co", - "cookie": { + "extra": { "ips4_device_key": "", "ips4_member_id": "", "ips4_login_key": ""