mirror of
https://github.com/SoPat712/allstarr.git
synced 2026-02-09 23:55:10 -05:00
feat: add configurable audio quality selection for Deezer downloads
This commit is contained in:
@@ -10,3 +10,7 @@ DEEZER_ARL=your-deezer-arl-token
|
|||||||
|
|
||||||
# Fallback ARL token (optional)
|
# Fallback ARL token (optional)
|
||||||
DEEZER_ARL_FALLBACK=
|
DEEZER_ARL_FALLBACK=
|
||||||
|
|
||||||
|
# Preferred audio quality: FLAC, MP3_320, MP3_128 (optional)
|
||||||
|
# If not specified, the highest available quality for your account will be used
|
||||||
|
DEEZER_QUALITY=
|
||||||
|
|||||||
@@ -58,6 +58,10 @@ The easiest way to run Octo-Fiesta is with Docker Compose.
|
|||||||
|
|
||||||
# Deezer ARL token (required)
|
# Deezer ARL token (required)
|
||||||
DEEZER_ARL=your-deezer-arl-token
|
DEEZER_ARL=your-deezer-arl-token
|
||||||
|
|
||||||
|
# Preferred audio quality (optional): FLAC, MP3_320, MP3_128
|
||||||
|
# If not set, the highest available quality for your account will be used
|
||||||
|
DEEZER_QUALITY=
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Start the container**
|
3. **Start the container**
|
||||||
@@ -93,6 +97,7 @@ The easiest way to run Octo-Fiesta is with Docker Compose.
|
|||||||
|---------|-------------|
|
|---------|-------------|
|
||||||
| `Deezer:Arl` | Your Deezer ARL token (required for downloads) |
|
| `Deezer:Arl` | Your Deezer ARL token (required for downloads) |
|
||||||
| `Deezer:ArlFallback` | Backup ARL token if primary fails |
|
| `Deezer:ArlFallback` | Backup ARL token if primary fails |
|
||||||
|
| `Deezer:Quality` | Preferred audio quality: `FLAC`, `MP3_320`, `MP3_128`. If not specified, the highest available quality for your account will be used |
|
||||||
|
|
||||||
### Getting a Deezer ARL Token
|
### Getting a Deezer ARL Token
|
||||||
|
|
||||||
|
|||||||
@@ -15,5 +15,7 @@ services:
|
|||||||
- Deezer__Arl=${DEEZER_ARL}
|
- Deezer__Arl=${DEEZER_ARL}
|
||||||
# Fallback ARL token (optional)
|
# Fallback ARL token (optional)
|
||||||
- Deezer__ArlFallback=${DEEZER_ARL_FALLBACK:-}
|
- Deezer__ArlFallback=${DEEZER_ARL_FALLBACK:-}
|
||||||
|
# Preferred audio quality: FLAC, MP3_320, MP3_128 (optional, defaults to highest available)
|
||||||
|
- Deezer__Quality=${DEEZER_QUALITY:-}
|
||||||
volumes:
|
volumes:
|
||||||
- ${DOWNLOAD_PATH:-./downloads}:/app/downloads
|
- ${DOWNLOAD_PATH:-./downloads}:/app/downloads
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ public class DeezerDownloaderSettings
|
|||||||
public string? Arl { get; set; }
|
public string? Arl { get; set; }
|
||||||
public string? ArlFallback { get; set; }
|
public string? ArlFallback { get; set; }
|
||||||
public string DownloadPath { get; set; } = "./downloads";
|
public string DownloadPath { get; set; } = "./downloads";
|
||||||
|
/// <summary>
|
||||||
|
/// Preferred audio quality: FLAC, MP3_320, MP3_128
|
||||||
|
/// If not specified or unavailable, the highest available quality will be used.
|
||||||
|
/// </summary>
|
||||||
|
public string? Quality { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -35,6 +40,7 @@ public class DeezerDownloadService : IDownloadService
|
|||||||
private readonly string _downloadPath;
|
private readonly string _downloadPath;
|
||||||
private readonly string? _arl;
|
private readonly string? _arl;
|
||||||
private readonly string? _arlFallback;
|
private readonly string? _arlFallback;
|
||||||
|
private readonly string? _preferredQuality;
|
||||||
|
|
||||||
private string? _apiToken;
|
private string? _apiToken;
|
||||||
private string? _licenseToken;
|
private string? _licenseToken;
|
||||||
@@ -69,6 +75,7 @@ public class DeezerDownloadService : IDownloadService
|
|||||||
_downloadPath = configuration["Library:DownloadPath"] ?? "./downloads";
|
_downloadPath = configuration["Library:DownloadPath"] ?? "./downloads";
|
||||||
_arl = configuration["Deezer:Arl"];
|
_arl = configuration["Deezer:Arl"];
|
||||||
_arlFallback = configuration["Deezer:ArlFallback"];
|
_arlFallback = configuration["Deezer:ArlFallback"];
|
||||||
|
_preferredQuality = configuration["Deezer:Quality"];
|
||||||
|
|
||||||
if (!Directory.Exists(_downloadPath))
|
if (!Directory.Exists(_downloadPath))
|
||||||
{
|
{
|
||||||
@@ -280,6 +287,9 @@ public class DeezerDownloadService : IDownloadService
|
|||||||
: "";
|
: "";
|
||||||
|
|
||||||
// Get download URL via media API
|
// Get download URL via media API
|
||||||
|
// Build format list based on preferred quality
|
||||||
|
var formatsList = BuildFormatsList(_preferredQuality);
|
||||||
|
|
||||||
var mediaRequest = new
|
var mediaRequest = new
|
||||||
{
|
{
|
||||||
license_token = _licenseToken,
|
license_token = _licenseToken,
|
||||||
@@ -288,12 +298,7 @@ public class DeezerDownloadService : IDownloadService
|
|||||||
new
|
new
|
||||||
{
|
{
|
||||||
type = "FULL",
|
type = "FULL",
|
||||||
formats = new[]
|
formats = formatsList
|
||||||
{
|
|
||||||
new { cipher = "BF_CBC_STRIPE", format = "MP3_128" },
|
|
||||||
new { cipher = "BF_CBC_STRIPE", format = "MP3_320" },
|
|
||||||
new { cipher = "BF_CBC_STRIPE", format = "FLAC" }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
track_tokens = new[] { trackToken }
|
track_tokens = new[] { trackToken }
|
||||||
@@ -326,29 +331,61 @@ public class DeezerDownloadService : IDownloadService
|
|||||||
throw new Exception("No media sources available - track may be unavailable in your region");
|
throw new Exception("No media sources available - track may be unavailable in your region");
|
||||||
}
|
}
|
||||||
|
|
||||||
string? downloadUrl = null;
|
// Build a dictionary of available formats
|
||||||
string? format = null;
|
var availableFormats = new Dictionary<string, string>();
|
||||||
|
|
||||||
foreach (var mediaItem in media.EnumerateArray())
|
foreach (var mediaItem in media.EnumerateArray())
|
||||||
{
|
{
|
||||||
if (mediaItem.TryGetProperty("sources", out var sources) &&
|
if (mediaItem.TryGetProperty("format", out var formatEl) &&
|
||||||
|
mediaItem.TryGetProperty("sources", out var sources) &&
|
||||||
sources.GetArrayLength() > 0)
|
sources.GetArrayLength() > 0)
|
||||||
{
|
{
|
||||||
downloadUrl = sources[0].GetProperty("url").GetString();
|
var fmt = formatEl.GetString();
|
||||||
format = mediaItem.GetProperty("format").GetString();
|
var url = sources[0].GetProperty("url").GetString();
|
||||||
|
if (!string.IsNullOrEmpty(fmt) && !string.IsNullOrEmpty(url))
|
||||||
|
{
|
||||||
|
availableFormats[fmt] = url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (availableFormats.Count == 0)
|
||||||
|
{
|
||||||
|
throw new Exception("No download URL found in media sources - track may be region locked");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log available formats for debugging
|
||||||
|
_logger.LogInformation("Available formats from Deezer: {Formats}", string.Join(", ", availableFormats.Keys));
|
||||||
|
|
||||||
|
// Quality priority order (highest to lowest)
|
||||||
|
// Since we already filtered the requested formats based on preference,
|
||||||
|
// we just need to pick the best one available
|
||||||
|
var qualityPriority = new[] { "FLAC", "MP3_320", "MP3_128" };
|
||||||
|
|
||||||
|
string? selectedFormat = null;
|
||||||
|
string? downloadUrl = null;
|
||||||
|
|
||||||
|
// Select the best available quality from what Deezer returned
|
||||||
|
foreach (var quality in qualityPriority)
|
||||||
|
{
|
||||||
|
if (availableFormats.TryGetValue(quality, out var url))
|
||||||
|
{
|
||||||
|
selectedFormat = quality;
|
||||||
|
downloadUrl = url;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(downloadUrl))
|
if (string.IsNullOrEmpty(downloadUrl))
|
||||||
{
|
{
|
||||||
throw new Exception("No download URL found in media sources - track may be region locked");
|
throw new Exception("No compatible format found in available media sources");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Selected quality: {Format}", selectedFormat);
|
||||||
|
|
||||||
return new DownloadResult
|
return new DownloadResult
|
||||||
{
|
{
|
||||||
DownloadUrl = downloadUrl,
|
DownloadUrl = downloadUrl,
|
||||||
Format = format ?? "MP3_128",
|
Format = selectedFormat ?? "MP3_128",
|
||||||
Title = title,
|
Title = title,
|
||||||
Artist = artist
|
Artist = artist
|
||||||
};
|
};
|
||||||
@@ -642,6 +679,44 @@ public class DeezerDownloadService : IDownloadService
|
|||||||
|
|
||||||
#region Utility Methods
|
#region Utility Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Builds the list of formats to request from Deezer based on preferred quality.
|
||||||
|
/// If a specific quality is preferred, only request that quality and lower.
|
||||||
|
/// This prevents Deezer from returning higher quality formats when user wants a specific one.
|
||||||
|
/// </summary>
|
||||||
|
private static object[] BuildFormatsList(string? preferredQuality)
|
||||||
|
{
|
||||||
|
var allFormats = new[]
|
||||||
|
{
|
||||||
|
new { cipher = "BF_CBC_STRIPE", format = "FLAC" },
|
||||||
|
new { cipher = "BF_CBC_STRIPE", format = "MP3_320" },
|
||||||
|
new { cipher = "BF_CBC_STRIPE", format = "MP3_128" }
|
||||||
|
};
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(preferredQuality))
|
||||||
|
{
|
||||||
|
// No preference, request all formats (highest quality will be selected)
|
||||||
|
return allFormats;
|
||||||
|
}
|
||||||
|
|
||||||
|
var preferred = preferredQuality.ToUpperInvariant();
|
||||||
|
|
||||||
|
return preferred switch
|
||||||
|
{
|
||||||
|
"FLAC" => allFormats, // Request all, FLAC will be preferred
|
||||||
|
"MP3_320" => new object[]
|
||||||
|
{
|
||||||
|
new { cipher = "BF_CBC_STRIPE", format = "MP3_320" },
|
||||||
|
new { cipher = "BF_CBC_STRIPE", format = "MP3_128" }
|
||||||
|
},
|
||||||
|
"MP3_128" => new object[]
|
||||||
|
{
|
||||||
|
new { cipher = "BF_CBC_STRIPE", format = "MP3_128" }
|
||||||
|
},
|
||||||
|
_ => allFormats // Unknown preference, request all
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<T> RetryWithBackoffAsync<T>(Func<Task<T>> action, int maxRetries = 3, int initialDelayMs = 1000)
|
private async Task<T> RetryWithBackoffAsync<T>(Func<Task<T>> action, int maxRetries = 3, int initialDelayMs = 1000)
|
||||||
{
|
{
|
||||||
Exception? lastException = null;
|
Exception? lastException = null;
|
||||||
|
|||||||
Reference in New Issue
Block a user