implemeented file format convertion

This commit is contained in:
Xoconoch
2025-06-03 22:37:45 -06:00
parent 41461aef3f
commit c2e526be60
9 changed files with 194 additions and 20 deletions

View File

@@ -2,4 +2,4 @@ waitress==3.0.2
celery==5.5.3
Flask==3.1.1
flask_cors==6.0.0
deezspot-spotizerr==1.4.1
deezspot-spotizerr==1.5.0

View File

@@ -19,7 +19,9 @@ def download_album(
initial_retry_delay=5,
retry_delay_increase=5,
max_retries=3,
progress_callback=None
progress_callback=None,
convert_to=None,
bitrate=None
):
try:
# Detect URL source (Spotify or Deezer) from URL
@@ -118,7 +120,9 @@ def download_album(
save_cover=save_cover,
initial_retry_delay=initial_retry_delay,
retry_delay_increase=retry_delay_increase,
max_retries=max_retries
max_retries=max_retries,
convert_to=convert_to,
bitrate=bitrate
)
print(f"DEBUG: Album download completed successfully using Deezer credentials")
except Exception as e:
@@ -160,7 +164,9 @@ def download_album(
save_cover=save_cover,
initial_retry_delay=initial_retry_delay,
retry_delay_increase=retry_delay_increase,
max_retries=max_retries
max_retries=max_retries,
convert_to=convert_to,
bitrate=bitrate
)
print(f"DEBUG: Album download completed successfully using Spotify fallback")
except Exception as e2:
@@ -201,7 +207,9 @@ def download_album(
save_cover=save_cover,
initial_retry_delay=initial_retry_delay,
retry_delay_increase=retry_delay_increase,
max_retries=max_retries
max_retries=max_retries,
convert_to=convert_to,
bitrate=bitrate
)
print(f"DEBUG: Album download completed successfully using Spotify main")
# For Deezer URLs: download directly from Deezer
@@ -236,7 +244,9 @@ def download_album(
save_cover=save_cover,
initial_retry_delay=initial_retry_delay,
retry_delay_increase=retry_delay_increase,
max_retries=max_retries
max_retries=max_retries,
convert_to=convert_to,
bitrate=bitrate
)
print(f"DEBUG: Album download completed successfully using Deezer direct")
else:

View File

@@ -39,7 +39,9 @@ DEFAULT_MAIN_CONFIG = {
'maxConcurrentDownloads': 3,
'maxRetries': 3,
'retryDelaySeconds': 5,
'retry_delay_increase': 5
'retry_delay_increase': 5,
'convertTo': None,
'bitrate': None
}
def get_config_params():

View File

@@ -60,7 +60,9 @@ def get_config_params():
'save_cover': config.get('save_cover', True),
'maxRetries': config.get('maxRetries', 3),
'retryDelaySeconds': config.get('retryDelaySeconds', 5),
'retry_delay_increase': config.get('retry_delay_increase', 5)
'retry_delay_increase': config.get('retry_delay_increase', 5),
'convertTo': config.get('convertTo', None),
'bitrate': config.get('bitrate', None)
}
except Exception as e:
logger.error(f"Error reading config for parameters: {e}")
@@ -78,7 +80,9 @@ def get_config_params():
'save_cover': True,
'maxRetries': 3,
'retryDelaySeconds': 5,
'retry_delay_increase': 5
'retry_delay_increase': 5,
'convertTo': None, # Default for conversion
'bitrate': None # Default for bitrate
}
class CeleryDownloadQueueManager:
@@ -201,6 +205,8 @@ class CeleryDownloadQueueManager:
"custom_track_format": original_request.get("custom_track_format", config_params['customTrackFormat']),
"pad_tracks": self._parse_bool_param(original_request.get("tracknum_padding"), config_params['tracknum_padding']),
"save_cover": self._parse_bool_param(original_request.get("save_cover"), config_params['save_cover']),
"convertTo": original_request.get("convertTo", config_params.get('convertTo')),
"bitrate": original_request.get("bitrate", config_params.get('bitrate')),
"retry_count": 0,
"original_request": original_request,
"created_at": time.time()

View File

