Skip to content

Commit 353a23d

Browse files
authored
Versione 3.0.0 (#301)
* Update ScrapeSerie.py * Update site.py * Update util.py * Update ffmpeg_installer.py * Update os.py * Update ffmpeg_installer.py * Update setup.py * Update version.py * Update util.py
1 parent 0a03be0 commit 353a23d

File tree

7 files changed

+177
-114
lines changed

7 files changed

+177
-114
lines changed

StreamingCommunity/Api/Site/animeunity/site.py

Lines changed: 63 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,8 @@ def get_token() -> dict:
5151

5252
for html_meta in soup.find_all("meta"):
5353
if html_meta.get('name') == "csrf-token":
54-
5554
find_csrf_token = html_meta.get('content')
5655

57-
logging.info(f"Extract: ('animeunity_session': {response.cookies['animeunity_session']}, 'csrf_token': {find_csrf_token})")
5856
return {
5957
'animeunity_session': response.cookies['animeunity_session'],
6058
'csrf_token': find_csrf_token
@@ -64,9 +62,6 @@ def get_token() -> dict:
6462
def get_real_title(record):
6563
"""
6664
Get the real title from a record.
67-
68-
This function takes a record, which is assumed to be a dictionary representing a row of JSON data.
69-
It looks for a title in the record, prioritizing English over Italian titles if available.
7065
7166
Parameters:
7267
- record (dict): A dictionary representing a row of JSON data.
@@ -84,7 +79,7 @@ def get_real_title(record):
8479

8580
def title_search(query: str) -> int:
8681
"""
87-
Function to perform an anime search using a provided query.
82+
Function to perform an anime search using both APIs and combine results.
8883
8984
Parameters:
9085
- query (str): The query to search for.
@@ -97,43 +92,85 @@ def title_search(query: str) -> int:
9792

9893
media_search_manager.clear()
9994
table_show_manager.clear()
95+
seen_titles = set()
96+
choices = [] if site_constant.TELEGRAM_BOT else None
10097

10198
# Create parameter for request
10299
data = get_token()
103-
cookies = {'animeunity_session': data.get('animeunity_session')}
100+
cookies = {
101+
'animeunity_session': data.get('animeunity_session')
102+
}
104103
headers = {
105104
'user-agent': get_userAgent(),
106105
'x-csrf-token': data.get('csrf_token')
107106
}
108-
json_data = {'title': query}
109107

110-
# Send a POST request to the API endpoint for live search
108+
# First API call - livesearch
109+
try:
110+
response1 = httpx.post(
111+
f'{site_constant.FULL_URL}/livesearch',
112+
cookies=cookies,
113+
headers=headers,
114+
json={'title': query},
115+
timeout=max_timeout
116+
)
117+
118+
response1.raise_for_status()
119+
process_results(response1.json()['records'], seen_titles, media_search_manager, choices)
120+
121+
except Exception as e:
122+
console.print(f"Site: {site_constant.SITE_NAME}, livesearch error: {e}")
123+
124+
# Second API call - archivio
111125
try:
112-
response = httpx.post(
113-
f'{site_constant.FULL_URL}/livesearch',
114-
cookies=cookies,
115-
headers=headers,
126+
json_data = {
127+
'title': query,
128+
'type': False,
129+
'year': False,
130+
'order': 'Lista A-Z',
131+
'status': False,
132+
'genres': False,
133+
'offset': 0,
134+
'dubbed': False,
135+
'season': False
136+
}
137+
138+
response2 = httpx.post(
139+
f'{site_constant.FULL_URL}/archivio/get-animes',
140+
cookies=cookies,
141+
headers=headers,
116142
json=json_data,
117143
timeout=max_timeout
118144
)
119-
response.raise_for_status()
145+
146+
response2.raise_for_status()
147+
process_results(response2.json()['records'], seen_titles, media_search_manager, choices)
120148

121149
except Exception as e:
122-
console.print(f"Site: {site_constant.SITE_NAME}, request search error: {e}")
123-
return 0
150+
console.print(f"Site: {site_constant.SITE_NAME}, archivio search error: {e}")
124151

125-
# Inizializza la lista delle scelte
126-
if site_constant.TELEGRAM_BOT:
127-
choices = []
152+
if site_constant.TELEGRAM_BOT and choices and len(choices) > 0:
153+
bot.send_message(f"Lista dei risultati:", choices)
154+
155+
result_count = media_search_manager.get_length()
156+
if result_count == 0:
157+
console.print(f"Nothing matching was found for: {query}")
158+
159+
return result_count
128160

129-
for dict_title in response.json()['records']:
161+
def process_results(records: list, seen_titles: set, media_manager: MediaManager, choices: list = None) -> None:
162+
"""Helper function to process search results and add unique entries."""
163+
for dict_title in records:
130164
try:
131-
132-
# Rename keys for consistency
165+
title_id = dict_title.get('id')
166+
if title_id in seen_titles:
167+
continue
168+
169+
seen_titles.add(title_id)
133170
dict_title['name'] = get_real_title(dict_title)
134171

135-
media_search_manager.add_media({
136-
'id': dict_title.get('id'),
172+
media_manager.add_media({
173+
'id': title_id,
137174
'slug': dict_title.get('slug'),
138175
'name': dict_title.get('name'),
139176
'type': dict_title.get('type'),
@@ -142,18 +179,9 @@ def title_search(query: str) -> int:
142179
'image': dict_title.get('imageurl')
143180
})
144181

145-
if site_constant.TELEGRAM_BOT:
146-
147-
# Crea una stringa formattata per ogni scelta con numero
182+
if choices is not None:
148183
choice_text = f"{len(choices)} - {dict_title.get('name')} ({dict_title.get('type')}) - Episodi: {dict_title.get('episodes_count')}"
149184
choices.append(choice_text)
150185

151186
except Exception as e:
152-
print(f"Error parsing a film entry: {e}")
153-
154-
if site_constant.TELEGRAM_BOT:
155-
if choices:
156-
bot.send_message(f"Lista dei risultati:", choices)
157-
158-
# Return the length of media search manager
159-
return media_search_manager.get_length()
187+
print(f"Error parsing a title entry: {e}")

StreamingCommunity/Api/Site/animeunity/util/ScrapeSerie.py

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ def __init__(self, url: str):
2929
self.is_series = False
3030
self.headers = {'user-agent': get_userAgent()}
3131
self.url = url
32+
self.episodes_cache = None
3233

3334
def setup(self, version: str = None, media_id: int = None, series_name: str = None):
3435
self.version = version
@@ -62,38 +63,41 @@ def get_count_episodes(self):
6263
logging.error(f"Error fetching episode count: {e}")
6364
return None
6465

65-
def get_info_episode(self, index_ep: int) -> Episode:
66+
def _fetch_all_episodes(self):
6667
"""
67-
Fetch detailed information for a specific episode.
68-
69-
Args:
70-
index_ep (int): Zero-based index of the target episode
71-
72-
Returns:
73-
Episode: Detailed episode information
68+
Fetch all episodes data at once and cache it
7469
"""
7570
try:
76-
77-
params = {
78-
"start_range": index_ep,
79-
"end_range": index_ep + 1
80-
}
71+
count = self.get_count_episodes()
72+
if not count:
73+
return
8174

8275
response = httpx.get(
83-
url=f"{self.url}/info_api/{self.media_id}/{index_ep}",
84-
headers=self.headers,
85-
params=params,
76+
url=f"{self.url}/info_api/{self.media_id}/1",
77+
params={
78+
"start_range": 1,
79+
"end_range": count
80+
},
81+
headers=self.headers,
8682
timeout=max_timeout
8783
)
8884
response.raise_for_status()
89-
90-
# Return information about the episode
91-
json_data = response.json()["episodes"][-1]
92-
return Episode(json_data)
93-
85+
86+
self.episodes_cache = response.json()["episodes"]
9487
except Exception as e:
95-
logging.error(f"Error fetching episode information: {e}")
96-
return None
88+
logging.error(f"Error fetching all episodes: {e}")
89+
self.episodes_cache = None
90+
91+
def get_info_episode(self, index_ep: int) -> Episode:
92+
"""
93+
Get episode info from cache
94+
"""
95+
if self.episodes_cache is None:
96+
self._fetch_all_episodes()
97+
98+
if self.episodes_cache and 0 <= index_ep < len(self.episodes_cache):
99+
return Episode(self.episodes_cache[index_ep])
100+
return None
97101

98102

99103
# ------------- FOR GUI -------------
@@ -108,4 +112,4 @@ def selectEpisode(self, season_number: int = 1, episode_index: int = 0) -> Episo
108112
"""
109113
Get information for a specific episode.
110114
"""
111-
return self.get_info_episode(episode_index)
115+
return self.get_info_episode(episode_index)

StreamingCommunity/Lib/FFmpeg/util.py

Lines changed: 48 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -132,59 +132,67 @@ def print_duration_table(file_path: str, description: str = "Duration", return_s
132132
def get_ffprobe_info(file_path):
133133
"""
134134
Get format and codec information for a media file using ffprobe.
135-
136135
Parameters:
137136
- file_path (str): Path to the media file.
138-
139137
Returns:
140138
dict: A dictionary containing the format name and a list of codec names.
141139
Returns None if file does not exist or ffprobe crashes.
142140
"""
143141
if not os.path.exists(file_path):
144142
logging.error(f"File not found: {file_path}")
145143
return None
146-
144+
145+
# Get ffprobe path and verify it exists
146+
ffprobe_path = get_ffprobe_path()
147+
if not ffprobe_path or not os.path.exists(ffprobe_path):
148+
logging.error(f"FFprobe not found at path: {ffprobe_path}")
149+
return None
150+
151+
# Verify file permissions
152+
try:
153+
file_stat = os.stat(file_path)
154+
logging.info(f"File permissions: {oct(file_stat.st_mode)}")
155+
if not os.access(file_path, os.R_OK):
156+
logging.error(f"No read permission for file: {file_path}")
157+
return None
158+
except OSError as e:
159+
logging.error(f"Cannot access file {file_path}: {e}")
160+
return None
161+
147162
try:
148-
# Use subprocess.Popen instead of run to better handle crashes
149-
cmd = [get_ffprobe_path(), '-v', 'error', '-show_format', '-show_streams', '-print_format', 'json', file_path]
150-
logging.info(f"FFmpeg command: {cmd}")
163+
cmd = [ffprobe_path, '-v', 'error', '-show_format', '-show_streams', '-print_format', 'json', file_path]
164+
logging.info(f"Running FFprobe command: {' '.join(cmd)}")
151165

152-
with subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) as proc:
153-
stdout, stderr = proc.communicate()
154-
155-
if proc.returncode != 0:
156-
logging.error(f"FFprobe failed with return code {proc.returncode} for file {file_path}")
157-
if stderr:
158-
logging.error(f"FFprobe stderr: {stderr}")
159-
return {
160-
'format_name': None,
161-
'codec_names': []
162-
}
163-
164-
# Make sure we have valid JSON before parsing
165-
if not stdout or not stdout.strip():
166-
logging.warning(f"FFprobe returned empty output for file {file_path}")
167-
return {
168-
'format_name': None,
169-
'codec_names': []
170-
}
171-
172-
info = json.loads(stdout)
173-
174-
format_name = info['format']['format_name'] if 'format' in info else None
175-
codec_names = [stream['codec_name'] for stream in info['streams']] if 'streams' in info else []
176-
166+
# Use subprocess.run instead of Popen for better error handling
167+
result = subprocess.run(
168+
cmd,
169+
capture_output=True,
170+
text=True,
171+
check=False # Don't raise exception on non-zero exit
172+
)
173+
174+
if result.returncode != 0:
175+
logging.error(f"FFprobe failed with return code {result.returncode}")
176+
logging.error(f"FFprobe stderr: {result.stderr}")
177+
logging.error(f"FFprobe stdout: {result.stdout}")
178+
logging.error(f"Command: {' '.join(cmd)}")
179+
logging.error(f"FFprobe path permissions: {oct(os.stat(ffprobe_path).st_mode)}")
180+
return None
181+
182+
# Parse JSON output
183+
try:
184+
info = json.loads(result.stdout)
177185
return {
178-
'format_name': format_name,
179-
'codec_names': codec_names
186+
'format_name': info.get('format', {}).get('format_name'),
187+
'codec_names': [stream.get('codec_name') for stream in info.get('streams', [])]
180188
}
181-
189+
except json.JSONDecodeError as e:
190+
logging.error(f"Failed to parse FFprobe output: {e}")
191+
return None
192+
182193
except Exception as e:
183-
logging.error(f"Failed to get ffprobe info for file {file_path}: {e}")
184-
return {
185-
'format_name': None,
186-
'codec_names': []
187-
}
194+
logging.error(f"FFprobe execution failed: {e}")
195+
return None
188196

189197

190198
def is_png_format_or_codec(file_info):
@@ -255,4 +263,4 @@ def check_duration_v_a(video_path, audio_path, tolerance=1.0):
255263
if duration_difference <= tolerance:
256264
return True, duration_difference
257265
else:
258-
return False, duration_difference
266+
return False, duration_difference
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
__title__ = 'StreamingCommunity'
2-
__version__ = '2.9.9'
2+
__version__ = '3.0.0'
33
__author__ = 'Arrowar'
44
__description__ = 'A command-line program to download film'
55
__copyright__ = 'Copyright 2024'

StreamingCommunity/Util/ffmpeg_installer.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,31 @@ def download(self) -> Tuple[Optional[str], Optional[str], Optional[str]]:
238238
Returns:
239239
Tuple[Optional[str], Optional[str], Optional[str]]: Paths to ffmpeg, ffprobe, and ffplay executables.
240240
"""
241+
if self.os_name == 'linux':
242+
try:
243+
# Attempt to install FFmpeg using apt
244+
console.print("[bold blue]Trying to install FFmpeg using 'sudo apt install ffmpeg'[/]")
245+
result = subprocess.run(
246+
['sudo', 'apt', 'install', '-y', 'ffmpeg'],
247+
stdout=subprocess.PIPE,
248+
stderr=subprocess.PIPE,
249+
text=True
250+
)
251+
if result.returncode == 0:
252+
ffmpeg_path = shutil.which('ffmpeg')
253+
ffprobe_path = shutil.which('ffprobe')
254+
255+
if ffmpeg_path and ffprobe_path:
256+
console.print("[bold green]FFmpeg successfully installed via apt[/]")
257+
return ffmpeg_path, ffprobe_path, None
258+
else:
259+
console.print("[bold yellow]Failed to install FFmpeg via apt. Proceeding with static download.[/]")
260+
261+
except Exception as e:
262+
logging.error(f"Error during 'sudo apt install ffmpeg': {e}")
263+
console.print("[bold red]Error during 'sudo apt install ffmpeg'. Proceeding with static download.[/]")
264+
265+
# Proceed with static download if apt installation fails or is not applicable
241266
config = FFMPEG_CONFIGURATION[self.os_name]
242267
executables = [exe.format(arch=self.arch) for exe in config['executables']]
243268
successful_extractions = []
@@ -346,4 +371,4 @@ def check_ffmpeg() -> Tuple[Optional[str], Optional[str], Optional[str]]:
346371

347372
except Exception as e:
348373
logging.error(f"Error checking or downloading FFmpeg executables: {e}")
349-
return None, None, None
374+
return None, None, None

0 commit comments

Comments
 (0)