warp-beacon 2.5.0__tar.gz → 2.5.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.
- {warp_beacon-2.5.0/warp_beacon.egg-info → warp_beacon-2.5.1}/PKG-INFO +1 -1
- warp_beacon-2.5.1/warp_beacon/__version__.py +2 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon/scraper/youtube/music.py +1 -1
- warp_beacon-2.5.1/warp_beacon/scraper/youtube/youtube.py +195 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1/warp_beacon.egg-info}/PKG-INFO +1 -1
- warp_beacon-2.5.0/warp_beacon/__version__.py +0 -2
- warp_beacon-2.5.0/warp_beacon/scraper/youtube/youtube.py +0 -88
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/LICENSE +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/MANIFEST.in +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/README.md +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/assets/placeholder.gif +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/etc/.gitignore +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/etc/accounts.json +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/etc/proxies.json +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/etc/warp_beacon.conf +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/etc/warp_beacon.service +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/pyproject.toml +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/setup.cfg +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/setup.py +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon/__init__.py +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon/compress/__init__.py +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon/compress/video.py +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon/jobs/__init__.py +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon/jobs/abstract.py +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon/jobs/download_job.py +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon/jobs/types.py +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon/jobs/upload_job.py +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon/mediainfo/__init__.py +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon/mediainfo/abstract.py +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon/mediainfo/audio.py +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon/mediainfo/silencer.py +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon/mediainfo/video.py +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon/scheduler/__init__.py +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon/scheduler/instagram_human.py +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon/scheduler/scheduler.py +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon/scraper/__init__.py +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon/scraper/abstract.py +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon/scraper/account_selector.py +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon/scraper/exceptions.py +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon/scraper/fail_handler.py +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon/scraper/instagram/__init__.py +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon/scraper/instagram/instagram.py +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon/scraper/link_resolver.py +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon/scraper/youtube/__init__.py +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon/scraper/youtube/abstract.py +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon/scraper/youtube/shorts.py +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon/storage/__init__.py +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon/storage/mongo.py +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon/telegram/__init__.py +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon/telegram/bot.py +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon/telegram/caption_shortener.py +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon/telegram/handlers.py +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon/telegram/placeholder_message.py +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon/telegram/utils.py +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon/uploader/__init__.py +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon/warp_beacon.py +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon/yt_auth.py +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon.egg-info/SOURCES.txt +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon.egg-info/dependency_links.txt +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon.egg-info/entry_points.txt +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon.egg-info/requires.txt +0 -0
- {warp_beacon-2.5.0 → warp_beacon-2.5.1}/warp_beacon.egg-info/top_level.txt +0 -0
@@ -49,7 +49,7 @@ class YoutubeMusicScraper(YoutubeAbstract):
|
|
49
49
|
"local_media_path": self.rename_local_file(local_file),
|
50
50
|
"performer": yt.author,
|
51
51
|
"thumb": thumbnail,
|
52
|
-
"canonical_name":
|
52
|
+
"canonical_name": yt.title,
|
53
53
|
"media_type": JobType.AUDIO
|
54
54
|
})
|
55
55
|
except Exception:
|
@@ -0,0 +1,195 @@
|
|
1
|
+
import time
|
2
|
+
import os
|
3
|
+
import logging
|
4
|
+
|
5
|
+
import av
|
6
|
+
from pytubefix.exceptions import AgeRestrictedError
|
7
|
+
|
8
|
+
from warp_beacon.jobs.types import JobType
|
9
|
+
from warp_beacon.scraper.youtube.abstract import YoutubeAbstract
|
10
|
+
from warp_beacon.scraper.exceptions import YoutubeLiveError, NotFound, YotubeAgeRestrictedError
|
11
|
+
|
12
|
+
class YoutubeScraper(YoutubeAbstract):
|
13
|
+
YT_MAX_RETRIES_DEFAULT = 8
|
14
|
+
YT_PAUSE_BEFORE_RETRY_DEFAULT = 3
|
15
|
+
YT_TIMEOUT_DEFAULT = 2
|
16
|
+
YT_TIMEOUT_INCREMENT_DEFAULT = 60
|
17
|
+
|
18
|
+
def is_live(self, data: dict) -> bool:
|
19
|
+
'''
|
20
|
+
x.contents.twoColumnWatchNextResults.results.results.contents[0].videoPrimaryInfoRenderer.viewCount.videoViewCountRenderer.isLive
|
21
|
+
'''
|
22
|
+
try:
|
23
|
+
contents = data.get("contents", {}).get("twoColumnWatchNextResults", {}).get("results", {}).get("results", {}).get("contents", [])
|
24
|
+
for i in contents:
|
25
|
+
video_view_count_renderer = i.get("videoPrimaryInfoRenderer", {}).get("viewCount", {}).get("videoViewCountRenderer", {})
|
26
|
+
if video_view_count_renderer:
|
27
|
+
return video_view_count_renderer.get("isLive", False)
|
28
|
+
except Exception as e:
|
29
|
+
logging.warning("Failed to check if stream is live!")
|
30
|
+
logging.exception(e)
|
31
|
+
|
32
|
+
return False
|
33
|
+
|
34
|
+
def _download(self, url: str, session: bool = True, timeout: int = 60) -> list:
|
35
|
+
res = None
|
36
|
+
try:
|
37
|
+
res = self._download_pytubefix_max_res(url=url, session=session, timeout=timeout)
|
38
|
+
except Exception as e:
|
39
|
+
logging.error("Failed to download video with highest resolution via pytubefix!")
|
40
|
+
logging.exception(e)
|
41
|
+
|
42
|
+
if not res:
|
43
|
+
try:
|
44
|
+
res = self._download_pytube_dash(url=url, session=session, timeout=timeout)
|
45
|
+
except Exception as e:
|
46
|
+
logging.error("Failed to download DASH stream video via pytubefix!")
|
47
|
+
logging.exception(e)
|
48
|
+
|
49
|
+
return res
|
50
|
+
|
51
|
+
def mux_raw_copy(self, video_path: str, audio_path: str, output_path: str) -> str:
|
52
|
+
try:
|
53
|
+
with av.open(video_path) as input_video, av.open(audio_path) as input_audio, av.open(output_path, mode='w') as output:
|
54
|
+
in_video_stream = input_video.streams.video[0]
|
55
|
+
in_audio_stream = input_audio.streams.audio[0]
|
56
|
+
|
57
|
+
out_video_stream = output.add_stream(codec_name=in_video_stream.codec.name)
|
58
|
+
out_audio_stream = output.add_stream(codec_name=in_audio_stream.codec.name)
|
59
|
+
|
60
|
+
for packet in input_video.demux(in_video_stream):
|
61
|
+
if packet.dts is None:
|
62
|
+
continue
|
63
|
+
packet.stream = out_video_stream
|
64
|
+
output.mux(packet)
|
65
|
+
|
66
|
+
for packet in input_audio.demux(in_audio_stream):
|
67
|
+
if packet.dts is None:
|
68
|
+
continue
|
69
|
+
packet.stream = out_audio_stream
|
70
|
+
output.mux(packet)
|
71
|
+
except Exception as e:
|
72
|
+
logging.error("Failed to mux audio and video!")
|
73
|
+
logging.exception(e)
|
74
|
+
return ''
|
75
|
+
|
76
|
+
return output_path
|
77
|
+
|
78
|
+
def _download_pytubefix_max_res(self, url: str, session: bool = True, timeout: int = 60) -> list:
|
79
|
+
res = []
|
80
|
+
local_video_file, local_audio_file = '', ''
|
81
|
+
try:
|
82
|
+
thumbnail = None
|
83
|
+
video_id = self.get_video_id(url)
|
84
|
+
if video_id:
|
85
|
+
thumbnail = self.download_hndlr(self.download_thumbnail, video_id)
|
86
|
+
yt = self.build_yt(url, session=session)
|
87
|
+
|
88
|
+
if self.is_live(yt.initial_data):
|
89
|
+
raise YoutubeLiveError("Youtube Live is not supported")
|
90
|
+
|
91
|
+
video_stream = yt.streams.filter(adaptive=True, file_extension='mp4', only_video=True).order_by('resolution').desc().first()
|
92
|
+
audio_stream = yt.streams.filter(adaptive=True, file_extension='mp4', only_audio=True).order_by('abr').desc().first()
|
93
|
+
|
94
|
+
local_video_file = video_stream.download(
|
95
|
+
output_path=self.DOWNLOAD_DIR,
|
96
|
+
max_retries=3,
|
97
|
+
timeout=timeout,
|
98
|
+
skip_existing=False,
|
99
|
+
filename_prefix="yt_download_video_"
|
100
|
+
)
|
101
|
+
local_video_file = self.rename_local_file(local_video_file)
|
102
|
+
logging.debug("Temp video filename: '%s'", local_video_file)
|
103
|
+
local_audio_file = audio_stream.download(
|
104
|
+
output_path=self.DOWNLOAD_DIR,
|
105
|
+
max_retries=3,
|
106
|
+
timeout=timeout,
|
107
|
+
skip_existing=False,
|
108
|
+
filename_prefix="yt_download_audio_"
|
109
|
+
)
|
110
|
+
local_audio_file = self.rename_local_file(local_audio_file)
|
111
|
+
logging.debug("Temp audio filename: '%s'", local_audio_file)
|
112
|
+
|
113
|
+
muxed_video = self.mux_raw_copy(
|
114
|
+
video_path=local_video_file,
|
115
|
+
audio_path=local_audio_file,
|
116
|
+
output_path=f"{self.DOWNLOAD_DIR}/yt_muxed_video_{int(time.time())}.mp4")
|
117
|
+
if muxed_video:
|
118
|
+
muxed_video = self.rename_local_file(muxed_video)
|
119
|
+
logging.debug("Temp muxed filename: '%s'", muxed_video)
|
120
|
+
|
121
|
+
res.append({
|
122
|
+
"local_media_path": muxed_video,
|
123
|
+
"performer": yt.author,
|
124
|
+
"thumb": thumbnail,
|
125
|
+
"canonical_name": yt.title,
|
126
|
+
"media_type": JobType.VIDEO
|
127
|
+
})
|
128
|
+
except AgeRestrictedError as e:
|
129
|
+
raise YotubeAgeRestrictedError("Youtube Age Restricted error")
|
130
|
+
finally:
|
131
|
+
if os.path.exists(local_video_file):
|
132
|
+
os.unlink(local_video_file)
|
133
|
+
if os.path.exists(local_audio_file):
|
134
|
+
os.unlink(local_audio_file)
|
135
|
+
|
136
|
+
return res
|
137
|
+
|
138
|
+
def _download_pytube_dash(self, url: str, session: bool = True, timeout: int = 60) -> list:
|
139
|
+
res = []
|
140
|
+
try:
|
141
|
+
thumbnail = None
|
142
|
+
video_id = self.get_video_id(url)
|
143
|
+
if video_id:
|
144
|
+
thumbnail = self.download_hndlr(self.download_thumbnail, video_id)
|
145
|
+
yt = self.build_yt(url, session=session)
|
146
|
+
|
147
|
+
if self.is_live(yt.initial_data):
|
148
|
+
raise YoutubeLiveError("Youtube Live is not supported")
|
149
|
+
|
150
|
+
stream = yt.streams.get_highest_resolution()
|
151
|
+
|
152
|
+
if not stream:
|
153
|
+
raise NotFound("No suitable video stream found")
|
154
|
+
|
155
|
+
logging.info("Starting download ...")
|
156
|
+
|
157
|
+
local_file = stream.download(
|
158
|
+
output_path=self.DOWNLOAD_DIR,
|
159
|
+
max_retries=0,
|
160
|
+
timeout=timeout,
|
161
|
+
skip_existing=False,
|
162
|
+
filename_prefix="yt_download_"
|
163
|
+
)
|
164
|
+
logging.debug("Temp filename: '%s'", local_file)
|
165
|
+
res.append({
|
166
|
+
"local_media_path": self.rename_local_file(local_file),
|
167
|
+
"performer": yt.author,
|
168
|
+
"thumb": thumbnail,
|
169
|
+
"canonical_name": stream.title,
|
170
|
+
"media_type": JobType.VIDEO
|
171
|
+
})
|
172
|
+
except AgeRestrictedError as e:
|
173
|
+
raise YotubeAgeRestrictedError("Youtube Age Restricted error")
|
174
|
+
|
175
|
+
return res
|
176
|
+
|
177
|
+
def _download_yt_dlp(self, url: str, timeout: int = 60) -> list:
|
178
|
+
res = []
|
179
|
+
thumbnail = None
|
180
|
+
video_id = self.get_video_id(url)
|
181
|
+
if video_id:
|
182
|
+
thumbnail = self.download_hndlr(self.download_thumbnail, video_id)
|
183
|
+
with self.build_yt_dlp(timeout) as ydl:
|
184
|
+
info = ydl.extract_info(url, download=True)
|
185
|
+
local_file = ydl.prepare_filename(info)
|
186
|
+
logging.debug("Temp filename: '%s'", local_file)
|
187
|
+
res.append({
|
188
|
+
"local_media_path": local_file,
|
189
|
+
"performer": info.get("uploader", "Unknown"),
|
190
|
+
"thumb": thumbnail,
|
191
|
+
"canonical_name": info.get("title", ''),
|
192
|
+
"media_type": JobType.VIDEO
|
193
|
+
})
|
194
|
+
|
195
|
+
return res
|
@@ -1,88 +0,0 @@
|
|
1
|
-
import logging
|
2
|
-
|
3
|
-
from pytubefix.exceptions import AgeRestrictedError
|
4
|
-
|
5
|
-
from warp_beacon.jobs.types import JobType
|
6
|
-
from warp_beacon.scraper.youtube.abstract import YoutubeAbstract
|
7
|
-
from warp_beacon.scraper.exceptions import YoutubeLiveError, NotFound, YotubeAgeRestrictedError
|
8
|
-
|
9
|
-
class YoutubeScraper(YoutubeAbstract):
|
10
|
-
YT_MAX_RETRIES_DEFAULT = 8
|
11
|
-
YT_PAUSE_BEFORE_RETRY_DEFAULT = 3
|
12
|
-
YT_TIMEOUT_DEFAULT = 2
|
13
|
-
YT_TIMEOUT_INCREMENT_DEFAULT = 60
|
14
|
-
|
15
|
-
def is_live(self, data: dict) -> bool:
|
16
|
-
'''
|
17
|
-
x.contents.twoColumnWatchNextResults.results.results.contents[0].videoPrimaryInfoRenderer.viewCount.videoViewCountRenderer.isLive
|
18
|
-
'''
|
19
|
-
try:
|
20
|
-
contents = data.get("contents", {}).get("twoColumnWatchNextResults", {}).get("results", {}).get("results", {}).get("contents", [])
|
21
|
-
for i in contents:
|
22
|
-
video_view_count_renderer = i.get("videoPrimaryInfoRenderer", {}).get("viewCount", {}).get("videoViewCountRenderer", {})
|
23
|
-
if video_view_count_renderer:
|
24
|
-
return video_view_count_renderer.get("isLive", False)
|
25
|
-
except Exception as e:
|
26
|
-
logging.warning("Failed to check if stream is live!")
|
27
|
-
logging.exception(e)
|
28
|
-
|
29
|
-
return False
|
30
|
-
|
31
|
-
def _download(self, url: str, session: bool = True, timeout: int = 60) -> list:
|
32
|
-
res = []
|
33
|
-
try:
|
34
|
-
thumbnail = None
|
35
|
-
video_id = self.get_video_id(url)
|
36
|
-
if video_id:
|
37
|
-
thumbnail = self.download_hndlr(self.download_thumbnail, video_id)
|
38
|
-
yt = self.build_yt(url, session=session)
|
39
|
-
|
40
|
-
if self.is_live(yt.initial_data):
|
41
|
-
raise YoutubeLiveError("Youtube Live is not supported")
|
42
|
-
|
43
|
-
stream = yt.streams.get_highest_resolution()
|
44
|
-
|
45
|
-
if not stream:
|
46
|
-
raise NotFound("No suitable video stream found")
|
47
|
-
|
48
|
-
logging.info("Starting download ...")
|
49
|
-
|
50
|
-
local_file = stream.download(
|
51
|
-
output_path=self.DOWNLOAD_DIR,
|
52
|
-
max_retries=0,
|
53
|
-
timeout=timeout,
|
54
|
-
skip_existing=False,
|
55
|
-
filename_prefix="yt_download_"
|
56
|
-
)
|
57
|
-
logging.debug("Temp filename: '%s'", local_file)
|
58
|
-
res.append({
|
59
|
-
"local_media_path": self.rename_local_file(local_file),
|
60
|
-
"performer": yt.author,
|
61
|
-
"thumb": thumbnail,
|
62
|
-
"canonical_name": stream.title,
|
63
|
-
"media_type": JobType.VIDEO
|
64
|
-
})
|
65
|
-
except AgeRestrictedError as e:
|
66
|
-
raise YotubeAgeRestrictedError("Youtube Age Restricted error")
|
67
|
-
|
68
|
-
return res
|
69
|
-
|
70
|
-
def _download_yt_dlp(self, url: str, timeout: int = 60) -> list:
|
71
|
-
res = []
|
72
|
-
thumbnail = None
|
73
|
-
video_id = self.get_video_id(url)
|
74
|
-
if video_id:
|
75
|
-
thumbnail = self.download_hndlr(self.download_thumbnail, video_id)
|
76
|
-
with self.build_yt_dlp(timeout) as ydl:
|
77
|
-
info = ydl.extract_info(url, download=True)
|
78
|
-
local_file = ydl.prepare_filename(info)
|
79
|
-
logging.debug("Temp filename: '%s'", local_file)
|
80
|
-
res.append({
|
81
|
-
"local_media_path": local_file,
|
82
|
-
"performer": info.get("uploader", "Unknown"),
|
83
|
-
"thumb": thumbnail,
|
84
|
-
"canonical_name": info.get("title", ''),
|
85
|
-
"media_type": JobType.VIDEO
|
86
|
-
})
|
87
|
-
|
88
|
-
return res
|
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
|
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
|
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
|
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
|
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
|
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
|
File without changes
|