Skip to content

fix(local): repair metadata decoding and song restoration - #545

Merged
cwuom merged 5 commits into
masterfrom
intention/fix-local-metadata-restore
Oct 19, 2026
Merged

cwuom merged 5 commits into
masterfrom
intention/fix-local-metadata-restore

Conversation

@cwuom

@cwuom cwuom commented Oct 18, 2026 •

Copy link
Copy Markdown
Owner

描述 / Description

Local music could show corrupted title or artist text, retain incorrectly matched lyrics and artwork after Restore, and create duplicate or empty lyric sidecars.

  • Decode reversible legacy tag encodings and read bounded raw MP3 ID3 identity fields even when MediaStore supplies readable but corrupted Unicode. Seekable files and descriptors skip large artwork frames; existing custom values and independent originals remain authoritative. Unlabelled container text still scores reversible legacy Chinese candidates for ambiguous UTF-8 symbols, while declared Unicode encodings remain authoritative.
  • Preserve original lyrics and artwork before single-song edits, matching, and bulk embedding. Keep all three lyric tracks across rescans, distinguish unknown sources from explicitly empty originals, and restore durable originals before edited sidecars. Record a confirmed no-artwork baseline on the first edit across single-song, matching, and bulk paths; permission loss, malformed listings, and unreadable sources remain unknown.
  • Reuse lyric sidecars, avoid creating absent empty tracks, and commit lyrics with metadata transactionally. Validate fresh SAF document identity and stop URI fallback while rollback remains unresolved.
  • Keep large content-only scans on directory rows and explicitly indexed sidecars without per-audio queries or probes. Hydrate deferred identity in preview and await bounded raw MP3 identity checks before all three scanned-song persistence paths, so immediate import also retains corrected identity, custom fields, and independent baselines.
  • Repair the CI failures with direct baseline-reader coverage and shared restore selection, isolated recovery query measurement, and a landscape test host that restores its original display policy. Keep the quality thresholds and behavioral assertions intact.

类型 / Type

  • Bug 修复 / Bug Fix
  • 新功能 / New Feature
  • 文档更新 / Documentation Update
  • 其他(请描述)/ Other (please describe):

修复或解决的问题 / Issues Fixed or Closed by This PR

Fixes #544

Also fixes modified lyrics or incorrect artwork surviving Restore, duplicate sidecars, and partial companion writes after failure or cancellation.

清单 / Checklist

  • 我已阅读并遵循贡献指南 / I have read and followed the contribution guidelines
  • 我已在本地测试这些更改 / I have tested these changes locally
  • 我已更新相关文档或注释(如适用) / I have updated relevant documentation or comments (if applicable)
  • 我确认本次更改没有破坏任何原有逻辑 / I confirm this change does not break existing behavior
  • 我确认本次更改保持向下兼容(如适用) / I confirm this change remains backward compatible (if applicable)
  • 我已运行必要的构建、测试或静态检查 / I have run the required build, tests, or static checks
  • 我确认没有提交密钥、Token 或其他敏感信息 / I confirm no secrets, tokens, or other sensitive information were committed
  • 涉及 UI 变更时,我已补充截图或录屏(如适用) / I have added screenshots or recordings for UI changes (if applicable)

其他信息 / Additional Information

Local validation used JDK 21:

  • The full CI Gradle quality command, additional local/runtime CRAP and lint checks, and module boundaries passed on the final source. App and owned-module JVM reports contain 13,539 cases: 13,532 passed, 7 skipped, no failures or errors. Targeted regressions first reproduced the deferred scan, immediate-import persistence, readable-but-corrupt preview identity, and cancellation defects before passing with the fixes.
  • Debug APK and instrumentation APK builds passed in a separate fresh Gradle JVM; no repository memory settings or quality thresholds were changed.
  • The final APK passed all 136 tests, without skips, in LocalAudioImportSafLyricsTest, DownloadCorePublicationInstrumentedTest, CommentLandscapeLayoutTest, DownloadedM4aStagingRegressionTest, LocalMediaCompanionRollbackTest, LocalMediaSupportSafLyricsTest, LocalCoverBaselineInspectorInstrumentedTest, and Issue339MetadataCreationContractTest on the task-owned API 35 arm64 emulator. Native M4A regression tests verified unchanged audio sample hashes and duration. Display orientation policy was restored after the landscape tests.
  • The unchanged 2,000-song SAF scan regression passed with zero per-audio queries. Scan logs recorded 459 ms locally and 1,195 ms in the latest remote CI against the existing 3-second assertion; all three native no-artwork baseline tests also passed in that CI shard.
  • git diff --check passed. No compiler or lint warnings were introduced in the modified files.

Remote CI for e6fe4be168ac0952a3786f6eb6f006d1d7d9835f completed successfully: NeriPlayer Android CI. All seven intended jobs passed, including both Android test shards, the External Lyrics host, Gradle quality gates, Listen Together, Worker checks, and the unsigned Release build with its Native USB host tests. Remote testDebugUnitTest reports match the local 13,539-case totals above; the 38 targeted raw-tag, scanned-persistence, and preview-policy regressions all ran without skips.

