v6.8
This commit is contained in:
@@ -254,7 +254,12 @@ func (a *App) DownloadTrack(req DownloadRequest) (DownloadResponse, error) {
|
|||||||
|
|
||||||
case "qobuz":
|
case "qobuz":
|
||||||
downloader := backend.NewQobuzDownloader()
|
downloader := backend.NewQobuzDownloader()
|
||||||
filename, err = downloader.DownloadByISRC(req.ISRC, req.OutputDir, req.AudioFormat, req.FilenameFormat, req.TrackNumber, req.Position, req.TrackName, req.ArtistName, req.AlbumName, req.UseAlbumTrackNumber)
|
// Default to "6" (FLAC 16-bit) for Qobuz if not specified
|
||||||
|
quality := req.AudioFormat
|
||||||
|
if quality == "" {
|
||||||
|
quality = "6"
|
||||||
|
}
|
||||||
|
filename, err = downloader.DownloadByISRC(req.ISRC, req.OutputDir, quality, req.FilenameFormat, req.TrackNumber, req.Position, req.TrackName, req.ArtistName, req.AlbumName, req.UseAlbumTrackNumber)
|
||||||
|
|
||||||
default: // deezer
|
default: // deezer
|
||||||
downloader := backend.NewDeezerDownloader()
|
downloader := backend.NewDeezerDownloader()
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ulikunitz/xz"
|
"github.com/ulikunitz/xz"
|
||||||
@@ -74,6 +75,12 @@ func IsFFmpegInstalled() (bool, error) {
|
|||||||
|
|
||||||
// Verify it's executable
|
// Verify it's executable
|
||||||
cmd := exec.Command(ffmpegPath, "-version")
|
cmd := exec.Command(ffmpegPath, "-version")
|
||||||
|
// Hide console window on Windows
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||||
|
HideWindow: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
err = cmd.Run()
|
err = cmd.Run()
|
||||||
return err == nil, nil
|
return err == nil, nil
|
||||||
}
|
}
|
||||||
@@ -384,6 +391,12 @@ func ConvertAudio(req ConvertAudioRequest) ([]ConvertAudioResult, error) {
|
|||||||
fmt.Printf("[FFmpeg] Converting: %s -> %s\n", inputFile, outputFile)
|
fmt.Printf("[FFmpeg] Converting: %s -> %s\n", inputFile, outputFile)
|
||||||
|
|
||||||
cmd := exec.Command(ffmpegPath, args...)
|
cmd := exec.Command(ffmpegPath, args...)
|
||||||
|
// Hide console window on Windows
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||||
|
HideWindow: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
result.Error = fmt.Sprintf("conversion failed: %s - %s", err.Error(), string(output))
|
result.Error = fmt.Sprintf("conversion failed: %s - %s", err.Error(), string(output))
|
||||||
@@ -528,6 +541,12 @@ func InstallFFmpegFromFile(filePath string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(ffmpegPath, "-version")
|
cmd := exec.Command(ffmpegPath, "-version")
|
||||||
|
// Hide console window on Windows
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||||
|
HideWindow: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
verifyErr = cmd.Run()
|
verifyErr = cmd.Run()
|
||||||
if verifyErr == nil {
|
if verifyErr == nil {
|
||||||
break
|
break
|
||||||
|
|||||||
+7
-2
@@ -122,15 +122,20 @@ func (q *QobuzDownloader) SearchByISRC(isrc string) (*QobuzTrack, error) {
|
|||||||
func (q *QobuzDownloader) GetDownloadURL(trackID int64, quality string) (string, error) {
|
func (q *QobuzDownloader) GetDownloadURL(trackID int64, quality string) (string, error) {
|
||||||
// Map quality to Qobuz quality code
|
// Map quality to Qobuz quality code
|
||||||
// Qobuz uses: 5 (MP3 320), 6 (FLAC 16-bit), 7 (FLAC 24-bit), 27 (Hi-Res)
|
// Qobuz uses: 5 (MP3 320), 6 (FLAC 16-bit), 7 (FLAC 24-bit), 27 (Hi-Res)
|
||||||
qualityCode := "27" // Default to Hi-Res
|
qualityCode := quality // Use the provided quality parameter
|
||||||
|
if qualityCode == "" {
|
||||||
|
qualityCode = "6" // Default to FLAC 16-bit if not specified
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Printf("Getting download URL for track ID: %d\n", trackID)
|
fmt.Printf("Getting download URL for track ID: %d with requested quality: %s\n", trackID, qualityCode)
|
||||||
|
fmt.Printf("Quality codes: 6=FLAC 16-bit, 7=FLAC 24-bit, 27=Hi-Res\n")
|
||||||
|
|
||||||
// Decode base64 API URLs
|
// Decode base64 API URLs
|
||||||
primaryBase, _ := base64.StdEncoding.DecodeString("aHR0cHM6Ly9kYWIueWVldC5zdS9hcGkvc3RyZWFtP3RyYWNrSWQ9")
|
primaryBase, _ := base64.StdEncoding.DecodeString("aHR0cHM6Ly9kYWIueWVldC5zdS9hcGkvc3RyZWFtP3RyYWNrSWQ9")
|
||||||
|
|
||||||
// Try primary API first
|
// Try primary API first
|
||||||
primaryURL := fmt.Sprintf("%s%d&quality=%s", string(primaryBase), trackID, qualityCode)
|
primaryURL := fmt.Sprintf("%s%d&quality=%s", string(primaryBase), trackID, qualityCode)
|
||||||
|
fmt.Printf("Qobuz API URL: %s\n", primaryURL)
|
||||||
|
|
||||||
resp, err := q.client.Get(primaryURL)
|
resp, err := q.client.Get(primaryURL)
|
||||||
if err == nil && resp.StatusCode == 200 {
|
if err == nil && resp.StatusCode == 200 {
|
||||||
|
|||||||
@@ -38,12 +38,12 @@
|
|||||||
"tailwindcss": "^4.1.18"
|
"tailwindcss": "^4.1.18"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.39.1",
|
"@eslint/js": "^9.39.2",
|
||||||
"@types/node": "^25.0.1",
|
"@types/node": "^25.0.1",
|
||||||
"@types/react": "^19.2.7",
|
"@types/react": "^19.2.7",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@vitejs/plugin-react": "^5.1.2",
|
"@vitejs/plugin-react": "^5.1.2",
|
||||||
"eslint": "^9.39.1",
|
"eslint": "^9.39.2",
|
||||||
"eslint-plugin-react-hooks": "^7.0.1",
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
"eslint-plugin-react-refresh": "^0.4.24",
|
"eslint-plugin-react-refresh": "^0.4.24",
|
||||||
"globals": "^16.5.0",
|
"globals": "^16.5.0",
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
0cfdd24cb906bd58f1194d05e38654ae
|
39c59c100ededac1c1e21fae937a2755
|
||||||
Generated
+39
-39
@@ -82,8 +82,8 @@ importers:
|
|||||||
version: 4.1.18
|
version: 4.1.18
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@eslint/js':
|
'@eslint/js':
|
||||||
specifier: ^9.39.1
|
specifier: ^9.39.2
|
||||||
version: 9.39.1
|
version: 9.39.2
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^25.0.1
|
specifier: ^25.0.1
|
||||||
version: 25.0.1
|
version: 25.0.1
|
||||||
@@ -97,14 +97,14 @@ importers:
|
|||||||
specifier: ^5.1.2
|
specifier: ^5.1.2
|
||||||
version: 5.1.2(vite@7.2.7(@types/node@25.0.1)(jiti@2.6.1)(lightningcss@1.30.2))
|
version: 5.1.2(vite@7.2.7(@types/node@25.0.1)(jiti@2.6.1)(lightningcss@1.30.2))
|
||||||
eslint:
|
eslint:
|
||||||
specifier: ^9.39.1
|
specifier: ^9.39.2
|
||||||
version: 9.39.1(jiti@2.6.1)
|
version: 9.39.2(jiti@2.6.1)
|
||||||
eslint-plugin-react-hooks:
|
eslint-plugin-react-hooks:
|
||||||
specifier: ^7.0.1
|
specifier: ^7.0.1
|
||||||
version: 7.0.1(eslint@9.39.1(jiti@2.6.1))
|
version: 7.0.1(eslint@9.39.2(jiti@2.6.1))
|
||||||
eslint-plugin-react-refresh:
|
eslint-plugin-react-refresh:
|
||||||
specifier: ^0.4.24
|
specifier: ^0.4.24
|
||||||
version: 0.4.24(eslint@9.39.1(jiti@2.6.1))
|
version: 0.4.24(eslint@9.39.2(jiti@2.6.1))
|
||||||
globals:
|
globals:
|
||||||
specifier: ^16.5.0
|
specifier: ^16.5.0
|
||||||
version: 16.5.0
|
version: 16.5.0
|
||||||
@@ -119,7 +119,7 @@ importers:
|
|||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
typescript-eslint:
|
typescript-eslint:
|
||||||
specifier: ^8.49.0
|
specifier: ^8.49.0
|
||||||
version: 8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)
|
version: 8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||||
vite:
|
vite:
|
||||||
specifier: ^7.2.7
|
specifier: ^7.2.7
|
||||||
version: 7.2.7(@types/node@25.0.1)(jiti@2.6.1)(lightningcss@1.30.2)
|
version: 7.2.7(@types/node@25.0.1)(jiti@2.6.1)(lightningcss@1.30.2)
|
||||||
@@ -394,8 +394,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==}
|
resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
'@eslint/js@9.39.1':
|
'@eslint/js@9.39.2':
|
||||||
resolution: {integrity: sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==}
|
resolution: {integrity: sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
'@eslint/object-schema@2.1.7':
|
'@eslint/object-schema@2.1.7':
|
||||||
@@ -1507,8 +1507,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==}
|
resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
eslint@9.39.1:
|
eslint@9.39.2:
|
||||||
resolution: {integrity: sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==}
|
resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -2260,9 +2260,9 @@ snapshots:
|
|||||||
'@esbuild/win32-x64@0.25.12':
|
'@esbuild/win32-x64@0.25.12':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@eslint-community/eslint-utils@4.9.0(eslint@9.39.1(jiti@2.6.1))':
|
'@eslint-community/eslint-utils@4.9.0(eslint@9.39.2(jiti@2.6.1))':
|
||||||
dependencies:
|
dependencies:
|
||||||
eslint: 9.39.1(jiti@2.6.1)
|
eslint: 9.39.2(jiti@2.6.1)
|
||||||
eslint-visitor-keys: 3.4.3
|
eslint-visitor-keys: 3.4.3
|
||||||
|
|
||||||
'@eslint-community/regexpp@4.12.2': {}
|
'@eslint-community/regexpp@4.12.2': {}
|
||||||
@@ -2297,7 +2297,7 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@eslint/js@9.39.1': {}
|
'@eslint/js@9.39.2': {}
|
||||||
|
|
||||||
'@eslint/object-schema@2.1.7': {}
|
'@eslint/object-schema@2.1.7': {}
|
||||||
|
|
||||||
@@ -3098,15 +3098,15 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
csstype: 3.2.3
|
csstype: 3.2.3
|
||||||
|
|
||||||
'@typescript-eslint/eslint-plugin@8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)':
|
'@typescript-eslint/eslint-plugin@8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint-community/regexpp': 4.12.2
|
'@eslint-community/regexpp': 4.12.2
|
||||||
'@typescript-eslint/parser': 8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)
|
'@typescript-eslint/parser': 8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||||
'@typescript-eslint/scope-manager': 8.49.0
|
'@typescript-eslint/scope-manager': 8.49.0
|
||||||
'@typescript-eslint/type-utils': 8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)
|
'@typescript-eslint/type-utils': 8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||||
'@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)
|
'@typescript-eslint/utils': 8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||||
'@typescript-eslint/visitor-keys': 8.49.0
|
'@typescript-eslint/visitor-keys': 8.49.0
|
||||||
eslint: 9.39.1(jiti@2.6.1)
|
eslint: 9.39.2(jiti@2.6.1)
|
||||||
ignore: 7.0.5
|
ignore: 7.0.5
|
||||||
natural-compare: 1.4.0
|
natural-compare: 1.4.0
|
||||||
ts-api-utils: 2.1.0(typescript@5.9.3)
|
ts-api-utils: 2.1.0(typescript@5.9.3)
|
||||||
@@ -3114,14 +3114,14 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)':
|
'@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/scope-manager': 8.49.0
|
'@typescript-eslint/scope-manager': 8.49.0
|
||||||
'@typescript-eslint/types': 8.49.0
|
'@typescript-eslint/types': 8.49.0
|
||||||
'@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3)
|
'@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3)
|
||||||
'@typescript-eslint/visitor-keys': 8.49.0
|
'@typescript-eslint/visitor-keys': 8.49.0
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
eslint: 9.39.1(jiti@2.6.1)
|
eslint: 9.39.2(jiti@2.6.1)
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
@@ -3144,13 +3144,13 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
|
|
||||||
'@typescript-eslint/type-utils@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)':
|
'@typescript-eslint/type-utils@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/types': 8.49.0
|
'@typescript-eslint/types': 8.49.0
|
||||||
'@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3)
|
'@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3)
|
||||||
'@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)
|
'@typescript-eslint/utils': 8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
eslint: 9.39.1(jiti@2.6.1)
|
eslint: 9.39.2(jiti@2.6.1)
|
||||||
ts-api-utils: 2.1.0(typescript@5.9.3)
|
ts-api-utils: 2.1.0(typescript@5.9.3)
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -3173,13 +3173,13 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/utils@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)':
|
'@typescript-eslint/utils@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1))
|
'@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@2.6.1))
|
||||||
'@typescript-eslint/scope-manager': 8.49.0
|
'@typescript-eslint/scope-manager': 8.49.0
|
||||||
'@typescript-eslint/types': 8.49.0
|
'@typescript-eslint/types': 8.49.0
|
||||||
'@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3)
|
'@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3)
|
||||||
eslint: 9.39.1(jiti@2.6.1)
|
eslint: 9.39.2(jiti@2.6.1)
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
@@ -3328,20 +3328,20 @@ snapshots:
|
|||||||
|
|
||||||
escape-string-regexp@4.0.0: {}
|
escape-string-regexp@4.0.0: {}
|
||||||
|
|
||||||
eslint-plugin-react-hooks@7.0.1(eslint@9.39.1(jiti@2.6.1)):
|
eslint-plugin-react-hooks@7.0.1(eslint@9.39.2(jiti@2.6.1)):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.28.5
|
'@babel/core': 7.28.5
|
||||||
'@babel/parser': 7.28.5
|
'@babel/parser': 7.28.5
|
||||||
eslint: 9.39.1(jiti@2.6.1)
|
eslint: 9.39.2(jiti@2.6.1)
|
||||||
hermes-parser: 0.25.1
|
hermes-parser: 0.25.1
|
||||||
zod: 4.1.13
|
zod: 4.1.13
|
||||||
zod-validation-error: 4.0.2(zod@4.1.13)
|
zod-validation-error: 4.0.2(zod@4.1.13)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
eslint-plugin-react-refresh@0.4.24(eslint@9.39.1(jiti@2.6.1)):
|
eslint-plugin-react-refresh@0.4.24(eslint@9.39.2(jiti@2.6.1)):
|
||||||
dependencies:
|
dependencies:
|
||||||
eslint: 9.39.1(jiti@2.6.1)
|
eslint: 9.39.2(jiti@2.6.1)
|
||||||
|
|
||||||
eslint-scope@8.4.0:
|
eslint-scope@8.4.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -3352,15 +3352,15 @@ snapshots:
|
|||||||
|
|
||||||
eslint-visitor-keys@4.2.1: {}
|
eslint-visitor-keys@4.2.1: {}
|
||||||
|
|
||||||
eslint@9.39.1(jiti@2.6.1):
|
eslint@9.39.2(jiti@2.6.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1))
|
'@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@2.6.1))
|
||||||
'@eslint-community/regexpp': 4.12.2
|
'@eslint-community/regexpp': 4.12.2
|
||||||
'@eslint/config-array': 0.21.1
|
'@eslint/config-array': 0.21.1
|
||||||
'@eslint/config-helpers': 0.4.2
|
'@eslint/config-helpers': 0.4.2
|
||||||
'@eslint/core': 0.17.0
|
'@eslint/core': 0.17.0
|
||||||
'@eslint/eslintrc': 3.3.3
|
'@eslint/eslintrc': 3.3.3
|
||||||
'@eslint/js': 9.39.1
|
'@eslint/js': 9.39.2
|
||||||
'@eslint/plugin-kit': 0.4.1
|
'@eslint/plugin-kit': 0.4.1
|
||||||
'@humanfs/node': 0.16.7
|
'@humanfs/node': 0.16.7
|
||||||
'@humanwhocodes/module-importer': 1.0.1
|
'@humanwhocodes/module-importer': 1.0.1
|
||||||
@@ -3780,13 +3780,13 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
prelude-ls: 1.2.1
|
prelude-ls: 1.2.1
|
||||||
|
|
||||||
typescript-eslint@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3):
|
typescript-eslint@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/eslint-plugin': 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)
|
'@typescript-eslint/eslint-plugin': 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||||
'@typescript-eslint/parser': 8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)
|
'@typescript-eslint/parser': 8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||||
'@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3)
|
'@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3)
|
||||||
'@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)
|
'@typescript-eslint/utils': 8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||||
eslint: 9.39.1(jiti@2.6.1)
|
eslint: 9.39.2(jiti@2.6.1)
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Spinner } from "@/components/ui/spinner";
|
import { Spinner } from "@/components/ui/spinner";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -8,8 +8,7 @@ import {
|
|||||||
TrendingUp,
|
TrendingUp,
|
||||||
FileAudio,
|
FileAudio,
|
||||||
Clock,
|
Clock,
|
||||||
Gauge,
|
Gauge
|
||||||
HardDrive
|
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import type { AnalysisResult } from "@/types/api";
|
import type { AnalysisResult } from "@/types/api";
|
||||||
|
|
||||||
@@ -80,30 +79,13 @@ export function AudioAnalysis({
|
|||||||
// Calculate Nyquist frequency (half of sample rate)
|
// Calculate Nyquist frequency (half of sample rate)
|
||||||
const nyquistFreq = result.sample_rate / 2;
|
const nyquistFreq = result.sample_rate / 2;
|
||||||
|
|
||||||
// Calculate approximate data size (uncompressed PCM)
|
|
||||||
// Formula: sample_rate * channels * (bits_per_sample / 8) * duration
|
|
||||||
const dataSizeBytes = result.sample_rate * result.channels * (result.bits_per_sample / 8) * result.duration;
|
|
||||||
const dataSizeMB = dataSizeBytes / (1024 * 1024);
|
|
||||||
|
|
||||||
const formatDataSize = (mb: number) => {
|
|
||||||
if (mb >= 1024) {
|
|
||||||
return `${(mb / 1024).toFixed(2)} GB`;
|
|
||||||
}
|
|
||||||
return `${mb.toFixed(2)} MB`;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="space-y-1">
|
<CardTitle className="flex items-center gap-2">
|
||||||
<CardTitle className="flex items-center gap-2">
|
<Activity className="h-5 w-5" />
|
||||||
<Activity className="h-5 w-5" />
|
Audio Quality Analysis
|
||||||
Audio Quality Analysis
|
</CardTitle>
|
||||||
</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
Technical analysis of audio file properties
|
|
||||||
</CardDescription>
|
|
||||||
</div>
|
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<CardContent className="px-6 space-y-6">
|
<CardContent className="px-6 space-y-6">
|
||||||
@@ -149,14 +131,6 @@ export function AudioAnalysis({
|
|||||||
</div>
|
</div>
|
||||||
<p className="font-semibold">{(nyquistFreq / 1000).toFixed(1)} kHz</p>
|
<p className="font-semibold">{(nyquistFreq / 1000).toFixed(1)} kHz</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1">
|
|
||||||
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
|
||||||
<HardDrive className="h-3 w-3" />
|
|
||||||
Data Size
|
|
||||||
</div>
|
|
||||||
<p className="font-semibold">{formatDataSize(dataSizeMB)}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Dynamic Range Analysis */}
|
{/* Dynamic Range Analysis */}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useCallback, useEffect, useRef } from "react";
|
import { useState, useCallback, useEffect } from "react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import {
|
import {
|
||||||
@@ -11,10 +11,10 @@ import {
|
|||||||
X,
|
X,
|
||||||
CheckCircle2,
|
CheckCircle2,
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
Loader2,
|
|
||||||
Trash2,
|
Trash2,
|
||||||
FileMusic,
|
FileMusic,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
import { Spinner } from "@/components/ui/spinner";
|
||||||
import {
|
import {
|
||||||
IsFFmpegInstalled,
|
IsFFmpegInstalled,
|
||||||
DownloadFFmpeg,
|
DownloadFFmpeg,
|
||||||
@@ -47,9 +47,9 @@ export function AudioConverterPage() {
|
|||||||
const [ffmpegInstalled, setFfmpegInstalled] = useState<boolean>(false);
|
const [ffmpegInstalled, setFfmpegInstalled] = useState<boolean>(false);
|
||||||
const [installingFfmpeg, setInstallingFfmpeg] = useState(false);
|
const [installingFfmpeg, setInstallingFfmpeg] = useState(false);
|
||||||
const [files, setFiles] = useState<AudioFile[]>(() => {
|
const [files, setFiles] = useState<AudioFile[]>(() => {
|
||||||
// Initialize from localStorage synchronously
|
// Initialize from sessionStorage synchronously
|
||||||
try {
|
try {
|
||||||
const saved = localStorage.getItem(STORAGE_KEY);
|
const saved = sessionStorage.getItem(STORAGE_KEY);
|
||||||
if (saved) {
|
if (saved) {
|
||||||
const parsed = JSON.parse(saved);
|
const parsed = JSON.parse(saved);
|
||||||
if (parsed.files && Array.isArray(parsed.files) && parsed.files.length > 0) {
|
if (parsed.files && Array.isArray(parsed.files) && parsed.files.length > 0) {
|
||||||
@@ -63,7 +63,7 @@ export function AudioConverterPage() {
|
|||||||
});
|
});
|
||||||
const [outputFormat, setOutputFormat] = useState<"mp3" | "m4a">(() => {
|
const [outputFormat, setOutputFormat] = useState<"mp3" | "m4a">(() => {
|
||||||
try {
|
try {
|
||||||
const saved = localStorage.getItem(STORAGE_KEY);
|
const saved = sessionStorage.getItem(STORAGE_KEY);
|
||||||
if (saved) {
|
if (saved) {
|
||||||
const parsed = JSON.parse(saved);
|
const parsed = JSON.parse(saved);
|
||||||
if (parsed.outputFormat === "mp3" || parsed.outputFormat === "m4a") {
|
if (parsed.outputFormat === "mp3" || parsed.outputFormat === "m4a") {
|
||||||
@@ -77,7 +77,7 @@ export function AudioConverterPage() {
|
|||||||
});
|
});
|
||||||
const [bitrate, setBitrate] = useState(() => {
|
const [bitrate, setBitrate] = useState(() => {
|
||||||
try {
|
try {
|
||||||
const saved = localStorage.getItem(STORAGE_KEY);
|
const saved = sessionStorage.getItem(STORAGE_KEY);
|
||||||
if (saved) {
|
if (saved) {
|
||||||
const parsed = JSON.parse(saved);
|
const parsed = JSON.parse(saved);
|
||||||
if (parsed.bitrate) {
|
if (parsed.bitrate) {
|
||||||
@@ -92,38 +92,47 @@ export function AudioConverterPage() {
|
|||||||
const [converting, setConverting] = useState(false);
|
const [converting, setConverting] = useState(false);
|
||||||
const [isDragging, setIsDragging] = useState(false);
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
const [isDraggingFFmpeg, setIsDraggingFFmpeg] = useState(false);
|
const [isDraggingFFmpeg, setIsDraggingFFmpeg] = useState(false);
|
||||||
const isInitialMount = useRef(true);
|
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||||
|
|
||||||
// Helper function to save state
|
// Helper function to save state to sessionStorage
|
||||||
const saveState = useCallback((stateToSave: { files: AudioFile[]; outputFormat: "mp3" | "m4a"; bitrate: string }) => {
|
const saveState = useCallback((stateToSave: { files: AudioFile[]; outputFormat: "mp3" | "m4a"; bitrate: string }) => {
|
||||||
try {
|
try {
|
||||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(stateToSave));
|
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(stateToSave));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to save state:", err);
|
console.error("Failed to save state:", err);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Load saved state from localStorage on mount (only for ffmpeg check)
|
// Load saved state from sessionStorage on mount (only for ffmpeg check)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
checkFfmpegInstallation();
|
checkFfmpegInstallation();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Save state to localStorage whenever files, outputFormat, or bitrate changes
|
// Save state to sessionStorage whenever files, outputFormat, or bitrate changes
|
||||||
// Skip on initial mount to avoid overwriting with empty state
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isInitialMount.current) {
|
|
||||||
isInitialMount.current = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
saveState({ files, outputFormat, bitrate });
|
saveState({ files, outputFormat, bitrate });
|
||||||
}, [files, outputFormat, bitrate, saveState]);
|
}, [files, outputFormat, bitrate, saveState]);
|
||||||
|
|
||||||
// Save state on unmount as well
|
// Detect fullscreen/maximized window
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
const checkFullscreen = () => {
|
||||||
saveState({ files, outputFormat, bitrate });
|
// Check if window is maximized or fullscreen
|
||||||
|
// For Wails, we can check if window height is close to screen height
|
||||||
|
const isMaximized = window.innerHeight >= window.screen.height * 0.9;
|
||||||
|
setIsFullscreen(isMaximized);
|
||||||
};
|
};
|
||||||
}, [files, outputFormat, bitrate, saveState]);
|
|
||||||
|
checkFullscreen();
|
||||||
|
window.addEventListener("resize", checkFullscreen);
|
||||||
|
|
||||||
|
// Also check on window focus in case user maximizes externally
|
||||||
|
window.addEventListener("focus", checkFullscreen);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("resize", checkFullscreen);
|
||||||
|
window.removeEventListener("focus", checkFullscreen);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
const checkFfmpegInstallation = async () => {
|
const checkFfmpegInstallation = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -373,7 +382,7 @@ export function AudioConverterPage() {
|
|||||||
const getStatusIcon = (status: AudioFile["status"]) => {
|
const getStatusIcon = (status: AudioFile["status"]) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case "converting":
|
case "converting":
|
||||||
return <Loader2 className="h-4 w-4 animate-spin text-primary" />;
|
return <Spinner className="h-4 w-4 text-primary" />;
|
||||||
case "success":
|
case "success":
|
||||||
return <CheckCircle2 className="h-4 w-4 text-green-500" />;
|
return <CheckCircle2 className="h-4 w-4 text-green-500" />;
|
||||||
case "error":
|
case "error":
|
||||||
@@ -390,13 +399,15 @@ export function AudioConverterPage() {
|
|||||||
// Show FFmpeg installation prompt if not installed
|
// Show FFmpeg installation prompt if not installed
|
||||||
if (ffmpegInstalled === false) {
|
if (ffmpegInstalled === false) {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className={`space-y-6 ${isFullscreen ? "h-full flex flex-col" : ""}`}>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<h1 className="text-2xl font-bold">Audio Converter</h1>
|
<h1 className="text-2xl font-bold">Audio Converter</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={`flex flex-col items-center justify-center h-[400px] border-2 border-dashed rounded-lg transition-colors ${
|
className={`flex flex-col items-center justify-center border-2 border-dashed rounded-lg transition-all ${
|
||||||
|
isFullscreen ? "flex-1 min-h-[400px]" : "h-[400px]"
|
||||||
|
} ${
|
||||||
isDraggingFFmpeg
|
isDraggingFFmpeg
|
||||||
? "border-primary bg-primary/10"
|
? "border-primary bg-primary/10"
|
||||||
: "border-muted-foreground/30"
|
: "border-muted-foreground/30"
|
||||||
@@ -433,7 +444,7 @@ export function AudioConverterPage() {
|
|||||||
>
|
>
|
||||||
{installingFfmpeg ? (
|
{installingFfmpeg ? (
|
||||||
<>
|
<>
|
||||||
<Loader2 className="h-5 w-5" />
|
<Spinner className="h-5 w-5" />
|
||||||
Installing FFmpeg...
|
Installing FFmpeg...
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
@@ -449,7 +460,7 @@ export function AudioConverterPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className={`space-y-6 ${isFullscreen ? "h-full flex flex-col" : ""}`}>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<h1 className="text-2xl font-bold">Audio Converter</h1>
|
<h1 className="text-2xl font-bold">Audio Converter</h1>
|
||||||
@@ -457,7 +468,9 @@ export function AudioConverterPage() {
|
|||||||
|
|
||||||
{/* Drop Zone / File List */}
|
{/* Drop Zone / File List */}
|
||||||
<div
|
<div
|
||||||
className={`flex flex-col items-center justify-center h-[400px] border-2 border-dashed rounded-lg transition-colors ${
|
className={`flex flex-col items-center justify-center border-2 border-dashed rounded-lg transition-all ${
|
||||||
|
isFullscreen ? "flex-1 min-h-[400px]" : "h-[400px]"
|
||||||
|
} ${
|
||||||
isDragging
|
isDragging
|
||||||
? "border-primary bg-primary/10"
|
? "border-primary bg-primary/10"
|
||||||
: "border-muted-foreground/30"
|
: "border-muted-foreground/30"
|
||||||
@@ -599,15 +612,15 @@ export function AudioConverterPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Convert Button */}
|
{/* Convert Button */}
|
||||||
<div className="flex justify-end pt-4 border-t shrink-0">
|
<div className="flex justify-center pt-4 border-t shrink-0">
|
||||||
<Button
|
<Button
|
||||||
onClick={handleConvert}
|
onClick={handleConvert}
|
||||||
disabled={converting || convertableCount === 0}
|
disabled={converting || convertableCount === 0}
|
||||||
size="lg"
|
size="lg"
|
||||||
>
|
>
|
||||||
{converting ? (
|
{converting ? (
|
||||||
<>
|
<>
|
||||||
<Loader2 className="h-4 w-4 animate-spin" />
|
<Spinner className="h-4 w-4" />
|
||||||
Converting...
|
Converting...
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -221,29 +221,62 @@ export function SettingsPage() {
|
|||||||
{/* Source Selection */}
|
{/* Source Selection */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="downloader" className="text-sm">Source</Label>
|
<Label htmlFor="downloader" className="text-sm">Source</Label>
|
||||||
<Select
|
<div className="flex gap-2">
|
||||||
value={tempSettings.downloader}
|
<Select
|
||||||
onValueChange={(value: "auto" | "deezer" | "tidal" | "qobuz" | "amazon") => setTempSettings((prev) => ({ ...prev, downloader: value }))}
|
value={tempSettings.downloader}
|
||||||
>
|
onValueChange={(value: "auto" | "deezer" | "tidal" | "qobuz" | "amazon") => setTempSettings((prev) => ({ ...prev, downloader: value }))}
|
||||||
<SelectTrigger id="downloader" className="h-9">
|
>
|
||||||
<SelectValue placeholder="Select a source" />
|
<SelectTrigger id="downloader" className="h-9 w-fit">
|
||||||
</SelectTrigger>
|
<SelectValue placeholder="Select a source" />
|
||||||
<SelectContent>
|
</SelectTrigger>
|
||||||
<SelectItem value="auto">Auto</SelectItem>
|
<SelectContent>
|
||||||
<SelectItem value="tidal">
|
<SelectItem value="auto">Auto</SelectItem>
|
||||||
<span className="flex items-center"><TidalIcon />Tidal</span>
|
<SelectItem value="tidal">
|
||||||
</SelectItem>
|
<span className="flex items-center"><TidalIcon />Tidal</span>
|
||||||
<SelectItem value="deezer">
|
</SelectItem>
|
||||||
<span className="flex items-center"><DeezerIcon />Deezer</span>
|
<SelectItem value="deezer">
|
||||||
</SelectItem>
|
<span className="flex items-center"><DeezerIcon />Deezer</span>
|
||||||
<SelectItem value="qobuz">
|
</SelectItem>
|
||||||
<span className="flex items-center"><QobuzIcon />Qobuz</span>
|
<SelectItem value="qobuz">
|
||||||
</SelectItem>
|
<span className="flex items-center"><QobuzIcon />Qobuz</span>
|
||||||
<SelectItem value="amazon">
|
</SelectItem>
|
||||||
<span className="flex items-center"><AmazonIcon />Amazon Music</span>
|
<SelectItem value="amazon">
|
||||||
</SelectItem>
|
<span className="flex items-center"><AmazonIcon />Amazon Music</span>
|
||||||
</SelectContent>
|
</SelectItem>
|
||||||
</Select>
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
{/* Quality dropdown for Tidal */}
|
||||||
|
{tempSettings.downloader === "tidal" && (
|
||||||
|
<Select
|
||||||
|
value={tempSettings.tidalQuality}
|
||||||
|
onValueChange={(value: "LOSSLESS" | "HI_RES_LOSSLESS") => setTempSettings((prev) => ({ ...prev, tidalQuality: value }))}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="h-9 w-fit">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="LOSSLESS">Lossless (16-bit/CD Quality)</SelectItem>
|
||||||
|
<SelectItem value="HI_RES_LOSSLESS">Hi-Res Lossless (24-bit/48kHz+)</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
)}
|
||||||
|
{/* Quality dropdown for Qobuz */}
|
||||||
|
{tempSettings.downloader === "qobuz" && (
|
||||||
|
<Select
|
||||||
|
value={tempSettings.qobuzQuality}
|
||||||
|
onValueChange={(value: "6" | "7" | "27") => setTempSettings((prev) => ({ ...prev, qobuzQuality: value }))}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="h-9 w-fit">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="6">FLAC 16-bit (CD Quality)</SelectItem>
|
||||||
|
<SelectItem value="7">FLAC 24-bit</SelectItem>
|
||||||
|
<SelectItem value="27">Hi-Res (24-bit/96kHz+)</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Embed Lyrics */}
|
{/* Embed Lyrics */}
|
||||||
@@ -256,7 +289,7 @@ export function SettingsPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-t pt-4" />
|
<div className="border-t pt-2" />
|
||||||
|
|
||||||
{/* Folder Structure */}
|
{/* Folder Structure */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
@@ -306,7 +339,7 @@ export function SettingsPage() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-t pt-4" />
|
<div className="border-t pt-2" />
|
||||||
|
|
||||||
{/* Filename Format */}
|
{/* Filename Format */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ export function useDownload() {
|
|||||||
service_url: streamingURLs.tidal_url,
|
service_url: streamingURLs.tidal_url,
|
||||||
duration: durationSeconds,
|
duration: durationSeconds,
|
||||||
item_id: itemID, // Pass the same itemID through all attempts
|
item_id: itemID, // Pass the same itemID through all attempts
|
||||||
|
audio_format: settings.tidalQuality || "LOSSLESS", // Use default LOSSLESS for auto mode
|
||||||
});
|
});
|
||||||
|
|
||||||
if (tidalResponse.success) {
|
if (tidalResponse.success) {
|
||||||
@@ -209,6 +210,7 @@ export function useDownload() {
|
|||||||
embed_lyrics: settings.embedLyrics,
|
embed_lyrics: settings.embedLyrics,
|
||||||
duration: durationMs ? Math.round(durationMs / 1000) : undefined,
|
duration: durationMs ? Math.round(durationMs / 1000) : undefined,
|
||||||
item_id: itemID,
|
item_id: itemID,
|
||||||
|
audio_format: settings.qobuzQuality || "6", // Use default 6 (16-bit) for auto mode
|
||||||
});
|
});
|
||||||
|
|
||||||
// If Qobuz also failed, mark the item as failed
|
// If Qobuz also failed, mark the item as failed
|
||||||
@@ -224,6 +226,14 @@ export function useDownload() {
|
|||||||
// Convert duration from ms to seconds for backend
|
// Convert duration from ms to seconds for backend
|
||||||
const durationSecondsForFallback = durationMs ? Math.round(durationMs / 1000) : undefined;
|
const durationSecondsForFallback = durationMs ? Math.round(durationMs / 1000) : undefined;
|
||||||
|
|
||||||
|
// Determine audio format based on service
|
||||||
|
let audioFormat: string | undefined;
|
||||||
|
if (service === "tidal") {
|
||||||
|
audioFormat = settings.tidalQuality || "LOSSLESS";
|
||||||
|
} else if (service === "qobuz") {
|
||||||
|
audioFormat = settings.qobuzQuality || "6";
|
||||||
|
}
|
||||||
|
|
||||||
const singleServiceResponse = await downloadTrack({
|
const singleServiceResponse = await downloadTrack({
|
||||||
isrc,
|
isrc,
|
||||||
service: service as "deezer" | "tidal" | "qobuz" | "amazon",
|
service: service as "deezer" | "tidal" | "qobuz" | "amazon",
|
||||||
@@ -240,6 +250,7 @@ export function useDownload() {
|
|||||||
embed_lyrics: settings.embedLyrics,
|
embed_lyrics: settings.embedLyrics,
|
||||||
duration: durationSecondsForFallback,
|
duration: durationSecondsForFallback,
|
||||||
item_id: itemID, // Pass itemID for tracking
|
item_id: itemID, // Pass itemID for tracking
|
||||||
|
audio_format: audioFormat,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mark as failed if download failed for single-service attempt
|
// Mark as failed if download failed for single-service attempt
|
||||||
@@ -344,6 +355,7 @@ export function useDownload() {
|
|||||||
service_url: streamingURLs.tidal_url,
|
service_url: streamingURLs.tidal_url,
|
||||||
duration: durationSeconds,
|
duration: durationSeconds,
|
||||||
item_id: itemID,
|
item_id: itemID,
|
||||||
|
audio_format: settings.tidalQuality || "LOSSLESS", // Use default LOSSLESS for auto mode
|
||||||
});
|
});
|
||||||
|
|
||||||
if (tidalResponse.success) {
|
if (tidalResponse.success) {
|
||||||
@@ -429,6 +441,7 @@ export function useDownload() {
|
|||||||
embed_lyrics: settings.embedLyrics,
|
embed_lyrics: settings.embedLyrics,
|
||||||
duration: durationMs ? Math.round(durationMs / 1000) : undefined,
|
duration: durationMs ? Math.round(durationMs / 1000) : undefined,
|
||||||
item_id: itemID,
|
item_id: itemID,
|
||||||
|
audio_format: settings.qobuzQuality || "6", // Use default 6 (16-bit) for auto mode
|
||||||
});
|
});
|
||||||
|
|
||||||
// If Qobuz also failed, mark the item as failed
|
// If Qobuz also failed, mark the item as failed
|
||||||
@@ -443,6 +456,14 @@ export function useDownload() {
|
|||||||
// Single service download
|
// Single service download
|
||||||
const durationSecondsForFallback = durationMs ? Math.round(durationMs / 1000) : undefined;
|
const durationSecondsForFallback = durationMs ? Math.round(durationMs / 1000) : undefined;
|
||||||
|
|
||||||
|
// Determine audio format based on service
|
||||||
|
let audioFormat: string | undefined;
|
||||||
|
if (service === "tidal") {
|
||||||
|
audioFormat = settings.tidalQuality || "LOSSLESS";
|
||||||
|
} else if (service === "qobuz") {
|
||||||
|
audioFormat = settings.qobuzQuality || "6";
|
||||||
|
}
|
||||||
|
|
||||||
const singleServiceResponse = await downloadTrack({
|
const singleServiceResponse = await downloadTrack({
|
||||||
isrc,
|
isrc,
|
||||||
service: service as "deezer" | "tidal" | "qobuz" | "amazon",
|
service: service as "deezer" | "tidal" | "qobuz" | "amazon",
|
||||||
@@ -459,6 +480,7 @@ export function useDownload() {
|
|||||||
embed_lyrics: settings.embedLyrics,
|
embed_lyrics: settings.embedLyrics,
|
||||||
duration: durationSecondsForFallback,
|
duration: durationSecondsForFallback,
|
||||||
item_id: itemID,
|
item_id: itemID,
|
||||||
|
audio_format: audioFormat,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mark as failed if download failed for single-service attempt
|
// Mark as failed if download failed for single-service attempt
|
||||||
|
|||||||
@@ -26,7 +26,10 @@ export interface Settings {
|
|||||||
trackNumber: boolean;
|
trackNumber: boolean;
|
||||||
sfxEnabled: boolean;
|
sfxEnabled: boolean;
|
||||||
embedLyrics: boolean;
|
embedLyrics: boolean;
|
||||||
operatingSystem: "Windows" | "linux/MacOS"
|
operatingSystem: "Windows" | "linux/MacOS";
|
||||||
|
// Quality settings for specific sources
|
||||||
|
tidalQuality: "LOSSLESS" | "HI_RES_LOSSLESS";
|
||||||
|
qobuzQuality: "6" | "7" | "27";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Folder preset templates
|
// Folder preset templates
|
||||||
@@ -83,7 +86,9 @@ export const DEFAULT_SETTINGS: Settings = {
|
|||||||
trackNumber: false,
|
trackNumber: false,
|
||||||
sfxEnabled: true,
|
sfxEnabled: true,
|
||||||
embedLyrics: false,
|
embedLyrics: false,
|
||||||
operatingSystem: detectOS()
|
operatingSystem: detectOS(),
|
||||||
|
tidalQuality: "LOSSLESS", // Default: 16-bit lossless
|
||||||
|
qobuzQuality: "6" // Default: FLAC 16-bit
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FONT_OPTIONS: { value: FontFamily; label: string; fontFamily: string }[] = [
|
export const FONT_OPTIONS: { value: FontFamily; label: string; fontFamily: string }[] = [
|
||||||
@@ -160,6 +165,13 @@ export function getSettings(): Settings {
|
|||||||
}
|
}
|
||||||
// Always use detected OS (don't persist it)
|
// Always use detected OS (don't persist it)
|
||||||
parsed.operatingSystem = detectOS();
|
parsed.operatingSystem = detectOS();
|
||||||
|
// Set default quality if not present
|
||||||
|
if (!('tidalQuality' in parsed)) {
|
||||||
|
parsed.tidalQuality = "LOSSLESS";
|
||||||
|
}
|
||||||
|
if (!('qobuzQuality' in parsed)) {
|
||||||
|
parsed.qobuzQuality = "6";
|
||||||
|
}
|
||||||
return { ...DEFAULT_SETTINGS, ...parsed };
|
return { ...DEFAULT_SETTINGS, ...parsed };
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user