btrtracks/src/external.js

144 lines
3.9 KiB
JavaScript

import path from 'path'
import sanitize from 'sanitize-filename'
import * as asn from './common/async'
import * as dl from './common/download'
import { dbPromise } from './database'
import { URL } from 'url'
const fs = require('fs').promises
const values = require(path.join(process.cwd(), 'values.json'))
const memexpire = 1800
const externalTracks = {}
const downloadQueue = []
let downloading = false
async function downloadLocally (id) {
const info = await getTrackMetaReal(id)
if (!info) throw new Error('No track with this ID in external list.')
const file = await dl.fetchVideo(info)
let filename = info.artist + ' - ' + info.title + '.mp3'
filename = sanitize(filename)
info.file = path.join(values.directory, filename)
await asn.promiseExec(`ffmpeg -i "${file.source}" -metadata artist="${info.artist}" -metadata title="${info.title}" -codec copy "${info.file}"`)
await fs.unlink(file.source)
const db = await dbPromise
const ins = await asn.insertDB(db, info)
if (!ins) {
throw new Error('A track of this description already exists in the database.')
}
}
async function getTrackMetaReal (id) {
if (!id || !externalTracks[id]) return null
const trdata = externalTracks[id]
// Check for expiry
if (trdata.file && trdata.expires > Date.now()) {
return Object.assign({}, trdata)
}
const trsrch = 'ytsearch3:' + trdata.artist + ' - ' + trdata.title
const dldata = await dl.getVideoInfo(trsrch)
let bestMatch
if (dldata.length === 1) bestMatch = dldata[0]
let candidates = []
for (const i in dldata) {
const obj = dldata[i]
const title = obj.title.toLowerCase()
// Skip any video with 'video' in it, but keep lyric videos
if (title.indexOf('video') !== -1 && title.indexOf('lyric') === -1) continue
// If the title has 'audio' in it, it might be the best match
if (title.indexOf('audio') !== -1) {
bestMatch = obj
break
}
candidates.push(obj)
}
if (candidates.length && !bestMatch) {
// Sort candidates by view count
candidates = candidates.sort(function (a, b) {
return b.view_count - a.view_count
})
// Select the one with the most views
bestMatch = candidates[0]
}
// If there were no suitable candidates, just take the first response
if (!candidates.length && !bestMatch) bestMatch = dldata[0]
externalTracks[id] = {
id: trdata.id,
title: trdata.title,
artist: trdata.artist,
file: bestMatch.url,
duration: bestMatch.duration,
expires: Date.now() + memexpire * 1000,
external: true
}
return Object.assign({}, externalTracks[id])
}
// Download thread
let dltd = null
function invokeDownload (add) {
if (add) downloadQueue.push(add)
if (dltd) return
dltd = setTimeout(function (argument) {
dltd = null
if (downloading) return invokeDownload()
if (!downloadQueue.length) return
downloading = true
downloadLocally(downloadQueue.shift()).then(() => {
downloading = false
if (downloadQueue.length) invokeDownload()
}).catch((e) => {
console.error(e)
downloading = false
if (downloadQueue.length) invokeDownload()
})
}, 2 * 1000)
}
function addListExternal (id, obj) {
if (externalTracks[id]) return Object.assign({}, externalTracks[id])
externalTracks[id] = obj
return obj
}
async function searchURL (url) {
const urlp = new URL(url)
if (urlp.hostname.indexOf('youtube.com') !== -1 ||
urlp.hostname.indexOf('youtu.be') !== -1) {
const id = urlp.searchParams.get('v') || urlp.pathname.substr(1)
const ytb = await dl.getVideoInfo(id)
externalTracks[id] = {
id: id,
title: ytb.title,
artist: ytb.artist || ytb.uploader,
file: ytb.url,
duration: ytb.duration,
expires: Date.now() + memexpire * 1000,
external: true
}
return [externalTracks[id]]
}
return []
}
export {
downloadLocally,
invokeDownload,
addListExternal,
getTrackMetaReal,
searchURL
}