The latest Codex review for this head completed with no unresolved findings, and all seven existing review threads are resolved.

Coverage limits: the original reported MP3 files were unavailable. Encoding and MediaStore corruption cases use generated ID3 fixtures, including stale metadata snapshots, partial tags, large leading artwork, and content-only providers. Non-seekable streams retain a bounded read budget and may keep the queried fallback when later identity frames cannot be reached. No physical-device or vivo OEM-provider testing was performed. The local emulator uses API 35 arm64 with phone geometry; remote CI uses its configured API 35 x86_64 images. Local builds preserve the pre-existing unrelated miuix checkout; remote CI validates the committed submodule revision.

The UI checklist concerns test-fixture validation in this change; both local landscape captures were inspected. Existing unrelated warnings remain outside this change.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-19T12:04:32.019616Z e6fe4be New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

本次变更调整了本地歌词侧车的解析、写入和回滚流程,并扩展歌词来源状态记录。导入、播放元数据更新及编辑恢复流程现在保留原始歌词和封面信息,包括明确为空的值。新增本地媒体文本修复逻辑,并增加针对侧车事务、缓存刷新、元数据恢复和文本解码的测试。

Priority: ➖ Normal

Merge Risk: 🔵 Low · up to 97c59

Some short accented metadata may display as a Chinese character. This is a bounded issue, but the decoding ambiguity should be fixed or accepted before merging.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 198 functions across 35 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Title check Passed 标题准确概括了本次变更的两个主要方向:本地元数据解码修复和歌曲信息恢复。标题简洁且与变更内容相关。
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.

🟡 Other comments: 1
modules/playback/runtime/src/main/java/moe/ouom/neriplayer/core/player/persistence/PlayerManagerMetadataBaseline.kt: 1

47-82: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

在 SAF 权限失效时跳过原始歌词扫描

inspectLyricsFast 会重新抛出 SAF 读取产生的 SecurityException。preserveLocalSongMetadataOriginals 在本地歌词、翻译歌词和自定义信息写入前调用它。runSongMetadataMutation 和公开编辑入口都不捕获该异常,因此编辑会在写入同步状态或歌曲状态前失败。手动匹配虽然捕获异常,但也只会显示失败提示。

原始歌词扫描是基线读取,不应阻止编辑。权限失效时应跳过该扫描并继续使用已有歌曲数据。

Suggested fix
-        LocalMediaSupport.inspectLyricsFast(
-            context = application,
-            song = localSong,
-            includeStoredFallback = false,
-            forceRefresh = true
-        )
+        try {
+            LocalMediaSupport.inspectLyricsFast(
+                context = application,
+                song = localSong,
+                includeStoredFallback = false,
+                forceRefresh = true
+            )
+        } catch (_: SecurityException) {
+            null
+        }

ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository: cwuom/NeriPlayer/.coderabbit.yaml
  • Review profile: QUIET
  • Plan: Advanced
  • Run ID: 833d3afe-48fe-45ff-9615-0a1b192ba796
📥 Commits

Reviewing files that changed from the base of the PR and between b0da29c and b209618.

