diff --git a/api/bridge.py b/api/bridge.py index e708d78..d49a329 100644 --- a/api/bridge.py +++ b/api/bridge.py @@ -640,6 +640,7 @@ class Api: urls = payload.get("urls") or [] output_dir = (payload.get("output_dir") or "").strip() quality = payload.get("quality") or "1080p" + audio_only = bool(payload.get("audio_only", False)) if not urls: return {"ok": False, "error": "Nessun URL fornito"} @@ -648,7 +649,7 @@ class Api: self._video_thread = threading.Thread( target=self._video_worker, - args=(list(urls), output_dir, quality), + args=(list(urls), output_dir, quality, audio_only), daemon=True, ) self._video_thread.start() @@ -659,10 +660,12 @@ class Api: self._log("video", "[INFO] Interruzione richiesta...") return {"ok": True} - def _video_worker(self, urls: list, output_dir: str, quality: str) -> None: + def _video_worker(self, urls: list, output_dir: str, quality: str, + audio_only: bool = False) -> None: reset_download_stop() cfg = load_config() cookies_path = cfg.get("cookies_path", "") + bitrate = cfg.get("bitrate", "320K") total_urls = len(urls) multi = total_urls > 1 view = "video" @@ -718,9 +721,11 @@ class Api: self._emit("video:progress", payload_evt) - self._log(view, f"[INFO] Video: {url}") + mode_lbl = f"Solo audio MP3 {bitrate}" if audio_only else f"Video {quality}" + self._log(view, f"[INFO] {mode_lbl}: {url}") try: - download_video(url, output_dir, quality, cookies_path, progress_cb) + download_video(url, output_dir, quality, cookies_path, progress_cb, + audio_only=audio_only, audio_bitrate=bitrate) except Exception as e: self._log(view, f"[ERRORE] {e}") continue diff --git a/core/downloader.py b/core/downloader.py index e89f0e3..9eac552 100644 --- a/core/downloader.py +++ b/core/downloader.py @@ -418,10 +418,14 @@ def download_video( quality: str = "1080p", cookies_path: Optional[str] = None, progress_callback: Optional[Callable] = None, + audio_only: bool = False, + audio_bitrate: str = "320K", ) -> None: - """Scarica video (con audio) da YouTube/TikTok/Instagram/Facebook/etc. + """Scarica video (con audio) o solo audio da YouTube/TikTok/Instagram/Facebook/etc. - quality: "best" | "1080p" | "720p" | "480p" + quality: "best" | "1080p" | "720p" | "480p" (ignorato se audio_only=True) + audio_only: True -> estrae solo audio in MP3 + audio_bitrate: bitrate MP3 quando audio_only=True Per Instagram/Facebook privati servono cookies validi. """ global _current_process @@ -461,7 +465,7 @@ def download_video( return total = len(entries) - fmt = _video_format(quality) + fmt = _video_format(quality) if not audio_only else "bestaudio/best" for i, entry in enumerate(entries): if is_stopped(): @@ -483,16 +487,31 @@ def download_video( if progress_callback: progress_callback(i, total, title, "downloading", 0) - cmd = [ - ytdlp, - "-f", fmt, - "--merge-output-format", "mp4", - "--add-metadata", - "--no-check-certificates", - "--newline", - "--output", str(Path(output_dir) / "%(title)s [%(id)s].%(ext)s"), - entry_url, - ] + if audio_only: + cmd = [ + ytdlp, + "-f", fmt, + "--extract-audio", + "--audio-format", "mp3", + "--audio-quality", audio_bitrate.rstrip("Kk"), + "--embed-thumbnail", + "--add-metadata", + "--no-check-certificates", + "--newline", + "--output", str(Path(output_dir) / "%(title)s [%(id)s].%(ext)s"), + entry_url, + ] + else: + cmd = [ + ytdlp, + "-f", fmt, + "--merge-output-format", "mp4", + "--add-metadata", + "--no-check-certificates", + "--newline", + "--output", str(Path(output_dir) / "%(title)s [%(id)s].%(ext)s"), + entry_url, + ] ffmpeg_dir = find_ffmpeg_dir() if ffmpeg_dir: cmd.extend(["--ffmpeg-location", ffmpeg_dir]) diff --git a/webui/css/style.css b/webui/css/style.css index 7f87bf2..71c8ec4 100644 --- a/webui/css/style.css +++ b/webui/css/style.css @@ -615,6 +615,36 @@ input[type="number"]::-webkit-inner-spin-button { .log .l-warn { color: #FDCB6E; } .log .l-muted { color: var(--text-3); } +/* =============================== + SEGMENTED CONTROL + =============================== */ +.segmented { + display: inline-flex; + background: var(--bg-input); + padding: 4px; + border-radius: var(--r-pill); + gap: 2px; +} + +.seg-btn { + border: none; + background: transparent; + color: var(--text-2); + padding: 8px 18px; + border-radius: var(--r-pill); + cursor: pointer; + font-weight: 700; + font-size: 13px; + transition: background-color 0.15s, color 0.15s; +} + +.seg-btn:hover:not(.active) { color: var(--text); } + +.seg-btn.active { + background: var(--pink); + color: #fff; +} + /* =============================== HINT BOX =============================== */ diff --git a/webui/index.html b/webui/index.html index f9e1988..65f7aa6 100644 --- a/webui/index.html +++ b/webui/index.html @@ -144,6 +144,14 @@
+ +
+ + +
+
+ +