Compare commits
3 Commits
1.5.0
...
cf39731cb4
| Author | SHA1 | Date | |
|---|---|---|---|
|
cf39731cb4
|
|||
|
0bb001e7e2
|
|||
|
b104e1352f
|
47
README.md
47
README.md
@@ -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
|
||||||
|
```
|
||||||
|
|
||||||
## 开发
|
## 开发
|
||||||
|
|
||||||
|
|||||||
2
build.sh
2
build.sh
@@ -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
|
||||||
|
|||||||
112
main.go
112
main.go
@@ -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,55 +59,86 @@ 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)
|
os.Exit(1)
|
||||||
if err != nil {
|
}
|
||||||
utils.ErrorPrintfln("Unable to access directory: '%s'", folderPath)
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// dump files in the folder
|
outputDirSpecified := flag.Lookup("output").Changed
|
||||||
files, err := os.ReadDir(folderPath)
|
|
||||||
if err != nil {
|
|
||||||
utils.ErrorPrintfln("Unable to read directory: '%s'", folderPath)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, file := range files {
|
if outputDirSpecified {
|
||||||
if file.IsDir() {
|
if utils.PathExists(outputDir) {
|
||||||
continue
|
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())
|
if sourceDir != "" {
|
||||||
// skip if the extension is not .ncm
|
if !utils.IsDir(sourceDir) {
|
||||||
if filePath[len(filePath)-4:] != ".ncm" {
|
utils.ErrorPrintfln("The source directory '%s' is not valid.", sourceDir)
|
||||||
continue
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
err = processFile(filePath)
|
|
||||||
|
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 {
|
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 {
|
} 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user