@@ -1087,6 +1087,8 @@ def download_track(self, **task_data):
custom_track_format = task_data.get("custom_track_format", config_params.get("customTrackFormat", "%tracknum%. %music%"))
pad_tracks = task_data.get("pad_tracks", config_params.get("tracknum_padding", True))
save_cover = task_data.get("save_cover", config_params.get("save_cover", True))
convert_to = task_data.get("convertTo", config_params.get("convertTo"))
bitrate = task_data.get("bitrate", config_params.get("bitrate"))
# Execute the download - service is now determined from URL
download_track_func(
@@ -1100,7 +1102,9 @@ def download_track(self, **task_data):
custom_track_format=custom_track_format,
pad_tracks=pad_tracks,
save_cover=save_cover,
progress_callback=self.progress_callback
progress_callback=self.progress_callback,
convert_to=convert_to,
bitrate=bitrate
)
return {"status": "success", "message": "Track download completed"}
@@ -1156,6 +1160,8 @@ def download_album(self, **task_data):
custom_track_format = task_data.get("custom_track_format", config_params.get("customTrackFormat", "%tracknum%. %music%"))
pad_tracks = task_data.get("pad_tracks", config_params.get("tracknum_padding", True))
save_cover = task_data.get("save_cover", config_params.get("save_cover", True))
convert_to = task_data.get("convertTo", config_params.get("convertTo"))
bitrate = task_data.get("bitrate", config_params.get("bitrate"))
# Execute the download - service is now determined from URL
download_album_func(
@@ -1169,7 +1175,9 @@ def download_album(self, **task_data):
custom_track_format=custom_track_format,
pad_tracks=pad_tracks,
save_cover=save_cover,
progress_callback=self.progress_callback
progress_callback=self.progress_callback,
convert_to=convert_to,
bitrate=bitrate
)
return {"status": "success", "message": "Album download completed"}
@@ -1225,6 +1233,8 @@ def download_playlist(self, **task_data):
custom_track_format = task_data.get("custom_track_format", config_params.get("customTrackFormat", "%tracknum%. %music%"))
pad_tracks = task_data.get("pad_tracks", config_params.get("tracknum_padding", True))
save_cover = task_data.get("save_cover", config_params.get("save_cover", True))
convert_to = task_data.get("convertTo", config_params.get("convertTo"))
bitrate = task_data.get("bitrate", config_params.get("bitrate"))
# Get retry parameters
initial_retry_delay = task_data.get("initial_retry_delay", config_params.get("retryDelaySeconds", 5))
@@ -1247,6 +1257,8 @@ def download_playlist(self, **task_data):
retry_delay_increase=retry_delay_increase,
max_retries=max_retries,
progress_callback=self.progress_callback,
convert_to=convert_to,
bitrate=bitrate
)
return {"status": "success", "message": "Playlist download completed"}

View File

@@ -20,6 +20,8 @@ def download_playlist(
retry_delay_increase=5,
max_retries=3,
progress_callback=None,
convert_to=None,
bitrate=None
):
try:
# Detect URL source (Spotify or Deezer) from URL
@@ -114,6 +116,8 @@ def download_playlist(
initial_retry_delay=initial_retry_delay,
retry_delay_increase=retry_delay_increase,
max_retries=max_retries,
convert_to=convert_to,
bitrate=bitrate
)
print(f"DEBUG: Playlist download completed successfully using Deezer credentials")
except Exception as e:
@@ -155,7 +159,9 @@ def download_playlist(
save_cover=save_cover,
initial_retry_delay=initial_retry_delay,
retry_delay_increase=retry_delay_increase,
max_retries=max_retries
max_retries=max_retries,
convert_to=convert_to,
bitrate=bitrate
)
print(f"DEBUG: Playlist download completed successfully using Spotify fallback")
except Exception as e2:
@@ -196,7 +202,9 @@ def download_playlist(
save_cover=save_cover,
initial_retry_delay=initial_retry_delay,
retry_delay_increase=retry_delay_increase,
max_retries=max_retries
max_retries=max_retries,
convert_to=convert_to,
bitrate=bitrate
)
print(f"DEBUG: Playlist download completed successfully using Spotify main")
# For Deezer URLs: download directly from Deezer
@@ -231,7 +239,9 @@ def download_playlist(
save_cover=save_cover,
initial_retry_delay=initial_retry_delay,
retry_delay_increase=retry_delay_increase,
max_retries=max_retries
max_retries=max_retries,
convert_to=convert_to,
bitrate=bitrate
)
print(f"DEBUG: Playlist download completed successfully using Deezer direct")
else:

View File

@@ -19,7 +19,9 @@ def download_track(
initial_retry_delay=5,
retry_delay_increase=5,
max_retries=3,
progress_callback=None
progress_callback=None,
convert_to=None,
bitrate=None
):
try:
# Detect URL source (Spotify or Deezer) from URL
@@ -112,7 +114,9 @@ def download_track(
save_cover=save_cover,
initial_retry_delay=initial_retry_delay,
retry_delay_increase=retry_delay_increase,
max_retries=max_retries
max_retries=max_retries,
convert_to=convert_to,
bitrate=bitrate
)
except Exception as e:
deezer_error = e
@@ -148,7 +152,9 @@ def download_track(
save_cover=save_cover,
initial_retry_delay=initial_retry_delay,
retry_delay_increase=retry_delay_increase,
max_retries=max_retries
max_retries=max_retries,
convert_to=convert_to,
bitrate=bitrate
)
except Exception as e2:
# If fallback also fails, raise an error indicating both attempts failed
@@ -182,7 +188,9 @@ def download_track(
save_cover=save_cover,
initial_retry_delay=initial_retry_delay,
retry_delay_increase=retry_delay_increase,
max_retries=max_retries
max_retries=max_retries,
convert_to=convert_to,
bitrate=bitrate
)
# For Deezer URLs: download directly from Deezer
elif service == 'deezer':
@@ -211,7 +219,9 @@ def download_track(
save_cover=save_cover,
initial_retry_delay=initial_retry_delay,
retry_delay_increase=retry_delay_increase,
max_retries=max_retries
max_retries=max_retries,
convert_to=convert_to,
bitrate=bitrate
)
else:
raise ValueError(f"Unsupported service: {service}")

View File

