123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101 |
- package kwm
- import (
- "bytes"
- "errors"
- "fmt"
- "io"
- "strconv"
- "strings"
- "unicode"
- "unlock-music.dev/cli/algo/common"
- )
- const magicHeader1 = "yeelion-kuwo-tme"
- const magicHeader2 = "yeelion-kuwo\x00\x00\x00\x00"
- const keyPreDefined = "MoOtOiTvINGwd2E6n0E1i7L5t2IoOoNk"
- type Decoder struct {
- rd io.ReadSeeker
- cipher common.StreamDecoder
- offset int
- outputExt string
- bitrate int
- }
- func (d *Decoder) GetAudioExt() string {
- return "." + d.outputExt
- }
- func NewDecoder(p *common.DecoderParams) common.Decoder {
- return &Decoder{rd: p.Reader}
- }
- // Validate checks if the file is a valid Kuwo .kw file.
- // rd will be seeked to the beginning of the encrypted audio.
- 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)
- }
- // check magic header, 0x00 - 0x0F
- magicHeader := header[:0x10]
- if !bytes.Equal([]byte(magicHeader1), magicHeader) &&
- !bytes.Equal([]byte(magicHeader2), magicHeader) {
- return errors.New("kwm magic header not matched")
- }
- d.cipher = newKwmCipher(header[0x18:0x20]) // Crypto Key, 0x18 - 0x1F
- d.bitrate, d.outputExt = parseBitrateAndType(header[0x30:0x38]) // Bitrate & File Extension, 0x30 - 0x38
- return nil
- }
- func parseBitrateAndType(header []byte) (int, string) {
- tmp := strings.TrimRight(string(header), "\x00")
- sep := strings.IndexFunc(tmp, func(r rune) bool {
- return !unicode.IsDigit(r)
- })
- bitrate, _ := strconv.Atoi(tmp[:sep]) // just ignore the error
- outputExt := strings.ToLower(tmp[sep:])
- return bitrate, outputExt
- }
- 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
- }
- return n, err
- }
- 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
- }
- func init() {
- // Kuwo Mp3/Flac
- common.RegisterDecoder("kwm", false, NewDecoder)
- common.RegisterDecoder("kwm", false, common.NewRawDecoder)
- }
|