Phoe2004 commited on
Commit
0301fc3
·
verified ·
1 Parent(s): 81bdb5c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +26 -195
app.py CHANGED
@@ -1,221 +1,52 @@
1
- import os, re, uuid, threading, time, subprocess, shutil
2
  from pathlib import Path
3
  from flask import Flask, request, jsonify, send_from_directory, Response
4
 
5
- BASE_DIR = Path(__file__).parent
6
  COOKIES_FILE = str(BASE_DIR / 'm_youtube_com_cookies.txt')
7
- OUTPUT_DIR = BASE_DIR / 'outputs'
8
  OUTPUT_DIR.mkdir(exist_ok=True)
9
 
10
  app = Flask(__name__)
11
- app.config['MAX_CONTENT_LENGTH'] = 10 * 1024 * 1024
12
 
13
- # ── Per-job progress store ──
14
- job_progress = {} # tid → {pct, msg, done, error, files}
15
- _prog_lock = threading.Lock()
16
-
17
- def set_prog(tid, pct, msg, **extra):
18
- with _prog_lock:
19
- job_progress[tid] = {'pct': pct, 'msg': msg, **extra}
20
-
21
- # ── Quality → yt-dlp format string ──
22
  QUALITY_FMT = {
23
- '1080': (
24
- 'bestvideo[height<=1080][ext=mp4]+bestaudio[ext=m4a]'
25
- '/bestvideo[height<=1080]+bestaudio'
26
- '/best[height<=1080]'
27
- '/best'
28
- ),
29
- '720': (
30
- 'bestvideo[height<=720][ext=mp4]+bestaudio[ext=m4a]'
31
- '/bestvideo[height<=720]+bestaudio'
32
- '/best[height<=720]'
33
- '/best'
34
- ),
35
- '480': (
36
- 'bestvideo[height<=480][ext=mp4]+bestaudio[ext=m4a]'
37
- '/best[height<=480]'
38
- '/best'
39
- ),
40
- '360': (
41
- 'best[height<=360]'
42
- '/worst'
43
- ),
44
- 'audio': 'bestaudio[ext=m4a]/bestaudio',
45
  }
46
 
47
- def ytdlp_download(out_tmpl, video_url, quality='720', timeout=1200):
48
- """
49
- yt-dlp download — platform-aware, cookies, quality selector, audio-only support.
50
- Extracted and extended from Recap Studio app.py.
51
- """
52
- url_lower = video_url.lower()
53
- is_tiktok = 'tiktok.com' in url_lower
54
- is_facebook = 'facebook.com' in url_lower or 'fb.watch' in url_lower
55
- is_instagram = 'instagram.com' in url_lower
56
-
57
  fmt = QUALITY_FMT.get(quality, QUALITY_FMT['720'])
58
-
59
- # Social platforms — simpler format, no mp4+m4a merge required
60
- if (is_tiktok or is_facebook or is_instagram) and quality != 'audio':
61
- h = {'1080':'1080','720':'720','480':'480','360':'360'}.get(quality,'720')
62
- fmt = (
63
- f'bestvideo[height<={h}]+bestaudio'
64
- f'/best[height<={h}]'
65
- '/best'
66
- )
67
-
68
- ext = 'mp3' if quality == 'audio' else 'mp4'
69
-
70
- cmd = [
71
- 'yt-dlp',
72
- '--no-playlist',
73
- '-f', fmt,
74
- '--merge-output-format', ext,
75
- '--no-check-certificates',
76
- '--no-warnings',
77
- ]
78
-
79
- # Audio-only: extract + convert to mp3
80
- if quality == 'audio':
81
- cmd += [
82
- '-x', '--audio-format', 'mp3',
83
- '--audio-quality', '0',
84
- ]
85
-
86
  if os.path.exists(COOKIES_FILE):
87
  cmd += ['--cookies', COOKIES_FILE]
