diff --git a/backend/app/utils/url_parser.py b/backend/app/utils/url_parser.py index 8f76a169..3cbf38e2 100644 --- a/backend/app/utils/url_parser.py +++ b/backend/app/utils/url_parser.py @@ -23,8 +23,8 @@ def extract_video_id(url: str, platform: str) -> Optional[str]: return f"BV{match.group(1)}" if match else None elif platform == "youtube": - # 匹配 v=xxxxx 或 youtu.be/xxxxx,ID 长度通常为 11 - match = re.search(r"(?:v=|youtu\.be/)([0-9A-Za-z_-]{11})", url) + # 匹配 v=xxxxx、youtu.be/xxxxx 或 shorts/xxxxx,ID 长度通常为 11 + match = re.search(r"(?:v=|youtu\.be/|shorts/)([0-9A-Za-z_-]{11})", url) return match.group(1) if match else None elif platform == "douyin": diff --git a/backend/app/validators/video_url_validator.py b/backend/app/validators/video_url_validator.py index 205f7867..cf0a240d 100644 --- a/backend/app/validators/video_url_validator.py +++ b/backend/app/validators/video_url_validator.py @@ -4,7 +4,7 @@ SUPPORTED_PLATFORMS = { "bilibili": r"(https?://)?(www\.)?bilibili\.com/video/[a-zA-Z0-9]+", - "youtube": r"(https?://)?(www\.)?(youtube\.com/watch\?v=|youtu\.be/)[\w\-]+", + "youtube": r"(https?://)?(www\.)?(youtube\.com/(watch\?v=|shorts/)|youtu\.be/)[\w\-]+", "douyin": "douyin", "kuaishou": "kuaishou" } diff --git a/backend/tests/test_video_url_support.py b/backend/tests/test_video_url_support.py new file mode 100644 index 00000000..b4021278 --- /dev/null +++ b/backend/tests/test_video_url_support.py @@ -0,0 +1,50 @@ +import importlib.util +import pathlib +import unittest + + +ROOT = pathlib.Path(__file__).resolve().parents[1] + + +def _load_module(name, relative_path): + module_path = ROOT / relative_path + spec = importlib.util.spec_from_file_location(name, module_path) + if spec is None or spec.loader is None: + raise ImportError(f"{name} module spec not found") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +url_parser = _load_module("url_parser", pathlib.Path("app") / "utils" / "url_parser.py") +video_url_validator = _load_module( + "video_url_validator", + pathlib.Path("app") / "validators" / "video_url_validator.py", +) + + +class TestVideoUrlSupport(unittest.TestCase): + def test_extract_youtube_video_id_from_supported_url_shapes(self): + expected_id = "dQw4w9WgXcQ" + + cases = [ + f"https://www.youtube.com/watch?v={expected_id}", + f"https://youtu.be/{expected_id}", + f"https://www.youtube.com/shorts/{expected_id}", + ] + + for url in cases: + with self.subTest(url=url): + self.assertEqual( + url_parser.extract_video_id(url, "youtube"), + expected_id, + ) + + def test_accepts_youtube_shorts_url(self): + url = "https://www.youtube.com/shorts/dQw4w9WgXcQ" + + self.assertTrue(video_url_validator.is_supported_video_url(url)) + + +if __name__ == "__main__": + unittest.main()