7 Commits

Author SHA1 Message Date
0bb001e7e2 bump version to 1.6.0 2024-09-27 22:23:34 +08:00
b104e1352f feat: allow to specify output dir; process dir recursively 2024-09-27 22:22:56 +08:00
30a50c1eae chore: rename binary 2024-09-16 13:27:40 +08:00
d396c91e90 enhanced: build script 2024-09-16 13:11:04 +08:00
ee68843d9e chore: enhance output 2024-09-15 21:58:12 +08:00
ab1c9fcf0b fix: build script 2024-09-15 21:52:54 +08:00
da31a12acb feat: docs for functions 2024-09-15 21:51:32 +08:00
4 changed files with 154 additions and 44 deletions

View File

@@ -1,13 +1,37 @@
#!/usr/bin/env bash
VERSION=1.6.0
# Clean up the build directory
rm -rf build
mkdir build
# Linux amd64
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o ./build/ncmdump_linux_amd64 ncmdump
echo "Building for Linux amd64..."
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o build/ncmdump github.com/taurusxin/ncmdump-go
tar zcf build/ncmdump_linux_amd64_$VERSION.tar.gz -C build ncmdump
rm build/ncmdump
# Linux arm64
echo "Building for Linux arm64..."
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-w -s" -o build/ncmdump github.com/taurusxin/ncmdump-go
tar zcf build/ncmdump_linux_arm64_$VERSION.tar.gz -C build ncmdump
rm build/ncmdump
# macOS amd64
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags="-w -s" -o ./build/ncmdump_darwin_amd64 ncmdump
echo "Building for macOS amd64..."
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags="-w -s" -o build/ncmdump github.com/taurusxin/ncmdump-go
tar zcf build/ncmdump_darwin_amd64_$VERSION.tar.gz -C build ncmdump
rm build/ncmdump
# macOS arm64
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags="-w -s" -o ./build/ncmdump_darwin_arm64 ncmdump
echo "Building for macOS arm64..."
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags="-w -s" -o build/ncmdump github.com/taurusxin/ncmdump-go
tar zcf build/ncmdump_darwin_arm64_$VERSION.tar.gz -C build ncmdump
rm build/ncmdump
# Windows amd64
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-w -s" -o ./build/ncmdump_windows_amd64.exe ncmdump
echo "Building for Windows amd64..."
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-w -s" -o build/ncmdump.exe github.com/taurusxin/ncmdump-go
zip -q -j build/ncmdump_windows_amd64_$VERSION.zip ./build/ncmdump.exe
rm build/ncmdump.exe

118
main.go
View File

