fourmovie commited on
Commit
7fec1da
·
1 Parent(s): a927ae7
Dockerfile ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:20-bullseye
2
+
3
+ RUN apt-get update && \
4
+ apt-get install -y --no-install-recommends \
5
+ python3 \
6
+ python3-pip \
7
+ python3-dev \
8
+ ffmpeg \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ RUN python3 -m pip install --upgrade pip && \
12
+ python3 -m pip install --no-cache-dir --upgrade \
13
+ spotdl \
14
+ yt-dlp
15
+
16
+ RUN mkdir -p /app/downloads && \
17
+ mkdir -p /app/cookies && \
18
+ chmod 777 -R /app/downloads && \
19
+ chmod 777 -R /app/cookies
20
+
21
+ WORKDIR /app
22
+
23
+ COPY package.json package-lock.json ./
24
+
25
+ RUN npm ci
26
+
27
+ COPY . .
28
+
29
+ RUN touch /app/cookies/youtube_cookies.txt && \
30
+ chmod 666 /app/cookies/youtube_cookies.txt
31
+
32
+ CMD ["node", "app.js"]
33
+
34
+ EXPOSE 7860
README.md CHANGED
@@ -3,10 +3,8 @@ title: Hai
3
  emoji: 🐢
4
  colorFrom: purple
5
  colorTo: pink
6
- sdk: gradio
7
  sdk_version: 5.38.0
8
- app_file: app.py
9
- pinned: false
10
  license: apache-2.0
11
  short_description: ytdl
12
  ---
 
3
  emoji: 🐢
4
  colorFrom: purple
5
  colorTo: pink
6
+ sdk: docker
7
  sdk_version: 5.38.0
 
 
8
  license: apache-2.0
9
  short_description: ytdl
10
  ---
