diff --git a/app_worker/uv.lock b/app_worker/uv.lock index a099ec5..d1c6907 100644 --- a/app_worker/uv.lock +++ b/app_worker/uv.lock @@ -165,9 +165,9 @@ wheels = [ [[package]] name = "yt-dlp" -version = "2025.2.19.23542.dev0" +version = "2025.2.21.232913.dev0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0d/5b/d9c57f3b0ad58925984472b25635f7d9095fa36a9b073efedfd7ceabe83b/yt_dlp-2025.2.19.23542.dev0.tar.gz", hash = "sha256:09c030058f1c82c82baaaf61887ecd82f4617c3e421b61c7af4087987e850a8d", size = 2929764 } +sdist = { url = "https://files.pythonhosted.org/packages/9a/24/a783403be9f00758cb68713cd6a18377b9be5567b466e4eae08fac03bae7/yt_dlp-2025.2.21.232913.dev0.tar.gz", hash = "sha256:c67bc876c0e1079a17381ff65d9911bc5a4018150d8ff926f06b707113c55280", size = 2933334 } wheels = [ - { url = "https://files.pythonhosted.org/packages/51/b5/9dca7278b7ad92f96f81bf228f28c4a6147a7db67cc6f5d46b7ddc53e5ba/yt_dlp-2025.2.19.23542.dev0-py3-none-any.whl", hash = "sha256:cdd11c03a5d137026d6bddc99159ed1afacc680194f949a3a7a346a2b6013d22", size = 3186829 }, + { url = "https://files.pythonhosted.org/packages/6f/f8/6ac465b056e597a0f4d7ace38c9e478f3db38b7a72c3cbb054274b2351dd/yt_dlp-2025.2.21.232913.dev0-py3-none-any.whl", hash = "sha256:dd87567ce88d0da203af70b09154e62ba6dc1a9f45a39ee5906c67e1872649f2", size = 3191145 }, ] diff --git a/app_worker/worker/utils.py b/app_worker/worker/utils.py index 39ac57c..0069b46 100644 --- a/app_worker/worker/utils.py +++ b/app_worker/worker/utils.py @@ -5,6 +5,7 @@ import yt_dlp _PRIVATE_COOKIES_FILEPATH: Final[Path] = Path('/app/cookies/_cookies.txt') _COOKIES_FILEPATH: Final[Path] = Path('/app/cookies/cookies.txt') +_COOKIES_OPTION_NAME: Final[str] = '--cookies' def cli_to_api(opts: list) -> dict: @@ -25,13 +26,13 @@ def is_file_empty(filepath: Path) -> bool: return filepath.is_file() and filepath.stat().st_size == 0 -def get_cookies_opts_if_not_empty() -> list[str]: +def get_cookies_opts_if_not_empty() -> tuple[str, str] | tuple: """Return yt-dlp cookies option with cookies filepath.""" if _PRIVATE_COOKIES_FILEPATH.exists() and not is_file_empty( _PRIVATE_COOKIES_FILEPATH ): - return ['--cookies', str(_PRIVATE_COOKIES_FILEPATH)] + return _COOKIES_OPTION_NAME, str(_PRIVATE_COOKIES_FILEPATH) if is_file_empty(_COOKIES_FILEPATH): - return [] - return ['--cookies', str(_COOKIES_FILEPATH)] + return () + return _COOKIES_OPTION_NAME, str(_COOKIES_FILEPATH) diff --git a/app_worker/ytdl_opts/default.py b/app_worker/ytdl_opts/default.py index 1ed73eb6..5c66bf3 100644 --- a/app_worker/ytdl_opts/default.py +++ b/app_worker/ytdl_opts/default.py @@ -5,15 +5,29 @@ More here https://github.com/yt-dlp/yt-dlp/blob/master/yt_dlp/options.py or 'yt- If you want to change any of these values or add new ones, copy all content to the `user.py` in the same directory as this file, and edit the values. + +For example, if you want to the add proxy option, you should copy the whole content of this file and add +the proxy option line split by two into the 'DEFAULT_YTDL_OPTS' tuple of CLI options. + +DEFAULT_YTDL_OPTS: Final[_OptsType] = ( + '--proxy', + 'http://', + ... +) """ +from typing import Final + from worker.core.config import settings from worker.utils import get_cookies_opts_if_not_empty -FINAL_AUDIO_FORMAT = 'mp3' -FINAL_THUMBNAIL_FORMAT = 'jpg' +FINAL_AUDIO_FORMAT: Final[str] = 'mp3' +FINAL_THUMBNAIL_FORMAT: Final[str] = 'jpg' -DEFAULT_YTDL_OPTS = [ + +type _OptsType = tuple[str, ...] + +DEFAULT_YTDL_OPTS: Final[_OptsType] = ( '--output', '%(title).200B.%(ext)s', '--no-playlist', @@ -24,24 +38,27 @@ DEFAULT_YTDL_OPTS = [ '--ignore-errors', '--verbose', *get_cookies_opts_if_not_empty(), -] +) -DEFAULT_VIDEO_FORMAT_SORT_OPT = ['--format-sort', 'res,vcodec:h265,h264'] +DEFAULT_VIDEO_FORMAT_SORT_OPT: Final[_OptsType] = ( + '--format-sort', + 'res,vcodec:h265,h264', +) -AUDIO_YTDL_OPTS = [ +AUDIO_YTDL_OPTS: Final[_OptsType] = ( '--extract-audio', '--audio-quality', '0', '--audio-format', FINAL_AUDIO_FORMAT, -] +) -AUDIO_FORMAT_YTDL_OPTS = ['--format', 'bestaudio/best'] +AUDIO_FORMAT_YTDL_OPTS: Final[_OptsType] = ('--format', 'bestaudio/best') -VIDEO_YTDL_OPTS = [ +VIDEO_YTDL_OPTS: Final[_OptsType] = ( '--format', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/mp4', '--write-thumbnail', '--convert-thumbnails', FINAL_THUMBNAIL_FORMAT, -] +) diff --git a/app_worker/ytdl_opts/per_host/_base.py b/app_worker/ytdl_opts/per_host/_base.py index 8e0e71c..47ad43a 100644 --- a/app_worker/ytdl_opts/per_host/_base.py +++ b/app_worker/ytdl_opts/per_host/_base.py @@ -60,16 +60,16 @@ class AbstractHostConfig: KEEP_VIDEO_OPTION: str = '--keep-video' - DEFAULT_YTDL_OPTS: list[str] = DEFAULT_YTDL_OPTS + DEFAULT_YTDL_OPTS: tuple[str, ...] = DEFAULT_YTDL_OPTS - AUDIO_YTDL_OPTS: list[str] = AUDIO_YTDL_OPTS - AUDIO_FORMAT_YTDL_OPTS: list[str] = AUDIO_FORMAT_YTDL_OPTS + AUDIO_YTDL_OPTS: tuple[str, ...] = AUDIO_YTDL_OPTS + AUDIO_FORMAT_YTDL_OPTS: tuple[str, ...] = AUDIO_FORMAT_YTDL_OPTS FINAL_AUDIO_FORMAT: str = FINAL_AUDIO_FORMAT FINAL_THUMBNAIL_FORMAT: str = FINAL_THUMBNAIL_FORMAT - DEFAULT_VIDEO_YTDL_OPTS: list[str] = VIDEO_YTDL_OPTS - DEFAULT_VIDEO_FORMAT_SORT_OPT: list[str] = DEFAULT_VIDEO_FORMAT_SORT_OPT + DEFAULT_VIDEO_YTDL_OPTS: tuple[str, ...] = VIDEO_YTDL_OPTS + DEFAULT_VIDEO_FORMAT_SORT_OPT: tuple[str, ...] = DEFAULT_VIDEO_FORMAT_SORT_OPT FFMPEG_AUDIO_OPTS: str | None = None FFMPEG_VIDEO_OPTS: str | None = None @@ -95,7 +95,7 @@ class AbstractHostConfig: ytdl_opts_.extend(self.DEFAULT_VIDEO_YTDL_OPTS) ytdl_opts_.extend(self._build_custom_ytdl_video_opts()) - ytdl_opts = deepcopy(self.DEFAULT_YTDL_OPTS) + ytdl_opts = list(deepcopy(self.DEFAULT_YTDL_OPTS)) match media_type: case DownMediaType.AUDIO: @@ -115,5 +115,5 @@ class AbstractHostConfig: return ytdl_opts @abstractmethod - def _build_custom_ytdl_video_opts(self) -> list[str]: + def _build_custom_ytdl_video_opts(self) -> tuple[str, ...]: pass diff --git a/app_worker/ytdl_opts/per_host/_default.py b/app_worker/ytdl_opts/per_host/_default.py index e2f8e0a..e5f7ccd 100644 --- a/app_worker/ytdl_opts/per_host/_default.py +++ b/app_worker/ytdl_opts/per_host/_default.py @@ -28,5 +28,5 @@ class DefaultHost(AbstractHostConfig, metaclass=HostConfRegistry): ytdl_opts=self._build_ytdl_opts(media_type, curr_tmp_dir), ) - def _build_custom_ytdl_video_opts(self) -> list[str]: + def _build_custom_ytdl_video_opts(self) -> tuple[str, ...]: return self.DEFAULT_VIDEO_FORMAT_SORT_OPT diff --git a/app_worker/ytdl_opts/per_host/facebook.py b/app_worker/ytdl_opts/per_host/facebook.py index 4dfc4f2..be42949 100644 --- a/app_worker/ytdl_opts/per_host/facebook.py +++ b/app_worker/ytdl_opts/per_host/facebook.py @@ -21,7 +21,7 @@ class FacebookHost(AbstractHostConfig, metaclass=HostConfRegistry): FFMPEG_AUDIO_OPTS = None # Facebook returns VP9+AAC in MP4 container for logged users and needs to be # encoded to H264 since Telegram doesn't play VP9 on iOS. - FFMPEG_VIDEO_OPTS = 'ffmpeg -y -loglevel error -i "{filepath}" -c:v libx264 -pix_fmt yuv420p -preset slow -threads 1 -crf 22 -movflags +faststart -c:a copy "{output}"' + FFMPEG_VIDEO_OPTS = 'ffmpeg -y -loglevel error -i "{filepath}" -c:v libx264 -pix_fmt yuv420p -preset slow -threads 2 -crf 22 -movflags +faststart -c:a copy "{output}"' def build_config( self, media_type: DownMediaType, curr_tmp_dir: Path @@ -35,5 +35,5 @@ class FacebookHost(AbstractHostConfig, metaclass=HostConfRegistry): ytdl_opts=self._build_ytdl_opts(media_type, curr_tmp_dir), ) - def _build_custom_ytdl_video_opts(self) -> list[str]: + def _build_custom_ytdl_video_opts(self) -> tuple[str, ...]: return self.DEFAULT_VIDEO_FORMAT_SORT_OPT diff --git a/app_worker/ytdl_opts/per_host/instagram.py b/app_worker/ytdl_opts/per_host/instagram.py index 8692fc6..046158b 100644 --- a/app_worker/ytdl_opts/per_host/instagram.py +++ b/app_worker/ytdl_opts/per_host/instagram.py @@ -21,7 +21,7 @@ class InstagramHost(AbstractHostConfig, metaclass=HostConfRegistry): FFMPEG_AUDIO_OPTS = None # Instagram returns VP9+AAC in MP4 container for logged users and needs to be # encoded to H264 since Telegram doesn't play VP9 on iOS. - FFMPEG_VIDEO_OPTS = 'ffmpeg -y -loglevel error -i "{filepath}" -c:v libx264 -pix_fmt yuv420p -preset slow -threads 1 -crf 22 -movflags +faststart -c:a copy "{output}"' + FFMPEG_VIDEO_OPTS = 'ffmpeg -y -loglevel error -i "{filepath}" -c:v libx264 -pix_fmt yuv420p -preset slow -threads 2 -crf 22 -movflags +faststart -c:a copy "{output}"' def build_config( self, media_type: DownMediaType, curr_tmp_dir: Path @@ -35,5 +35,5 @@ class InstagramHost(AbstractHostConfig, metaclass=HostConfRegistry): ytdl_opts=self._build_ytdl_opts(media_type, curr_tmp_dir), ) - def _build_custom_ytdl_video_opts(self) -> list[str]: + def _build_custom_ytdl_video_opts(self) -> tuple[str, ...]: return self.DEFAULT_VIDEO_FORMAT_SORT_OPT diff --git a/app_worker/ytdl_opts/per_host/tiktok.py b/app_worker/ytdl_opts/per_host/tiktok.py index 3f5d551..90a817d 100644 --- a/app_worker/ytdl_opts/per_host/tiktok.py +++ b/app_worker/ytdl_opts/per_host/tiktok.py @@ -29,5 +29,5 @@ class TikTokHost(AbstractHostConfig, metaclass=HostConfRegistry): ytdl_opts=self._build_ytdl_opts(media_type, curr_tmp_dir), ) - def _build_custom_ytdl_video_opts(self) -> list[str]: + def _build_custom_ytdl_video_opts(self) -> tuple[str, ...]: return self.DEFAULT_VIDEO_FORMAT_SORT_OPT diff --git a/app_worker/ytdl_opts/per_host/twitter.py b/app_worker/ytdl_opts/per_host/twitter.py index 86718d3..a45d6bd 100644 --- a/app_worker/ytdl_opts/per_host/twitter.py +++ b/app_worker/ytdl_opts/per_host/twitter.py @@ -29,5 +29,5 @@ class TwitterHost(AbstractHostConfig, metaclass=HostConfRegistry): ytdl_opts=self._build_ytdl_opts(media_type, curr_tmp_dir), ) - def _build_custom_ytdl_video_opts(self) -> list[str]: - return ['--format-sort', 'res,proto:https,vcodec:h265,h264'] + def _build_custom_ytdl_video_opts(self) -> tuple[str, ...]: + return '--format-sort', 'res,proto:https,vcodec:h265,h264' diff --git a/uv.lock b/uv.lock index f5a4544..82ab4e3 100644 --- a/uv.lock +++ b/uv.lock @@ -794,15 +794,15 @@ wheels = [ [[package]] name = "pydantic-settings" -version = "2.7.1" +version = "2.8.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "python-dotenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/73/7b/c58a586cd7d9ac66d2ee4ba60ca2d241fa837c02bca9bea80a9a8c3d22a9/pydantic_settings-2.7.1.tar.gz", hash = "sha256:10c9caad35e64bfb3c2fbf70a078c0e25cc92499782e5200747f942a065dec93", size = 79920 } +sdist = { url = "https://files.pythonhosted.org/packages/ca/a2/ad2511ede77bb424f3939e5148a56d968cdc6b1462620d24b2a1f4ab65b4/pydantic_settings-2.8.0.tar.gz", hash = "sha256:88e2ca28f6e68ea102c99c3c401d6c9078e68a5df600e97b43891c34e089500a", size = 83347 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/46/93416fdae86d40879714f72956ac14df9c7b76f7d41a4d68aa9f71a0028b/pydantic_settings-2.7.1-py3-none-any.whl", hash = "sha256:590be9e6e24d06db33a4262829edef682500ef008565a969c73d39d5f8bfb3fd", size = 29718 }, + { url = "https://files.pythonhosted.org/packages/c1/a9/3b9642025174bbe67e900785fb99c9bfe91ea584b0b7126ff99945c24a0e/pydantic_settings-2.8.0-py3-none-any.whl", hash = "sha256:c782c7dc3fb40e97b238e713c25d26f64314aece2e91abcff592fcac15f71820", size = 30746 }, ] [[package]] @@ -1307,11 +1307,11 @@ wheels = [ [[package]] name = "yt-dlp" -version = "2025.2.19.23542.dev0" +version = "2025.2.21.232913.dev0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0d/5b/d9c57f3b0ad58925984472b25635f7d9095fa36a9b073efedfd7ceabe83b/yt_dlp-2025.2.19.23542.dev0.tar.gz", hash = "sha256:09c030058f1c82c82baaaf61887ecd82f4617c3e421b61c7af4087987e850a8d", size = 2929764 } +sdist = { url = "https://files.pythonhosted.org/packages/9a/24/a783403be9f00758cb68713cd6a18377b9be5567b466e4eae08fac03bae7/yt_dlp-2025.2.21.232913.dev0.tar.gz", hash = "sha256:c67bc876c0e1079a17381ff65d9911bc5a4018150d8ff926f06b707113c55280", size = 2933334 } wheels = [ - { url = "https://files.pythonhosted.org/packages/51/b5/9dca7278b7ad92f96f81bf228f28c4a6147a7db67cc6f5d46b7ddc53e5ba/yt_dlp-2025.2.19.23542.dev0-py3-none-any.whl", hash = "sha256:cdd11c03a5d137026d6bddc99159ed1afacc680194f949a3a7a346a2b6013d22", size = 3186829 }, + { url = "https://files.pythonhosted.org/packages/6f/f8/6ac465b056e597a0f4d7ace38c9e478f3db38b7a72c3cbb054274b2351dd/yt_dlp-2025.2.21.232913.dev0-py3-none-any.whl", hash = "sha256:dd87567ce88d0da203af70b09154e62ba6dc1a9f45a39ee5906c67e1872649f2", size = 3191145 }, ] [[package]] diff --git a/yt_shared/uv.lock b/yt_shared/uv.lock index ffd6449..d237570 100644 --- a/yt_shared/uv.lock +++ b/yt_shared/uv.lock @@ -429,15 +429,15 @@ wheels = [ [[package]] name = "pydantic-settings" -version = "2.7.1" +version = "2.8.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "python-dotenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/73/7b/c58a586cd7d9ac66d2ee4ba60ca2d241fa837c02bca9bea80a9a8c3d22a9/pydantic_settings-2.7.1.tar.gz", hash = "sha256:10c9caad35e64bfb3c2fbf70a078c0e25cc92499782e5200747f942a065dec93", size = 79920 } +sdist = { url = "https://files.pythonhosted.org/packages/ca/a2/ad2511ede77bb424f3939e5148a56d968cdc6b1462620d24b2a1f4ab65b4/pydantic_settings-2.8.0.tar.gz", hash = "sha256:88e2ca28f6e68ea102c99c3c401d6c9078e68a5df600e97b43891c34e089500a", size = 83347 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/46/93416fdae86d40879714f72956ac14df9c7b76f7d41a4d68aa9f71a0028b/pydantic_settings-2.7.1-py3-none-any.whl", hash = "sha256:590be9e6e24d06db33a4262829edef682500ef008565a969c73d39d5f8bfb3fd", size = 29718 }, + { url = "https://files.pythonhosted.org/packages/c1/a9/3b9642025174bbe67e900785fb99c9bfe91ea584b0b7126ff99945c24a0e/pydantic_settings-2.8.0-py3-none-any.whl", hash = "sha256:c782c7dc3fb40e97b238e713c25d26f64314aece2e91abcff592fcac15f71820", size = 30746 }, ] [[package]]