修复歌手为[]时误导入情况
Showing
2 changed files
with
52 additions
and
28 deletions
| ... | @@ -153,15 +153,16 @@ def _select_qq_import_record( | ... | @@ -153,15 +153,16 @@ def _select_qq_import_record( |
| 153 | current_record: dict, | 153 | current_record: dict, |
| 154 | candidate_records: list[dict], | 154 | candidate_records: list[dict], |
| 155 | songs_by_mid: dict[str, dict], | 155 | songs_by_mid: dict[str, dict], |
| 156 | ) -> dict | None: | 156 | ) -> tuple[dict | None, str]: |
| 157 | """为导入选择 QQ 录音;当前录音的歌词或封面不可用时,改选同源的合格候选。""" | 157 | """为导入选择 QQ 录音;当前录音的歌词或封面不可用时,改选同源的合格候选。 |
| 158 | 返回 (record, reason),record 为 None 时 reason 说明失败原因。""" | ||
| 158 | current_song = songs_by_mid.get(current_record['platform_unique_key']) | 159 | current_song = songs_by_mid.get(current_record['platform_unique_key']) |
| 159 | if not current_song: | 160 | if not current_song: |
| 160 | return None | 161 | return None, 'spider data missing' |
| 161 | needs_lyric = not _has_usable_qq_lyric(current_song) | 162 | needs_lyric = not _has_usable_qq_lyric(current_song) |
| 162 | needs_cover = not _is_usable_qq_cover(_qq_cover_source(hk_row, current_song)) or not current_song.get('album_id') | 163 | needs_cover = not _is_usable_qq_cover(_qq_cover_source(hk_row, current_song)) or not current_song.get('album_id') |
| 163 | if not needs_lyric and not needs_cover: | 164 | if not needs_lyric and not needs_cover: |
| 164 | return current_record | 165 | return current_record, 'ok' |
| 165 | 166 | ||
| 166 | for candidate in candidate_records: | 167 | for candidate in candidate_records: |
| 167 | song = songs_by_mid.get(candidate['platform_unique_key']) | 168 | song = songs_by_mid.get(candidate['platform_unique_key']) |
| ... | @@ -171,8 +172,13 @@ def _select_qq_import_record( | ... | @@ -171,8 +172,13 @@ def _select_qq_import_record( |
| 171 | continue | 172 | continue |
| 172 | if needs_cover and (not song.get('album_id') or not _is_usable_qq_cover(song.get('cover'))): | 173 | if needs_cover and (not song.get('album_id') or not _is_usable_qq_cover(song.get('cover'))): |
| 173 | continue | 174 | continue |
| 174 | return candidate | 175 | return candidate, 'ok' |
| 175 | return None | 176 | missing = [] |
| 177 | if needs_lyric: | ||
| 178 | missing.append('lyric') | ||
| 179 | if needs_cover: | ||
| 180 | missing.append('cover') | ||
| 181 | return None, f"no candidate with usable {' and '.join(missing)}" | ||
| 176 | 182 | ||
| 177 | 183 | ||
| 178 | def _select_kugou_import_record( | 184 | def _select_kugou_import_record( |
| ... | @@ -180,16 +186,17 @@ def _select_kugou_import_record( | ... | @@ -180,16 +186,17 @@ def _select_kugou_import_record( |
| 180 | current_record: dict, | 186 | current_record: dict, |
| 181 | candidate_records: list[dict], | 187 | candidate_records: list[dict], |
| 182 | songs_by_id: dict[int, dict], | 188 | songs_by_id: dict[int, dict], |
| 183 | ) -> dict | None: | 189 | ) -> tuple[dict | None, str]: |
| 184 | """为导入选择酷狗录音;当前录音的歌词或封面不可用时,改选同源的合格候选。""" | 190 | """为导入选择酷狗录音;当前录音的歌词或封面不可用时,改选同源的合格候选。 |
| 191 | 返回 (record, reason),record 为 None 时 reason 说明失败原因。""" | ||
| 185 | song_id = int(current_record['platform_unique_key']) | 192 | song_id = int(current_record['platform_unique_key']) |
| 186 | current_song = songs_by_id.get(song_id) | 193 | current_song = songs_by_id.get(song_id) |
| 187 | if not current_song: | 194 | if not current_song: |
| 188 | return None | 195 | return None, 'spider data missing' |
| 189 | needs_lyric = not _has_kugou_lyric(current_song) | 196 | needs_lyric = not _has_kugou_lyric(current_song) |
| 190 | needs_cover = not _has_kugou_cover(hk_row, current_song) | 197 | needs_cover = not _has_kugou_cover(hk_row, current_song) |
| 191 | if not needs_lyric and not needs_cover: | 198 | if not needs_lyric and not needs_cover: |
| 192 | return current_record | 199 | return current_record, 'ok' |
| 193 | 200 | ||
| 194 | for candidate in candidate_records: | 201 | for candidate in candidate_records: |
| 195 | cid = int(candidate['platform_unique_key']) | 202 | cid = int(candidate['platform_unique_key']) |
| ... | @@ -200,8 +207,13 @@ def _select_kugou_import_record( | ... | @@ -200,8 +207,13 @@ def _select_kugou_import_record( |
| 200 | continue | 207 | continue |
| 201 | if needs_cover and not _has_kugou_cover(hk_row, song): | 208 | if needs_cover and not _has_kugou_cover(hk_row, song): |
| 202 | continue | 209 | continue |
| 203 | return candidate | 210 | return candidate, 'ok' |
| 204 | return None | 211 | missing = [] |
| 212 | if needs_lyric: | ||
| 213 | missing.append('lyric') | ||
| 214 | if needs_cover: | ||
| 215 | missing.append('cover') | ||
| 216 | return None, f"no candidate with usable {' and '.join(missing)}" | ||
| 205 | 217 | ||
| 206 | 218 | ||
| 207 | def _select_netease_import_record( | 219 | def _select_netease_import_record( |
| ... | @@ -209,16 +221,17 @@ def _select_netease_import_record( | ... | @@ -209,16 +221,17 @@ def _select_netease_import_record( |
| 209 | current_record: dict, | 221 | current_record: dict, |
| 210 | candidate_records: list[dict], | 222 | candidate_records: list[dict], |
| 211 | songs_by_id: dict[int, dict], | 223 | songs_by_id: dict[int, dict], |
| 212 | ) -> dict | None: | 224 | ) -> tuple[dict | None, str]: |
| 213 | """为导入选择网易录音;当前录音的歌词或封面不可用时,改选同源的合格候选。""" | 225 | """为导入选择网易录音;当前录音的歌词或封面不可用时,改选同源的合格候选。 |
| 226 | 返回 (record, reason),record 为 None 时 reason 说明失败原因。""" | ||
| 214 | song_id = int(current_record['platform_unique_key']) | 227 | song_id = int(current_record['platform_unique_key']) |
| 215 | current_song = songs_by_id.get(song_id) | 228 | current_song = songs_by_id.get(song_id) |
| 216 | if not current_song: | 229 | if not current_song: |
| 217 | return None | 230 | return None, 'spider data missing' |
| 218 | needs_lyric = not _has_netease_lyric(current_song) | 231 | needs_lyric = not _has_netease_lyric(current_song) |
| 219 | needs_cover = not _has_netease_cover(hk_row, current_song) | 232 | needs_cover = not _has_netease_cover(hk_row, current_song) |
| 220 | if not needs_lyric and not needs_cover: | 233 | if not needs_lyric and not needs_cover: |
| 221 | return current_record | 234 | return current_record, 'ok' |
| 222 | 235 | ||
| 223 | for candidate in candidate_records: | 236 | for candidate in candidate_records: |
| 224 | cid = int(candidate['platform_unique_key']) | 237 | cid = int(candidate['platform_unique_key']) |
| ... | @@ -229,8 +242,13 @@ def _select_netease_import_record( | ... | @@ -229,8 +242,13 @@ def _select_netease_import_record( |
| 229 | continue | 242 | continue |
| 230 | if needs_cover and not _has_netease_cover(hk_row, song): | 243 | if needs_cover and not _has_netease_cover(hk_row, song): |
| 231 | continue | 244 | continue |
| 232 | return candidate | 245 | return candidate, 'ok' |
| 233 | return None | 246 | missing = [] |
| 247 | if needs_lyric: | ||
| 248 | missing.append('lyric') | ||
| 249 | if needs_cover: | ||
| 250 | missing.append('cover') | ||
| 251 | return None, f"no candidate with usable {' and '.join(missing)}" | ||
| 234 | 252 | ||
| 235 | 253 | ||
| 236 | def _prepare_qq_payload(hk_row: dict, pr: dict, bucket, base_url, sp: dict, singer_list: list[dict]) -> dict: | 254 | def _prepare_qq_payload(hk_row: dict, pr: dict, bucket, base_url, sp: dict, singer_list: list[dict]) -> dict: |
| ... | @@ -626,6 +644,8 @@ def _prepare_import_payload( | ... | @@ -626,6 +644,8 @@ def _prepare_import_payload( |
| 626 | return None | 644 | return None |
| 627 | singer_key = int(song_data['id']) if platform == PLATFORM_QQ else int(platform_unique_id) | 645 | singer_key = int(song_data['id']) if platform == PLATFORM_QQ else int(platform_unique_id) |
| 628 | singer_list = singers_maps.get(platform, {}).get(singer_key, []) | 646 | singer_list = singers_maps.get(platform, {}).get(singer_key, []) |
| 647 | if not singer_list: | ||
| 648 | return None | ||
| 629 | payload = preparer(hk_row, pr, bucket, base_url, song_data, singer_list) | 649 | payload = preparer(hk_row, pr, bucket, base_url, song_data, singer_list) |
| 630 | payload['yinyan_record'] = { | 650 | payload['yinyan_record'] = { |
| 631 | 'song_id': int(pending['song_id']), | 651 | 'song_id': int(pending['song_id']), |
| ... | @@ -1708,23 +1728,23 @@ def run( | ... | @@ -1708,23 +1728,23 @@ def run( |
| 1708 | ) | 1728 | ) |
| 1709 | continue | 1729 | continue |
| 1710 | if platform == PLATFORM_QQ: | 1730 | if platform == PLATFORM_QQ: |
| 1711 | selected = _select_qq_import_record( | 1731 | selected, reason = _select_qq_import_record( |
| 1712 | hk_row, pr, qq_records_by_song.get(src_id, []), qq_songs_map, | 1732 | hk_row, pr, qq_records_by_song.get(src_id, []), qq_songs_map, |
| 1713 | ) | 1733 | ) |
| 1714 | elif platform == PLATFORM_KUGOU: | 1734 | elif platform == PLATFORM_KUGOU: |
| 1715 | selected = _select_kugou_import_record( | 1735 | selected, reason = _select_kugou_import_record( |
| 1716 | hk_row, pr, kugou_records_by_song.get(src_id, []), kugou_songs_map, | 1736 | hk_row, pr, kugou_records_by_song.get(src_id, []), kugou_songs_map, |
| 1717 | ) | 1737 | ) |
| 1718 | elif platform == PLATFORM_NETEASE: | 1738 | elif platform == PLATFORM_NETEASE: |
| 1719 | selected = _select_netease_import_record( | 1739 | selected, reason = _select_netease_import_record( |
| 1720 | hk_row, pr, netease_records_by_song.get(src_id, []), netease_songs_map, | 1740 | hk_row, pr, netease_records_by_song.get(src_id, []), netease_songs_map, |
| 1721 | ) | 1741 | ) |
| 1722 | else: | 1742 | else: |
| 1723 | selected = pr | 1743 | selected, reason = pr, 'ok' |
| 1724 | if selected is None: | 1744 | if selected is None: |
| 1725 | log.warning( | 1745 | log.warning( |
| 1726 | 'Skip import without usable lyric/cover candidate: platform=%s source_song_id=%s record_id=%s', | 1746 | 'Skip import (%s): platform=%s source_song_id=%s record_id=%s', |
| 1727 | platform, src_id, pending['record_id'], | 1747 | reason, platform, src_id, pending['record_id'], |
| 1728 | ) | 1748 | ) |
| 1729 | rejected_pending.append(pending) | 1749 | rejected_pending.append(pending) |
| 1730 | continue | 1750 | continue | ... | ... |
| ... | @@ -102,7 +102,7 @@ def test_qq_cover_source_keeps_valid_hk_cover(): | ... | @@ -102,7 +102,7 @@ def test_qq_cover_source_keeps_valid_hk_cover(): |
| 102 | def test_select_qq_import_record_replaces_missing_album_cover_with_same_source_candidate(): | 102 | def test_select_qq_import_record_replaces_missing_album_cover_with_same_source_candidate(): |
| 103 | current = {'record_id': 10, 'platform_unique_key': 'bad-mid'} | 103 | current = {'record_id': 10, 'platform_unique_key': 'bad-mid'} |
| 104 | candidate = {'record_id': 20, 'platform_unique_key': 'good-mid'} | 104 | candidate = {'record_id': 20, 'platform_unique_key': 'good-mid'} |
| 105 | selected = runner._select_qq_import_record( | 105 | selected, reason = runner._select_qq_import_record( |
| 106 | {'cover_url': runner.QQ_MISSING_ALBUM_COVER}, | 106 | {'cover_url': runner.QQ_MISSING_ALBUM_COVER}, |
| 107 | current, | 107 | current, |
| 108 | [current, candidate], | 108 | [current, candidate], |
| ... | @@ -112,12 +112,13 @@ def test_select_qq_import_record_replaces_missing_album_cover_with_same_source_c | ... | @@ -112,12 +112,13 @@ def test_select_qq_import_record_replaces_missing_album_cover_with_same_source_c |
| 112 | }, | 112 | }, |
| 113 | ) | 113 | ) |
| 114 | assert selected == candidate | 114 | assert selected == candidate |
| 115 | assert reason == 'ok' | ||
| 115 | 116 | ||
| 116 | 117 | ||
| 117 | def test_select_qq_import_record_replaces_missing_lyric_with_same_source_candidate(): | 118 | def test_select_qq_import_record_replaces_missing_lyric_with_same_source_candidate(): |
| 118 | current = {'record_id': 10, 'platform_unique_key': 'without-lyric'} | 119 | current = {'record_id': 10, 'platform_unique_key': 'without-lyric'} |
| 119 | candidate = {'record_id': 20, 'platform_unique_key': 'with-lyric'} | 120 | candidate = {'record_id': 20, 'platform_unique_key': 'with-lyric'} |
| 120 | selected = runner._select_qq_import_record( | 121 | selected, reason = runner._select_qq_import_record( |
| 121 | {'cover_url': 'https://example.com/hk-cover.jpg'}, | 122 | {'cover_url': 'https://example.com/hk-cover.jpg'}, |
| 122 | current, | 123 | current, |
| 123 | [current, candidate], | 124 | [current, candidate], |
| ... | @@ -127,16 +128,19 @@ def test_select_qq_import_record_replaces_missing_lyric_with_same_source_candida | ... | @@ -127,16 +128,19 @@ def test_select_qq_import_record_replaces_missing_lyric_with_same_source_candida |
| 127 | }, | 128 | }, |
| 128 | ) | 129 | ) |
| 129 | assert selected == candidate | 130 | assert selected == candidate |
| 131 | assert reason == 'ok' | ||
| 130 | 132 | ||
| 131 | 133 | ||
| 132 | def test_select_qq_import_record_returns_none_when_no_candidate_satisfies_missing_fields(): | 134 | def test_select_qq_import_record_returns_none_when_no_candidate_satisfies_missing_fields(): |
| 133 | current = {'record_id': 10, 'platform_unique_key': 'bad-mid'} | 135 | current = {'record_id': 10, 'platform_unique_key': 'bad-mid'} |
| 134 | assert runner._select_qq_import_record( | 136 | result, reason = runner._select_qq_import_record( |
| 135 | {'cover_url': runner.QQ_MISSING_ALBUM_COVER}, | 137 | {'cover_url': runner.QQ_MISSING_ALBUM_COVER}, |
| 136 | current, | 138 | current, |
| 137 | [current], | 139 | [current], |
| 138 | {'bad-mid': {'album_id': 0, 'cover': runner.QQ_MISSING_ALBUM_COVER, 'lyric': ''}}, | 140 | {'bad-mid': {'album_id': 0, 'cover': runner.QQ_MISSING_ALBUM_COVER, 'lyric': ''}}, |
| 139 | ) is None | 141 | ) |
| 142 | assert result is None | ||
| 143 | assert 'lyric' in reason and 'cover' in reason | ||
| 140 | 144 | ||
| 141 | 145 | ||
| 142 | def test_run_imports_only_pending_yinyan_platform_record(monkeypatch): | 146 | def test_run_imports_only_pending_yinyan_platform_record(monkeypatch): | ... | ... |
-
Please register or sign in to post a comment