@@ -10,21 +10,27 @@ import (
flag "github.com/spf13/pflag"
)
func processFile(filePath string) error {
func processFile(filePath string, outputDir string) error {
// skip if the extension is not .ncm
if filePath[len(filePath)-4:] != ".ncm" {
return nil
}
// process the file
currentFile, err := ncmcrypt.NewNeteaseCloudMusic(filePath)
if err != nil {
utils.ErrorPrintfln("Reading '%s' failed: '%s'", filePath, err.Error())
utils.ErrorPrintfln("Reading '%s' failed: %s", filePath, err.Error())
return err
}
dump, err := currentFile.Dump(filepath.Dir(filePath))
dump, err := currentFile.Dump(outputDir)
if err != nil {
utils.ErrorPrintfln("Processing '%s' failed: '%s'", filePath, err.Error())
utils.ErrorPrintfln("Processing '%s' failed: %s", filePath, err.Error())
return err
}
if dump {
metadata, err := currentFile.FixMetadata(true)
if !metadata {
utils.WarningPrintfln("Fix metadata for '%s' failed: '%s'", filePath, err.Error())
utils.WarningPrintfln("Fix metadata for '%s' failed: %s", filePath, err.Error())
return err
}
utils.DonePrintfln("'%s' -> '%s'", filePath, currentFile.GetDumpFilePath())
@@ -33,10 +39,13 @@ func processFile(filePath string) error {
}
func main() {
var folderPath string
var sourceDir string
var outputDir string
showHelp := flag.BoolP("help", "h", false, "Display help message")
showVersion := flag.BoolP("version", "v", false, "Display version information")
flag.StringVarP(&folderPath, "dir", "d", "", "Process all files in the directory")
processRecursive := flag.BoolP("recursive", "r", false, "Process all files in the directory recursively")
flag.StringVarP(&outputDir, "output", "o", "", "Output directory for the dump files")
flag.StringVarP(&sourceDir, "dir", "d", "", "Process all files in the directory")
flag.Parse()
if len(os.Args) == 1 {
@@ -50,55 +59,86 @@ func main() {
}
if *showVersion {
fmt.Println("ncmdump version 1.5.0")
fmt.Println("ncmdump version 1.6.0")
os.Exit(0)
}
if folderPath != "" {
// check if the folder exists
info, err := os.Stat(folderPath)
if err != nil {
utils.ErrorPrintfln("Unable to access directory: '%s'", folderPath)
os.Exit(1)
}
if !flag.Lookup("dir").Changed && sourceDir == "" && len(flag.Args()) == 0 {
flag.Usage()
os.Exit(1)
}
if !info.IsDir() {
utils.ErrorPrintfln("Not a directory: '%s'", folderPath)
os.Exit(1)
}
if flag.Lookup("recursive").Changed && !flag.Lookup("dir").Changed {
utils.ErrorPrintfln("The -r option can only be used with the -d option")
os.Exit(1)
}
// dump files in the folder
files, err := os.ReadDir(folderPath)
if err != nil {
utils.ErrorPrintfln("Unable to read directory: '%s'", folderPath)
os.Exit(1)
}
outputDirSpecified := flag.Lookup("output").Changed
for _, file := range files {
if file.IsDir() {
continue
if outputDirSpecified {
if utils.PathExists(outputDir) {
if !utils.IsDir(outputDir) {
utils.ErrorPrintfln("Output directory '%s' is not valid.", outputDir)
os.Exit(1)
}
} else {
_ = os.MkdirAll(outputDir, os.ModePerm)
}
}
filePath := filepath.Join(folderPath, file.Name())
// skip if the extension is not .ncm
if filePath[len(filePath)-4:] != ".ncm" {
continue
}
err = processFile(filePath)
if sourceDir != "" {
if !utils.IsDir(sourceDir) {
utils.ErrorPrintfln("The source directory '%s' is not valid.", sourceDir)
os.Exit(1)
}
if *processRecursive {
_ = filepath.WalkDir(sourceDir, func(p string, d os.DirEntry, err_ error) error {
if !outputDirSpecified {
outputDir = sourceDir
}
relativePath := utils.GetRelativePath(sourceDir, p)
destinationPath := filepath.Join(outputDir, relativePath)
if utils.IsRegularFile(p) {
parentDir := filepath.Dir(destinationPath)
_ = os.MkdirAll(parentDir, os.ModePerm)
_ = processFile(p, parentDir)
}
return nil
})
} else {
// dump files in the folder
files, err := os.ReadDir(sourceDir)
if err != nil {
continue
utils.ErrorPrintfln("Unable to read directory: '%s'", sourceDir)
os.Exit(1)
}
for _, file := range files {
if file.IsDir() {
continue
}
filePath := filepath.Join(sourceDir, file.Name())
if outputDirSpecified {
_ = processFile(filePath, outputDir)
} else {
_ = processFile(filePath, sourceDir)
}
}
}
} else {
// dump file from args
// process files from args
for _, filePath := range flag.Args() {
// skip if the extension is not .ncm
if filePath[len(filePath)-4:] != ".ncm" {
continue
}
err := processFile(filePath)
if err != nil {
continue
if outputDirSpecified {
_ = processFile(filePath, outputDir)
} else {
_ = processFile(filePath, sourceDir)
}
}
}

View File

@@ -111,6 +111,7 @@ func (ncm *NeteaseCloudMusic) mimeType() string {
return "image/jpeg"
}
// Dump encrypted ncm file to normal music file. If `targetDir` is "", the converted file will be saved to the original directory.
func (ncm *NeteaseCloudMusic) Dump(targetDir string) (bool, error) {
ncm.mDumpFilePath = ncm.mFilePath
var outputStream *os.File
@@ -157,6 +158,8 @@ func (ncm *NeteaseCloudMusic) Dump(targetDir string) (bool, error) {
return true, nil
}
// FixMetadata will fix the missing metadata for target music file, the source of the metadata comes from origin ncm file.
// Since NeteaseCloudMusic version 3.0, the album cover image is no longer embedded in the ncm file. If the parameter is true, it means downloading the image from the NetEase server and embedding it into the target music file (network connection required)
func (ncm *NeteaseCloudMusic) FixMetadata(fetchAlbumImageFromRemote bool) (bool, error) {
if fetchAlbumImageFromRemote {
// get the album pic from url
@@ -226,10 +229,16 @@ func (ncm *NeteaseCloudMusic) FixMetadata(fetchAlbumImageFromRemote bool) (bool,
return true, nil
}
// GetDumpFilePath returns the absolute path of dumped music file
func (ncm *NeteaseCloudMusic) GetDumpFilePath() string {
return ncm.mDumpFilePath
path, err := filepath.Abs(ncm.mDumpFilePath)
if err != nil {
return ncm.mDumpFilePath
}
return path
}
// NewNeteaseCloudMusic returns a new NeteaseCloudMusic instance, if the format of the file is incorrect, the error will be returned.
func NewNeteaseCloudMusic(filePath string) (*NeteaseCloudMusic, error) {
ncm := &NeteaseCloudMusic{
sCoreKey: [17]byte{0x68, 0x7A, 0x48, 0x52, 0x41, 0x6D, 0x73, 0x6F, 0x35, 0x6B, 0x49, 0x6E, 0x62, 0x61, 0x78, 0x57, 0},

View File

@@ -1,6 +1,7 @@
package utils
import (
"os"
"path/filepath"
"strings"
)
@@ -9,3 +10,39 @@ func ReplaceExtension(filepathStr, newExt string) string {
ext := filepath.Ext(filepathStr)
return strings.TrimSuffix(filepathStr, ext) + newExt
}
func PathExists(path string) bool {
_, err := os.Stat(path)
if err == nil {
return true
}
if os.IsNotExist(err) {
return false
}
return false
}
func IsDir(path string) bool {
s, err := os.Stat(path)
if err != nil {
return false
}
return s.IsDir()
}
func GetRelativePath(from, to string) string {
rel, err := filepath.Rel(from, to)
if err != nil {
return ""
}
return rel
}
func IsRegularFile(path string) bool {
s, err := os.Stat(path)
if err != nil {
return false
}
return s.Mode().IsRegular()
}