mediadown 3.2.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.
mediadown/__init__.py
ADDED
|
File without changes
|
mediadown/cli.py
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import subprocess
|
|
4
|
+
import time
|
|
5
|
+
import shutil
|
|
6
|
+
import re
|
|
7
|
+
import inquirer
|
|
8
|
+
import yt_dlp
|
|
9
|
+
import requests
|
|
10
|
+
import urllib3
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from bs4 import BeautifulSoup
|
|
13
|
+
from rich.console import Console
|
|
14
|
+
from rich.panel import Panel
|
|
15
|
+
from rich.progress import Progress, TextColumn, BarColumn, DownloadColumn, TransferSpeedColumn, TimeRemainingColumn
|
|
16
|
+
|
|
17
|
+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
18
|
+
console = Console()
|
|
19
|
+
SAVE_DIR = Path("/sdcard/Download")
|
|
20
|
+
|
|
21
|
+
def clear():
|
|
22
|
+
os.system("clear" if os.name != "nt" else "cls")
|
|
23
|
+
|
|
24
|
+
def draw_banner():
|
|
25
|
+
banner = (
|
|
26
|
+
"[italic magenta]╦ ╦╔╦╗ ╔╦╗╦ ╔═╗[/]\n"
|
|
27
|
+
"[magenta]╚╦╝ ║───║║║ ╠═╝[/]\n"
|
|
28
|
+
"[magenta] ╩ ╩ ═╩╝╩═╝╩ [/] [white]Version : 3.2 [/]\n"
|
|
29
|
+
" [bold reverse magenta] KEYBOARDWARRIOR | OFFICIAL [/]\n\n"
|
|
30
|
+
"[bold cyan]Social Media Downloader[/]\n"
|
|
31
|
+
"[dim white]YouTube, Facebook, Instagram, TikTok[/]"
|
|
32
|
+
)
|
|
33
|
+
console.print(Panel(banner, border_style="magenta", expand=False))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def tentukan_lokasi(subfolder):
|
|
37
|
+
print(f"\n[ℹ] Info: Folder default -> {SAVE_DIR / subfolder}")
|
|
38
|
+
user_input = input("➔ Masukkan lokasi penyimpanan (Kosongin = default): ").strip()
|
|
39
|
+
jalur_final = Path(user_input) if user_input else SAVE_DIR / subfolder
|
|
40
|
+
try:
|
|
41
|
+
jalur_final.mkdir(parents=True, exist_ok=True)
|
|
42
|
+
except PermissionError:
|
|
43
|
+
console.print("[bold red]❌ Error:[/bold red] Izin penyimpanan ditolak! Ketik 'termux-setup-storage' dulu.")
|
|
44
|
+
sys.exit(1)
|
|
45
|
+
return jalur_final
|
|
46
|
+
|
|
47
|
+
def ambil_input_url():
|
|
48
|
+
console.print("\n[bold yellow]➔ Tempel/Masukkan URL Target[/bold yellow]")
|
|
49
|
+
console.print("[dim]Pisahkan dengan spasi, koma, atau Enter. Tekan Enter 2x jika sudah selesai:[/dim]")
|
|
50
|
+
baris = []
|
|
51
|
+
while True:
|
|
52
|
+
line = input()
|
|
53
|
+
if not line: break
|
|
54
|
+
baris.append(line)
|
|
55
|
+
teks_mentah = " ".join(baris).replace(",", " ")
|
|
56
|
+
return [u.strip() for u in teks_mentah.split() if u.strip()]
|
|
57
|
+
|
|
58
|
+
def jalankan_unduhan(urls, subfolder, butuh_login=False):
|
|
59
|
+
folder_tujuan = tentukan_lokasi(subfolder)
|
|
60
|
+
for index, url in enumerate(urls, 1):
|
|
61
|
+
clear()
|
|
62
|
+
draw_banner()
|
|
63
|
+
console.print(Panel(f"[bold cyan]Mengunduh ke-{index} dari {len(urls)}[/bold cyan]\n[dim]Target : {url}[/dim]", border_style="cyan"))
|
|
64
|
+
ydl_opts = {
|
|
65
|
+
'format': 'bestvideo+bestaudio/best',
|
|
66
|
+
'merge_output_format': 'mp4',
|
|
67
|
+
'outtmpl': str(folder_tujuan / "%(title)s_%(id)s.%(ext)s"),
|
|
68
|
+
'quiet': True,
|
|
69
|
+
'no_warnings': True,
|
|
70
|
+
'ignoreerrors': True
|
|
71
|
+
}
|
|
72
|
+
if butuh_login:
|
|
73
|
+
ydl_opts['cookiesfrombrowser'] = ('chrome',)
|
|
74
|
+
progress_bar = Progress(
|
|
75
|
+
TextColumn("[bold blue]{task.description}"),
|
|
76
|
+
BarColumn(bar_width=25, complete_style="green"),
|
|
77
|
+
DownloadColumn(),
|
|
78
|
+
TransferSpeedColumn(),
|
|
79
|
+
TimeRemainingColumn(),
|
|
80
|
+
console=console
|
|
81
|
+
)
|
|
82
|
+
with progress_bar:
|
|
83
|
+
task_id = progress_bar.add_task("📥 Downloading ", total=None)
|
|
84
|
+
def progress_hook(d):
|
|
85
|
+
if d['status'] == 'downloading':
|
|
86
|
+
total = d.get('total_bytes') or d.get('total_bytes_estimate') or 0
|
|
87
|
+
progress_bar.update(task_id, total=total, completed=d.get('downloaded_bytes', 0))
|
|
88
|
+
elif d['status'] == 'finished':
|
|
89
|
+
progress_bar.update(task_id, description="[bold green]🔄 Muxing Audio+Video...")
|
|
90
|
+
|
|
91
|
+
ydl_opts['progress_hooks'] = [progress_hook]
|
|
92
|
+
try:
|
|
93
|
+
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
|
94
|
+
ydl.download([url])
|
|
95
|
+
console.print(f"[bold green]✔ Sukses mengunduh antrean ke-{index}![/bold green]\n")
|
|
96
|
+
except Exception as e:
|
|
97
|
+
console.print(f"[bold red]❌ Gagal pada antrean ke-{index}:[/bold red] {e}\n")
|
|
98
|
+
time.sleep(1)
|
|
99
|
+
console.print(f"[bold green]✔ PROSES SELESAI![/bold green] File disimpan di: [underline cyan]{folder_tujuan}[/underline cyan]")
|
|
100
|
+
input("\nTekan Enter untuk kembali ke menu...")
|
|
101
|
+
|
|
102
|
+
def menu_youtube():
|
|
103
|
+
while True:
|
|
104
|
+
clear()
|
|
105
|
+
draw_banner()
|
|
106
|
+
questions = [
|
|
107
|
+
inquirer.List('opsi', message="YouTube", choices=[
|
|
108
|
+
('1. Download Video', '1'),
|
|
109
|
+
('2. Download Shorts', '2'),
|
|
110
|
+
('3. Mass Download Channel/Playlist Video', '3'),
|
|
111
|
+
('4. Mass Download Channel/Playlist Shorts', '4'),
|
|
112
|
+
('➔ Kembali ke Menu Utama', 'kembali')
|
|
113
|
+
], carousel=True)
|
|
114
|
+
]
|
|
115
|
+
ans = inquirer.prompt(questions)
|
|
116
|
+
if not ans or ans['opsi'] == 'kembali': break
|
|
117
|
+
urls = ambil_input_url()
|
|
118
|
+
if not urls: continue
|
|
119
|
+
mapping = {
|
|
120
|
+
'1': 'YT_Videos',
|
|
121
|
+
'2': 'YT_Shorts',
|
|
122
|
+
'3': 'YT_Bulk_Videos',
|
|
123
|
+
'4': 'YT_Bulk_Shorts'
|
|
124
|
+
}
|
|
125
|
+
jalankan_unduhan(urls, mapping[ans['opsi']])
|
|
126
|
+
|
|
127
|
+
def menu_facebook():
|
|
128
|
+
while True:
|
|
129
|
+
clear()
|
|
130
|
+
draw_banner()
|
|
131
|
+
questions = [
|
|
132
|
+
inquirer.List('opsi', message="Facebook", choices=[
|
|
133
|
+
('1. Download FB Reels', '1'),
|
|
134
|
+
('2. Download FB Story (Harus Publik / Login)', '2'),
|
|
135
|
+
('3. Download FB Video', '3'),
|
|
136
|
+
('➔ Kembali ke Menu Utama', 'kembali')
|
|
137
|
+
], carousel=True)
|
|
138
|
+
]
|
|
139
|
+
ans = inquirer.prompt(questions)
|
|
140
|
+
if not ans or ans['opsi'] == 'kembali': break
|
|
141
|
+
urls = ambil_input_url()
|
|
142
|
+
if not urls: continue
|
|
143
|
+
mapping = {'1': 'FB_Reels', '2': 'FB_Stories', '3': 'FB_Videos'}
|
|
144
|
+
butuh_login = True if ans['opsi'] == '2' else False
|
|
145
|
+
jalankan_unduhan(urls, mapping[ans['opsi']], butuh_login=butuh_login)
|
|
146
|
+
|
|
147
|
+
def menu_instagram():
|
|
148
|
+
while True:
|
|
149
|
+
clear()
|
|
150
|
+
draw_banner()
|
|
151
|
+
questions = [
|
|
152
|
+
inquirer.List('opsi', message="Instagram", choices=[
|
|
153
|
+
('1. Download IG Reels', '1'),
|
|
154
|
+
('2. Download IG Story (Memerlukan Akses/Cookies)', '2'),
|
|
155
|
+
('➔ Kembali ke Menu Utama', 'kembali')
|
|
156
|
+
], carousel=True)
|
|
157
|
+
]
|
|
158
|
+
ans = inquirer.prompt(questions)
|
|
159
|
+
if not ans or ans['opsi'] == 'kembali': break
|
|
160
|
+
urls = ambil_input_url()
|
|
161
|
+
if not urls: continue
|
|
162
|
+
mapping = {'1': 'IG_Reels', '2': 'IG_Stories'}
|
|
163
|
+
butuh_login = True if ans['opsi'] == '2' else False
|
|
164
|
+
jalankan_unduhan(urls, mapping[ans['opsi']], butuh_login=butuh_login)
|
|
165
|
+
|
|
166
|
+
def menu_tiktok():
|
|
167
|
+
while True:
|
|
168
|
+
clear()
|
|
169
|
+
draw_banner()
|
|
170
|
+
questions = [
|
|
171
|
+
inquirer.List('opsi', message="TikTok", choices=[
|
|
172
|
+
('1. Download TikTok Video (No Watermark)', '1'),
|
|
173
|
+
('➔ Kembali ke Menu Utama', 'kembali')
|
|
174
|
+
], carousel=True)
|
|
175
|
+
]
|
|
176
|
+
ans = inquirer.prompt(questions)
|
|
177
|
+
if not ans or ans['opsi'] == 'kembali': break
|
|
178
|
+
urls = ambil_input_url()
|
|
179
|
+
if not urls: continue
|
|
180
|
+
folder_tujuan = tentukan_lokasi('TikTok_Videos')
|
|
181
|
+
session = requests.Session()
|
|
182
|
+
for index, url in enumerate(urls, 1):
|
|
183
|
+
clear()
|
|
184
|
+
draw_banner()
|
|
185
|
+
console.print(Panel(f"[bold cyan]Mengunduh ke-{index} dari {len(urls)}[/bold cyan]\n[dim]Target : {url}[/dim]", border_style="cyan"))
|
|
186
|
+
with console.status("[yellow]⏳ Memproses data dari server ssstik...[/]"):
|
|
187
|
+
try:
|
|
188
|
+
user_ip = "140.213.39.86"
|
|
189
|
+
user_loc = "ID"
|
|
190
|
+
try:
|
|
191
|
+
trace_response = session.get("https://ssstik.io/cdn-cgi/trace", verify=False, timeout=10).text
|
|
192
|
+
for line in trace_response.split("\n"):
|
|
193
|
+
if "=" in line:
|
|
194
|
+
parts = line.split("=", 1)
|
|
195
|
+
if parts[0] == 'ip': user_ip = parts[1]
|
|
196
|
+
if parts[0] == 'loc': user_loc = parts[1]
|
|
197
|
+
except:
|
|
198
|
+
pass
|
|
199
|
+
|
|
200
|
+
html_homepage = session.get("https://ssstik.io/id", verify=False, timeout=10).text
|
|
201
|
+
token_tt = "Y0FLY3E_"
|
|
202
|
+
match = re.search(r'name="tt"\s+value="([^"]+)"', html_homepage)
|
|
203
|
+
if match:
|
|
204
|
+
token_tt = match.group(1)
|
|
205
|
+
else:
|
|
206
|
+
match2 = re.search(r'tt\s*=\s*["\']([^"\']+)["\']', html_homepage)
|
|
207
|
+
if match2:
|
|
208
|
+
token_tt = match2.group(1)
|
|
209
|
+
|
|
210
|
+
dl_headers = {
|
|
211
|
+
"Host": "ssstik.io",
|
|
212
|
+
"Connection": "keep-alive",
|
|
213
|
+
"sec-ch-ua-platform": '"Android"',
|
|
214
|
+
"User-Agent": "Mozilla/5.0 (Linux; Android 14; V2201 Build/UP1A.231005.007)",
|
|
215
|
+
"Accept": "*/*",
|
|
216
|
+
"X-Requested-With": "mark.via.gp",
|
|
217
|
+
"Sec-Fetch-Site": "same-origin",
|
|
218
|
+
"Sec-Fetch-Mode": "cors",
|
|
219
|
+
"Referer": "https://ssstik.io/id",
|
|
220
|
+
"HX-Trigger": "_gcaptcha_pt",
|
|
221
|
+
"HX-Target": "target",
|
|
222
|
+
"HX-Current-URL": "https://ssstik.io/id",
|
|
223
|
+
"HX-Request": "true",
|
|
224
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
225
|
+
}
|
|
226
|
+
debug_param = f"ab=1&loc={user_loc}&ip={user_ip}"
|
|
227
|
+
post_data = {
|
|
228
|
+
"id": url,
|
|
229
|
+
"locale": "id",
|
|
230
|
+
"tt": token_tt,
|
|
231
|
+
"debug": debug_param
|
|
232
|
+
}
|
|
233
|
+
response = session.post("https://ssstik.io/abc?url=dl", data=post_data, headers=dl_headers, verify=False, timeout=10).text
|
|
234
|
+
soup = BeautifulSoup(response, "html.parser")
|
|
235
|
+
user_node = soup.select_one('#avatarAndTextUsual h2')
|
|
236
|
+
username = user_node.text.strip() if user_node else "Unknown"
|
|
237
|
+
caption_node = soup.select_one('p.maintext')
|
|
238
|
+
caption = caption_node.text.strip() if caption_node else "-"
|
|
239
|
+
if len(caption) > 150:
|
|
240
|
+
caption = caption[:147] + "..."
|
|
241
|
+
video_node = soup.find("a", class_=lambda x: x and "without_watermark" in x)
|
|
242
|
+
video_url = video_node.get("href") if video_node else None
|
|
243
|
+
if video_url:
|
|
244
|
+
clean_username = re.sub(r'[^\w\-_\.]', '', username)
|
|
245
|
+
filename = f"{clean_username}_{int(time.time())}.mp4"
|
|
246
|
+
filepath = folder_tujuan / filename
|
|
247
|
+
res_file = session.get(video_url, stream=True, verify=False, timeout=30)
|
|
248
|
+
with open(filepath, 'wb') as f:
|
|
249
|
+
for chunk in res_file.iter_content(chunk_size=1024*1024):
|
|
250
|
+
if chunk:
|
|
251
|
+
f.write(chunk)
|
|
252
|
+
hasil = (
|
|
253
|
+
f"[bold green]✔ Berhasil Mengunduh Antrean ke-{index}![/bold green]\n\n"
|
|
254
|
+
f"[white]👤 Creator : @{username}\n"
|
|
255
|
+
f"📝 Caption : {caption}\n"
|
|
256
|
+
f"📂 Simpan : {filepath}[/white]"
|
|
257
|
+
)
|
|
258
|
+
console.print(Panel(hasil, border_style="green"))
|
|
259
|
+
else:
|
|
260
|
+
console.print(f"[bold red]❌ Gagal mendapatkan link video dari target ke-{index}[/bold red]\n")
|
|
261
|
+
except Exception as e:
|
|
262
|
+
console.print(f"[bold red]❌ Gagal pada antrean ke-{index}:[/bold red] {e}\n")
|
|
263
|
+
time.sleep(1.5)
|
|
264
|
+
console.print(f"[bold green]✔ PROSES SELESAI![/bold green] File disimpan di: [underline cyan]{folder_tujuan}[/underline cyan]")
|
|
265
|
+
input("\nTekan Enter untuk kembali ke menu...")
|
|
266
|
+
|
|
267
|
+
def main():
|
|
268
|
+
while True:
|
|
269
|
+
clear()
|
|
270
|
+
draw_banner()
|
|
271
|
+
questions = [
|
|
272
|
+
inquirer.List('platform', message="Pilih Platform Sosial Media", choices=[
|
|
273
|
+
('YouTube Downloader Menu', 'yt'),
|
|
274
|
+
('Facebook Downloader Menu', 'fb'),
|
|
275
|
+
('Instagram Downloader Menu', 'ig'),
|
|
276
|
+
('TikTok Downloader Menu', 'tt'),
|
|
277
|
+
('Keluar Aplikasi', 'keluar')
|
|
278
|
+
], carousel=True)
|
|
279
|
+
]
|
|
280
|
+
answers = inquirer.prompt(questions)
|
|
281
|
+
if not answers or answers['platform'] == 'keluar':
|
|
282
|
+
console.print("\n[bold yellow]Keluar dari aplikasi[/bold yellow]")
|
|
283
|
+
break
|
|
284
|
+
platform = answers['platform']
|
|
285
|
+
if platform == 'yt': menu_youtube()
|
|
286
|
+
elif platform == 'fb': menu_facebook()
|
|
287
|
+
elif platform == 'ig': menu_instagram()
|
|
288
|
+
elif platform == 'tt': menu_tiktok()
|
|
289
|
+
|
|
290
|
+
if __name__ == "__main__":
|
|
291
|
+
try:
|
|
292
|
+
main()
|
|
293
|
+
except KeyboardInterrupt:
|
|
294
|
+
print("\n\n[!] Program distop paksa (Ctrl+C). Keluar!")
|
|
295
|
+
sys.exit(0)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mediadown
|
|
3
|
+
Version: 3.2.0
|
|
4
|
+
Summary: Social Media Downloader untuk YouTube, Facebook, Instagram, dan TikTok
|
|
5
|
+
Author: KEYBOARDWARRIOR
|
|
6
|
+
Requires-Dist: inquirer
|
|
7
|
+
Requires-Dist: yt-dlp
|
|
8
|
+
Requires-Dist: requests
|
|
9
|
+
Requires-Dist: urllib3
|
|
10
|
+
Requires-Dist: beautifulsoup4
|
|
11
|
+
Requires-Dist: rich
|
|
12
|
+
Dynamic: author
|
|
13
|
+
Dynamic: requires-dist
|
|
14
|
+
Dynamic: summary
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
mediadown/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
mediadown/cli.py,sha256=bhLsYlq-rEdfVh2Ba2AUZrd-hdOMuUp5cZP5iOKK5n0,10534
|
|
3
|
+
mediadown-3.2.0.dist-info/METADATA,sha256=Q2p42IpCT-kYFJtcygX7-3twfWB_T-lo_z_zH0ohSLM,356
|
|
4
|
+
mediadown-3.2.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
5
|
+
mediadown-3.2.0.dist-info/entry_points.txt,sha256=CN_URNDzoYUo9GLg1UHs8vklkaNrsxocCabkJKR3AQA,49
|
|
6
|
+
mediadown-3.2.0.dist-info/top_level.txt,sha256=pY3YJgU_Xe3pqskNAlohv-3DMYY8wkC5gtT3DB6zowM,10
|
|
7
|
+
mediadown-3.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
mediadown
|