track metadata editing
This commit is contained in:
parent
a70cf0e508
commit
bcc708ded9
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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()
|
||||
|
@ -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 }
|
||||
|
@ -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 }
|
||||
|
@ -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++
|
||||
|
@ -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!')
|
||||
|
@ -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 //
|
||||
// --------- //
|
||||
|
Loading…
Reference in New Issue
Block a user