- This tool scans for video files in your source folder that are NOT in TA's
- database.
- It will attempt to fetch metadata (using yt-dlp) and move them to the Import
- folder.
+ This tool scans for files in your Source and Archive
+ folders to find issues.
+
- Scan for Unindexed Files
+ Run System Scan
-
-
-
-
- Video ID
- Filename
- Size
- Action
-
-
-
-
- Click Scan to begin...
-
-
-
+
+
+
+
+ Unindexed (Import) 0
+
+
+
+
+ Rescue Needed 0
+
+
+
+
+ Redundant Dupes 0
+
+
+
+
+
+
+
+
Files found on disk but missing from TubeArchivist. Recover
+ them to restore your library.
+
+
+
+
+ Video ID
+ Location
+ Size
+ Action
+
+
+
+
+ Click Scan to begin...
+
+
+
+
+
+
+
+
+
+
CRITICAL: TubeArchivist thinks it has
+ these videos, but the source file is MISSING. However, we found a copy in your
+ archives! Recover immediately.
+
+
+
+
+ Video ID
+ Found At
+ Missing Source
+ Action
+
+
+
+
+ Click Scan to begin...
+
+
+
+
+
+
+
+
+
+
SAFE TO DELETE: These files are duplicates.
+ TubeArchivist already has a verified copy in the source folder.
+
+
+
+
+ Video ID
+ Duplicate Path
+ Verified Source
+ Action
+
+
+
+
+ Click Scan to begin...
+
+
+
+
+
+
+
@@ -342,41 +423,74 @@
}
async function scanRecoveryFiles() {
- const tbody = document.getElementById('recovery-table-body');
- tbody.innerHTML = '
Scanning...';
+ // Loading state for all tabs
+ const loadingRow = '
Scanning...';
+ const ids = ['tbody-unindexed', 'tbody-rescue', 'tbody-redundant'];
+ ids.forEach(id => {
+ const el = document.getElementById(id);
+ if (el) el.innerHTML = loadingRow;
+ });
try {
const res = await fetch('/api/recovery/scan', { method: 'POST' });
const data = await res.json();
- tbody.innerHTML = '';
- if (data.count === 0) {
- tbody.innerHTML = '
No unindexed files found! ';
- return;
- }
+ // Helper to render rows
+ const renderRow = (f, type) => {
+ const cleanPath = f.path.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
+ if (type === 'unindexed') {
+ return `
+ ${f.video_id}
+ ${f.filename}
+ ${f.size_mb} MB
+ Recover
+ `;
+ }
+ if (type === 'rescue') {
+ return `
+ ${f.video_id}
+ ${f.filename}
+ Missing from: ${f.ta_source}
+ RESCUE
+ `;
+ }
+ if (type === 'redundant') {
+ return `
+ ${f.video_id}
+ ${f.filename}
+ Exists at: ${f.ta_source}
+ Delete
+ `;
+ }
+ };
- data.files.forEach(f => {
- const tr = document.createElement('tr');
- tr.innerHTML = `
-
${f.video_id}
-
${f.filename}
-
${f.size_mb} MB
-
-
- Recover
-
-
- `;
- tbody.appendChild(tr);
+ // Clear & Fill
+ ids.forEach(id => {
+ const el = document.getElementById(id);
+ if (el) el.innerHTML = '';
});
+
+ // Update Badges
+ document.getElementById('badge-unindexed').innerText = data.files.unindexed.length;
+ document.getElementById('badge-rescue').innerText = data.files.rescue.length;
+ document.getElementById('badge-redundant').innerText = data.files.redundant.length;
+
+ // Populate Tables
+ data.files.unindexed.forEach(f => document.getElementById('tbody-unindexed').innerHTML += renderRow(f, 'unindexed'));
+ data.files.rescue.forEach(f => document.getElementById('tbody-rescue').innerHTML += renderRow(f, 'rescue'));
+ data.files.redundant.forEach(f => document.getElementById('tbody-redundant').innerHTML += renderRow(f, 'redundant'));
+
+ if (data.files.unindexed.length === 0) document.getElementById('tbody-unindexed').innerHTML = '
No unindexed files found. ';
+ if (data.files.rescue.length === 0) document.getElementById('tbody-rescue').innerHTML = '
No rescue candidates found. ';
+ if (data.files.redundant.length === 0) document.getElementById('tbody-redundant').innerHTML = '
No duplicates found. ';
+
} catch (e) {
- tbody.innerHTML = `
Error: ${e} `;
+ alert("Scan failed: " + e);
}
}
async function startRecovery(filepath) {
if (!confirm("Start recovery for this file? This will try to fetch metadata and move it to the Import folder.")) return;
-
try {
const res = await fetch('/api/recovery/start', {
method: 'POST',
@@ -385,10 +499,25 @@
});
const data = await res.json();
alert(data.message || "Recovery started! Check logs.");
- // Optionally remove the row
- } catch (e) {
- alert("Error starting recovery: " + e);
- }
+ } catch (e) { alert("Error: " + e); }
+ }
+
+ async function deleteFile(filepath) {
+ if (!confirm("⚠️ DELETE WARNING: Are you sure you want to delete this file?\n\n" + filepath + "\n\nOnly click OK if you are sure TA has a copy.")) return;
+ try {
+ const res = await fetch('/api/recovery/delete', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ filepath })
+ });
+ const data = await res.json();
+ if (data.success) {
+ alert("File deleted.");
+ scanRecoveryFiles(); // Refresh
+ } else {
+ alert("Error: " + data.error);
+ }
+ } catch (e) { alert("Error: " + e); }
}
function clearLogs() {