Skip to content

Commit

Permalink
MusicMK fixes.
Browse files Browse the repository at this point in the history
  • Loading branch information
bengarrett committed Aug 11, 2024
1 parent 57b357a commit 68cd4d2
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 94 deletions.
53 changes: 34 additions & 19 deletions handler/app/internal/mf/mf.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ func (e *entry) parse(path, platform string, info fs.FileInfo) bool {
}
defer r.Close()
var err error
e.sign, err = magicnumber.Find1500B(r)
e.sign, err = magicnumber.Find2K(r)
if err != nil {
fmt.Fprintf(io.Discard, "ignore this error, %v", err)
return skipEntry
Expand Down Expand Up @@ -235,34 +235,49 @@ func (e *entry) parseMusicMod(path string) bool {
return !skipEntry
}

// ParseMusicID3 parses the ID3 tag in the byte slice and returns the title, artist and year if available.
// It looks up in order the ID3v2.3, ID3v2.2 and ID3v1 tags in the byte slice with the priority being
// the newer versions of the tag.
//
// ID3v1 is a completely different tag format to ID3v2 and has serious limitations,
// so it is only used as a last resort.
func (e *entry) parseMusicID3(path string) bool {
const skipEntry = true
r1, _ := os.Open(path)
if r1 == nil {

// ID3 v2.x tags are located at the start of the file.
id3, _ := os.Open(path)
if id3 == nil {
return skipEntry
}
defer r1.Close()
size, offset := 128, int64(-128)
_, err := r1.Seek(offset, io.SeekEnd)
if err != nil {
return skipEntry
defer id3.Close()
buf := make([]byte, magicnumber.MusicTrackerSize)
if _, err := io.ReadFull(id3, buf); err == nil {
if s := magicnumber.MusicID3v2(buf); s != "" {
e.module = s
return !skipEntry
}
}
buf := make([]byte, size)
if _, err := io.ReadFull(r1, buf); err == nil {
e.module = magicnumber.MusicID3v1(buf)

// ID3 v1 tags are located at the end of the file.
id3v1, _ := os.Open(path)
if id3v1 == nil {
return skipEntry
}
defer id3v1.Close()

rr, _ := os.Open(path)
if rr == nil {
size := magicnumber.ID3v1Size
offset := -int64(size)
_, err := id3v1.Seek(offset, io.SeekEnd)
if err != nil {
return skipEntry
}
defer rr.Close()
buf = make([]byte, 1024*10)
x := ""
if _, err := io.ReadFull(rr, buf); err == nil {
x = magicnumber.MusicID3v2(buf)
buf = make([]byte, size)
if _, err := io.ReadFull(id3v1, buf); err == nil {
if s := magicnumber.MusicID3v1(buf); s != "" {
e.module = s
return !skipEntry
}
}
fmt.Println("ID3v2.3:", x)
return !skipEntry
}

Expand Down
103 changes: 44 additions & 59 deletions internal/magicnumber/id3.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import (

// Package file id3.go contains the functions that parse bytes as common ID3 tag formats usually found in MP3 files.

// ID3v1Size is the minimum buffer size of an ID3 v1 tag.
const ID3v1Size = 128

// MusicID3v1 reads the [ID3 v1] tag in the byte slice and returns the title and artist.
// The ID3 v1 tag is a 128 byte tag at the end of an MP3 audio file.
//
// [ID3 v1]: http://id3.org/ID3v1
func MusicID3v1(p []byte) string {
const length = 128
if len(p) < length {
if len(p) < ID3v1Size {
return ""
}
if !bytes.Equal(p[0:3], []byte{'T', 'A', 'G'}) {
Expand All @@ -36,6 +38,18 @@ func MusicID3v1(p []byte) string {
return strings.TrimSpace(s)
}

func headerSize(p []byte) int {
const length = 10
if len(p) < length {
return 0
}
b0 := int(p[6]) * 2097152
b1 := int(p[7]) * 16384
b2 := int(p[8]) * 128
b3 := int(p[9]) * 1
return b0 + b1 + b2 + b3
}

func MusicID3v2(p []byte) string {
const length = 10
if len(p) < length {
Expand All @@ -44,74 +58,45 @@ func MusicID3v2(p []byte) string {
if !bytes.Equal(p[0:3], []byte{'I', 'D', '3'}) {
return ""
}
ha := int(p[6]) * 2097152
hb := int(p[7]) * 16384
hc := int(p[8]) * 128
hd := int(p[9])
headerSize := ha + hb + hc + hd
fmt.Println(headerSize, "header size", headerSize, "length", len(p))
if headerSize+length > len(p) {
hsize := headerSize(p)
if hsize+length > len(p) {
return ""
}
data := p[length : headerSize+length]
fmt.Println(headerSize, "header size", headerSize)

// frame id 4 bytes
// size 4 bytes
// flags 2 bytes
talb := bytes.Index(data, []byte{'T', 'A', 'L', 'B'})
fmt.Println("talb", talb)
// if talb != -1 && talb+10 < len(data) {
// //tablSize := binary.LittleEndian.Uint32(data[talb+4 : talb+8])
// b0 := int(data[talb+4]) * 2097152
// b1 := int(data[talb+5]) * 16384
// b2 := int(data[talb+6]) * 128
// b3 := int(data[talb+7])
// tablSize := b0 + b1 + b2 + b3
// fmt.Println("tabl size u32", tablSize)
// title := string(data[talb+10 : talb+10+tablSize])
// fmt.Println("title", title)
// }
// h0 := p[6]*2 ^ 21
// h1 := p[7]*2 ^ 14
// h2 := p[8]*2 ^ 7
// headerSize := int(h0) + int(h1) + int(h2) + int(p[9])
//b := p[0x06:0xa]
//
//headerSize := binary.LittleEndian.Uint16(b)
// An easy way of calculating the tag size is
// A*2^21+B*2^14+C*2^7+D = A*2097152+B*16384+C*128+D,
//where A is the first byte, B the second, C the third and D the fourth byte.
tabl := [4]byte{'T', 'A', 'L', 'B'}
fmt.Println("TABL", ID3v3Frame(tabl, data...))
tpe1 := [4]byte{'T', 'P', 'E', '1'}
fmt.Println("TPE1", ID3v3Frame(tpe1, data...))
tit1 := [4]byte{'T', 'I', 'T', '1'}
fmt.Println("TIT1", ID3v3Frame(tit1, data...))
tit2 := [4]byte{'T', 'I', 'T', '2'}
fmt.Println("TIT2", ID3v3Frame(tit2, data...))
tyer := [4]byte{'T', 'Y', 'E', 'R'}
fmt.Println("TYER", ID3v3Frame(tyer, data...))

fmt.Println(headerSize, "header size", p[6], p[7], p[8], p[9])
// lookup version
version := p[3]
switch version {
data := p[length : length+hsize]
switch version := p[3]; version {
case 0x2:
return "ID3v2.2"
case 0x3:
return "ID3v2.3"
return ID3v230(data...)
case 0x4:
return "ID3v2.4"
}
// 0 * 2^21 = 0
// 0 * 2^14 = 0
// 2 * 2^7 = 256
// 1 = 1
return ""
}

func ID3v3Frame(id [4]byte, data ...byte) string {
func ID3v230(data ...byte) string {
ablumTitle := [4]byte{'T', 'A', 'L', 'B'}
leadPerformer := [4]byte{'T', 'P', 'E', '1'}
contentGroup := [4]byte{'T', 'I', 'T', '1'}
songName := [4]byte{'T', 'I', 'T', '2'}
year := [4]byte{'T', 'Y', 'E', 'R'}
s := ID3v2Frame(songName, data...)
if s != "" {
if lp := ID3v2Frame(leadPerformer, data...); lp != "" {
s += fmt.Sprintf(" by %s", lp)
} else if cg := ID3v2Frame(contentGroup, data...); cg != "" {
s += fmt.Sprintf(" by %s", cg)
}
} else if ab := ID3v2Frame(ablumTitle, data...); ab == "" {
return ""
}
if y := ID3v2Frame(year, data...); y != "" {
s += fmt.Sprintf(" (%s)", y)
}
return strings.TrimSpace(s)
}

func ID3v2Frame(id [4]byte, data ...byte) string {
const header = 10
frameID := []byte{id[0], id[1], id[2], id[3]}
offset := bytes.Index(data, frameID)
Expand Down
15 changes: 7 additions & 8 deletions internal/magicnumber/magicnumber.go
Original file line number Diff line number Diff line change
Expand Up @@ -585,16 +585,15 @@ func Find(r io.Reader) (Signature, error) {
return FindBytes(buf), nil
}

// Find1500B reads the first 1536 bytes from the reader and returns the file type signature.
// Find2K reads the first 2048 bytes from the reader and returns the file type signature.
// This is a less accurate method than Find but should be faster.
func Find1500B(r io.Reader) (Signature, error) {
const size = 512 * 3
buf := make([]byte, size)
func Find2K(r io.Reader) (Signature, error) {
buf := make([]byte, MusicTrackerSize)
_, err := io.ReadFull(r, buf)
if err != nil && !errors.Is(err, io.ErrUnexpectedEOF) {
return Unknown, fmt.Errorf("magic number find first %d bytes: %w", size, err)
return Unknown, fmt.Errorf("magic number find first %d bytes: %w", MusicTrackerSize, err)
}
return FindBytes512B(buf), nil
return FindBytes2K(buf), nil
}

// FindBytes returns the file type signature from the byte slice.
Expand All @@ -618,9 +617,9 @@ func FindBytes(p []byte) Signature {
}
}

// FindBytes512B returns the file type signature and skips the magic number checks
// FindBytes2K returns the file type signature and skips the magic number checks
// that require the entire file to be read.
func FindBytes512B(p []byte) Signature {
func FindBytes2K(p []byte) Signature {
if len(p) == 0 {
return ZeroByte
}
Expand Down
4 changes: 2 additions & 2 deletions internal/magicnumber/magicnumber_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,11 +296,11 @@ func TestTxtWindows(t *testing.T) {
assert.True(t, magicnumber.TxtWindows(b))
}

func TestFind1500B(t *testing.T) {
func TestFind2K(t *testing.T) {
f, err := os.Open(tduncompress("TEST.JPEG"))
require.NoError(t, err)
defer f.Close()
sign, err := magicnumber.Find1500B(f)
sign, err := magicnumber.Find2K(f)
require.NoError(t, err)
assert.Equal(t, magicnumber.JPEGFileInterchangeFormat, sign)
}
Expand Down
51 changes: 45 additions & 6 deletions internal/magicnumber/synthesismusic.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,18 +118,57 @@ func MusicXM(p []byte) string {
//
// [ProTracker]: https://ftp.modland.com/pub/documents/format_documentation/ProTracker%20v1.0%20(.mod).html
func MusicMK(p []byte) string {
const offset, length, headerLen = 1080, 4, 20
if len(p) < offset+length+headerLen {
const offset, length, healder = 1080, 4, 20
if len(p) < offset+length+healder {
return ""
}
modHeader := p[offset : offset+length]
if !bytes.Equal(modHeader, []byte{'M', '.', 'K', '.'}) {
switch {
case
bytes.Equal(modHeader, []byte{'2', 'C', 'H', 'N'}):
return music2Chan(p[0:healder])
case
bytes.Equal(modHeader, []byte{'M', '.', 'K', '.'}),
bytes.Equal(modHeader, []byte{'M', '!', 'K', '!'}),
bytes.Equal(modHeader, []byte{'F', 'L', 'T', '4'}),
bytes.Equal(modHeader, []byte{'4', 'C', 'H', 'N'}):
return music4Chan(p[0:healder])
case
bytes.Equal(modHeader, []byte{'6', 'C', 'H', 'N'}):
return music6Chan(p[0:healder])
case
bytes.Equal(modHeader, []byte{'8', 'C', 'H', 'N'}),
bytes.Equal(modHeader, []byte{'O', 'C', 'T', 'A'}):
return music8Chan(p[0:healder])
default:
return ""
}
s := "ProTracker song"
song := string(bytes.Trim(p[0:headerLen], "\x00"))
}

func music2Chan(b []byte) string {
s := "ProTracker 2-channel song"
return modSong(s, b)
}

func music4Chan(b []byte) string {
s := "ProTracker 4-channel song"
return modSong(s, b)
}

func music6Chan(b []byte) string {
s := "ProTracker 6-channel song"
return modSong(s, b)
}

func music8Chan(b []byte) string {
s := "ProTracker 8-channel song"
return modSong(s, b)
}

func modSong(info string, b []byte) string {
s := info
song := string(bytes.Trim(b, "\x00"))
song = strings.TrimSpace(song)
fmt.Println("MusicMK", string(modHeader), "<>", song)
if song != "" {
s += fmt.Sprintf(", %q", song)
}
Expand Down

0 comments on commit 68cd4d2

Please sign in to comment.