1
0
mirror of https://github.com/darkzoul5/YoutubePlaylistSync.git synced 2026-07-03 04:23:59 +03:00

fix download modes

This commit is contained in:
2026-05-15 22:57:40 +03:00
parent da8bf1786c
commit bf57bd77e6
5 changed files with 33 additions and 53 deletions
+6 -1
View File
@@ -20,7 +20,7 @@ Local-first YouTube playlist synchronization client.
- Python 3.10+
- `yt-dlp` (pip)
- `ffmpeg` (for audio extraction)
- `ffmpeg` (only needed for audio extraction / "both" mode)
Install:
@@ -45,6 +45,11 @@ Create/edit `config/yt-playlist-config.json`:
}
```
`download_mode`:
- `video`: download playlist videos as muxed `.mp4` (no ffmpeg processing)
- `audio`: download muxed `.mp4`, extract `.mp3`, delete the `.mp4`
- `both`: download muxed `.mp4`, extract `.mp3`, keep both files
## Run
- Compute-only:
+1 -1
View File
@@ -51,7 +51,7 @@ def main(argv: list[str] | None = None) -> int:
if msg not in seen_errors:
seen_errors.add(msg)
# Friendly hint for missing ffmpeg
if "ffprobe and ffmpeg not found" in msg.lower():
if "ffmpeg not found" in msg.lower():
print("ERROR: ffmpeg not found. Install ffmpeg or set 'ffmpeg_path' in config.")
else:
print(f"ERROR: {msg}")
+12 -31
View File
@@ -52,38 +52,19 @@ class Downloader:
pass
outtmpl = str(job.output_path)
if job.mode == "audio":
ydl_opts = {
"format": "bestaudio/best",
"outtmpl": outtmpl,
"postprocessors": [
{
"key": "FFmpegExtractAudio",
"preferredcodec": "mp3",
"preferredquality": "0",
}
],
"noplaylist": True,
"quiet": True,
"no_warnings": True,
"logger": _QuietLogger(),
}
else: # video
ydl_opts = {
"format": "bestvideo+bestaudio/best",
"merge_output_format": "mp4",
"outtmpl": outtmpl,
"noplaylist": True,
"quiet": True,
"no_warnings": True,
"logger": _QuietLogger(),
}
# Prefer job-provided path first
if job.ffmpeg_path:
ydl_opts["ffmpeg_location"] = job.ffmpeg_path
elif self.ffmpeg_path:
ydl_opts["ffmpeg_location"] = self.ffmpeg_path
# All modes download a single muxed mp4 when possible.
# This avoids any ffmpeg-driven merging during the download step, satisfying:
# - video: "original file, no processing"
# - audio/both: extraction is done separately after download
ydl_opts = {
"format": "best[ext=mp4][acodec!=none][vcodec!=none]/best[ext=mp4]",
"outtmpl": outtmpl,
"noplaylist": True,
"quiet": True,
"no_warnings": True,
"logger": _QuietLogger(),
}
with yt_dlp.YoutubeDL(ydl_opts) as ydl: # type: ignore[attr-defined]
ydl.download([job.url])
+5 -3
View File
@@ -55,9 +55,11 @@ class ActionExecutor:
# yt-dlp is required for any download job (Python API usage)
ensure_yt_dlp_available()
# ffmpeg/ffprobe are required for merges and audio extraction; check once up-front
ffmpeg_hint = playlist_cfg.get("ffmpeg_path", "ffmpeg")
ensure_ffmpeg_available(str(ffmpeg_hint) if ffmpeg_hint is not None else None)
# ffmpeg is only required when we will extract audio (audio/both modes)
needs_audio = any((a.to_name or "").lower().endswith(".mp3") for a in actions if a.type == SyncActionType.DOWNLOAD)
if needs_audio:
ffmpeg_hint = playlist_cfg.get("ffmpeg_path", "ffmpeg")
ensure_ffmpeg_available(str(ffmpeg_hint) if ffmpeg_hint is not None else None)
async def _apply_renames(self, actions: Iterable[SyncAction], audio_root: Path, video_root: Path, playlist_cfg: dict) -> None:
playlist_id = extract_playlist_id(playlist_cfg.get("url", "")) or playlist_cfg.get("url", "")
+9 -17
View File
@@ -52,7 +52,8 @@ def _resolve_tool_paths(tool_hint: Optional[str], exe_name: str) -> Tuple[Option
- Otherwise, treat tool_hint as a command and fall back to PATH resolution.
"""
if tool_hint:
hint = Path(tool_hint)
hint_str = str(tool_hint).strip().strip('"').strip("'")
hint = Path(hint_str)
# Expand envvars (%FFMPEG%) etc.
expanded = Path(os.path.expandvars(str(hint)))
if expanded.is_dir():
@@ -71,31 +72,22 @@ def _resolve_tool_paths(tool_hint: Optional[str], exe_name: str) -> Tuple[Option
def ensure_ffmpeg_available(ffmpeg_hint: Optional[str]) -> Tuple[str, str]:
"""
Ensures both ffmpeg and ffprobe are runnable. Returns (ffmpeg_path, ffprobe_path).
Ensures ffmpeg is runnable. Returns (ffmpeg_path, ffmpeg_path).
Note: ffprobe is intentionally not required. This project uses ffmpeg directly for
audio extraction, and yt-dlp can still function for muxed downloads without ffprobe.
"""
ffmpeg_exe = "ffmpeg.exe" if sys.platform.startswith("win") else "ffmpeg"
ffprobe_exe = "ffprobe.exe" if sys.platform.startswith("win") else "ffprobe"
ffmpeg_path, ffmpeg_dir = _resolve_tool_paths(ffmpeg_hint, ffmpeg_exe)
if not ffmpeg_path:
raise DependencyError("ffmpeg not found. Install ffmpeg or set 'ffmpeg_path' in config.")
# For ffprobe prefer the same directory if we have one
ffprobe_path = None
if ffmpeg_dir:
cand = Path(ffmpeg_dir) / ffprobe_exe
if cand.exists():
ffprobe_path = str(cand)
if not ffprobe_path:
ffprobe_path, _ = _resolve_tool_paths(None, ffprobe_exe)
if not ffprobe_path:
raise DependencyError("ffprobe not found (usually ships with ffmpeg). Install ffmpeg or fix 'ffmpeg_path'.")
# Smoke test (fast)
try:
subprocess.run([ffmpeg_path, "-version"], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
subprocess.run([ffprobe_path, "-version"], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
except Exception as exc:
raise DependencyError("ffmpeg/ffprobe exist but are not runnable. Check permissions/architecture/path.") from exc
raise DependencyError("ffmpeg exists but is not runnable. Check permissions/architecture/path.") from exc
return ffmpeg_path, ffprobe_path
# Keep return shape stable for existing callers
return ffmpeg_path, ffmpeg_path