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  }