diff --git a/index.js b/index.js index 8e155dc..3fb54e6 100644 --- a/index.js +++ b/index.js @@ -3,20 +3,23 @@ const fs = require('fs') const youtubedl = require('youtube-dl-exec') const tmp = require('tmp') const playlist = require('./playlist-search') +const scraper = require('./playlist-scrape') const STREAM_URL = "https://www.youtube.com/henrikomagnifico/live" const DURATION_REGEX = /(\d{1,2}:\d{2})\/(\d{1,2}:\d{2})/ const EXPIRATION_REGEX = /.+\/expire\/([0-9]{10})\/.+/ +let albumWhitelistCharacters = playlist.getValidAlbumCharacters() +let trackWhitelistCharacters = playlist.getValidTrackCharacters() let currentTrack = {} let nextTrackTimestamp = 0 +let readAttempts = 0 let resolvedUrl = { url: '', expires: 0 } - function readText(tmpfile, charWhitelist) { return new Promise((resolve, reject) => { let command = `tesseract ${tmpfile.name} -` @@ -29,7 +32,7 @@ function readText(tmpfile, charWhitelist) { console.error(stderr) reject(error) } else { - resolve(stdout) + resolve(stdout.replace('\n', '').replace('\f', '')) } })) }) @@ -97,6 +100,7 @@ function getYoutubeAudioUrl() { function getVotes(ocrResults) { return ocrResults.map((text, index, arr) => { // Actually perform the search. Map returns array of search results, result.refIndex is of interest + console.debug(`Worker ${index} input:`, text) const title = text[0].trim() const album = text[1].trim() const position_duration = text[2].trim() @@ -113,29 +117,55 @@ function getVotes(ocrResults) { function tallyVotes(votes) { const trackResults = {} const positionResults = {} - votes.forEach(vote => { + let overallVotes = 0 + let positionVotes = 0 + const voters = [] + const durationVotes = [] + votes.forEach((vote, index, arr) => { if (vote.result != null) { + overallVotes++ + voters.push("v") if (trackResults.hasOwnProperty(vote.result.refIndex)) { trackResults[`${vote.result.refIndex}`] = trackResults[`${vote.result.refIndex}`] + 1 } else { trackResults[`${vote.result.refIndex}`] = 1 } + } else { + voters.push("x") } if (vote.position) { + positionVotes++ + durationVotes.push("v") if (positionResults.hasOwnProperty(vote.position)) { positionResults[`${vote.position}`] = positionResults[`${vote.position}`] + 1 } else { positionResults[`${vote.position}`] = 1 } + } else { + durationVotes.push("x") } }) - if (trackResults.length === 0) { - console.log("votes resulted in no results!") + + console.debug("Voter status:") + console.debug("Result vote:", voters) + console.debug("Duration vote:", durationVotes) + if (overallVotes === 0) { + console.warn("no workers voted") return { position: null, positionConfidence: 0, result: null, - resultConfidence: 0 + resultConfidence: 0, + voterConfidence: overallVotes / votes.length + } + } else if (overallVotes / votes.length < 0.5) { + console.warn("not enough workers voted") + return { + position: null, + positionConfidence: 0, + result: null, + resultConfidence: 0, + voterConfidence: overallVotes / votes.length } } @@ -144,6 +174,7 @@ function tallyVotes(votes) { let trackMaxVotes = trackResults[trackIndex] let position = Object.keys(positionResults)[0] let positionMaxVotes = positionResults[position] + console.debug("Initial index:", trackIndex, trackResults) for (const index in trackResults) { if (trackResults[index] > trackMaxVotes) { trackIndex = index @@ -158,13 +189,20 @@ function tallyVotes(votes) { } // console.log(JSON.stringify(votes)) - console.debug(`Vote was index ${trackIndex}`) + console.debug(`Vote was index`, trackIndex) + console.debug("Voter results:") + // console.debug("Track accuracy:", voters.map((v, i, a) => { + // if (v === 'v') { + + // } + // })) const actualResult = votes.find(v => v.result != null && `${v.result.refIndex}` === trackIndex).result.item return { position: position, positionConfidence: positionMaxVotes / votes.length, result: actualResult, - resultConfidence: trackMaxVotes / votes.length + resultConfidence: trackMaxVotes / overallVotes, + voterConfidence: overallVotes / votes.length } } @@ -181,7 +219,7 @@ function performQuorumProcessing(titleCroppedPromise, albumCroppedPromise, durat return new Promise(((resolve, reject) => { Promise.all([titleCroppedPromise, albumCroppedPromise, durationCroppedPromise]) // Wait for all promises to be resolved, returns paths to cropped segments of frame .then(images => { - const thresholds = [15, 12.5, 10, 7.5, 5] + const thresholds = [20, 17.5, 15, 12.5, 10, 7.5, 5] Promise.all(thresholds.map(t => { // Threshold all segments at determined levels, returns text determined by each threshold level as [title, album, position/duration] return Promise.all([ thresholdImage(images[0], t).then(file => readText(file)), @@ -197,8 +235,9 @@ function performQuorumProcessing(titleCroppedPromise, albumCroppedPromise, durat // Tally votes const winner = tallyVotes(votes) - console.debug(`Position confidence: ${winner.positionConfidence}`) - console.debug(`Result confidence: ${winner.resultConfidence}`) + console.debug('Position confidence:', winner.positionConfidence) + console.debug('Result confidence:', winner.resultConfidence) + console.debug('Voter confidence:', winner.voterConfidence) resolve(winner) }) @@ -220,7 +259,7 @@ async function updateTrackInfo() { } else if (urlExpiresSoon) { updateStreamUrl().then(() => console.log("Updated stream info in the background")) } - console.log(`Now: ${Date.now()}\tExpiration: ${resolvedUrl.expires}`) + console.log(`Now: ${Date.now()}\tExpiration: ${resolvedUrl.expires}\tAttempt: ${readAttempts}`) const frame = await getFrame(resolvedUrl.url) const frameTime = Date.now() @@ -229,16 +268,18 @@ async function updateTrackInfo() { const durationPromise = getRegion(frame, 0, 1028, 235, 34) const result = await performQuorumProcessing(titlePromise, albumPromise, durationPromise) - if (result.resultConfidence < 0.5) { - setTimeout(() => updateTrackInfo(), 250) + if (result.resultConfidence < 0.4) { + setTimeout(() => updateTrackInfo(), 0) + readAttempts++ return; } + readAttempts = 0 currentTrack = result.result console.log(`${currentTrack.album}: ${currentTrack.track} (${result.position}/${currentTrack.duration})`) const secondsUntilNext = timeUntilNextTrack(result.position, currentTrack.duration) const processingTime = Date.now() - frameTime nextTrackTimestamp = frameTime + (secondsUntilNext * 1000) - setTimeout(() => updateTrackInfo(), (secondsUntilNext * 1000) - processingTime + 500) + setTimeout(() => updateTrackInfo(), (secondsUntilNext * 1000) - processingTime + 250) console.log(`Frame processing took ${processingTime}ms`) console.log(`Next track should be at: ${new Date(nextTrackTimestamp).toLocaleString()}`) }