diff --git a/qbittorrent_performer_sorter.html b/qbittorrent_performer_sorter.html
index af286d2..c4d33d5 100644
--- a/qbittorrent_performer_sorter.html
+++ b/qbittorrent_performer_sorter.html
@@ -8027,7 +8027,7 @@ Return ONLY a valid JSON object with an array named "results":
}
}
- // Batch Replace Action
+ // Batch Replace Action with Live Per-Item Progress
const btnBatchReplaceTranscodes = document.getElementById('btn-batch-replace-transcodes');
if (btnBatchReplaceTranscodes) {
btnBatchReplaceTranscodes.addEventListener('click', async () => {
@@ -8043,35 +8043,64 @@ Return ONLY a valid JSON object with an array named "results":
return;
}
- try {
- btnBatchReplaceTranscodes.disabled = true;
- btnBatchReplaceTranscodes.textContent = `Replacing ${selectedItems.length}...`;
+ const deleteCache = document.getElementById('transcode-opt-delete-cache')?.checked;
+ const triggerRescan = document.getElementById('transcode-opt-trigger-rescan')?.checked;
- const deleteCache = document.getElementById('transcode-opt-delete-cache')?.checked;
- const triggerRescan = document.getElementById('transcode-opt-trigger-rescan')?.checked;
+ let successCount = 0;
+ let totalSavedBytes = 0;
+ btnBatchReplaceTranscodes.disabled = true;
- const res = await fetch('/api/stash/transcodes/batch-replace', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({
- items: selectedItems,
- delete_transcode: deleteCache,
- trigger_rescan: triggerRescan
- })
- });
- const data = await res.json();
- if (data.success) {
- showToast(`✓ Batch replaced ${data.replaced_count} files (Saved ${(data.total_saved_bytes/(1024*1024)).toFixed(1)} MB)!`, 'success');
- await scanStashTranscodes();
- } else {
- showToast(`Batch error: ${data.error}`, 'error');
+ for (let i = 0; i < selectedItems.length; i++) {
+ const item = selectedItems[i];
+ btnBatchReplaceTranscodes.textContent = `Replacing ${i + 1} / ${selectedItems.length}...`;
+
+ const row = document.getElementById(`transcode-row-${item.oshash}`);
+ const replaceBtn = row?.querySelector('.btn-transcode-replace-single');
+ if (replaceBtn) {
+ replaceBtn.disabled = true;
+ replaceBtn.textContent = '⏳ Copying...';
+ }
+
+ try {
+ const res = await fetch('/api/stash/transcodes/replace', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ orig_local_path: item.orig_local_path,
+ transcode_path: item.transcode_path,
+ delete_transcode: deleteCache,
+ trigger_rescan: (i === selectedItems.length - 1) ? triggerRescan : false
+ })
+ });
+ const data = await res.json();
+ if (data.success) {
+ successCount++;
+ totalSavedBytes += (data.saved_bytes || 0);
+ if (row) {
+ row.style.opacity = '0.4';
+ row.style.background = 'rgba(16, 185, 129, 0.1)';
+ if (replaceBtn) replaceBtn.textContent = '✅ Replaced';
+ }
+ } else {
+ if (replaceBtn) {
+ replaceBtn.disabled = false;
+ replaceBtn.textContent = '❌ Failed';
+ replaceBtn.title = data.error || 'Failed';
+ }
+ }
+ } catch (err) {
+ if (replaceBtn) {
+ replaceBtn.disabled = false;
+ replaceBtn.textContent = '❌ Error';
+ replaceBtn.title = err.message;
+ }
}
- } catch (err) {
- showToast(`Batch execution error: ${err.message}`, 'error');
- } finally {
- btnBatchReplaceTranscodes.disabled = false;
- btnBatchReplaceTranscodes.textContent = `⚡ Batch Replace Selected (${selectedTranscodeHashes.size})`;
}
+
+ const savedMb = (totalSavedBytes / (1024 * 1024)).toFixed(1);
+ showToast(`✓ Batch completed: Replaced ${successCount} / ${selectedItems.length} files (Saved ${savedMb} MB)!`, 'success');
+ btnBatchReplaceTranscodes.disabled = false;
+ btnBatchReplaceTranscodes.textContent = `⚡ Batch Replace Selected (${selectedTranscodeHashes.size})`;
});
}
diff --git a/server.py b/server.py
index a8bf5df..d457720 100644
--- a/server.py
+++ b/server.py
@@ -2889,14 +2889,18 @@ class RobustProxyHandler(http.server.SimpleHTTPRequestHandler):
saved_bytes = orig_size - transcode_size
# Step 5: Record to Relocation History audit log
- record_relocation_history(
- source_path=transcode_path,
- target_path=orig_local_path,
- performer_name="Transcode Optimization",
- status="completed",
- size_bytes=transcode_size,
- action_type="transcode_replace"
- )
+ try:
+ record_relocation_history(
+ source_type="stash_transcode",
+ item_key=orig_local_path,
+ item_name=os.path.basename(orig_local_path),
+ source_path=transcode_path,
+ target_path=orig_local_path,
+ performer="Transcode Optimization",
+ file_size=transcode_size
+ )
+ except Exception as e:
+ print(f"Warning: History record error: {e}")
# Step 6: Trigger Stash metadata scan on parent folder if requested
if trigger_rescan:
@@ -2967,6 +2971,19 @@ class RobustProxyHandler(http.server.SimpleHTTPRequestHandler):
saved = orig_size - transcode_size
total_saved_bytes += saved
rescanned_dirs.add(os.path.dirname(orig_local_path))
+
+ try:
+ record_relocation_history(
+ source_type="stash_transcode",
+ item_key=orig_local_path,
+ item_name=os.path.basename(orig_local_path),
+ source_path=transcode_path,
+ target_path=orig_local_path,
+ performer="Transcode Optimization",
+ file_size=transcode_size
+ )
+ except Exception:
+ pass
except Exception as e:
errors.append({"path": orig_local_path, "error": str(e)})