Opera8 commited on
Commit
86a39d2
·
verified ·
1 Parent(s): f05726b

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +181 -0
app.py ADDED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import time
3
+ import uuid
4
+ import threading
5
+ from flask import Flask, request, jsonify, send_file, render_template_string
6
+ from flask_cors import CORS
7
+ import yt_dlp
8
+
9
+ app = Flask(__name__)
10
+ CORS(app)
11
+
12
+ DOWNLOAD_FOLDER = 'downloads'
13
+ os.makedirs(DOWNLOAD_FOLDER, exist_ok=True)
14
+
15
+ # ==========================================
16
+ # سیستم پاکسازی خودکار فایل‌های قدیمی
17
+ # ==========================================
18
+ def cleanup_old_files():
19
+ """این تابع هر یک ساعت اجرا می‌شود و فایل‌های قدیمی‌تر از 1 ساعت را پاک می‌کند تا حافظه پر نشود"""
20
+ while True:
21
+ try:
22
+ current_time = time.time()
23
+ for filename in os.listdir(DOWNLOAD_FOLDER):
24
+ file_path = os.path.join(DOWNLOAD_FOLDER, filename)
25
+ if os.path.isfile(file_path):
26
+ # اگر فایل قدیمی‌تر از 3600 ثانیه (1 ساعت) بود، حذف شود
27
+ if current_time - os.path.getctime(file_path) > 3600:
28
+ os.remove(file_path)
29
+ except Exception as e:
30
+ print(f"Cleanup error: {e}")
31
+ time.sleep(3600)
32
+
33
+ # راه‌اندازی نخ پاکسازی در پس‌زمینه
34
+ cleanup_thread = threading.Thread(target=cleanup_old_files, daemon=True)
35
+ cleanup_thread.start()
36
+
37
+ # ==========================================
38
+ # رابط کاربری ساده برای تست (رابط وب)
39
+ # ==========================================
40
+ HTML_TEMPLATE = """
41
+ <!DOCTYPE html>
42
+ <html lang="fa" dir="rtl">
43
+ <head>
44
+ <meta charset="UTF-8">
45
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
46
+ <title>دانلودر اختصاصی ویدیو</title>
47
+ <style>
48
+ body { font-family: Tahoma, Arial, sans-serif; background-color: #f3f4f6; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; }
49
+ .container { background: #fff; padding: 30px; border-radius: 12px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); max-width: 500px; width: 100%; text-align: center; }
50
+ input { width: 90%; padding: 12px; margin: 15px 0; border: 1px solid #ccc; border-radius: 8px; font-size: 16px; direction: ltr; }
51
+ button { background: #3b82f6; color: white; border: none; padding: 12px 24px; font-size: 16px; border-radius: 8px; cursor: pointer; transition: 0.3s; width: 100%; font-weight: bold; }
52
+ button:hover { background: #2563eb; }
53
+ button:disabled { background: #9ca3af; cursor: not-allowed; }
54
+ #status { margin-top: 15px; font-size: 14px; color: #4b5563; }
55
+ .video-container { margin-top: 20px; display: none; }
56
+ video { width: 100%; border-radius: 8px; }
57
+ .dl-btn { background: #10b981; margin-top: 10px; display: inline-block; text-decoration: none; padding: 10px 20px; color: white; border-radius: 8px; }
58
+ .dl-btn:hover { background: #059669; }
59
+ </style>
60
+ </head>
61
+ <body>
62
+ <div class="container">
63
+ <h2>📥 دانلودر ویدیو (YouTube / Instagram)</h2>
64
+ <p>لینک ویدیو را در کادر زیر قرار دهید:</p>
65
+ <input type="text" id="urlInput" placeholder="https://www.youtube.com/watch?v=...">
66
+ <button id="downloadBtn" onclick="startDownload()">دریافت ویدیو</button>
67
+ <div id="status"></div>
68
+
69
+ <div class="video-container" id="videoContainer">
70
+ <video id="videoPlayer" controls></video>
71
+ <br>
72
+ <a id="downloadLink" class="dl-btn" href="#" download>ذخیره ویدیو در دستگاه</a>
73
+ </div>
74
+ </div>
75
+
76
+ <script>
77
+ async function startDownload() {
78
+ const url = document.getElementById('urlInput').value;
79
+ const btn = document.getElementById('downloadBtn');
80
+ const status = document.getElementById('status');
81
+ const videoContainer = document.getElementById('videoContainer');
82
+ const videoPlayer = document.getElementById('videoPlayer');
83
+ const downloadLink = document.getElementById('downloadLink');
84
+
85
+ if (!url) return alert('لطفا لینک را وارد کنید');
86
+
87
+ btn.disabled = true;
88
+ status.innerHTML = "⏳ در حال استخراج و دانلود ویدیو از سرور اصلی... لطفاً صبور باشید.";
89
+ videoContainer.style.display = 'none';
90
+
91
+ try {
92
+ const response = await fetch('/api/download', {
93
+ method: 'POST',
94
+ headers: { 'Content-Type': 'application/json' },
95
+ body: JSON.stringify({ url: url })
96
+ });
97
+
98
+ if (!response.ok) {
99
+ const data = await response.json();
100
+ throw new Error(data.error || 'خطا در دانلود ویدیو');
101
+ }
102
+
103
+ // دریافت فایل به صورت Blob
104
+ const blob = await response.blob();
105
+ const videoUrl = URL.createObjectURL(blob);
106
+
107
+ videoPlayer.src = videoUrl;
108
+ downloadLink.href = videoUrl;
109
+ downloadLink.download = "video.mp4";
110
+
111
+ status.innerHTML = "✅ ویدیو با موفقیت دریافت شد!";
112
+ videoContainer.style.display = 'block';
113
+
114
+ } catch (error) {
115
+ status.innerHTML = "❌ خطا: " + error.message;
116
+ } finally {
117
+ btn.disabled = false;
118
+ }
119
+ }
120
+ </script>
121
+ </body>
122
+ </html>
123
+ """
124
+
125
+ @app.route('/')
126
+ def index():
127
+ return render_template_string(HTML_TEMPLATE)
128
+
129
+ # ==========================================
130
+ # API دانلود ویدیو
131
+ # ==========================================
132
+ @app.route('/api/download', methods=['POST'])
133
+ def download_video():
134
+ data = request.get_json()
135
+ if not data or 'url' not in data:
136
+ return jsonify({'error': 'URL is required'}), 400
137
+
138
+ url = data['url']
139
+
140
+ # شناسه یکتا برای فایل خروجی
141
+ file_id = str(uuid.uuid4())
142
+ output_template = os.path.join(DOWNLOAD_FOLDER, f"{file_id}.%(ext)s")
143
+
144
+ # تنظیمات yt-dlp
145
+ ydl_opts = {
146
+ 'format': 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best', # تلاش برای دریافت فرمت mp4
147
+ 'outtmpl': output_template,
148
+ 'quiet': True,
149
+ 'no_warnings': True,
150
+ 'merge_output_format': 'mp4', # ترکیب صدا و تصویر در فایل mp4
151
+ }
152
+
153
+ try:
154
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
155
+ # استخراج اطلاعات و دانلود
156
+ info_dict = ydl.extract_info(url, download=True)
157
+ # پیدا کردن مسیر دقیق فایل دانلود شده
158
+ downloaded_ext = info_dict.get('ext', 'mp4')
159
+ final_filename = f"{file_id}.{downloaded_ext}"
160
+
161
+ # در صورتی که yt-dlp فایل را پس از merge به mp4 تغییر نام داده باشد
162
+ if not os.path.exists(os.path.join(DOWNLOAD_FOLDER, final_filename)):
163
+ final_filename = f"{file_id}.mp4"
164
+
165
+ final_path = os.path.join(DOWNLOAD_FOLDER, final_filename)
166
+
167
+ if os.path.exists(final_path):
168
+ # ارسال فایل به صورت مستقیم (پس از ارسال فایل پاک نمیشود، توسط سیستم cleanup پاک خواهد شد)
169
+ return send_file(final_path, as_attachment=True, download_name=f"video_{int(time.time())}.mp4")
170
+ else:
171
+ return jsonify({'error': 'فایل پس از دانلود یافت نشد.'}), 500
172
+
173
+ except Exception as e:
174
+ error_msg = str(e)
175
+ if "Sign in to confirm" in error_msg or "Private video" in error_msg:
176
+ return jsonify({'error': 'این ویدیو خصوصی است یا نیاز به ورود به اکانت دارد.'}), 403
177
+ return jsonify({'error': f'خطا در دانلود: {error_msg}'}), 500
178
+
179
+ if __name__ == '__main__':
180
+ port = int(os.environ.get('PORT', 7860))
181
+ app.run(host='0.0.0.0', port=port)