3 Commits

Author SHA1 Message Date
cf39731cb4 update: README.md 2024-09-27 22:25:42 +08:00
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
4 changed files with 154 additions and 44 deletions

View File

@@ -2,19 +2,52 @@
基于 https://github.com/taurusxin/ncmdump 的 Golang 移植版 基于 https://github.com/taurusxin/ncmdump 的 Golang 移植版
支持网易云音乐最新的 3.x 版本 支持网易云音乐最新的 3.x 版本,但需要注意:从该版本开始网易云音乐不再在 ncm 文件中内置封面图片,本工具支持从网易服务器上自动下载对应歌曲的封面图并写入到最终的音乐文件中
## 使用方法 ## 使用方法
```shell 使用 `-h``--help` 参数来打印帮助
# 处理单个或多个文件
ncmdump test1.ncm test2.ncm...
# 处理 Music 文件夹下的所有文件 ```shell
ncmdump -d Music ncmdump -h
``` ```
注意:网易云音乐从 3.0 版本开始不再在 ncm 文件中嵌入封面图片,本工具支持从网易服务器上自动下载对应歌曲的封面图并写入到最终的音乐文件中 使用 `-v``--version` 参数来打印版本信息
```shell
ncmdump -v
```
处理单个或多个文件
```shell
ncmdump 1.ncm 2.ncm...
```
使用 `-d` 参数来指定一个文件夹,对文件夹下的所有以 ncm 为扩展名的文件进行批量处理
```shell
ncmdump -d source_dir
```
使用 `-r` 配合 `-d` 参数来递归处理文件夹下的所有以 ncm 为扩展名的文件
```shell
ncmdump -d source_dir -r
```
使用 `-o` 参数来指定输出目录,将转换后的文件输出到指定目录,该参数支持与 `-r` 参数一起使用
```shell
# 处理单个或多个文件并输出到指定目录
ncmdump 1.ncm 2.ncm -o output_dir
# 处理文件夹下的所有以 ncm 为扩展名并输出到指定目录,不包含子文件夹
ncmdump -d source_dir -o output_dir
# 递归处理文件夹并输出到指定目录,并保留目录结构
ncmdump -d source_dir -o output_dir -r
```
## 开发 ## 开发

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
VERSION=1.5.0 VERSION=1.6.0
# Clean up the build directory # Clean up the build directory
rm -rf build rm -rf build

90
main.go
View File

@@ -10,13 +10,19 @@ import (
flag "github.com/spf13/pflag" 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) currentFile, err := ncmcrypt.NewNeteaseCloudMusic(filePath)
if err != nil { if err != nil {
utils.ErrorPrintfln("Reading '%s' failed: %s", filePath, err.Error()) utils.ErrorPrintfln("Reading '%s' failed: %s", filePath, err.Error())
return err return err
} }
dump, err := currentFile.Dump(filepath.Dir(filePath)) dump, err := currentFile.Dump(outputDir)
if err != nil { if err != nil {
utils.ErrorPrintfln("Processing '%s' failed: %s", filePath, err.Error()) utils.ErrorPrintfln("Processing '%s' failed: %s", filePath, err.Error())
return err return err
@@ -33,10 +39,13 @@ func processFile(filePath string) error {
} }
func main() { func main() {
var folderPath string var sourceDir string
var outputDir string
showHelp := flag.BoolP("help", "h", false, "Display help message") showHelp := flag.BoolP("help", "h", false, "Display help message")
showVersion := flag.BoolP("version", "v", false, "Display version information") 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() flag.Parse()
if len(os.Args) == 1 { if len(os.Args) == 1 {
@@ -50,27 +59,59 @@ func main() {
} }
if *showVersion { if *showVersion {
fmt.Println("ncmdump version 1.5.0") fmt.Println("ncmdump version 1.6.0")
os.Exit(0) os.Exit(0)
} }
if folderPath != "" { if !flag.Lookup("dir").Changed && sourceDir == "" && len(flag.Args()) == 0 {
// check if the folder exists flag.Usage()
info, err := os.Stat(folderPath)
if err != nil {
utils.ErrorPrintfln("Unable to access directory: '%s'", folderPath)
os.Exit(1) os.Exit(1)
} }
if !info.IsDir() { if flag.Lookup("recursive").Changed && !flag.Lookup("dir").Changed {
utils.ErrorPrintfln("Not a directory: %s", folderPath) utils.ErrorPrintfln("The -r option can only be used with the -d option")
os.Exit(1) os.Exit(1)
} }
outputDirSpecified := flag.Lookup("output").Changed
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)
}
}
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 // dump files in the folder
files, err := os.ReadDir(folderPath) files, err := os.ReadDir(sourceDir)
if err != nil { if err != nil {
utils.ErrorPrintfln("Unable to read directory: '%s'", folderPath) utils.ErrorPrintfln("Unable to read directory: '%s'", sourceDir)
os.Exit(1) os.Exit(1)
} }
@@ -79,26 +120,25 @@ func main() {
continue continue
} }
filePath := filepath.Join(folderPath, file.Name()) filePath := filepath.Join(sourceDir, file.Name())
// skip if the extension is not .ncm if outputDirSpecified {
if filePath[len(filePath)-4:] != ".ncm" { _ = processFile(filePath, outputDir)
continue } else {
_ = processFile(filePath, sourceDir)
} }
err = processFile(filePath)
if err != nil {
continue
} }
} }
} else { } else {
// dump file from args // process files from args
for _, filePath := range flag.Args() { for _, filePath := range flag.Args() {
// skip if the extension is not .ncm // skip if the extension is not .ncm
if filePath[len(filePath)-4:] != ".ncm" { if filePath[len(filePath)-4:] != ".ncm" {
continue continue
} }
err := processFile(filePath) if outputDirSpecified {
if err != nil { _ = processFile(filePath, outputDir)
continue } else {
_ = processFile(filePath, sourceDir)
} }
} }
} }

View File

@@ -1,6 +1,7 @@
package utils package utils
import ( import (
"os"
"path/filepath" "path/filepath"
"strings" "strings"
) )
@@ -9,3 +10,39 @@ func ReplaceExtension(filepathStr, newExt string) string {
ext := filepath.Ext(filepathStr) ext := filepath.Ext(filepathStr)
return strings.TrimSuffix(filepathStr, ext) + newExt 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()
}