track metadata editing

This commit is contained in:
Evert Prants 2020-01-12 15:29:31 +02:00
parent a70cf0e508
commit bcc708ded9
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
8 changed files with 225 additions and 53 deletions

View File

@ -362,6 +362,36 @@ canvas#visualizer {
.sidebar a {
color: #18b9c1;
}
.modal {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: hsla(0, 0%, 1%, 0.5);
}
.modal-box {
max-width: 500px;
margin: auto;
margin-top: 10%;
background-color: #152b3a;
}
.modal-header {
padding: 10px;
font-size: 2em;
background-color: #1087bd;
border-bottom: 4px solid #1b6da5;
}
.modal-content {
padding: 10px;
}
.modal-content label {
display: block;
margin-top: 10px;
}
.modal-content input[type="text"] {
width: 100%;
}
@media only screen and (max-width: 600px) {
tr td:nth-child(1), th:nth-child(1) {
display: none;

View File

@ -71,6 +71,7 @@
<ul>
<li><a class="ctx-item" data-action="play">Play Track</a></li>
<li><a class="ctx-item" data-action="queue">Queue Track</a></li>
<li><a class="ctx-item" data-action="edit">Edit Metadata</a></li>
<li><a class="ctx-item" data-action="download">Download</a></li>
<li class="ctx-multi"><a class="ctx-item playlist-add" style="display: none;">Add to Playlist</a>
<ul class="ctx-sub-items playlist-list" id="ctx-playlists"></ul>
@ -136,6 +137,29 @@
</div>
</div>
</div>
<div class="modal" id="track-edit-modal" style="display: none;">
<div class="modal-box">
<div class="modal-header">Edit Track</div>
<div class="modal-content">
<form id="track-set">
<label for="ts-title">Title</label>
<input type="text" name="ts-title" id="ts-title">
<label for="ts-artist">Artist</label>
<input type="text" name="ts-artist" id="ts-artist">
<label for="ts-album">Album</label>
<input type="text" name="ts-album" id="ts-album">
<label for="ts-genre">Genre</label>
<input type="text" name="ts-genre" id="ts-genre">
<label for="ts-year">Year</label>
<input type="text" name="ts-year" id="ts-year">
<label for="ts-track">Track nr</label>
<input type="text" name="ts-track" id="ts-track">
<input type="submit" value="Edit">
</form>
<button id="track-edit-close">Close</button>
</div>
</div>
</div>
<script type="text/javascript" src="index.js"></script>
<script type="text/javascript" src="player.js"></script>
<script type="text/javascript" src="visuals.js"></script>

View File

@ -7,6 +7,9 @@
var optdrop = document.getElementById('options-drop')
var optmenu = document.getElementById('options')
var loggedin = document.getElementById('logged-in')
var trackedit = document.getElementById('track-edit-modal')
var trackform = document.getElementById('track-set')
var trackclose = document.getElementById('track-edit-close')
var menu = document.getElementById('menu')
@ -19,6 +22,7 @@
<th class="small">Duration</th> \
</tr>'
var editing = null
var nowPlaying = 0
var externalStream = false
@ -45,6 +49,7 @@
var ctxPlaylist = document.getElementById('ctx-playlists')
// Options
var optcont = document.querySelector('.sidebar.bar')
var options = {
autoplay: true,
trackids: true,
@ -151,7 +156,8 @@
return artist + ' - ' + title
}
function handleHash (hash) {
function handleHash () {
let hash = window.location.hash
if (hash.indexOf('#') === 0) hash = hash.substr(1)
if (hash.length === 0) return
@ -269,6 +275,25 @@
})
}
function closeTrackModal () {
editing = null
trackedit.style.display = 'none'
}
function editTrack (tid) {
httpGet('/api/track/' + tid).then(function (metadata) {
editing = tid
for (let i in metadata) {
let el = trackform.querySelector('#ts-' + i)
if (!el) continue
el.value = metadata[i]
}
trackedit.style.display = 'block'
}, function (e) {
console.log(e)
})
}
function ctxHandle (el) {
if (ctxState === 0) return
let dt = el.getAttribute("data-action")
@ -285,6 +310,9 @@
case 'download':
window.open('/api/serve/by-id/' + ctxState + '?dl=1', '_blank')
break
case 'edit':
editTrack(ctxState)
break
case 'playlist-remove':
removeFromPlaylist(ctxState)
break
@ -311,6 +339,9 @@
let plAdd = menu.querySelector('.playlist-add')
plAdd.style.display = isNaN(parseInt(xid)) ? 'none' : 'block'
let tEdit = menu.querySelector('.ctx-item[data-action="edit"]')
tEdit.style.display = isNaN(parseInt(xid)) ? 'none' : 'block'
let plDel = menu.querySelector('.ctx-item[data-action="playlist-remove"]')
plDel.style.display = (playlist != null && playlist >= 0) ? 'block' : 'none'
@ -368,7 +399,7 @@
let tag = trackDataRow(track)
tag.addEventListener('click', function (e) {
play(track.trackId || track.id)
play.call(this, track.trackId || track.id)
}, false)
tag.addEventListener('contextmenu', function (e) {
@ -737,6 +768,33 @@
showTracks(pagePrev !== 0 ? pagePrev : 1)
})
var metas = ['title', 'artist', 'album', 'year', 'genre', 'track']
trackform.addEventListener('submit', function (e) {
e.preventDefault()
if (editing == null) return closeTrackModal()
let meta = {}
for (let k in metas) {
let p = metas[k]
let a = trackedit.querySelector('#ts-' + p)
if (!a) continue
meta[p] = a.value
}
httpPost('/api/track/' + editing, meta).then(function () {
closeTrackModal()
alert('Successfully edited track metadata!')
if (!playlist || playlist < 0) showTracks(pageNum)
else showPlaylist(playlist)
}).catch(function (e) {
alert(e.message)
console.error(e)
})
}, false)
trackclose.addEventListener('click', closeTrackModal, false)
document.getElementById('player-next').addEventListener('click', playNext, false)
document.getElementById('player-prev').addEventListener('click', playPrevious, false)
@ -760,7 +818,7 @@
window.addEventListener('hashchange', function (e) {
e.preventDefault()
handleHash(window.location.hash)
handleHash()
}, false)
window.addEventListener('resize', function (e) {
@ -781,7 +839,6 @@
}
})
var optcont = document.querySelector('.sidebar.bar')
document.addEventListener('click', function (event) {
// event.target.closest(optcont) === null
if (!optcont.contains(event.target) && optdrop.className.indexOf('active') !== -1) {
@ -810,7 +867,7 @@
checkUser()
loadOptions()
showTracks(1)
handleHash(window.location.hash)
handleHash()
populatePlaylists()
handleSelect()
handleOptions()

View File

@ -2,8 +2,11 @@ import {exec} from 'child_process'
import path from 'path'
import fs from 'fs-extra'
import url from 'url'
import os from 'os'
import qs from 'querystring'
const supportedMetadata = ['album', 'genre', 'title', 'artist', 'year', 'track']
function filewalker (dir, done) {
let results = []
@ -50,6 +53,17 @@ async function insertDB (db, track) {
return track
}
async function updateDB (db, id, meta) {
let sanit = []
for (let key in meta) {
if (supportedMetadata.indexOf(key) === -1) continue
let val = meta[key]
sanit.push(key + '="' + val + '"')
}
await db.run('UPDATE Track SET ' + sanit.join(',') + ' WHERE id = ?', id)
return meta
}
function getFiles (dir) {
return new Promise((resolve, reject) => {
filewalker(dir, (err, files) => {
@ -85,4 +99,77 @@ function copyAsync (fsrc, fdst) {
})
}
export default {getFiles, promiseExec, askAsync, insertDB, copyAsync}
async function getInfos (file) {
let formatData = await promiseExec(`ffprobe -i "${file}" -show_entries format -v quiet -of json`)
let parsed = JSON.parse(formatData.stdout)
if (!parsed || !parsed.format || !parsed.format.duration) throw new Error('Failed to parse metadata!')
parsed = parsed.format
let data = {
file,
duration: parseFloat(parsed.duration)
}
if (Math.floor(data.duration) === 0) throw new Error('Invalid file type!')
if (parsed.tags) {
for (let k in parsed.tags) {
let tagtype = k.toLowerCase()
let value = parsed.tags[k]
if (tagtype === 'date') tagtype = 'year'
if (tagtype === 'track' || tagtype === 'year') {
if (value.indexOf('/') !== -1) {
value = value.split('/')[0]
}
value = parseInt(value)
}
data[tagtype] = value
}
}
if (!data.title) {
let parsed = path.parse(file)
data.title = parsed.name
}
return data
}
async function setMetadata (file, meta, source, cleanup = false) {
if (!meta || !meta['title'] || meta.title === '') throw new Error('Invalid metadata provided')
let sanit = []
let dbq = {}
for (let key in meta) {
if (supportedMetadata.indexOf(key) === -1) continue
let val = meta[key]
if ((key === 'artist' || key === 'title') && (!val || val === '')) continue
dbq[key] = val
if (key === 'year') key = 'date'
sanit.push('-metadata ' + key + '="' + val + '"')
}
if (!sanit.length) throw new Error('Invalid metadata provided')
if (!source) {
let p = path.parse(file)
source = path.join(os.tmpdir(), '.retmp' + p.ext)
cleanup = true
await copyAsync(file, source)
await fs.unlink(file)
}
await promiseExec(`ffmpeg -i "${source}" ${sanit.join(' ')} -codec copy "${file}"`)
if (cleanup) {
await fs.unlink(source)
}
return { file, dbq }
}
export default { getFiles, promiseExec, askAsync, insertDB, updateDB,
copyAsync, getInfos, setMetadata, supportedMetadata }

View File

@ -93,44 +93,4 @@ function fetchVideo (data) {
})
}
async function getInfos (file) {
let formatData = await asn.promiseExec(`ffprobe -i "${file}" -show_entries format -v quiet -of json`)
let parsed = JSON.parse(formatData.stdout)
if (!parsed || !parsed.format || !parsed.format.duration) throw new Error('Failed to parse metadata!')
parsed = parsed.format
let data = {
file,
duration: parseFloat(parsed.duration)
}
if (Math.floor(data.duration) === 0) throw new Error('Invalid file type!')
if (parsed.tags) {
for (let k in parsed.tags) {
let tagtype = k.toLowerCase()
let value = parsed.tags[k]
if (tagtype === 'date') tagtype = 'year'
if (tagtype === 'track' || tagtype === 'year') {
if (value.indexOf('/') !== -1) {
value = value.split('/')[0]
}
value = parseInt(value)
}
data[tagtype] = value
}
}
if (!data.title) {
let parsed = path.parse(file)
data.title = parsed.name
}
return data
}
export default {parseTitle, getVideoInfo, fetchVideo, getInfos}
export default { parseTitle, getVideoInfo, fetchVideo }

View File

@ -51,7 +51,7 @@ async function handlePassed (db, fpath) {
let trackinf
try {
trackinf = await dl.getInfos(filePath)
trackinf = await asn.getInfos(filePath)
} catch (e) {
trackinf = await interactive(filePath, db)
}
@ -93,7 +93,7 @@ async function run () {
// if (cleanTrackData.length > 10) break // debug purposes
process.stdout.write(`\rProcessing file ${parseInt(i) + 1} of ${files.length}.. (${skips} skipped)`)
try {
let fd = await dl.getInfos(file)
let fd = await asn.getInfos(file)
cleanTrackData.push(fd)
} catch (e) {
skips++

View File

@ -49,15 +49,13 @@ async function download (furl) {
}
let fn = path.join(musicdir, filename)
await asn.promiseExec(`ffmpeg -i "${file.source}" -metadata artist="${clean.artist}" -metadata title="${clean.title}" -codec copy "${fn}"`)
await fs.unlink(file.source)
await asn.setMetadata(fn, clean, file.source, true)
let addAnsw = await asn.askAsync(rl, `Would you like to add it to the database now? [N/y] ? `)
if (addAnsw && addAnsw.trim().toLowerCase().indexOf('y') === 0) {
// Add to database
try {
let verify = await dl.getInfos(fn)
let verify = await asn.getInfos(fn)
await asn.insertDB(await dbPromise, verify)
} catch (e) {
console.warn('=!= Add to database failed!')

View File

@ -10,6 +10,8 @@ import ffmpeg from 'fluent-ffmpeg'
import { user, userMiddleware } from './user'
import { dbPromise } from './database'
import asn from './common/async'
import playlist from './playlist'
import lastfm from './lastfm'
@ -185,6 +187,20 @@ router.get('/track/:id', userMiddleware, async (req, res, next) => {
res.jsonp(track)
})
router.post('/track/:id', userMiddleware, async (req, res, next) => {
let id = req.params.id
let meta = req.body
let db = await dbPromise
let track = await db.get('SELECT file FROM Track WHERE id = ?', id)
if (!track) throw new Error('404 track not found')
let m = await asn.setMetadata(track.file, meta)
await asn.updateDB(db, id, m.dbq)
res.jsonp(m)
})
// --------- //
// PLAYLISTS //
// --------- //