eaf-base-api 1.0__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-1.0/PKG-INFO +44 -0
- eaf_base_api-1.0/README.md +30 -0
- eaf_base_api-1.0/base_api/__init__.py +0 -0
- eaf_base_api-1.0/base_api/base.py +110 -0
- eaf_base_api-1.0/base_api/modules/__init__.py +0 -0
- eaf_base_api-1.0/base_api/modules/download.py +129 -0
- eaf_base_api-1.0/base_api/modules/progress_bars.py +40 -0
- eaf_base_api-1.0/base_api/modules/quality.py +7 -0
- eaf_base_api-1.0/eaf_base_api.egg-info/PKG-INFO +44 -0
- eaf_base_api-1.0/eaf_base_api.egg-info/SOURCES.txt +13 -0
- eaf_base_api-1.0/eaf_base_api.egg-info/dependency_links.txt +1 -0
- eaf_base_api-1.0/eaf_base_api.egg-info/requires.txt +2 -0
- eaf_base_api-1.0/eaf_base_api.egg-info/top_level.txt +1 -0
- eaf_base_api-1.0/setup.cfg +4 -0
- eaf_base_api-1.0/setup.py +27 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: eaf_base_api
|
|
3
|
+
Version: 1.0
|
|
4
|
+
Summary: A base API for EchterAlsFake's Porn APIs
|
|
5
|
+
Home-page: https://github.com/EchterAlsFake/eaf_base_api
|
|
6
|
+
Author: Johannes Habel
|
|
7
|
+
Author-email: EchterAlsFake@proton.me
|
|
8
|
+
License: LGPLv3
|
|
9
|
+
Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)
|
|
10
|
+
Classifier: Programming Language :: Python
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Requires-Dist: requests
|
|
13
|
+
Requires-Dist: ffmpeg-progress-yield
|
|
14
|
+
|
|
15
|
+
# EAF Base API
|
|
16
|
+
|
|
17
|
+
! Still in Development
|
|
18
|
+
|
|
19
|
+
This Python package is a base api for all my Porn Site APIs. A lot of sites use the same methods, and
|
|
20
|
+
there's a lot of code that I've rewritten again and again. This is why I created this base api, so that
|
|
21
|
+
I need to write less code and everything is easier to be maintainable.
|
|
22
|
+
|
|
23
|
+
# PRs
|
|
24
|
+
Pull Requests are welcome, but if you make changes run `pytest` on all APIs that are using this to make sure your
|
|
25
|
+
PR didn't break anything.
|
|
26
|
+
|
|
27
|
+
# Documentation
|
|
28
|
+
|
|
29
|
+
As this is some kind of 'raw' base API, this won't be documented, as it's only functional when used together
|
|
30
|
+
with the main APIs.
|
|
31
|
+
|
|
32
|
+
Maybe you can look at the code comments. They could be helpful
|
|
33
|
+
|
|
34
|
+
# Implementation
|
|
35
|
+
You are welcome to implement this API in your own projects. If it's useful... But I don't recommend this, as things
|
|
36
|
+
might change without a warning. But things like the threaded downloads can be useful for other projects :)
|
|
37
|
+
|
|
38
|
+
I'd say the threaded modes from the download module are already very well optimized, so feel free to just copy them
|
|
39
|
+
for your own projects. You don't need to do any attribution, I don't care :)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# License
|
|
43
|
+
|
|
44
|
+
LGPLv3
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# EAF Base API
|
|
2
|
+
|
|
3
|
+
! Still in Development
|
|
4
|
+
|
|
5
|
+
This Python package is a base api for all my Porn Site APIs. A lot of sites use the same methods, and
|
|
6
|
+
there's a lot of code that I've rewritten again and again. This is why I created this base api, so that
|
|
7
|
+
I need to write less code and everything is easier to be maintainable.
|
|
8
|
+
|
|
9
|
+
# PRs
|
|
10
|
+
Pull Requests are welcome, but if you make changes run `pytest` on all APIs that are using this to make sure your
|
|
11
|
+
PR didn't break anything.
|
|
12
|
+
|
|
13
|
+
# Documentation
|
|
14
|
+
|
|
15
|
+
As this is some kind of 'raw' base API, this won't be documented, as it's only functional when used together
|
|
16
|
+
with the main APIs.
|
|
17
|
+
|
|
18
|
+
Maybe you can look at the code comments. They could be helpful
|
|
19
|
+
|
|
20
|
+
# Implementation
|
|
21
|
+
You are welcome to implement this API in your own projects. If it's useful... But I don't recommend this, as things
|
|
22
|
+
might change without a warning. But things like the threaded downloads can be useful for other projects :)
|
|
23
|
+
|
|
24
|
+
I'd say the threaded modes from the download module are already very well optimized, so feel free to just copy them
|
|
25
|
+
for your own projects. You don't need to do any attribution, I don't care :)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# License
|
|
29
|
+
|
|
30
|
+
LGPLv3
|
|
File without changes
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import logging
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
from base_api.modules.quality import Quality
|
|
6
|
+
from base_api.modules.progress_bars import Callback
|
|
7
|
+
from base_api.modules.download import default, threaded, FFMPEG
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def setup_api(do_logging=False):
|
|
11
|
+
if do_logging:
|
|
12
|
+
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
13
|
+
logging.getLogger("urllib3").setLevel(logging.WARNING)
|
|
14
|
+
|
|
15
|
+
else:
|
|
16
|
+
logging.disable(logging.CRITICAL)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
base_qualities = ["250p", "360p", "480p", "720p", "1080p", "1440p", "2160p"]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Core:
|
|
23
|
+
@classmethod
|
|
24
|
+
def get_content(cls, url, headers=None, cookies=None, retries=4):
|
|
25
|
+
for i in range(retries):
|
|
26
|
+
try:
|
|
27
|
+
logging.debug(f"Trying to fetch {url} / Attempt: [{i+1}]")
|
|
28
|
+
response = requests.get(url, headers=headers, cookies=cookies)
|
|
29
|
+
if response.status_code == 200:
|
|
30
|
+
logging.info(f"Successfully fetched {url} on attempt [{i+1}]")
|
|
31
|
+
return response.content
|
|
32
|
+
else:
|
|
33
|
+
logging.warning(f"Failed to fetch {url} with status code {response.status_code} on attempt [{i+1}]")
|
|
34
|
+
except requests.exceptions.RequestException as e:
|
|
35
|
+
logging.error(f"Exception occurred when trying to fetch {url} on attempt [{i+1}]: {e}")
|
|
36
|
+
if i < retries - 1: # Implement exponential backoff
|
|
37
|
+
time.sleep(2 ** i)
|
|
38
|
+
logging.error(f"Failed to fetch {url} after {retries} attempts.")
|
|
39
|
+
logging.error("Returning None")
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
def fix_quality(cls, quality):
|
|
44
|
+
"""This method gives the user the opportunity to pass a string instead of the quality object"""
|
|
45
|
+
if isinstance(quality, Quality):
|
|
46
|
+
return quality
|
|
47
|
+
|
|
48
|
+
elif str(quality) == "best":
|
|
49
|
+
return Quality.BEST
|
|
50
|
+
|
|
51
|
+
elif str(quality) == "half":
|
|
52
|
+
return Quality.HALF
|
|
53
|
+
|
|
54
|
+
elif str(quality) == "worst":
|
|
55
|
+
return Quality.WORST
|
|
56
|
+
|
|
57
|
+
def get_available_qualities(self, m3u8_base_url):
|
|
58
|
+
"""Returns the available qualities from the M3U8 base url"""
|
|
59
|
+
content = self.get_content(m3u8_base_url).decode("utf-8")
|
|
60
|
+
lines = content.splitlines()
|
|
61
|
+
|
|
62
|
+
quality_url_map = {}
|
|
63
|
+
|
|
64
|
+
for line in lines:
|
|
65
|
+
for quality in base_qualities:
|
|
66
|
+
if f"hls-{quality}" in line:
|
|
67
|
+
quality_url_map[quality] = line
|
|
68
|
+
|
|
69
|
+
self.quality_url_map = quality_url_map
|
|
70
|
+
self.available_qualities = list(quality_url_map.keys())
|
|
71
|
+
return self.available_qualities
|
|
72
|
+
|
|
73
|
+
def get_m3u8_by_quality(self, quality, m3u8_base_url):
|
|
74
|
+
"""Returns the m3u8 url for the given quality"""
|
|
75
|
+
quality = self.fix_quality(quality)
|
|
76
|
+
|
|
77
|
+
self.get_available_qualities(m3u8_base_url)
|
|
78
|
+
if quality == Quality.BEST:
|
|
79
|
+
selected_quality = max(self.available_qualities, key=lambda q: base_qualities.index(q))
|
|
80
|
+
elif quality == Quality.WORST:
|
|
81
|
+
selected_quality = min(self.available_qualities, key=lambda q: base_qualities.index(q))
|
|
82
|
+
elif quality == Quality.HALF:
|
|
83
|
+
sorted_qualities = sorted(self.available_qualities, key=lambda q: base_qualities.index(q))
|
|
84
|
+
middle_index = len(sorted_qualities) // 2
|
|
85
|
+
selected_quality = sorted_qualities[middle_index]
|
|
86
|
+
|
|
87
|
+
return self.quality_url_map.get(selected_quality)
|
|
88
|
+
|
|
89
|
+
def download(self, video, quality, downloader, output_path, callback=None):
|
|
90
|
+
"""
|
|
91
|
+
:param callback:
|
|
92
|
+
:param downloader:
|
|
93
|
+
:param quality:
|
|
94
|
+
:param output_path:
|
|
95
|
+
:return:
|
|
96
|
+
"""
|
|
97
|
+
quality = self.fix_quality(quality)
|
|
98
|
+
|
|
99
|
+
if callback is None:
|
|
100
|
+
callback = Callback.text_progress_bar
|
|
101
|
+
|
|
102
|
+
if downloader == default or str(downloader) == "default":
|
|
103
|
+
default(video=video, quality=quality, path=output_path, callback=callback)
|
|
104
|
+
|
|
105
|
+
elif downloader == threaded or str(downloader) == "threaded":
|
|
106
|
+
threaded_download = threaded(max_workers=20, timeout=10)
|
|
107
|
+
threaded_download(video=video, quality=quality, path=output_path, callback=callback)
|
|
108
|
+
|
|
109
|
+
elif downloader == FFMPEG or str(downloader) == "FFMPEG":
|
|
110
|
+
FFMPEG(video=video, quality=quality, path=output_path, callback=callback)
|
|
File without changes
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Thanks to: https://github.com/EchterAlsFake/PHUB/blob/master/src/phub/modules/download.py
|
|
2
|
+
# oh and of course ChatGPT lol
|
|
3
|
+
import logging
|
|
4
|
+
import time
|
|
5
|
+
import requests
|
|
6
|
+
import os
|
|
7
|
+
from ffmpeg_progress_yield import FfmpegProgress
|
|
8
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
9
|
+
from typing import Callable, List
|
|
10
|
+
|
|
11
|
+
CallbackType = Callable[[int, int], None]
|
|
12
|
+
|
|
13
|
+
"""
|
|
14
|
+
Important: The title of the video isn't applied to the output path. You need to manually append it to
|
|
15
|
+
the output path. This has good reasons, to make this library more adaptable into other applications.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def download_segment(url: str, timeout: int, retries: int = 3, backoff_factor: float = 0.3) -> tuple[str, bytes, bool]:
|
|
20
|
+
"""
|
|
21
|
+
Attempt to download a single segment, retrying on failure.
|
|
22
|
+
Returns a tuple of the URL, content (empty if failed after retries), and a success flag.
|
|
23
|
+
"""
|
|
24
|
+
for attempt in range(retries):
|
|
25
|
+
try:
|
|
26
|
+
response = requests.get(url, timeout=timeout)
|
|
27
|
+
response.raise_for_status() # Raises stored HTTPError, if one occurred.
|
|
28
|
+
return (url, response.content, True) # Success
|
|
29
|
+
except requests.RequestException as e:
|
|
30
|
+
print(f"Retry {attempt + 1} for {url}: {e}")
|
|
31
|
+
time.sleep(backoff_factor * (2 ** attempt)) # Exponential backoff
|
|
32
|
+
|
|
33
|
+
# After all retries have failed
|
|
34
|
+
return (url, b'', False) # Failed download
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def threaded(max_workers: int = 20, timeout: int = 10, retries: int = 3):
|
|
38
|
+
"""
|
|
39
|
+
Creates a wrapper function for the actual download process, with retry logic.
|
|
40
|
+
"""
|
|
41
|
+
def wrapper(video, quality, callback, path):
|
|
42
|
+
"""
|
|
43
|
+
Download video segments in parallel, with retries for failures, and write to a file.
|
|
44
|
+
"""
|
|
45
|
+
segments = video.get_segments(quality=quality)
|
|
46
|
+
length = len(segments)
|
|
47
|
+
completed, successful_downloads = 0, 0
|
|
48
|
+
|
|
49
|
+
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
50
|
+
# Map the last part of the URL (filename) to the future
|
|
51
|
+
future_to_hls_part = {executor.submit(download_segment, url, timeout, retries): os.path.basename(url) for url in segments}
|
|
52
|
+
|
|
53
|
+
for future in as_completed(future_to_hls_part):
|
|
54
|
+
hls_part = future_to_hls_part[future]
|
|
55
|
+
try:
|
|
56
|
+
_, data, success = future.result()
|
|
57
|
+
completed += 1
|
|
58
|
+
if success:
|
|
59
|
+
successful_downloads += 1
|
|
60
|
+
callback(completed, length) # Update callback regardless of success to reflect progress
|
|
61
|
+
except Exception as e:
|
|
62
|
+
raise e
|
|
63
|
+
|
|
64
|
+
# Writing only successful downloads to the file
|
|
65
|
+
with open(path, 'wb') as file:
|
|
66
|
+
for segment_url in segments:
|
|
67
|
+
# Find the future object using the HLS part of the URL as the key
|
|
68
|
+
matched_futures = [future for future, hls_part in future_to_hls_part.items() if hls_part == os.path.basename(segment_url)]
|
|
69
|
+
if matched_futures:
|
|
70
|
+
logging.info("Got a match")
|
|
71
|
+
future = matched_futures[0] # Assuming unique HLS parts, take the first match
|
|
72
|
+
try:
|
|
73
|
+
_, data, success = future.result()
|
|
74
|
+
if success:
|
|
75
|
+
file.write(data)
|
|
76
|
+
except Exception as e:
|
|
77
|
+
pass # Optionally handle or log the exception here
|
|
78
|
+
|
|
79
|
+
return wrapper
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def default(video, quality, callback, path, start: int = 0) -> bool:
|
|
83
|
+
buffer = b''
|
|
84
|
+
segments = video.get_segments(quality)[start:]
|
|
85
|
+
length = len(segments)
|
|
86
|
+
|
|
87
|
+
for i, url in enumerate(segments):
|
|
88
|
+
for _ in range(5):
|
|
89
|
+
|
|
90
|
+
segment = requests.get(url)
|
|
91
|
+
|
|
92
|
+
if segment.ok:
|
|
93
|
+
buffer += segment.content
|
|
94
|
+
callback(i + 1, length)
|
|
95
|
+
break
|
|
96
|
+
|
|
97
|
+
with open(path, 'wb') as file:
|
|
98
|
+
file.write(buffer)
|
|
99
|
+
|
|
100
|
+
return True
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def FFMPEG(video, quality, callback, path, start=0) -> bool:
|
|
104
|
+
base_url = video.m3u8_base_url
|
|
105
|
+
new_segment = video.get_m3u8_by_quality(quality)
|
|
106
|
+
url_components = base_url.split('/')
|
|
107
|
+
url_components[-1] = new_segment
|
|
108
|
+
new_url = '/'.join(url_components)
|
|
109
|
+
|
|
110
|
+
# Build the command for FFMPEG as a list directly
|
|
111
|
+
command = [
|
|
112
|
+
"ffmpeg",
|
|
113
|
+
"-i", new_url, # Input URL
|
|
114
|
+
"-bsf:a", "aac_adtstoasc",
|
|
115
|
+
"-y", # Overwrite output files without asking
|
|
116
|
+
"-c", "copy", # Copy streams without reencoding
|
|
117
|
+
path # Output file path
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
# Initialize FfmpegProgress and execute the command
|
|
121
|
+
ff = FfmpegProgress(command)
|
|
122
|
+
for progress in ff.run_command_with_progress():
|
|
123
|
+
# Update the callback with the current progress
|
|
124
|
+
callback(int(round(progress)), 100)
|
|
125
|
+
|
|
126
|
+
if progress == 100:
|
|
127
|
+
return True
|
|
128
|
+
|
|
129
|
+
return False
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Callback:
|
|
5
|
+
@classmethod
|
|
6
|
+
def custom_callback(cls, downloaded, total):
|
|
7
|
+
"""This is an example of how you can implement the custom callback"""
|
|
8
|
+
|
|
9
|
+
percentage = (downloaded / total) * 100
|
|
10
|
+
print(f"Downloaded: {downloaded} bytes / {total} bytes ({percentage:.2f}%)")
|
|
11
|
+
|
|
12
|
+
@classmethod
|
|
13
|
+
def text_progress_bar(cls, downloaded, total):
|
|
14
|
+
bar_length = 50
|
|
15
|
+
filled_length = int(round(bar_length * downloaded / float(total)))
|
|
16
|
+
percents = round(100.0 * downloaded / float(total), 1)
|
|
17
|
+
bar = '#' * filled_length + '-' * (bar_length - filled_length)
|
|
18
|
+
print(f"\r[{bar}] {percents}%", end='')
|
|
19
|
+
|
|
20
|
+
@staticmethod
|
|
21
|
+
def update_progress(downloaded, total, animation_phase):
|
|
22
|
+
bar_length = 50
|
|
23
|
+
filled_length = int(round(bar_length * downloaded / float(total)))
|
|
24
|
+
percents = round(100.0 * downloaded / float(total), 1)
|
|
25
|
+
|
|
26
|
+
# Animation phases could be anything. Here's a simple example with a rotating bar
|
|
27
|
+
animation_characters = ['|', '/', '-', '\\']
|
|
28
|
+
animation_char = animation_characters[animation_phase % len(animation_characters)]
|
|
29
|
+
|
|
30
|
+
bar = '#' * filled_length + animation_char + '-' * (bar_length - filled_length - 1)
|
|
31
|
+
|
|
32
|
+
sys.stdout.write(f"\r[{bar}] {percents}%")
|
|
33
|
+
sys.stdout.flush()
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def animated_text_progress(cls, downloaded, total_size):
|
|
37
|
+
animation_phase = 0
|
|
38
|
+
while downloaded <= total_size:
|
|
39
|
+
cls.update_progress(downloaded, total_size, animation_phase)
|
|
40
|
+
animation_phase += 1
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: eaf_base_api
|
|
3
|
+
Version: 1.0
|
|
4
|
+
Summary: A base API for EchterAlsFake's Porn APIs
|
|
5
|
+
Home-page: https://github.com/EchterAlsFake/eaf_base_api
|
|
6
|
+
Author: Johannes Habel
|
|
7
|
+
Author-email: EchterAlsFake@proton.me
|
|
8
|
+
License: LGPLv3
|
|
9
|
+
Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)
|
|
10
|
+
Classifier: Programming Language :: Python
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Requires-Dist: requests
|
|
13
|
+
Requires-Dist: ffmpeg-progress-yield
|
|
14
|
+
|
|
15
|
+
# EAF Base API
|
|
16
|
+
|
|
17
|
+
! Still in Development
|
|
18
|
+
|
|
19
|
+
This Python package is a base api for all my Porn Site APIs. A lot of sites use the same methods, and
|
|
20
|
+
there's a lot of code that I've rewritten again and again. This is why I created this base api, so that
|
|
21
|
+
I need to write less code and everything is easier to be maintainable.
|
|
22
|
+
|
|
23
|
+
# PRs
|
|
24
|
+
Pull Requests are welcome, but if you make changes run `pytest` on all APIs that are using this to make sure your
|
|
25
|
+
PR didn't break anything.
|
|
26
|
+
|
|
27
|
+
# Documentation
|
|
28
|
+
|
|
29
|
+
As this is some kind of 'raw' base API, this won't be documented, as it's only functional when used together
|
|
30
|
+
with the main APIs.
|
|
31
|
+
|
|
32
|
+
Maybe you can look at the code comments. They could be helpful
|
|
33
|
+
|
|
34
|
+
# Implementation
|
|
35
|
+
You are welcome to implement this API in your own projects. If it's useful... But I don't recommend this, as things
|
|
36
|
+
might change without a warning. But things like the threaded downloads can be useful for other projects :)
|
|
37
|
+
|
|
38
|
+
I'd say the threaded modes from the download module are already very well optimized, so feel free to just copy them
|
|
39
|
+
for your own projects. You don't need to do any attribution, I don't care :)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# License
|
|
43
|
+
|
|
44
|
+
LGPLv3
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
setup.py
|
|
3
|
+
base_api/__init__.py
|
|
4
|
+
base_api/base.py
|
|
5
|
+
base_api/modules/__init__.py
|
|
6
|
+
base_api/modules/download.py
|
|
7
|
+
base_api/modules/progress_bars.py
|
|
8
|
+
base_api/modules/quality.py
|
|
9
|
+
eaf_base_api.egg-info/PKG-INFO
|
|
10
|
+
eaf_base_api.egg-info/SOURCES.txt
|
|
11
|
+
eaf_base_api.egg-info/dependency_links.txt
|
|
12
|
+
eaf_base_api.egg-info/requires.txt
|
|
13
|
+
eaf_base_api.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
base_api
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name="eaf_base_api",
|
|
5
|
+
version="1.0",
|
|
6
|
+
packages=find_packages(),
|
|
7
|
+
install_requires=[
|
|
8
|
+
"requests", "ffmpeg-progress-yield"
|
|
9
|
+
],
|
|
10
|
+
entry_points={
|
|
11
|
+
'console_scripts': [
|
|
12
|
+
# If you want to create any executable scripts
|
|
13
|
+
],
|
|
14
|
+
},
|
|
15
|
+
author="Johannes Habel",
|
|
16
|
+
author_email="EchterAlsFake@proton.me",
|
|
17
|
+
description="A base API for EchterAlsFake's Porn APIs",
|
|
18
|
+
long_description=open('README.md').read(),
|
|
19
|
+
long_description_content_type='text/markdown',
|
|
20
|
+
license="LGPLv3",
|
|
21
|
+
url="https://github.com/EchterAlsFake/eaf_base_api",
|
|
22
|
+
classifiers=[
|
|
23
|
+
# Classifiers help users find your project on PyPI
|
|
24
|
+
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
|
|
25
|
+
"Programming Language :: Python",
|
|
26
|
+
],
|
|
27
|
+
)
|