enzer1992 commited on
Commit
7cbe46d
·
verified ·
1 Parent(s): 1d2d333

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +101 -233
app.py CHANGED
@@ -1,233 +1,101 @@
1
- from flask import Flask, render_template_string, request, jsonify, send_file
2
- import os
3
- from pathlib import Path
4
- from yt_dlp import YoutubeDL
5
- from PIL import Image
6
- import threading
7
- import uuid
8
-
9
- app = Flask(__name__)
10
-
11
- # Store download status
12
- downloads = {}
13
-
14
- # HTML template as a string in the same file
15
- HTML_TEMPLATE = """
16
- <!DOCTYPE html>
17
- <html>
18
- <head>
19
- <title>YouTube Music Downloader</title>
20
- <meta name="viewport" content="width=device-width, initial-scale=1">
21
- <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
22
- <style>
23
- .download-card {
24
- margin: 20px 0;
25
- padding: 15px;
26
- border-radius: 8px;
27
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
28
- }
29
- .progress {
30
- height: 25px;
31
- }
32
- .download-btn {
33
- display: inline-block;
34
- padding: 8px 16px;
35
- background-color: #28a745;
36
- color: white;
37
- text-decoration: none;
38
- border-radius: 4px;
39
- margin-top: 10px;
40
- }
41
- .download-btn:hover {
42
- background-color: #218838;
43
- color: white;
44
- }
45
- </style>
46
- </head>
47
- <body>
48
- <div class="container mt-5">
49
- <h1 class="text-center mb-4">YouTube Music Downloader</h1>
50
-
51
- <div class="row justify-content-center">
52
- <div class="col-md-8">
53
- <div class="card">
54
- <div class="card-body">
55
- <form id="downloadForm">
56
- <div class="input-group mb-3">
57
- <input type="text" class="form-control" id="url" placeholder="Enter YouTube URL" required>
58
- <button class="btn btn-primary" type="submit">Download</button>
59
- </div>
60
- </form>
61
- </div>
62
- </div>
63
-
64
- <div id="downloads"></div>
65
- </div>
66
- </div>
67
- </div>
68
-
69
- <script>
70
- document.getElementById('downloadForm').onsubmit = async (e) => {
71
- e.preventDefault();
72
- const url = document.getElementById('url').value;
73
-
74
- const response = await fetch('/download', {
75
- method: 'POST',
76
- headers: {'Content-Type': 'application/json'},
77
- body: JSON.stringify({url: url})
78
- });
79
-
80
- const data = await response.json();
81
- if (data.download_id) {
82
- createDownloadCard(data.download_id);
83
- document.getElementById('url').value = '';
84
- }
85
- };
86
-
87
- function createDownloadCard(downloadId) {
88
- const card = document.createElement('div');
89
- card.className = 'download-card bg-light';
90
- card.id = `download-${downloadId}`;
91
- card.innerHTML = `
92
- <div class="progress mb-3">
93
- <div class="progress-bar progress-bar-striped progress-bar-animated"
94
- role="progressbar" style="width: 0%">0%</div>
95
- </div>
96
- <div class="status">Downloading...</div>
97
- `;
98
- document.getElementById('downloads').prepend(card);
99
-
100
- pollStatus(downloadId);
101
- }
102
-
103
- async function pollStatus(downloadId) {
104
- while (true) {
105
- const response = await fetch(`/status/${downloadId}`);
106
- const data = await response.json();
107
-
108
- const card = document.getElementById(`download-${downloadId}`);
109
- const progressBar = card.querySelector('.progress-bar');
110
- const statusDiv = card.querySelector('.status');
111
-
112
- progressBar.style.width = data.progress;
113
- progressBar.textContent = data.progress;
114
-
115
- if (data.status === 'completed') {
116
- const downloadLink = document.createElement('a');
117
- downloadLink.href = `/download/${downloadId}`;
118
- downloadLink.className = 'download-btn';
119
- downloadLink.download = ''; // Ensures file downloads instead of opening
120
- downloadLink.textContent = 'Download MP3';
121
-
122
- statusDiv.innerHTML = 'Download complete! ';
123
- statusDiv.appendChild(downloadLink);
124
- break;
125
- } else if (data.status === 'failed') {
126
- statusDiv.innerHTML = `Error: ${data.error}`;
127
- progressBar.className = 'progress-bar bg-danger';
128
- break;
129
- }
130
-
131
- await new Promise(resolve => setTimeout(resolve, 1000));
132
- }
133
- }
134
- </script>
135
- </body>
136
- </html>
137
- """
138
-
139
- class MusicDownloader:
140
- def __init__(self):
141
- self.download_dir = Path("downloads")
142
- self.download_dir.mkdir(exist_ok=True)
143
-
144
- def download(self, url: str, download_id: str) -> bool:
145
- downloads[download_id] = {'status': 'downloading', 'progress': '0%'}
146
-
147
- ydl_opts = {
148
- 'format': 'bestaudio/best',
149
- 'postprocessors': [{
150
- 'key': 'FFmpegExtractAudio',
151
- 'preferredcodec': 'mp3',
152
- 'preferredquality': '192',
153
- }],
154
- 'outtmpl': str(self.download_dir / '%(title)s.%(ext)s'),
155
- 'writethumbnail': True,
156
- 'progress_hooks': [lambda d: self._progress_hook(d, download_id)],
157
- }
158
-
159
- try:
160
- with YoutubeDL(ydl_opts) as ydl:
161
- info = ydl.extract_info(url, download=True)
162
- title = info.get('title', 'unknown_title')
163
-
164
- # Convert thumbnail
165
- webp_path = self.download_dir / f"{title}.webp"
166
- if webp_path.exists():
167
- self._convert_thumbnail(webp_path)
168
-
169
- downloads[download_id]['status'] = 'completed'
170
- downloads[download_id]['title'] = title
171
- return True
172
-
173
- except Exception as e:
174
- downloads[download_id]['status'] = 'failed'
175
- downloads[download_id]['error'] = str(e)
176
- return False
177
-
178
- def _progress_hook(self, d, download_id):
179
- if d['status'] == 'downloading':
180
- downloads[download_id]['progress'] = d.get('_percent_str', '0%')
181
-
182
- def _convert_thumbnail(self, thumbnail_path: Path):
183
- try:
184
- with Image.open(thumbnail_path) as img:
185
- jpg_path = thumbnail_path.with_suffix('.jpg')
186
- img.convert('RGB').save(jpg_path, 'JPEG')
187
- thumbnail_path.unlink()
188
- except Exception:
189
- pass
190
-
191
- @app.route('/')
192
- def index():
193
- return render_template_string(HTML_TEMPLATE)
194
-
195
- @app.route('/download', methods=['POST'])
196
- def start_download():
197
- url = request.json.get('url')
198
- if not url:
199
- return jsonify({'error': 'No URL provided'}), 400
200
-
201
- download_id = str(uuid.uuid4())
202
- downloader = MusicDownloader()
203
-
204
- threading.Thread(target=downloader.download, args=(url, download_id)).start()
205
-
206
- return jsonify({'download_id': download_id})
207
-
208
- @app.route('/status/<download_id>')
209
- def get_status(download_id):
210
- if download_id not in downloads:
211
- return jsonify({'status': 'not_found'}), 404
212
- return jsonify(downloads[download_id])
213
-
214
- @app.route('/download/<download_id>')
215
- def get_file(download_id):
216
- if download_id not in downloads or downloads[download_id]['status'] != 'completed':
217
- return 'File not found', 404
218
-
219
- title = downloads[download_id]['title']
220
- file_path = os.path.join('downloads', f'{title}.mp3')
221
-
222
- if not os.path.exists(file_path):
223
- return 'File not found', 404
224
-
225
- return send_file(
226
- file_path,
227
- as_attachment=True,
228
- download_name=f"{title}.mp3",
229
- mimetype='audio/mpeg'
230
- )
231
-
232
- if __name__ == '__main__':
233
- app.run(host='0.0.0.0', port=5000, debug=True)
 