app.js ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const cors = require('cors');
3
+ const swaggerUi = require('swagger-ui-express');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const { exec } = require('child_process');
7
+ const { trackUsage, getUsageStats } = require('./utils/usageTracker');
8
+
9
+ const app = express();
10
+ app.use(cors());
11
+ app.use(express.json());
12
+
13
+ // Config
14
+ const DOMAIN = process.env.DOMAIN || 'https://rezaharis-ytdl.hf.space';
15
+ const AUTHOR = 'Fourstore';
16
+ const RELEASE_DATE = new Date().toISOString().split('T')[0];
17
+
18
+ // Ensure directories exist
19
+ const cookiesDir = path.join(__dirname, 'cookies');
20
+ const downloadsDir = path.join(__dirname, 'downloads');
21
+ const cookieFile = path.join(cookiesDir, 'youtube_cookie.txt');
22
+
23
+ [downloadsDir, cookiesDir].forEach(dir => {
24
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir);
25
+ });
26
+
27
+ // Fix for static files with special characters
28
+ app.use('/downloads', express.static(downloadsDir, {
29
+ setHeaders: (res, filePath) => {
30
+ const ext = path.extname(filePath);
31
+ if (['.mp3', '.webm', '.mp4'].includes(ext)) {
32
+ res.setHeader('Content-Disposition', 'inline');
33
+ }
34
+ }
35
+ }));
36
+
37
+ // Swagger setup
38
+ const swaggerDocument = require('./swagger.json');
39
+ app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument));
40
+
41
+ // Homepage
42
+ app.get('/', (req, res) => {
43
+ res.json({
44
+ service: "YouTube Downloader API",
45
+ author: AUTHOR,
46
+ release_date: RELEASE_DATE,
47
+ endpoints: {
48
+ download: `${DOMAIN}/download`,
49
+ downloads: `${DOMAIN}/downloads/{filename}`,
50
+ docs: `${DOMAIN}/api-docs`
51
+ }
52
+ });
53
+ });
54
+
55
+ // Download endpoint
56
+ app.post('/download', (req, res) => {
57
+ const { url, format = 'audio' } = req.body;
58
+
59
+ if (!url) return res.status(400).json({ error: "URL is required" });
60
+
61
+ trackUsage('/download');
62
+
63
+ const outputTemplate = `${downloadsDir}/%(title)s.%(ext)s`;
64
+ let command = `yt-dlp --cookies ${cookieFile} -o "${outputTemplate}" "${url}"`;
65
+
66
+ if (format === 'audio') {
67
+ command += ' -x --audio-format mp3';
68
+ }
69
+
70
+ console.log(`[EXEC] Running: ${command}`);
71
+
72
+ exec(command, (error, stdout, stderr) => {
73
+ if (error) {
74
+ console.error(`[ERROR] ${error.message}`);
75
+ return res.status(500).json({ error: "Download failed", details: stderr });
76
+ }
77
+
78
+ // Parse filename from yt-dlp output
79
+ const filenameMatch = stdout.match(/Destination: (.+)/) || stdout.match(/\[ExtractAudio\] Destination: (.+)/);
80
+ if (!filenameMatch) {
81
+ return res.status(500).json({ error: "Cannot determine output file" });
82
+ }
83
+
84
+ const localPath = filenameMatch[1];
85
+ const filename = path.basename(localPath);
86
+ const encodedFilename = encodeURIComponent(filename); // Encode untuk URL
87
+ const downloadUrl = `${DOMAIN}/downloads/${encodedFilename}`;
88
+
89
+ res.json({
90
+ success: true,
91
+ filename: filename,
92
+ download_url: downloadUrl,
93
+ direct_access: `${DOMAIN}/downloads/${filename.replace(/ /g, '%20')}`, // Alternatif encode
94
+ format: format
95
+ });
96
+ });
97
+ });
98
+
99
+ const PORT = process.env.PORT || 7860;
100
+ app.listen(PORT, () => {
101
+ console.log(`Server ready at ${DOMAIN}`);
102
+ console.log(`Downloads directory: ${downloadsDir}`);
103
+ });
cookies/youtube_cookie.txt ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Netscape HTTP Cookie File
2
+ # This file is generated by yt-dlp. Do not edit.
3
+
4
+ .youtube.com TRUE / FALSE 0 PREF hl=en&tz=UTC
5
+ .youtube.com TRUE / TRUE 0 SOCS CAI
6
+ .youtube.com TRUE / TRUE 1753020256 GPS 1
7
+ .youtube.com TRUE / TRUE 0 YSC ZC8AayWgNcM
8
+ .youtube.com TRUE / TRUE 1768570458 __Secure-ROLLOUT_TOKEN CNKrhu_yw6WdPRDRtZzTxsuOAxjejoXUxsuOAw%3D%3D
9
+ .youtube.com TRUE / TRUE 1768571639 VISITOR_INFO1_LIVE hnd3q5rsoHU
10
+ .youtube.com TRUE / TRUE 1768571639 VISITOR_PRIVACY_METADATA CgJJRBIEGgAgMA%3D%3D
11
+ .youtube.com TRUE / TRUE 1816091639 __Secure-YT_TVFAS t=486949&s=2
12
+ .youtube.com TRUE / TRUE 1768571639 DEVICE_INFO ChxOelV5T1RFMU5qazBOalUwTnpRd01EUXdNdz09EPfp88MGGNrg88MG
13
+ .youtube.com TRUE /tv TRUE 1785851639 __Secure-YT_DERP CMz7zaEj
cookies/youtube_cookies.txt ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Netscape HTTP Cookie File
2
+ # This file is generated by yt-dlp. Do not edit.
3
+
4
+ .youtube.com TRUE / FALSE 1784982811 APISID NWd04DOcF_zkkR-c/A1_Pi-KeUnP0ja2uI
5
+ .youtube.com TRUE / TRUE 1768542025 DEVICE_INFO ChxOelV5T0RJME16UXlOVFUwTVRFMk16WTJNUT09EMmC8sMGGILj5sMG
6
+ .youtube.com TRUE / FALSE 1784982811 HSID AHTZaRDFVCxh8_6n8
7
+ .youtube.com TRUE / TRUE 1784982810 LOGIN_INFO AFmmF2swRQIgLGmH_pPa9AnlL9l89bz2yI53bOy7OuuCa3S1vVFz0XsCIQDTSHR_57_dppoeU26P8ZC5EW76vMjhBNbz5lG1qDUllQ:QUQ3MjNmeERya25nS1BiclFuWTlIbGw0RUFZakhkNV9KYlRzVmpxWjluVUNRWHl5Sk5GaEVpVy1mcU9DUVVUNXhYZVhodlg0alJicmU5UzJuRldraVR2LTJIWDZfdC1PYkpyVEJwNFZJek1ZYTlSUmgwMzBoQU1SZHVUcEtkdjlsZmpDREg4OFBMelZtV0lac3lBbVpERG1RRGtFMmRwNkt3
8
+ .youtube.com TRUE / FALSE 0 PREF f6=40000000&f7=100&tz=UTC&f4=4000000&hl=en
9
+ .youtube.com TRUE / TRUE 1784982811 SAPISID Z-qSt9Jer8rm1LS-/AjYk3cQAifxKHhm96
10
+ .youtube.com TRUE / FALSE 1784982811 SID g.a000yQi31c_RBOgcCrBAWWFnGF9cdgoBBlqqbq5a3HjVl4O6dTc1E3A5e6tXroppOs40E5CIxgACgYKAe4SARcSFQHGX2MiwEuQKGkrQpKI2hx2xp-CaRoVAUF8yKoNBAy-rwyg7KN41S56rPb70076
11
+ .youtube.com TRUE / FALSE 1784526025 SIDCC AKEyXzXa-29jOFvw9HEXPe_FpaGdQROt2Ee9tTWzyYcw4RxnFW8PAZcR4K4mX-UeDrVfRofGlKQ
12
+ .youtube.com TRUE / TRUE 1784982811 SSID ArO6uvJ-TZjKh_n4J
13
+ .youtube.com TRUE / TRUE 1768542025 VISITOR_INFO1_LIVE SYSStDYQ_LU
14
+ .youtube.com TRUE / TRUE 1768542025 VISITOR_PRIVACY_METADATA CgJJRBIEGgAgZQ%3D%3D
15
+ .youtube.com TRUE / TRUE 0 YSC fy6TKx4Ywuw
16
+ .youtube.com TRUE / TRUE 1768542025 YT_DEVICE_MEASUREMENT_ID sYK7Av0=
17
+ .youtube.com TRUE / TRUE 1784982811 __Secure-1PAPISID Z-qSt9Jer8rm1LS-/AjYk3cQAifxKHhm96
18
+ .youtube.com TRUE / TRUE 1784982811 __Secure-1PSID g.a000yQi31c_RBOgcCrBAWWFnGF9cdgoBBlqqbq5a3HjVl4O6dTc1voyedI0JiIy9ISBPcCmiKAACgYKAeoSARcSFQHGX2MiFhdbXG7KcZah26PRxDqcvBoVAUF8yKr2U2FwhTlEeoh8-U-BbZ8z0076
19
+ .youtube.com TRUE / TRUE 1784526025 __Secure-1PSIDCC AKEyXzU722esjX0yigTDUuzz_dDiUL_n08fWTYFdmV02tgGMkhRFY4XzSZriaUeB_GwHo2gvyg
20
+ .youtube.com TRUE / TRUE 1784266992 __Secure-1PSIDTS sidts-CjIB5H03PweZ8m_GluvOU4rTZtn1O8EFGYoG_qMa98QozRDP9Hjti-fpoaoWk9X0zqbkOhAA
21
+ .youtube.com TRUE / TRUE 1784982811 __Secure-3PAPISID Z-qSt9Jer8rm1LS-/AjYk3cQAifxKHhm96
22
+ .youtube.com TRUE / TRUE 1784982811 __Secure-3PSID g.a000yQi31c_RBOgcCrBAWWFnGF9cdgoBBlqqbq5a3HjVl4O6dTc1XKes4aQmATQgvPwR_4JH7QACgYKAbQSARcSFQHGX2MigZvNTSpjh_HfNmmDG5yFWRoVAUF8yKrS_3mgYrdJG0Anza3S1RVT0076
23
+ .youtube.com TRUE / TRUE 1784526025 __Secure-3PSIDCC AKEyXzVcQ-Ua8HW4U-62Ei8511byR2LY5h_wa6f_74_e6TN1-NyWoOJknxge2lyBtLbYFtJUFBI
24
+ .youtube.com TRUE / TRUE 1784266992 __Secure-3PSIDTS sidts-CjIB5H03PweZ8m_GluvOU4rTZtn1O8EFGYoG_qMa98QozRDP9Hjti-fpoaoWk9X0zqbkOhAA
25
+ .youtube.com TRUE / TRUE 1768467610 __Secure-ROLLOUT_TOKEN CMeR16GirZGQGRDNjpnWl8OOAxjsnq_Cx8iOAw%3D%3D
26
+ .youtube.com TRUE / TRUE 1816062025 __Secure-YT_TVFAS t=485164&s=2
27
+ .youtube.com TRUE /tv TRUE 1785822025 __Secure-YT_DERP CMKU9PYe
package.json ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "ytdlp-service",
3
+ "version": "1.0.0",
4
+ "description": "YouTube Downloader Service by Fourstore",
5
+ "main": "app.js",
6
+ "scripts": {
7
+ "start": "node app.js"
8
+ },
9
+ "dependencies": {
10
+ "express": "^4.18.2",
11
+ "cors": "^2.8.5",
12
+ "swagger-ui-express": "^5.0.0",
13
+ "fs": "^0.0.1-security",
14
+ "path": "^0.12.7",
15
+ "child_process": "^1.0.2"
16
+ }
17
+ }
swagger.json ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "openapi": "3.0.0",
3
+ "info": {
4
+ "title": "YouTube Downloader API",
5
+ "version": "1.0.0",
6
+ "description": "API untuk mengunduh konten dari YouTube (audio/video) - Dibuat oleh Fourstore",
7
+ "contact": {
8
+ "name": "Fourstore"
9
+ }
10
+ },
11
+ "servers": [
12
+ {
13
+ "url": "https://rezaharis-ytdl.hf.space",
14
+ "description": "Development server"
15
+ }
16
+ ],
17
+ "paths": {
18
+ "/": {
19
+ "get": {
20
+ "summary": "Informasi layanan",
21
+ "responses": {
22
+ "200": {
23
+ "description": "Informasi dasar layanan dan statistik penggunaan"
24
+ }
25
+ }
26
+ }
27
+ },
28
+ "/download": {
29
+ "post": {
30
+ "summary": "Unduh konten dari YouTube",
31
+ "requestBody": {
32
+ "required": true,
33
+ "content": {
34
+ "application/json": {
35
+ "schema": {
36
+ "type": "object",
37
+ "properties": {
38
+ "url": {
39
+ "type": "string",
40
+ "example": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
41
+ "description": "URL video YouTube"
42
+ },
43
+ "format": {
44
+ "type": "string",
45
+ "enum": ["audio", "video"],
46
+ "default": "audio",
47
+ "description": "Format output (audio/mp3 atau video asli)"
48
+ }
49
+ },
50
+ "required": ["url"]
51
+ }
52
+ }
53
+ }
54
+ },
55
+ "responses": {
56
+ "200": {
57
+ "description": "Unduhan berhasil",
58
+ "content": {
59
+ "application/json": {
60
+ "schema": {
61
+ "type": "object",
62
+ "properties": {
63
+ "success": { "type": "boolean" },
64
+ "message": { "type": "string" },
65
+ "filename": { "type": "string" },
66
+ "download_url": { "type": "string" },
67
+ "format": { "type": "string" }
68
+ }
69
+ }
70
+ }
71
+ }
72
+ }
73
+ }
74
+ }
75
+ }
76
+ }
77
+ }
usage.json ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ {
2
+ "endpoints": {
3
+ "/download": 4
4
+ }
5
+ }
utils/usageTracker.js ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ const usageFile = path.join(__dirname, '../usage.json');
5
+
6
+ // Initialize with author info if not exists
7
+ if (!fs.existsSync(usageFile)) {
8
+ fs.writeFileSync(usageFile, JSON.stringify({
9
+ initialized: new Date().toISOString(),
10
+ endpoints: {
11
+ '/download': 0,
12
+ '/usage': 0,
13
+ '/': 0
14
+ }
15
+ }, null, 2));
16
+ }
17
+
18
+ function trackUsage(endpoint) {
19
+ const usageData = JSON.parse(fs.readFileSync(usageFile));
20
+ usageData.endpoints[endpoint] = (usageData.endpoints[endpoint] || 0) + 1;
21
+ fs.writeFileSync(usageFile, JSON.stringify(usageData, null, 2));
22
+ }
23
+
24
+ function getUsageStats() {
25
+ return JSON.parse(fs.readFileSync(usageFile));
26
+ }
27
+
28
+ module.exports = { trackUsage, getUsageStats };