simplesoup 0.1.0__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.
- simplesoup/__init__.py +3 -0
- simplesoup/converter.py +32 -0
- simplesoup/core.py +85 -0
- simplesoup/downloader.py +555 -0
- simplesoup/gallery.py +44 -0
- simplesoup/parser.py +218 -0
- simplesoup-0.1.0.dist-info/METADATA +315 -0
- simplesoup-0.1.0.dist-info/RECORD +10 -0
- simplesoup-0.1.0.dist-info/WHEEL +5 -0
- simplesoup-0.1.0.dist-info/top_level.txt +1 -0
simplesoup/__init__.py
ADDED
simplesoup/converter.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Converter:
|
|
5
|
+
|
|
6
|
+
@staticmethod
|
|
7
|
+
def convert(input_file, output_file):
|
|
8
|
+
|
|
9
|
+
command = [
|
|
10
|
+
"ffmpeg",
|
|
11
|
+
"-y",
|
|
12
|
+
"-i",
|
|
13
|
+
input_file,
|
|
14
|
+
"-vf",
|
|
15
|
+
"format=nv12",
|
|
16
|
+
"-c:v",
|
|
17
|
+
"h264_mediacodec",
|
|
18
|
+
"-g",
|
|
19
|
+
"120",
|
|
20
|
+
"-c:a",
|
|
21
|
+
"aac",
|
|
22
|
+
"-movflags",
|
|
23
|
+
"+faststart",
|
|
24
|
+
output_file
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
subprocess.run(
|
|
28
|
+
command,
|
|
29
|
+
check=True
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
return output_file
|
simplesoup/core.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from .parser import parse
|
|
2
|
+
from .downloader import Downloader
|
|
3
|
+
from .gallery import Gallery
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# ============================================================
|
|
7
|
+
# SimpleSoup Configuration
|
|
8
|
+
# ============================================================
|
|
9
|
+
|
|
10
|
+
# Put your cookies.txt file path here.
|
|
11
|
+
# Example:
|
|
12
|
+
# COOKIES = "/storage/emulated/0/Download/instagram_cookies.txt"
|
|
13
|
+
#
|
|
14
|
+
# Disable cookies:
|
|
15
|
+
# COOKIES = None
|
|
16
|
+
|
|
17
|
+
COOKIES = "/storage/emulated/0/Download/reddit_cookies.txt"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SimpleSoup:
|
|
21
|
+
|
|
22
|
+
def __init__(self):
|
|
23
|
+
self.downloader = Downloader()
|
|
24
|
+
self.gallery = Gallery()
|
|
25
|
+
|
|
26
|
+
def __call__(
|
|
27
|
+
self,
|
|
28
|
+
expression,
|
|
29
|
+
url=None,
|
|
30
|
+
progress_callback=None
|
|
31
|
+
):
|
|
32
|
+
return self.run(
|
|
33
|
+
expression,
|
|
34
|
+
url,
|
|
35
|
+
progress_callback
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
def run(
|
|
39
|
+
self,
|
|
40
|
+
expression,
|
|
41
|
+
url=None,
|
|
42
|
+
progress_callback=None
|
|
43
|
+
):
|
|
44
|
+
|
|
45
|
+
config = parse(expression)
|
|
46
|
+
|
|
47
|
+
if url is not None:
|
|
48
|
+
config["url"] = url
|
|
49
|
+
|
|
50
|
+
if not config["url"]:
|
|
51
|
+
raise ValueError("URL missing")
|
|
52
|
+
|
|
53
|
+
# ========================================================
|
|
54
|
+
# Gallery-dl engine
|
|
55
|
+
# ========================================================
|
|
56
|
+
|
|
57
|
+
for block in config["blocks"]:
|
|
58
|
+
|
|
59
|
+
if block["type"] == "gallery":
|
|
60
|
+
|
|
61
|
+
result = self.gallery.execute(
|
|
62
|
+
config["url"],
|
|
63
|
+
cookies=COOKIES
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
if block["variable"]:
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
block["variable"]: result
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return result
|
|
73
|
+
|
|
74
|
+
# ========================================================
|
|
75
|
+
# Normal yt-dlp engine
|
|
76
|
+
# ========================================================
|
|
77
|
+
|
|
78
|
+
return self.downloader.execute(
|
|
79
|
+
config,
|
|
80
|
+
cookies=COOKIES,
|
|
81
|
+
progress_callback=progress_callback
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
rz = SimpleSoup()
|
simplesoup/downloader.py
ADDED
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import urllib.request
|
|
3
|
+
import yt_dlp
|
|
4
|
+
|
|
5
|
+
from .parser import STAGES
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Downloader:
|
|
9
|
+
|
|
10
|
+
def __init__(self):
|
|
11
|
+
self.path = "/storage/emulated/0/Download/ReiDownloader"
|
|
12
|
+
os.makedirs(self.path, exist_ok=True)
|
|
13
|
+
|
|
14
|
+
# ============================================================
|
|
15
|
+
# Helpers
|
|
16
|
+
# ============================================================
|
|
17
|
+
|
|
18
|
+
def _format_speed(self, speed):
|
|
19
|
+
if not speed:
|
|
20
|
+
return "0 KB/s"
|
|
21
|
+
|
|
22
|
+
if speed >= 1024 * 1024:
|
|
23
|
+
return f"{speed / (1024 * 1024):.1f} MB/s"
|
|
24
|
+
|
|
25
|
+
return f"{speed / 1024:.0f} KB/s"
|
|
26
|
+
|
|
27
|
+
def _progress_hook(
|
|
28
|
+
self,
|
|
29
|
+
data,
|
|
30
|
+
mode="Video",
|
|
31
|
+
progress_callback=None
|
|
32
|
+
):
|
|
33
|
+
|
|
34
|
+
status = data.get("status")
|
|
35
|
+
|
|
36
|
+
if status == "downloading":
|
|
37
|
+
|
|
38
|
+
total = (
|
|
39
|
+
data.get("total_bytes")
|
|
40
|
+
or data.get("total_bytes_estimate")
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
downloaded = data.get("downloaded_bytes", 0)
|
|
44
|
+
speed = data.get("speed")
|
|
45
|
+
|
|
46
|
+
if total:
|
|
47
|
+
percent = (downloaded / total) * 100
|
|
48
|
+
else:
|
|
49
|
+
percent = 0
|
|
50
|
+
|
|
51
|
+
network = self._format_speed(speed)
|
|
52
|
+
|
|
53
|
+
print(
|
|
54
|
+
f"\r[Download {percent:.0f}%] "
|
|
55
|
+
f"[Network {network}] "
|
|
56
|
+
f"[Mode {mode}]",
|
|
57
|
+
end="",
|
|
58
|
+
flush=True
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
if progress_callback:
|
|
62
|
+
try:
|
|
63
|
+
progress_callback(
|
|
64
|
+
f"{percent:.0f}%",
|
|
65
|
+
network,
|
|
66
|
+
mode
|
|
67
|
+
)
|
|
68
|
+
except Exception:
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
elif status == "finished":
|
|
72
|
+
|
|
73
|
+
network = self._format_speed(
|
|
74
|
+
data.get("speed")
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
print(
|
|
78
|
+
f"\r[Download 100%] "
|
|
79
|
+
f"[Network {network}] "
|
|
80
|
+
f"[Mode {mode}]"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
if progress_callback:
|
|
84
|
+
try:
|
|
85
|
+
progress_callback(
|
|
86
|
+
"100%",
|
|
87
|
+
network,
|
|
88
|
+
mode
|
|
89
|
+
)
|
|
90
|
+
except Exception:
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
def _get_format(self, quality):
|
|
94
|
+
|
|
95
|
+
if quality == "audio":
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
"bestaudio[acodec^=mp4a]/"
|
|
99
|
+
"bestaudio"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
elif quality == "best":
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
"bestvideo[vcodec^=avc1]+"
|
|
106
|
+
"bestaudio[acodec^=mp4a]/"
|
|
107
|
+
"bestvideo[vcodec^=avc1]+"
|
|
108
|
+
"bestaudio/"
|
|
109
|
+
"best"
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
else:
|
|
113
|
+
|
|
114
|
+
height = quality.replace("p", "")
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
f"bestvideo[height<={height}]"
|
|
118
|
+
f"[vcodec^=avc1]+"
|
|
119
|
+
f"bestaudio[acodec^=mp4a]/"
|
|
120
|
+
f"bestvideo[height<={height}]"
|
|
121
|
+
f"[vcodec^=avc1]+"
|
|
122
|
+
f"bestaudio/"
|
|
123
|
+
f"best"
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# ============================================================
|
|
127
|
+
# Main Engine
|
|
128
|
+
# ============================================================
|
|
129
|
+
|
|
130
|
+
def execute(
|
|
131
|
+
self,
|
|
132
|
+
config,
|
|
133
|
+
cookies=None,
|
|
134
|
+
progress_callback=None
|
|
135
|
+
):
|
|
136
|
+
|
|
137
|
+
url = config["url"]
|
|
138
|
+
|
|
139
|
+
# ---------------------------------
|
|
140
|
+
# Cookie validation
|
|
141
|
+
# ---------------------------------
|
|
142
|
+
|
|
143
|
+
if cookies:
|
|
144
|
+
|
|
145
|
+
if not os.path.isfile(cookies):
|
|
146
|
+
raise FileNotFoundError(
|
|
147
|
+
f"Cookies file not found: {cookies}"
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# ---------------------------------
|
|
151
|
+
# Find playlist block
|
|
152
|
+
# ---------------------------------
|
|
153
|
+
|
|
154
|
+
playlist = None
|
|
155
|
+
|
|
156
|
+
for block in config["blocks"]:
|
|
157
|
+
|
|
158
|
+
if block["type"] == "playlist":
|
|
159
|
+
playlist = block
|
|
160
|
+
break
|
|
161
|
+
|
|
162
|
+
# ---------------------------------
|
|
163
|
+
# Find video block
|
|
164
|
+
# ---------------------------------
|
|
165
|
+
|
|
166
|
+
video = None
|
|
167
|
+
|
|
168
|
+
for block in config["blocks"]:
|
|
169
|
+
|
|
170
|
+
if block["type"] == "video":
|
|
171
|
+
video = block
|
|
172
|
+
break
|
|
173
|
+
|
|
174
|
+
# =========================================================
|
|
175
|
+
# PLAYLIST ENGINE
|
|
176
|
+
# =========================================================
|
|
177
|
+
|
|
178
|
+
if playlist is not None:
|
|
179
|
+
|
|
180
|
+
quality_stage = playlist["quality"]
|
|
181
|
+
skip = playlist["skip"]
|
|
182
|
+
count = playlist["count"]
|
|
183
|
+
|
|
184
|
+
quality = STAGES[quality_stage]
|
|
185
|
+
|
|
186
|
+
fmt = self._get_format(quality)
|
|
187
|
+
|
|
188
|
+
# -----------------------------------------------------
|
|
189
|
+
# Metadata + playlist title
|
|
190
|
+
# -----------------------------------------------------
|
|
191
|
+
|
|
192
|
+
metadata_options = {
|
|
193
|
+
"quiet": True,
|
|
194
|
+
"no_warnings": True,
|
|
195
|
+
"extract_flat": True,
|
|
196
|
+
"noplaylist": False
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if cookies:
|
|
200
|
+
metadata_options["cookiefile"] = cookies
|
|
201
|
+
|
|
202
|
+
with yt_dlp.YoutubeDL(
|
|
203
|
+
metadata_options
|
|
204
|
+
) as ydl:
|
|
205
|
+
|
|
206
|
+
playlist_info = ydl.extract_info(
|
|
207
|
+
url,
|
|
208
|
+
download=False
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
result = {}
|
|
212
|
+
|
|
213
|
+
# -----------------------------------------------------
|
|
214
|
+
# Metadata blocks
|
|
215
|
+
# -----------------------------------------------------
|
|
216
|
+
|
|
217
|
+
for block in config["blocks"]:
|
|
218
|
+
|
|
219
|
+
variable = block["variable"]
|
|
220
|
+
|
|
221
|
+
if not variable:
|
|
222
|
+
continue
|
|
223
|
+
|
|
224
|
+
block_type = block["type"]
|
|
225
|
+
|
|
226
|
+
if block_type == "title":
|
|
227
|
+
|
|
228
|
+
result[variable] = playlist_info.get(
|
|
229
|
+
"title"
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
elif block_type == "creator":
|
|
233
|
+
|
|
234
|
+
result[variable] = (
|
|
235
|
+
playlist_info.get("uploader")
|
|
236
|
+
or playlist_info.get("channel")
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
elif block_type == "url":
|
|
240
|
+
|
|
241
|
+
result[variable] = (
|
|
242
|
+
playlist_info.get("webpage_url")
|
|
243
|
+
or url
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
elif block_type == "views":
|
|
247
|
+
|
|
248
|
+
result[variable] = playlist_info.get(
|
|
249
|
+
"view_count"
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
elif block_type == "likes":
|
|
253
|
+
|
|
254
|
+
result[variable] = playlist_info.get(
|
|
255
|
+
"like_count"
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
elif block_type == "count":
|
|
259
|
+
|
|
260
|
+
result[variable] = playlist_info.get(
|
|
261
|
+
"comment_count"
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
elif block_type == "time":
|
|
265
|
+
|
|
266
|
+
result[variable] = playlist_info.get(
|
|
267
|
+
"duration"
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
elif block_type == "playlist_title":
|
|
271
|
+
|
|
272
|
+
result[variable] = playlist_info.get(
|
|
273
|
+
"title"
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
# -----------------------------------------------------
|
|
277
|
+
# Playlist range
|
|
278
|
+
# -----------------------------------------------------
|
|
279
|
+
|
|
280
|
+
start = skip + 1
|
|
281
|
+
end = skip + count
|
|
282
|
+
|
|
283
|
+
playlist_items = f"{start}-{end}"
|
|
284
|
+
|
|
285
|
+
output = os.path.join(
|
|
286
|
+
self.path,
|
|
287
|
+
"%(title)s.%(ext)s"
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
options = {
|
|
291
|
+
"format": fmt,
|
|
292
|
+
"outtmpl": output,
|
|
293
|
+
"noplaylist": False,
|
|
294
|
+
"playlist_items": playlist_items,
|
|
295
|
+
"merge_output_format": "mp4",
|
|
296
|
+
"quiet": True,
|
|
297
|
+
"no_warnings": True,
|
|
298
|
+
"progress_hooks": [
|
|
299
|
+
lambda data: self._progress_hook(
|
|
300
|
+
data,
|
|
301
|
+
"Playlist",
|
|
302
|
+
progress_callback
|
|
303
|
+
)
|
|
304
|
+
]
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if cookies:
|
|
308
|
+
options["cookiefile"] = cookies
|
|
309
|
+
|
|
310
|
+
print(
|
|
311
|
+
f"Playlist download: "
|
|
312
|
+
f"quality={quality}, "
|
|
313
|
+
f"skip={skip}, "
|
|
314
|
+
f"count={count}"
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
# -----------------------------------------------------
|
|
318
|
+
# Download playlist
|
|
319
|
+
# -----------------------------------------------------
|
|
320
|
+
|
|
321
|
+
downloaded_files = []
|
|
322
|
+
|
|
323
|
+
with yt_dlp.YoutubeDL(options) as ydl:
|
|
324
|
+
|
|
325
|
+
final_info = ydl.extract_info(
|
|
326
|
+
url,
|
|
327
|
+
download=True
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
entries = final_info.get(
|
|
331
|
+
"entries",
|
|
332
|
+
[]
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
for entry in entries:
|
|
336
|
+
|
|
337
|
+
if not entry:
|
|
338
|
+
continue
|
|
339
|
+
|
|
340
|
+
filepath = ydl.prepare_filename(
|
|
341
|
+
entry
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
if not os.path.exists(filepath):
|
|
345
|
+
|
|
346
|
+
mp4_path = (
|
|
347
|
+
os.path.splitext(filepath)[0]
|
|
348
|
+
+ ".mp4"
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
if os.path.exists(mp4_path):
|
|
352
|
+
filepath = mp4_path
|
|
353
|
+
|
|
354
|
+
if os.path.exists(filepath):
|
|
355
|
+
|
|
356
|
+
downloaded_files.append(
|
|
357
|
+
filepath
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
# -----------------------------------------------------
|
|
361
|
+
# Return playlist files
|
|
362
|
+
# -----------------------------------------------------
|
|
363
|
+
|
|
364
|
+
result["videos"] = downloaded_files
|
|
365
|
+
|
|
366
|
+
return result
|
|
367
|
+
|
|
368
|
+
# =========================================================
|
|
369
|
+
# NORMAL SINGLE-VIDEO ENGINE
|
|
370
|
+
# =========================================================
|
|
371
|
+
|
|
372
|
+
metadata_options = {
|
|
373
|
+
"quiet": True,
|
|
374
|
+
"no_warnings": True
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if cookies:
|
|
378
|
+
metadata_options["cookiefile"] = cookies
|
|
379
|
+
|
|
380
|
+
with yt_dlp.YoutubeDL(
|
|
381
|
+
metadata_options
|
|
382
|
+
) as ydl:
|
|
383
|
+
|
|
384
|
+
info = ydl.extract_info(
|
|
385
|
+
url,
|
|
386
|
+
download=False
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
result = {}
|
|
390
|
+
|
|
391
|
+
# ---------------------------------
|
|
392
|
+
# Metadata blocks
|
|
393
|
+
# ---------------------------------
|
|
394
|
+
|
|
395
|
+
for block in config["blocks"]:
|
|
396
|
+
|
|
397
|
+
variable = block["variable"]
|
|
398
|
+
|
|
399
|
+
if not variable:
|
|
400
|
+
continue
|
|
401
|
+
|
|
402
|
+
block_type = block["type"]
|
|
403
|
+
|
|
404
|
+
if block_type == "title":
|
|
405
|
+
|
|
406
|
+
result[variable] = info.get(
|
|
407
|
+
"title"
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
elif block_type == "creator":
|
|
411
|
+
|
|
412
|
+
result[variable] = (
|
|
413
|
+
info.get("uploader")
|
|
414
|
+
or info.get("channel")
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
elif block_type == "url":
|
|
418
|
+
|
|
419
|
+
result[variable] = (
|
|
420
|
+
info.get("webpage_url")
|
|
421
|
+
or url
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
elif block_type == "views":
|
|
425
|
+
|
|
426
|
+
result[variable] = info.get(
|
|
427
|
+
"view_count"
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
elif block_type == "likes":
|
|
431
|
+
|
|
432
|
+
result[variable] = info.get(
|
|
433
|
+
"like_count"
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
elif block_type == "count":
|
|
437
|
+
|
|
438
|
+
result[variable] = info.get(
|
|
439
|
+
"comment_count"
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
elif block_type == "time":
|
|
443
|
+
|
|
444
|
+
result[variable] = info.get(
|
|
445
|
+
"duration"
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
elif block_type == "thumbnail":
|
|
449
|
+
|
|
450
|
+
thumbnail_url = info.get(
|
|
451
|
+
"thumbnail"
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
if thumbnail_url:
|
|
455
|
+
|
|
456
|
+
title = (
|
|
457
|
+
info.get("title")
|
|
458
|
+
or "thumbnail"
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
thumbnail_path = os.path.join(
|
|
462
|
+
self.path,
|
|
463
|
+
f"{title}.jpg"
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
urllib.request.urlretrieve(
|
|
467
|
+
thumbnail_url,
|
|
468
|
+
thumbnail_path
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
result[variable] = thumbnail_path
|
|
472
|
+
|
|
473
|
+
else:
|
|
474
|
+
|
|
475
|
+
result[variable] = None
|
|
476
|
+
|
|
477
|
+
elif block_type == "formats":
|
|
478
|
+
|
|
479
|
+
result[variable] = info.get(
|
|
480
|
+
"formats",
|
|
481
|
+
[]
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
# ---------------------------------
|
|
485
|
+
# Metadata-only request
|
|
486
|
+
# ---------------------------------
|
|
487
|
+
|
|
488
|
+
if video is None:
|
|
489
|
+
return result
|
|
490
|
+
|
|
491
|
+
# ---------------------------------
|
|
492
|
+
# Quality
|
|
493
|
+
# ---------------------------------
|
|
494
|
+
|
|
495
|
+
stage = video["stage"]
|
|
496
|
+
|
|
497
|
+
quality = STAGES[stage]
|
|
498
|
+
|
|
499
|
+
fmt = self._get_format(quality)
|
|
500
|
+
|
|
501
|
+
# ---------------------------------
|
|
502
|
+
# Download options
|
|
503
|
+
# ---------------------------------
|
|
504
|
+
|
|
505
|
+
output = os.path.join(
|
|
506
|
+
self.path,
|
|
507
|
+
"%(title)s.%(ext)s"
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
options = {
|
|
511
|
+
"format": fmt,
|
|
512
|
+
"outtmpl": output,
|
|
513
|
+
"noplaylist": True,
|
|
514
|
+
"merge_output_format": "mp4",
|
|
515
|
+
"quiet": True,
|
|
516
|
+
"progress_hooks": [
|
|
517
|
+
lambda data: self._progress_hook(
|
|
518
|
+
data,
|
|
519
|
+
"Video",
|
|
520
|
+
progress_callback
|
|
521
|
+
)
|
|
522
|
+
]
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
if cookies:
|
|
526
|
+
options["cookiefile"] = cookies
|
|
527
|
+
|
|
528
|
+
print(
|
|
529
|
+
f"Downloading: {quality}"
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
with yt_dlp.YoutubeDL(options) as ydl:
|
|
533
|
+
|
|
534
|
+
final_info = ydl.extract_info(
|
|
535
|
+
url,
|
|
536
|
+
download=True
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
filepath = ydl.prepare_filename(
|
|
540
|
+
final_info
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
if not os.path.exists(filepath):
|
|
544
|
+
|
|
545
|
+
mp4_path = (
|
|
546
|
+
os.path.splitext(filepath)[0]
|
|
547
|
+
+ ".mp4"
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
if os.path.exists(mp4_path):
|
|
551
|
+
filepath = mp4_path
|
|
552
|
+
|
|
553
|
+
result[video["variable"]] = filepath
|
|
554
|
+
|
|
555
|
+
return result
|
simplesoup/gallery.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import subprocess
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Gallery:
|
|
6
|
+
|
|
7
|
+
def __init__(self):
|
|
8
|
+
self.path = "/storage/emulated/0/Download/ReiDownloader"
|
|
9
|
+
os.makedirs(self.path, exist_ok=True)
|
|
10
|
+
|
|
11
|
+
def execute(self, url, cookies=None):
|
|
12
|
+
|
|
13
|
+
command = [
|
|
14
|
+
"gallery-dl",
|
|
15
|
+
"--directory",
|
|
16
|
+
self.path,
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
# ---------------------------------
|
|
20
|
+
# Automatic cookies
|
|
21
|
+
# ---------------------------------
|
|
22
|
+
|
|
23
|
+
if cookies:
|
|
24
|
+
|
|
25
|
+
if not os.path.isfile(cookies):
|
|
26
|
+
raise FileNotFoundError(
|
|
27
|
+
f"Cookies file not found: {cookies}"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
command.extend([
|
|
31
|
+
"--cookies",
|
|
32
|
+
cookies
|
|
33
|
+
])
|
|
34
|
+
|
|
35
|
+
command.append(url)
|
|
36
|
+
|
|
37
|
+
print("Downloading gallery...")
|
|
38
|
+
|
|
39
|
+
subprocess.run(
|
|
40
|
+
command,
|
|
41
|
+
check=True
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
return self.path
|
simplesoup/parser.py
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
STAGES = {
|
|
2
|
+
0: "audio",
|
|
3
|
+
2: "360p",
|
|
4
|
+
3: "480p",
|
|
5
|
+
4: "720p",
|
|
6
|
+
5: "1080p",
|
|
7
|
+
6: "best",
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def parse(expression):
|
|
12
|
+
"""
|
|
13
|
+
Parse SimpleSoup configuration.
|
|
14
|
+
|
|
15
|
+
Supported format:
|
|
16
|
+
|
|
17
|
+
{
|
|
18
|
+
"video[4]": "video",
|
|
19
|
+
"playlist": "4:10:4",
|
|
20
|
+
"title": "title",
|
|
21
|
+
"creator": "creator",
|
|
22
|
+
"url": "url",
|
|
23
|
+
"views": "views",
|
|
24
|
+
"likes": "likes",
|
|
25
|
+
"count": "count",
|
|
26
|
+
"time": "duration",
|
|
27
|
+
"thumbnail": "thumbnail"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
Playlist format:
|
|
31
|
+
|
|
32
|
+
"playlist": "quality:skip:count"
|
|
33
|
+
|
|
34
|
+
Example:
|
|
35
|
+
|
|
36
|
+
"playlist": "4:10:4"
|
|
37
|
+
|
|
38
|
+
Means:
|
|
39
|
+
|
|
40
|
+
4 = 720p
|
|
41
|
+
10 = skip first 10 videos
|
|
42
|
+
4 = download next 4 videos
|
|
43
|
+
|
|
44
|
+
Old rz.(...) syntax is also supported.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
# -----------------------------
|
|
48
|
+
# New dictionary-style syntax
|
|
49
|
+
# -----------------------------
|
|
50
|
+
if isinstance(expression, dict):
|
|
51
|
+
|
|
52
|
+
result = {
|
|
53
|
+
"url": None,
|
|
54
|
+
"blocks": []
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
for key, variable in expression.items():
|
|
58
|
+
|
|
59
|
+
key = str(key).strip()
|
|
60
|
+
variable = str(variable).strip()
|
|
61
|
+
|
|
62
|
+
# video[4]
|
|
63
|
+
if key.startswith("video[") and key.endswith("]"):
|
|
64
|
+
|
|
65
|
+
stage_text = key[6:-1]
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
stage = int(stage_text)
|
|
69
|
+
except ValueError:
|
|
70
|
+
raise ValueError(
|
|
71
|
+
f"Invalid video stage: {stage_text}"
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
if stage not in STAGES:
|
|
75
|
+
raise ValueError(
|
|
76
|
+
f"Unknown video stage: {stage}"
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
result["blocks"].append({
|
|
80
|
+
"type": "video",
|
|
81
|
+
"stage": stage,
|
|
82
|
+
"variable": variable
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
continue
|
|
86
|
+
|
|
87
|
+
# playlist
|
|
88
|
+
if key == "playlist":
|
|
89
|
+
|
|
90
|
+
parts = variable.split(":")
|
|
91
|
+
|
|
92
|
+
if len(parts) != 3:
|
|
93
|
+
raise ValueError(
|
|
94
|
+
"Playlist syntax: playlist:quality:skip:count"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
quality = int(parts[0])
|
|
99
|
+
skip = int(parts[1])
|
|
100
|
+
count = int(parts[2])
|
|
101
|
+
except ValueError:
|
|
102
|
+
raise ValueError(
|
|
103
|
+
"Playlist values must be numbers"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
if quality not in STAGES:
|
|
107
|
+
raise ValueError(
|
|
108
|
+
f"Unknown playlist quality: {quality}"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
if skip < 0:
|
|
112
|
+
raise ValueError(
|
|
113
|
+
"Playlist skip cannot be negative"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
if count <= 0:
|
|
117
|
+
raise ValueError(
|
|
118
|
+
"Playlist count must be greater than 0"
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
result["blocks"].append({
|
|
122
|
+
"type": "playlist",
|
|
123
|
+
"quality": quality,
|
|
124
|
+
"skip": skip,
|
|
125
|
+
"count": count,
|
|
126
|
+
"variable": None
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
continue
|
|
130
|
+
|
|
131
|
+
# normal block
|
|
132
|
+
result["blocks"].append({
|
|
133
|
+
"type": key,
|
|
134
|
+
"variable": variable
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
return result
|
|
138
|
+
|
|
139
|
+
# -----------------------------
|
|
140
|
+
# Old rz.(...) syntax
|
|
141
|
+
# -----------------------------
|
|
142
|
+
expression = expression.strip()
|
|
143
|
+
|
|
144
|
+
if not expression.startswith("rz.(") or not expression.endswith(")"):
|
|
145
|
+
raise ValueError("Invalid SimpleSoup syntax")
|
|
146
|
+
|
|
147
|
+
body = expression[4:-1].strip()
|
|
148
|
+
|
|
149
|
+
parts = [
|
|
150
|
+
p.strip()
|
|
151
|
+
for p in body.split(";")
|
|
152
|
+
if p.strip()
|
|
153
|
+
]
|
|
154
|
+
|
|
155
|
+
result = {
|
|
156
|
+
"url": None,
|
|
157
|
+
"blocks": []
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
for part in parts:
|
|
161
|
+
|
|
162
|
+
# URL
|
|
163
|
+
if part.startswith("{") and part.endswith("}"):
|
|
164
|
+
result["url"] = part[1:-1].strip()
|
|
165
|
+
continue
|
|
166
|
+
|
|
167
|
+
# video[4]:variable
|
|
168
|
+
if part.startswith("video["):
|
|
169
|
+
|
|
170
|
+
if "]:" not in part:
|
|
171
|
+
raise ValueError(
|
|
172
|
+
"Video syntax: video[stage]:variable"
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
stage_text, variable = part.split("]:", 1)
|
|
176
|
+
|
|
177
|
+
try:
|
|
178
|
+
stage = int(stage_text[6:])
|
|
179
|
+
except ValueError:
|
|
180
|
+
raise ValueError(
|
|
181
|
+
"Invalid video stage"
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
if stage not in STAGES:
|
|
185
|
+
raise ValueError(
|
|
186
|
+
f"Unknown video stage: {stage}"
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
result["blocks"].append({
|
|
190
|
+
"type": "video",
|
|
191
|
+
"stage": stage,
|
|
192
|
+
"variable": variable.strip()
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
continue
|
|
196
|
+
|
|
197
|
+
# name:variable
|
|
198
|
+
if ":" in part:
|
|
199
|
+
|
|
200
|
+
name, variable = part.split(":", 1)
|
|
201
|
+
|
|
202
|
+
result["blocks"].append({
|
|
203
|
+
"type": name.strip(),
|
|
204
|
+
"variable": variable.strip()
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
continue
|
|
208
|
+
|
|
209
|
+
# simple block
|
|
210
|
+
result["blocks"].append({
|
|
211
|
+
"type": part,
|
|
212
|
+
"variable": None
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
if not result["url"]:
|
|
216
|
+
raise ValueError("URL missing")
|
|
217
|
+
|
|
218
|
+
return result
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: simplesoup
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Simple CSS-style yt-dlp and FFmpeg wrapper
|
|
5
|
+
Requires-Python: >=3.8
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: yt-dlp
|
|
8
|
+
Provides-Extra: gallery
|
|
9
|
+
Requires-Dist: gallery-dl; extra == "gallery"
|
|
10
|
+
|
|
11
|
+
# SimpleSoup
|
|
12
|
+
|
|
13
|
+
A simple Python wrapper for yt-dlp and FFmpeg.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install simplesoup
|
|
19
|
+
|
|
20
|
+
For gallery support:
|
|
21
|
+
|
|
22
|
+
pip install "simplesoup[gallery]"
|
|
23
|
+
|
|
24
|
+
Import
|
|
25
|
+
|
|
26
|
+
from simplesoup import rz
|
|
27
|
+
|
|
28
|
+
Basic Syntax
|
|
29
|
+
|
|
30
|
+
result = rz({
|
|
31
|
+
"WHAT_TO_REQUEST": "VARIABLE_NAME"
|
|
32
|
+
}, url)
|
|
33
|
+
|
|
34
|
+
WHAT_TO_REQUEST batata hai kya chahiye, aur VARIABLE_NAME batata hai result mein us value ko kis naam se save karna hai.
|
|
35
|
+
|
|
36
|
+
Example:
|
|
37
|
+
|
|
38
|
+
result = rz({
|
|
39
|
+
"title": "title"
|
|
40
|
+
}, url)
|
|
41
|
+
|
|
42
|
+
print(result["title"])
|
|
43
|
+
|
|
44
|
+
Video Download
|
|
45
|
+
|
|
46
|
+
result = rz({
|
|
47
|
+
"video[4]": "video"
|
|
48
|
+
}, url)
|
|
49
|
+
|
|
50
|
+
print(result["video"])
|
|
51
|
+
|
|
52
|
+
video[4] = 720p.
|
|
53
|
+
|
|
54
|
+
Video Quality
|
|
55
|
+
|
|
56
|
+
video[0] → audio
|
|
57
|
+
video[2] → 360p
|
|
58
|
+
video[3] → 480p
|
|
59
|
+
video[4] → 720p
|
|
60
|
+
video[5] → 1080p
|
|
61
|
+
video[6] → best available
|
|
62
|
+
|
|
63
|
+
Example:
|
|
64
|
+
|
|
65
|
+
result = rz({
|
|
66
|
+
"video[5]": "video"
|
|
67
|
+
}, url)
|
|
68
|
+
|
|
69
|
+
Multiple Values At Once
|
|
70
|
+
|
|
71
|
+
Ek hi request mein video aur metadata dono le sakte ho:
|
|
72
|
+
|
|
73
|
+
result = rz({
|
|
74
|
+
"video[4]": "video",
|
|
75
|
+
"title": "title",
|
|
76
|
+
"creator": "creator",
|
|
77
|
+
"url": "video_url",
|
|
78
|
+
"views": "views",
|
|
79
|
+
"likes": "likes",
|
|
80
|
+
"count": "comments",
|
|
81
|
+
"time": "duration",
|
|
82
|
+
"thumbnail": "thumbnail"
|
|
83
|
+
}, url)
|
|
84
|
+
|
|
85
|
+
Values access karne ke liye:
|
|
86
|
+
|
|
87
|
+
print(result["video"])
|
|
88
|
+
print(result["title"])
|
|
89
|
+
print(result["creator"])
|
|
90
|
+
print(result["video_url"])
|
|
91
|
+
print(result["views"])
|
|
92
|
+
print(result["likes"])
|
|
93
|
+
print(result["comments"])
|
|
94
|
+
print(result["duration"])
|
|
95
|
+
print(result["thumbnail"])
|
|
96
|
+
|
|
97
|
+
Order matter nahi karta.
|
|
98
|
+
|
|
99
|
+
Metadata
|
|
100
|
+
|
|
101
|
+
Available metadata:
|
|
102
|
+
|
|
103
|
+
title → video title
|
|
104
|
+
creator → uploader/channel
|
|
105
|
+
url → video URL
|
|
106
|
+
views → view count
|
|
107
|
+
likes → like count
|
|
108
|
+
count → comment count
|
|
109
|
+
time → duration in seconds
|
|
110
|
+
thumbnail → downloaded thumbnail path
|
|
111
|
+
formats → available yt-dlp formats
|
|
112
|
+
|
|
113
|
+
Metadata Only
|
|
114
|
+
|
|
115
|
+
Agar sirf metadata chahiye aur video download nahi karna:
|
|
116
|
+
|
|
117
|
+
result = rz({
|
|
118
|
+
"title": "title",
|
|
119
|
+
"creator": "creator",
|
|
120
|
+
"views": "views",
|
|
121
|
+
"likes": "likes",
|
|
122
|
+
"count": "comments",
|
|
123
|
+
"time": "duration"
|
|
124
|
+
}, url)
|
|
125
|
+
|
|
126
|
+
video[...] block nahi hone par video download nahi hota.
|
|
127
|
+
|
|
128
|
+
Thumbnail
|
|
129
|
+
|
|
130
|
+
result = rz({
|
|
131
|
+
"thumbnail": "thumbnail"
|
|
132
|
+
}, url)
|
|
133
|
+
|
|
134
|
+
print(result["thumbnail"])
|
|
135
|
+
|
|
136
|
+
Thumbnail actual .jpg file ke roop mein download hota hai.
|
|
137
|
+
|
|
138
|
+
Playlist
|
|
139
|
+
|
|
140
|
+
Playlist syntax:
|
|
141
|
+
|
|
142
|
+
result = rz({
|
|
143
|
+
"playlist": "QUALITY:SKIP:COUNT"
|
|
144
|
+
}, playlist_url)
|
|
145
|
+
|
|
146
|
+
Example:
|
|
147
|
+
|
|
148
|
+
result = rz({
|
|
149
|
+
"playlist": "4:20:3"
|
|
150
|
+
}, playlist_url)
|
|
151
|
+
|
|
152
|
+
Meaning:
|
|
153
|
+
|
|
154
|
+
4 → 720p
|
|
155
|
+
20 → first 20 videos skip
|
|
156
|
+
3 → next 3 videos download
|
|
157
|
+
|
|
158
|
+
So:
|
|
159
|
+
|
|
160
|
+
Video 1-20 → skip
|
|
161
|
+
Video 21 → download
|
|
162
|
+
Video 22 → download
|
|
163
|
+
Video 23 → download
|
|
164
|
+
|
|
165
|
+
Downloaded videos:
|
|
166
|
+
|
|
167
|
+
for video in result["videos"]:
|
|
168
|
+
print(video)
|
|
169
|
+
|
|
170
|
+
Playlist + Metadata
|
|
171
|
+
|
|
172
|
+
result = rz({
|
|
173
|
+
"playlist": "4:20:3",
|
|
174
|
+
"title": "playlist_title"
|
|
175
|
+
}, playlist_url)
|
|
176
|
+
|
|
177
|
+
print(result["playlist_title"])
|
|
178
|
+
print(result["videos"])
|
|
179
|
+
|
|
180
|
+
Gallery
|
|
181
|
+
|
|
182
|
+
Gallery downloading ke liye:
|
|
183
|
+
|
|
184
|
+
result = rz({
|
|
185
|
+
"gallery": "images"
|
|
186
|
+
}, gallery_url)
|
|
187
|
+
|
|
188
|
+
print(result)
|
|
189
|
+
|
|
190
|
+
Gallery support ke liye gallery-dl install hona chahiye:
|
|
191
|
+
|
|
192
|
+
pip install "simplesoup[gallery]"
|
|
193
|
+
|
|
194
|
+
Cookies
|
|
195
|
+
|
|
196
|
+
Cookies simplesoup/core.py mein configure kiye jaate hain:
|
|
197
|
+
|
|
198
|
+
COOKIES = "/storage/emulated/0/Download/cookies.txt"
|
|
199
|
+
|
|
200
|
+
Example:
|
|
201
|
+
|
|
202
|
+
COOKIES = "/storage/emulated/0/Download/reddit_cookies.txt"
|
|
203
|
+
|
|
204
|
+
Cookies disable karne ke liye:
|
|
205
|
+
|
|
206
|
+
COOKIES = None
|
|
207
|
+
|
|
208
|
+
SimpleSoup configured cookie file ko use karta hai.
|
|
209
|
+
|
|
210
|
+
Progress Callback
|
|
211
|
+
|
|
212
|
+
Optional progress callback:
|
|
213
|
+
|
|
214
|
+
def progress(percent, speed, mode):
|
|
215
|
+
print(
|
|
216
|
+
f"[Download {percent}] "
|
|
217
|
+
f"[Network {speed}] "
|
|
218
|
+
f"[Mode {mode}]"
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
result = rz(
|
|
222
|
+
{
|
|
223
|
+
"video[4]": "video"
|
|
224
|
+
},
|
|
225
|
+
url,
|
|
226
|
+
progress_callback=progress
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
Callback ko ye 3 values milti hain:
|
|
230
|
+
|
|
231
|
+
percent → download percentage
|
|
232
|
+
speed → network speed
|
|
233
|
+
mode → Video / Playlist
|
|
234
|
+
|
|
235
|
+
Complete Example
|
|
236
|
+
|
|
237
|
+
from simplesoup import rz
|
|
238
|
+
|
|
239
|
+
url = "VIDEO_URL"
|
|
240
|
+
|
|
241
|
+
result = rz({
|
|
242
|
+
"video[4]": "video",
|
|
243
|
+
"title": "title",
|
|
244
|
+
"creator": "creator",
|
|
245
|
+
"url": "video_url",
|
|
246
|
+
"views": "views",
|
|
247
|
+
"likes": "likes",
|
|
248
|
+
"count": "comments",
|
|
249
|
+
"time": "duration",
|
|
250
|
+
"thumbnail": "thumbnail"
|
|
251
|
+
}, url)
|
|
252
|
+
|
|
253
|
+
print("Video:", result["video"])
|
|
254
|
+
print("Title:", result["title"])
|
|
255
|
+
print("Creator:", result["creator"])
|
|
256
|
+
print("URL:", result["video_url"])
|
|
257
|
+
print("Views:", result["views"])
|
|
258
|
+
print("Likes:", result["likes"])
|
|
259
|
+
print("Comments:", result["comments"])
|
|
260
|
+
print("Duration:", result["duration"])
|
|
261
|
+
print("Thumbnail:", result["thumbnail"])
|
|
262
|
+
|
|
263
|
+
Default Download Location
|
|
264
|
+
|
|
265
|
+
/storage/emulated/0/Download/ReiDownloader/
|
|
266
|
+
|
|
267
|
+
API Syntax Summary
|
|
268
|
+
|
|
269
|
+
Single video:
|
|
270
|
+
|
|
271
|
+
rz({
|
|
272
|
+
"video[QUALITY]": "variable"
|
|
273
|
+
}, url)
|
|
274
|
+
|
|
275
|
+
Metadata:
|
|
276
|
+
|
|
277
|
+
rz({
|
|
278
|
+
"title": "variable",
|
|
279
|
+
"creator": "variable",
|
|
280
|
+
"url": "variable",
|
|
281
|
+
"views": "variable",
|
|
282
|
+
"likes": "variable",
|
|
283
|
+
"count": "variable",
|
|
284
|
+
"time": "variable",
|
|
285
|
+
"thumbnail": "variable"
|
|
286
|
+
}, url)
|
|
287
|
+
|
|
288
|
+
Video + metadata:
|
|
289
|
+
|
|
290
|
+
rz({
|
|
291
|
+
"video[4]": "video",
|
|
292
|
+
"title": "title",
|
|
293
|
+
"creator": "creator",
|
|
294
|
+
"views": "views",
|
|
295
|
+
"likes": "likes",
|
|
296
|
+
"count": "comments",
|
|
297
|
+
"time": "duration",
|
|
298
|
+
"thumbnail": "thumbnail"
|
|
299
|
+
}, url)
|
|
300
|
+
|
|
301
|
+
Playlist:
|
|
302
|
+
|
|
303
|
+
rz({
|
|
304
|
+
"playlist": "QUALITY:SKIP:COUNT"
|
|
305
|
+
}, playlist_url)
|
|
306
|
+
|
|
307
|
+
Gallery:
|
|
308
|
+
|
|
309
|
+
rz({
|
|
310
|
+
"gallery": "variable"
|
|
311
|
+
}, gallery_url)
|
|
312
|
+
|
|
313
|
+
Version
|
|
314
|
+
|
|
315
|
+
SimpleSoup 0.1.0
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
simplesoup/__init__.py,sha256=sqmWkYkrTrw-fNgy-SJU0nLd2Gwnt8WJITEAfucpARE,65
|
|
2
|
+
simplesoup/converter.py,sha256=x8Rhipe1XYvrsVjnxsObKrGAB-lQwiNFohVMP3MQXYQ,566
|
|
3
|
+
simplesoup/core.py,sha256=gjHagBqNHnI2-ur5wBSKFSKtq39PShHSk7mxp_6pWag,1958
|
|
4
|
+
simplesoup/downloader.py,sha256=LECSYyzWnhvymfHH-HzTrzwjhbJZIXCmtoGzSbgfH9Q,14052
|
|
5
|
+
simplesoup/gallery.py,sha256=9S7DT5AD1maEXFye3H5Q9fBbZU-0JjD15amQ39_vgNE,903
|
|
6
|
+
simplesoup/parser.py,sha256=AJzp9Lkjgu7arCk-MPTu_yK6xvF6YgRs8QG8gPwtM_g,5216
|
|
7
|
+
simplesoup-0.1.0.dist-info/METADATA,sha256=deC95gVPhsOnDmJcr0E19l5gjmffaFLBbzphK1qGc84,5283
|
|
8
|
+
simplesoup-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
9
|
+
simplesoup-0.1.0.dist-info/top_level.txt,sha256=jtEKGzuqamPe-w0c81H0XtiE-9xk27twJdIwN43L_tk,11
|
|
10
|
+
simplesoup-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
simplesoup
|