• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

feeluown / feeluown-netease / 8986919738

07 May 2024 02:11PM UTC coverage: 22.263% (-0.06%) from 22.319%
8986919738

push

github

cosven
support toplist

4 of 24 new or added lines in 4 files covered. (16.67%)

1 existing line in 1 file now uncovered.

362 of 1626 relevant lines covered (22.26%)

0.22 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

65.12
/fuo_netease/schemas.py
1
"""
2

3
关于命名
4
~~~~~~~~~~~~~~~
5
V2{X}Schema 返回的 model 都是 v2 版本的 Model 实例,也就是从 feeluown.library
6
模块导入进来的 Model 类。
7

8
{X}SchemaForV3 的数据来源都是 api uri_v3 接口获取的数据。
9
"""
10

11
import logging
1✔
12
from datetime import datetime
1✔
13

14
from marshmallow import Schema, post_load, fields, EXCLUDE
1✔
15

16
from feeluown.library import (
1✔
17
    SongModel, BriefAlbumModel, BriefArtistModel, ModelState, BriefSongModel,
18
    VideoModel, AlbumModel, ArtistModel, PlaylistModel, BriefUserModel,
19
    SimpleSearchResult, MediaFlags, AlbumType
20
)
21
from feeluown.media import Quality, MediaType, Media
1✔
22

23
logger = logging.getLogger(__name__)
1✔
24

25

26
class BaseSchema(Schema):
1✔
27
    source = fields.Str(missing='netease')
1✔
28

29
    class Meta:
1✔
30
        unknown = EXCLUDE
1✔
31

32

33
Schema = BaseSchema
1✔
34
Unknown = 'Unknown'
1✔
35
DjradioPrefix = 'djradio_'
1✔
36

37

38
def create_model(model_cls, data, fields_to_cache=None):
1✔
39
    """
40
    maybe this function should be provided by feeluown
41

42
    :param fields_to_cache: list of fields name to be cached
43
    """
44
    if fields_to_cache is not None:
1✔
45
        cache_data = {}
1✔
46
        for field in fields_to_cache:
1✔
47
            value = data.pop(field)
1✔
48
            if value is not None:
1✔
49
                cache_data[field] = value
1✔
50
        model = model_cls(**data)
1✔
51
        for field, value in cache_data.items():
1✔
52
            model.cache_set(field, value)
1✔
53
    else:
54
        model = model_cls(**data)
×
55
    return model
1✔
56

57

58
class V2MvSchema(Schema):
1✔
59
    identifier = fields.Int(required=True, data_key='id')
1✔
60
    title = fields.Str(required=True, data_key='name')
1✔
61
    cover = fields.Str(required=True)
1✔
62
    brs = fields.Dict(required=True)
1✔
63
    artists = fields.List(fields.Nested('V2BriefArtistSchema'))
1✔
64
    duration = fields.Int(required=True)
1✔
65

66
    @post_load
1✔
67
    def create_model(self, data, **kwargs):
1✔
68
        brs = data.pop('brs')
×
69
        q_media_mapping = {}
×
70
        for q, url in brs.items():
×
71
            media = Media(url, type_=MediaType.video)
×
72
            if q == '1080':
×
73
                quality = Quality.Video.fhd
×
74
            elif q == '720':
×
75
                quality = Quality.Video.hd
×
76
            elif q == '480':
×
77
                quality = Quality.Video.sd
×
78
            elif q == '240':
×
79
                quality = Quality.Video.ld
×
80
            else:
81
                logger.warning('There exists another quality:%s mv.', q)
×
82
                quality = Quality.Video.sd
×
83
            q_media_mapping[quality] = media
×
84
        data['q_media_mapping'] = q_media_mapping
×
85
        data['identifier'] = 'mv_' + str(data['identifier'])
×
86
        return create_model(VideoModel, data, ['q_media_mapping'])
×
87

88

89
class V2BriefAlbumSchema(Schema):
1✔
90
    identifier = fields.Int(required=True, data_key='id')
1✔
91
    name = fields.Str(required=True, allow_none=True)
1✔
92
    # cover = fields.Str(data_key='picUrl', allow_none=True)
93
    artist = fields.Dict()
1✔
94

95
    @post_load
1✔
96
    def create_v2_model(self, data, **kwargs):
1✔
97
        if data['name'] is None:
1✔
98
            data['name'] = Unknown
×
99
        if 'artist' in data:
1✔
100
            artist = data.pop('artist')
×
101
            data['artists_name'] = artist['name']
×
102
        return BriefAlbumModel(**data)
1✔
103

104

105
class V2BriefArtistSchema(Schema):
1✔
106
    identifier = fields.Int(required=True, data_key='id')
1✔
107
    name = fields.Str(required=True, allow_none=True)
1✔
108

109
    # cover = fields.Str(data_key='picUrl', allow_none=True)
110
    # songs = fields.List(fields.Nested('V2SongSchema'))
111

112
    @post_load
1✔
113
    def create_v2_model(self, data, **kwargs):
1✔
114
        if data['name'] is None:
1✔
115
            data['name'] = Unknown
×
116
        return BriefArtistModel(**data)
1✔
117

118

119
class V2SongSchema(Schema):
1✔
120
    identifier = fields.Int(required=True, data_key='id')
1✔
121
    title = fields.Str(required=True, data_key='name', allow_none=True)
1✔
122
    duration = fields.Float(required=True)
1✔
123
    album = fields.Nested('V2BriefAlbumSchema')
1✔
124
    artists = fields.List(fields.Nested('V2BriefArtistSchema'))
1✔
125

126
    mv_id = fields.Int(required=True, data_key='mvid')
1✔
127
    comment_thread_id = fields.Str(data_key='commentThreadId',
1✔
128
                                   allow_none=True, missing=None)
129
    fee = fields.Int(required=True)
1✔
130

131
    @post_load
1✔
132
    def create_v2_model(self, data, **kwargs):
1✔
133
        # https://github.com/feeluown/FeelUOwn/issues/499
134
        if data['title'] is None:
×
135
            data['title'] = Unknown
×
136

137
        # 0: 免费或无版权
138
        # 1: VIP 歌曲
139
        # 4: 购买专辑
140
        # 8: 非会员可免费播放低音质,会员可播放高音质及下载
141
        fee = data.pop('fee')
×
142
        if fee == 1:
×
143
            data['media_flags'] = MediaFlags.vip
×
144
        elif fee == 4:
×
145
            data['media_flags'] = MediaFlags.pay
×
146
        return create_model(SongModel, data, ['mv_id', 'comment_thread_id'])
×
147

148

149
class V2SongSchemaForV3(Schema):
1✔
150
    identifier = fields.Int(required=True, data_key='id')
1✔
151
    title = fields.Str(required=True, data_key='name')
1✔
152
    duration = fields.Float(required=True, data_key='dt')
1✔
153
    album = fields.Nested('V2BriefAlbumSchema', data_key='al', allow_none=True)
1✔
154
    artists = fields.List(fields.Nested('V2BriefArtistSchema'),
1✔
155
                          data_key='ar',
156
                          allow_none=True)
157

158
    mv_id = fields.Int(required=True, data_key='mv')
1✔
159

160
    @post_load
1✔
161
    def create_v2_model(self, data, **kwargs):
1✔
162
        data['artists'] = data['artists'] or []
1✔
163
        return create_model(SongModel, data, ['mv_id'])
1✔
164

165

166
class V2AlbumSchema(Schema):
1✔
167
    identifier = fields.Int(required=True, data_key='id')
1✔
168
    name = fields.Str(required=True)
1✔
169
    cover = fields.Str(data_key='picUrl', allow_none=True)
1✔
170
    artists = fields.List(fields.Nested('V2BriefArtistSchema'))
1✔
171
    # 收藏和搜索接口返回的 album 数据中的 songs 为 None
172
    songs = fields.List(fields.Nested('V2SongSchema'), allow_none=True)
1✔
173
    song_count = fields.Int(data_key='size')
1✔
174

175
    # Description is fetched seperatelly by `album_desc` API.
176
    description = fields.Str(missing='')
1✔
177
    released = fields.Int(data_key='publishTime', missing=0)
1✔
178

179
    # Single/专辑/"EP/Single"/合集/专辑/精选集
180
    type = fields.Str(required=True, data_key='type')
1✔
181

182
    @post_load
1✔
183
    def create_v2_model(self, data, **kwargs):
1✔
184
        released = data['released']
×
185
        if released:
×
186
            released_date = datetime.fromtimestamp(released / 1000)
×
187
            released_str = released_date.strftime('%Y-%m-%d')
×
188
            data['released'] = released_str
×
189
        data['songs'] = data['songs'] or []
×
NEW
190
        type_str = data.pop('type')
×
NEW
191
        if type_str == 'Single':
×
NEW
192
            type_ = AlbumType.single
×
NEW
193
        elif type_str == 'EP/Single':
×
NEW
194
            type_ = AlbumType.ep
×
NEW
195
        elif type_str == '合集':
×
NEW
196
            type_ = AlbumType.compilation
×
NEW
197
        elif type_str == '精选集':
×
NEW
198
            type_ = AlbumType.retrospective
×
199
        else:
NEW
200
            type_ = AlbumType.standard
×
NEW
201
        data['type_'] = type_
×
UNCOV
202
        return AlbumModel(**data)
×
203

204

205
class V2ArtistSchema(Schema):
1✔
206
    identifier = fields.Int(required=True, data_key='id')
1✔
207
    name = fields.Str()
1✔
208
    pic_url = fields.Str(data_key='picUrl', allow_none=True)
1✔
209
    hot_songs = fields.List(fields.Nested('V2SongSchema'), data_key='songs')
1✔
210

211
    # TODO:
212
    aliases = fields.List(fields.Str(), missing=[])
1✔
213

214
    # Description is fetched seperatelly by `artist_desc` API.
215
    description = fields.Str(missing='')
1✔
216

217
    @post_load
1✔
218
    def create_v2_model(self, data, **kwargs):
1✔
219
        return ArtistModel(**data)
×
220

221

