mirror of
https://github.com/zhaarey/apple-music-alac-atmos-downloader.git
synced 2026-01-15 14:22:53 -03:00
Merge pull request #80 from york9675/main
Add post-download conversion feature
This commit is contained in:
@@ -50,3 +50,10 @@ mv-max: 2160
|
||||
# if your account is from Japan, you must use jp.
|
||||
# if the storefront is different from your account, you will see a "failed to get lyrics" error in most of the songs. By default the storefront is set to US if not set.
|
||||
storefront: "enter your account storefront"
|
||||
# Conversion settings
|
||||
convert-after-download: false # Enable post-download conversion (requires ffmpeg)
|
||||
convert-format: "flac" # flac | mp3 | opus | wav | copy (no re-encode)
|
||||
convert-keep-original: false # Keep original file after successful conversion
|
||||
convert-skip-if-source-matches: true # If already in target format, skip
|
||||
ffmpeg-path: "ffmpeg" # Override if ffmpeg is not in PATH
|
||||
convert-extra-args: "" # Additional raw args appended (advanced)
|
||||
|
||||
148
main.go
148
main.go
@@ -635,14 +635,131 @@ func handleSearch(searchType string, queryParts []string, token string) (string,
|
||||
|
||||
// END: New functions for search functionality
|
||||
|
||||
// CONVERSION FEATURE: Determine if source codec is lossy (rough heuristic by extension/codec name).
|
||||
func isLossySource(ext string, codec string) bool {
|
||||
ext = strings.ToLower(ext)
|
||||
if ext == ".m4a" && (codec == "AAC" || strings.Contains(codec, "AAC") || strings.Contains(codec, "ATMOS")) {
|
||||
return true
|
||||
}
|
||||
if ext == ".mp3" || ext == ".opus" || ext == ".ogg" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// CONVERSION FEATURE: Build ffmpeg arguments for desired target.
|
||||
func buildFFmpegArgs(ffmpegPath, inPath, outPath, targetFmt, extraArgs string) ([]string, error) {
|
||||
args := []string{"-y", "-i", inPath, "-vn"}
|
||||
switch targetFmt {
|
||||
case "flac":
|
||||
args = append(args, "-c:a", "flac")
|
||||
case "mp3":
|
||||
// VBR quality 2 ~ high quality
|
||||
args = append(args, "-c:a", "libmp3lame", "-qscale:a", "2")
|
||||
case "opus":
|
||||
// Medium/high quality
|
||||
args = append(args, "-c:a", "libopus", "-b:a", "192k", "-vbr", "on")
|
||||
case "wav":
|
||||
args = append(args, "-c:a", "pcm_s16le")
|
||||
case "copy":
|
||||
// Just container copy (probably pointless for same container)
|
||||
args = append(args, "-c", "copy")
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported convert-format: %s", targetFmt)
|
||||
}
|
||||
if extraArgs != "" {
|
||||
// naive split; for complex quoting you could enhance
|
||||
args = append(args, strings.Fields(extraArgs)...)
|
||||
}
|
||||
args = append(args, outPath)
|
||||
return args, nil
|
||||
}
|
||||
|
||||
// CONVERSION FEATURE: Perform conversion if enabled.
|
||||
func convertIfNeeded(track *task.Track) {
|
||||
if !Config.ConvertAfterDownload {
|
||||
return
|
||||
}
|
||||
if Config.ConvertFormat == "" {
|
||||
return
|
||||
}
|
||||
srcPath := track.SavePath
|
||||
if srcPath == "" {
|
||||
return
|
||||
}
|
||||
ext := strings.ToLower(filepath.Ext(srcPath))
|
||||
targetFmt := strings.ToLower(Config.ConvertFormat)
|
||||
|
||||
// Map extension for output
|
||||
if targetFmt == "copy" {
|
||||
fmt.Println("Convert (copy) requested; skipping because it produces no new format.")
|
||||
return
|
||||
}
|
||||
|
||||
if Config.ConvertSkipIfSourceMatch {
|
||||
if ext == "."+targetFmt {
|
||||
fmt.Printf("Conversion skipped (already %s)\n", targetFmt)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
outBase := strings.TrimSuffix(srcPath, ext)
|
||||
outPath := outBase + "." + targetFmt
|
||||
|
||||
// Warn about lossy -> lossless
|
||||
if Config.ConvertWarnLossyToLossless && (targetFmt == "flac" || targetFmt == "wav") &&
|
||||
isLossySource(ext, track.Codec) {
|
||||
fmt.Println("Warning: Converting lossy source to lossless container will not improve quality.")
|
||||
}
|
||||
|
||||
if _, err := exec.LookPath(Config.FFmpegPath); err != nil {
|
||||
fmt.Printf("ffmpeg not found at '%s'; skipping conversion.\n", Config.FFmpegPath)
|
||||
return
|
||||
}
|
||||
|
||||
args, err := buildFFmpegArgs(Config.FFmpegPath, srcPath, outPath, targetFmt, Config.ConvertExtraArgs)
|
||||
if err != nil {
|
||||
fmt.Println("Conversion config error:", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Converting -> %s ...\n", targetFmt)
|
||||
cmd := exec.Command(Config.FFmpegPath, args...)
|
||||
cmd.Stdout = nil
|
||||
cmd.Stderr = nil
|
||||
start := time.Now()
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Println("Conversion failed:", err)
|
||||
// leave original
|
||||
return
|
||||
}
|
||||
fmt.Printf("Conversion completed in %s: %s\n", time.Since(start).Truncate(time.Millisecond), filepath.Base(outPath))
|
||||
|
||||
if !Config.ConvertKeepOriginal {
|
||||
if err := os.Remove(srcPath); err != nil {
|
||||
fmt.Println("Failed to remove original after conversion:", err)
|
||||
} else {
|
||||
track.SavePath = outPath
|
||||
track.SaveName = filepath.Base(outPath)
|
||||
fmt.Println("Original removed.")
|
||||
}
|
||||
} else {
|
||||
// Keep both but point track to new file (optional decision)
|
||||
track.SavePath = outPath
|
||||
track.SaveName = filepath.Base(outPath)
|
||||
}
|
||||
}
|
||||
|
||||
func ripTrack(track *task.Track, token string, mediaUserToken string) {
|
||||
var err error
|
||||
counter.Total++
|
||||
fmt.Printf("Track %d of %d: %s\n", track.TaskNum, track.TaskTotal, track.Type)
|
||||
|
||||
//提前获取到的播放列表下track所在的专辑信息
|
||||
if track.PreType == "playlists" && Config.UseSongInfoForPlaylist {
|
||||
track.GetAlbumData(token)
|
||||
}
|
||||
|
||||
//mv dl dev
|
||||
if track.Type == "music-videos" {
|
||||
if len(mediaUserToken) <= 50 {
|
||||
@@ -664,6 +781,7 @@ func ripTrack(track *task.Track, token string, mediaUserToken string) {
|
||||
counter.Success++
|
||||
return
|
||||
}
|
||||
|
||||
needDlAacLc := false
|
||||
if dl_aac && Config.AacType == "aac-lc" {
|
||||
needDlAacLc = true
|
||||
@@ -743,6 +861,17 @@ func ripTrack(track *task.Track, token string, mediaUserToken string) {
|
||||
trackPath := filepath.Join(track.SaveDir, track.SaveName)
|
||||
lrcFilename := fmt.Sprintf("%s.%s", forbiddenNames.ReplaceAllString(songName, "_"), Config.LrcFormat)
|
||||
|
||||
// Determine possible post-conversion target file (so we can skip re-download)
|
||||
var convertedPath string
|
||||
considerConverted := false
|
||||
if Config.ConvertAfterDownload &&
|
||||
Config.ConvertFormat != "" &&
|
||||
strings.ToLower(Config.ConvertFormat) != "copy" &&
|
||||
!Config.ConvertKeepOriginal {
|
||||
convertedPath = strings.TrimSuffix(trackPath, filepath.Ext(trackPath)) + "." + strings.ToLower(Config.ConvertFormat)
|
||||
considerConverted = true
|
||||
}
|
||||
|
||||
//get lrc
|
||||
var lrc string = ""
|
||||
if Config.EmbedLrc || Config.SaveLrcFile {
|
||||
@@ -762,16 +891,27 @@ func ripTrack(track *task.Track, token string, mediaUserToken string) {
|
||||
}
|
||||
}
|
||||
|
||||
exists, err := fileExists(trackPath)
|
||||
// Existence check now considers converted output (if original was deleted)
|
||||
existsOriginal, err := fileExists(trackPath)
|
||||
if err != nil {
|
||||
fmt.Println("Failed to check if track exists.")
|
||||
}
|
||||
if exists {
|
||||
if existsOriginal {
|
||||
fmt.Println("Track already exists locally.")
|
||||
counter.Success++
|
||||
okDict[track.PreID] = append(okDict[track.PreID], track.TaskNum)
|
||||
return
|
||||
}
|
||||
if considerConverted {
|
||||
existsConverted, err2 := fileExists(convertedPath)
|
||||
if err2 == nil && existsConverted {
|
||||
fmt.Println("Converted track already exists locally.")
|
||||
counter.Success++
|
||||
okDict[track.PreID] = append(okDict[track.PreID], track.TaskNum)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if needDlAacLc {
|
||||
if len(mediaUserToken) <= 50 {
|
||||
fmt.Println("Invalid media-user-token")
|
||||
@@ -837,6 +977,10 @@ func ripTrack(track *task.Track, token string, mediaUserToken string) {
|
||||
counter.Unavailable++
|
||||
return
|
||||
}
|
||||
|
||||
// CONVERSION FEATURE hook
|
||||
convertIfNeeded(track)
|
||||
|
||||
counter.Success++
|
||||
okDict[track.PreID] = append(okDict[track.PreID], track.TaskNum)
|
||||
}
|
||||
|
||||
@@ -38,6 +38,13 @@ type ConfigSet struct {
|
||||
DlAlbumcoverForPlaylist bool `yaml:"dl-albumcover-for-playlist"`
|
||||
MVAudioType string `yaml:"mv-audio-type"`
|
||||
MVMax int `yaml:"mv-max"`
|
||||
ConvertAfterDownload bool `yaml:"convert-after-download"`
|
||||
ConvertFormat string `yaml:"convert-format"`
|
||||
ConvertKeepOriginal bool `yaml:"convert-keep-original"`
|
||||
ConvertSkipIfSourceMatch bool `yaml:"convert-skip-if-source-matches"`
|
||||
FFmpegPath string `yaml:"ffmpeg-path"`
|
||||
ConvertExtraArgs string `yaml:"convert-extra-args"`
|
||||
ConvertWarnLossyToLossless bool `yaml:"convert-warn-lossy-to-lossless"`
|
||||
}
|
||||
|
||||
type Counter struct {
|
||||
|
||||
Reference in New Issue
Block a user