github.com/derat/nup@v0.0.0-20230418113745-15592ba7c620/cmd/nup/debug/id3.go (about) 1 // Copyright 2022 Daniel Erat. 2 // All rights reserved. 3 4 package debug 5 6 import ( 7 "errors" 8 "os" 9 "strconv" 10 11 "github.com/derat/mpeg" 12 "github.com/derat/taglib-go/taglib" 13 "github.com/derat/taglib-go/taglib/id3" 14 ) 15 16 const ( 17 // Maximum ID3v2 frame content size. Longer frames (e.g. image data) are not decoded. 18 maxID3FrameSize = 256 19 // Unique file identifier frame ID. 20 ufidID = "UFID" 21 ) 22 23 type id3Frame struct { 24 id string 25 size int // bytes 26 fields []string 27 } 28 29 func readID3Frames(p string) ([]id3Frame, error) { 30 f, err := os.Open(p) 31 if err != nil { 32 return nil, err 33 } 34 defer f.Close() 35 36 fi, err := f.Stat() 37 if err != nil { 38 return nil, err 39 } 40 41 var ret []id3Frame 42 43 // Return miscellaneous info using fake user-defined frames. 44 appendTextFrame := func(fields []string) { 45 var size int 46 for _, s := range fields { 47 size += len(s) 48 } 49 ret = append(ret, id3Frame{id: "TXXX", size: size, fields: fields}) 50 } 51 52 if gen, err := taglib.Decode(f, fi.Size()); err == nil { 53 for url, id := range gen.UniqueFileIdentifiers() { 54 ret = append(ret, id3Frame{id: "UFID", fields: []string{url, id}}) 55 } 56 57 switch tag := gen.(type) { 58 case *id3.Id3v23Tag: 59 for id, frames := range tag.Frames { 60 if id == ufidID { 61 continue 62 } 63 for _, frame := range frames { 64 info := id3Frame{id: id, size: len(frame.Content)} 65 if info.size <= maxID3FrameSize { 66 info.fields, _ = id3.GetId3v23TextIdentificationFrame(frame) 67 } 68 ret = append(ret, info) 69 } 70 } 71 case *id3.Id3v24Tag: 72 for id, frames := range tag.Frames { 73 if id == ufidID { 74 continue 75 } 76 for _, frame := range frames { 77 info := id3Frame{id: id, size: len(frame.Content)} 78 if info.size <= maxID3FrameSize { 79 info.fields, _ = id3.GetId3v24TextIdentificationFrame(frame) 80 } 81 ret = append(ret, info) 82 } 83 } 84 default: 85 appendTextFrame([]string{"ID3v2 version unsupported"}) 86 } 87 } 88 89 if tag, err := mpeg.ReadID3v1Footer(f, fi); err == nil && tag != nil { 90 add := func(name, val string) { 91 if val != "" { 92 appendTextFrame([]string{name, val}) 93 } 94 } 95 add("ID3v1 Artist", tag.Artist) 96 add("ID3v1 Title", tag.Title) 97 add("ID3v1 Album", tag.Album) 98 add("ID3v1 Year", tag.Year) 99 add("ID3v1 Comment", tag.Comment) 100 if tag.Track != 0 { 101 add("ID3v1 Track", strconv.Itoa(int(tag.Track))) 102 } 103 if tag.Genre != 255 { // 0 is "Blues", 255 is none: https://exiftool.org/TagNames/ID3.html 104 add("ID3v1 Genre", strconv.Itoa(int(tag.Genre))) 105 } 106 } 107 108 if len(ret) == 0 { 109 return nil, errors.New("no ID3 tag") 110 } 111 return ret, nil 112 }