Video tab: modalita 'Solo audio' (estrazione MP3)
Aggiunto toggle Video/Solo audio nella tab Video. In modalita audio yt-dlp scarica solo bestaudio ed estrae in MP3 al bitrate impostato in Impostazioni (default 320 kbps), con embed thumbnail come la tab Scarica. - core/downloader.py: download_video() accetta audio_only e audio_bitrate; quando True usa format 'bestaudio/best' e --extract-audio --audio-format mp3 - api/bridge.py: start_video_download passa audio_only nel worker; log mostra 'Solo audio MP3 320K' vs 'Video 1080p' - webui: segmented control 'Video (MP4)' / 'Solo audio (MP3)' rosa, selettore qualita nascosto in modalita audio, hint aggiornato Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
1 parent
39c92f44f4
commit
6f5c32165a
5 files changed
+84
-7
No files matched your search
+9
-4
@@ -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
|
||||
|
||||
+22
-3
@@ -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,6 +487,21 @@ def download_video(
|
||||
if progress_callback:
|
||||
progress_callback(i, total, title, "downloading", 0)
|
||||
|
||||
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,
|
||||
|
||||
@@ -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
|
||||
=============================== */
|
||||
|
||||
@@ -144,6 +144,14 @@
|
||||
</div>
|
||||
|
||||
<div class="field inline">
|
||||
<label class="field-label">Modalità</label>
|
||||
<div class="segmented" id="videoMode">
|
||||
<button type="button" class="seg-btn active" data-mode="video">🎬 Video (MP4)</button>
|
||||
<button type="button" class="seg-btn" data-mode="audio">🎵 Solo audio (MP3)</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field inline" id="videoQualityField">
|
||||
<label class="field-label">Qualità</label>
|
||||
<select id="videoQuality" class="input pill input-sm">
|
||||
<option value="best">Migliore disponibile</option>
|
||||
@@ -155,9 +163,12 @@
|
||||
|
||||
<div class="hint">
|
||||
<span class="hint-ico">ⓘ</span>
|
||||
<div>
|
||||
In modalità <strong>Solo audio</strong> il file viene salvato in MP3 al bitrate impostato in Impostazioni → Download.<br>
|
||||
Per Instagram/Facebook privati serve un file <strong>cookies.txt</strong> valido (Impostazioni → Percorsi).
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card actions">
|
||||
<button class="btn btn-pink pill btn-lg" id="videoDownloadBtn">
|
||||
|
||||
@@ -375,6 +375,17 @@ $("#videoBrowseBtn").addEventListener("click", async () => {
|
||||
}
|
||||
});
|
||||
|
||||
let videoMode = "video"; // "video" | "audio"
|
||||
|
||||
$$("#videoMode .seg-btn").forEach((b) => {
|
||||
b.addEventListener("click", () => {
|
||||
$$("#videoMode .seg-btn").forEach((x) => x.classList.remove("active"));
|
||||
b.classList.add("active");
|
||||
videoMode = b.dataset.mode;
|
||||
$("#videoQualityField").style.display = videoMode === "audio" ? "none" : "";
|
||||
});
|
||||
});
|
||||
|
||||
$("#videoDownloadBtn").addEventListener("click", async () => {
|
||||
const url = $("#videoUrlInput").value.trim();
|
||||
if (!url) {
|
||||
@@ -396,6 +407,7 @@ $("#videoDownloadBtn").addEventListener("click", async () => {
|
||||
urls: [url],
|
||||
output_dir: state.videoOutputDir,
|
||||
quality: $("#videoQuality").value,
|
||||
audio_only: videoMode === "audio",
|
||||
});
|
||||
if (!res.ok) {
|
||||
toast(res.error || "Errore", "error");
|
||||
|
||||
Reference in new issue
Block a user