StreamingCommunity 3.2.1__py3-none-any.whl → 3.2.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of StreamingCommunity might be problematic. Click here for more details.
- StreamingCommunity/Api/Player/Helper/Vixcloud/util.py +4 -0
- StreamingCommunity/Api/Player/hdplayer.py +2 -2
- StreamingCommunity/Api/Player/mixdrop.py +1 -1
- StreamingCommunity/Api/Player/vixcloud.py +4 -5
- StreamingCommunity/Api/Site/altadefinizione/film.py +2 -2
- StreamingCommunity/Api/Site/altadefinizione/series.py +1 -1
- StreamingCommunity/Api/Site/animeunity/serie.py +1 -1
- StreamingCommunity/Api/Site/animeworld/film.py +1 -1
- StreamingCommunity/Api/Site/animeworld/serie.py +1 -2
- StreamingCommunity/Api/Site/cb01new/film.py +1 -1
- StreamingCommunity/Api/Site/crunchyroll/__init__.py +103 -0
- StreamingCommunity/Api/Site/crunchyroll/film.py +82 -0
- StreamingCommunity/Api/Site/crunchyroll/series.py +186 -0
- StreamingCommunity/Api/Site/crunchyroll/site.py +113 -0
- StreamingCommunity/Api/Site/crunchyroll/util/ScrapeSerie.py +238 -0
- StreamingCommunity/Api/Site/crunchyroll/util/get_license.py +227 -0
- StreamingCommunity/Api/Site/guardaserie/series.py +1 -2
- StreamingCommunity/Api/Site/guardaserie/site.py +1 -2
- StreamingCommunity/Api/Site/guardaserie/util/ScrapeSerie.py +9 -8
- StreamingCommunity/Api/Site/mediasetinfinity/__init__.py +96 -0
- StreamingCommunity/Api/Site/mediasetinfinity/film.py +85 -0
- StreamingCommunity/Api/Site/mediasetinfinity/series.py +185 -0
- StreamingCommunity/Api/Site/mediasetinfinity/site.py +112 -0
- StreamingCommunity/Api/Site/mediasetinfinity/util/ScrapeSerie.py +259 -0
- StreamingCommunity/Api/Site/mediasetinfinity/util/fix_mpd.py +64 -0
- StreamingCommunity/Api/Site/mediasetinfinity/util/get_license.py +214 -0
- StreamingCommunity/Api/Site/raiplay/film.py +2 -2
- StreamingCommunity/Api/Site/raiplay/series.py +2 -1
- StreamingCommunity/Api/Site/streamingcommunity/__init__.py +6 -17
- StreamingCommunity/Api/Site/streamingcommunity/film.py +3 -3
- StreamingCommunity/Api/Site/streamingcommunity/series.py +11 -11
- StreamingCommunity/Api/Site/streamingcommunity/site.py +2 -4
- StreamingCommunity/Api/Site/streamingcommunity/util/ScrapeSerie.py +3 -6
- StreamingCommunity/Api/Site/streamingwatch/__init__.py +6 -14
- StreamingCommunity/Api/Site/streamingwatch/film.py +3 -3
- StreamingCommunity/Api/Site/streamingwatch/series.py +9 -9
- StreamingCommunity/Api/Site/streamingwatch/site.py +5 -7
- StreamingCommunity/Api/Site/streamingwatch/util/ScrapeSerie.py +2 -2
- StreamingCommunity/Lib/Downloader/DASH/cdm_helpher.py +131 -0
- StreamingCommunity/Lib/Downloader/DASH/decrypt.py +79 -0
- StreamingCommunity/Lib/Downloader/DASH/downloader.py +218 -0
- StreamingCommunity/Lib/Downloader/DASH/parser.py +249 -0
- StreamingCommunity/Lib/Downloader/DASH/segments.py +332 -0
- StreamingCommunity/Lib/Downloader/HLS/downloader.py +10 -30
- StreamingCommunity/Lib/Downloader/HLS/segments.py +146 -263
- StreamingCommunity/Lib/Downloader/MP4/downloader.py +0 -5
- StreamingCommunity/Lib/FFmpeg/capture.py +3 -3
- StreamingCommunity/Lib/FFmpeg/command.py +1 -1
- StreamingCommunity/TelegramHelp/config.json +3 -7
- StreamingCommunity/Upload/version.py +1 -1
- StreamingCommunity/Util/bento4_installer.py +191 -0
- StreamingCommunity/Util/config_json.py +1 -1
- StreamingCommunity/Util/headers.py +0 -3
- StreamingCommunity/Util/os.py +36 -46
- StreamingCommunity/__init__.py +2 -1
- StreamingCommunity/run.py +11 -10
- {streamingcommunity-3.2.1.dist-info → streamingcommunity-3.2.7.dist-info}/METADATA +7 -9
- streamingcommunity-3.2.7.dist-info/RECORD +111 -0
- StreamingCommunity/Api/Site/1337xx/__init__.py +0 -72
- StreamingCommunity/Api/Site/1337xx/site.py +0 -82
- StreamingCommunity/Api/Site/1337xx/title.py +0 -61
- StreamingCommunity/Lib/Proxies/proxy.py +0 -72
- streamingcommunity-3.2.1.dist-info/RECORD +0 -96
- {streamingcommunity-3.2.1.dist-info → streamingcommunity-3.2.7.dist-info}/WHEEL +0 -0
- {streamingcommunity-3.2.1.dist-info → streamingcommunity-3.2.7.dist-info}/entry_points.txt +0 -0
- {streamingcommunity-3.2.1.dist-info → streamingcommunity-3.2.7.dist-info}/licenses/LICENSE +0 -0
- {streamingcommunity-3.2.1.dist-info → streamingcommunity-3.2.7.dist-info}/top_level.txt +0 -0
|
@@ -90,9 +90,13 @@ class SeasonManager:
|
|
|
90
90
|
Parameters:
|
|
91
91
|
- number (int): The season number (1-based index)
|
|
92
92
|
"""
|
|
93
|
+
if len(self.seasons) == 1:
|
|
94
|
+
return self.seasons[0]
|
|
95
|
+
|
|
93
96
|
for season in self.seasons:
|
|
94
97
|
if season.number == number:
|
|
95
98
|
return season
|
|
99
|
+
|
|
96
100
|
return None
|
|
97
101
|
|
|
98
102
|
def __len__(self) -> int:
|
|
@@ -18,8 +18,8 @@ REQUEST_VERIFY = config_manager.get_bool('REQUESTS', 'verify')
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class VideoSource:
|
|
21
|
-
def __init__(self
|
|
22
|
-
self.client = httpx.Client(headers={'user-agent': get_userAgent()}, timeout=MAX_TIMEOUT,
|
|
21
|
+
def __init__(self):
|
|
22
|
+
self.client = httpx.Client(headers={'user-agent': get_userAgent()}, timeout=MAX_TIMEOUT, verify=REQUEST_VERIFY)
|
|
23
23
|
|
|
24
24
|
def extractLinkHdPlayer(self, response):
|
|
25
25
|
"""Extract iframe source from the page."""
|
|
@@ -25,7 +25,7 @@ console = Console()
|
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
class VideoSource:
|
|
28
|
-
def __init__(self, url: str, is_series: bool, media_id: int = None
|
|
28
|
+
def __init__(self, url: str, is_series: bool, media_id: int = None):
|
|
29
29
|
"""
|
|
30
30
|
Initialize video source for streaming site.
|
|
31
31
|
|
|
@@ -36,7 +36,6 @@ class VideoSource:
|
|
|
36
36
|
"""
|
|
37
37
|
self.headers = {'user-agent': get_userAgent()}
|
|
38
38
|
self.url = url
|
|
39
|
-
self.proxy = proxy
|
|
40
39
|
self.is_series = is_series
|
|
41
40
|
self.media_id = media_id
|
|
42
41
|
self.iframe_src = None
|
|
@@ -58,7 +57,7 @@ class VideoSource:
|
|
|
58
57
|
}
|
|
59
58
|
|
|
60
59
|
try:
|
|
61
|
-
response = httpx.get(f"{self.url}/iframe/{self.media_id}", headers=self.headers, params=params, timeout=MAX_TIMEOUT,
|
|
60
|
+
response = httpx.get(f"{self.url}/iframe/{self.media_id}", headers=self.headers, params=params, timeout=MAX_TIMEOUT, verify=REQUEST_VERIFY)
|
|
62
61
|
response.raise_for_status()
|
|
63
62
|
|
|
64
63
|
# Parse response with BeautifulSoup to get iframe source
|
|
@@ -123,12 +122,12 @@ class VideoSource:
|
|
|
123
122
|
logging.error(f"Error getting content: {e}")
|
|
124
123
|
raise
|
|
125
124
|
|
|
126
|
-
def get_playlist(self) -> str
|
|
125
|
+
def get_playlist(self) -> str:
|
|
127
126
|
"""
|
|
128
127
|
Generate authenticated playlist URL.
|
|
129
128
|
|
|
130
129
|
Returns:
|
|
131
|
-
str
|
|
130
|
+
str: Fully constructed playlist URL with authentication parameters, or None if content unavailable
|
|
132
131
|
"""
|
|
133
132
|
if not self.window_parameter:
|
|
134
133
|
return None
|
|
@@ -12,10 +12,9 @@ from rich.console import Console
|
|
|
12
12
|
|
|
13
13
|
# Internal utilities
|
|
14
14
|
from StreamingCommunity.Util.os import os_manager
|
|
15
|
-
from StreamingCommunity.Util.message import start_message
|
|
16
15
|
from StreamingCommunity.Util.headers import get_headers
|
|
16
|
+
from StreamingCommunity.Util.message import start_message
|
|
17
17
|
from StreamingCommunity.Util.config_json import config_manager
|
|
18
|
-
from StreamingCommunity.Lib.Downloader import HLS_Downloader
|
|
19
18
|
from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance, TelegramSession
|
|
20
19
|
|
|
21
20
|
|
|
@@ -25,6 +24,7 @@ from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
|
|
|
25
24
|
|
|
26
25
|
|
|
27
26
|
# Player
|
|
27
|
+
from StreamingCommunity import HLS_Downloader
|
|
28
28
|
from StreamingCommunity.Api.Player.supervideo import VideoSource
|
|
29
29
|
|
|
30
30
|
|
|
@@ -11,7 +11,6 @@ from rich.prompt import Prompt
|
|
|
11
11
|
|
|
12
12
|
# Internal utilities
|
|
13
13
|
from StreamingCommunity.Util.message import start_message
|
|
14
|
-
from StreamingCommunity.Lib.Downloader import HLS_Downloader
|
|
15
14
|
from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance, TelegramSession
|
|
16
15
|
|
|
17
16
|
|
|
@@ -29,6 +28,7 @@ from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
|
|
|
29
28
|
|
|
30
29
|
|
|
31
30
|
# Player
|
|
31
|
+
from StreamingCommunity import HLS_Downloader
|
|
32
32
|
from StreamingCommunity.Api.Player.supervideo import VideoSource
|
|
33
33
|
|
|
34
34
|
|
|
@@ -12,7 +12,6 @@ from rich.prompt import Prompt
|
|
|
12
12
|
# Internal utilities
|
|
13
13
|
from StreamingCommunity.Util.os import os_manager
|
|
14
14
|
from StreamingCommunity.Util.message import start_message
|
|
15
|
-
from StreamingCommunity.Lib.Downloader import MP4_downloader
|
|
16
15
|
from StreamingCommunity.TelegramHelp.telegram_bot import TelegramSession, get_bot_instance
|
|
17
16
|
|
|
18
17
|
|
|
@@ -24,6 +23,7 @@ from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
|
|
|
24
23
|
|
|
25
24
|
|
|
26
25
|
# Player
|
|
26
|
+
from StreamingCommunity import MP4_downloader
|
|
27
27
|
from StreamingCommunity.Api.Player.vixcloud import VideoSourceAnime
|
|
28
28
|
|
|
29
29
|
|
|
@@ -9,7 +9,6 @@ from rich.console import Console
|
|
|
9
9
|
# Internal utilities
|
|
10
10
|
from StreamingCommunity.Util.os import os_manager
|
|
11
11
|
from StreamingCommunity.Util.message import start_message
|
|
12
|
-
from StreamingCommunity.Lib.Downloader import MP4_downloader
|
|
13
12
|
|
|
14
13
|
|
|
15
14
|
# Logic class
|
|
@@ -19,6 +18,7 @@ from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
|
|
|
19
18
|
|
|
20
19
|
|
|
21
20
|
# Player
|
|
21
|
+
from StreamingCommunity import MP4_downloader
|
|
22
22
|
from StreamingCommunity.Api.Player.sweetpixel import VideoSource
|
|
23
23
|
|
|
24
24
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# 11.03.24
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
-
import logging
|
|
5
4
|
from typing import Tuple
|
|
6
5
|
|
|
7
6
|
|
|
@@ -13,7 +12,6 @@ from rich.prompt import Prompt
|
|
|
13
12
|
# Internal utilities
|
|
14
13
|
from StreamingCommunity.Util.os import os_manager
|
|
15
14
|
from StreamingCommunity.Util.message import start_message
|
|
16
|
-
from StreamingCommunity.Lib.Downloader import MP4_downloader
|
|
17
15
|
|
|
18
16
|
|
|
19
17
|
# Logic class
|
|
@@ -24,6 +22,7 @@ from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
|
|
|
24
22
|
|
|
25
23
|
|
|
26
24
|
# Player
|
|
25
|
+
from StreamingCommunity import MP4_downloader
|
|
27
26
|
from StreamingCommunity.Api.Player.sweetpixel import VideoSource
|
|
28
27
|
|
|
29
28
|
|
|
@@ -10,7 +10,6 @@ from rich.console import Console
|
|
|
10
10
|
# Internal utilities
|
|
11
11
|
from StreamingCommunity.Util.os import os_manager
|
|
12
12
|
from StreamingCommunity.Util.message import start_message
|
|
13
|
-
from StreamingCommunity.Lib.Downloader import HLS_Downloader
|
|
14
13
|
|
|
15
14
|
|
|
16
15
|
# Logic class
|
|
@@ -19,6 +18,7 @@ from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
|
|
|
19
18
|
|
|
20
19
|
|
|
21
20
|
# Player
|
|
21
|
+
from StreamingCommunity import HLS_Downloader
|
|
22
22
|
from StreamingCommunity.Api.Player.maxstream import VideoSource
|
|
23
23
|
|
|
24
24
|
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# 16.03.25
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import subprocess
|
|
5
|
+
from urllib.parse import quote_plus
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# External library
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.prompt import Prompt
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# Internal utilities
|
|
14
|
+
from StreamingCommunity.Api.Template import get_select_title
|
|
15
|
+
from StreamingCommunity.Api.Template.config_loader import site_constant
|
|
16
|
+
from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Logic class
|
|
20
|
+
from .site import title_search, table_show_manager, media_search_manager
|
|
21
|
+
from .film import download_film
|
|
22
|
+
from .series import download_series
|
|
23
|
+
|
|
24
|
+
# Variable
|
|
25
|
+
indice = 8
|
|
26
|
+
_useFor = "Anime"
|
|
27
|
+
_priority = 0
|
|
28
|
+
_engineDownload = "hls"
|
|
29
|
+
_deprecate = False
|
|
30
|
+
|
|
31
|
+
msg = Prompt()
|
|
32
|
+
console = Console()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_user_input(string_to_search: str = None):
|
|
36
|
+
"""
|
|
37
|
+
Asks the user to input a search term.
|
|
38
|
+
Handles both Telegram bot input and direct input.
|
|
39
|
+
"""
|
|
40
|
+
if string_to_search is None:
|
|
41
|
+
string_to_search = msg.ask(f"\n[purple]Insert a word to search in [green]{site_constant.SITE_NAME}").strip()
|
|
42
|
+
|
|
43
|
+
return string_to_search
|
|
44
|
+
|
|
45
|
+
def process_search_result(select_title, selections=None):
|
|
46
|
+
"""
|
|
47
|
+
Handles the search result and initiates the download for either a film or series.
|
|
48
|
+
|
|
49
|
+
Parameters:
|
|
50
|
+
select_title (MediaItem): The selected media item
|
|
51
|
+
selections (dict, optional): Dictionary containing selection inputs that bypass manual input
|
|
52
|
+
{'season': season_selection, 'episode': episode_selection}
|
|
53
|
+
"""
|
|
54
|
+
if select_title.type == 'tv':
|
|
55
|
+
season_selection = None
|
|
56
|
+
episode_selection = None
|
|
57
|
+
|
|
58
|
+
if selections:
|
|
59
|
+
season_selection = selections.get('season')
|
|
60
|
+
episode_selection = selections.get('episode')
|
|
61
|
+
|
|
62
|
+
download_series(select_title, season_selection, episode_selection)
|
|
63
|
+
|
|
64
|
+
else:
|
|
65
|
+
download_film(select_title)
|
|
66
|
+
|
|
67
|
+
# search("Game of Thrones", selections={"season": "1", "episode": "1-3"})
|
|
68
|
+
def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None, selections: dict = None):
|
|
69
|
+
"""
|
|
70
|
+
Main function of the application for search.
|
|
71
|
+
|
|
72
|
+
Parameters:
|
|
73
|
+
string_to_search (str, optional): String to search for
|
|
74
|
+
get_onylDatabase (bool, optional): If True, return only the database object
|
|
75
|
+
direct_item (dict, optional): Direct item to process (bypass search)
|
|
76
|
+
selections (dict, optional): Dictionary containing selection inputs that bypass manual input
|
|
77
|
+
{'season': season_selection, 'episode': episode_selection}
|
|
78
|
+
"""
|
|
79
|
+
if direct_item:
|
|
80
|
+
select_title = MediaItem(**direct_item)
|
|
81
|
+
process_search_result(select_title, selections)
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
# Get the user input for the search term
|
|
85
|
+
string_to_search = get_user_input(string_to_search)
|
|
86
|
+
|
|
87
|
+
# Perform the database search
|
|
88
|
+
len_database = title_search(quote_plus(string_to_search))
|
|
89
|
+
|
|
90
|
+
# If only the database is needed, return the manager
|
|
91
|
+
if get_onlyDatabase:
|
|
92
|
+
return media_search_manager
|
|
93
|
+
|
|
94
|
+
if len_database > 0:
|
|
95
|
+
select_title = get_select_title(table_show_manager, media_search_manager, len_database)
|
|
96
|
+
process_search_result(select_title, selections)
|
|
97
|
+
|
|
98
|
+
else:
|
|
99
|
+
console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}")
|
|
100
|
+
|
|
101
|
+
# If no results are found, ask again
|
|
102
|
+
string_to_search = get_user_input()
|
|
103
|
+
search(string_to_search, get_onlyDatabase, None, selections)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# 16.03.25
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from urllib.parse import urlparse, parse_qs
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# External library
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# Internal utilities
|
|
12
|
+
from StreamingCommunity.Util.message import start_message
|
|
13
|
+
from StreamingCommunity.Util.config_json import config_manager
|
|
14
|
+
from StreamingCommunity.Util.os import os_manager, get_wvd_path
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# Logic class
|
|
18
|
+
from StreamingCommunity.Api.Template.config_loader import site_constant
|
|
19
|
+
from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# Player
|
|
23
|
+
from StreamingCommunity import DASH_Downloader
|
|
24
|
+
from .util.get_license import get_playback_session, get_auth_token, generate_device_id
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# Variable
|
|
28
|
+
console = Console()
|
|
29
|
+
max_timeout = config_manager.get_int("REQUESTS", "timeout")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def download_film(select_title: MediaItem) -> str:
|
|
33
|
+
"""
|
|
34
|
+
Downloads a film using the provided film ID, title name, and domain.
|
|
35
|
+
|
|
36
|
+
Parameters:
|
|
37
|
+
- select_title (MediaItem): The selected media item.
|
|
38
|
+
|
|
39
|
+
Return:
|
|
40
|
+
- str: output path if successful, otherwise None
|
|
41
|
+
"""
|
|
42
|
+
start_message()
|
|
43
|
+
console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [cyan]{select_title.name}[/cyan] \n")
|
|
44
|
+
|
|
45
|
+
# Define filename and path for the downloaded video
|
|
46
|
+
mp4_name = os_manager.get_sanitize_file(select_title.name) + ".mp4"
|
|
47
|
+
mp4_path = os.path.join(site_constant.MOVIE_FOLDER, mp4_name.replace(".mp4", ""))
|
|
48
|
+
|
|
49
|
+
# Generate mpd and license URLs
|
|
50
|
+
url_id = select_title.get('url').split('/')[-1]
|
|
51
|
+
device_id = generate_device_id()
|
|
52
|
+
mpd_url, mpd_headers = get_playback_session(get_auth_token(device_id), device_id, url_id)
|
|
53
|
+
parsed_url = urlparse(mpd_url)
|
|
54
|
+
query_params = parse_qs(parsed_url.query)
|
|
55
|
+
|
|
56
|
+
# Download the episode
|
|
57
|
+
r_proc = DASH_Downloader(
|
|
58
|
+
cdm_device=get_wvd_path(),
|
|
59
|
+
license_url='https://www.crunchyroll.com/license/v1/license/widevine',
|
|
60
|
+
mpd_url=mpd_url,
|
|
61
|
+
output_path=os.path.join(mp4_path, mp4_name),
|
|
62
|
+
)
|
|
63
|
+
r_proc.parse_manifest(custom_headers=mpd_headers)
|
|
64
|
+
|
|
65
|
+
# Create headers for license request
|
|
66
|
+
license_headers = mpd_headers.copy()
|
|
67
|
+
license_headers.update({
|
|
68
|
+
"x-cr-content-id": url_id,
|
|
69
|
+
"x-cr-video-token": query_params['playbackGuid'][0],
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
if r_proc.download_and_decrypt(custom_headers=license_headers):
|
|
73
|
+
r_proc.finalize_output()
|
|
74
|
+
|
|
75
|
+
# Get final output path and status
|
|
76
|
+
status = r_proc.get_status()
|
|
77
|
+
|
|
78
|
+
if status['error'] is not None and status['path']:
|
|
79
|
+
try: os.remove(status['path'])
|
|
80
|
+
except Exception: pass
|
|
81
|
+
|
|
82
|
+
return status['path'], status['stopped']
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# 16.03.25
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from typing import Tuple
|
|
5
|
+
from urllib.parse import urlparse, parse_qs
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# External library
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.prompt import Prompt
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# Internal utilities
|
|
14
|
+
from StreamingCommunity.Util.message import start_message
|
|
15
|
+
from StreamingCommunity.Util.os import os_manager, get_wvd_path
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# Logic class
|
|
19
|
+
from .util.ScrapeSerie import GetSerieInfo, delete_stream_episode
|
|
20
|
+
from StreamingCommunity.Api.Template.Util import (
|
|
21
|
+
manage_selection,
|
|
22
|
+
map_episode_title,
|
|
23
|
+
validate_selection,
|
|
24
|
+
validate_episode_selection,
|
|
25
|
+
display_episodes_list
|
|
26
|
+
)
|
|
27
|
+
from StreamingCommunity.Api.Template.config_loader import site_constant
|
|
28
|
+
from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# Player
|
|
32
|
+
from StreamingCommunity import DASH_Downloader
|
|
33
|
+
from .util.get_license import get_playback_session, get_auth_token, generate_device_id
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# Variable
|
|
37
|
+
msg = Prompt()
|
|
38
|
+
console = Console()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def download_video(index_season_selected: int, index_episode_selected: int, scrape_serie: GetSerieInfo) -> Tuple[str,bool]:
|
|
42
|
+
"""
|
|
43
|
+
Downloads a specific episode from a specified season.
|
|
44
|
+
|
|
45
|
+
Parameters:
|
|
46
|
+
- index_season_selected (int): Season number
|
|
47
|
+
- index_episode_selected (int): Episode index
|
|
48
|
+
- scrape_serie (GetSerieInfo): Scraper object with series information
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
- str: Path to downloaded file
|
|
52
|
+
- bool: Whether download was stopped
|
|
53
|
+
"""
|
|
54
|
+
start_message()
|
|
55
|
+
|
|
56
|
+
# Get episode information
|
|
57
|
+
obj_episode = scrape_serie.selectEpisode(index_season_selected, index_episode_selected-1)
|
|
58
|
+
console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [bold magenta]{obj_episode.get('name')}[/bold magenta] ([cyan]S{index_season_selected}E{index_episode_selected}[/cyan]) \n")
|
|
59
|
+
|
|
60
|
+
# Define filename and path for the downloaded video
|
|
61
|
+
mp4_name = f"{map_episode_title(scrape_serie.series_name, index_season_selected, index_episode_selected, obj_episode.get('name'))}.mp4"
|
|
62
|
+
mp4_path = os_manager.get_sanitize_path(os.path.join(site_constant.SERIES_FOLDER, scrape_serie.series_name, f"S{index_season_selected}"))
|
|
63
|
+
|
|
64
|
+
# Generate mpd and license URLs
|
|
65
|
+
url_id = obj_episode.get('url').split('/')[-1]
|
|
66
|
+
device_id = generate_device_id()
|
|
67
|
+
token_mpd = get_auth_token(device_id)
|
|
68
|
+
|
|
69
|
+
mpd_url, mpd_headers = get_playback_session(token_mpd, device_id, url_id)
|
|
70
|
+
parsed_url = urlparse(mpd_url)
|
|
71
|
+
query_params = parse_qs(parsed_url.query)
|
|
72
|
+
|
|
73
|
+
# Download the episode
|
|
74
|
+
r_proc = DASH_Downloader(
|
|
75
|
+
cdm_device=get_wvd_path(),
|
|
76
|
+
license_url='https://www.crunchyroll.com/license/v1/license/widevine',
|
|
77
|
+
mpd_url=mpd_url,
|
|
78
|
+
output_path=os.path.join(mp4_path, mp4_name),
|
|
79
|
+
)
|
|
80
|
+
r_proc.parse_manifest(custom_headers=mpd_headers)
|
|
81
|
+
|
|
82
|
+
# Create headers for license request
|
|
83
|
+
license_headers = mpd_headers.copy()
|
|
84
|
+
license_headers.update({
|
|
85
|
+
"x-cr-content-id": url_id,
|
|
86
|
+
"x-cr-video-token": query_params['playbackGuid'][0],
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
if r_proc.download_and_decrypt(custom_headers=license_headers):
|
|
90
|
+
r_proc.finalize_output()
|
|
91
|
+
|
|
92
|
+
# Get final output path and status
|
|
93
|
+
status = r_proc.get_status()
|
|
94
|
+
|
|
95
|
+
if status['error'] is not None and status['path']:
|
|
96
|
+
try: os.remove(status['path'])
|
|
97
|
+
except Exception: pass
|
|
98
|
+
|
|
99
|
+
# Delete episode stream
|
|
100
|
+
delete_stream_episode(url_id, query_params['playbackGuid'][0], mpd_headers)
|
|
101
|
+
|
|
102
|
+
return status['path'], status['stopped']
|
|
103
|
+
|
|
104
|
+
def download_episode(index_season_selected: int, scrape_serie: GetSerieInfo, download_all: bool = False, episode_selection: str = None) -> None:
|
|
105
|
+
"""
|
|
106
|
+
Handle downloading episodes for a specific season.
|
|
107
|
+
|
|
108
|
+
Parameters:
|
|
109
|
+
- index_season_selected (int): Season number
|
|
110
|
+
- scrape_serie (GetSerieInfo): Scraper object with series information
|
|
111
|
+
- download_all (bool): Whether to download all episodes
|
|
112
|
+
- episode_selection (str, optional): Pre-defined episode selection that bypasses manual input
|
|
113
|
+
"""
|
|
114
|
+
# Get episodes for the selected season
|
|
115
|
+
episodes = scrape_serie.getEpisodeSeasons(index_season_selected)
|
|
116
|
+
episodes_count = len(episodes)
|
|
117
|
+
|
|
118
|
+
if download_all:
|
|
119
|
+
for i_episode in range(1, episodes_count + 1):
|
|
120
|
+
path, stopped = download_video(index_season_selected, i_episode, scrape_serie)
|
|
121
|
+
|
|
122
|
+
if stopped:
|
|
123
|
+
break
|
|
124
|
+
|
|
125
|
+
console.print(f"\n[red]End downloaded [yellow]season: [red]{index_season_selected}.")
|
|
126
|
+
|
|
127
|
+
else:
|
|
128
|
+
if episode_selection is not None:
|
|
129
|
+
last_command = episode_selection
|
|
130
|
+
console.print(f"\n[cyan]Using provided episode selection: [yellow]{episode_selection}")
|
|
131
|
+
|
|
132
|
+
else:
|
|
133
|
+
last_command = display_episodes_list(episodes)
|
|
134
|
+
|
|
135
|
+
# Prompt user for episode selection
|
|
136
|
+
list_episode_select = manage_selection(last_command, episodes_count)
|
|
137
|
+
list_episode_select = validate_episode_selection(list_episode_select, episodes_count)
|
|
138
|
+
|
|
139
|
+
# Download selected episodes if not stopped
|
|
140
|
+
for i_episode in list_episode_select:
|
|
141
|
+
path, stopped = download_video(index_season_selected, i_episode, scrape_serie)
|
|
142
|
+
|
|
143
|
+
if stopped:
|
|
144
|
+
break
|
|
145
|
+
|
|
146
|
+
def download_series(select_season: MediaItem, season_selection: str = None, episode_selection: str = None) -> None:
|
|
147
|
+
"""
|
|
148
|
+
Handle downloading a complete series.
|
|
149
|
+
|
|
150
|
+
Parameters:
|
|
151
|
+
- select_season (MediaItem): Series metadata from search
|
|
152
|
+
- season_selection (str, optional): Pre-defined season selection that bypasses manual input
|
|
153
|
+
- episode_selection (str, optional): Pre-defined episode selection that bypasses manual input
|
|
154
|
+
"""
|
|
155
|
+
scrape_serie = GetSerieInfo(select_season.url.split("/")[-1])
|
|
156
|
+
|
|
157
|
+
# Get total number of seasons
|
|
158
|
+
seasons_count = scrape_serie.getNumberSeason()
|
|
159
|
+
|
|
160
|
+
# Prompt user for season selection and download episodes
|
|
161
|
+
console.print(f"\n[green]Seasons found: [red]{seasons_count}")
|
|
162
|
+
|
|
163
|
+
# If season_selection is provided, use it instead of asking for input
|
|
164
|
+
if season_selection is None:
|
|
165
|
+
index_season_selected = msg.ask(
|
|
166
|
+
"\n[cyan]Insert season number [yellow](e.g., 1), [red]* [cyan]to download all seasons, "
|
|
167
|
+
"[yellow](e.g., 1-2) [cyan]for a range of seasons, or [yellow](e.g., 3-*) [cyan]to download from a specific season to the end"
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
else:
|
|
171
|
+
index_season_selected = season_selection
|
|
172
|
+
console.print(f"\n[cyan]Using provided season selection: [yellow]{season_selection}")
|
|
173
|
+
|
|
174
|
+
# Validate the selection
|
|
175
|
+
list_season_select = manage_selection(index_season_selected, seasons_count)
|
|
176
|
+
list_season_select = validate_selection(list_season_select, seasons_count)
|
|
177
|
+
|
|
178
|
+
# Loop through the selected seasons and download episodes
|
|
179
|
+
for i_season in list_season_select:
|
|
180
|
+
if len(list_season_select) > 1 or index_season_selected == "*":
|
|
181
|
+
# Download all episodes if multiple seasons are selected or if '*' is used
|
|
182
|
+
download_episode(i_season, scrape_serie, download_all=True)
|
|
183
|
+
|
|
184
|
+
else:
|
|
185
|
+
# Otherwise, let the user select specific episodes for the single season
|
|
186
|
+
download_episode(i_season, scrape_serie, download_all=False, episode_selection=episode_selection)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# 16.03.25
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# External libraries
|
|
5
|
+
from curl_cffi import requests
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# Internal utilities
|
|
10
|
+
from StreamingCommunity.Util.config_json import config_manager
|
|
11
|
+
from StreamingCommunity.Util.headers import get_headers
|
|
12
|
+
from StreamingCommunity.Util.table import TVShowManager
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Logic class
|
|
16
|
+
from StreamingCommunity.Api.Template.config_loader import site_constant
|
|
17
|
+
from StreamingCommunity.Api.Template.Class.SearchType import MediaManager
|
|
18
|
+
from .util.get_license import get_auth_token, generate_device_id
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Variable
|
|
22
|
+
console = Console()
|
|
23
|
+
media_search_manager = MediaManager()
|
|
24
|
+
table_show_manager = TVShowManager()
|
|
25
|
+
max_timeout = config_manager.get_int("REQUESTS", "timeout")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def title_search(query: str) -> int:
|
|
29
|
+
"""
|
|
30
|
+
Search for titles based on a search query.
|
|
31
|
+
|
|
32
|
+
Parameters:
|
|
33
|
+
- query (str): The query to search for.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
int: The number of titles found.
|
|
37
|
+
"""
|
|
38
|
+
media_search_manager.clear()
|
|
39
|
+
table_show_manager.clear()
|
|
40
|
+
|
|
41
|
+
# Build new Crunchyroll API search URL
|
|
42
|
+
api_url = f"https://www.crunchyroll.com/content/v2/discover/search"
|
|
43
|
+
|
|
44
|
+
params = {
|
|
45
|
+
"q": query,
|
|
46
|
+
"n": 20,
|
|
47
|
+
"type": "series,movie_listing",
|
|
48
|
+
"ratings": "true",
|
|
49
|
+
"preferred_audio_language": "it-IT",
|
|
50
|
+
"locale": "it-IT"
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
headers = get_headers()
|
|
54
|
+
headers['authorization'] = f"Bearer {get_auth_token(generate_device_id()).access_token}"
|
|
55
|
+
|
|
56
|
+
console.print(f"[cyan]Search url: [yellow]{api_url}")
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
response = requests.get(
|
|
60
|
+
api_url,
|
|
61
|
+
params=params,
|
|
62
|
+
headers=headers,
|
|
63
|
+
timeout=max_timeout,
|
|
64
|
+
impersonate="chrome110"
|
|
65
|
+
)
|
|
66
|
+
response.raise_for_status()
|
|
67
|
+
|
|
68
|
+
except Exception as e:
|
|
69
|
+
console.print(f"[red]Site: {site_constant.SITE_NAME}, request search error: {e}")
|
|
70
|
+
return 0
|
|
71
|
+
|
|
72
|
+
data = response.json()
|
|
73
|
+
found = 0
|
|
74
|
+
|
|
75
|
+
# Parse results
|
|
76
|
+
for block in data.get("data", []):
|
|
77
|
+
if block.get("type") not in ("series", "movie_listing", "top_results"):
|
|
78
|
+
continue
|
|
79
|
+
|
|
80
|
+
for item in block.get("items", []):
|
|
81
|
+
tipo = None
|
|
82
|
+
|
|
83
|
+
if item.get("type") == "movie_listing":
|
|
84
|
+
tipo = "film"
|
|
85
|
+
elif item.get("type") == "series":
|
|
86
|
+
meta = item.get("series_metadata", {})
|
|
87
|
+
|
|
88
|
+
if meta.get("episode_count") == 1 and meta.get("season_count", 1) == 1 and meta.get("series_launch_year"):
|
|
89
|
+
tipo = "film" if "film" in item.get("description", "").lower() or "movie" in item.get("description", "").lower() else "tv"
|
|
90
|
+
else:
|
|
91
|
+
tipo = "tv"
|
|
92
|
+
|
|
93
|
+
else:
|
|
94
|
+
continue
|
|
95
|
+
|
|
96
|
+
url = ""
|
|
97
|
+
if tipo == "tv":
|
|
98
|
+
url = f"https://www.crunchyroll.com/series/{item.get('id')}"
|
|
99
|
+
elif tipo == "film":
|
|
100
|
+
url = f"https://www.crunchyroll.com/series/{item.get('id')}"
|
|
101
|
+
else:
|
|
102
|
+
continue
|
|
103
|
+
|
|
104
|
+
title = item.get("title", "")
|
|
105
|
+
|
|
106
|
+
media_search_manager.add_media({
|
|
107
|
+
'url': url,
|
|
108
|
+
'name': title,
|
|
109
|
+
'type': tipo
|
|
110
|
+
})
|
|
111
|
+
found += 1
|
|
112
|
+
|
|
113
|
+
return media_search_manager.get_length()
|