From df65123abe3f1ceab4df09c0e89aff42c0d01ef2 Mon Sep 17 00:00:00 2001 From: Andy Huang Date: Tue, 23 Sep 2025 00:00:02 +0800 Subject: [PATCH] test: cover AcFun playlist extraction --- test/test_acfun.py | 74 +++++++++++++++++++++++++++++++++++++++ yt_dlp/extractor/acfun.py | 27 ++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 test/test_acfun.py diff --git a/test/test_acfun.py b/test/test_acfun.py new file mode 100644 index 0000000000..1b98b93600 --- /dev/null +++ b/test/test_acfun.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 + +# Allow direct execution +import json +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from test.helper import FakeYDL + +from yt_dlp.extractor.acfun import AcFunVideoIE + + +class AcFunPlaylistTest(unittest.TestCase): + def setUp(self): + self.ie = AcFunVideoIE() + self.ie.set_downloader(FakeYDL({'noplaylist': False})) + + def test_playlist_entries_are_generated_for_multi_part_videos(self): + video_info = { + 'title': 'Sample Playlist', + 'description': 'Sample description', + 'coverUrl': 'https://example.com/thumb.jpg', + 'user': { + 'name': 'Uploader Name', + 'href': 'uploader-id', + }, + 'videoList': [ + { + 'id': 'part-1', + 'title': 'Episode 1', + }, + { + 'id': 'part-2', + 'title': 'Episode 2', + }, + ], + 'currentVideoInfo': { + 'id': 'part-1', + }, + } + webpage = f'' + self.ie._download_webpage = lambda url, video_id: webpage + + result = self.ie._real_extract('https://www.acfun.cn/v/ac12345?foo=bar') + + self.assertEqual(result['_type'], 'playlist') + self.assertEqual(result['id'], '12345') + self.assertEqual(result['title'], 'Sample Playlist') + self.assertEqual(result['description'], 'Sample description') + self.assertEqual(result['uploader'], 'Uploader Name') + self.assertEqual(result['uploader_id'], 'uploader-id') + self.assertEqual( + [entry['url'] for entry in result['entries']], + [ + 'https://www.acfun.cn/v/ac12345?foo=bar', + 'https://www.acfun.cn/v/ac12345_2?foo=bar', + ], + ) + self.assertEqual( + [entry['id'] for entry in result['entries']], + ['12345', '12345_2'], + ) + self.assertEqual( + [entry['title'] for entry in result['entries']], + ['Episode 1', 'Episode 2'], + ) + self.assertTrue(all(entry['ie_key'] == 'AcFunVideo' for entry in result['entries'])) + + +if __name__ == '__main__': + unittest.main() diff --git a/yt_dlp/extractor/acfun.py b/yt_dlp/extractor/acfun.py index 28559baecf..574e60b50a 100644 --- a/yt_dlp/extractor/acfun.py +++ b/yt_dlp/extractor/acfun.py @@ -7,6 +7,7 @@ from ..utils import ( parse_qs, str_or_none, traverse_obj, + update_url_query, ) @@ -78,6 +79,13 @@ class AcFunVideoIE(AcFunVideoBaseIE): 'thumbnail': r're:^https?://.*\.(jpg|jpeg)', 'description': 'md5:67583aaf3a0f933bd606bc8a2d3ebb17', }, + }, { + 'url': 'https://www.acfun.cn/v/ac35468952', + 'info_dict': { + 'id': '35468952', + 'title': 'regex:.+', + }, + 'playlist_mincount': 2, }] def _real_extract(self, url): @@ -89,6 +97,25 @@ class AcFunVideoIE(AcFunVideoBaseIE): title = json_all.get('title') video_list = json_all.get('videoList') or [] video_internal_id = traverse_obj(json_all, ('currentVideoInfo', 'id')) + playlist_id = video_id.partition('_')[0] + if video_id == playlist_id and len(video_list) > 1 and self._yes_playlist(playlist_id, video_id): + entries = [] + query = parse_qs(url) + for idx, part_video_info in enumerate(video_list, start=1): + part_suffix = '' if idx == 1 else f'_{idx}' + part_id = f'{playlist_id}{part_suffix}' + entry_url = update_url_query(f'https://www.acfun.cn/v/ac{part_id}', query) + entries.append(self.url_result( + entry_url, ie=self.ie_key(), video_id=part_id, + video_title=traverse_obj(part_video_info, 'title'))) + + return self.playlist_result( + entries, playlist_id, title, + description=json_all.get('description'), + thumbnail=json_all.get('coverUrl'), + uploader=traverse_obj(json_all, ('user', 'name')), + uploader_id=traverse_obj(json_all, ('user', 'href'))) + if video_internal_id and len(video_list) > 1: part_idx, part_video_info = next( (idx + 1, v) for (idx, v) in enumerate(video_list)