@@ -53,6 +53,17 @@ let isEditingSearch = false;
let activeSpotifyAccount = '';
let activeDeezerAccount = '';
// Define available formats and their bitrates
const CONVERSION_FORMATS: Record<string, string[]> = {
MP3: ['32k', '64k', '96k', '128k', '192k', '256k', '320k'],
AAC: ['32k', '64k', '96k', '128k', '192k', '256k'],
OGG: ['64k', '96k', '128k', '192k', '256k', '320k'],
OPUS: ['32k', '64k', '96k', '128k', '192k', '256k'],
FLAC: [], // No specific bitrates
WAV: [], // No specific bitrates
ALAC: [] // No specific bitrates
};
// Reference to the credentials form card and add button
let credentialsFormCard: HTMLElement | null = null;
let showAddAccountFormBtn: HTMLElement | null = null;
@@ -124,6 +135,23 @@ async function loadConfig() {
const saveCoverToggle = document.getElementById('saveCoverToggle') as HTMLInputElement | null;
if (saveCoverToggle) saveCoverToggle.checked = savedConfig.save_cover === undefined ? true : !!savedConfig.save_cover;
// Load conversion settings
const convertToSelect = document.getElementById('convertToSelect') as HTMLSelectElement | null;
if (convertToSelect) {
convertToSelect.value = savedConfig.convertTo || '';
updateBitrateOptions(convertToSelect.value);
}
const bitrateSelect = document.getElementById('bitrateSelect') as HTMLSelectElement | null;
if (bitrateSelect && savedConfig.bitrate) {
if (Array.from(bitrateSelect.options).some(option => option.value === savedConfig.bitrate)) {
bitrateSelect.value = savedConfig.bitrate;
}
} else if (bitrateSelect) {
if (convertToSelect && !CONVERSION_FORMATS[convertToSelect.value]?.length) {
bitrateSelect.value = '';
}
}
// Update explicit filter status
updateExplicitFilterStatus(savedConfig.explicitFilter);
@@ -249,6 +277,13 @@ function setupEventListeners() {
(document.getElementById('maxRetries') as HTMLInputElement | null)?.addEventListener('change', saveConfig);
(document.getElementById('retryDelaySeconds') as HTMLInputElement | null)?.addEventListener('change', saveConfig);
// Conversion settings listeners
(document.getElementById('convertToSelect') as HTMLSelectElement | null)?.addEventListener('change', function() {
updateBitrateOptions(this.value);
saveConfig();
});
(document.getElementById('bitrateSelect') as HTMLSelectElement | null)?.addEventListener('change', saveConfig);
// Update active account globals when the account selector is changed.
(document.getElementById('spotifyAccountSelect') as HTMLSelectElement | null)?.addEventListener('change', (e: Event) => {
activeSpotifyAccount = (e.target as HTMLSelectElement).value;
@@ -821,6 +856,13 @@ function resetForm() {
}
(document.getElementById('credentialForm') as HTMLFormElement | null)?.reset();
// Reset conversion dropdowns to ensure bitrate is updated correctly
const convertToSelect = document.getElementById('convertToSelect') as HTMLSelectElement | null;
if (convertToSelect) {
convertToSelect.value = ''; // Reset to 'No Conversion'
updateBitrateOptions(''); // Update bitrate for 'No Conversion'
}
// Reset form title and button text
const serviceName = currentService.charAt(0).toUpperCase() + currentService.slice(1);
(document.getElementById('formTitle') as HTMLElement | null)!.textContent = `Add New ${serviceName} Account`;
@@ -847,7 +889,9 @@ async function saveConfig() {
retryDelaySeconds: parseInt((document.getElementById('retryDelaySeconds') as HTMLInputElement | null)?.value || '5', 10) || 5,
retry_delay_increase: parseInt((document.getElementById('retryDelayIncrease') as HTMLInputElement | null)?.value || '5', 10) || 5,
tracknum_padding: (document.getElementById('tracknumPaddingToggle') as HTMLInputElement | null)?.checked,
save_cover: (document.getElementById('saveCoverToggle') as HTMLInputElement | null)?.checked
save_cover: (document.getElementById('saveCoverToggle') as HTMLInputElement | null)?.checked,
convertTo: (document.getElementById('convertToSelect') as HTMLSelectElement | null)?.value || null, // Get convertTo value
bitrate: (document.getElementById('bitrateSelect') as HTMLSelectElement | null)?.value || null // Get bitrate value
};
try {
@@ -908,6 +952,23 @@ async function saveConfig() {
const saveCoverToggle = document.getElementById('saveCoverToggle') as HTMLInputElement | null;
if (saveCoverToggle) saveCoverToggle.checked = savedConfig.save_cover === undefined ? true : !!savedConfig.save_cover;
// Load conversion settings after save
const convertToSelect = document.getElementById('convertToSelect') as HTMLSelectElement | null;
if (convertToSelect) {
convertToSelect.value = savedConfig.convertTo || '';
updateBitrateOptions(convertToSelect.value);
}
const bitrateSelect = document.getElementById('bitrateSelect') as HTMLSelectElement | null;
if (bitrateSelect && savedConfig.bitrate) {
if (Array.from(bitrateSelect.options).some(option => option.value === savedConfig.bitrate)) {
bitrateSelect.value = savedConfig.bitrate;
}
} else if (bitrateSelect) {
if (convertToSelect && !CONVERSION_FORMATS[convertToSelect.value]?.length) {
bitrateSelect.value = '';
}
}
// Update explicit filter status
updateExplicitFilterStatus(savedConfig.explicitFilter);
@@ -1087,3 +1148,36 @@ function updateWatchWarningDisplay() {
firstEnableNoticeDiv.style.display = 'none';
}
}
// Function to update bitrate options based on selected format
function updateBitrateOptions(selectedFormat: string) {
const bitrateSelect = document.getElementById('bitrateSelect') as HTMLSelectElement | null;
if (!bitrateSelect) return;
bitrateSelect.innerHTML = ''; // Clear existing options
const currentBitrateValue = bitrateSelect.value; // Preserve current value if possible
if (selectedFormat && CONVERSION_FORMATS[selectedFormat] && CONVERSION_FORMATS[selectedFormat].length > 0) {
bitrateSelect.disabled = false;
CONVERSION_FORMATS[selectedFormat].forEach(bRate => {
const option = document.createElement('option');
option.value = bRate;
option.textContent = bRate;
bitrateSelect.appendChild(option);
});
// Try to restore previous valid bitrate or set to first available
if (CONVERSION_FORMATS[selectedFormat].includes(currentBitrateValue)) {
bitrateSelect.value = currentBitrateValue;
} else {
bitrateSelect.value = CONVERSION_FORMATS[selectedFormat][0]; // Default to first available bitrate
}
} else {
// For formats with no specific bitrates (FLAC, WAV, ALAC) or 'No Conversion'
bitrateSelect.disabled = true;
const option = document.createElement('option');
option.value = '';
option.textContent = 'N/A';
bitrateSelect.appendChild(option);
bitrateSelect.value = '';
}
}

View File

@@ -86,6 +86,36 @@
<label for="maxConcurrentDownloads">Max Concurrent Downloads:</label>
<input type="number" id="maxConcurrentDownloads" min="1" value="3" class="form-input">
</div>
<!-- New Conversion Options -->
<h2 class="section-title">Conversion Settings</h2>
<div class="config-item">
<label for="convertToSelect">Convert To Format:</label>
<select id="convertToSelect" class="form-select">
<option value="">No Conversion</option>
<option value="MP3">MP3</option>
<option value="AAC">AAC</option>
<option value="OGG">OGG</option>
<option value="OPUS">OPUS</option>
<option value="FLAC">FLAC</option>
<option value="WAV">WAV</option>
<option value="ALAC">ALAC</option>
</select>
<div class="setting-description">
Select a format to convert downloaded files to. "No Conversion" keeps the original format.
</div>
</div>
<div class="config-item">
<label for="bitrateSelect">Bitrate:</label>
<select id="bitrateSelect" class="form-select" disabled>
<!-- Options will be populated by JavaScript -->
<option value="">N/A</option>
</select>
<div class="setting-description">
Select the bitrate for the chosen format. Only applicable for lossy formats.
</div>
</div>
<!-- New Retry Options -->
<h2 class="section-title">Retry Options</h2>
<div class="config-item">