⛔ Files ignored due to path filters: 1
  • modules/local/README.md is excluded by !**/*.md
📒 Files selected for processing: 33
  • app/src/androidTest/java/moe/ouom/neriplayer/data/local/media/DownloadedM4aStagingRegressionTest.kt
  • app/src/androidTest/java/moe/ouom/neriplayer/data/local/media/LocalMediaSupportSafLyricsTest.kt
  • app/src/main/java/moe/ouom/neriplayer/ui/screen/nowplaying/edit/NowPlayingSongEditPolicy.kt
  • app/src/main/java/moe/ouom/neriplayer/ui/screen/nowplaying/edit/NowPlayingSongEditRestoreSources.kt
  • app/src/test/java/moe/ouom/neriplayer/ui/screen/NowPlayingManagedRestoreBaselineTest.kt
  • app/src/test/java/moe/ouom/neriplayer/ui/screen/NowPlayingSongEditRestoreSourcesTest.kt
  • modules/local/src/main/java/moe/ouom/neriplayer/data/local/audioimport/LocalAudioImportManager.kt
  • modules/local/src/main/java/moe/ouom/neriplayer/data/local/audioimport/LocalAudioImportManagerScan.kt
  • modules/local/src/main/java/moe/ouom/neriplayer/data/local/media/LocalMediaMetadataText.kt
  • modules/local/src/main/java/moe/ouom/neriplayer/data/local/media/LocalMediaSupport.kt
  • modules/local/src/main/java/moe/ouom/neriplayer/data/local/media/LocalMediaSupportAssets.kt
  • modules/local/src/main/java/moe/ouom/neriplayer/data/local/media/LocalMediaSupportContracts.kt
  • modules/local/src/main/java/moe/ouom/neriplayer/data/local/media/LocalMediaSupportFacadeInspect.kt
  • modules/local/src/main/java/moe/ouom/neriplayer/data/local/media/LocalMediaSupportFacadeScan.kt
  • modules/local/src/main/java/moe/ouom/neriplayer/data/local/media/LocalMediaSupportSaf.kt
  • modules/local/src/main/java/moe/ouom/neriplayer/data/local/media/metadata/LocalMediaSupportMetadata.kt
  • modules/local/src/main/java/moe/ouom/neriplayer/data/local/playlist/LocalPlaylistRepositoryMetadata.kt
  • modules/local/src/test/java/moe/ouom/neriplayer/data/local/audioimport/LocalAudioImportMetadataEncodingTest.kt
  • modules/local/src/test/java/moe/ouom/neriplayer/data/local/audioimport/LocalAudioImportOriginalMetadataTest.kt
  • modules/local/src/test/java/moe/ouom/neriplayer/data/local/media/LocalMediaDocumentLyricReferenceEnsureTest.kt
  • modules/local/src/test/java/moe/ouom/neriplayer/data/local/media/LocalMediaLyricSidecarRepairTest.kt
  • modules/local/src/test/java/moe/ouom/neriplayer/data/local/media/LocalMediaLyricTransactionTest.kt
  • modules/local/src/test/java/moe/ouom/neriplayer/data/local/media/LocalMediaMetadataEncodingTest.kt
  • modules/local/src/test/java/moe/ouom/neriplayer/data/local/media/LocalMediaSupportTest.kt
  • modules/local/src/test/java/moe/ouom/neriplayer/data/local/media/metadata/LocalMediaLyricSidecarMutationTest.kt
  • modules/local/src/test/java/moe/ouom/neriplayer/data/local/playlist/LocalPlaylistRepositoryTest.kt
  • modules/playback/runtime/src/main/java/moe/ouom/neriplayer/core/player/metadata/PlayerMetadataHelper.kt
  • modules/playback/runtime/src/main/java/moe/ouom/neriplayer/core/player/persistence/PlayerManagerMetadataBaseline.kt
  • modules/playback/runtime/src/main/java/moe/ouom/neriplayer/core/player/persistence/PlayerManagerPersistenceExtensions.kt
  • modules/playback/runtime/src/test/java/moe/ouom/neriplayer/core/player/metadata/PlayerManagerCustomMetadataNormalizationTest.kt
  • modules/playback/runtime/src/test/java/moe/ouom/neriplayer/core/player/metadata/PlayerManagerSearchMetadataTest.kt
  • modules/playback/runtime/src/test/java/moe/ouom/neriplayer/core/player/metadata/PlayerMetadataHelperCoverTest.kt
  • modules/playback/runtime/src/test/java/moe/ouom/neriplayer/core/player/persistence/PlayerManagerMetadataBaselineTest.kt

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b209618df0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.

🟡 Other comments: 1
modules/local/src/main/java/moe/ouom/neriplayer/data/local/media/LocalMediaMetadataText.kt: 1

36-36: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

不要仅凭两个高字节判定 GB18030。

例如,合法的 Latin-1 文本 "éè" 只有一个长度为 2 的高字节片段。该片段也可按 GB18030 解为汉字并通过重编码检查,因此 decodeContainerText() 会替换原文。请在转换这种有歧义的短文本前要求额外的编码证据;否则保留原文。


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository: cwuom/NeriPlayer/.coderabbit.yaml
  • Review profile: QUIET
  • Plan: Advanced
  • Run ID: 5f80d0dd-6af1-4b0d-a34d-5da1474df97b
📥 Commits

Reviewing files that changed from the base of the PR and between b209618 and 97c5980.

📒 Files selected for processing: 9
  • modules/local/src/main/java/moe/ouom/neriplayer/data/local/media/LocalMediaMetadataText.kt
  • modules/local/src/main/java/moe/ouom/neriplayer/data/local/media/LocalMediaSupport.kt
  • modules/local/src/main/java/moe/ouom/neriplayer/data/local/media/LocalMediaSupportFacadeInspect.kt
  • modules/local/src/test/java/moe/ouom/neriplayer/data/local/media/LocalMediaEmbeddedLyricsCancellationTest.kt
  • modules/local/src/test/java/moe/ouom/neriplayer/data/local/media/LocalMediaLyricTransactionTest.kt
  • modules/local/src/test/java/moe/ouom/neriplayer/data/local/media/LocalMediaMetadataEncodingTest.kt
  • modules/playback/runtime/src/main/java/moe/ouom/neriplayer/core/player/persistence/PlayerManagerMetadataBaseline.kt
  • modules/playback/runtime/src/main/java/moe/ouom/neriplayer/core/player/persistence/PlayerManagerPersistenceExtensions.kt
  • modules/playback/runtime/src/test/java/moe/ouom/neriplayer/core/player/persistence/PlayerManagerLocalMetadataEditRegressionTest.kt

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 6 remain after this review.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 97c5980514

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dcd78efb97

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@cwuom
cwuom merged commit 113699b into master Oct 19, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] 部分本地音乐 ID3 标签乱码(UTF-8/GBK 编码识别问题)

1 participant