The previous implementation used tag.WriteTo() which only emits the ID3
tag (~311 bytes), leaving audio data behind. Files were truncated to just
the tag on every rating write.
New strategy:
1. Parse the 10-byte ID3v2 header to find the audio start offset.
2. Encode the new tag into an in-memory buffer via WriteTo.
3. Write tag + original audio into a temp file.
4. Try atomic os.Rename (works when Winamp does not hold the file).
5. Fall back to direct O_WRONLY|O_TRUNC write (works while Winamp plays,
because Winamp opens with FILE_SHARE_WRITE on Windows).
Tests cover: POPM<->stars mapping, id3v2AudioStart, full round-trip
(1-5 stars + unrate) with audio-integrity check, and error cases.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
bogem/id3v2 Save() renames a temp file over the original, which Windows
denies when Winamp holds the file open (no FILE_SHARE_DELETE). Fix:
- Use tag.WriteTo(&buf) to encode tag+audio into memory while the read
handle is still open
- Close the read handle
- Reopen the original file with O_WRONLY|O_TRUNC and write the buffer
Winamp opens MP3s with FILE_SHARE_WRITE so the in-place overwrite succeeds.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend:
- winamp.GetCurrentFile() reads file path via IPC_GETPLAYLISTFILE (211)
+ ReadProcessMemory, same pattern as playlist titles
- internal/rating: Get/Set POPM frame via bogem/id3v2
- Email: rating@winamp.com (Winamp standard)
- Byte scale: 0/1/64/128/196/255 = 0-5 stars
- Compatible with Windows Explorer and Winamp
- GET /api/rating -> {stars: N}
- POST /api/rating {stars: N} -> writes POPM, returns {stars: N}
Frontend:
- 5 stars in track-info, gold when lit
- Fetched automatically on track change
- Tap to rate; tap same star again to remove rating
- Optimistic update with revert on error
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>