package xm import ( "bytes" "errors" "github.com/umlock-music/cli/algo/common" "github.com/umlock-music/cli/internal/logging" "go.uber.org/zap" ) var ( magicHeader = []byte{'i', 'f', 'm', 't'} magicHeader2 = []byte{0xfe, 0xfe, 0xfe, 0xfe} typeMapping = map[string]string{ " WAV": "wav", "FLAC": "flac", " MP3": "mp3", " A4M": "m4a", } ErrFileSize = errors.New("xm invalid file size") ErrMagicHeader = errors.New("xm magic header not matched") ) type Decoder struct { file []byte headerLen uint32 outputExt string mask byte audio []byte } func (d *Decoder) GetCoverImage() []byte { return nil } func (d *Decoder) GetAudioData() []byte { return d.audio } func (d *Decoder) GetAudioExt() string { return d.outputExt } func (d *Decoder) GetMeta() common.Meta { return nil } func NewDecoder(data []byte) common.Decoder { return &Decoder{file: data} } func (d *Decoder) Validate() error { lenData := len(d.file) if lenData < 16 { return ErrFileSize } if !bytes.Equal(magicHeader, d.file[:4]) || !bytes.Equal(magicHeader2, d.file[8:12]) { return ErrMagicHeader } var ok bool d.outputExt, ok = typeMapping[string(d.file[4:8])] if !ok { return errors.New("detect unknown xm file type: " + string(d.file[4:8])) } if d.file[14] != 0 { logging.Log().Warn("not a simple xm file", zap.Uint8("b[14]", d.file[14])) } d.headerLen = uint32(d.file[12]) | uint32(d.file[13])<<8 | uint32(d.file[14])<<16 // LittleEndian Unit24 if d.headerLen+16 > uint32(lenData) { return ErrFileSize } return nil } func (d *Decoder) Decode() error { d.mask = d.file[15] d.audio = d.file[16:] dataLen := uint32(len(d.audio)) for i := d.headerLen; i < dataLen; i++ { d.audio[i] = ^(d.audio[i] - d.mask) } return nil } func DecoderFuncWithExt(ext string) common.NewDecoderFunc { return func(file []byte) common.Decoder { return &Decoder{file: file, outputExt: ext} } } func init() { // Xiami Wav/M4a/Mp3/Flac common.RegisterDecoder("xm", NewDecoder) // Xiami Typed Format common.RegisterDecoder("wav", DecoderFuncWithExt("wav")) common.RegisterDecoder("mp3", DecoderFuncWithExt("mp3")) common.RegisterDecoder("flac", DecoderFuncWithExt("flac")) common.RegisterDecoder("m4a", DecoderFuncWithExt("m4a")) }