88
-
89
- cmd += ['-o', out_tmpl, video_url]
90
- print(f'[ytdlp] {" ".join(cmd)}')
91
- subprocess.run(cmd, check=True, timeout=timeout)
92
-
93
- def get_video_info(url):
94
- """Fetch title, thumbnail, duration via yt-dlp --dump-json (no download)."""
95
- cmd = [
96
- 'yt-dlp',
97
- '--no-playlist',
98
- '--dump-json',
99
- '--no-check-certificates',
100
- '--no-warnings',
101
- ]
102
- if os.path.exists(COOKIES_FILE):
103
- cmd += ['--cookies', COOKIES_FILE]
104
- cmd.append(url)
105
- result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
106
- if result.returncode != 0:
107
- raise RuntimeError(result.stderr.strip() or 'yt-dlp info fetch failed')
108
- import json
109
- data = json.loads(result.stdout)
110
- return {
111
- 'title': data.get('title', 'Unknown'),
112
- 'thumbnail': data.get('thumbnail', ''),
113
- 'duration': data.get('duration', 0),
114
- 'uploader': data.get('uploader', ''),
115
- 'view_count': data.get('view_count', 0),
116
- }
117
-
118
- # ── Routes ──
119
 
120
  @app.route('/')
121
  def index():
122
- return send_from_directory('.', 'index.html')
123
-
124
- @app.route('/outputs/<path:fname>')
125
- def serve_output(fname):
126
- return send_from_directory(str(OUTPUT_DIR), fname)
127
-
128
- @app.route('/api/info', methods=['POST'])
129
- def api_info():
130
- """Fetch video metadata without downloading."""
131
- data = request.get_json(force=True) or {}
132
- url = (data.get('url') or '').strip()
133
- if not url:
134
- return jsonify(ok=False, msg='URL မပေးရသေး')
135
- try:
136
- info = get_video_info(url)
137
- return jsonify(ok=True, **info)
138
- except Exception as e:
139
- return jsonify(ok=False, msg=str(e))
140
 
141
  @app.route('/api/download', methods=['POST'])
142
  def api_download():
143
- """Start background download job."""
144
- data = request.get_json(force=True) or {}
145
- url = (data.get('url') or '').strip()
146
- quality = (data.get('quality') or '720').strip()
147
-
148
  if not url:
149
- return jsonify(ok=False, msg='URL မပေးရသေး')
150
- if quality not in QUALITY_FMT:
151
- return jsonify(ok=False, msg=f'Quality မမှန်: {quality}')
152
-
153
- tid = uuid.uuid4().hex[:12]
154
- ext = 'mp3' if quality == 'audio' else 'mp4'
155
- out_fn = f'dl_{tid}.{ext}'
156
- out_tmpl = str(OUTPUT_DIR / out_fn)
157
-
158
- set_prog(tid, 0, '⏳ Download စတင်နေသည်…')
159
-
160
  def _bg():
161
  try:
162
- set_prog(tid, 10, '⬇️ Downloading…')
163
- ytdlp_download(out_tmpl, url, quality=quality)
164
-
165
- # yt-dlp may produce dl_tid.mp4, dl_tid.webm, dl_tid.mkv etc.
166
- # Find the actual output file
167
- candidates = list(OUTPUT_DIR.glob(f'dl_{tid}.*'))
168
- if not candidates:
169
- raise FileNotFoundError('Output file not found after download')
170
-
171
- out_file = candidates[0]
172
- file_size = out_file.stat().st_size
173
-
174
- set_prog(tid, 100,
175
- f'✅ Download ပြီးပါပြီ!',
176
- done=True,
177
- file_url=f'/outputs/{out_file.name}',
178
- file_name=out_file.name,
179
- file_size=file_size,
180
- quality=quality)
181
  except Exception as e:
182
- import traceback; traceback.print_exc()
183
- set_prog(tid, 0, f'❌ {e}', error=True)
184
-
185
- threading.Thread(target=_bg, daemon=True).start()
186
- return jsonify(ok=True, tid=tid)
187
-
188
- @app.route('/api/progress/<tid>')
189
- def api_progress(tid):
190
- """SSE stream for job progress."""
191
- def _stream():
192
- last = None
193
- for _ in range(600): # 10 min max
194
- with _prog_lock:
195
- state = dict(job_progress.get(tid, {'pct': 0, 'msg': '…'}))
196
- if state != last:
197
- import json
198
- yield f'data: {json.dumps(state)}\n\n'
199
- last = dict(state)
200
- if state.get('done') or state.get('error'):
201
- break
202
- time.sleep(0.5)
203
- return Response(_stream(), mimetype='text/event-stream',
204
- headers={'Cache-Control': 'no-cache', 'X-Accel-Buffering': 'no'})
205
-
206
- # ── Auto cleanup outputs older than 2 hours ──
207
- def _cleanup_loop():
208
- while True:
209
- time.sleep(1800)
210
- now = time.time()
211
- for fp in OUTPUT_DIR.glob('dl_*'):
212
- try:
213
- if now - fp.stat().st_mtime > 7200:
214
- fp.unlink(missing_ok=True)
215
- except:
216
- pass
217
-
218
- threading.Thread(target=_cleanup_loop, daemon=True).start()
219
 
220
  if __name__ == '__main__':
221
- app.run(host='0.0.0.0', port=7860, debug=False, threaded=True)
 
1
+ import os, re, uuid, threading, time, subprocess
2
  from pathlib import Path
3
  from flask import Flask, request, jsonify, send_from_directory, Response
4
 
5
+ BASE_DIR = Path(__file__).parent
6
  COOKIES_FILE = str(BASE_DIR / 'm_youtube_com_cookies.txt')
7
+ OUTPUT_DIR = BASE_DIR / 'outputs'
8
  OUTPUT_DIR.mkdir(exist_ok=True)
9
 
10
  app = Flask(__name__)
 
11
 
12
+ # Quality formats
 
 
 
 
 
 
 
 
13
  QUALITY_FMT = {
14
+ '720': 'bestvideo[height<=720][ext=mp4]+bestaudio[ext=m4a]/best[height<=720]/best',
15
+ '480': 'best[height<=480]/best',
16
+ '360': 'best[height<=360]/best',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  }
18
 
19
+ def ytdlp_download(out_path, url, quality='720'):
 
 
 
 
 
 
 
 
 
20
  fmt = QUALITY_FMT.get(quality, QUALITY_FMT['720'])
21
+ cmd = ['yt-dlp', '-f', fmt, '--merge-output-format', 'mp4', '-o', out_path, url]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  if os.path.exists(COOKIES_FILE):
23
  cmd += ['--cookies', COOKIES_FILE]
24
+ subprocess.run(cmd, check=True, timeout=300)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
  @app.route('/')
27
  def index():
28
+ return "YouTube Downloader & Auto Poster is running!"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
 
30
  @app.route('/api/download', methods=['POST'])
31
  def api_download():
32
+ data = request.get_json() or {}
33
+ url = data.get('url', '').strip()
34
+ quality = data.get('quality', '720')
35
+
 
36
  if not url:
37
+ return jsonify(ok=False, msg='No URL')
38
+
39
+ tid = uuid.uuid4().hex[:8]
40
+ out_path = str(OUTPUT_DIR / f'{tid}.mp4')
41
+
 
 
 
 
 
 
42
  def _bg():
43
  try:
44
+ ytdlp_download(out_path, url, quality)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  except Exception as e:
46
+ print(f"Error: {e}")
47
+
48
+ threading.Thread(target=_bg).start()
49
+ return jsonify(ok=True, tid=tid, msg='Download started')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
  if __name__ == '__main__':
52
+ app.run(host='0.0.0.0', port=7860)