From 9a8746f12df9efa5a61ebd4e685925a06cccd154 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20F=C3=A4hrmann?= Date: Sun, 7 Dec 2025 20:30:47 +0100 Subject: [PATCH] [tiktok] add 'covers' option (#8515) --- docs/configuration.rst | 10 ++++++++++ docs/gallery-dl.conf | 1 + gallery_dl/extractor/tiktok.py | 32 ++++++++++++++++++++++++++++++-- test/results/tiktok.py | 10 ++++++++++ 4 files changed, 51 insertions(+), 2 deletions(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index f6dab9e8..057560c1 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -5782,6 +5782,16 @@ Description Ignore audio tracks +extractor.tiktok.covers +----------------------- +Type + ``bool`` +Default + ``false`` +Description + Download video covers. + + extractor.tiktok.videos ----------------------- Type diff --git a/docs/gallery-dl.conf b/docs/gallery-dl.conf index ba85e933..40781973 100644 --- a/docs/gallery-dl.conf +++ b/docs/gallery-dl.conf @@ -824,6 +824,7 @@ { "audio" : true, "videos": true, + "covers": false, "user": { "avatar": true, diff --git a/gallery_dl/extractor/tiktok.py b/gallery_dl/extractor/tiktok.py index 870a534d..a4c71712 100644 --- a/gallery_dl/extractor/tiktok.py +++ b/gallery_dl/extractor/tiktok.py @@ -25,6 +25,7 @@ class TiktokExtractor(Extractor): def _init(self): self.audio = self.config("audio", True) self.video = self.config("videos", True) + self.cover = self.config("covers", False) def items(self): for tiktok_url in self.urls(): @@ -73,8 +74,11 @@ class TiktokExtractor(Extractor): elif url := self._extract_audio(post): yield Message.Url, url, post - elif self.video and "video" in post: - ytdl_media = "video" + elif "video" in post: + if self.video: + ytdl_media = "video" + if self.cover and (url := self._extract_cover(post, "video")): + yield Message.Url, url, post else: self.log.info("%s: Skipping post", tiktok_url) @@ -143,6 +147,30 @@ class TiktokExtractor(Extractor): post["extension"] = "mp3" return url + def _extract_cover(self, post, type): + media = post[type] + + for cover_id in ("thumbnail", "cover", "originCover", "dynamicCover"): + if url := media.get(cover_id): + break + else: + return + + text.nameext_from_url(url, post) + post.update({ + "type" : "cover", + "extension": "jpg", + "image" : url, + "title" : post["desc"] or f"TikTok {type} cover #{post['id']}", + "duration" : media.get("duration"), + "num" : 0, + "img_id" : "", + "cover_id" : cover_id, + "width" : 0, + "height" : 0, + }) + return url + def _check_status_code(self, detail, url): status = detail.get("statusCode") if not status: diff --git a/test/results/tiktok.py b/test/results/tiktok.py index 21eb0f9b..c2e99b84 100644 --- a/test/results/tiktok.py +++ b/test/results/tiktok.py @@ -111,6 +111,16 @@ __tests__ = ( "#options" : {"videos": True, "audio": True}, }, +{ + "#url" : "https://www.tiktok.com/@memezar/video/7449708266168274208", + "#comment" : "video post cover image", + "#class" : tiktok.TiktokPostExtractor, + "#pattern" : r"https://p19-common-sign-useastred.tiktokcdn-eu.com/tos-useast2a-p-0037-euttp/o4rVzhI1bABhooAaEqtCAYGi6nijIsDib8NGfC~tplv-tiktokx-origin.image\?dr=10395&x-expires=\d+&x-signature=.+", + "#options" : {"videos": False, "covers": True}, + + +}, + { "#url" : "https://www.tiktok.com/@memezar/photo/7449708266168274208", "#comment" : "Video post as a /photo/ link",