235 lines
6.4 KiB
JavaScript
235 lines
6.4 KiB
JavaScript
// re-render the troubleshooter with the latest offset
|
|
function renderTroubleshooter() {
|
|
paintFingerprintDiff(canvas, lhs, rhs, Number(offset.value));
|
|
findIntros();
|
|
}
|
|
|
|
// refresh the upper & lower bounds for the offset
|
|
function refreshBounds() {
|
|
const len = Math.min(lhs.length, rhs.length) - 1;
|
|
offset.min = -1 * len;
|
|
offset.max = len;
|
|
}
|
|
|
|
function findIntros() {
|
|
let times = [];
|
|
|
|
// get the times of all similar fingerprint points
|
|
for (let i in fprDiffs) {
|
|
if (fprDiffs[i] > fprDiffMinimum) {
|
|
times.push(i * 0.128);
|
|
}
|
|
}
|
|
|
|
// always close the last range
|
|
times.push(Number.MAX_VALUE);
|
|
|
|
let last = times[0];
|
|
let start = last;
|
|
let end = last;
|
|
let ranges = [];
|
|
|
|
for (let t of times) {
|
|
const diff = t - last;
|
|
|
|
if (diff <= 3.5) {
|
|
end = t;
|
|
last = t;
|
|
continue;
|
|
}
|
|
|
|
const dur = Math.round(end - start);
|
|
if (dur >= 15) {
|
|
ranges.push({
|
|
"start": start,
|
|
"end": end,
|
|
"duration": dur
|
|
});
|
|
}
|
|
|
|
start = t;
|
|
end = t;
|
|
last = t;
|
|
}
|
|
|
|
const introsLog = document.querySelector("span#intros");
|
|
introsLog.style.position = "relative";
|
|
introsLog.style.left = "115px";
|
|
introsLog.innerHTML = "";
|
|
|
|
const offset = Number(txtOffset.value) * 0.128;
|
|
for (let r of ranges) {
|
|
let lStart, lEnd, rStart, rEnd;
|
|
|
|
if (offset < 0) {
|
|
// negative offset, the diff is aligned with the RHS
|
|
lStart = r.start - offset;
|
|
lEnd = r.end - offset;
|
|
rStart = r.start;
|
|
rEnd = r.end;
|
|
|
|
} else {
|
|
// positive offset, the diff is aligned with the LHS
|
|
lStart = r.start;
|
|
lEnd = r.end;
|
|
rStart = r.start + offset;
|
|
rEnd = r.end + offset;
|
|
}
|
|
|
|
const lTitle = selectEpisode1.options[selectEpisode1.selectedIndex].text;
|
|
const rTitle = selectEpisode2.options[selectEpisode2.selectedIndex].text;
|
|
introsLog.innerHTML += "<span>" + lTitle + ": " +
|
|
secondsToString(lStart) + " - " + secondsToString(lEnd) + "</span> <br />";
|
|
introsLog.innerHTML += "<span>" + rTitle + ": " +
|
|
secondsToString(rStart) + " - " + secondsToString(rEnd) + "</span> <br />";
|
|
}
|
|
}
|
|
|
|
// find all shifts which align exact matches of audio.
|
|
function findExactMatches() {
|
|
let shifts = [];
|
|
|
|
for (let lhsIndex in lhs) {
|
|
let lhsPoint = lhs[lhsIndex];
|
|
let rhsIndex = rhs.findIndex((x) => x === lhsPoint);
|
|
|
|
if (rhsIndex === -1) {
|
|
continue;
|
|
}
|
|
|
|
let shift = rhsIndex - lhsIndex;
|
|
if (shifts.includes(shift)) {
|
|
continue;
|
|
}
|
|
|
|
shifts.push(shift);
|
|
}
|
|
|
|
// Only suggest up to 20 shifts
|
|
shifts = shifts.slice(0, 20);
|
|
|
|
txtSuggested.textContent = "Suggested shifts: ";
|
|
if (shifts.length === 0) {
|
|
txtSuggested.textContent += "none available";
|
|
} else {
|
|
shifts.sort((a, b) => { return a - b });
|
|
txtSuggested.textContent += shifts.join(", ");
|
|
}
|
|
}
|
|
|
|
// The below two functions were modified from https://github.com/dnknth/acoustid-match/blob/ffbf21d8c53c40d3b3b4c92238c35846545d3cd7/fingerprints/static/fingerprints/fputils.js
|
|
// Originally licensed as MIT.
|
|
function renderFingerprintData(ctx, fp, xor = false) {
|
|
const pixels = ctx.createImageData(32, fp.length);
|
|
let idx = 0;
|
|
|
|
for (let i = 0; i < fp.length; i++) {
|
|
for (let j = 0; j < 32; j++) {
|
|
if (fp[i] & (1 << j)) {
|
|
pixels.data[idx + 0] = 255;
|
|
pixels.data[idx + 1] = 255;
|
|
pixels.data[idx + 2] = 255;
|
|
|
|
} else {
|
|
pixels.data[idx + 0] = 0;
|
|
pixels.data[idx + 1] = 0;
|
|
pixels.data[idx + 2] = 0;
|
|
}
|
|
|
|
pixels.data[idx + 3] = 255;
|
|
idx += 4;
|
|
}
|
|
}
|
|
|
|
if (!xor) {
|
|
return pixels;
|
|
}
|
|
|
|
// if rendering the XOR of the fingerprints, count how many bits are different at each timecode
|
|
fprDiffs = [];
|
|
|
|
for (let i = 0; i < fp.length; i++) {
|
|
let count = 0;
|
|
|
|
for (let j = 0; j < 32; j++) {
|
|
if (fp[i] & (1 << j)) {
|
|
count++;
|
|
}
|
|
}
|
|
|
|
// push the percentage similarity
|
|
fprDiffs[i] = 100 - (count * 100) / 32;
|
|
}
|
|
|
|
return pixels;
|
|
}
|
|
|
|
function paintFingerprintDiff(canvas, fp1, fp2, offset) {
|
|
if (fp1.length == 0) {
|
|
return;
|
|
}
|
|
|
|
canvas.style.display = "unset";
|
|
|
|
let leftOffset = 0, rightOffset = 0;
|
|
if (offset < 0) {
|
|
leftOffset -= offset;
|
|
} else {
|
|
rightOffset += offset;
|
|
}
|
|
|
|
let fpDiff = [];
|
|
fpDiff.length = Math.min(fp1.length, fp2.length) - Math.abs(offset);
|
|
for (let i = 0; i < fpDiff.length; i++) {
|
|
fpDiff[i] = fp1[i + leftOffset] ^ fp2[i + rightOffset];
|
|
}
|
|
|
|
const ctx = canvas.getContext('2d');
|
|
const pixels1 = renderFingerprintData(ctx, fp1);
|
|
const pixels2 = renderFingerprintData(ctx, fp2);
|
|
const pixelsDiff = renderFingerprintData(ctx, fpDiff, true);
|
|
const border = 4;
|
|
|
|
canvas.width = pixels1.width + border + // left fingerprint
|
|
pixels2.width + border + // right fingerprint
|
|
pixelsDiff.width + border // fingerprint diff
|
|
+ 4; // if diff[x] >= fprDiffMinimum
|
|
|
|
canvas.height = Math.max(pixels1.height, pixels2.height) + Math.abs(offset);
|
|
|
|
ctx.rect(0, 0, canvas.width, canvas.height);
|
|
ctx.fillStyle = "#C5C5C5";
|
|
ctx.fill();
|
|
|
|
// draw left fingerprint
|
|
let dx = 0;
|
|
ctx.putImageData(pixels1, dx, rightOffset);
|
|
dx += pixels1.width + border;
|
|
|
|
// draw right fingerprint
|
|
ctx.putImageData(pixels2, dx, leftOffset);
|
|
dx += pixels2.width + border;
|
|
|
|
// draw fingerprint diff
|
|
ctx.putImageData(pixelsDiff, dx, Math.abs(offset));
|
|
dx += pixelsDiff.width + border;
|
|
|
|
// draw the fingerprint diff similarity indicator
|
|
// https://davidmathlogic.com/colorblind/#%23EA3535-%232C92EF
|
|
for (let i in fprDiffs) {
|
|
const j = Number(i);
|
|
const y = Math.abs(offset) + j;
|
|
const point = fprDiffs[j];
|
|
|
|
if (point >= 100) {
|
|
ctx.fillStyle = "#002FFF"
|
|
} else if (point >= fprDiffMinimum) {
|
|
ctx.fillStyle = "#2C92EF";
|
|
} else {
|
|
ctx.fillStyle = "#EA3535";
|
|
}
|
|
|
|
ctx.fillRect(dx, y, 4, 1);
|
|
}
|
|
}
|