Created
September 11, 2024 22:39
-
-
Save netham45/d7432f1bff3143f3ea181aaf58d7a654 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const videoElement = document.getElementsByTagName('video')[0]; | |
let isRecording = false; | |
let audioContext, source, analyser, scriptProcessor; | |
let canvas, ctx; | |
let currentFingerprint = []; | |
let storedFingerprint = null; | |
let lastCheckTime = 0; | |
const SAMPLE_RATE = 22050; | |
const FFT_SIZE = 1024; | |
const MIN_FREQ = 300; | |
const MAX_FREQ = 2000; | |
const FINGERPRINT_DURATION = 5; // seconds | |
const CHECK_INTERVAL = 1; // seconds | |
const FINGERPRINT_LENGTH = Math.floor(SAMPLE_RATE * FINGERPRINT_DURATION / (FFT_SIZE / 2)); | |
function setupCanvas() { | |
canvas = document.createElement('canvas'); | |
canvas.width = FINGERPRINT_LENGTH; | |
canvas.height = 32; | |
ctx = canvas.getContext('2d'); | |
canvas.style.position = "absolute"; | |
canvas.style.zIndex = 9999; | |
canvas.style.top = "10px"; | |
canvas.style.left = "10px"; | |
canvas.style.border = "1px solid white"; | |
canvas.style.backgroundColor = "black"; | |
document.body.insertBefore(canvas, document.body.firstChild); | |
} | |
function startRecording(videoElement) { | |
if (isRecording) return; | |
isRecording = true; | |
if (!canvas) setupCanvas(); | |
try { | |
audioContext = new (window.AudioContext || window.webkitAudioContext)({ sampleRate: SAMPLE_RATE }); | |
} catch (e) { | |
console.error('Web Audio API is not supported in this browser', e); | |
return; | |
} | |
source = audioContext.createMediaElementSource(videoElement); | |
analyser = audioContext.createAnalyser(); | |
scriptProcessor = audioContext.createScriptProcessor(FFT_SIZE, 1, 1); | |
analyser.fftSize = FFT_SIZE; | |
analyser.minDecibels = -90; | |
analyser.maxDecibels = -30; | |
analyser.smoothingTimeConstant = 0; | |
source.connect(analyser); | |
source.connect(audioContext.destination); | |
analyser.connect(scriptProcessor); | |
scriptProcessor.connect(audioContext.destination); | |
const bufferLength = analyser.frequencyBinCount; | |
const dataArray = new Uint8Array(bufferLength); | |
const minBin = Math.floor(MIN_FREQ / (SAMPLE_RATE / FFT_SIZE)); | |
const maxBin = Math.ceil(MAX_FREQ / (SAMPLE_RATE / FFT_SIZE)); | |
scriptProcessor.onaudioprocess = function() { | |
analyser.getByteFrequencyData(dataArray); | |
let columnFingerprint = new Array(32).fill(0); | |
for (let i = minBin; i < maxBin; i++) { | |
const y = Math.floor((i - minBin) / ((maxBin - minBin) / 32)); | |
columnFingerprint[y] = dataArray[i] > 128 ? 1 : 0; | |
} | |
currentFingerprint.push(columnFingerprint); | |
if (currentFingerprint.length > FINGERPRINT_LENGTH) { | |
currentFingerprint.shift(); | |
} | |
renderFingerprint(); | |
if (storedFingerprint && audioContext.currentTime - lastCheckTime >= CHECK_INTERVAL) { | |
checkForMatch(); | |
lastCheckTime = audioContext.currentTime; | |
} | |
}; | |
videoElement.play(); | |
} | |
function renderFingerprint() { | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
for (let x = 0; x < currentFingerprint.length; x++) { | |
for (let y = 0; y < 32; y++) { | |
if (currentFingerprint[x][y] === 1) { | |
ctx.fillStyle = 'white'; | |
ctx.fillRect(x, y, 1, 1); | |
} | |
} | |
} | |
} | |
function stopRecording() { | |
if (!isRecording) return; | |
isRecording = false; | |
scriptProcessor.disconnect(); | |
analyser.disconnect(); | |
source.disconnect(); | |
} | |
function captureFiveSeconds() { | |
if (currentFingerprint.length < FINGERPRINT_LENGTH) { | |
console.log(`Not enough data to capture five seconds. Current length: ${currentFingerprint.length}, Required: ${FINGERPRINT_LENGTH}`); | |
return null; | |
} | |
storedFingerprint = currentFingerprint.slice(); | |
localStorage.setItem('storedFingerprint', JSON.stringify(storedFingerprint)); | |
console.log("Five second fingerprint captured and stored in localStorage"); | |
return storedFingerprint; | |
} | |
function checkForMatch() { | |
if (!storedFingerprint || currentFingerprint.length < FINGERPRINT_LENGTH) return; | |
const lastSecond = currentFingerprint.slice(-Math.floor(FINGERPRINT_LENGTH / 5)); | |
for (let i = 0; i <= storedFingerprint.length - lastSecond.length; i++) { | |
const similarity = calculateSimilarity(storedFingerprint.slice(i, i + lastSecond.length), lastSecond); | |
if (similarity > 0.9) { | |
console.log(`Match found at position ${i / (FINGERPRINT_LENGTH / FINGERPRINT_DURATION)} seconds with ${similarity.toFixed(2)} similarity`); | |
onMatch(i, similarity); | |
return; | |
} | |
} | |
} | |
function calculateSimilarity(fingerprint1, fingerprint2) { | |
let matchingPixels = 0; | |
let totalPixels = fingerprint1.length * fingerprint1[0].length; | |
for (let i = 0; i < fingerprint1.length; i++) { | |
for (let j = 0; j < fingerprint1[i].length; j++) { | |
if (fingerprint1[i][j] === fingerprint2[i][j]) { | |
matchingPixels++; | |
} | |
} | |
} | |
return matchingPixels / totalPixels; | |
} | |
function skipForward(seconds) { | |
if (videoElement && !isNaN(seconds) && videoElement.readyState >= 2) { | |
const newTime = videoElement.currentTime + seconds; | |
videoElement.currentTime = Math.min(newTime, videoElement.duration); | |
} else { | |
console.warn('Video not ready or invalid skip time'); | |
} | |
} | |
function onMatch(position, similarity) { | |
let jump = 5 - position / (FINGERPRINT_LENGTH / FINGERPRINT_DURATION); | |
console.log(`Match found! The current audio matches the stored fingerprint at position ${position / (FINGERPRINT_LENGTH / FINGERPRINT_DURATION)} seconds with ${similarity.toFixed(2)} similarity`); | |
canvas.style.border = "3px solid red"; | |
setTimeout(() => { canvas.style.border = "1px solid white"; }, 1000); | |
skipForward(jump); | |
} | |
function startAudioProcessing() { | |
if (!videoElement) { | |
console.error('Video element not found'); | |
return; | |
} | |
const storedFingerprintString = localStorage.getItem('storedFingerprint'); | |
if (storedFingerprintString) { | |
storedFingerprint = JSON.parse(storedFingerprintString); | |
console.log('Fingerprint loaded from localStorage'); | |
} | |
startRecording(videoElement); | |
const btnTag = document.createElement("button"); | |
btnTag.textContent = "Tag Last 5s of audio to be skipped"; | |
btnTag.style.position = "absolute"; | |
btnTag.style.zIndex = "99999"; | |
btnTag.style.top = "50px"; | |
btnTag.style.left = "10px"; | |
btnTag.style.padding = "10px"; | |
btnTag.style.backgroundColor = "#4CAF50"; | |
btnTag.style.color = "white"; | |
btnTag.style.border = "none"; | |
btnTag.style.cursor = "pointer"; | |
btnTag.addEventListener('click', captureFiveSeconds); | |
document.body.appendChild(btnTag); | |
videoElement.addEventListener('ended', stopRecording); | |
} | |
function exportFingerprintAsBase64() { | |
if (!storedFingerprint) { | |
console.log("No fingerprint stored to export"); | |
return null; | |
} | |
// Convert the fingerprint to a JSON string | |
const fingerprintJSON = JSON.stringify(storedFingerprint); | |
// Convert the JSON string to a Uint8Array | |
const uint8Array = new TextEncoder().encode(fingerprintJSON); | |
// Convert the Uint8Array to base64 | |
const base64String = btoa(String.fromCharCode.apply(null, uint8Array)); | |
console.log("Fingerprint exported as base64"); | |
return base64String; | |
} | |
function importFingerprintFromBase64(base64String) { | |
if (!base64String) { | |
console.log("No base64 string provided"); | |
return false; | |
} | |
try { | |
// Convert base64 to Uint8Array | |
const uint8Array = new Uint8Array(atob(base64String).split('').map(char => char.charCodeAt(0))); | |
// Convert Uint8Array to JSON string | |
const fingerprintJSON = new TextDecoder().decode(uint8Array); | |
// Parse JSON string to get the fingerprint | |
const importedFingerprint = JSON.parse(fingerprintJSON); | |
// Validate the imported fingerprint structure | |
if (!Array.isArray(importedFingerprint) || importedFingerprint.length !== FINGERPRINT_LENGTH) { | |
throw new Error("Invalid fingerprint structure"); | |
} | |
// Store the imported fingerprint | |
storedFingerprint = importedFingerprint; | |
localStorage.setItem('storedFingerprint', JSON.stringify(storedFingerprint)); | |
console.log("Fingerprint imported successfully"); | |
return true; | |
} catch (error) { | |
console.error("Error importing fingerprint:", error); | |
return false; | |
} | |
} | |
startAudioProcessing(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment