feat(yt): pull cookies from firefox dev edition, firefox, then chrome

age/login-gated videos need browser cookies to download.
`ydl_opts` becomes a computed_field that picks the first available source.
This commit is contained in:
matt
2026-08-07 12:38:13 -07:00
parent b9f6aaf462
commit e0d3af2a4f
+32 -11
View File
@@ -1,12 +1,13 @@
import asyncio import asyncio
import glob
import os import os
import re import re
from pathlib import Path from pathlib import Path
from typing import ClassVar
from pydantic_settings import CliImplicitFlag, CliPositionalArg from pydantic_settings import CliImplicitFlag, CliPositionalArg
import structlog import structlog
import yt_dlp import yt_dlp
import yt_dlp.cookies
from mutagen.easyid3 import EasyID3 from mutagen.easyid3 import EasyID3
from pydantic import Field, computed_field from pydantic import Field, computed_field
from shazamio import Shazam from shazamio import Shazam
@@ -73,22 +74,24 @@ def current_quarter() -> Path:
return Path().home() / "Music" / f"{date.quarter}_{date.year}" return Path().home() / "Music" / f"{date.quarter}_{date.year}"
def _cookiesfrombrowser() -> tuple | None:
"""First available cookies source: firefox dev edition, firefox, then chrome."""
for root in yt_dlp.cookies._firefox_browser_dirs():
# dev edition lives in a randomly-prefixed dir, so pass an absolute path
matches = glob.glob(os.path.join(root, "*.dev-edition-default"))
if matches:
return ("firefox", matches[0], None, None)
if any(yt_dlp.cookies._firefox_cookie_dbs(yt_dlp.cookies._firefox_browser_dirs())):
return ("firefox", None, None, None)
return ("chrome", None, None, None)
class YtDownload(Command): class YtDownload(Command):
""" """
download a youtube song from {URL} to current quarter dir. download a youtube song from {URL} to current quarter dir.
""" """
url: CliPositionalArg[str] = Field(description="youtube url to download") url: CliPositionalArg[str] = Field(description="youtube url to download")
ydl_opts: ClassVar = {
"format": "mp3/bestaudio/best",
"postprocessors": [
{
"key": "FFmpegExtractAudio",
"preferredcodec": "mp3",
}
],
"remote_components": ["ejs:github"],
}
parents: CliImplicitFlag[bool] = Field( parents: CliImplicitFlag[bool] = Field(
default=True, description="create the parent dirs if not exists" default=True, description="create the parent dirs if not exists"
) )
@@ -99,6 +102,24 @@ class YtDownload(Command):
date = DateParse() date = DateParse()
return Path().home() / "Music" / f"{date.quarter}_{date.year}" return Path().home() / "Music" / f"{date.quarter}_{date.year}"
@computed_field()
@property
def ydl_opts(self) -> dict:
opts = {
"format": "mp3/bestaudio/best",
"postprocessors": [
{
"key": "FFmpegExtractAudio",
"preferredcodec": "mp3",
}
],
"remote_components": ["ejs:github"],
}
cookies = _cookiesfrombrowser()
if cookies is not None:
opts["cookiesfrombrowser"] = cookies
return opts
def run(self) -> None: def run(self) -> None:
os.chdir(Path.home() / "Downloads") os.chdir(Path.home() / "Downloads")
with yt_dlp.YoutubeDL(self.ydl_opts) as ydl: with yt_dlp.YoutubeDL(self.ydl_opts) as ydl: