parsehub 2.3.16__tar.gz → 2.3.17__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {parsehub-2.3.16/src/parsehub.egg-info → parsehub-2.3.17}/PKG-INFO +1 -1
- {parsehub-2.3.16 → parsehub-2.3.17}/pyproject.toml +1 -1
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/parsers/base/ytdlp.py +1 -1
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/parsers/parser/instagram.py +3 -2
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/parsers/parser/zuiyou.py +1 -1
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/provider_api/instagram.py +45 -2
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/provider_api/zuiyou.py +10 -2
- {parsehub-2.3.16 → parsehub-2.3.17/src/parsehub.egg-info}/PKG-INFO +1 -1
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub.egg-info/SOURCES.txt +3 -1
- parsehub-2.3.17/test/test_instagram_duration_offline.py +134 -0
- parsehub-2.3.17/test/test_zuiyou_duration_offline.py +69 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/LICENSE +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/README.md +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/setup.cfg +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/__init__.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/__init__.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/cli.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/cli_config.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/config/__init__.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/config/config.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/errors.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/parsers/__init__.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/parsers/base/__init__.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/parsers/base/base.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/parsers/parser/__init__.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/parsers/parser/bangumi.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/parsers/parser/bilibili.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/parsers/parser/coolapk.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/parsers/parser/douban.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/parsers/parser/douyin.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/parsers/parser/facebook.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/parsers/parser/kuaishou.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/parsers/parser/medium.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/parsers/parser/pipix.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/parsers/parser/snapchat.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/parsers/parser/threads.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/parsers/parser/tieba.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/parsers/parser/tiktok.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/parsers/parser/toutiao.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/parsers/parser/twitter.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/parsers/parser/weibo.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/parsers/parser/weixin.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/parsers/parser/wxchannels.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/parsers/parser/xhs.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/parsers/parser/xiaoheihe.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/parsers/parser/youtube.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/parsers/parser/zhihu.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/provider_api/__init__.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/provider_api/bangumi.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/provider_api/bilibili.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/provider_api/coolapk.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/provider_api/douban.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/provider_api/douyin.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/provider_api/kuaishou.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/provider_api/medium.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/provider_api/pipix.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/provider_api/threads.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/provider_api/tieba.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/provider_api/tiktok.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/provider_api/toutiao.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/provider_api/twitter.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/provider_api/weibo.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/provider_api/weixin.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/provider_api/wxchannels.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/provider_api/xhs.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/provider_api/xiaoheihe.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/provider_api/zhihu.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/types/__init__.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/types/callback.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/types/media_file.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/types/media_ref.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/types/platform.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/types/post.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/types/result.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/utils/downloader.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/utils/fallback.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/utils/helpers.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub/utils/media_info.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub.egg-info/dependency_links.txt +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub.egg-info/entry_points.txt +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub.egg-info/requires.txt +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/src/parsehub.egg-info/top_level.txt +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/test/test_bilibili_offline.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/test/test_cli.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/test/test_cli_config.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/test/test_core_offline.py +0 -0
- {parsehub-2.3.16 → parsehub-2.3.17}/test/test_downloader.py +0 -0
|
@@ -411,7 +411,7 @@ class YtParser(BaseParser, register=False):
|
|
|
411
411
|
title = dl["title"]
|
|
412
412
|
duration = dl.get("duration", 0)
|
|
413
413
|
thumbnail = dl.get("thumbnail")
|
|
414
|
-
description = dl
|
|
414
|
+
description = dl.get("description", "")
|
|
415
415
|
width = dl.get("width", 0)
|
|
416
416
|
height = dl.get("height", 0)
|
|
417
417
|
return YtVideoInfo(
|
|
@@ -71,11 +71,12 @@ class InstagramParser(BaseParser):
|
|
|
71
71
|
title=post.title,
|
|
72
72
|
content=post.caption,
|
|
73
73
|
video=VideoRef(
|
|
74
|
-
|
|
74
|
+
# yt-dlp 的 info.url 是帖子页,provider 的 video_url 才是媒体直链
|
|
75
|
+
url=item.video_url or info.url,
|
|
75
76
|
thumb_url=item.display_url or info.thumbnail,
|
|
76
77
|
width=info.width,
|
|
77
78
|
height=info.height,
|
|
78
|
-
duration=int(
|
|
79
|
+
duration=int(item.duration or info.duration),
|
|
79
80
|
),
|
|
80
81
|
)
|
|
81
82
|
if not item.video_url:
|
|
@@ -15,7 +15,7 @@ class ZuiYouParser(BaseParser):
|
|
|
15
15
|
return MultimediaParseResult(
|
|
16
16
|
content=zy.content,
|
|
17
17
|
media=[
|
|
18
|
-
VideoRef(url=i.url, thumb_url=i.thumb_url)
|
|
18
|
+
VideoRef(url=i.url, thumb_url=i.thumb_url, duration=i.duration)
|
|
19
19
|
if i.type == MediaType.VIDEO
|
|
20
20
|
else ImageRef(url=i.url, thumb_url=i.thumb_url)
|
|
21
21
|
for i in zy.media
|
|
@@ -116,11 +116,54 @@ class InstagramAPI:
|
|
|
116
116
|
cookies=self.cookie or None,
|
|
117
117
|
) as session:
|
|
118
118
|
if self.cookie:
|
|
119
|
-
|
|
120
|
-
|
|
119
|
+
post = await self._get_cookie_post(session, shortcode)
|
|
120
|
+
else:
|
|
121
|
+
post = await self._get_logged_out_post(session, shortcode)
|
|
122
|
+
return await self._fill_missing_video_fields(session, post, shortcode)
|
|
121
123
|
except requests.RequestsError as exc:
|
|
122
124
|
raise InstagramAPIError(f"请求 Instagram 失败: {exc}") from exc
|
|
123
125
|
|
|
126
|
+
async def _fill_missing_video_fields(
|
|
127
|
+
self,
|
|
128
|
+
session: requests.AsyncSession,
|
|
129
|
+
post: InstagramPost,
|
|
130
|
+
shortcode: str,
|
|
131
|
+
) -> InstagramPost:
|
|
132
|
+
"""用 embed 补齐 v1 / GraphQL 缺失的视频直链和时长。"""
|
|
133
|
+
if not any(
|
|
134
|
+
(post.type is InstagramMediaType.VIDEO or item.is_video)
|
|
135
|
+
and (not item.video_url or item.duration <= 0)
|
|
136
|
+
for item in post.media
|
|
137
|
+
):
|
|
138
|
+
return post
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
node = await self._fetch_embed(session, shortcode)
|
|
142
|
+
except requests.RequestsError:
|
|
143
|
+
return post
|
|
144
|
+
if node is None:
|
|
145
|
+
return post
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
embed = InstagramPost.from_graph(node)
|
|
149
|
+
except InstagramAPIError:
|
|
150
|
+
return post
|
|
151
|
+
|
|
152
|
+
for index, item in enumerate(post.media):
|
|
153
|
+
if post.type is not InstagramMediaType.VIDEO and not item.is_video:
|
|
154
|
+
continue
|
|
155
|
+
source = embed.media[index] if index < len(embed.media) else None
|
|
156
|
+
if source is None and len(post.media) == 1 and len(embed.media) == 1:
|
|
157
|
+
source = embed.media[0]
|
|
158
|
+
if source is None:
|
|
159
|
+
continue
|
|
160
|
+
if not item.video_url and source.video_url:
|
|
161
|
+
item.video_url = source.video_url
|
|
162
|
+
item.is_video = True
|
|
163
|
+
if item.duration <= 0 and source.duration > 0:
|
|
164
|
+
item.duration = source.duration
|
|
165
|
+
return post
|
|
166
|
+
|
|
124
167
|
async def _get_logged_out_post(self, session: requests.AsyncSession, shortcode: str) -> InstagramPost:
|
|
125
168
|
detail = "未返回帖子数据"
|
|
126
169
|
media_id = str(self.shortcode_to_media_id(shortcode))
|
|
@@ -15,6 +15,7 @@ class Media:
|
|
|
15
15
|
url: str
|
|
16
16
|
thumb_url: str
|
|
17
17
|
type: MediaType
|
|
18
|
+
duration: int = 0
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
@dataclass
|
|
@@ -34,8 +35,15 @@ class ZuiYouPost:
|
|
|
34
35
|
id_ = img["id"]
|
|
35
36
|
img_url = list(img["urls"].values())[-1]["urls"][-1]
|
|
36
37
|
if img.get("video"):
|
|
37
|
-
|
|
38
|
-
media.append(
|
|
38
|
+
video = videos[str(id_)]
|
|
39
|
+
media.append(
|
|
40
|
+
Media(
|
|
41
|
+
url=video["url"],
|
|
42
|
+
thumb_url=img_url,
|
|
43
|
+
type=MediaType.VIDEO,
|
|
44
|
+
duration=int(video.get("dur") or 0),
|
|
45
|
+
)
|
|
46
|
+
)
|
|
39
47
|
continue
|
|
40
48
|
media.append(Media(url=img_url, thumb_url=img_url, type=MediaType.PHOTO))
|
|
41
49
|
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from unittest.mock import AsyncMock, patch
|
|
3
|
+
|
|
4
|
+
from parsehub.parsers.base.ytdlp import YtParser, YtVideoInfo
|
|
5
|
+
from parsehub.parsers.parser.instagram import InstagramParser
|
|
6
|
+
from parsehub.provider_api.instagram import InstagramAPI, InstagramMedia, InstagramMediaType, InstagramPost
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestInstagramParserDuration(unittest.IsolatedAsyncioTestCase):
|
|
10
|
+
async def test_video_prefers_provider_url_and_duration(self):
|
|
11
|
+
post = InstagramPost(
|
|
12
|
+
shortcode="abc",
|
|
13
|
+
type=InstagramMediaType.VIDEO,
|
|
14
|
+
media=[
|
|
15
|
+
InstagramMedia(
|
|
16
|
+
is_video=True,
|
|
17
|
+
display_url="https://cdn.example/thumb.jpg",
|
|
18
|
+
video_url="https://cdn.example/v.mp4",
|
|
19
|
+
duration=18.018,
|
|
20
|
+
width=1920,
|
|
21
|
+
height=1080,
|
|
22
|
+
)
|
|
23
|
+
],
|
|
24
|
+
)
|
|
25
|
+
info = YtVideoInfo(
|
|
26
|
+
title="t",
|
|
27
|
+
description="",
|
|
28
|
+
thumbnail=None,
|
|
29
|
+
url="https://www.instagram.com/p/abc/",
|
|
30
|
+
info_json={},
|
|
31
|
+
duration=0,
|
|
32
|
+
width=1920,
|
|
33
|
+
height=1080,
|
|
34
|
+
)
|
|
35
|
+
parser = InstagramParser()
|
|
36
|
+
|
|
37
|
+
with (
|
|
38
|
+
patch.object(InstagramParser, "_parse", AsyncMock(return_value=post)),
|
|
39
|
+
patch(
|
|
40
|
+
"parsehub.parsers.parser.instagram.InstagramYtParse._parse",
|
|
41
|
+
AsyncMock(return_value=info),
|
|
42
|
+
),
|
|
43
|
+
):
|
|
44
|
+
result = await parser._do_parse("https://www.instagram.com/reel/abc/")
|
|
45
|
+
|
|
46
|
+
self.assertEqual(result.media.url, "https://cdn.example/v.mp4")
|
|
47
|
+
self.assertEqual(result.media.duration, 18)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class TestInstagramAPIDuration(unittest.IsolatedAsyncioTestCase):
|
|
51
|
+
@staticmethod
|
|
52
|
+
def _embed_node() -> dict:
|
|
53
|
+
return {
|
|
54
|
+
"__typename": "GraphVideo",
|
|
55
|
+
"shortcode": "abc",
|
|
56
|
+
"display_url": "https://cdn.example/thumb.jpg",
|
|
57
|
+
"video_url": "https://cdn.example/v.mp4",
|
|
58
|
+
"video_duration": 30.933,
|
|
59
|
+
"dimensions": {"width": 1080, "height": 1920},
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async def test_fills_duration_from_embed(self):
|
|
63
|
+
api = InstagramAPI()
|
|
64
|
+
post = InstagramPost(
|
|
65
|
+
shortcode="abc",
|
|
66
|
+
type=InstagramMediaType.VIDEO,
|
|
67
|
+
media=[
|
|
68
|
+
InstagramMedia(
|
|
69
|
+
is_video=True,
|
|
70
|
+
display_url="https://cdn.example/thumb.jpg",
|
|
71
|
+
video_url="https://cdn.example/v.mp4",
|
|
72
|
+
duration=0,
|
|
73
|
+
)
|
|
74
|
+
],
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
with patch.object(InstagramAPI, "_fetch_embed", AsyncMock(return_value=self._embed_node())):
|
|
78
|
+
result = await api._fill_missing_video_fields(object(), post, "abc")
|
|
79
|
+
|
|
80
|
+
self.assertEqual(result.media[0].duration, 30.933)
|
|
81
|
+
|
|
82
|
+
async def test_fills_missing_video_url_from_embed(self):
|
|
83
|
+
api = InstagramAPI()
|
|
84
|
+
post = InstagramPost(
|
|
85
|
+
shortcode="abc",
|
|
86
|
+
type=InstagramMediaType.VIDEO,
|
|
87
|
+
media=[
|
|
88
|
+
InstagramMedia(
|
|
89
|
+
is_video=False,
|
|
90
|
+
display_url="https://cdn.example/thumb.jpg",
|
|
91
|
+
duration=0,
|
|
92
|
+
)
|
|
93
|
+
],
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
with patch.object(InstagramAPI, "_fetch_embed", AsyncMock(return_value=self._embed_node())):
|
|
97
|
+
result = await api._fill_missing_video_fields(object(), post, "abc")
|
|
98
|
+
|
|
99
|
+
self.assertEqual(result.media[0].video_url, "https://cdn.example/v.mp4")
|
|
100
|
+
self.assertTrue(result.media[0].is_video)
|
|
101
|
+
self.assertEqual(result.media[0].duration, 30.933)
|
|
102
|
+
|
|
103
|
+
async def test_skips_embed_when_complete(self):
|
|
104
|
+
api = InstagramAPI()
|
|
105
|
+
post = InstagramPost(
|
|
106
|
+
shortcode="abc",
|
|
107
|
+
type=InstagramMediaType.VIDEO,
|
|
108
|
+
media=[
|
|
109
|
+
InstagramMedia(
|
|
110
|
+
is_video=True,
|
|
111
|
+
display_url="https://cdn.example/thumb.jpg",
|
|
112
|
+
video_url="https://cdn.example/v.mp4",
|
|
113
|
+
duration=18,
|
|
114
|
+
)
|
|
115
|
+
],
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
with patch.object(InstagramAPI, "_fetch_embed", AsyncMock()) as fetch:
|
|
119
|
+
await api._fill_missing_video_fields(object(), post, "abc")
|
|
120
|
+
|
|
121
|
+
fetch.assert_not_awaited()
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class _FakeYtParser(YtParser, register=False):
|
|
125
|
+
async def _extract_info(self, url: str) -> dict:
|
|
126
|
+
return {"title": "t", "duration": 10, "webpage_url": url}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class TestYtParserDescription(unittest.IsolatedAsyncioTestCase):
|
|
130
|
+
async def test_missing_description_does_not_raise(self):
|
|
131
|
+
info = await _FakeYtParser()._parse("https://example.com/v")
|
|
132
|
+
|
|
133
|
+
self.assertEqual(info.description, "")
|
|
134
|
+
self.assertEqual(info.duration, 10)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from unittest.mock import AsyncMock, patch
|
|
3
|
+
|
|
4
|
+
from parsehub.parsers.parser.zuiyou import ZuiYouParser
|
|
5
|
+
from parsehub.provider_api.zuiyou import Media, MediaType, ZuiYou, ZuiYouPost
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TestZuiyouDuration(unittest.TestCase):
|
|
9
|
+
def test_parse_reads_video_dur(self):
|
|
10
|
+
payload = {
|
|
11
|
+
"data": {
|
|
12
|
+
"post": {
|
|
13
|
+
"content": "x",
|
|
14
|
+
"imgs": [
|
|
15
|
+
{
|
|
16
|
+
"id": 1,
|
|
17
|
+
"urls": {"a": {"urls": ["https://cdn.example/thumb.jpg"]}},
|
|
18
|
+
"video": True,
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"videos": {"1": {"dur": 5, "url": "https://cdn.example/v.mp4"}},
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
post = ZuiYouPost.parse(payload)
|
|
27
|
+
|
|
28
|
+
self.assertEqual(post.media[0].type, MediaType.VIDEO)
|
|
29
|
+
self.assertEqual(post.media[0].duration, 5)
|
|
30
|
+
self.assertEqual(post.media[0].url, "https://cdn.example/v.mp4")
|
|
31
|
+
|
|
32
|
+
def test_photo_duration_defaults_to_zero(self):
|
|
33
|
+
payload = {
|
|
34
|
+
"data": {
|
|
35
|
+
"post": {
|
|
36
|
+
"imgs": [
|
|
37
|
+
{
|
|
38
|
+
"id": 1,
|
|
39
|
+
"urls": {"a": {"urls": ["https://cdn.example/p.jpg"]}},
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
post = ZuiYouPost.parse(payload)
|
|
47
|
+
|
|
48
|
+
self.assertEqual(post.media[0].duration, 0)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class TestZuiyouParserDuration(unittest.IsolatedAsyncioTestCase):
|
|
52
|
+
async def test_parser_sets_video_duration(self):
|
|
53
|
+
zy = ZuiYouPost(
|
|
54
|
+
content="x",
|
|
55
|
+
media=[
|
|
56
|
+
Media(
|
|
57
|
+
url="https://cdn.example/v.mp4",
|
|
58
|
+
thumb_url="https://cdn.example/thumb.jpg",
|
|
59
|
+
type=MediaType.VIDEO,
|
|
60
|
+
duration=19,
|
|
61
|
+
)
|
|
62
|
+
],
|
|
63
|
+
raw={},
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
with patch.object(ZuiYou, "parse", AsyncMock(return_value=zy)):
|
|
67
|
+
result = await ZuiYouParser()._do_parse("https://s.izuiyou.com/s/abc")
|
|
68
|
+
|
|
69
|
+
self.assertEqual(result.media[0].duration, 19)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|