This commit is contained in:
pemako 2022-09-02 16:38:33 +00:00 committed by GitHub
commit 04539a138d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 104 additions and 40 deletions

View File

@ -18,6 +18,7 @@ func RegisterDecoder(ext string, noop bool, dispatchFunc NewDecoderFunc) {
DecoderRegistry[ext] = append(DecoderRegistry[ext], DecoderRegistry[ext] = append(DecoderRegistry[ext],
decoderItem{noop: noop, decoder: dispatchFunc}) decoderItem{noop: noop, decoder: dispatchFunc})
} }
func GetDecoder(filename string, skipNoop bool) (rs []NewDecoderFunc) { func GetDecoder(filename string, skipNoop bool) (rs []NewDecoderFunc) {
ext := strings.ToLower(strings.TrimLeft(filepath.Ext(filename), ".")) ext := strings.ToLower(strings.TrimLeft(filepath.Ext(filename), "."))
for _, dec := range DecoderRegistry[ext] { for _, dec := range DecoderRegistry[ext] {

View File

@ -35,15 +35,19 @@ func SnifferOGG(header []byte) bool {
func SnifferFLAC(header []byte) bool { func SnifferFLAC(header []byte) bool {
return bytes.HasPrefix(header, []byte("fLaC")) return bytes.HasPrefix(header, []byte("fLaC"))
} }
func SnifferMP3(header []byte) bool { func SnifferMP3(header []byte) bool {
return bytes.HasPrefix(header, []byte("ID3")) return bytes.HasPrefix(header, []byte("ID3"))
} }
func SnifferWAV(header []byte) bool { func SnifferWAV(header []byte) bool {
return bytes.HasPrefix(header, []byte("RIFF")) return bytes.HasPrefix(header, []byte("RIFF"))
} }
func SnifferWMA(header []byte) bool { func SnifferWMA(header []byte) bool {
return bytes.HasPrefix(header, []byte("\x30\x26\xb2\x75\x8e\x66\xcf\x11\xa6\xd9\x00\xaa\x00\x62\xce\x6c")) return bytes.HasPrefix(header, []byte("\x30\x26\xb2\x75\x8e\x66\xcf\x11\xa6\xd9\x00\xaa\x00\x62\xce\x6c"))
} }
func SnifferAAC(header []byte) bool { func SnifferAAC(header []byte) bool {
return bytes.HasPrefix(header, []byte{0xFF, 0xF1}) return bytes.HasPrefix(header, []byte{0xFF, 0xF1})
} }

View File

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"errors" "errors"
"github.com/unlock-music/cli/algo/common" "github.com/unlock-music/cli/algo/common"
"github.com/unlock-music/cli/internal/logging" "github.com/unlock-music/cli/internal/logging"
) )
@ -59,6 +60,7 @@ func (d *Decoder) Validate() error {
d.key = d.file[0x1c:0x2c] d.key = d.file[0x1c:0x2c]
d.key = append(d.key, 0x00) d.key = append(d.key, 0x00)
_ = d.file[0x2c:0x3c] //todo: key2 _ = d.file[0x2c:0x3c] //todo: key2
return nil return nil
} }
@ -67,6 +69,7 @@ func (d *Decoder) Decode() error {
dataEncrypted := d.file[headerLen:] dataEncrypted := d.file[headerLen:]
lenData := len(dataEncrypted) lenData := len(dataEncrypted)
initMask() initMask()
if fullMaskLen < lenData { if fullMaskLen < lenData {
logging.Log().Warn("The file is too large and the processed audio is incomplete, " + logging.Log().Warn("The file is too large and the processed audio is incomplete, " +
"please report to us about this file at https://github.com/unlock-music/cli/issues") "please report to us about this file at https://github.com/unlock-music/cli/issues")
@ -78,13 +81,16 @@ func (d *Decoder) Decode() error {
med8 := dataEncrypted[i] ^ d.key[i%17] ^ maskV2PreDef[i%(16*17)] ^ maskV2[i>>4] med8 := dataEncrypted[i] ^ d.key[i%17] ^ maskV2PreDef[i%(16*17)] ^ maskV2[i>>4]
d.audio[i] = med8 ^ (med8&0xf)<<4 d.audio[i] = med8 ^ (med8&0xf)<<4
} }
if d.isVpr { if d.isVpr {
for i := 0; i < lenData; i++ { for i := 0; i < lenData; i++ {
d.audio[i] ^= maskDiffVpr[i%17] d.audio[i] ^= maskDiffVpr[i%17]
} }
} }
return nil return nil
} }
func init() { func init() {
// Kugou // Kugou
common.RegisterDecoder("kgm", false, NewDecoder) common.RegisterDecoder("kgm", false, NewDecoder)

View File

@ -3,10 +3,11 @@ package kgm
import ( import (
"bytes" "bytes"
_ "embed" _ "embed"
"io"
"github.com/ulikunitz/xz" "github.com/ulikunitz/xz"
"github.com/unlock-music/cli/internal/logging" "github.com/unlock-music/cli/internal/logging"
"go.uber.org/zap" "go.uber.org/zap"
"io"
) )
var maskDiffVpr = []byte{ var maskDiffVpr = []byte{
@ -41,7 +42,7 @@ var maskV2 []byte
var fullMaskLen int var fullMaskLen int
var initMaskOK = false var initMaskOK = false
//todo: decompress mask on demand // TODO: decompress mask on demand
func initMask() { func initMask() {
if initMaskOK { if initMaskOK {
return return

View File

@ -4,10 +4,11 @@ import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"errors" "errors"
"github.com/unlock-music/cli/algo/common"
"strconv" "strconv"
"strings" "strings"
"unicode" "unicode"
"github.com/unlock-music/cli/algo/common"
) )
var ( var (
@ -57,6 +58,7 @@ func (d *Decoder) Validate() error {
if lenData < 1024 { if lenData < 1024 {
return ErrKwFileSize return ErrKwFileSize
} }
if !bytes.Equal(magicHeader, d.file[:16]) { if !bytes.Equal(magicHeader, d.file[:16]) {
return ErrKwMagicHeader return ErrKwMagicHeader
} }
@ -69,9 +71,11 @@ func generateMask(key []byte) []byte {
keyStr := strconv.FormatUint(keyInt, 10) keyStr := strconv.FormatUint(keyInt, 10)
keyStrTrim := padOrTruncate(keyStr, 32) keyStrTrim := padOrTruncate(keyStr, 32)
mask := make([]byte, 32) mask := make([]byte, 32)
for i := 0; i < 32; i++ { for i := 0; i < 32; i++ {
mask[i] = keyPreDefined[i] ^ keyStrTrim[i] mask[i] = keyPreDefined[i] ^ keyStrTrim[i]
} }
return mask return mask
} }
@ -83,13 +87,13 @@ func (d *Decoder) parseBitrateAndType() {
break break
} }
} }
var err error var err error
d.bitrate, err = strconv.Atoi(bitType[:charPos]) d.bitrate, err = strconv.Atoi(bitType[:charPos])
if err != nil { if err != nil {
d.bitrate = 0 d.bitrate = 0
} }
d.outputExt = strings.ToLower(bitType[charPos:]) d.outputExt = strings.ToLower(bitType[charPos:])
} }
func (d *Decoder) Decode() error { func (d *Decoder) Decode() error {
@ -102,6 +106,7 @@ func (d *Decoder) Decode() error {
for i := 0; i < dataLen; i++ { for i := 0; i < dataLen; i++ {
d.audio[i] ^= d.mask[i&0x1F] //equals: [i % 32] d.audio[i] ^= d.mask[i&0x1F] //equals: [i % 32]
} }
return nil return nil
} }

View File

@ -1,8 +1,9 @@
package ncm package ncm
import ( import (
"github.com/unlock-music/cli/algo/common"
"strings" "strings"
"github.com/unlock-music/cli/algo/common"
) )
type RawMeta interface { type RawMeta interface {
@ -10,6 +11,7 @@ type RawMeta interface {
GetFormat() string GetFormat() string
GetAlbumImageURL() string GetAlbumImageURL() string
} }
type RawMetaMusic struct { type RawMetaMusic struct {
Format string `json:"format"` Format string `json:"format"`
MusicID int `json:"musicId"` MusicID int `json:"musicId"`
@ -30,11 +32,11 @@ type RawMetaMusic struct {
func (m RawMetaMusic) GetAlbumImageURL() string { func (m RawMetaMusic) GetAlbumImageURL() string {
return m.AlbumPic return m.AlbumPic
} }
func (m RawMetaMusic) GetArtists() (artists []string) { func (m RawMetaMusic) GetArtists() (artists []string) {
for _, artist := range m.Artist { for _, artist := range m.Artist {
for _, item := range artist { for _, item := range artist {
name, ok := item.(string) if name, ok := item.(string); ok {
if ok {
artists = append(artists, name) artists = append(artists, name)
} }
} }
@ -49,6 +51,7 @@ func (m RawMetaMusic) GetTitle() string {
func (m RawMetaMusic) GetAlbum() string { func (m RawMetaMusic) GetAlbum() string {
return m.Album return m.Album
} }
func (m RawMetaMusic) GetFormat() string { func (m RawMetaMusic) GetFormat() string {
return m.Format return m.Format
} }

View File

@ -6,13 +6,14 @@ import (
"encoding/binary" "encoding/binary"
"encoding/json" "encoding/json"
"errors" "errors"
"io/ioutil"
"net/http"
"strings"
"github.com/unlock-music/cli/algo/common" "github.com/unlock-music/cli/algo/common"
"github.com/unlock-music/cli/internal/logging" "github.com/unlock-music/cli/internal/logging"
"github.com/unlock-music/cli/internal/utils" "github.com/unlock-music/cli/internal/utils"
"go.uber.org/zap" "go.uber.org/zap"
"io/ioutil"
"net/http"
"strings"
) )
var ( var (
@ -65,6 +66,7 @@ func (d *Decoder) readKeyData() error {
if d.offsetKey == 0 || d.offsetKey+4 > d.fileLen { if d.offsetKey == 0 || d.offsetKey+4 > d.fileLen {
return errors.New("invalid cover file offset") return errors.New("invalid cover file offset")
} }
bKeyLen := d.file[d.offsetKey : d.offsetKey+4] bKeyLen := d.file[d.offsetKey : d.offsetKey+4]
iKeyLen := binary.LittleEndian.Uint32(bKeyLen) iKeyLen := binary.LittleEndian.Uint32(bKeyLen)
d.offsetMeta = d.offsetKey + 4 + iKeyLen d.offsetMeta = d.offsetKey + 4 + iKeyLen
@ -82,9 +84,11 @@ func (d *Decoder) readMetaData() error {
if d.offsetMeta == 0 || d.offsetMeta+4 > d.fileLen { if d.offsetMeta == 0 || d.offsetMeta+4 > d.fileLen {
return errors.New("invalid meta file offset") return errors.New("invalid meta file offset")
} }
bMetaLen := d.file[d.offsetMeta : d.offsetMeta+4] bMetaLen := d.file[d.offsetMeta : d.offsetMeta+4]
iMetaLen := binary.LittleEndian.Uint32(bMetaLen) iMetaLen := binary.LittleEndian.Uint32(bMetaLen)
d.offsetCover = d.offsetMeta + 4 + iMetaLen d.offsetCover = d.offsetMeta + 4 + iMetaLen
if iMetaLen == 0 { if iMetaLen == 0 {
return errors.New("no any meta file found") return errors.New("no any meta file found")
} }
@ -159,7 +163,9 @@ func (d *Decoder) readCoverData() error {
if iCoverLen == 0 { if iCoverLen == 0 {
return errors.New("no any cover file found") return errors.New("no any cover file found")
} }
d.cover = d.file[coverLenStart+4 : 4+coverLenStart+iCoverLen] d.cover = d.file[coverLenStart+4 : 4+coverLenStart+iCoverLen]
return nil return nil
} }
@ -167,6 +173,7 @@ func (d *Decoder) readAudioData() error {
if d.offsetAudio == 0 || d.offsetAudio > d.fileLen { if d.offsetAudio == 0 || d.offsetAudio > d.fileLen {
return errors.New("invalid audio offset") return errors.New("invalid audio offset")
} }
audioRaw := d.file[d.offsetAudio:] audioRaw := d.file[d.offsetAudio:]
audioLen := len(audioRaw) audioLen := len(audioRaw)
d.audio = make([]byte, audioLen) d.audio = make([]byte, audioLen)
@ -186,6 +193,7 @@ func (d *Decoder) Decode() error {
if err == nil { if err == nil {
err = d.parseMeta() err = d.parseMeta()
} }
if err != nil { if err != nil {
logging.Log().Warn("parse ncm meta file failed", zap.Error(err)) logging.Log().Warn("parse ncm meta file failed", zap.Error(err))
} }
@ -215,27 +223,32 @@ func (d Decoder) GetCoverImage() []byte {
if d.cover != nil { if d.cover != nil {
return d.cover return d.cover
} }
{ {
imgURL := d.meta.GetAlbumImageURL() imgURL := d.meta.GetAlbumImageURL()
if d.meta != nil && !strings.HasPrefix(imgURL, "http") { if d.meta != nil && !strings.HasPrefix(imgURL, "http") {
return nil return nil
} }
resp, err := http.Get(imgURL) resp, err := http.Get(imgURL)
if err != nil { if err != nil {
logging.Log().Warn("download image failed", zap.Error(err), zap.String("url", imgURL)) logging.Log().Warn("download image failed", zap.Error(err), zap.String("url", imgURL))
return nil return nil
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
logging.Log().Warn("download image failed", zap.String("http", resp.Status), logging.Log().Warn("download image failed", zap.String("http", resp.Status),
zap.String("url", imgURL)) zap.String("url", imgURL))
return nil return nil
} }
data, err := ioutil.ReadAll(resp.Body) data, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
logging.Log().Warn("download image failed", zap.Error(err), zap.String("url", imgURL)) logging.Log().Warn("download image failed", zap.Error(err), zap.String("url", imgURL))
return nil return nil
} }
return data return data
} }
} }

View File

@ -22,6 +22,7 @@ func loadTestDataMapCipher(name string) ([]byte, []byte, []byte, error) {
} }
return key, raw, target, nil return key, raw, target, nil
} }
func Test_mapCipher_Decrypt(t *testing.T) { func Test_mapCipher_Decrypt(t *testing.T) {
tests := []struct { tests := []struct {

View File

@ -4,7 +4,7 @@ import (
"errors" "errors"
) )
// A rc4Cipher is an instance of RC4 using a particular key. // rc4Cipher is an instance of RC4 using a particular key.
type rc4Cipher struct { type rc4Cipher struct {
box []byte box []byte
key []byte key []byte
@ -87,6 +87,7 @@ func (c *rc4Cipher) Decrypt(src []byte, offset int) {
return return
} }
} }
for toProcess > rc4SegmentSize { for toProcess > rc4SegmentSize {
c.encASegment(src[processed:processed+rc4SegmentSize], offset) c.encASegment(src[processed:processed+rc4SegmentSize], offset)
markProcess(rc4SegmentSize) markProcess(rc4SegmentSize)
@ -96,6 +97,7 @@ func (c *rc4Cipher) Decrypt(src []byte, offset int) {
c.encASegment(src[processed:], offset) c.encASegment(src[processed:], offset)
} }
} }
func (c *rc4Cipher) encFirstSegment(buf []byte, offset int) { func (c *rc4Cipher) encFirstSegment(buf []byte, offset int) {
for i := 0; i < len(buf); i++ { for i := 0; i < len(buf); i++ {
buf[i] ^= c.key[c.getSegmentSkip(offset+i)] buf[i] ^= c.key[c.getSegmentSkip(offset+i)]
@ -117,6 +119,7 @@ func (c *rc4Cipher) encASegment(buf []byte, offset int) {
} }
} }
} }
func (c *rc4Cipher) getSegmentSkip(id int) int { func (c *rc4Cipher) getSegmentSkip(id int) int {
seed := int(c.key[id%c.n]) seed := int(c.key[id%c.n])
idx := int64(float64(c.hash) / float64((id+1)*seed) * 100.0) idx := int64(float64(c.hash) / float64((id+1)*seed) * 100.0)

View File

@ -13,6 +13,7 @@ func (c *staticCipher) Decrypt(buf []byte, offset int) {
buf[i] ^= c.getMask(offset + i) buf[i] ^= c.getMask(offset + i)
} }
} }
func (c *staticCipher) getMask(offset int) byte { func (c *staticCipher) getMask(offset int) byte {
if offset > 0x7FFF { if offset > 0x7FFF {
offset %= 0x7FFF offset %= 0x7FFF

View File

@ -16,17 +16,19 @@ func simpleMakeKey(salt byte, length int) []byte {
} }
return keyBuf return keyBuf
} }
func DecryptKey(rawKey []byte) ([]byte, error) { func DecryptKey(rawKey []byte) ([]byte, error) {
rawKeyDec := make([]byte, base64.StdEncoding.DecodedLen(len(rawKey))) rawKeyDec := make([]byte, base64.StdEncoding.DecodedLen(len(rawKey)))
n, err := base64.StdEncoding.Decode(rawKeyDec, rawKey) n, err := base64.StdEncoding.Decode(rawKeyDec, rawKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if n < 16 { if n < 16 {
return nil, errors.New("key length is too short") return nil, errors.New("key length is too short")
} }
rawKeyDec = rawKeyDec[:n]
rawKeyDec = rawKeyDec[:n]
simpleKey := simpleMakeKey(106, 8) simpleKey := simpleMakeKey(106, 8)
teaKey := make([]byte, 16) teaKey := make([]byte, 16)
for i := 0; i < 8; i++ { for i := 0; i < 8; i++ {
@ -38,14 +40,17 @@ func DecryptKey(rawKey []byte) ([]byte, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return append(rawKeyDec[:8], rs...), nil return append(rawKeyDec[:8], rs...), nil
} }
func decryptTencentTea(inBuf []byte, key []byte) ([]byte, error) { func decryptTencentTea(inBuf []byte, key []byte) ([]byte, error) {
const saltLen = 2 const saltLen = 2
const zeroLen = 7 const zeroLen = 7
if len(inBuf)%8 != 0 { if len(inBuf)%8 != 0 {
return nil, errors.New("inBuf size not a multiple of the block size") return nil, errors.New("inBuf size not a multiple of the block size")
} }
if len(inBuf) < 16 { if len(inBuf) < 16 {
return nil, errors.New("inBuf size too small") return nil, errors.New("inBuf size too small")
} }
@ -62,8 +67,8 @@ func decryptTencentTea(inBuf []byte, key []byte) ([]byte, error) {
if padLen+saltLen != 8 { if padLen+saltLen != 8 {
return nil, errors.New("invalid pad len") return nil, errors.New("invalid pad len")
} }
out := make([]byte, outLen)
out := make([]byte, outLen)
ivPrev := make([]byte, 8) ivPrev := make([]byte, 8)
ivCur := inBuf[:8] ivCur := inBuf[:8]
@ -80,6 +85,7 @@ func decryptTencentTea(inBuf []byte, key []byte) ([]byte, error) {
inBufPos += 8 inBufPos += 8
destIdx = 0 destIdx = 0
} }
for i := 1; i <= saltLen; { for i := 1; i <= saltLen; {
if destIdx < 8 { if destIdx < 8 {
destIdx++ destIdx++
@ -108,6 +114,7 @@ func decryptTencentTea(inBuf []byte, key []byte) ([]byte, error) {
return out, nil return out, nil
} }
func xor8Bytes(dst, a, b []byte) { func xor8Bytes(dst, a, b []byte) {
for i := 0; i < 8; i++ { for i := 0; i < 8; i++ {
dst[i] = a[i] ^ b[i] dst[i] = a[i] ^ b[i]

View File

@ -15,6 +15,7 @@ func TestSimpleMakeKey(t *testing.T) {
} }
}) })
} }
func loadDecryptKeyData(name string) ([]byte, []byte, error) { func loadDecryptKeyData(name string) ([]byte, []byte, error) {
keyRaw, err := os.ReadFile(fmt.Sprintf("./testdata/%s_key_raw.bin", name)) keyRaw, err := os.ReadFile(fmt.Sprintf("./testdata/%s_key_raw.bin", name))
if err != nil { if err != nil {
@ -26,6 +27,7 @@ func loadDecryptKeyData(name string) ([]byte, []byte, error) {
} }
return keyRaw, keyDec, nil return keyRaw, keyDec, nil
} }
func TestDecryptKey(t *testing.T) { func TestDecryptKey(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
@ -37,6 +39,7 @@ func TestDecryptKey(t *testing.T) {
{"mflac_rc4(256)", "mflac_rc4", false}, {"mflac_rc4(256)", "mflac_rc4", false},
{"mgg_map(256)", "mgg_map", false}, {"mgg_map(256)", "mgg_map", false},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
raw, want, err := loadDecryptKeyData(tt.filename) raw, want, err := loadDecryptKeyData(tt.filename)

View File

@ -100,10 +100,12 @@ func (d *Decoder) searchKey() error {
if err != nil { if err != nil {
return err return err
} }
buf, err := io.ReadAll(io.LimitReader(d.r, 4)) buf, err := io.ReadAll(io.LimitReader(d.r, 4))
if err != nil { if err != nil {
return err return err
} }
if string(buf) == "QTag" { if string(buf) == "QTag" {
if err := d.readRawMetaQTag(); err != nil { if err := d.readRawMetaQTag(); err != nil {
return err return err
@ -118,6 +120,7 @@ func (d *Decoder) searchKey() error {
return nil return nil
} }
} }
return nil return nil
} }
@ -146,10 +149,12 @@ func (d *Decoder) readRawMetaQTag() error {
if _, err := d.r.Seek(-8, io.SeekEnd); err != nil { if _, err := d.r.Seek(-8, io.SeekEnd); err != nil {
return err return err
} }
buf, err := io.ReadAll(io.LimitReader(d.r, 4)) buf, err := io.ReadAll(io.LimitReader(d.r, 4))
if err != nil { if err != nil {
return err return err
} }
rawMetaLen := int64(binary.BigEndian.Uint32(buf)) rawMetaLen := int64(binary.BigEndian.Uint32(buf))
// read raw meta data // read raw meta data
@ -157,6 +162,7 @@ func (d *Decoder) readRawMetaQTag() error {
if err != nil { if err != nil {
return err return err
} }
d.audioLen = int(audioLen) d.audioLen = int(audioLen)
rawMetaData, err := io.ReadAll(io.LimitReader(d.r, rawMetaLen)) rawMetaData, err := io.ReadAll(io.LimitReader(d.r, rawMetaLen))
if err != nil { if err != nil {
@ -177,6 +183,7 @@ func (d *Decoder) readRawMetaQTag() error {
if err != nil { if err != nil {
return err return err
} }
d.rawMetaExtra2, err = strconv.Atoi(items[2]) d.rawMetaExtra2, err = strconv.Atoi(items[2])
if err != nil { if err != nil {
return err return err

View File

@ -24,8 +24,8 @@ func loadTestDataQmcDecoder(filename string) ([]byte, []byte, error) {
return nil, nil, err return nil, nil, err
} }
return bytes.Join([][]byte{encBody, encSuffix}, nil), target, nil return bytes.Join([][]byte{encBody, encSuffix}, nil), target, nil
} }
func TestMflac0Decoder_Read(t *testing.T) { func TestMflac0Decoder_Read(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
@ -60,7 +60,6 @@ func TestMflac0Decoder_Read(t *testing.T) {
} }
}) })
} }
} }
func TestMflac0Decoder_Validate(t *testing.T) { func TestMflac0Decoder_Validate(t *testing.T) {

View File

@ -3,6 +3,7 @@ package tm
import ( import (
"bytes" "bytes"
"errors" "errors"
"github.com/unlock-music/cli/algo/common" "github.com/unlock-music/cli/algo/common"
) )
@ -75,5 +76,4 @@ func init() {
// QQ Music IOS Mp3 // QQ Music IOS Mp3
common.RegisterDecoder("tm0", false, common.NewRawDecoder) common.RegisterDecoder("tm0", false, common.NewRawDecoder)
common.RegisterDecoder("tm3", false, common.NewRawDecoder) common.RegisterDecoder("tm3", false, common.NewRawDecoder)
} }

View File

@ -3,6 +3,7 @@ package xm
import ( import (
"bytes" "bytes"
"errors" "errors"
"github.com/unlock-music/cli/algo/common" "github.com/unlock-music/cli/algo/common"
"github.com/unlock-music/cli/internal/logging" "github.com/unlock-music/cli/internal/logging"
"go.uber.org/zap" "go.uber.org/zap"
@ -58,6 +59,7 @@ func (d *Decoder) Validate() error {
if lenData < 16 { if lenData < 16 {
return ErrFileSize return ErrFileSize
} }
if !bytes.Equal(magicHeader, d.file[:4]) || if !bytes.Equal(magicHeader, d.file[:4]) ||
!bytes.Equal(magicHeader2, d.file[8:12]) { !bytes.Equal(magicHeader2, d.file[8:12]) {
return ErrMagicHeader return ErrMagicHeader
@ -76,6 +78,7 @@ func (d *Decoder) Validate() error {
if d.headerLen+16 > uint32(lenData) { if d.headerLen+16 > uint32(lenData) {
return ErrFileSize return ErrFileSize
} }
return nil return nil
} }

View File

@ -9,17 +9,17 @@ import (
"sort" "sort"
"strings" "strings"
"github.com/unlock-music/cli/algo/common"
"github.com/unlock-music/cli/internal/logging"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"go.uber.org/zap" "go.uber.org/zap"
"github.com/unlock-music/cli/algo/common"
_ "github.com/unlock-music/cli/algo/kgm" _ "github.com/unlock-music/cli/algo/kgm"
_ "github.com/unlock-music/cli/algo/kwm" _ "github.com/unlock-music/cli/algo/kwm"
_ "github.com/unlock-music/cli/algo/ncm" _ "github.com/unlock-music/cli/algo/ncm"
_ "github.com/unlock-music/cli/algo/qmc" _ "github.com/unlock-music/cli/algo/qmc"
_ "github.com/unlock-music/cli/algo/tm" _ "github.com/unlock-music/cli/algo/tm"
_ "github.com/unlock-music/cli/algo/xm" _ "github.com/unlock-music/cli/algo/xm"
"github.com/unlock-music/cli/internal/logging"
) )
var AppVersion = "v0.0.6" var AppVersion = "v0.0.6"
@ -43,26 +43,30 @@ func main() {
HideHelpCommand: true, HideHelpCommand: true,
UsageText: "um [-o /path/to/output/dir] [--extra-flags] [-i] /path/to/input", UsageText: "um [-o /path/to/output/dir] [--extra-flags] [-i] /path/to/input",
} }
err := app.Run(os.Args)
if err != nil { if err := app.Run(os.Args); err != nil {
logging.Log().Fatal("run app failed", zap.Error(err)) logging.Log().Fatal("run app failed", zap.Error(err))
} }
} }
func printSupportedExtensions() { func printSupportedExtensions() {
exts := []string{} exts := []string{}
for ext := range common.DecoderRegistry { for ext := range common.DecoderRegistry {
exts = append(exts, ext) exts = append(exts, ext)
} }
sort.Strings(exts) sort.Strings(exts)
for _, ext := range exts { for _, ext := range exts {
fmt.Printf("%s: %d\n", ext, len(common.DecoderRegistry[ext])) fmt.Printf("%s: %d\n", ext, len(common.DecoderRegistry[ext]))
} }
} }
func appMain(c *cli.Context) (err error) { func appMain(c *cli.Context) (err error) {
if c.Bool("supported-ext") { if c.Bool("supported-ext") {
printSupportedExtensions() printSupportedExtensions()
return nil return nil
} }
input := c.String("input") input := c.String("input")
if input == "" { if input == "" {
switch c.Args().Len() { switch c.Args().Len() {
@ -90,9 +94,6 @@ func appMain(c *cli.Context) (err error) {
} }
} }
skipNoop := c.Bool("skip-noop")
removeSource := c.Bool("remove-source")
inputStat, err := os.Stat(input) inputStat, err := os.Stat(input)
if err != nil { if err != nil {
return err return err
@ -110,26 +111,32 @@ func appMain(c *cli.Context) (err error) {
return errors.New("output should be a writable directory") return errors.New("output should be a writable directory")
} }
skipNoop := c.Bool("skip-noop")
removeSource := c.Bool("remove-source")
if inputStat.IsDir() { if inputStat.IsDir() {
return dealDirectory(input, output, skipNoop, removeSource) return dealDirectory(input, output, skipNoop, removeSource)
} else {
allDec := common.GetDecoder(inputStat.Name(), skipNoop)
if len(allDec) == 0 {
logging.Log().Fatal("skipping while no suitable decoder")
}
return tryDecFile(input, output, allDec, removeSource)
} }
allDec := common.GetDecoder(inputStat.Name(), skipNoop)
if len(allDec) == 0 {
logging.Log().Fatal("skipping while no suitable decoder")
}
return tryDecFile(input, output, allDec, removeSource)
} }
func dealDirectory(inputDir string, outputDir string, skipNoop bool, removeSource bool) error { func dealDirectory(inputDir string, outputDir string, skipNoop bool, removeSource bool) error {
items, err := os.ReadDir(inputDir) items, err := os.ReadDir(inputDir)
if err != nil { if err != nil {
return err return err
} }
for _, item := range items { for _, item := range items {
if item.IsDir() { if item.IsDir() {
continue continue
} }
allDec := common.GetDecoder(item.Name(), skipNoop) allDec := common.GetDecoder(item.Name(), skipNoop)
if len(allDec) == 0 { if len(allDec) == 0 {
logging.Log().Info("skipping while no suitable decoder", zap.String("file", item.Name())) logging.Log().Info("skipping while no suitable decoder", zap.String("file", item.Name()))
@ -141,6 +148,7 @@ func dealDirectory(inputDir string, outputDir string, skipNoop bool, removeSourc
logging.Log().Error("conversion failed", zap.String("source", item.Name())) logging.Log().Error("conversion failed", zap.String("source", item.Name()))
} }
} }
return nil return nil
} }
@ -159,9 +167,11 @@ func tryDecFile(inputFile string, outputDir string, allDec []common.NewDecoderFu
logging.Log().Warn("try decode failed", zap.Error(err)) logging.Log().Warn("try decode failed", zap.Error(err))
dec = nil dec = nil
} }
if dec == nil { if dec == nil {
return errors.New("no any decoder can resolve the file") return errors.New("no any decoder can resolve the file")
} }
if err := dec.Decode(); err != nil { if err := dec.Decode(); err != nil {
return errors.New("failed while decoding: " + err.Error()) return errors.New("failed while decoding: " + err.Error())
} }
@ -178,21 +188,20 @@ func tryDecFile(inputFile string, outputDir string, allDec []common.NewDecoderFu
filenameOnly := strings.TrimSuffix(filepath.Base(inputFile), filepath.Ext(inputFile)) filenameOnly := strings.TrimSuffix(filepath.Base(inputFile), filepath.Ext(inputFile))
outPath := filepath.Join(outputDir, filenameOnly+outExt) outPath := filepath.Join(outputDir, filenameOnly+outExt)
err = os.WriteFile(outPath, outData, 0644) if err = os.WriteFile(outPath, outData, 0644); err != nil {
if err != nil {
return err return err
} }
// if source file need to be removed // if source file need to be removed
if removeSource { if !removeSource {
err := os.RemoveAll(inputFile)
if err != nil {
return err
}
logging.Log().Info("successfully converted, and source file is removed", zap.String("source", inputFile), zap.String("destination", outPath))
} else {
logging.Log().Info("successfully converted", zap.String("source", inputFile), zap.String("destination", outPath)) logging.Log().Info("successfully converted", zap.String("source", inputFile), zap.String("destination", outPath))
return nil
} }
if err := os.RemoveAll(inputFile); err != nil {
return err
}
logging.Log().Info("successfully converted, and source file is removed", zap.String("source", inputFile), zap.String("destination", outPath))
return nil return nil
} }

View File

@ -4,9 +4,7 @@ import (
"os" "os"
"sync" "sync"
"time" "time"
)
import (
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zapcore" "go.uber.org/zap/zapcore"
) )