Displaying fingerprints on scene page.

This commit is contained in:
DebaucheryLibrarian 2026-01-26 02:02:35 +01:00
parent 54e9fd9f6a
commit fde2d607b8
2 changed files with 111 additions and 0 deletions

View File

@ -343,6 +343,43 @@
</ul>
</div>
<div
v-if="user && scene.fingerprints.length > 0"
class="section fingerprints"
>
<h3 class="heading">Fingerprints</h3>
<div class="fingerprints-container">
<table class="fingerprints-table">
<thead class="fingerprints-head">
<tr class="fingerprints-header">
<th class="fingerprints-heading">Hash</th>
<th class="fingerprints-heading">Type</th>
<th class="fingerprints-heading">Duration</th>
<th class="fingerprints-heading">Submissions</th>
<th class="fingerprints-heading">Source</th>
<th class="fingerprints-heading">First added</th>
</tr>
</thead>
<tbody class="fingerprints-body">
<tr
v-for="fingerprint in scene.fingerprints"
:key="`fingerprint-${fingerprint.hash}`"
class="fingerprint"
>
<td class="fingerprint-field fingerprint-hash">{{ fingerprint.hash }}</td>
<td class="fingerprint-field fingerprint-type">{{ fingerprint.type.toUpperCase() }}</td>
<td class="fingerprint-field fingerprint-duration">{{ formatDuration(fingerprint.duration) }}</td>
<td class="fingerprint-field fingerprint-submission">{{ fingerprint.submissions }}</td>
<td class="fingerprint-field fingerprint-source">{{ fingerprint.source || 'traxxx' }}</td>
<td class="fingerprint-field fingerprint-date">{{ formatDate(fingerprint.createdAt, 'yyyy-MM-dd') }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div
class="scene-actions section"
>
@ -740,6 +777,55 @@ function copySummary() {
display: none;
}
.fingerprints {
margin-top: 1.5rem;
}
.fingerprints-container {
max-height: 10rem;
overflow-y: auto;
resize: vertical;
&[style*="height"] {
max-height: unset;
}
}
.fingerprints-table {
width: 100%;
}
.fingerprints-head {
background: var(--background-base-10);
position: sticky;
top: 0;
}
.fingerprints-heading {
color: var(--shadow);
font-weight: normal;
padding: .25rem;
text-align: left;
}
.fingerprint {
&:nth-child(2n + 1) {
background: var(--shadow-weak-50);
}
&:hover {
background: var(--shadow-weak-40);
}
}
.fingerprint-field {
padding: .25rem;
}
.fingerprint-hash {
user-select: all;
}
@media(--compact) {
.content {
margin: 0;

View File

@ -112,6 +112,14 @@ function curateScene(rawScene, assets) {
photos: assets.photos?.map((photo) => curateMedia(photo, { type: 'photo' })) || [],
caps: assets.caps?.map((cap) => curateMedia(cap, { type: 'cap' })) || [],
stashes: assets.stashes?.map((stash) => curateStash(stash)) || [],
fingerprints: assets.fingerprints?.map((fingerprint) => ({
hash: fingerprint.hash,
type: fingerprint.type,
duration: fingerprint.duration,
source: fingerprint.source,
submissions: fingerprint.source_submissions,
createdAt: fingerprint.created_at,
})) || [],
createdBatchId: rawScene.created_batch_id,
updatedBatchId: rawScene.updated_batch_id,
isNew: assets.lastBatchId === rawScene.created_batch_id,
@ -138,6 +146,7 @@ export async function fetchScenesById(sceneIds, { reqUser, ...context } = {}) {
caps,
trailers,
teasers,
fingerprints,
stashes,
lastBatch: { id: lastBatchId },
} = await promiseProps({
@ -259,6 +268,20 @@ export async function fetchScenesById(sceneIds, { reqUser, ...context } = {}) {
.whereIn('release_id', sceneIds)
.leftJoin('media', 'media.id', 'releases_teasers.media_id');
}) : [],
fingerprints: context.includeAssets ? knex.transaction(async (trx) => {
if (reqUser) {
await trx.select(knex.raw('set_config(\'user.id\', :userId, true)', { userId: reqUser.id }));
}
return trx('releases_fingerprints')
.select('scene_id', 'hash', 'type', 'duration', 'source', 'source_submissions', knex.raw('min(coalesce(source_created_at, created_at)) as created_at'))
.whereIn('scene_id', sceneIds)
.orderBy([
{ column: 'source_submissions', order: 'desc' },
{ column: knex.raw('min(coalesce(source_created_at, created_at))'), order: 'desc' },
])
.groupBy(['scene_id', 'hash', 'type', 'duration', 'source', 'source_submissions']);
}) : [],
lastBatch: knex('batches')
.select('id')
.where('showcased', true)
@ -299,6 +322,7 @@ export async function fetchScenesById(sceneIds, { reqUser, ...context } = {}) {
const sceneCaps = caps.filter((cap) => cap.release_id === sceneId);
const sceneTrailers = trailers.find((trailer) => trailer.release_id === sceneId);
const sceneTeasers = teasers.find((teaser) => teaser.release_id === sceneId);
const sceneFingerprints = fingerprints.filter((fingerprint) => fingerprint.scene_id === sceneId);
const sceneStashes = stashes.filter((stash) => stash.scene_id === sceneId);
const sceneActorStashes = sceneActors.map((actor) => actorStashes.find((stash) => stash.actor_id === actor.id)).filter(Boolean);
@ -316,6 +340,7 @@ export async function fetchScenesById(sceneIds, { reqUser, ...context } = {}) {
caps: sceneCaps,
trailer: sceneTrailers,
teaser: sceneTeasers,
fingerprints: sceneFingerprints,
stashes: sceneStashes,
actorStashes: sceneActorStashes,
lastBatchId,