.unicode issue converter
This commit is contained in:
+7
-1
@@ -16,6 +16,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ulikunitz/xz"
|
"github.com/ulikunitz/xz"
|
||||||
|
"golang.org/x/text/unicode/norm"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ValidateExecutable(path string) error {
|
func ValidateExecutable(path string) error {
|
||||||
@@ -650,6 +651,7 @@ func ConvertAudio(req ConvertAudioRequest) ([]ConvertAudioResult, error) {
|
|||||||
|
|
||||||
outputExt := "." + strings.ToLower(req.OutputFormat)
|
outputExt := "." + strings.ToLower(req.OutputFormat)
|
||||||
outputFile := filepath.Join(outputDir, baseName+outputExt)
|
outputFile := filepath.Join(outputDir, baseName+outputExt)
|
||||||
|
outputFile = norm.NFC.String(outputFile)
|
||||||
|
|
||||||
if inputExt == outputExt {
|
if inputExt == outputExt {
|
||||||
result.Error = "Input and output formats are the same"
|
result.Error = "Input and output formats are the same"
|
||||||
@@ -671,7 +673,11 @@ func ConvertAudio(req ConvertAudioRequest) ([]ConvertAudioResult, error) {
|
|||||||
fmt.Printf("[FFmpeg] Warning: Failed to extract metadata from %s: %v\n", inputFile, err)
|
fmt.Printf("[FFmpeg] Warning: Failed to extract metadata from %s: %v\n", inputFile, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
coverArtPath, _ = ExtractCoverArt(inputFile)
|
inputFile = norm.NFC.String(inputFile)
|
||||||
|
coverArtPath, err = ExtractCoverArt(inputFile)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("[FFmpeg] Warning: Failed to extract cover art from %s: %v\n", inputFile, err)
|
||||||
|
}
|
||||||
lyrics, err = ExtractLyrics(inputFile)
|
lyrics, err = ExtractLyrics(inputFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("[FFmpeg] Warning: Failed to extract lyrics from %s: %v\n", inputFile, err)
|
fmt.Printf("[FFmpeg] Warning: Failed to extract lyrics from %s: %v\n", inputFile, err)
|
||||||
|
|||||||
+112
-5
@@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/go-flac/flacpicture"
|
"github.com/go-flac/flacpicture"
|
||||||
"github.com/go-flac/flacvorbis"
|
"github.com/go-flac/flacvorbis"
|
||||||
"github.com/go-flac/go-flac"
|
"github.com/go-flac/go-flac"
|
||||||
|
"golang.org/x/text/unicode/norm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Metadata struct {
|
type Metadata struct {
|
||||||
@@ -218,16 +219,68 @@ func EmbedLyricsOnly(filepath string, lyrics string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ExtractCoverArt(filePath string) (string, error) {
|
func ExtractCoverArt(filePath string) (string, error) {
|
||||||
|
filePath = norm.NFC.String(filePath)
|
||||||
ext := strings.ToLower(pathfilepath.Ext(filePath))
|
ext := strings.ToLower(pathfilepath.Ext(filePath))
|
||||||
|
|
||||||
|
var coverPath string
|
||||||
|
var err error
|
||||||
|
|
||||||
switch ext {
|
switch ext {
|
||||||
case ".mp3":
|
case ".mp3":
|
||||||
return extractCoverFromMp3(filePath)
|
coverPath, err = extractCoverFromMp3(filePath)
|
||||||
case ".m4a", ".flac":
|
case ".m4a", ".flac":
|
||||||
return extractCoverFromM4AOrFlac(filePath)
|
coverPath, err = extractCoverFromM4AOrFlac(filePath)
|
||||||
default:
|
default:
|
||||||
return "", fmt.Errorf("unsupported file format: %s", ext)
|
return "", fmt.Errorf("unsupported file format: %s", ext)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err != nil || coverPath == "" {
|
||||||
|
fmt.Printf("[ExtractCoverArt] Library extraction failed for %s, trying FFmpeg fallback...\n", filePath)
|
||||||
|
ffmpegCover, ffmpegErr := extractCoverWithFFmpeg(filePath)
|
||||||
|
if ffmpegErr == nil {
|
||||||
|
return ffmpegCover, nil
|
||||||
|
}
|
||||||
|
return coverPath, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return coverPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractCoverWithFFmpeg(filePath string) (string, error) {
|
||||||
|
ffmpegPath, err := GetFFmpegPath()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpFile, err := os.CreateTemp("", "cover-*.jpg")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
tmpPath := tmpFile.Name()
|
||||||
|
tmpFile.Close()
|
||||||
|
|
||||||
|
cmd := exec.Command(ffmpegPath,
|
||||||
|
"-i", filePath,
|
||||||
|
"-an",
|
||||||
|
"-vframes", "1",
|
||||||
|
"-f", "image2",
|
||||||
|
"-update", "1",
|
||||||
|
"-y",
|
||||||
|
tmpPath,
|
||||||
|
)
|
||||||
|
|
||||||
|
setHideWindow(cmd)
|
||||||
|
if output, err := cmd.CombinedOutput(); err != nil {
|
||||||
|
os.Remove(tmpPath)
|
||||||
|
return "", fmt.Errorf("ffmpeg cover extraction failed: %v, output: %s", err, string(output))
|
||||||
|
}
|
||||||
|
|
||||||
|
if info, err := os.Stat(tmpPath); err != nil || info.Size() == 0 {
|
||||||
|
os.Remove(tmpPath)
|
||||||
|
return "", fmt.Errorf("ffmpeg produced empty cover file")
|
||||||
|
}
|
||||||
|
|
||||||
|
return tmpPath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractCoverFromMp3(filePath string) (string, error) {
|
func extractCoverFromMp3(filePath string) (string, error) {
|
||||||
@@ -298,19 +351,71 @@ func extractCoverFromM4AOrFlac(filePath string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ExtractLyrics(filePath string) (string, error) {
|
func ExtractLyrics(filePath string) (string, error) {
|
||||||
|
filePath = norm.NFC.String(filePath)
|
||||||
ext := strings.ToLower(pathfilepath.Ext(filePath))
|
ext := strings.ToLower(pathfilepath.Ext(filePath))
|
||||||
|
|
||||||
|
var lyrics string
|
||||||
|
var err error
|
||||||
|
|
||||||
switch ext {
|
switch ext {
|
||||||
case ".mp3":
|
case ".mp3":
|
||||||
return extractLyricsFromMp3(filePath)
|
lyrics, err = extractLyricsFromMp3(filePath)
|
||||||
case ".flac":
|
case ".flac":
|
||||||
return extractLyricsFromFlac(filePath)
|
lyrics, err = extractLyricsFromFlac(filePath)
|
||||||
case ".m4a":
|
case ".m4a":
|
||||||
|
|
||||||
return "", nil
|
return "", nil
|
||||||
default:
|
default:
|
||||||
return "", fmt.Errorf("unsupported file format: %s", ext)
|
return "", fmt.Errorf("unsupported file format: %s", ext)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (err != nil || lyrics == "") && ext != ".m4a" {
|
||||||
|
fmt.Printf("[ExtractLyrics] Library extraction failed for %s, trying ffprobe fallback...\n", filePath)
|
||||||
|
ffprobeLyrics, ffprobeErr := extractLyricsWithFFprobe(filePath)
|
||||||
|
if ffprobeErr == nil && ffprobeLyrics != "" {
|
||||||
|
return ffprobeLyrics, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lyrics, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractLyricsWithFFprobe(filePath string) (string, error) {
|
||||||
|
ffprobePath, err := GetFFprobePath()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(ffprobePath,
|
||||||
|
"-v", "quiet",
|
||||||
|
"-show_entries", "format_tags=lyrics:format_tags=unsyncedlyrics:format_tags=lyric",
|
||||||
|
"-of", "json",
|
||||||
|
filePath,
|
||||||
|
)
|
||||||
|
|
||||||
|
setHideWindow(cmd)
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var result struct {
|
||||||
|
Format struct {
|
||||||
|
Tags map[string]string `json:"tags"`
|
||||||
|
} `json:"format"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(output, &result); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
tags := result.Format.Tags
|
||||||
|
for _, key := range []string{"lyrics", "unsyncedlyrics", "lyric", "LYRICS", "UNSYNCEDLYRICS", "LYRIC"} {
|
||||||
|
if val, ok := tags[key]; ok && val != "" {
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractLyricsFromMp3(filePath string) (string, error) {
|
func extractLyricsFromMp3(filePath string) (string, error) {
|
||||||
@@ -688,6 +793,7 @@ func parseLRCTimestamp(timestamp string) int64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ExtractFullMetadataFromFile(filePath string) (Metadata, error) {
|
func ExtractFullMetadataFromFile(filePath string) (Metadata, error) {
|
||||||
|
filePath = norm.NFC.String(filePath)
|
||||||
var metadata Metadata
|
var metadata Metadata
|
||||||
|
|
||||||
ffprobePath, err := GetFFprobePath()
|
ffprobePath, err := GetFFprobePath()
|
||||||
@@ -796,6 +902,7 @@ func ExtractFullMetadataFromFile(filePath string) (Metadata, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func EmbedMetadataToConvertedFile(filePath string, metadata Metadata, coverPath string) error {
|
func EmbedMetadataToConvertedFile(filePath string, metadata Metadata, coverPath string) error {
|
||||||
|
filePath = norm.NFC.String(filePath)
|
||||||
ext := strings.ToLower(pathfilepath.Ext(filePath))
|
ext := strings.ToLower(pathfilepath.Ext(filePath))
|
||||||
|
|
||||||
switch ext {
|
switch ext {
|
||||||
|
|||||||
Reference in New Issue
Block a user