Add duplicate audio hash detection
This commit is contained in:
@@ -155,7 +155,7 @@ function renderReviewGroup() {
|
||||
drilldownSummary.textContent = 'Listen to each candidate, choose the keeper, and we’ll move to the next group. Skip anything uncertain.';
|
||||
drilldownList.innerHTML = `
|
||||
<article class="review-workspace">
|
||||
<div class="review-heading"><div><span>Comparing now</span><strong>${escapeHtml(group.filename)}</strong></div><span>${reviewState.choices.size} selected</span></div>
|
||||
<div class="review-heading"><div><span>Comparing now</span><strong>${escapeHtml(group.filename)}</strong></div><div class="review-signals"><span id="hash-confidence" class="hash-confidence checking">Checking file identity…</span><span>${reviewState.choices.size} selected</span></div></div>
|
||||
<div class="review-candidates">${group.file_previews.map((file, index) => `
|
||||
<section class="review-candidate ${chosen === file.path ? 'winner' : ''}">
|
||||
<span class="candidate-number">Option ${index + 1}</span>
|
||||
@@ -168,6 +168,31 @@ function renderReviewGroup() {
|
||||
<div class="review-navigation"><button type="button" data-review-previous ${reviewState.index === 0 ? 'disabled' : ''}>← Previous</button><button type="button" data-review-skip>Skip for now</button></div>
|
||||
</article>`;
|
||||
updateBatchSummary();
|
||||
loadHashConfidence(group, reviewState.index, reviewState.key);
|
||||
}
|
||||
|
||||
async function loadHashConfidence(group, groupIndex, detailKey) {
|
||||
const badge = document.querySelector('#hash-confidence');
|
||||
if (group.hashComparison) {
|
||||
showHashConfidence(badge, group.hashComparison);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response = await fetch('/api/duplicates/hash', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({tokens: group.file_previews.map((file) => file.reveal_token)})});
|
||||
const result = await response.json();
|
||||
if (!response.ok) throw new Error(result.error || 'Identity check failed');
|
||||
group.hashComparison = result;
|
||||
if (reviewState?.index === groupIndex && reviewState?.key === detailKey) showHashConfidence(document.querySelector('#hash-confidence'), result);
|
||||
} catch (error) {
|
||||
if (reviewState?.index === groupIndex && reviewState?.key === detailKey && badge) { badge.className = 'hash-confidence unavailable'; badge.textContent = 'Identity not verified'; }
|
||||
}
|
||||
}
|
||||
|
||||
function showHashConfidence(badge, comparison) {
|
||||
if (!badge) return;
|
||||
badge.className = `hash-confidence ${comparison.status}`;
|
||||
badge.textContent = comparison.status === 'identical' ? '✓ Byte-for-byte identical' : '⚠ Files differ — listen carefully';
|
||||
badge.title = comparison.files.map((file) => `${formatBytes(file.size)} · ${file.fingerprint}`).join('\n');
|
||||
}
|
||||
|
||||
function updateBatchSummary() {
|
||||
@@ -200,7 +225,7 @@ previewRepairButton.addEventListener('click', async () => {
|
||||
previewedRepair = await requestRepair('/api/duplicates/batch/preview');
|
||||
batchPreviewSummary.textContent = `${previewedRepair.choice_count} keeper decision(s) · ${previewedRepair.replaced.length} duplicate file(s) consolidated`;
|
||||
batchPreviewList.innerHTML = previewedRepair.decisions.map((decision, index) => `
|
||||
<article class="preview-decision"><span>Decision ${index + 1}</span><div class="winner-path"><b>Keep</b><strong>${escapeHtml(decision.keeper.split('/').pop())}</strong><small>${escapeHtml(decision.keeper)}</small></div><div class="replaced-paths"><b>Replace with a shortcut</b>${decision.replaced.map((path) => `<small>${escapeHtml(path)}</small>`).join('')}</div></article>
|
||||
<article class="preview-decision"><span>Decision ${index + 1}<em class="hash-confidence ${decision.hash_status}">${decision.hash_status === 'identical' ? 'Identical' : 'Files differ'}</em></span><div class="winner-path"><b>Keep</b><strong>${escapeHtml(decision.keeper.split('/').pop())}</strong><small>${escapeHtml(decision.keeper)}</small></div><div class="replaced-paths"><b>Replace with a shortcut</b>${decision.replaced.map((path) => `<small>${escapeHtml(path)}</small>`).join('')}</div></article>
|
||||
`).join('');
|
||||
batchPreviewSafety.textContent = `${previewedRepair.metadata_backups} Serato metadata file(s) and every replaced audio file will be backed up. Database V2 will not be modified.`;
|
||||
repairPreview.innerHTML = `<strong>Preview approved</strong><p>${previewedRepair.choice_count} keeper decision(s) are ready for one backed-up apply.</p>`;
|
||||
|
||||
Reference in New Issue
Block a user