222
class NAlbumSchemaV3(Schema):
1✔
223
    # 如果 album 无效,id 则为 0
224
    # 只有当 album 无效时,name 才可能为 None
225
    identifier = fields.Int(required=True, data_key='id')
1✔
226
    name = fields.Str(required=True, allow_none=True)
1✔
227

228
    @post_load
1✔
229
    def create_model(self, data, **kwargs):
1✔
230
        album = BriefAlbumModel(**data)
×
231
        if album.identifier == 0:
×
232
            album.state = ModelState.not_exists
×
233
            album.name = ''
×
234
        return album
×
235

236

237
class NeteaseDjradioSchema(Schema):
1✔
238
    identifier = fields.Int(required=True, data_key='id')
1✔
239
    name = fields.Str(required=True)
1✔
240
    description = fields.Str(data_key='desc', required=False)
1✔
241
    cover = fields.Str(required=False, data_key='picUrl')
1✔
242

243
    @post_load
1✔
244
    def create_model(self, data, **kwargs):
1✔
245
        identifier = data['identifier']
×
246
        data['identifier'] = f'{DjradioPrefix}{identifier}'
×
247
        # TODO: set creator properly.
248
        return PlaylistModel(**data, creator=None)
×
249

250

251
class NDjradioSchema(Schema):
1✔
252
    # identifier = fields.Int(required=True, data_key='id')
253
    # title = fields.Str(required=True, data_key='name')
254
    main_song = fields.Dict(required=True, data_key='mainSong')
1✔
255
    # cover = fields.Str(required=False, data_key='coverUrl')
256
    radio = fields.Dict(required=True, data_key='radio')
1✔
257

258
    @post_load
1✔
259
    def create_model(self, data, **kwargs):
1✔
260

261
        def to_duration_ms(duration):
×
262
            seconds = duration / 1000
×
263
            m, s = seconds / 60, seconds % 60
×
264
            return '{:02}:{:02}'.format(int(m), int(s))
×
265

266
        song = data.pop('main_song')
×
267
        album_name = data.pop('radio')['name']
×
268
        artists_name = ','.join([artist['name'] for artist in song['artists']])
×
269

270
        return BriefSongModel(identifier=song['id'],
×
271
                              title=song['name'],
272
                              duration_ms=to_duration_ms(song['duration']),
273
                              artists_name=artists_name,
274
                              album_name=album_name,
275
                              state=ModelState.cant_upgrade,
276
                              **data)
277

278

279
class NCloudSchema(Schema):
1✔
280
    main_song = fields.Dict(required=True, data_key='simpleSong')
1✔
281
    album_name = fields.Str(required=True, data_key='album')
1✔
282
    artists_name = fields.Str(required=True, data_key='artist')
1✔
283

284
    @post_load
1✔
285
    def create_model(self, data, **kwargs):
1✔
286

287
        def to_duration_ms(duration):
×
288
            seconds = duration / 1000
×
289
            m, s = seconds / 60, seconds % 60
×
290
            return '{:02}:{:02}'.format(int(m), int(s))
×
291

292
        song = data.pop('main_song')
×
293

294
        return BriefSongModel(identifier=song['id'],
×
295
                              title=song['name'],
296
                              duration_ms=to_duration_ms(song['dt']),
297
                              state=ModelState.cant_upgrade,
298
                              **data)
299

300

301
class V2PlaylistCreatorScehma(Schema):
1✔
302
    identifier = fields.Int(required=True, data_key='userId')
1✔
303
    name = fields.Str(required=True, data_key='nickname')
1✔
304

305
    @post_load
1✔
306
    def create_v2_model(self, data, **kwargs):
1✔
307
        data['identifier'] = str(data['identifier'])
1✔
308
        return BriefUserModel(**data)
1✔
309

310

311
class V2PlaylistSchema(Schema):
1✔
312
    identifier = fields.Int(required=True, data_key='id')
1✔
313
    creator = fields.Nested(V2PlaylistCreatorScehma, missing=None)
1✔
314
    name = fields.Str(required=True)
1✔
315
    description = fields.Str(required=True, allow_none=True, data_key='description')
1✔
316
    cover = fields.Url(required=True, data_key='coverImgUrl')
1✔
317

318
    @post_load
1✔
319
    def create_v2_model(self, data, **kwargs):
1✔
320
        if data.get('description') is None:
1✔
321
            data['description'] = ''
×
322
        return PlaylistModel(**data)
1✔
323

324

325
class NeteaseSearchSchema(Schema):
1✔
326
    """搜索结果 Schema"""
327
    q = fields.Str()
1✔
328
    songs = fields.List(fields.Nested(V2SongSchema))
1✔
329
    albums = fields.List(fields.Nested(V2AlbumSchema))
1✔
330
    artists = fields.List(fields.Nested(V2BriefArtistSchema))
1✔
331
    playlists = fields.List(fields.Nested(V2PlaylistSchema))
1✔
332

333
    @post_load
1✔
334
    def create_model(self, data, **kwargs):
1✔
335
        data.pop('source')
×
336
        return SimpleSearchResult(**data)
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc