Bladeren bron

fix: rating write — avoid rename, overwrite file in-place

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>
master
Jan Svabenik 1 maand geleden
bovenliggende
commit
8ffad2d0ab
1 gewijzigde bestanden met toevoegingen van 27 en 4 verwijderingen
  1. +27
    -4
      internal/rating/rating.go

+ 27
- 4
internal/rating/rating.go Bestand weergeven

@@ -13,8 +13,10 @@
package rating

import (
"bytes"
"fmt"
"math/big"
"os"
"path/filepath"
"strings"

@@ -47,6 +49,13 @@ func Get(path string) (int, error) {

// Set writes a 0–5 star rating into the POPM frame of path.
// stars=0 removes any existing POPM frame (unrated).
//
// bogem/id3v2's Save() writes a temp file and renames it over the original,
// which Windows denies when another process (Winamp) holds the file open
// without FILE_SHARE_DELETE. Instead we use WriteTo to stream the modified
// tag+audio into an in-memory buffer, close our read handle, then overwrite
// the original file in-place — which succeeds because Winamp opens files
// with FILE_SHARE_WRITE.
func Set(path string, stars int) error {
if !isMP3(path) {
return fmt.Errorf("rating.Set: not an MP3 file: %s", filepath.Base(path))
@@ -57,9 +66,8 @@ func Set(path string, stars int) error {

tag, err := id3.Open(path, id3.Options{Parse: true})
if err != nil {
return fmt.Errorf("rating.Set: %w", err)
return fmt.Errorf("rating.Set: open: %w", err)
}
defer tag.Close()

tag.DeleteFrames("POPM")
if stars > 0 {
@@ -69,8 +77,23 @@ func Set(path string, stars int) error {
Counter: big.NewInt(0),
})
}
if err := tag.Save(); err != nil {
return fmt.Errorf("rating.Set: save %s: %w", filepath.Base(path), err)

// Stream modified tag + audio into memory buffer while file is still open.
var buf bytes.Buffer
if _, err := tag.WriteTo(&buf); err != nil {
tag.Close()
return fmt.Errorf("rating.Set: encode: %w", err)
}
tag.Close() // release read handle before we open for writing

// Overwrite the original file in-place (no rename needed).
f, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
return fmt.Errorf("rating.Set: open for write: %w", err)
}
defer f.Close()
if _, err := f.Write(buf.Bytes()); err != nil {
return fmt.Errorf("rating.Set: write: %w", err)
}
return nil
}


Laden…
Annuleren
Opslaan