cli/algo/kwm/kwm.go

98 lines
2.1 KiB
Go
Raw Normal View History

2020-12-25 20:14:41 +00:00
package kwm
import (
"bytes"
"errors"
"fmt"
"io"
2020-12-25 20:14:41 +00:00
"strconv"
"strings"
"unicode"
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:14:41 +00:00
)
const magicHeader = "yeelion-kuwo-tme"
2020-12-25 20:14:41 +00:00
const keyPreDefined = "MoOtOiTvINGwd2E6n0E1i7L5t2IoOoNk"
type Decoder struct {
2022-11-19 18:47:28 +00:00
rd io.ReadSeeker
2022-11-19 18:47:28 +00:00
cipher common.StreamDecoder
offset int
2020-12-25 20:14:41 +00:00
outputExt string
bitrate int
}
func (d *Decoder) GetAudioExt() string {
return "." + d.outputExt
2020-12-25 20:14:41 +00:00
}
func NewDecoder(rd io.ReadSeeker) common.Decoder {
return &Decoder{rd: rd}
2020-12-25 20:14:41 +00:00
}
2022-11-19 18:47:28 +00:00
// Validate checks if the file is a valid Kuwo .kw file.
// rd will be seeked to the beginning of the encrypted audio.
2020-12-25 20:14:41 +00:00
func (d *Decoder) Validate() error {
header := make([]byte, 0x400) // kwm header is fixed to 1024 bytes
_, err := io.ReadFull(d.rd, header)
if err != nil {
return fmt.Errorf("kwm read header: %w", err)
2020-12-25 20:14:41 +00:00
}
// check magic header, 0x00 - 0x0F
if !bytes.Equal([]byte(magicHeader), header[:len(magicHeader)]) {
return errors.New("kwm magic header not matched")
2020-12-25 20:14:41 +00:00
}
d.cipher = newKwmCipher(header[0x18:0x20]) // Crypto Key, 0x18 - 0x1F
d.bitrate, d.outputExt = parseBitrateAndType(header[0x30:0x38]) // Bitrate & File Extension, 0x30 - 0x38
2020-12-25 20:14:41 +00:00
return nil
2020-12-25 20:14:41 +00:00
}
func parseBitrateAndType(header []byte) (int, string) {
tmp := strings.TrimRight(string(header), "\x00")
sep := strings.IndexFunc(tmp, func(r rune) bool {
return !unicode.IsDigit(r)
})
2020-12-25 20:14:41 +00:00
bitrate, _ := strconv.Atoi(tmp[:sep]) // just ignore the error
outputExt := strings.ToLower(tmp[sep:])
return bitrate, outputExt
}
2020-12-25 20:14:41 +00:00
func (d *Decoder) Read(b []byte) (int, error) {
n, err := d.rd.Read(b)
if n > 0 {
d.cipher.Decrypt(b[:n], d.offset)
d.offset += n
2020-12-25 20:14:41 +00:00
}
return n, err
2020-12-25 20:14:41 +00:00
}
func padOrTruncate(raw string, length int) string {
lenRaw := len(raw)
out := raw
if lenRaw == 0 {
out = string(make([]byte, length))
} else if lenRaw > length {
out = raw[:length]
} else if lenRaw < length {
_tmp := make([]byte, 32)
for i := 0; i < 32; i++ {
_tmp[i] = raw[i%lenRaw]
}
out = string(_tmp)
}
return out
}
2020-12-26 07:47:10 +00:00
func init() {
// Kuwo Mp3/Flac
2021-11-11 15:43:20 +00:00
common.RegisterDecoder("kwm", false, NewDecoder)
common.RegisterDecoder("kwm", false, common.NewRawDecoder)
2020-12-26 07:47:10 +00:00
}