diff --git a/.env.example b/.env.example
index 87fca43..67dd47a 100644
--- a/.env.example
+++ b/.env.example
@@ -10,3 +10,7 @@ DEEZER_ARL=your-deezer-arl-token
# Fallback ARL token (optional)
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=
diff --git a/README.md b/README.md
index fed88b5..a6011e8 100644
--- a/README.md
+++ b/README.md
@@ -58,6 +58,10 @@ The easiest way to run Octo-Fiesta is with Docker Compose.
# Deezer ARL token (required)
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**
@@ -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: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
diff --git a/docker-compose.yml b/docker-compose.yml
index 7baf567..d8e1afd 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -15,5 +15,7 @@ services:
- Deezer__Arl=${DEEZER_ARL}
# Fallback ARL token (optional)
- Deezer__ArlFallback=${DEEZER_ARL_FALLBACK:-}
+ # Preferred audio quality: FLAC, MP3_320, MP3_128 (optional, defaults to highest available)
+ - Deezer__Quality=${DEEZER_QUALITY:-}
volumes:
- ${DOWNLOAD_PATH:-./downloads}:/app/downloads
diff --git a/octo-fiesta/Services/DeezerDownloadService.cs b/octo-fiesta/Services/DeezerDownloadService.cs
index 0442bd6..a6d45bd 100644
--- a/octo-fiesta/Services/DeezerDownloadService.cs
+++ b/octo-fiesta/Services/DeezerDownloadService.cs
@@ -18,6 +18,11 @@ public class DeezerDownloaderSettings
public string? Arl { get; set; }
public string? ArlFallback { get; set; }
public string DownloadPath { get; set; } = "./downloads";
+ ///
+ /// Preferred audio quality: FLAC, MP3_320, MP3_128
+ /// If not specified or unavailable, the highest available quality will be used.
+ ///
+ public string? Quality { get; set; }
}
///
@@ -35,6 +40,7 @@ public class DeezerDownloadService : IDownloadService
private readonly string _downloadPath;
private readonly string? _arl;
private readonly string? _arlFallback;
+ private readonly string? _preferredQuality;
private string? _apiToken;
private string? _licenseToken;
@@ -69,6 +75,7 @@ public class DeezerDownloadService : IDownloadService
_downloadPath = configuration["Library:DownloadPath"] ?? "./downloads";
_arl = configuration["Deezer:Arl"];
_arlFallback = configuration["Deezer:ArlFallback"];
+ _preferredQuality = configuration["Deezer:Quality"];
if (!Directory.Exists(_downloadPath))
{
@@ -280,6 +287,9 @@ public class DeezerDownloadService : IDownloadService
: "";
// Get download URL via media API
+ // Build format list based on preferred quality
+ var formatsList = BuildFormatsList(_preferredQuality);
+
var mediaRequest = new
{
license_token = _licenseToken,
@@ -288,12 +298,7 @@ public class DeezerDownloadService : IDownloadService
new
{
type = "FULL",
- formats = new[]
- {
- new { cipher = "BF_CBC_STRIPE", format = "MP3_128" },
- new { cipher = "BF_CBC_STRIPE", format = "MP3_320" },
- new { cipher = "BF_CBC_STRIPE", format = "FLAC" }
- }
+ formats = formatsList
}
},
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");
}
- string? downloadUrl = null;
- string? format = null;
-
+ // Build a dictionary of available formats
+ var availableFormats = new Dictionary();
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)
{
- downloadUrl = sources[0].GetProperty("url").GetString();
- format = mediaItem.GetProperty("format").GetString();
+ var fmt = formatEl.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;
}
}
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
{
DownloadUrl = downloadUrl,
- Format = format ?? "MP3_128",
+ Format = selectedFormat ?? "MP3_128",
Title = title,
Artist = artist
};
@@ -642,6 +679,44 @@ public class DeezerDownloadService : IDownloadService
#region Utility Methods
+ ///
+ /// 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.
+ ///
+ 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 RetryWithBackoffAsync(Func> action, int maxRetries = 3, int initialDelayMs = 1000)
{
Exception? lastException = null;