diff --git a/app.py b/app.py index 703f435..8b65d52 100644 --- a/app.py +++ b/app.py @@ -17,7 +17,7 @@ def run_download(job_id, url, format_choice, format_id): job = jobs[job_id] out_template = os.path.join(DOWNLOAD_DIR, f"{job_id}.%(ext)s") - cmd = ["yt-dlp", "--no-playlist", "-o", out_template] + cmd = ["yt-dlp", "-o", out_template] if format_choice == "audio": cmd += ["-x", "--audio-format", "mp3"] @@ -85,39 +85,50 @@ def get_info(): if not url: return jsonify({"error": "No URL provided"}), 400 - cmd = ["yt-dlp", "--no-playlist", "-j", url] + cmd = ["yt-dlp", "-j", url] try: - result = subprocess.run(cmd, capture_output=True, text=True, timeout=60) + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) if result.returncode != 0: return jsonify({"error": result.stderr.strip().split("\n")[-1]}), 400 - info = json.loads(result.stdout) + info = [json.loads(line) for line in result.stdout.strip().split("\n") if line.strip()] # Build quality options — keep best format per resolution - best_by_height = {} - for f in info.get("formats", []): - height = f.get("height") - if height and f.get("vcodec", "none") != "none": - tbr = f.get("tbr") or 0 - if height not in best_by_height or tbr > (best_by_height[height].get("tbr") or 0): - best_by_height[height] = f - - formats = [] - for height, f in best_by_height.items(): - formats.append({ - "id": f["format_id"], - "label": f"{height}p", - "height": height, + def extract_info(info): + best_by_height = {} + for f in info.get("formats", []): + height = f.get("height") + if height and f.get("vcodec", "none") != "none": + tbr = f.get("tbr") or 0 + if height not in best_by_height or tbr > (best_by_height[height].get("tbr") or 0): + best_by_height[height] = f + + formats = [] + for height, f in best_by_height.items(): + formats.append({ + "id": f["format_id"], + "label": f"{height}p", + "height": height, + }) + formats.sort(key=lambda x: x["height"], reverse=True) + + return { + "title": info.get("title", ""), + "thumbnail": info.get("thumbnail", ""), + "duration": info.get("duration"), + "uploader": info.get("uploader", ""), + "formats": formats, + "url": info.get("webpage_url", url), + } + result_list = [extract_info(v) for v in info] + + if len(result_list) ==1: + return jsonify(result_list[0]) + else: + return jsonify({ + "is_playlist": True, + "videos": result_list, }) - formats.sort(key=lambda x: x["height"], reverse=True) - - return jsonify({ - "title": info.get("title", ""), - "thumbnail": info.get("thumbnail", ""), - "duration": info.get("duration"), - "uploader": info.get("uploader", ""), - "formats": formats, - }) except subprocess.TimeoutExpired: return jsonify({"error": "Timed out fetching video info"}), 400 except Exception as e: diff --git a/templates/index.html b/templates/index.html index 0bae3d3..230df20 100644 --- a/templates/index.html +++ b/templates/index.html @@ -477,6 +477,31 @@

ReClip

const data = await res.json(); if (data.error) { cardData[idx] = { ...cardData[idx], status: 'info-error', error: data.error }; + renderCard(idx); + } else if (data.is_playlist) { + cardData.splice(idx, 1); + const container = document.getElementById('cards'); + const placeholder = document.getElementById(`card-${idx}`); + if (placeholder) placeholder.remove(); + + for (const video of data.videos) { + const vidIdx = cardData.length; + cardData.push({ + url: video.url, + status: 'ready', + title: video.title || '', + thumbnail: video.thumbnail || '', + duration: video.duration, + uploader: video.uploader || '', + formats: video.formats || [], + selectedFormatId: video.formats?.[0]?.id || null, + }); + const el = document.createElement('div'); + el.id = `card-${vidIdx}`; + el.className = 'card'; + container.appendChild(el); + renderCard(vidIdx); + } } else { cardData[idx] = { ...cardData[idx], @@ -488,11 +513,12 @@

ReClip

formats: data.formats || [], selectedFormatId: data.formats?.[0]?.id || null, }; + renderCard(idx); } } catch (err) { cardData[idx] = { ...cardData[idx], status: 'info-error', error: err.message }; + renderCard(idx); } - renderCard(idx); } if (cardData.filter(c => c.status === 'ready').length > 1) {