From 88a3153df8bb7137b8272f684520409752d0c4ba Mon Sep 17 00:00:00 2001 From: CasualYouTuber31 <21147925+CasualYT31@users.noreply.github.com> Date: Fri, 23 Jan 2026 18:09:53 +0000 Subject: [PATCH] [tiktok] download best quality videos (#8846) * [tiktok] download best quality videos * [tiktok] code formatting fix * simplify sorting in '_extract_video_urls' --- gallery_dl/extractor/tiktok.py | 45 ++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/gallery_dl/extractor/tiktok.py b/gallery_dl/extractor/tiktok.py index b03a7e27..3d51bbe8 100644 --- a/gallery_dl/extractor/tiktok.py +++ b/gallery_dl/extractor/tiktok.py @@ -92,6 +92,7 @@ class TiktokExtractor(Extractor): ytdl_media = "video" elif self.video and (url := self._extract_video(post)): yield Message.Url, url, post + del post["_fallback"] if self.cover and (url := self._extract_cover(post, "video")): yield Message.Url, url, post @@ -212,13 +213,16 @@ class TiktokExtractor(Extractor): def _extract_video(self, post): video = post["video"] - try: - url = video["playAddr"] - except KeyError: - raise exception.ExtractionError("Failed to extract video URL, you " - "may need cookies to continue") + urls = self._extract_video_urls(video) + if not urls: + raise exception.ExtractionError( + f"{post['id']}: Failed to extract video URLs. " + f"You may need cookies to continue.") + + url = urls[0] text.nameext_from_url(url, post) post.update({ + "_fallback": urls[1:], "type" : "video", "image" : None, "title" : post["desc"] or f"TikTok video #{post['id']}", @@ -232,6 +236,37 @@ class TiktokExtractor(Extractor): post["extension"] = video.get("format", "mp4") return url + def _extract_video_urls(self, video): + # First, look for bitrateInfo. + # This will include URLs pointing to the best quality videos. + if "bitrateInfo" in video: + bitrate_info = video["bitrateInfo"] + if not isinstance(bitrate_info, list): + bitrate_info = [bitrate_info] + bitrate_urls = {} + for video_info in bitrate_info: + play_addr = video_info["PlayAddr"] + width = text.parse_int(play_addr.get("Width")) + height = text.parse_int(play_addr.get("Height")) + size = width * height + if size in bitrate_urls: + bitrate_urls[size] += play_addr.get("UrlList") + else: + bitrate_urls[size] = play_addr.get("UrlList").copy() + # Sort the URLs by descending quality. + sizes = list(bitrate_urls) + sizes.sort(reverse=True) + urls = [url for size in sizes for url in bitrate_urls[size]] + else: + urls = [] + + # As a fallback, try to look for the root playAddr, + # which won't necessarily point to the best quality. + if "playAddr" in video: + urls.append(video["playAddr"]) + + return urls + def _extract_audio(self, post): audio = post["music"] url = audio["playUrl"]