1.1.1 - OCR garbage removal detection frontend changes

This commit is contained in:
2026-01-27 10:39:04 +13:00
parent c9d55fbee8
commit 67ea357495
4 changed files with 210 additions and 1 deletions
+1 -1
View File
@@ -172,7 +172,7 @@
class={`flex items-center px-3 py-2 text-xs ${collapsed ? "justify-center" : "justify-between"}`}
>
{#if !collapsed}
<Badge className="text-text-secondary">Version: v1.1.0</Badge>
<Badge className="text-text-secondary">Version: v1.1.1</Badge>
{:else}
<Badge className="text-text-secondary">v</Badge>
{/if}
@@ -15,6 +15,7 @@
searchTitle,
saveSuggestedMatches,
processBatch,
previewClean,
} from "../../lib/api.js";
const dispatch = createEventDispatcher();
@@ -68,6 +69,11 @@
let showPreview = false;
let previewFile = null;
let showCleanPreview = false;
let cleanPreviewFile = null;
let cleanPreviewData = null;
let cleanPreviewLoading = false;
let cleanPreviewError = null;
let showTitleSelector = false;
let titleSelectorFile = null;
let searchResults = [];
@@ -759,6 +765,30 @@
previewFile = null;
}
async function openCleanPreview(file) {
cleanPreviewFile = file;
showCleanPreview = true;
cleanPreviewLoading = true;
cleanPreviewError = null;
cleanPreviewData = null;
try {
const response = await previewClean(file.path);
cleanPreviewData = response.preview;
} catch (err) {
cleanPreviewError = err?.message || "Failed to load preview";
} finally {
cleanPreviewLoading = false;
}
}
function closeCleanPreview() {
showCleanPreview = false;
cleanPreviewFile = null;
cleanPreviewData = null;
cleanPreviewError = null;
}
function getMediaType(file) {
return file.media_type || "N/A";
}
@@ -1261,6 +1291,13 @@
</TableCell>
<TableCell className="w-64">
<div class="flex items-center justify-end gap-2">
<Button
on:click={() => openCleanPreview(file)}
size="sm"
className="bg-bg-secondary text-text-secondary border border-border hover:text-white"
>
Preview
</Button>
<span class="text-[11px] text-text-tertiary">
Clean only
</span>
@@ -1361,6 +1398,13 @@
</TableCell>
<TableCell className="w-64">
<div class="flex items-center justify-end gap-2">
<Button
on:click={() => openCleanPreview(file)}
size="sm"
className="bg-bg-secondary text-text-secondary border border-border hover:text-white"
>
Preview
</Button>
<span class="text-[11px] text-text-tertiary">
Clean only
</span>
@@ -2312,6 +2356,104 @@
</div>
{/if}
<!-- Clean Preview Modal -->
{#if showCleanPreview && cleanPreviewFile}
<div
class="fixed inset-0 bg-black/95 flex items-center justify-center z-50 p-4"
on:click={closeCleanPreview}
role="button"
tabindex="-1"
on:keydown={(e) => e.key === "Escape" && closeCleanPreview()}
>
<div
class="bg-bg-card border border-border rounded-2xl p-8 max-w-4xl w-full max-h-[90vh] overflow-y-auto"
on:click|stopPropagation
role="dialog"
tabindex="-1"
on:keydown
>
<div class="flex items-start justify-between mb-6">
<div class="flex-1">
<h3 class="text-lg font-medium mb-1">Clean Preview</h3>
<p class="text-sm text-text-secondary font-mono">
{cleanPreviewFile.name}
</p>
</div>
<button
on:click={closeCleanPreview}
class="text-text-secondary hover:text-white transition-colors"
>
<svg
class="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
</div>
{#if cleanPreviewLoading}
<div class="py-16 text-center text-text-tertiary text-sm">
Building preview…
</div>
{:else if cleanPreviewError}
<div class="py-12 text-center text-red-400 text-sm">
{cleanPreviewError}
</div>
{:else if cleanPreviewData}
<div class="space-y-5">
<div class="flex items-center justify-between text-xs text-text-tertiary uppercase tracking-wide">
<span>Summary</span>
<span>
{cleanPreviewData.removed_blocks} removed ·
{cleanPreviewData.modified_blocks} modified
</span>
</div>
{#if cleanPreviewData.total_changed_blocks === 0}
<div class="text-sm text-text-tertiary">
No changes detected.
</div>
{:else}
<div class="space-y-3">
{#each cleanPreviewData.changes as change}
<div class="border border-border rounded-xl p-4 bg-bg-primary/40">
<div class="text-[11px] text-text-tertiary uppercase tracking-wide mb-3">
{change.type} · {change.timecode}
</div>
<div class="grid grid-cols-2 gap-3 text-[11px]">
<div class="text-text-secondary">Before</div>
<div class="text-text-secondary">After</div>
<pre
class="whitespace-pre-wrap text-text-tertiary bg-bg-secondary/40 border border-border rounded-lg p-2 min-h-[48px]"
>{change.before || "(removed)"}</pre>
<pre
class="whitespace-pre-wrap text-text-secondary bg-bg-secondary/40 border border-border rounded-lg p-2 min-h-[48px]"
>{change.after || "(removed)"}</pre>
</div>
</div>
{/each}
</div>
{#if cleanPreviewData.changes_truncated}
<div class="text-[11px] text-text-tertiary">
Preview truncated. Run clean to apply all changes.
</div>
{/if}
{/if}
</div>
{/if}
</div>
</div>
{/if}
<!-- Title Selector Modal -->
{#if showTitleSelector && titleSelectorFile}
<div
@@ -7,6 +7,7 @@
let stripKeywords = settings.strip_keywords !== false;
let cleanSubtitleContent = settings.clean_subtitle_content !== false;
let cleanSubtitleGarbage = settings.clean_subtitle_garbage === true;
let forceRemoveKeywords = Array.isArray(settings.clean_subtitle_force_remove)
? settings.clean_subtitle_force_remove.join(", ")
: settings.clean_subtitle_force_remove || "YTS, OpenSubtitles";
@@ -28,6 +29,12 @@
{ before: "Downloaded from RARBG", after: "(removed)" },
];
const garbageExamples = [
{ before: "♪♪", after: "(removed)" },
{ before: "00:01:23,456 --> 00:01:25,000", after: "(removed)" },
{ before: "WE HAVE TO GO!\nWE HAVE TO GO!", after: "WE HAVE TO GO!" },
];
// Keywords that get stripped from filenames
const filenameKeywords = [
{ name: "Quality", examples: ["480p", "720p", "1080p", "4K", "HDR"] },
@@ -56,6 +63,7 @@
onSave({
strip_keywords: stripKeywords,
clean_subtitle_content: cleanSubtitleContent,
clean_subtitle_garbage: cleanSubtitleGarbage,
clean_subtitle_force_remove: forceRemoveKeywords
.split(/[\n,]+/)
.map((entry) => entry.trim())
@@ -169,6 +177,53 @@
</label>
</div>
<div class="p-5 bg-bg-secondary border border-border rounded-xl mb-5">
<label class="flex items-start justify-between gap-4 rounded-xl border border-border bg-bg-secondary/40 px-4 py-3">
<div class="flex-1">
<div class="text-[14px] font-medium mb-1">Remove OCR Garbage</div>
<div class="text-[12px] text-text-tertiary leading-relaxed">
Strip music-only lines (♪♪), embedded timecodes, and duplicate OCR
lines inside a block. Can be enabled without ad removal.
</div>
</div>
<span class="relative mt-0.5 inline-flex items-center">
<input type="checkbox" bind:checked={cleanSubtitleGarbage} class="sr-only peer" />
<span class="h-6 w-11 rounded-full border border-border bg-bg-card transition-colors peer-checked:bg-accent peer-checked:border-accent/60"></span>
<span class="absolute left-0.5 h-5 w-5 rounded-full bg-text-tertiary transition-transform peer-checked:translate-x-5 peer-checked:bg-bg-primary"></span>
</span>
</label>
</div>
{#if cleanSubtitleGarbage}
<div class="bg-bg-card border border-border rounded-xl overflow-hidden mb-5">
<div class="px-4 py-2.5 border-b border-border bg-bg-secondary">
<span
class="text-[11px] font-medium text-text-secondary uppercase tracking-wide"
>OCR Garbage Examples</span
>
</div>
<div class="divide-y divide-border">
{#each garbageExamples as example}
<div class="px-4 py-2.5 flex items-center gap-3">
<div class="flex-1 min-w-0">
<code class="text-[11px] text-text-tertiary break-all"
>{example.before}</code
>
</div>
<ArrowRight class="w-3.5 h-3.5 text-text-tertiary flex-shrink-0" />
<div class="flex-shrink-0">
<code
class="text-[11px] {example.after === '(removed)'
? 'text-red-400'
: 'text-green-400'} font-medium">{example.after}</code
>
</div>
</div>
{/each}
</div>
</div>
{/if}
<!-- Collapsible Details -->
{#if cleanSubtitleContent}
<div class="space-y-4 pl-2 border-l-2 border-border ml-2">
+12
View File
@@ -485,6 +485,18 @@ export async function clearAllSuggestedMatches() {
})
}
/**
* POST /api/clean/preview - Preview cleaning changes
* Body: { file: string }
* Returns: { success, preview }
*/
export async function previewClean(file) {
return apiFetch('/clean/preview', {
method: 'POST',
body: JSON.stringify({ file })
})
}
// ============ MAINTENANCE API ============
/**