Author SHA1 Message Date
JoJo 628bea38be fix: 完善苹果系统构建与文件兼容
动机:解决苹果系统发布包缺少执行权限和特殊文件误处理问题。
改动:校验发布包权限,支持两类芯片架构并改进文件识别。
影响:苹果系统可直接运行发布包,其他平台构建与使用方式兼容。
验证:运行 go test ./...、go vet ./... 和完整发布构建,结果通过。
关联:无
2026-09-07 16:14:11 +08:00
taurusxin a62f5156f0 release: 1.7.5 2025-11-25 15:17:09 +08:00
taurusxin 747343162b chore: sorted import 2025-11-25 15:16:05 +08:00
taurusxin 676641d5f9 fix: dump failed 2025-11-25 15:15:30 +08:00
taurusxin 96dc36f112 更新 README.md 2025-10-16 11:14:06 +08:00
taurusxin 54da5b7a35 release: 1.7.4 2025-02-01 12:29:33 +08:00
taurusxin 4d0622073a fix: null ref when add meta for flac 2025-02-01 12:28:44 +08:00
taurusxin 49e554931e 更新 README.md 2025-01-31 11:53:10 +08:00
taurusxin 2814a23874 release: 1.7.3 2025-01-30 10:16:33 +08:00
taurusxin 3a48a5d667 Merge pull request 'fix: prevent duplicated flac metadata' (#1) from d0ngd1gua/ncmdump-go:main into main
Reviewed-on: taurusxin/ncmdump-go#1
2025-01-27 15:12:19 +08:00
dongdigua 5cb53dff3b fix: prevent duplicated flac metadata 2025-01-27 14:58:01 +08:00
taurusxin b3258662f6 feat: LICENSE 2024-11-28 09:05:11 +08:00
taurusxin 1765813f08 bump version to 1.7.2 2024-11-25 14:04:01 +08:00
taurusxin c11fbf88db chore: modified go mod name 2024-11-25 13:59:13 +08:00
8 changed files with 178 additions and 53 deletions
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018-present TaurusXin and contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+40 -8
View File
@@ -2,18 +2,51 @@
基于 https://github.com/taurusxin/ncmdump 的 Golang 移植版 基于 https://github.com/taurusxin/ncmdump 的 Golang 移植版
支持网易云音乐最新的 3.x 版本,但需要注意:从该版本开始网易云音乐不再在 ncm 文件中内置封面图片,本工具支持从网易服务器上自动下载对应歌曲的封面图并写入到最终的音乐文件中 支持网易云音乐最新的 3.x 版本,但需要注意:从 3. x开始的某些网易云音乐版本不再在 ncm 文件中内置封面图片,本项目支持从网易服务器上自动下载对应歌曲的封面图并写入到最终的音乐文件中
你也可以去 https://git.taurusxin.com/taurusxin/ncmdump-gui 下载基于本项目的 gui 可视化图形应用,只需简单点击即可自动转换。
## 如何提 Issue
由于本站恶意机器人注册过多,已关闭账号注册,如果需要提 Issue 请前往 [GitHub](https://github.com/taurusxin/ncmdump),必须注明 Issue 的主题为 ncmdump-go,敬请谅解。
## 安装 ## 安装
你可以使用去 [releases](https://git.taurusxin.com/taurusxin/ncmdump-go/releases/latest) 下载最新版预编译好的二进制文件,或者你也可以用包管理器安装 你可以前往 [releases](https://git.taurusxin.com/taurusxin/ncmdump-go/releases/latest) 下载最新版预编译好的二进制文件,或者使用包管理器安装
### macOS
项目同时支持 Apple SiliconM1/M2/M3/M4 等)和 Intel Mac
- Apple Silicon 下载文件名包含 `darwin_arm64` 的压缩包;
- Intel Mac 下载文件名包含 `darwin_amd64` 的压缩包;
- 不确定芯片架构时,可运行 `uname -m`:输出 `arm64` 对应 Apple Silicon,输出 `x86_64` 对应 Intel。
下载并解压后,在终端执行:
```shell
chmod +x ncmdump-go
sudo install -d /usr/local/bin
sudo install -m 0755 ncmdump-go /usr/local/bin/ncmdump-go
ncmdump-go --version
```
新发布的 macOS 压缩包已经包含可执行权限;这里保留 `chmod`,以兼容早期发布包。如果 macOS 提示无法验证开发者,可在确认文件来自本项目发布页后、执行安装命令前移除下载隔离属性:
```shell
xattr -d com.apple.quarantine ncmdump-go
```
也可以安装 Go 1.23 或更高版本后从源码构建:
```shell
go build -o ncmdump-go .
```
```shell ```shell
# Windows Scoop # Windows Scoop
scoop bucket add taurusxin https://git.taurusxin.com/taurusxin/scoop-bucket.git # 添加 scoop 源 scoop bucket add taurusxin https://git.taurusxin.com/taurusxin/scoop-bucket.git # 添加 scoop 源
scoop install ncmdump-go # 安装 ncmdump-go scoop install ncmdump-go # 安装 ncmdump-go
# macOS & Linux 之后会支持
``` ```
## 使用方法 ## 使用方法
@@ -63,10 +96,10 @@ ncmdump-go -d source_dir -o output_dir -r
## 开发 ## 开发
使用 go module 下载 ncmdump-go 包 使用 Go module 下载 ncmdump-go 包
```shell ```shell
go get -u github.com/taurusxin/ncmdump-go go get git.taurusxin.com/taurusxin/ncmdump-go@latest
``` ```
导入并使用 导入并使用
@@ -76,7 +109,7 @@ package main
import ( import (
"fmt" "fmt"
"github.com/taurusxin/ncmdump-go/ncmcrypt" "git.taurusxin.com/taurusxin/ncmdump-go/ncmcrypt"
) )
func main() { func main() {
@@ -105,4 +138,3 @@ func main() {
} }
} }
``` ```
+53 -27
View File
@@ -1,37 +1,63 @@
#!/usr/bin/env bash #!/usr/bin/env bash
VERSION=1.7.1 set -euo pipefail
# Clean up the build directory VERSION="${VERSION:-1.7.5}"
rm -rf build VERSION="${VERSION#v}"
mkdir build BUILD_DIR="${BUILD_DIR:-build}"
PACKAGE="git.taurusxin.com/taurusxin/ncmdump-go"
LDFLAGS="-w -s -X main.version=${VERSION}"
# Linux amd64 mkdir -p "${BUILD_DIR}"
echo "Building for Linux amd64..." rm -f \
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o build/ncmdump-go github.com/taurusxin/ncmdump-go "${BUILD_DIR}/ncmdump-go" \
tar zcf build/ncmdump-go_linux_amd64_$VERSION.tar.gz -C build ncmdump-go "${BUILD_DIR}/ncmdump-go.exe" \
rm build/ncmdump-go "${BUILD_DIR}/ncmdump-go_linux_amd64_${VERSION}.tar.gz" \
"${BUILD_DIR}/ncmdump-go_linux_arm64_${VERSION}.tar.gz" \
"${BUILD_DIR}/ncmdump-go_darwin_amd64_${VERSION}.tar.gz" \
"${BUILD_DIR}/ncmdump-go_darwin_arm64_${VERSION}.tar.gz" \
"${BUILD_DIR}/ncmdump-go_windows_amd64_${VERSION}.zip"
# Linux arm64 create_tar_archive() {
echo "Building for Linux arm64..." local os="$1"
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-w -s" -o build/ncmdump-go github.com/taurusxin/ncmdump-go local arch="$2"
tar zcf build/ncmdump-go_linux_arm64_$VERSION.tar.gz -C build ncmdump-go local binary="${BUILD_DIR}/ncmdump-go"
rm build/ncmdump-go local archive="${BUILD_DIR}/ncmdump-go_${os}_${arch}_${VERSION}.tar.gz"
local tar_version
# macOS amd64 echo "Building for ${os} ${arch}..."
echo "Building for macOS amd64..." CGO_ENABLED=0 GOOS="${os}" GOARCH="${arch}" \
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags="-w -s" -o build/ncmdump-go github.com/taurusxin/ncmdump-go go build -trimpath -ldflags="${LDFLAGS}" -o "${binary}" "${PACKAGE}"
tar zcf build/ncmdump-go_darwin_amd64_$VERSION.tar.gz -C build ncmdump-go
rm build/ncmdump-go
# macOS arm64 # Builds are sometimes produced from Git Bash on Windows, where chmod does
echo "Building for macOS arm64..." # not change NTFS permissions. Force the executable bit into GNU tar's
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags="-w -s" -o build/ncmdump-go github.com/taurusxin/ncmdump-go # header so macOS/Linux users can run the extracted binary immediately.
tar zcf build/ncmdump-go_darwin_arm64_$VERSION.tar.gz -C build ncmdump-go chmod 0755 "${binary}"
rm build/ncmdump-go tar_version="$(tar --version 2>/dev/null || true)"
if [[ "${tar_version}" == *"GNU tar"* ]]; then
tar --mode=0755 -zcf "${archive}" -C "${BUILD_DIR}" ncmdump-go
else
tar -zcf "${archive}" -C "${BUILD_DIR}" ncmdump-go
fi
if ! tar -tvzf "${archive}" | awk 'NR == 1 { exit(substr($1, 1, 10) == "-rwxr-xr-x" ? 0 : 1) }'; then
echo "Archive validation failed: ncmdump-go is not executable in ${archive}" >&2
exit 1
fi
rm "${binary}"
}
create_tar_archive linux amd64
create_tar_archive linux arm64
create_tar_archive darwin amd64
create_tar_archive darwin arm64
# Windows amd64 # Windows amd64
echo "Building for Windows amd64..." echo "Building for Windows amd64..."
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-w -s" -o build/ncmdump-go.exe github.com/taurusxin/ncmdump-go CGO_ENABLED=0 GOOS=windows GOARCH=amd64 \
zip -q -j build/ncmdump-go_windows_amd64_$VERSION.zip ./build/ncmdump-go.exe go build -trimpath -ldflags="${LDFLAGS}" -o "${BUILD_DIR}/ncmdump-go.exe" "${PACKAGE}"
rm build/ncmdump-go.exe zip -q -j "${BUILD_DIR}/ncmdump-go_windows_amd64_${VERSION}.zip" "${BUILD_DIR}/ncmdump-go.exe"
rm "${BUILD_DIR}/ncmdump-go.exe"
echo "Release archives written to ${BUILD_DIR}/"
+1 -1
View File
@@ -1,4 +1,4 @@
module github.com/taurusxin/ncmdump-go module git.taurusxin.com/taurusxin/ncmdump-go
go 1.23.0 go 1.23.0
+15 -5
View File
@@ -2,17 +2,27 @@ package main
import ( import (
"fmt" "fmt"
"github.com/taurusxin/ncmdump-go/ncmcrypt"
"github.com/taurusxin/ncmdump-go/utils"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"git.taurusxin.com/taurusxin/ncmdump-go/ncmcrypt"
"git.taurusxin.com/taurusxin/ncmdump-go/utils"
flag "github.com/spf13/pflag" flag "github.com/spf13/pflag"
) )
// version is replaced by build.sh for release builds.
var version = "1.7.5"
func isNCMFile(filePath string) bool {
baseName := filepath.Base(filePath)
return !strings.HasPrefix(baseName, "._") && strings.EqualFold(filepath.Ext(baseName), ".ncm")
}
func processFile(filePath string, outputDir string) error { func processFile(filePath string, outputDir string) error {
// skip if the extension is not .ncm // skip if the extension is not .ncm
if filePath[len(filePath)-4:] != ".ncm" { if !isNCMFile(filePath) {
return nil return nil
} }
@@ -59,7 +69,7 @@ func main() {
} }
if *showVersion { if *showVersion {
fmt.Println("ncmdump version 1.7.1") fmt.Printf("ncmdump version %s\n", version)
os.Exit(0) os.Exit(0)
} }
@@ -132,7 +142,7 @@ func main() {
// process files 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 !isNCMFile(filePath) {
continue continue
} }
if outputDirSpecified { if outputDirSpecified {
+27
View File
@@ -0,0 +1,27 @@
package main
import "testing"
func TestIsNCMFile(t *testing.T) {
tests := []struct {
name string
path string
want bool
}{
{name: "lowercase", path: "/Users/test/Music/song.ncm", want: true},
{name: "uppercase", path: "/Users/test/Music/song.NCM", want: true},
{name: "unicode path", path: "/Users/test/音乐/歌曲.ncm", want: true},
{name: "macOS AppleDouble file", path: "/Users/test/Music/._song.ncm", want: false},
{name: "short name", path: "a", want: false},
{name: "extension only", path: ".ncm", want: true},
{name: "different extension", path: "song.flac", want: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := isNCMFile(tt.path); got != tt.want {
t.Fatalf("isNCMFile(%q) = %v, want %v", tt.path, got, tt.want)
}
})
}
}
+17 -8
View File
@@ -5,15 +5,16 @@ import (
"encoding/base64" "encoding/base64"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"github.com/bogem/id3v2/v2"
"github.com/go-flac/flacpicture"
"github.com/go-flac/flacvorbis"
"github.com/go-flac/go-flac"
"github.com/taurusxin/ncmdump-go/utils"
"io" "io"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"git.taurusxin.com/taurusxin/ncmdump-go/utils"
"github.com/bogem/id3v2/v2"
"github.com/go-flac/flacpicture"
"github.com/go-flac/flacvorbis"
"github.com/go-flac/go-flac"
) )
type NcmFormat = string type NcmFormat = string
@@ -152,7 +153,7 @@ func (ncm *NeteaseCloudMusic) Dump(targetDir string) (bool, error) {
outputStream = output outputStream = output
} }
outputStream.Write(buffer) outputStream.Write(buffer[:n])
} }
outputStream.Close() outputStream.Close()
@@ -179,7 +180,8 @@ func (ncm *NeteaseCloudMusic) FixMetadata(fetchAlbumImageFromRemote bool) (bool,
} }
} }
} }
if ncm.mFormat == Mp3 { switch ncm.mFormat {
case Mp3:
audioFile, err := id3v2.Open(ncm.mDumpFilePath, id3v2.Options{Parse: true}) audioFile, err := id3v2.Open(ncm.mDumpFilePath, id3v2.Options{Parse: true})
if err != nil { if err != nil {
return false, err return false, err
@@ -205,7 +207,7 @@ func (ncm *NeteaseCloudMusic) FixMetadata(fetchAlbumImageFromRemote bool) (bool,
if err != nil { if err != nil {
return false, err return false, err
} }
} else if ncm.mFormat == Flac { case Flac:
audioFile, err := flac.ParseFile(ncm.mDumpFilePath) audioFile, err := flac.ParseFile(ncm.mDumpFilePath)
if err != nil { if err != nil {
return false, err return false, err
@@ -235,9 +237,16 @@ func (ncm *NeteaseCloudMusic) FixMetadata(fetchAlbumImageFromRemote bool) (bool,
cmts = flacvorbis.New() cmts = flacvorbis.New()
} }
// flac 可能自带元数据 当且仅当没有该项时才向目标添加元数据
if res, _ := cmts.Get(flacvorbis.FIELD_TITLE); len(res) == 0 {
_ = cmts.Add(flacvorbis.FIELD_TITLE, ncm.mMetadata.mName) _ = cmts.Add(flacvorbis.FIELD_TITLE, ncm.mMetadata.mName)
}
if res, _ := cmts.Get(flacvorbis.FIELD_ARTIST); len(res) == 0 {
_ = cmts.Add(flacvorbis.FIELD_ARTIST, ncm.mMetadata.mArtist) _ = cmts.Add(flacvorbis.FIELD_ARTIST, ncm.mMetadata.mArtist)
}
if res, _ := cmts.Get(flacvorbis.FIELD_ALBUM); len(res) == 0 {
_ = cmts.Add(flacvorbis.FIELD_ALBUM, ncm.mMetadata.mAlbum) _ = cmts.Add(flacvorbis.FIELD_ALBUM, ncm.mMetadata.mAlbum)
}
cmtsmeta := cmts.Marshal() cmtsmeta := cmts.Marshal()