eaf-base-api 2.0__tar.gz → 2.1__tar.gz
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.
- {eaf_base_api-2.0/eaf_base_api.egg-info → eaf_base_api-2.1}/PKG-INFO +2 -2
- {eaf_base_api-2.0 → eaf_base_api-2.1}/base_api/base.py +117 -81
- {eaf_base_api-2.0 → eaf_base_api-2.1}/base_api/modules/consts.py +1 -2
- {eaf_base_api-2.0 → eaf_base_api-2.1/eaf_base_api.egg-info}/PKG-INFO +2 -2
- {eaf_base_api-2.0 → eaf_base_api-2.1}/eaf_base_api.egg-info/requires.txt +1 -1
- {eaf_base_api-2.0 → eaf_base_api-2.1}/setup.py +2 -3
- {eaf_base_api-2.0 → eaf_base_api-2.1}/LICENSE +0 -0
- {eaf_base_api-2.0 → eaf_base_api-2.1}/README.md +0 -0
- {eaf_base_api-2.0 → eaf_base_api-2.1}/base_api/__init__.py +0 -0
- {eaf_base_api-2.0 → eaf_base_api-2.1}/base_api/modules/__init__.py +0 -0
- {eaf_base_api-2.0 → eaf_base_api-2.1}/base_api/modules/progress_bars.py +0 -0
- {eaf_base_api-2.0 → eaf_base_api-2.1}/eaf_base_api.egg-info/SOURCES.txt +0 -0
- {eaf_base_api-2.0 → eaf_base_api-2.1}/eaf_base_api.egg-info/dependency_links.txt +0 -0
- {eaf_base_api-2.0 → eaf_base_api-2.1}/eaf_base_api.egg-info/top_level.txt +0 -0
- {eaf_base_api-2.0 → eaf_base_api-2.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: eaf_base_api
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.1
|
|
4
4
|
Summary: A base API for EchterAlsFake's Porn APIs
|
|
5
5
|
Home-page: https://github.com/EchterAlsFake/eaf_base_api
|
|
6
6
|
Author: Johannes Habel
|
|
@@ -10,9 +10,9 @@ Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGP
|
|
|
10
10
|
Classifier: Programming Language :: Python
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
12
|
License-File: LICENSE
|
|
13
|
-
Requires-Dist: requests
|
|
14
13
|
Requires-Dist: ffmpeg-progress-yield
|
|
15
14
|
Requires-Dist: m3u8
|
|
15
|
+
Requires-Dist: httpx
|
|
16
16
|
|
|
17
17
|
# EAF Base API
|
|
18
18
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import os
|
|
2
|
-
import m3u8
|
|
3
2
|
import time
|
|
3
|
+
import m3u8
|
|
4
|
+
import httpx
|
|
4
5
|
import random
|
|
5
6
|
import string
|
|
6
7
|
import logging
|
|
7
|
-
import requests
|
|
8
8
|
import traceback
|
|
9
9
|
import threading
|
|
10
10
|
|
|
@@ -67,13 +67,26 @@ class BaseCore:
|
|
|
67
67
|
def __init__(self):
|
|
68
68
|
self.last_request_time = time.time()
|
|
69
69
|
self.total_requests = 0 # Tracks how many requests have been made
|
|
70
|
-
self.session =
|
|
71
|
-
self.
|
|
70
|
+
self.session = None
|
|
71
|
+
self.initialize_session()
|
|
72
|
+
|
|
72
73
|
|
|
73
74
|
def update_user_agent(self):
|
|
74
75
|
"""Updates the User-Agent"""
|
|
75
76
|
self.session.headers.update({"User-Agent": random.choice(consts.USER_AGENTS)})
|
|
76
77
|
|
|
78
|
+
def initialize_session(self):
|
|
79
|
+
verify = True
|
|
80
|
+
|
|
81
|
+
if consts.PROXY is not None:
|
|
82
|
+
verify = False
|
|
83
|
+
|
|
84
|
+
self.session = httpx.Client(proxy=consts.PROXY,
|
|
85
|
+
headers=consts.HEADERS,
|
|
86
|
+
verify=verify,
|
|
87
|
+
timeout=consts.TIMEOUT
|
|
88
|
+
)
|
|
89
|
+
|
|
77
90
|
def enforce_delay(self):
|
|
78
91
|
"""Enforces the specified delay in consts.REQUEST_DELAY"""
|
|
79
92
|
delay = consts.REQUEST_DELAY
|
|
@@ -85,67 +98,77 @@ class BaseCore:
|
|
|
85
98
|
logger.debug(f"Enforcing delay of {sleep_time:.2f} seconds.")
|
|
86
99
|
time.sleep(sleep_time)
|
|
87
100
|
|
|
88
|
-
def fetch(
|
|
89
|
-
|
|
90
|
-
|
|
101
|
+
def fetch(
|
|
102
|
+
self,
|
|
103
|
+
url: str,
|
|
104
|
+
get_bytes: bool = False,
|
|
105
|
+
timeout: int = consts.TIMEOUT,
|
|
106
|
+
get_response: bool = False,
|
|
107
|
+
save_cache: bool = True,
|
|
108
|
+
cookies: dict = None,
|
|
109
|
+
allow_redirects: bool = True,
|
|
110
|
+
) -> Union[bytes, str, httpx.Response, None]:
|
|
91
111
|
"""
|
|
92
|
-
Fetches content in UTF-8 Text, Bytes or as a stream using multiple request attempts,
|
|
93
|
-
and custom timeout.
|
|
112
|
+
Fetches content in UTF-8 Text, Bytes, or as a stream using multiple request attempts,
|
|
113
|
+
support for proxies and custom timeout.
|
|
94
114
|
"""
|
|
115
|
+
# Check cache first
|
|
95
116
|
content = cache.handle_cache(url)
|
|
96
117
|
if content is not None:
|
|
97
118
|
logger.info(f"Fetched content for: {url} from cache!")
|
|
98
119
|
return content
|
|
99
120
|
|
|
100
|
-
for attempt in range(1, consts.MAX_RETRIES):
|
|
101
|
-
if self.total_requests % 3 == 0: # Change user agent after 3 requests to prevent bot detection
|
|
102
|
-
self.update_user_agent()
|
|
103
|
-
|
|
121
|
+
for attempt in range(1, consts.MAX_RETRIES + 1):
|
|
104
122
|
try:
|
|
105
|
-
|
|
106
|
-
if
|
|
107
|
-
|
|
108
|
-
url = url.replace("https://", "http://") # Replace https to http to support HTTP proxies
|
|
123
|
+
# Update user agent periodically
|
|
124
|
+
if self.total_requests % 3 == 0:
|
|
125
|
+
self.update_user_agent()
|
|
109
126
|
|
|
110
|
-
|
|
111
|
-
verify = True
|
|
127
|
+
self.enforce_delay()
|
|
112
128
|
|
|
113
|
-
|
|
114
|
-
|
|
129
|
+
# Perform the request with stream handling
|
|
130
|
+
with self.session.stream("GET", url, timeout=timeout, cookies=cookies,
|
|
131
|
+
follow_redirects=allow_redirects) as response:
|
|
132
|
+
self.total_requests += 1
|
|
115
133
|
|
|
116
|
-
|
|
117
|
-
|
|
134
|
+
# Log and handle non-200 status codes
|
|
135
|
+
if response.status_code != 200:
|
|
136
|
+
logger.warning(
|
|
137
|
+
f"Attempt {attempt}: Unexpected status code {response.status_code} for URL: {url}")
|
|
118
138
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
139
|
+
if response.status_code == 404:
|
|
140
|
+
logger.error("Resource not found (404). This may indicate the content is unavailable.")
|
|
141
|
+
return None # Return None for unavailable resources
|
|
122
142
|
|
|
123
|
-
|
|
143
|
+
continue # Retry for other non-200 status codes
|
|
124
144
|
|
|
125
|
-
|
|
126
|
-
return response
|
|
145
|
+
logger.debug(f"Attempt {attempt}: Successfully fetched URL: {url}")
|
|
127
146
|
|
|
128
|
-
|
|
147
|
+
# Return response if requested
|
|
148
|
+
if get_response:
|
|
149
|
+
return response
|
|
129
150
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
151
|
+
# Process and return content
|
|
152
|
+
if get_bytes:
|
|
153
|
+
content = b"".join([chunk for chunk in response.iter_bytes()])
|
|
154
|
+
else:
|
|
155
|
+
content = "".join([chunk.decode("utf-8") for chunk in response.iter_bytes()])
|
|
156
|
+
if save_cache:
|
|
157
|
+
logger.debug(f"Saving content of {url} to local cache.")
|
|
158
|
+
cache.save_cache(url, content)
|
|
134
159
|
|
|
135
|
-
|
|
160
|
+
return content
|
|
136
161
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
content = response.content.decode("utf-8")
|
|
140
|
-
if save_cache:
|
|
141
|
-
logger.debug(f"Trying to save content of: {url} in local cache...")
|
|
142
|
-
cache.save_cache(url, content)
|
|
162
|
+
except httpx.RequestError as e:
|
|
163
|
+
logger.error(f"Attempt {attempt}: Request error for URL {url}: {e}")
|
|
143
164
|
|
|
144
|
-
|
|
165
|
+
except Exception:
|
|
166
|
+
logger.error(f"Attempt {attempt}: Unexpected error for URL {url}: {traceback.format_exc()}")
|
|
145
167
|
|
|
146
|
-
|
|
168
|
+
logger.info(f"Retrying ({attempt}/{consts.MAX_RETRIES}) for URL: {url}")
|
|
147
169
|
|
|
148
|
-
|
|
170
|
+
logger.error(f"Failed to fetch URL {url} after {consts.MAX_RETRIES} attempts.")
|
|
171
|
+
return None # Return None if all attempts fail
|
|
149
172
|
|
|
150
173
|
@classmethod
|
|
151
174
|
def strip_title(cls, title: str) -> str:
|
|
@@ -243,7 +266,8 @@ class BaseCore:
|
|
|
243
266
|
|
|
244
267
|
response = BaseCore().fetch(url, timeout=timeout, get_response=True)
|
|
245
268
|
response.raise_for_status()
|
|
246
|
-
|
|
269
|
+
content = b"".join(response.iter_bytes())
|
|
270
|
+
return url, content, True # Success
|
|
247
271
|
|
|
248
272
|
def threaded(self, max_workers: int = 20, timeout: int = 10):
|
|
249
273
|
"""
|
|
@@ -259,10 +283,10 @@ class BaseCore:
|
|
|
259
283
|
completed, successful_downloads = 0, 0
|
|
260
284
|
|
|
261
285
|
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
286
|
+
future_to_hls_part = {
|
|
287
|
+
executor.submit(self.download_segment, url, timeout): os.path.basename(url)
|
|
288
|
+
for url in segments
|
|
289
|
+
}
|
|
266
290
|
|
|
267
291
|
for future in as_completed(future_to_hls_part):
|
|
268
292
|
hls_part = future_to_hls_part[future]
|
|
@@ -271,26 +295,25 @@ class BaseCore:
|
|
|
271
295
|
completed += 1
|
|
272
296
|
if success:
|
|
273
297
|
successful_downloads += 1
|
|
274
|
-
callback(completed, length) # Update callback
|
|
298
|
+
callback(completed, length) # Update progress callback
|
|
275
299
|
except Exception as e:
|
|
276
|
-
|
|
300
|
+
logger.error(f"Error processing segment {hls_part}: {e}")
|
|
277
301
|
|
|
278
|
-
#
|
|
302
|
+
# Write only successful segments to the output file
|
|
279
303
|
with open(path, 'wb') as file:
|
|
280
304
|
for segment_url in segments:
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
305
|
+
matched_futures = [
|
|
306
|
+
future for future, hls_part in future_to_hls_part.items()
|
|
307
|
+
if hls_part == os.path.basename(segment_url)
|
|
308
|
+
]
|
|
284
309
|
if matched_futures:
|
|
285
|
-
|
|
286
|
-
future = matched_futures[0] # Assuming unique HLS parts, take the first match
|
|
310
|
+
future = matched_futures[0]
|
|
287
311
|
try:
|
|
288
312
|
_, data, success = future.result()
|
|
289
313
|
if success:
|
|
290
314
|
file.write(data)
|
|
291
|
-
except Exception:
|
|
292
|
-
error
|
|
293
|
-
logger.error(f"Exception while downloading segment: {error}")
|
|
315
|
+
except Exception as e:
|
|
316
|
+
logger.error(f"Exception writing segment {segment_url}: {e}")
|
|
294
317
|
|
|
295
318
|
return wrapper
|
|
296
319
|
|
|
@@ -325,7 +348,7 @@ class BaseCore:
|
|
|
325
348
|
"-i", new_url, # Input URL
|
|
326
349
|
"-bsf:a", "aac_adtstoasc",
|
|
327
350
|
"-y", # Overwrite output files without asking
|
|
328
|
-
"-c", "copy", # Copy streams without
|
|
351
|
+
"-c", "copy", # Copy streams without re-encoding
|
|
329
352
|
path # Output file path
|
|
330
353
|
]
|
|
331
354
|
|
|
@@ -340,34 +363,47 @@ class BaseCore:
|
|
|
340
363
|
|
|
341
364
|
return False
|
|
342
365
|
|
|
343
|
-
def legacy_download(self,
|
|
344
|
-
|
|
345
|
-
|
|
366
|
+
def legacy_download(self, path: str, url: str, callback=None) -> bool:
|
|
367
|
+
"""
|
|
368
|
+
Download a file using streaming, with support for progress updates.
|
|
369
|
+
"""
|
|
370
|
+
try:
|
|
371
|
+
with self.session.stream("GET", url, timeout=30) as response: # Use a reasonable timeout
|
|
372
|
+
response.raise_for_status()
|
|
346
373
|
|
|
347
|
-
|
|
348
|
-
progress_bar = Callback()
|
|
374
|
+
file_size = int(response.headers.get('content-length', 0))
|
|
349
375
|
|
|
350
|
-
|
|
376
|
+
if callback is None:
|
|
377
|
+
progress_bar = Callback()
|
|
351
378
|
|
|
352
|
-
|
|
353
|
-
with open(path, 'wb') as file:
|
|
354
|
-
for chunk in response.iter_content(chunk_size=1024):
|
|
355
|
-
file.write(chunk)
|
|
356
|
-
downloaded_so_far += len(chunk)
|
|
379
|
+
downloaded_so_far = 0
|
|
357
380
|
|
|
358
|
-
|
|
359
|
-
|
|
381
|
+
# Open file for writing
|
|
382
|
+
with open(path, 'wb') as file:
|
|
383
|
+
for chunk in response.iter_bytes(chunk_size=1024):
|
|
384
|
+
if not chunk:
|
|
385
|
+
break # End of stream
|
|
360
386
|
|
|
361
|
-
|
|
362
|
-
|
|
387
|
+
file.write(chunk)
|
|
388
|
+
downloaded_so_far += len(chunk)
|
|
363
389
|
|
|
364
|
-
|
|
365
|
-
|
|
390
|
+
# Update progress
|
|
391
|
+
if callback:
|
|
392
|
+
callback(downloaded_so_far, file_size)
|
|
393
|
+
else:
|
|
394
|
+
progress_bar.text_progress_bar(downloaded=downloaded_so_far, total=file_size)
|
|
366
395
|
|
|
367
|
-
|
|
396
|
+
if not callback:
|
|
397
|
+
del progress_bar
|
|
368
398
|
|
|
369
|
-
|
|
370
|
-
|
|
399
|
+
return True
|
|
400
|
+
|
|
401
|
+
except httpx.RequestError as e:
|
|
402
|
+
logger.error(f"Request failed for URL {url}: {e}")
|
|
403
|
+
return False
|
|
404
|
+
except Exception as e:
|
|
405
|
+
logger.error(f"Unexpected error during download: {e}")
|
|
406
|
+
return False
|
|
371
407
|
|
|
372
408
|
@classmethod
|
|
373
409
|
def return_path(cls, video, args) -> str:
|
|
@@ -5,8 +5,7 @@ MAX_RETRIES = 4
|
|
|
5
5
|
REQUEST_DELAY = 0
|
|
6
6
|
TIMEOUT = 20 # Maximal request timeout
|
|
7
7
|
FFMPEG_PATH = "ffmpeg"
|
|
8
|
-
|
|
9
|
-
PROXIES = {}
|
|
8
|
+
PROXY = None
|
|
10
9
|
USER_AGENTS = ["Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
|
|
11
10
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.6 Safari/605.1.1",
|
|
12
11
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.3",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: eaf_base_api
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.1
|
|
4
4
|
Summary: A base API for EchterAlsFake's Porn APIs
|
|
5
5
|
Home-page: https://github.com/EchterAlsFake/eaf_base_api
|
|
6
6
|
Author: Johannes Habel
|
|
@@ -10,9 +10,9 @@ Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGP
|
|
|
10
10
|
Classifier: Programming Language :: Python
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
12
|
License-File: LICENSE
|
|
13
|
-
Requires-Dist: requests
|
|
14
13
|
Requires-Dist: ffmpeg-progress-yield
|
|
15
14
|
Requires-Dist: m3u8
|
|
15
|
+
Requires-Dist: httpx
|
|
16
16
|
|
|
17
17
|
# EAF Base API
|
|
18
18
|
|
|
@@ -2,10 +2,9 @@ from setuptools import setup, find_packages
|
|
|
2
2
|
|
|
3
3
|
setup(
|
|
4
4
|
name="eaf_base_api",
|
|
5
|
-
version="2.
|
|
5
|
+
version="2.1",
|
|
6
6
|
packages=find_packages(),
|
|
7
|
-
install_requires=[
|
|
8
|
-
"requests", "ffmpeg-progress-yield", "m3u8"
|
|
7
|
+
install_requires=["ffmpeg-progress-yield", "m3u8", "httpx"
|
|
9
8
|
],
|
|
10
9
|
entry_points={
|
|
11
10
|
'console_scripts': [
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|