From 9c6eeaafdba79e783e61636ee3380eaa3f1536b2 Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Fri, 6 Jun 2025 15:22:08 +0000 Subject: [PATCH] feat: Fuzzy diff threshold configurable * Refactor fuzzy diff theshold to be configurable * Configure aggressive fuzzy discovery for Deezer to be 40 seconds #296 --- src/backend/sources/DeezerInternalSource.ts | 2 +- src/backend/tests/source/source.test.ts | 14 ++++++++++++++ src/backend/utils/PlayComparisonUtils.ts | 4 ++-- src/backend/utils/TimeUtils.ts | 13 +++++++++---- 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/backend/sources/DeezerInternalSource.ts b/src/backend/sources/DeezerInternalSource.ts index 8ede4bd5..5770dae7 100644 --- a/src/backend/sources/DeezerInternalSource.ts +++ b/src/backend/sources/DeezerInternalSource.ts @@ -212,7 +212,7 @@ export default class DeezerInternalSource extends MemorySource { if(this.config.options?.fuzzyDiscoveryIgnore === true || this.config.options?.fuzzyDiscoveryIgnore === 'aggressive') { const fuzzyIndex = list.findIndex(x => { const e = this.transformPlay(x, TRANSFORM_HOOK.existing); - return genericSourcePlayMatch(e, candidate, TA_FUZZY); + return genericSourcePlayMatch(e, candidate, TA_FUZZY, {fuzzyDiffThreshold: this.config.options?.fuzzyDiscoveryIgnore === 'aggressive' ? 40 : undefined}); }); if(fuzzyIndex !== -1) { if(this.config.options?.fuzzyDiscoveryIgnore === 'aggressive') { diff --git a/src/backend/tests/source/source.test.ts b/src/backend/tests/source/source.test.ts index b654494e..e60906c8 100644 --- a/src/backend/tests/source/source.test.ts +++ b/src/backend/tests/source/source.test.ts @@ -545,6 +545,20 @@ describe('Deezer Internal Source', function() { expect(discovered.length).to.eq(0); }); + it('does not discover fuzzy play with delay of up to 40 seconds', function() { + const interimPlay = generatePlay({playDate: lastPlay.data.playDate.add(15, 's'), duration: 80}); + const targetPlay = normalizedPlays[normalizedPlays.length - 2] + const fuzzyPlay = clone(targetPlay); + fuzzyPlay.data.playDate = targetPlay.data.playDate.add(targetPlay.data.duration + 39, 's'); + + const source = generateDeezerSource({fuzzyDiscoveryIgnore: 'aggressive'}); + source.discover([...normalizedPlays, interimPlay]); + + const discovered = source.discover([fuzzyPlay]); + + expect(discovered.length).to.eq(0); + }); + it('it does not discover fuzzy play when it is the last play ', function() { const targetPlay = normalizedPlays[normalizedPlays.length - 1] const fuzzyPlay = clone(targetPlay); diff --git a/src/backend/utils/PlayComparisonUtils.ts b/src/backend/utils/PlayComparisonUtils.ts index 021020e6..18919422 100644 --- a/src/backend/utils/PlayComparisonUtils.ts +++ b/src/backend/utils/PlayComparisonUtils.ts @@ -2,7 +2,7 @@ import { getListDiff, ListDiff } from "@donedeal0/superdiff"; import { PlayObject, TA_CLOSE, TemporalAccuracy } from "../../core/Atomic.js"; import { buildTrackString } from "../../core/StringUtils.js"; import { playObjDataMatch } from "../utils.js"; -import { comparePlayTemporally, temporalAccuracyIsAtLeast } from "./TimeUtils.js"; +import { comparePlayTemporally, temporalAccuracyIsAtLeast, TemporalPlayComparisonOptions } from "./TimeUtils.js"; export const metaInvariantTransform = (play: PlayObject): PlayObject => { @@ -224,4 +224,4 @@ export const humanReadableDiff = (aPlay: PlayObject[], bPlay: PlayObject[], resu }).join('\n'); } -export const genericSourcePlayMatch = (a: PlayObject, b: PlayObject, t: TemporalAccuracy = TA_CLOSE): boolean => playObjDataMatch(a, b) && temporalAccuracyIsAtLeast(t, comparePlayTemporally(a, b).match); \ No newline at end of file +export const genericSourcePlayMatch = (a: PlayObject, b: PlayObject, t: TemporalAccuracy = TA_CLOSE, temporalOptions?: TemporalPlayComparisonOptions): boolean => playObjDataMatch(a, b) && temporalAccuracyIsAtLeast(t, comparePlayTemporally(a, b, temporalOptions).match); \ No newline at end of file diff --git a/src/backend/utils/TimeUtils.ts b/src/backend/utils/TimeUtils.ts index 82431c48..dca8dc5c 100644 --- a/src/backend/utils/TimeUtils.ts +++ b/src/backend/utils/TimeUtils.ts @@ -54,11 +54,15 @@ export const temporalPlayComparisonSummary = (data: TemporalPlayComparison, exis } return parts.join(' | '); } -export const comparePlayTemporally = (existingPlay: PlayObject, candidatePlay: PlayObject, options: { + +export interface TemporalPlayComparisonOptions { diffThreshold?: number, fuzzyDuration?: boolean, + fuzzyDiffThreshold?: number useListRanges?: boolean -} = {}): TemporalPlayComparison => { +} + +export const comparePlayTemporally = (existingPlay: PlayObject, candidatePlay: PlayObject, options: TemporalPlayComparisonOptions = {}): TemporalPlayComparison => { const result: TemporalPlayComparison = { match: TA_NONE @@ -98,6 +102,7 @@ export const comparePlayTemporally = (existingPlay: PlayObject, candidatePlay: P const { diffThreshold = lowGranularitySources.some(x => x.toLocaleLowerCase() === source) ? 60 : 10, fuzzyDuration = false, + fuzzyDiffThreshold = 10, useListRanges = true, } = options; @@ -146,7 +151,7 @@ export const comparePlayTemporally = (existingPlay: PlayObject, candidatePlay: P // so check if the duration matches the diff between the two play dates if (result.match === TA_NONE && referenceDuration !== undefined) { result.date.fuzzyDurationDiff = Math.abs(scrobblePlayDiff - referenceDuration); - if (result.date.fuzzyDurationDiff <= 10) { // TODO use finer comparison for this? + if (result.date.fuzzyDurationDiff <= fuzzyDiffThreshold) { // TODO use finer comparison for this? result.match = TA_FUZZY; } } @@ -155,7 +160,7 @@ export const comparePlayTemporally = (existingPlay: PlayObject, candidatePlay: P // so check if there is a close match between candidate play date and source + listened for if (result.match === TA_NONE && referenceListenedFor !== undefined && fuzzyDuration) { result.date.fuzzyListenedDiff = Math.abs(scrobblePlayDiff - referenceListenedFor); - if (result.date.fuzzyListenedDiff <= 10) { // TODO use finer comparison for this? + if (result.date.fuzzyListenedDiff <= fuzzyDiffThreshold) { // TODO use finer comparison for this? result.match = TA_FUZZY } } -- 2.51.2