Opera8 commited on
Commit
a098cd6
·
verified ·
1 Parent(s): c52dbe6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +136 -1
app.py CHANGED
@@ -46,4 +46,139 @@ HTML_TEMPLATE = """
46
  body { font-family: Tahoma, Arial, sans-serif; background-color: #f3f4f6; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; }
47
  .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; }
48
  input { width: 90%; padding: 12px; margin: 15px 0; border: 1px solid #ccc; border-radius: 8px; font-size: 16px; direction: ltr; }
49
- button { background: #3b82f6; color: white; border: none; padding: 12px 24px
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  body { font-family: Tahoma, Arial, sans-serif; background-color: #f3f4f6; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; }
47
  .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; }
48
  input { width: 90%; padding: 12px; margin: 15px 0; border: 1px solid #ccc; border-radius: 8px; font-size: 16px; direction: ltr; }
49
+ 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; }
50
+ button:hover { background: #2563eb; }
51
+ button:disabled { background: #9ca3af; cursor: not-allowed; }
52
+ #status { margin-top: 15px; font-size: 14px; color: #4b5563; }
53
+ .video-container { margin-top: 20px; display: none; }
54
+ video { width: 100%; border-radius: 8px; }
55
+ .dl-btn { background: #10b981; margin-top: 10px; display: inline-block; text-decoration: none; padding: 10px 20px; color: white; border-radius: 8px; }
56
+ .dl-btn:hover { background: #059669; }
57
+ </style>
58
+ </head>
59
+ <body>
60
+ <div class="container">
61
+ <h2>📥 دانلودر ویدیو (YouTube / Instagram)</h2>
62
+ <p>لینک ویدیو را در کادر زیر قرار دهید:</p>
63
+ <input type="text" id="urlInput" placeholder="https://www.youtube.com/watch?v=...">
64
+ <button id="downloadBtn" onclick="startDownload()">دریافت ویدیو</button>
65
+ <div id="status"></div>
66
+
67
+ <div class="video-container" id="videoContainer">
68
+ <video id="videoPlayer" controls></video>
69
+ <br>
70
+ <a id="downloadLink" class="dl-btn" href="#" download>ذخیره ویدیو در دستگاه</a>
71
+ </div>
72
+ </div>
73
+
74
+ <script>
75
+ async function startDownload() {
76
+ const url = document.getElementById('urlInput').value;
77
+ const btn = document.getElementById('downloadBtn');
78
+ const status = document.getElementById('status');
79
+ const videoContainer = document.getElementById('videoContainer');
80
+ const videoPlayer = document.getElementById('videoPlayer');
81
+ const downloadLink = document.getElementById('downloadLink');
82
+
83
+ if (!url) return alert('لطفا لینک را وارد کنید');
84
+
85
+ btn.disabled = true;
86
+ status.innerHTML = "⏳ در حال برقراری ارتباط با سرور و دور زدن محدودیت‌ها... لطفاً صبور باشید.";
87
+ videoContainer.style.display = 'none';
88
+
89
+ try {
90
+ const response = await fetch('/api/download', {
91
+ method: 'POST',
92
+ headers: { 'Content-Type': 'application/json' },
93
+ body: JSON.stringify({ url: url })
94
+ });
95
+
96
+ if (!response.ok) {
97
+ const data = await response.json();
98
+ throw new Error(data.error || 'خطا در دانلود ویدیو');
99
+ }
100
+
101
+ const blob = await response.blob();
102
+ const videoUrl = URL.createObjectURL(blob);
103
+
104
+ videoPlayer.src = videoUrl;
105
+ downloadLink.href = videoUrl;
106
+ downloadLink.download = "video.mp4";
107
+
108
+ status.innerHTML = "✅ ویدیو با موفقیت دریافت شد!";
109
+ videoContainer.style.display = 'block';
110
+
111
+ } catch (error) {
112
+ status.innerHTML = "❌ خطا: " + error.message;
113
+ } finally {
114
+ btn.disabled = false;
115
+ }
116
+ }
117
+ </script>
118
+ </body>
119
+ </html>
120
+ """
121
+
122
+ @app.route('/')
123
+ def index():
124
+ return render_template_string(HTML_TEMPLATE)
125
+
126
+ # ==========================================
127
+ # API دانلود ویدیو
128
+ # ==========================================
129
+ @app.route('/api/download', methods=['POST'])
130
+ def download_video():
131
+ data = request.get_json()
132
+ if not data or 'url' not in data:
133
+ return jsonify({'error': 'URL is required'}), 400
134
+
135
+ url = data['url']
136
+ file_id = str(uuid.uuid4())
137
+ output_template = os.path.join(DOWNLOAD_FOLDER, f"{file_id}.%(ext)s")
138
+
139
+ # تنظیمات جدید yt-dlp برای دور زدن محدودیت‌های آی‌پی سرور
140
+ ydl_opts = {
141
+ 'format': 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best',
142
+ 'outtmpl': output_template,
143
+ 'quiet': True,
144
+ 'no_warnings': True,
145
+ 'merge_output_format': 'mp4',
146
+ 'nocheckcertificate': True, # نادیده گرفتن خطاهای گواهینامه
147
+ 'geo_bypass': True, # تلاش برای دور زدن محدودیت جغرافیایی
148
+ 'http_headers': {
149
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
150
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
151
+ 'Accept-Language': 'en-us,en;q=0.5',
152
+ 'Sec-Fetch-Mode': 'navigate',
153
+ }
154
+ }
155
+
156
+ # اگر فایل کوکی در کنار پروژه وجود داشت، از آن استفاده کند
157
+ if os.path.exists('cookies.txt'):
158
+ ydl_opts['cookiefile'] = 'cookies.txt'
159
+
160
+ try:
161
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
162
+ info_dict = ydl.extract_info(url, download=True)
163
+ downloaded_ext = info_dict.get('ext', 'mp4')
164
+ final_filename = f"{file_id}.{downloaded_ext}"
165
+
166
+ if not os.path.exists(os.path.join(DOWNLOAD_FOLDER, final_filename)):
167
+ final_filename = f"{file_id}.mp4"
168
+
169
+ final_path = os.path.join(DOWNLOAD_FOLDER, final_filename)
170
+
171
+ if os.path.exists(final_path):
172
+ return send_file(final_path, as_attachment=True, download_name=f"video_{int(time.time())}.mp4")
173
+ else:
174
+ return jsonify({'error': 'فایل پس از دانلود یافت نشد.'}), 500
175
+
176
+ except Exception as e:
177
+ error_msg = str(e)
178
+ if "Sign in to confirm" in error_msg or "Private video" in error_msg:
179
+ return jsonify({'error': 'یوتیوب سرور را مسدود کرده است. لطفاً فایل cookies.txt را به پروژه اضافه کنید.'}), 403
180
+ return jsonify({'error': f'خطا در دانلود: {error_msg}'}), 500
181
+
182
+ if __name__ == '__main__':
183
+ port = int(os.environ.get('PORT', 7860))
184
+ app.run(host='0.0.0.0', port=port)