import React, { useState } from "react"; import { Button, Col, Form, Row, Table } from "react-bootstrap"; import { Link, useHistory } from "react-router-dom"; import { FormattedNumber } from "react-intl"; import querystring from "query-string"; import * as GQL from "src/core/generated-graphql"; import { LoadingIndicator, ErrorMessage, HoverPopover, } from "src/components/Shared"; import { Pagination } from "src/components/List/Pagination"; import { TextUtils } from "src/utils"; import { DeleteScenesDialog } from "src/components/Scenes/DeleteScenesDialog"; const CLASSNAME = "DuplicateChecker"; export const SettingsDuplicatePanel: React.FC = () => { const history = useHistory(); const { page, size, distance } = querystring.parse(history.location.search); const currentPage = Number.parseInt( Array.isArray(page) ? page[0] : page ?? "1", 10 ); const pageSize = Number.parseInt( Array.isArray(size) ? size[0] : size ?? "20", 10 ); const hashDistance = Number.parseInt( Array.isArray(distance) ? distance[0] : distance ?? "0", 10 ); const [isMultiDelete, setIsMultiDelete] = useState(false); const [checkedScenes, setCheckedScenes] = useState>( {} ); const { data, loading, refetch } = GQL.useFindDuplicateScenesQuery({ fetchPolicy: "no-cache", variables: { distance: hashDistance }, }); const [deletingScene, setDeletingScene] = useState< GQL.SlimSceneDataFragment[] | null >(null); if (loading) return ; if (!data) return ; const scenes = data?.findDuplicateScenes ?? []; const filteredScenes = scenes.slice( (currentPage - 1) * pageSize, currentPage * pageSize ); const checkCount = Object.keys(checkedScenes).filter( (id) => checkedScenes[id] ).length; const setQuery = (q: Record) => { history.push({ search: querystring.stringify({ ...querystring.parse(history.location.search), ...q, }), }); }; function onDeleteDialogClosed(deleted: boolean) { setDeletingScene(null); if (deleted) { refetch(); if (isMultiDelete) setCheckedScenes({}); } } const handleCheck = (checked: boolean, sceneID: string) => { setCheckedScenes({ ...checkedScenes, [sceneID]: checked }); }; const handleDeleteChecked = () => { setDeletingScene(scenes.flat().filter((s) => checkedScenes[s.id])); setIsMultiDelete(true); }; const handleDeleteScene = (scene: GQL.SlimSceneDataFragment) => { setDeletingScene([scene]); setIsMultiDelete(false); }; const renderFilesize = (filesize: string | null | undefined) => { const { size: parsedSize, unit } = TextUtils.fileSize( Number.parseInt(filesize ?? "0", 10) ); return ( ); }; return (
{deletingScene && ( )}

Duplicate Scenes

Search Accuracy setQuery({ distance: e.currentTarget.value === "0" ? undefined : e.currentTarget.value, page: undefined, }) } defaultValue={distance ?? 0} className="ml-4" > Levels below “Exact” can take longer to calculate. False positives might also be returned on lower accuracy levels.
{scenes.length} sets of duplicates found.
{checkCount > 0 && ( )} setQuery({ page: newPage === 1 ? undefined : newPage }) } /> setQuery({ size: e.currentTarget.value === "20" ? undefined : e.currentTarget.value, }) } >
{filteredScenes.map((group) => group.map((scene, i) => ( )) )}
Title Duration Filesize Resolution Bitrate Codec Delete
handleCheck(e.currentTarget.checked, scene.id) } /> } placement="right" > {scene.title ?? TextUtils.fileNameFromPath(scene.path)} {scene.file.duration && TextUtils.secondsToTimestamp(scene.file.duration)} {renderFilesize(scene.file.size)} {`${scene.file.width}x${scene.file.height}`}  mbps {scene.file.video_codec}
{scenes.length === 0 && (

No duplicates found. Make sure the phash task has been run.

)}
); };