implemeented file format convertion
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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 = '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user