1.1.14 - importing and what not
This commit is contained in:
parent
def6d5286d
commit
b0ebe66d54
@ -27,8 +27,8 @@
|
||||
permanent
|
||||
fixed
|
||||
app
|
||||
mini-variant
|
||||
expand-on-hover
|
||||
:mini-variant='!this.$root.settings.sidebarOpen'
|
||||
:expand-on-hover='!this.$root.settings.sidebarOpen'
|
||||
><v-list nav dense>
|
||||
|
||||
<!-- Profile -->
|
||||
@ -127,6 +127,16 @@
|
||||
<v-list-item-title>{{$t('Downloads')}}</v-list-item-title>
|
||||
</v-list-item>
|
||||
|
||||
<!-- Importer -->
|
||||
<v-list-item link to='/importer'>
|
||||
<v-list-item-icon>
|
||||
<v-icon v-if='!$root.importer.done && !$root.importer.active'>mdi-import</v-icon>
|
||||
<v-icon v-if='$root.importer.done' color='primary'>mdi-check</v-icon>
|
||||
<v-progress-circular indeterminate style='top: -8px' size='42' v-if='$root.importer.active'></v-progress-circular>
|
||||
</v-list-item-icon>
|
||||
<v-list-item-title>{{$t('Importer')}}</v-list-item-title>
|
||||
</v-list-item>
|
||||
|
||||
<!-- About -->
|
||||
<v-list-item link to='/about'>
|
||||
<v-list-item-icon>
|
||||
@ -243,8 +253,7 @@
|
||||
<v-spacer></v-spacer>
|
||||
|
||||
<!-- Volume -->
|
||||
<v-col cols='auto' class='d-none d-sm-flex px-2' @click.stop>
|
||||
|
||||
<v-col cols='auto' class='d-none d-sm-flex px-2' @click.stop ref='volumeBar'>
|
||||
<div style='width: 180px;' class='d-flex'>
|
||||
<v-slider
|
||||
dense
|
||||
@ -445,6 +454,23 @@ export default {
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
//Scroll on volume
|
||||
this.$refs.volumeBar.addEventListener('wheel', e => {
|
||||
//Volup
|
||||
if (e.deltaY < 0) {
|
||||
if (this.volume + 0.05 > 1)
|
||||
this.volume = 1;
|
||||
else
|
||||
this.volume += 0.05;
|
||||
} else {
|
||||
//Voldown
|
||||
if (this.volume - 0.05 < 0)
|
||||
this.volume = 0;
|
||||
else
|
||||
this.volume -= 0.05;
|
||||
}
|
||||
});
|
||||
|
||||
//onClick for footer
|
||||
this.$refs.footer.addEventListener('click', () => {
|
||||
if (this.$root.track) this.showPlayer = true;
|
||||
|
@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<div>
|
||||
|
||||
<v-card max-width='175px' max-height='210px' @click='play' :loading='loading' elevation='0' color='transparent'>
|
||||
<v-card max-width='175px' max-height='220px' height='220px' @click='play' :loading='loading' elevation='0' color='transparent'>
|
||||
<v-img :src='stl.cover.thumb'>
|
||||
</v-img>
|
||||
|
||||
<div class='pa-2 text-subtitle-2 text-center text-truncate'>{{stl.title}}</div>
|
||||
<div class='pa-2 text-subtitle-2'>{{stl.subtitle}}</div>
|
||||
</v-card>
|
||||
|
||||
</div>
|
||||
|
@ -12,6 +12,7 @@ import Settings from '@/views/Settings.vue';
|
||||
import DeezerPage from '@/views/DeezerPage.vue';
|
||||
import DownloadsPage from '@/views/DownloadsPage.vue';
|
||||
import About from '@/views/About.vue';
|
||||
import Importer from '@/views/Importer.vue';
|
||||
|
||||
Vue.use(VueRouter);
|
||||
|
||||
@ -79,6 +80,10 @@ const routes = [
|
||||
{
|
||||
path: '/about',
|
||||
component: About
|
||||
},
|
||||
{
|
||||
path: '/importer',
|
||||
component: Importer
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -146,5 +146,12 @@
|
||||
"Collaborative": "Collaborative",
|
||||
"Edit playlist": "Edit playlist",
|
||||
"Save": "Save",
|
||||
"Edit": "Edit"
|
||||
"Edit": "Edit",
|
||||
"Importer": "Importer",
|
||||
"Enter URL": "Enter URL",
|
||||
"Currently only Spotify is supported and limited to 100 tracks.": "Currently only Spotify is supported and limited to 100 tracks.",
|
||||
"Import into playlist": "Import into playlist",
|
||||
"Keep sidebar open": "Keep sidebar open",
|
||||
"WARNING: Might require reload to work properly!": "WARNING: Might require reload to work properly!",
|
||||
"An error occured, URL might be invalid or unsupported.": "An error occured, URL might be invalid or unsupported."
|
||||
}
|
@ -106,6 +106,14 @@ new Vue({
|
||||
}
|
||||
},
|
||||
|
||||
//Importer
|
||||
importer: {
|
||||
active: false,
|
||||
done: false,
|
||||
error: false,
|
||||
tracks: []
|
||||
},
|
||||
|
||||
//Used to prevent double listen logging
|
||||
logListenId: null,
|
||||
|
||||
@ -586,6 +594,27 @@ new Vue({
|
||||
this.seek(data.position);
|
||||
});
|
||||
|
||||
//Importer
|
||||
|
||||
//Start
|
||||
this.sockets.subscribe('importerInit', (data) => {
|
||||
this.importer = data;
|
||||
});
|
||||
//New track imported
|
||||
this.sockets.subscribe('importerTrack', (data) => {
|
||||
this.importer.tracks.push(data);
|
||||
});
|
||||
//Mark as done
|
||||
this.sockets.subscribe('importerDone', () => {
|
||||
this.importer.active = false;
|
||||
this.importer.done = true;
|
||||
});
|
||||
this.sockets.subscribe('importerError', () => {
|
||||
this.importer.error = true;
|
||||
this.importer.active = false;
|
||||
this.importer.done = false;
|
||||
});
|
||||
|
||||
r();
|
||||
},
|
||||
|
||||
@ -605,6 +634,9 @@ new Vue({
|
||||
document.addEventListener('keyup', (e) => {
|
||||
//Don't handle keystrokes in text fields
|
||||
if (e.target.tagName == "INPUT") return;
|
||||
//Don't handle if specials
|
||||
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) return;
|
||||
|
||||
//K toggle playback
|
||||
if (e.code == "KeyK" || e.code == "Space") this.$root.toggle();
|
||||
//L +10s (from YT)
|
||||
|
@ -100,23 +100,25 @@
|
||||
</v-btn>
|
||||
|
||||
<!-- Volume -->
|
||||
<v-slider
|
||||
min='0.00'
|
||||
:prepend-icon='$root.muted ? "mdi-volume-off" : "mdi-volume-high"'
|
||||
max='1.00'
|
||||
step='0.01'
|
||||
v-model='$root.volume'
|
||||
class='px-8'
|
||||
style='padding-top: 2px;'
|
||||
@change='updateVolume'
|
||||
@click:prepend='$root.toggleMute()'
|
||||
>
|
||||
<template v-slot:append>
|
||||
<div style='position: absolute; padding-top: 4px;'>
|
||||
{{Math.round($root.volume * 100)}}%
|
||||
</div>
|
||||
</template>
|
||||
</v-slider>
|
||||
<div ref='volumeBar' style='width: 100%;'>
|
||||
<v-slider
|
||||
min='0.00'
|
||||
:prepend-icon='$root.muted ? "mdi-volume-off" : "mdi-volume-high"'
|
||||
max='1.00'
|
||||
step='0.01'
|
||||
v-model='$root.volume'
|
||||
class='px-8'
|
||||
style='padding-top: 2px;'
|
||||
@change='updateVolume'
|
||||
@click:prepend='$root.toggleMute()'
|
||||
>
|
||||
<template v-slot:append>
|
||||
<div style='position: absolute; padding-top: 4px;'>
|
||||
{{Math.round($root.volume * 100)}}%
|
||||
</div>
|
||||
</template>
|
||||
</v-slider>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -328,6 +330,22 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
//Scroll on volume
|
||||
this.$refs.volumeBar.addEventListener('wheel', e => {
|
||||
//Volup
|
||||
if (e.deltaY < 0) {
|
||||
if (this.$root.volume + 0.05 > 1)
|
||||
this.$root.volume = 1;
|
||||
else
|
||||
this.$root.volume += 0.05;
|
||||
} else {
|
||||
//Voldown
|
||||
if (this.$root.volume - 0.05 < 0)
|
||||
this.$root.volume = 0;
|
||||
else
|
||||
this.$root.volume -= 0.05;
|
||||
}
|
||||
});
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
|
86
app/client/src/views/Importer.vue
Normal file
86
app/client/src/views/Importer.vue
Normal file
@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<div>
|
||||
|
||||
<h1>{{$t("Importer")}}</h1><br>
|
||||
<span class='text-h6'>
|
||||
<v-icon right color='warning' class='mr-2'>mdi-alert</v-icon>
|
||||
{{$t("Currently only Spotify is supported and limited to 100 tracks.")}}
|
||||
</span>
|
||||
<br>
|
||||
<!-- URL entry and buttons -->
|
||||
<div class='d-flex mt-4' v-if='!$root.importer.done && !$root.importer.active && !$root.importer.error'>
|
||||
<v-text-field
|
||||
v-model=input
|
||||
:label='$t("Enter URL")'
|
||||
:rules='[valid]'
|
||||
></v-text-field>
|
||||
<v-btn class='mx-2 mt-4' color='primary' :disabled='!valid' @click='start("import")'>
|
||||
<v-icon left>mdi-playlist-plus</v-icon>
|
||||
{{$t("Import into playlist")}}
|
||||
</v-btn>
|
||||
<v-btn class='mx-2 mt-4' color='green' :disabled='!valid' @click='start("download")'>
|
||||
<v-icon left>mdi-download</v-icon>
|
||||
{{$t("Download")}}
|
||||
</v-btn>
|
||||
</div>
|
||||
<!-- Tracks -->
|
||||
<div class='mt-4' v-if='$root.importer.done || $root.importer.active'>
|
||||
<h2 class='mb-2'>Tracks:</h2>
|
||||
<v-list>
|
||||
<v-list-item v-for='(track, i) in $root.importer.tracks' :key='i'>
|
||||
<v-list-item-avatar>
|
||||
<v-img :src='track.art'></v-img>
|
||||
</v-list-item-avatar>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>{{track.title}}</v-list-item-title>
|
||||
<v-list-item-subtitle>{{track.artist}}</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
<v-list-item-action>
|
||||
<v-icon v-if='track.ok' color='green'>mdi-check</v-icon>
|
||||
<v-icon v-if='!track.ok' color='red'></v-icon>
|
||||
</v-list-item-action>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</div>
|
||||
|
||||
<!-- Error -->
|
||||
<div v-if='$root.importer.error' class='text-center mt-4'>
|
||||
<h2>{{$t("An error occured, URL might be invalid or unsupported.")}}</h2>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Importer',
|
||||
data() {
|
||||
return {
|
||||
input: null,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async start(type) {
|
||||
await this.$axios.post('/import', {url: this.input, type});
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
valid() {
|
||||
let i = this.input || '';
|
||||
return i.startsWith('https://open.spotify.com/playlist/') && !i.includes(' ');
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
},
|
||||
destroyed() {
|
||||
//If done
|
||||
if (this.$root.importer.done || this.$root.importer.error) {
|
||||
this.$root.importer.done = false;
|
||||
this.$root.importer.active = false;
|
||||
this.$root.importer.error = false;
|
||||
this.$root.importer.tracks = [];
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
@ -144,7 +144,6 @@
|
||||
<v-list-item-title class='pl-2'>{{$t("Select primary color")}}</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
<!-- Autocomplete -->
|
||||
<v-list-item>
|
||||
<v-list-item-action>
|
||||
@ -154,6 +153,17 @@
|
||||
<v-list-item-title>{{$t("Show autocomplete in search")}}</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
<!-- Keep sidebar open -->
|
||||
<v-list-item>
|
||||
<v-list-item-action>
|
||||
<v-checkbox v-model='$root.settings.sidebarOpen' class='pl-2'></v-checkbox>
|
||||
</v-list-item-action>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>{{$t("Keep sidebar open")}}</v-list-item-title>
|
||||
<v-list-item-subtitle>{{$t("WARNING: Might require reload to work properly!")}}</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
|
||||
<!-- Accounts -->
|
||||
<v-subheader>{{$t("Integrations")}}</v-subheader>
|
||||
|
145
app/package-lock.json
generated
145
app/package-lock.json
generated
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "freezer",
|
||||
"version": "1.1.9",
|
||||
"version": "1.1.13",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@ -206,11 +206,11 @@
|
||||
"integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA=="
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.19.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz",
|
||||
"integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==",
|
||||
"version": "0.21.1",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
|
||||
"integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
|
||||
"requires": {
|
||||
"follow-redirects": "1.5.10"
|
||||
"follow-redirects": "^1.10.0"
|
||||
}
|
||||
},
|
||||
"backo2": {
|
||||
@ -298,6 +298,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"boolbase": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
@ -379,6 +384,32 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"cheerio": {
|
||||
"version": "1.0.0-rc.5",
|
||||
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.5.tgz",
|
||||
"integrity": "sha512-yoqps/VCaZgN4pfXtenwHROTp8NG6/Hlt4Jpz2FEP0ZJQ+ZUkVDd0hAPDNKhj3nakpfPt/CNs57yEtxD1bXQiw==",
|
||||
"requires": {
|
||||
"cheerio-select-tmp": "^0.1.0",
|
||||
"dom-serializer": "~1.2.0",
|
||||
"domhandler": "^4.0.0",
|
||||
"entities": "~2.1.0",
|
||||
"htmlparser2": "^6.0.0",
|
||||
"parse5": "^6.0.0",
|
||||
"parse5-htmlparser2-tree-adapter": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"cheerio-select-tmp": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/cheerio-select-tmp/-/cheerio-select-tmp-0.1.1.tgz",
|
||||
"integrity": "sha512-YYs5JvbpU19VYJyj+F7oYrIE2BOll1/hRU7rEy/5+v9BzkSo3bK81iAeeQEMI92vRIxz677m72UmJUiVwwgjfQ==",
|
||||
"requires": {
|
||||
"css-select": "^3.1.2",
|
||||
"css-what": "^4.0.0",
|
||||
"domelementtype": "^2.1.0",
|
||||
"domhandler": "^4.0.0",
|
||||
"domutils": "^2.4.4"
|
||||
}
|
||||
},
|
||||
"color": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz",
|
||||
@ -502,6 +533,23 @@
|
||||
"which": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"css-select": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/css-select/-/css-select-3.1.2.tgz",
|
||||
"integrity": "sha512-qmss1EihSuBNWNNhHjxzxSfJoFBM/lERB/Q4EnsJQQC62R2evJDW481091oAdOr9uh46/0n4nrg0It5cAnj1RA==",
|
||||
"requires": {
|
||||
"boolbase": "^1.0.0",
|
||||
"css-what": "^4.0.0",
|
||||
"domhandler": "^4.0.0",
|
||||
"domutils": "^2.4.3",
|
||||
"nth-check": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"css-what": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/css-what/-/css-what-4.0.0.tgz",
|
||||
"integrity": "sha512-teijzG7kwYfNVsUh2H/YN62xW3KK9YhXEgSlbxMlcyjPNvdKJqFx5lrwlJgoFP1ZHlB89iGDlo/JyshKeRhv5A=="
|
||||
},
|
||||
"dashdash": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
|
||||
@ -510,14 +558,6 @@
|
||||
"assert-plus": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"deep-is": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
|
||||
@ -562,6 +602,39 @@
|
||||
"esutils": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"dom-serializer": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.2.0.tgz",
|
||||
"integrity": "sha512-n6kZFH/KlCrqs/1GHMOd5i2fd/beQHuehKdWvNNffbGHTr/almdhuVvTVFb3V7fglz+nC50fFusu3lY33h12pA==",
|
||||
"requires": {
|
||||
"domelementtype": "^2.0.1",
|
||||
"domhandler": "^4.0.0",
|
||||
"entities": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"domelementtype": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.1.0.tgz",
|
||||
"integrity": "sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w=="
|
||||
},
|
||||
"domhandler": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.0.0.tgz",
|
||||
"integrity": "sha512-KPTbnGQ1JeEMQyO1iYXoagsI6so/C96HZiFyByU3T6iAzpXn8EGEvct6unm1ZGoed8ByO2oirxgwxBmqKF9haA==",
|
||||
"requires": {
|
||||
"domelementtype": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"domutils": {
|
||||
"version": "2.4.4",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-2.4.4.tgz",
|
||||
"integrity": "sha512-jBC0vOsECI4OMdD0GC9mGn7NXPLb+Qt6KW1YDQzeQYRUFKmNG8lh7mO5HiELfr+lLQE7loDVI4QcAxV80HS+RA==",
|
||||
"requires": {
|
||||
"dom-serializer": "^1.0.1",
|
||||
"domelementtype": "^2.0.1",
|
||||
"domhandler": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"ecc-jsbn": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
|
||||
@ -692,6 +765,11 @@
|
||||
"ansi-colors": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"entities": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz",
|
||||
"integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w=="
|
||||
},
|
||||
"escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
@ -1002,12 +1080,9 @@
|
||||
"integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.5.10",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
|
||||
"integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
|
||||
"requires": {
|
||||
"debug": "=3.1.0"
|
||||
}
|
||||
"version": "1.13.1",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.1.tgz",
|
||||
"integrity": "sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg=="
|
||||
},
|
||||
"forever-agent": {
|
||||
"version": "0.6.1",
|
||||
@ -1119,6 +1194,17 @@
|
||||
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
|
||||
"dev": true
|
||||
},
|
||||
"htmlparser2": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.0.0.tgz",
|
||||
"integrity": "sha512-numTQtDZMoh78zJpaNdJ9MXb2cv5G3jwUoe3dMQODubZvLoGvTE/Ofp6sHvH8OGKcN/8A47pGLi/k58xHP/Tfw==",
|
||||
"requires": {
|
||||
"domelementtype": "^2.0.1",
|
||||
"domhandler": "^4.0.0",
|
||||
"domutils": "^2.4.4",
|
||||
"entities": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"http-errors": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
|
||||
@ -1511,6 +1597,14 @@
|
||||
"node-addon-api": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"nth-check": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.0.tgz",
|
||||
"integrity": "sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q==",
|
||||
"requires": {
|
||||
"boolbase": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"oauth-sign": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
|
||||
@ -1569,6 +1663,19 @@
|
||||
"callsites": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"parse5": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz",
|
||||
"integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="
|
||||
},
|
||||
"parse5-htmlparser2-tree-adapter": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz",
|
||||
"integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==",
|
||||
"requires": {
|
||||
"parse5": "^6.0.1"
|
||||
}
|
||||
},
|
||||
"parseqs": {
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz",
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "freezer",
|
||||
"private": true,
|
||||
"version": "1.1.13",
|
||||
"version": "1.1.14",
|
||||
"description": "",
|
||||
"main": "background.js",
|
||||
"scripts": {
|
||||
@ -11,9 +11,10 @@
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"arg": "^4.1.3",
|
||||
"axios": "^0.19.2",
|
||||
"axios": "^0.21.1",
|
||||
"browser-id3-writer": "^4.4.0",
|
||||
"chalk": "^4.1.0",
|
||||
"cheerio": "^1.0.0-rc.5",
|
||||
"compare-versions": "^3.6.0",
|
||||
"discord-rpc": "^3.1.4",
|
||||
"express": "^4.17.1",
|
||||
|
@ -10,7 +10,6 @@ const sanitize = require('sanitize-filename');
|
||||
const ID3Writer = require('browser-id3-writer');
|
||||
const Metaflac = require('metaflac-js2');
|
||||
const { Track, Lyrics, DeezerImage } = require('./definitions');
|
||||
const { throwDeprecation } = require('process');
|
||||
|
||||
let deezer;
|
||||
|
||||
@ -377,7 +376,7 @@ class DownloadThread {
|
||||
async tagTrack(path) {
|
||||
let cover;
|
||||
try {
|
||||
cover = await this.downloadCover(this.track.albumArt.hash, 'cover', this.settings.coverResolution);
|
||||
cover = await this.downloadCover(DeezerImage.url(this.track.albumArt.hash, 'cover', this.settings.coverResolution), 'cover', this.settings.coverResolution);
|
||||
} catch (e) {}
|
||||
|
||||
//Genre tag
|
||||
|
94
app/src/importer.js
Normal file
94
app/src/importer.js
Normal file
@ -0,0 +1,94 @@
|
||||
const {EventEmitter} = require('events');
|
||||
const axios = require('axios');
|
||||
const cheerio = require('cheerio');
|
||||
const logger = require('./winston');
|
||||
|
||||
class Importer extends EventEmitter {
|
||||
constructor(deezer) {
|
||||
super();
|
||||
this.deezer = deezer;
|
||||
this.tracks = [];
|
||||
}
|
||||
|
||||
//Conver spotify URL to URI
|
||||
async getSpotifyURI(inputUrl) {
|
||||
let url = new URL(inputUrl);
|
||||
return 'spotify' + url.pathname.replace(new RegExp('/', 'g'), ':');
|
||||
}
|
||||
|
||||
//Import spotify playlist
|
||||
async importSpotifyPlaylist(url) {
|
||||
//Clean
|
||||
this.tracks = [];
|
||||
try {
|
||||
//Fetch
|
||||
let uri = await this.getSpotifyURI(url);
|
||||
let spotifyData = await Spotify.getEmbedData(uri);
|
||||
if (!spotifyData.tracks.items) throw Error("No items!");
|
||||
|
||||
for (let track of spotifyData.tracks.items) {
|
||||
//Output track
|
||||
let out = new ImporterTrack(
|
||||
track.track.name,
|
||||
track.track.artists.map(a => a.name).join(', '),
|
||||
(track.track.album.images.length > 0) ? track.track.album.images[0].url : null
|
||||
);
|
||||
//Match
|
||||
try {
|
||||
let deezerData = await this.deezer.callPublicApi('track', 'isrc:' + track.track.external_ids.isrc);
|
||||
if (deezerData.id.toString()) {
|
||||
//Found track
|
||||
out.id = deezerData.id.toString();
|
||||
out.ok = true;
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error(`Error importing: Spotify: ${track.track.id} ${e}`);
|
||||
}
|
||||
//Send back
|
||||
this.emit('imported', out);
|
||||
this.tracks.push(out);
|
||||
}
|
||||
//Emit done with playlist details
|
||||
this.emit('done', {
|
||||
title: spotifyData.name,
|
||||
description: spotifyData.description,
|
||||
tracks: this.tracks
|
||||
});
|
||||
} catch (e) {
|
||||
//Emit error on error
|
||||
logger.error(`Error importing: ${e}`);
|
||||
this.emit('error', `${e}`);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Track only with most important metadata for UI
|
||||
class ImporterTrack {
|
||||
constructor(title, artist, art) {
|
||||
this.id = null;
|
||||
this.title = title;
|
||||
this.artist = artist;
|
||||
this.art = art;
|
||||
this.ok = false;
|
||||
}
|
||||
}
|
||||
|
||||
class Spotify {
|
||||
constructor() {}
|
||||
|
||||
//Fetch JSON data from embeded spotify
|
||||
static async getEmbedData(uri) {
|
||||
//Fetch
|
||||
let url = `https://embed.spotify.com/?uri=${uri}`;
|
||||
let res = await axios.get(url);
|
||||
const $ = cheerio.load(res.data);
|
||||
|
||||
//Get JSON
|
||||
let data = JSON.parse(decodeURIComponent($('#resource').html()));
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {Importer};
|
@ -6,7 +6,6 @@ const logger = require('./winston');
|
||||
class Integrations extends EventEmitter {
|
||||
|
||||
//LastFM, Discord etc
|
||||
|
||||
constructor(settings) {
|
||||
super();
|
||||
|
||||
@ -67,7 +66,7 @@ class Integrations extends EventEmitter {
|
||||
//Connect to discord client
|
||||
connectDiscord() {
|
||||
//Don't steal, k ty
|
||||
const CLIENTID = '759835951450292324';
|
||||
const CLIENTID = '803292927227854878';
|
||||
|
||||
this.discordReady = false;
|
||||
DiscordRPC.register(CLIENTID);
|
||||
|
@ -10,6 +10,7 @@ const {Settings} = require('./settings');
|
||||
const {Track, Album, Artist, Playlist, DeezerProfile, SearchResults, DeezerLibrary, DeezerPage, Lyrics} = require('./definitions');
|
||||
const {DownloadManager} = require('./downloads');
|
||||
const {Integrations} = require('./integrations');
|
||||
const {Importer} = require('./importer');
|
||||
|
||||
let settings;
|
||||
let deezer;
|
||||
@ -464,6 +465,69 @@ app.post('/log', async (req, res) => {
|
||||
res.status(200).end();
|
||||
});
|
||||
|
||||
//Importer
|
||||
app.post('/import', async (req, res) => {
|
||||
//Importer status
|
||||
sockets.forEach(s => s.emit('importerInit', {
|
||||
done: false,
|
||||
active: true,
|
||||
error: false,
|
||||
tracks: []
|
||||
}));
|
||||
let type = req.body.type;
|
||||
|
||||
//Create importer
|
||||
let importer = new Importer(deezer);
|
||||
//Error
|
||||
importer.on('error', t => {
|
||||
sockets.forEach(s => s.emit('importerError'));
|
||||
})
|
||||
//New track imported
|
||||
importer.on('imported', t => {
|
||||
sockets.forEach(s => s.emit('importerTrack', t));
|
||||
});
|
||||
//Finished
|
||||
importer.on('done', async (i) => {
|
||||
//Create playlist
|
||||
let playlistRaw = await deezer.callApi('playlist.create', {
|
||||
description: i.description,
|
||||
title: i.title,
|
||||
status: 1,
|
||||
songs: i.tracks.map(t => [parseInt(t.id, 10)])
|
||||
});
|
||||
//Download
|
||||
if (type == 'download') {
|
||||
//Fetch playlist
|
||||
let data = await deezer.callApi('deezer.pagePlaylist', {
|
||||
playlist_id: playlistRaw.results.toString(),
|
||||
lang: settings.contentLanguage,
|
||||
nb: 10000,
|
||||
start: 0,
|
||||
tags: true
|
||||
});
|
||||
let playlist = new Playlist(data.results.DATA, data.results.SONGS);
|
||||
//Enqueue
|
||||
await downloadManager.addBatch({
|
||||
tracks: playlist.tracks,
|
||||
quality: settings.downloadsQuality,
|
||||
playlistName: i.title
|
||||
});
|
||||
//Delete
|
||||
await deezer.callApi('playlist.delete', {playlist_id: parseInt(playlist.id.toString(),10)});
|
||||
downloadManager.start();
|
||||
}
|
||||
|
||||
//Send to UI
|
||||
sockets.forEach(s => {
|
||||
s.emit('importerDone');
|
||||
});
|
||||
})
|
||||
|
||||
importer.importSpotifyPlaylist(req.body.url);
|
||||
|
||||
res.status(200).end();
|
||||
});
|
||||
|
||||
//Last.FM authorization callback
|
||||
app.get('/lastfm', async (req, res) => {
|
||||
//Got token
|
||||
|
@ -43,6 +43,7 @@ class Settings {
|
||||
this.forceWhiteTrayIcon = false;
|
||||
this.contentLanguage = 'en';
|
||||
this.contentCountry = 'US';
|
||||
this.sidebarOpen = false;
|
||||
}
|
||||
|
||||
//Based on electorn app.getPath
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "freezer",
|
||||
"private": true,
|
||||
"version": "1.1.13",
|
||||
"version": "1.1.14",
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"pack": "electron-builder --dir",
|
||||
|
Loading…
Reference in New Issue
Block a user