1
0
forked from um/cli
cli/algo/xiami/xm.go

92 lines
2.0 KiB
Go
Raw Permalink Normal View History

2022-11-19 18:18:50 +00:00
package xiami
2020-12-25 20:38:23 +00:00
import (
"bytes"
"errors"
"fmt"
"io"
2022-11-18 23:25:40 +00:00
2022-11-18 23:44:44 +00:00
"unlock-music.dev/cli/algo/common"
2020-12-25 20:38:23 +00:00
)
var (
2020-12-25 20:54:59 +00:00
magicHeader = []byte{'i', 'f', 'm', 't'}
magicHeader2 = []byte{0xfe, 0xfe, 0xfe, 0xfe}
typeMapping = map[string]string{
2020-12-25 20:38:23 +00:00
" WAV": "wav",
"FLAC": "flac",
" MP3": "mp3",
" A4M": "m4a",
}
2020-12-25 20:54:59 +00:00
ErrMagicHeader = errors.New("xm magic header not matched")
)
2020-12-25 20:38:23 +00:00
type Decoder struct {
2022-11-19 18:47:28 +00:00
rd io.ReadSeeker // rd is the original file reader
offset int
cipher common.StreamDecoder
2020-12-25 20:38:23 +00:00
outputExt string
}
func (d *Decoder) GetAudioExt() string {
if d.outputExt != "" {
return "." + d.outputExt
}
return ""
2020-12-25 20:38:23 +00:00
}
func NewDecoder(rd io.ReadSeeker) common.Decoder {
return &Decoder{rd: rd}
2020-12-25 20:38:23 +00:00
}
2022-11-19 18:47:28 +00:00
// Validate checks if the file is a valid xiami .xm file.
// rd will set to the beginning of the encrypted audio data.
2020-12-25 20:38:23 +00:00
func (d *Decoder) Validate() error {
header := make([]byte, 16) // xm header is fixed to 16 bytes
if _, err := io.ReadFull(d.rd, header); err != nil {
return fmt.Errorf("xm read header: %w", err)
2020-12-25 20:38:23 +00:00
}
// 0x00 - 0x03 and 0x08 - 0x0B: magic header
if !bytes.Equal(magicHeader, header[:4]) || !bytes.Equal(magicHeader2, header[8:12]) {
2020-12-25 20:54:59 +00:00
return ErrMagicHeader
2020-12-25 20:38:23 +00:00
}
// 0x04 - 0x07: Audio File Type
2020-12-25 20:38:23 +00:00
var ok bool
d.outputExt, ok = typeMapping[string(header[4:8])]
2020-12-25 20:38:23 +00:00
if !ok {
return fmt.Errorf("xm detect unknown audio type: %s", string(header[4:8]))
2020-12-25 20:38:23 +00:00
}
// 0x0C - 0x0E, Encrypt Start At, LittleEndian Unit24
encStartAt := uint32(header[12]) | uint32(header[13])<<8 | uint32(header[14])<<16
// 0x0F, XOR Mask
d.cipher = newXmCipher(header[15], int(encStartAt))
2020-12-25 20:38:23 +00:00
return nil
}
func (d *Decoder) Read(p []byte) (int, error) {
n, err := d.rd.Read(p)
if n > 0 {
d.cipher.Decrypt(p[:n], d.offset)
d.offset += n
}
return n, err
}
2020-12-26 07:47:10 +00:00
func init() {
// Xiami Wav/M4a/Mp3/Flac
2021-11-11 15:43:20 +00:00
common.RegisterDecoder("xm", false, NewDecoder)
2020-12-26 07:47:10 +00:00
// Xiami Typed Format
common.RegisterDecoder("wav", false, NewDecoder)
common.RegisterDecoder("mp3", false, NewDecoder)
common.RegisterDecoder("flac", false, NewDecoder)
common.RegisterDecoder("m4a", false, NewDecoder)
2020-12-26 07:47:10 +00:00
}