修复音乐扫描;本地音乐支持读写ID3
parent
05e6e37021
commit
8efd8f2257
File diff suppressed because one or more lines are too long
10
css/app.scss
10
css/app.scss
|
@ -143,7 +143,7 @@ table {overflow:auto;display:table;width:100%;line-height:2.5rem;
|
|||
}
|
||||
|
||||
// 功能模块
|
||||
.module {flex:1;display:flex;flex-flow:column wrap;}
|
||||
.module {position:relative;flex:1;display:flex;flex-flow:column wrap;}
|
||||
|
||||
|
||||
|
||||
|
@ -194,7 +194,13 @@ table {overflow:auto;display:table;width:100%;line-height:2.5rem;
|
|||
|
||||
|
||||
|
||||
|
||||
.do-mod-contextmenu {width:145px;height:auto;padding:8px 0;line-height:35px;font-size:1.3rem;
|
||||
li {overflow:hidden;width:100%;height:35px;padding:0 10px;@include ts(background);cursor:default;
|
||||
&:hover {background:nth($cp, 1)}
|
||||
i {padding:0 3px;font-size:1.6rem;vertical-align:bottom;}
|
||||
}
|
||||
}
|
||||
.do-layer .layer-box.do-mod-contextmenu__fixed {padding:0}
|
||||
|
||||
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -95,6 +95,27 @@
|
|||
}
|
||||
|
||||
|
||||
.edit-form {position:absolute;left:0;top:0;z-index:90;display:flex;justify-content:center;align-items:center;width:100%;height:100%;
|
||||
|
||||
|
||||
.form {position:relative;display:flex;flex-flow:column wrap;flex:0 40rem;width:5rem;height:auto;padding:.5rem 4rem 2rem;background:#fff;box-shadow:0 .5rem 2rem rgba(0, 0, 0, .2);
|
||||
|
||||
.section {display:flex;flex:1;justify-content:center;align-items:center;margin:1rem 0;
|
||||
|
||||
&.title {line-height:2;font-size:1.6rem;
|
||||
i {position:absolute;right:1rem;top:1rem;color:nth($list: $cr, $n: 1);}
|
||||
i:hover {transform:scale(1.2);font-weight:bold}
|
||||
}
|
||||
|
||||
.label {flex:0 5rem;padding-right:2rem;color:nth($cgr, 1);text-align:right;}
|
||||
.field {flex:1}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -14,8 +14,8 @@ class AudioPlayer {
|
|||
constructor() {
|
||||
this.__PLAYER__ = new Audio()
|
||||
this.__IS_PLAYED__ = false
|
||||
this.__LIST__ = [] // 播放列表
|
||||
this.__CURR__ = -1 // 当前播放的歌曲的id
|
||||
this.__LIST__ = [] //播放列表
|
||||
this.__PLAY_MODE__ = 'all' // all | single | random
|
||||
this.__PLAYER__.volume = 0.7
|
||||
|
||||
|
@ -176,18 +176,17 @@ util.inherits(AudioPlayer, EventEmitter)
|
|||
export const ID3 = song => {
|
||||
let cmd = `ffprobe -v quiet -print_format json -show_entries format "${song}"`
|
||||
let pc = exec(cmd)
|
||||
let buf = []
|
||||
let buf = ''
|
||||
return new Promise((resolve, reject) => {
|
||||
pc.stdout.on('data', _ => {
|
||||
buf.push(_)
|
||||
buf += _
|
||||
})
|
||||
|
||||
pc.stderr.on('data', reject)
|
||||
|
||||
pc.stdout.on('close', _ => {
|
||||
let { format } = Buffer.from(buf)
|
||||
try {
|
||||
res = JSON.parse(res)
|
||||
let { format } = JSON.parse(buf)
|
||||
resolve({
|
||||
title: format.tags.TITLE || format.tags.title,
|
||||
album: format.tags.ALBUM || format.tags.album,
|
||||
|
|
|
@ -1 +1 @@
|
|||
const __STORE__={};function parse$And(_){let t="";for(let e in _){let i=_[e];switch(Anot.type(i)){case"object":if(i.$has){t+=`it.${e}.indexOf(${JSON.stringify(i.$has)}) > -1`;break}if(i.$in){t+=`${JSON.stringify(i.$in)}.indexOf(it.${e}) > -1`;break}if(i.$regex){t+=`${i.$regex}.test(it.${e})`;break}if(i.$lt||i.$lte){t+=`it.${e} <${i.$lte?"=":""} ${i.$lt||i.$lte}`,(i.$gt||i.$gte)&&(t+=` && it.${e} >${i.$gte?"=":""} ${i.$gt||i.$gte}`);break}if(i.$gt||i.$gte){t+=`it.${e} >${i.$gte?"=":""} ${i.$gt||i.$gte}`;break}if(i.$eq){t+=`it.${e} === ${i.$eq}`;break}default:t+=`it.${e} === ${JSON.stringify(_[e])}`}t+=" && "}return(t=t.slice(0,-4))||(t="true"),t}function parse$Or(_){let t="";return _.forEach(_=>{t+="(",t+=parse$And(_),t+=") || "}),t.slice(0,-4)}class AnotStore{constructor(_){Anot.hideProperty(this,"__name__",_),Anot.hideProperty(this,"__LAST_QUERY__",""),Anot.hideProperty(this,"__QUERY_HISTORY__",[]),__STORE__[_]||(__STORE__[_]=[],__STORE__[`${_}Dict`]={})}static collection(_){return new this(_)}__MAKE_FN__(_){let t="\n let result = [];\n let num = 0;\n for (let it of arr) {\n if(";return _.$or?t+=parse$Or(_.$or):t+=parse$And(_),t+="){\n result.push(it)\n num++\n if(limit > 0 && num >= limit){\n break\n }\n }\n }\n return result;",Function("arr","limit",t)}clear(_){this.__QUERY_HISTORY__=[],this.__LAST_QUERY__="",_&&(__STORE__[this.__name__]=[],__STORE__[`${this.__name__}Dict`]={})}getAll({filter:_,limit:t=[]}={}){const e=__STORE__[this.__name__];let i=[],r=!1;if(!e||!e.length)return i;if(t.length<1&&(t=[0]),t.length<2&&_&&(r=!0,t[0]>0&&t.unshift(0)),_){let n=JSON.stringify(_);if(this.__LAST_QUERY__===n)i=this.__QUERY_HISTORY__.slice.apply(this.__QUERY_HISTORY__,t);else{i=this.__MAKE_FN__(_)(e,r&&t[1]||0),r||(this.__LAST_QUERY__=n,this.__QUERY_HISTORY__=i,i=this.__QUERY_HISTORY__.slice.apply(this.__QUERY_HISTORY__,t))}}else i=e.slice.apply(e,t);return Anot.deepCopy(i)}get(_){const t=__STORE__[`${this.__name__}Dict`];return Anot.deepCopy(t[_])||null}count({filter:_}={}){return _?this.__LAST_QUERY__===JSON.stringify(_)?this.__QUERY_HISTORY__.length:this.getAll({filter:_,limit:[0]}).length:__STORE__[this.__name__].length}__INSERT__(_,t){let e=__STORE__[this.__name__],i=__STORE__[`${this.__name__}Dict`],r=_[t||"id"];i[r]?this.update(r,_):(e.push(_),i[r]=_)}insert(_,t){Array.isArray(_)||(_=[_]),_.forEach(_=>{this.__INSERT__(_,t)}),this.clear()}sort(_,t,e){let i="";t&&window.Intl&&(i+="\n let col = new Intl.Collator('zh')\n "),i+=e?"return arr.sort((b, a) => {":"return arr.sort((a, b) => {",i+=`\n let filter = function(val) {\n try {\n return val.${_} || ''\n } catch (err) {\n return ''\n }\n }\n `,t?window.Intl?i+="return col.compare(filter(a), filter(b))":i+="return (filter(a) + '').localeCompare(filter(b), 'zh')":i+="return filter(a) - filter(b)",i+="\n})",Function("arr",i).call(this,__STORE__[this.__name__]),this.clear()}update(_,t){let e=__STORE__[this.__name__],i=__STORE__[`${this.__name__}Dict`],r=i[_],n=e.indexOf(r);Object.assign(r,t),e.splice(n,1,r),i[_]=r}remove(_){let t=__STORE__[this.__name__],e=__STORE__[`${this.__name__}Dict`],i=e[_id],r=t.indexOf(i);t.splice(r,1),delete e[_id]}}Anot.store=window.store=AnotStore;export default AnotStore;
|
||||
const __STORE__={};function parse$And(_){let t="";for(let e in _){let i=_[e];switch(Anot.type(i)){case"object":if(i.$has){t+=`it.${e}.indexOf(${JSON.stringify(i.$has)}) > -1`;break}if(i.$in){t+=`${JSON.stringify(i.$in)}.indexOf(it.${e}) > -1`;break}if(i.$regex){t+=`${i.$regex}.test(it.${e})`;break}if(i.$lt||i.$lte){t+=`it.${e} <${i.$lte?"=":""} ${i.$lt||i.$lte}`,(i.$gt||i.$gte)&&(t+=` && it.${e} >${i.$gte?"=":""} ${i.$gt||i.$gte}`);break}if(i.$gt||i.$gte){t+=`it.${e} >${i.$gte?"=":""} ${i.$gt||i.$gte}`;break}if(i.$eq){t+=`it.${e} === ${i.$eq}`;break}default:t+=`it.${e} === ${JSON.stringify(_[e])}`}t+=" && "}return(t=t.slice(0,-4))||(t="true"),t}function parse$Or(_){let t="";return _.forEach(_=>{t+="(",t+=parse$And(_),t+=") || "}),t.slice(0,-4)}class AnotStore{constructor(_){Anot.hideProperty(this,"__name__",_),Anot.hideProperty(this,"__LAST_QUERY__",""),Anot.hideProperty(this,"__QUERY_HISTORY__",[]),__STORE__[_]||(__STORE__[_]=[],__STORE__[`${_}Dict`]={})}static collection(_){return new this(_)}__MAKE_FN__(_){let t="\n let result = [];\n let num = 0;\n for (let it of arr) {\n if(";return _.$or?t+=parse$Or(_.$or):t+=parse$And(_),t+="){\n result.push(it)\n num++\n if(limit > 0 && num >= limit){\n break\n }\n }\n }\n return result;",Function("arr","limit",t)}clear(_){this.__QUERY_HISTORY__=[],this.__LAST_QUERY__="",_&&(__STORE__[this.__name__]=[],__STORE__[`${this.__name__}Dict`]={})}getAll({filter:_,limit:t=[]}={}){const e=__STORE__[this.__name__];let i=[],r=!1;if(!e||!e.length)return i;if(t.length<1&&(t=[0]),t.length<2&&_&&(r=!0,t[0]>0&&t.unshift(0)),_){let n=JSON.stringify(_);if(this.__LAST_QUERY__===n)i=this.__QUERY_HISTORY__.slice.apply(this.__QUERY_HISTORY__,t);else{i=this.__MAKE_FN__(_)(e,r&&t[1]||0),r||(this.__LAST_QUERY__=n,this.__QUERY_HISTORY__=i,i=this.__QUERY_HISTORY__.slice.apply(this.__QUERY_HISTORY__,t))}}else i=e.slice.apply(e,t);return Anot.deepCopy(i)}get(_){const t=__STORE__[`${this.__name__}Dict`];return Anot.deepCopy(t[_])||null}count({filter:_}={}){return _?this.__LAST_QUERY__===JSON.stringify(_)?this.__QUERY_HISTORY__.length:this.getAll({filter:_,limit:[0]}).length:__STORE__[this.__name__].length}__INSERT__(_,t){let e=__STORE__[this.__name__],i=__STORE__[`${this.__name__}Dict`],r=_[t||"id"];i[r]?this.update(r,_):(e.push(_),i[r]=_)}insert(_,t){Array.isArray(_)||(_=[_]),_.forEach(_=>{this.__INSERT__(_,t)}),this.clear()}sort(_,t,e){let i="";t&&window.Intl&&(i+="\n let col = new Intl.Collator('zh')\n "),i+=e?"return arr.sort((b, a) => {":"return arr.sort((a, b) => {",i+=`\n let filter = function(val) {\n try {\n return val.${_} || ''\n } catch (err) {\n return ''\n }\n }\n `,t?window.Intl?i+="return col.compare(filter(a), filter(b))":i+="return (filter(a) + '').localeCompare(filter(b), 'zh')":i+="return filter(a) - filter(b)",i+="\n})",Function("arr",i).call(this,__STORE__[this.__name__]),this.clear()}update(_,t){let e=__STORE__[this.__name__],i=__STORE__[`${this.__name__}Dict`],r=i[_],n=e.indexOf(r);Object.assign(r,t),e.splice(n,1,r),i[_]=r}remove(_){let t=__STORE__[this.__name__],e=__STORE__[`${this.__name__}Dict`],i=e[_],r=t.indexOf(i);t.splice(r,1),delete e[_]}}Anot.store=window.store=AnotStore;export default AnotStore;
|
|
@ -8,7 +8,7 @@
|
|||
<link href="dist/css/elem-ui.css" rel="stylesheet">
|
||||
<link href="css/app.css" rel="stylesheet">
|
||||
<link href="css/modules.css" rel="stylesheet">
|
||||
<script>window.LIBS_BASE_URL = location.origin + '/dist'</script>
|
||||
<script>window.LIBS_BASE_URL = location.origin + '/dist';window.__ENV_LANG__ = 'zh'</script>
|
||||
<script type="module" src="js/app.js"></script>
|
||||
</head>
|
||||
<body class="do-fn-noselect">
|
||||
|
|
|
@ -59,7 +59,6 @@ Anot({
|
|||
volume: Anot.ls('volume') || 70,
|
||||
curr: {
|
||||
id: '',
|
||||
index: 0,
|
||||
title: '',
|
||||
artist: '',
|
||||
album: '',
|
||||
|
|
|
@ -28,7 +28,14 @@ export default Anot({
|
|||
state: {
|
||||
list: [],
|
||||
curr: '',
|
||||
__APP__: null
|
||||
editMode: false,
|
||||
form: {
|
||||
id: '',
|
||||
title: '',
|
||||
artist: '',
|
||||
album: '',
|
||||
path: ''
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
LS.insert(dbCache)
|
||||
|
@ -96,6 +103,7 @@ export default Anot({
|
|||
_P.then(next => {
|
||||
if (next) {
|
||||
Api.getSongInfoByHash(it.kgHash, it.albumId).then(json => {
|
||||
delete it.time
|
||||
it.album = json.album_name
|
||||
it.albumId = json.album_id
|
||||
it.kgHash = json.hash
|
||||
|
@ -105,6 +113,9 @@ export default Anot({
|
|||
this.list.set(idx, it)
|
||||
LS.insert(it)
|
||||
|
||||
SONIST.clear()
|
||||
SONIST.push(LS.getAll())
|
||||
|
||||
this.__APP__.updateCurr(it)
|
||||
this.__APP__.draw()
|
||||
|
||||
|
@ -123,11 +134,14 @@ export default Anot({
|
|||
el.textContent = '重新扫描'
|
||||
el = null
|
||||
if (this.__NEW_NUM__ > 0) {
|
||||
LS.sort('artist', true)
|
||||
dbCache = LS.getAll()
|
||||
this.list.clear()
|
||||
this.list.pushArray(dbCache)
|
||||
|
||||
SONIST.clear()
|
||||
SONIST.push(dbCache)
|
||||
|
||||
fs.echo(JSON.stringify(dbCache, '', 2), MUSIC_DB_PATH)
|
||||
dbCache = null
|
||||
}
|
||||
|
@ -172,6 +186,88 @@ export default Anot({
|
|||
ev.target.textContent = '正在扫描, 请稍候...'
|
||||
this.__checkSong__(ev.target)
|
||||
}
|
||||
},
|
||||
closeEdit() {
|
||||
this.editMode = false
|
||||
let song = this.list[this.__idx__].$model
|
||||
|
||||
Object.assign(song, {
|
||||
title: this.form.title,
|
||||
artist: this.form.artist,
|
||||
album: this.form.album
|
||||
})
|
||||
|
||||
this.list.set(this.__idx__, song)
|
||||
delete this.__idx__
|
||||
|
||||
let col = new Intl.Collator('zh')
|
||||
this.list.sort((a, b) => {
|
||||
return col.compare(a.artist, b.artist)
|
||||
})
|
||||
|
||||
LS.update(song.id, song)
|
||||
LS.sort('artist', true)
|
||||
|
||||
SONIST.clear()
|
||||
SONIST.push(LS.getAll())
|
||||
|
||||
fs.echo(JSON.stringify(LS.getAll(), '', 2), MUSIC_DB_PATH)
|
||||
},
|
||||
handleMenu(it, idx, ev) {
|
||||
let that = this
|
||||
|
||||
layer.open({
|
||||
type: 7,
|
||||
menubar: false,
|
||||
maskClose: true,
|
||||
fixed: true,
|
||||
extraClass: 'do-mod-contextmenu__fixed',
|
||||
offset: [ev.pageY, 'auto', 'auto', ev.pageX],
|
||||
shift: {
|
||||
top: ev.pageY,
|
||||
left: ev.pageX
|
||||
},
|
||||
content: `<ul class="do-mod-contextmenu" :click="onClick">
|
||||
<li data-key="del"><i class="do-icon-trash"></i>删除歌曲</li>
|
||||
<li data-key="edit"><i class="do-icon-edit"></i>编辑信息</li>
|
||||
</ul>`,
|
||||
onClick(ev) {
|
||||
if (ev.currentTarget === ev.target) {
|
||||
return
|
||||
}
|
||||
let target = ev.target
|
||||
let act = null
|
||||
if (target.nodeName === 'I') {
|
||||
target = target.parentNode
|
||||
}
|
||||
act = target.dataset.key
|
||||
this.close()
|
||||
if (act === 'del') {
|
||||
layer.confirm(
|
||||
'此操作只会将当前选中的歌曲从列表中移出<br>并不会将其从硬盘中删除!',
|
||||
`是否删除 (${it.title}) ?`,
|
||||
function() {
|
||||
this.close()
|
||||
that.list.splice(idx, 1)
|
||||
LS.remove(it.id)
|
||||
|
||||
SONIST.clear()
|
||||
SONIST.push(LS.getAll())
|
||||
|
||||
fs.echo(JSON.stringify(LS.getAll(), '', 2), MUSIC_DB_PATH)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
that.__idx__ = idx
|
||||
that.editMode = true
|
||||
that.form.id = it.id
|
||||
that.form.path = it.path.slice(7)
|
||||
that.form.title = it.title
|
||||
that.form.artist = it.artist
|
||||
that.form.album = it.album
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
<tr
|
||||
:class="{active: it.id === curr}"
|
||||
:for="it in list"
|
||||
:on-contextmenu="handleMenu(it, $index, $event)"
|
||||
:dblclick="play(it, $index)">
|
||||
<td class="ac"><i class="stat s-icon-music"></i></td>
|
||||
<td :text="it.title"></td>
|
||||
|
@ -28,4 +29,33 @@
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="edit-form" :if="editMode">
|
||||
<div class="form">
|
||||
<section class="section title">
|
||||
歌曲信息编辑
|
||||
<i class="do-icon-close" :click="closeEdit"></i>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<span class="field" :text="form.path"></span>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<span class="label">标题</span>
|
||||
<input class="field do-ui-input" :duplex="form.title">
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<span class="label">歌手</span>
|
||||
<input class="field do-ui-input" :duplex="form.artist">
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<span class="label">专辑</span>
|
||||
<input class="field do-ui-input" :duplex="form.album">
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
Reference in New Issue