1
+ import os
2
+ from flask import Flask, render_template, request, jsonify, send_file
3
+ import threading
4
+ import uuid
5
+ from pathlib import Path
6
+ from yt_dlp import YoutubeDL
7
+ from PIL import Image
8
+
9
+ app = Flask(__name__, template_folder=os.getcwd()) # Set template_folder to the current directory
10
+
11
+ downloads = {}
12
+
13
+ class MusicDownloader:
14
+ def __init__(self):
15
+ self.download_dir = Path("downloads")
16
+ self.download_dir.mkdir(exist_ok=True)
17
+
18
+ def download(self, url: str, download_id: str) -> bool:
19
+ downloads[download_id] = {'status': 'downloading', 'progress': '0%'}
20
+
21
+ ydl_opts = {
22
+ 'format': 'bestaudio/best',
23
+ 'postprocessors': [{
24
+ 'key': 'FFmpegExtractAudio',
25
+ 'preferredcodec': 'mp3',
26
+ 'preferredquality': '192',
27
+ }],
28
+ 'outtmpl': str(self.download_dir / '%(title)s.%(ext)s'),
29
+ 'writethumbnail': True,
30
+ 'progress_hooks': [lambda d: self._progress_hook(d, download_id)],
31
+ }
32
+
33
+ try:
34
+ with YoutubeDL(ydl_opts) as ydl:
35
+ info = ydl.extract_info(url, download=True)
36
+ title = info.get('title', 'unknown_title')
37
+
38
+ webp_path = self.download_dir / f"{title}.webp"
39
+ if webp_path.exists():
40
+ self._convert_thumbnail(webp_path)
41
+
42
+ downloads[download_id]['status'] = 'completed'
43
+ downloads[download_id]['title'] = title
44
+ return True
45
+
46
+ except Exception as e:
47
+ downloads[download_id]['status'] = 'failed'
48
+ downloads[download_id]['error'] = str(e)
49
+ return False
50
+
51
+ def _progress_hook(self, d, download_id):
52
+ if d['status'] == 'downloading':
53
+ downloads[download_id]['progress'] = d.get('_percent_str', '0%')
54
+
55
+ def _convert_thumbnail(self, thumbnail_path: Path):
56
+ try:
57
+ with Image.open(thumbnail_path) as img:
58
+ jpg_path = thumbnail_path.with_suffix('.jpg')
59
+ img.convert('RGB').save(jpg_path, 'JPEG')
60
+ thumbnail_path.unlink()
61
+ except Exception:
62
+ pass
63
+
64
+ @app.route('/')
65
+ def index():
66
+ return render_template('index.html') # No need for templates folder, index.html is in the same folder
67
+
68
+ @app.route('/download', methods=['POST'])
69
+ def start_download():
70
+ url = request.json.get('url')
71
+ if not url:
72
+ return jsonify({'error': 'No URL provided'}), 400
73
+
74
+ download_id = str(uuid.uuid4())
75
+ downloader = MusicDownloader()
76
+
77
+ threading.Thread(target=downloader.download, args=(url, download_id)).start()
78
+
79
+ return jsonify({'download_id': download_id})
80
+
81
+ @app.route('/status/<download_id>')
82
+ def get_status(download_id):
83
+ if download_id not in downloads:
84
+ return jsonify({'status': 'not_found'}), 404
85
+ return jsonify(downloads[download_id])
86
+
87
+ @app.route('/download/<download_id>')
88
+ def get_file(download_id):
89
+ if download_id not in downloads or downloads[download_id]['status'] != 'completed':
90
+ return 'File not found', 404
91
+
92
+ title = downloads[download_id]['title']
93
+ file_path = os.path.join('downloads', f'{title}.mp3')
94
+
95
+ if not os.path.exists(file_path):
96
+ return 'File not found', 404
97
+
98
+ return send_file(file_path, as_attachment=True)
99
+
100
+ if __name__ == '__main__':
101
+ app.run(host='0.0.0.0', port=7860) # Port set for Hugging Face Spaces