Move Waveform editor to Vue 3 style.
This commit is contained in:
parent
3ed6a966b1
commit
59cbfbfc66
|
@ -8,8 +8,8 @@
|
||||||
</div>
|
</div>
|
||||||
</b-form-group>
|
</b-form-group>
|
||||||
</b-row>
|
</b-row>
|
||||||
<b-row class="mt-3">
|
<b-row class="mt-3 align-items-center">
|
||||||
<b-col md="8">
|
<b-col md="7">
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
<label for="waveform-zoom">
|
<label for="waveform-zoom">
|
||||||
|
@ -17,26 +17,27 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-fill mx-3">
|
<div class="flex-fill mx-3">
|
||||||
<b-form-input id="waveform-zoom" v-model="zoom" type="range" min="0" max="256" class="w-100"></b-form-input>
|
<b-form-input id="waveform-zoom" v-model.number="zoom" type="range" min="0" max="256"
|
||||||
|
class="w-100"></b-form-input>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</b-col>
|
</b-col>
|
||||||
<b-col md="4">
|
<b-col md="5">
|
||||||
<div class="inline-volume-controls d-flex align-items-center">
|
<div class="inline-volume-controls d-flex align-items-center">
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
<a class="btn btn-sm btn-outline-inverse py-0 px-3" href="#" @click.prevent="volume = 0">
|
<a class="btn btn-sm btn-outline-inverse" href="#" @click.prevent="volume = 0"
|
||||||
|
:title="$gettext('Mute')">
|
||||||
<icon icon="volume_mute"></icon>
|
<icon icon="volume_mute"></icon>
|
||||||
{{ $gettext('Mute') }}
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-fill mx-1">
|
<div class="flex-fill mx-1">
|
||||||
<input type="range" :title="langVolume" class="player-volume-range custom-range w-100" min="0" max="100"
|
<input type="range" :title="$gettext('Volume')" class="player-volume-range custom-range w-100"
|
||||||
step="1" v-model="volume">
|
min="0" max="100" step="1" v-model.number="volume">
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
<a class="btn btn-sm btn-outline-inverse py-0 px-3" href="#" @click.prevent="volume = 100">
|
<a class="btn btn-sm btn-outline-inverse" href="#" @click.prevent="volume = 100"
|
||||||
|
:title="$gettext('Full Volume')">
|
||||||
<icon icon="volume_up"></icon>
|
<icon icon="volume_up"></icon>
|
||||||
{{ $gettext('Full Volume') }}
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -45,128 +46,119 @@
|
||||||
</b-form-group>
|
</b-form-group>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import WaveSurfer from 'wavesurfer.js';
|
import WS from 'wavesurfer.js';
|
||||||
import timeline from 'wavesurfer.js/dist/plugin/wavesurfer.timeline.js';
|
import timeline from 'wavesurfer.js/dist/plugin/wavesurfer.timeline.js';
|
||||||
import regions from 'wavesurfer.js/dist/plugin/wavesurfer.regions.js';
|
import regions from 'wavesurfer.js/dist/plugin/wavesurfer.regions.js';
|
||||||
import getLogarithmicVolume from '~/functions/getLogarithmicVolume.js';
|
import getLogarithmicVolume from '~/functions/getLogarithmicVolume.js';
|
||||||
import store from 'store';
|
|
||||||
import Icon from './Icon';
|
import Icon from './Icon';
|
||||||
|
import {useStorage} from "@vueuse/core";
|
||||||
|
import {onMounted, onUnmounted, ref, watch} from "vue";
|
||||||
|
import {useAxios} from "~/vendor/axios";
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
name: 'Waveform',
|
audioUrl: String,
|
||||||
components: { Icon },
|
waveformUrl: String
|
||||||
props: {
|
});
|
||||||
audioUrl: String,
|
|
||||||
waveformUrl: String
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
wavesurfer: null,
|
|
||||||
zoom: 0,
|
|
||||||
volume: 0
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted () {
|
|
||||||
this.wavesurfer = WaveSurfer.create({
|
|
||||||
backend: 'MediaElement',
|
|
||||||
container: '#waveform',
|
|
||||||
waveColor: '#2196f3',
|
|
||||||
progressColor: '#4081CF',
|
|
||||||
plugins: [
|
|
||||||
timeline.create({
|
|
||||||
container: '#waveform-timeline',
|
|
||||||
primaryColor: '#222',
|
|
||||||
secondaryColor: '#888',
|
|
||||||
primaryFontColor: '#222',
|
|
||||||
secondaryFontColor: '#888'
|
|
||||||
}),
|
|
||||||
regions.create({
|
|
||||||
regions: []
|
|
||||||
})
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
this.wavesurfer.on('ready', () => {
|
const emit = defineEmits(['ready']);
|
||||||
this.$emit('ready');
|
|
||||||
});
|
|
||||||
|
|
||||||
this.axios.get(this.waveformUrl).then((resp) => {
|
let wavesurfer = null;
|
||||||
let waveform = resp.data;
|
|
||||||
if (waveform.data) {
|
|
||||||
this.wavesurfer.load(this.audioUrl, waveform.data);
|
|
||||||
} else {
|
|
||||||
this.wavesurfer.load(this.audioUrl);
|
|
||||||
}
|
|
||||||
}).catch((err) => {
|
|
||||||
console.error(err);
|
|
||||||
this.wavesurfer.load(this.audioUrl);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check webstorage for existing volume preference.
|
const volume = useStorage('player_volume', 55);
|
||||||
if (store.enabled && store.get('player_volume') !== undefined) {
|
const zoom = ref(0);
|
||||||
this.volume = store.get('player_volume', 55);
|
|
||||||
|
watch(zoom, (val) => {
|
||||||
|
wavesurfer?.zoom(val);
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(volume, (val) => {
|
||||||
|
wavesurfer?.setVolume(getLogarithmicVolume(val));
|
||||||
|
});
|
||||||
|
|
||||||
|
const {axios} = useAxios();
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
wavesurfer = WS.create({
|
||||||
|
backend: 'MediaElement',
|
||||||
|
container: '#waveform',
|
||||||
|
waveColor: '#2196f3',
|
||||||
|
progressColor: '#4081CF',
|
||||||
|
plugins: [
|
||||||
|
timeline.create({
|
||||||
|
container: '#waveform-timeline',
|
||||||
|
primaryColor: '#222',
|
||||||
|
secondaryColor: '#888',
|
||||||
|
primaryFontColor: '#222',
|
||||||
|
secondaryFontColor: '#888'
|
||||||
|
}),
|
||||||
|
regions.create({
|
||||||
|
regions: []
|
||||||
|
})
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
wavesurfer.on('ready', () => {
|
||||||
|
wavesurfer.setVolume(getLogarithmicVolume(volume.value));
|
||||||
|
|
||||||
|
emit('ready');
|
||||||
|
});
|
||||||
|
|
||||||
|
axios.get(props.waveformUrl).then((resp) => {
|
||||||
|
let waveformJson = resp?.data?.data ?? null;
|
||||||
|
if (waveformJson) {
|
||||||
|
wavesurfer.load(props.audioUrl, waveformJson);
|
||||||
|
} else {
|
||||||
|
wavesurfer.load(props.audioUrl);
|
||||||
}
|
}
|
||||||
},
|
}).catch((err) => {
|
||||||
computed: {
|
console.error(err);
|
||||||
langVolume () {
|
wavesurfer.load(props.audioUrl);
|
||||||
return this.$gettext('Volume');
|
});
|
||||||
}
|
});
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
play () {
|
|
||||||
if (this.wavesurfer) {
|
|
||||||
this.wavesurfer.play();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
stop () {
|
|
||||||
if (this.wavesurfer) {
|
|
||||||
this.wavesurfer.pause();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getCurrentTime () {
|
|
||||||
if (this.wavesurfer) {
|
|
||||||
return this.wavesurfer.getCurrentTime();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getDuration () {
|
|
||||||
if (this.wavesurfer) {
|
|
||||||
return this.wavesurfer.getDuration();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
addRegion (start, end, color) {
|
|
||||||
if (this.wavesurfer) {
|
|
||||||
this.wavesurfer.addRegion(
|
|
||||||
{
|
|
||||||
start: start,
|
|
||||||
end: end,
|
|
||||||
resize: false,
|
|
||||||
drag: false,
|
|
||||||
color: color
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
clearRegions () {
|
|
||||||
if (this.wavesurfer) {
|
|
||||||
this.wavesurfer.clearRegions();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
zoom: function (val) {
|
|
||||||
this.wavesurfer.zoom(Number(val));
|
|
||||||
},
|
|
||||||
volume: function (volume) {
|
|
||||||
this.wavesurfer.setVolume(getLogarithmicVolume(volume));
|
|
||||||
|
|
||||||
if (store.enabled) {
|
onUnmounted(() => {
|
||||||
store.set('player_volume', volume);
|
wavesurfer = null;
|
||||||
}
|
});
|
||||||
}
|
|
||||||
},
|
const play = () => {
|
||||||
beforeDestroy () {
|
wavesurfer?.play();
|
||||||
this.wavesurfer = null;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const stop = () => {
|
||||||
|
wavesurfer?.pause();
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCurrentTime = () => {
|
||||||
|
return wavesurfer?.getCurrentTime();
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDuration = () => {
|
||||||
|
return wavesurfer?.getDuration();
|
||||||
|
}
|
||||||
|
|
||||||
|
const addRegion = (start, end, color) => {
|
||||||
|
wavesurfer?.addRegion(
|
||||||
|
{
|
||||||
|
start: start,
|
||||||
|
end: end,
|
||||||
|
resize: false,
|
||||||
|
drag: false,
|
||||||
|
color: color
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearRegions = () => {
|
||||||
|
wavesurfer?.clearRegions();
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
play,
|
||||||
|
stop,
|
||||||
|
getCurrentTime,
|
||||||
|
getDuration,
|
||||||
|
addRegion,
|
||||||
|
clearRegions
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<modal-form ref="modal" :loading="loading" :title="langTitle" :error="error" :disable-save-button="v$.form.$invalid"
|
<modal-form ref="modal" :loading="loading" :title="$gettext('Edit Media')" :error="error"
|
||||||
@submit="doEdit" @hidden="clearContents">
|
:disable-save-button="v$.$invalid"
|
||||||
|
@submit="doEdit" @hidden="resetForm">
|
||||||
|
|
||||||
<b-tabs content-class="mt-3" pills>
|
<b-tabs content-class="mt-3" pills>
|
||||||
<b-tab active>
|
<b-tab active>
|
||||||
|
@ -8,14 +9,14 @@
|
||||||
{{ $gettext('Basic Information') }}
|
{{ $gettext('Basic Information') }}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<media-form-basic-info :form="v$.form"></media-form-basic-info>
|
<media-form-basic-info :form="v$"></media-form-basic-info>
|
||||||
</b-tab>
|
</b-tab>
|
||||||
<b-tab>
|
<b-tab>
|
||||||
<template #title>
|
<template #title>
|
||||||
{{ $gettext('Playlists') }}
|
{{ $gettext('Playlists') }}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<media-form-playlists :form="v$.form" :playlists="playlists"></media-form-playlists>
|
<media-form-playlists :form="v$" :playlists="playlists"></media-form-playlists>
|
||||||
</b-tab>
|
</b-tab>
|
||||||
<b-tab lazy>
|
<b-tab lazy>
|
||||||
<template #title>
|
<template #title>
|
||||||
|
@ -30,7 +31,7 @@
|
||||||
{{ $gettext('Custom Fields') }}
|
{{ $gettext('Custom Fields') }}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<media-form-custom-fields :form="v$.form" :custom-fields="customFields"></media-form-custom-fields>
|
<media-form-custom-fields :form="v$" :custom-fields="customFields"></media-form-custom-fields>
|
||||||
</b-tab>
|
</b-tab>
|
||||||
|
|
||||||
<b-tab lazy>
|
<b-tab lazy>
|
||||||
|
@ -47,14 +48,15 @@
|
||||||
{{ $gettext('Advanced') }}
|
{{ $gettext('Advanced') }}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<media-form-advanced-settings :form="v$.form" :song-length="songLength"></media-form-advanced-settings>
|
<media-form-advanced-settings :form="v$" :song-length="songLength"></media-form-advanced-settings>
|
||||||
</b-tab>
|
</b-tab>
|
||||||
</b-tabs>
|
</b-tabs>
|
||||||
</modal-form>
|
</modal-form>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
|
||||||
|
<script setup>
|
||||||
import {required} from '@vuelidate/validators';
|
import {required} from '@vuelidate/validators';
|
||||||
import _ from 'lodash';
|
import {defaultTo, forEach, map} from 'lodash';
|
||||||
import MediaFormBasicInfo from './Form/BasicInfo';
|
import MediaFormBasicInfo from './Form/BasicInfo';
|
||||||
import MediaFormAlbumArt from './Form/AlbumArt';
|
import MediaFormAlbumArt from './Form/AlbumArt';
|
||||||
import MediaFormCustomFields from './Form/CustomFields';
|
import MediaFormCustomFields from './Form/CustomFields';
|
||||||
|
@ -62,173 +64,161 @@ import MediaFormAdvancedSettings from './Form/AdvancedSettings';
|
||||||
import MediaFormPlaylists from './Form/Playlists';
|
import MediaFormPlaylists from './Form/Playlists';
|
||||||
import MediaFormWaveformEditor from './Form/WaveformEditor';
|
import MediaFormWaveformEditor from './Form/WaveformEditor';
|
||||||
import ModalForm from "~/components/Common/ModalForm";
|
import ModalForm from "~/components/Common/ModalForm";
|
||||||
import useVuelidate from "@vuelidate/core";
|
import {ref} from "vue";
|
||||||
|
import {useVuelidateOnForm} from "~/functions/useVuelidateOnForm";
|
||||||
|
import {useAxios} from "~/vendor/axios";
|
||||||
|
import {useNotify} from "~/vendor/bootstrapVue";
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
name: 'EditModal',
|
customFields: Array,
|
||||||
components: {
|
playlists: Array
|
||||||
ModalForm,
|
});
|
||||||
MediaFormPlaylists,
|
|
||||||
MediaFormWaveformEditor,
|
const emit = defineEmits(['relist']);
|
||||||
MediaFormAdvancedSettings,
|
|
||||||
MediaFormCustomFields,
|
const loading = ref(true);
|
||||||
MediaFormAlbumArt,
|
const error = ref(null);
|
||||||
MediaFormBasicInfo
|
const recordUrl = ref('');
|
||||||
},
|
const albumArtUrl = ref('');
|
||||||
setup() {
|
const waveformUrl = ref('');
|
||||||
return {v$: useVuelidate()}
|
const audioUrl = ref('');
|
||||||
},
|
const songLength = ref(0);
|
||||||
props: {
|
|
||||||
customFields: Array,
|
const buildForm = () => {
|
||||||
playlists: Array
|
let blankForm = {
|
||||||
},
|
path: null,
|
||||||
data() {
|
title: null,
|
||||||
return {
|
artist: null,
|
||||||
loading: true,
|
album: null,
|
||||||
recordUrl: null,
|
genre: null,
|
||||||
error: null,
|
lyrics: null,
|
||||||
albumArtUrl: null,
|
isrc: null,
|
||||||
waveformUrl: null,
|
amplify: null,
|
||||||
audioUrl: null,
|
fade_overlap: null,
|
||||||
songLength: null,
|
fade_in: null,
|
||||||
form: {}
|
fade_out: null,
|
||||||
};
|
cue_in: null,
|
||||||
},
|
cue_out: null,
|
||||||
validations() {
|
playlists: [],
|
||||||
let validations = {
|
custom_fields: {}
|
||||||
form: {
|
};
|
||||||
path: {
|
|
||||||
required
|
let validations = {
|
||||||
},
|
path: {required},
|
||||||
title: {},
|
title: {},
|
||||||
artist: {},
|
artist: {},
|
||||||
album: {},
|
album: {},
|
||||||
genre: {},
|
genre: {},
|
||||||
lyrics: {},
|
lyrics: {},
|
||||||
isrc: {},
|
isrc: {},
|
||||||
art: {},
|
art: {},
|
||||||
amplify: {},
|
amplify: {},
|
||||||
fade_overlap: {},
|
fade_overlap: {},
|
||||||
fade_in: {},
|
fade_in: {},
|
||||||
fade_out: {},
|
fade_out: {},
|
||||||
cue_in: {},
|
cue_in: {},
|
||||||
cue_out: {},
|
cue_out: {},
|
||||||
playlists: {},
|
playlists: {},
|
||||||
custom_fields: {}
|
custom_fields: {}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
forEach(props.customFields.slice(), (field) => {
|
||||||
|
validations.custom_fields[field.short_name] = {};
|
||||||
|
blankForm.custom_fields[field.short_name] = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {blankForm, validations};
|
||||||
|
};
|
||||||
|
|
||||||
|
const {blankForm, validations} = buildForm();
|
||||||
|
const {form, resetForm: resetBaseForm, v$} = useVuelidateOnForm(validations, blankForm);
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
resetBaseForm();
|
||||||
|
|
||||||
|
loading.value = false;
|
||||||
|
error.value = null;
|
||||||
|
|
||||||
|
albumArtUrl.value = '';
|
||||||
|
waveformUrl.value = '';
|
||||||
|
recordUrl.value = '';
|
||||||
|
audioUrl.value = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const modal = ref(); // BModal
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
modal.value?.hide();
|
||||||
|
};
|
||||||
|
|
||||||
|
const {axios} = useAxios();
|
||||||
|
|
||||||
|
const open = (newRecordUrl, newAlbumArtUrl, newAudioUrl, newWaveformUrl) => {
|
||||||
|
resetForm();
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
recordUrl.value = newRecordUrl;
|
||||||
|
albumArtUrl.value = newAlbumArtUrl;
|
||||||
|
audioUrl.value = newAudioUrl;
|
||||||
|
waveformUrl.value = newWaveformUrl;
|
||||||
|
|
||||||
|
modal.value?.show();
|
||||||
|
|
||||||
|
axios.get(newRecordUrl).then((resp) => {
|
||||||
|
let d = resp.data;
|
||||||
|
|
||||||
|
songLength.value = d.length_text;
|
||||||
|
|
||||||
|
let newForm = {
|
||||||
|
path: d.path,
|
||||||
|
title: d.title,
|
||||||
|
artist: d.artist,
|
||||||
|
album: d.album,
|
||||||
|
genre: d.genre,
|
||||||
|
lyrics: d.lyrics,
|
||||||
|
isrc: d.isrc,
|
||||||
|
amplify: d.amplify,
|
||||||
|
fade_overlap: d.fade_overlap,
|
||||||
|
fade_in: d.fade_in,
|
||||||
|
fade_out: d.fade_out,
|
||||||
|
cue_in: d.cue_in,
|
||||||
|
cue_out: d.cue_out,
|
||||||
|
playlists: map(d.playlists, 'id'),
|
||||||
|
custom_fields: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
_.forEach(this.customFields.slice(), (field) => {
|
forEach(props.customFields.slice(), (field) => {
|
||||||
validations.form.custom_fields[field.short_name] = {};
|
newForm.custom_fields[field.short_name] = defaultTo(d.custom_fields[field.short_name], null);
|
||||||
});
|
});
|
||||||
|
|
||||||
return validations;
|
form.value = newForm;
|
||||||
},
|
}).catch(() => {
|
||||||
computed: {
|
close();
|
||||||
langTitle() {
|
}).finally(() => {
|
||||||
return this.$gettext('Edit Media');
|
loading.value = false;
|
||||||
}
|
});
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
resetForm() {
|
|
||||||
this.loading = false;
|
|
||||||
this.error = null;
|
|
||||||
|
|
||||||
this.albumArtUrl = null;
|
|
||||||
this.waveformUrl = null;
|
|
||||||
this.recordUrl = null;
|
|
||||||
this.audioUrl = null;
|
|
||||||
|
|
||||||
let customFields = {};
|
|
||||||
_.forEach(this.customFields.slice(), (field) => {
|
|
||||||
customFields[field.short_name] = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.form = {
|
|
||||||
path: null,
|
|
||||||
title: null,
|
|
||||||
artist: null,
|
|
||||||
album: null,
|
|
||||||
genre: null,
|
|
||||||
lyrics: null,
|
|
||||||
isrc: null,
|
|
||||||
amplify: null,
|
|
||||||
fade_overlap: null,
|
|
||||||
fade_in: null,
|
|
||||||
fade_out: null,
|
|
||||||
cue_in: null,
|
|
||||||
cue_out: null,
|
|
||||||
playlists: [],
|
|
||||||
custom_fields: customFields
|
|
||||||
};
|
|
||||||
},
|
|
||||||
open(recordUrl, albumArtUrl, audioUrl, waveformUrl) {
|
|
||||||
this.resetForm();
|
|
||||||
|
|
||||||
this.loading = true;
|
|
||||||
this.error = null;
|
|
||||||
|
|
||||||
this.albumArtUrl = albumArtUrl;
|
|
||||||
this.waveformUrl = waveformUrl;
|
|
||||||
this.recordUrl = recordUrl;
|
|
||||||
this.audioUrl = audioUrl;
|
|
||||||
|
|
||||||
this.$refs.modal.show();
|
|
||||||
|
|
||||||
this.axios.get(recordUrl).then((resp) => {
|
|
||||||
let d = resp.data;
|
|
||||||
|
|
||||||
this.songLength = d.length_text;
|
|
||||||
this.form = {
|
|
||||||
path: d.path,
|
|
||||||
title: d.title,
|
|
||||||
artist: d.artist,
|
|
||||||
album: d.album,
|
|
||||||
genre: d.genre,
|
|
||||||
lyrics: d.lyrics,
|
|
||||||
isrc: d.isrc,
|
|
||||||
amplify: d.amplify,
|
|
||||||
fade_overlap: d.fade_overlap,
|
|
||||||
fade_in: d.fade_in,
|
|
||||||
fade_out: d.fade_out,
|
|
||||||
cue_in: d.cue_in,
|
|
||||||
cue_out: d.cue_out,
|
|
||||||
playlists: _.map(d.playlists, 'id'),
|
|
||||||
custom_fields: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
_.forEach(this.customFields.slice(), (field) => {
|
|
||||||
this.form.custom_fields[field.short_name] = _.defaultTo(d.custom_fields[field.short_name], null);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.loading = false;
|
|
||||||
}).catch(() => {
|
|
||||||
this.close();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
close() {
|
|
||||||
this.$refs.modal.hide();
|
|
||||||
},
|
|
||||||
clearContents() {
|
|
||||||
this.resetForm();
|
|
||||||
this.v$.$reset();
|
|
||||||
},
|
|
||||||
doEdit() {
|
|
||||||
this.v$.$touch();
|
|
||||||
if (this.v$.$errors.length > 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.error = null;
|
|
||||||
|
|
||||||
this.axios.put(this.recordUrl, this.form).then(() => {
|
|
||||||
this.$notifySuccess();
|
|
||||||
this.$emit('relist');
|
|
||||||
this.close();
|
|
||||||
}).catch((error) => {
|
|
||||||
this.error = error.response.data.message;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const {notifySuccess} = useNotify();
|
||||||
|
|
||||||
|
const doEdit = () => {
|
||||||
|
v$.value.$touch();
|
||||||
|
if (v$.value.$errors.length > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
error.value = null;
|
||||||
|
|
||||||
|
axios.put(recordUrl.value, form.value).then(() => {
|
||||||
|
notifySuccess();
|
||||||
|
emit('relist');
|
||||||
|
close();
|
||||||
|
}).catch((error) => {
|
||||||
|
error.value = error.response.data.message;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
open
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -6,133 +6,131 @@
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<b-form-group>
|
<b-form-group>
|
||||||
<waveform ref="waveform" :audio-url="audioUrl" :waveform-url="waveformUrl"
|
<waveform-component ref="waveform" :audio-url="audioUrl" :waveform-url="waveformUrl"
|
||||||
@ready="updateRegions"></waveform>
|
@ready="updateRegions"></waveform-component>
|
||||||
</b-form-group>
|
</b-form-group>
|
||||||
<b-form-group>
|
<b-form-group>
|
||||||
<b-button-group>
|
<div class="buttons">
|
||||||
<b-button variant="light" @click="playAudio">
|
<b-button-group size="sm">
|
||||||
<icon icon="play_arrow"></icon>
|
<b-button variant="light" @click="playAudio" :title="$gettext('Play')" size="sm">
|
||||||
<span class="sr-only">{{ $gettext('Play') }}</span>
|
<icon icon="play_arrow"></icon>
|
||||||
</b-button>
|
</b-button>
|
||||||
<b-button variant="dark" @click="stopAudio">
|
<b-button variant="dark" @click="stopAudio" :title="$gettext('Stop')" size="sm">
|
||||||
<icon icon="stop"></icon>
|
<icon icon="stop"></icon>
|
||||||
<span class="sr-only">{{ $gettext('Stop') }}</span>
|
</b-button>
|
||||||
</b-button>
|
</b-button-group>
|
||||||
</b-button-group>
|
<b-button-group size="sm">
|
||||||
<b-button-group>
|
<b-button variant="primary" @click="setCueIn" size="sm">
|
||||||
<b-button variant="primary" @click="setCueIn">
|
{{ $gettext('Set Cue In') }}
|
||||||
{{ $gettext('Set Cue In') }}
|
</b-button>
|
||||||
</b-button>
|
<b-button variant="primary" @click="setCueOut" size="sm">
|
||||||
|
{{ $gettext('Set Cue Out') }}
|
||||||
|
</b-button>
|
||||||
|
</b-button-group>
|
||||||
|
<b-button-group size="sm">
|
||||||
|
<b-button variant="warning" @click="setFadeOverlap" size="sm">
|
||||||
|
{{ $gettext('Set Overlap') }}
|
||||||
|
</b-button>
|
||||||
|
</b-button-group>
|
||||||
|
<b-button-group size="sm">
|
||||||
|
<b-button variant="danger" @click="setFadeIn" size="sm">
|
||||||
|
{{ $gettext('Set Fade In') }}
|
||||||
|
</b-button>
|
||||||
|
|
||||||
<b-button variant="primary" @click="setCueOut">
|
<b-button variant="danger" @click="setFadeOut" size="sm">
|
||||||
{{ $gettext('Set Cue Out') }}
|
{{ $gettext('Set Fade Out') }}
|
||||||
</b-button>
|
</b-button>
|
||||||
</b-button-group>
|
</b-button-group>
|
||||||
<b-button-group>
|
</div>
|
||||||
<b-button variant="warning" @click="setFadeOverlap">
|
|
||||||
{{ $gettext('Set Overlap') }}
|
|
||||||
</b-button>
|
|
||||||
</b-button-group>
|
|
||||||
<b-button-group>
|
|
||||||
<b-button variant="danger" @click="setFadeIn">
|
|
||||||
{{ $gettext('Set Fade In') }}
|
|
||||||
</b-button>
|
|
||||||
|
|
||||||
<b-button variant="danger" @click="setFadeOut">
|
|
||||||
{{ $gettext('Set Fade Out') }}
|
|
||||||
</b-button>
|
|
||||||
</b-button-group>
|
|
||||||
</b-form-group>
|
</b-form-group>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import Waveform from '~/components/Common/Waveform';
|
import WaveformComponent from '~/components/Common/Waveform';
|
||||||
import Icon from '~/components/Common/Icon';
|
import Icon from '~/components/Common/Icon';
|
||||||
|
import {ref} from "vue";
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
name: 'MediaFormWaveformEditor',
|
form: Object,
|
||||||
components: {Icon, Waveform},
|
audioUrl: String,
|
||||||
props: {
|
waveformUrl: String
|
||||||
form: Object,
|
});
|
||||||
audioUrl: String,
|
|
||||||
waveformUrl: String
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
playAudio() {
|
|
||||||
this.$refs.waveform.play();
|
|
||||||
},
|
|
||||||
stopAudio() {
|
|
||||||
this.$refs.waveform.stop();
|
|
||||||
},
|
|
||||||
setCueIn() {
|
|
||||||
let currentTime = this.$refs.waveform.getCurrentTime();
|
|
||||||
|
|
||||||
this.form.cue_in = Math.round((currentTime) * 10) / 10;
|
const waveform = ref(); // Waveform
|
||||||
|
|
||||||
this.updateRegions();
|
const playAudio = () => {
|
||||||
},
|
waveform.value?.play();
|
||||||
setCueOut() {
|
};
|
||||||
let currentTime = this.$refs.waveform.getCurrentTime();
|
|
||||||
|
|
||||||
this.form.cue_out = Math.round((currentTime) * 10) / 10;
|
const stopAudio = () => {
|
||||||
|
waveform.value?.stop();
|
||||||
|
};
|
||||||
|
|
||||||
this.updateRegions();
|
const updateRegions = () => {
|
||||||
},
|
let duration = waveform.value?.getDuration();
|
||||||
setFadeOverlap() {
|
|
||||||
let duration = this.$refs.waveform.getDuration();
|
|
||||||
let cue_out = this.form.cue_out || duration;
|
|
||||||
let currentTime = this.$refs.waveform.getCurrentTime();
|
|
||||||
|
|
||||||
this.form.fade_overlap = Math.round((cue_out - currentTime) * 10) / 10;
|
let cue_in = props.form.cue_in ?? 0;
|
||||||
|
let cue_out = props.form.cue_out ?? duration;
|
||||||
|
let fade_overlap = props.form.fade_overlap ?? 0;
|
||||||
|
let fade_in = props.form.fade_in ?? 0;
|
||||||
|
let fade_out = props.form.fade_out ?? 0;
|
||||||
|
|
||||||
this.updateRegions();
|
waveform.value?.clearRegions();
|
||||||
},
|
|
||||||
setFadeIn() {
|
|
||||||
let currentTime = this.$refs.waveform.getCurrentTime();
|
|
||||||
let cue_in = this.form.cue_in || 0;
|
|
||||||
|
|
||||||
this.form.fade_in = Math.round((currentTime - cue_in) * 10) / 10;
|
// Create cue region
|
||||||
|
waveform.value?.addRegion(cue_in, cue_out, 'hsla(207,90%,54%,0.4)');
|
||||||
|
|
||||||
this.updateRegions();
|
// Create overlap region
|
||||||
},
|
if (fade_overlap > cue_in) {
|
||||||
setFadeOut() {
|
waveform.value?.addRegion(cue_out - fade_overlap, cue_out, 'hsla(29,100%,48%,0.4)');
|
||||||
let currentTime = this.$refs.waveform.getCurrentTime();
|
}
|
||||||
let duration = this.$refs.waveform.getDuration();
|
|
||||||
let cue_out = this.form.cue_out || duration;
|
|
||||||
|
|
||||||
this.form.fade_out = Math.round((cue_out - currentTime) * 10) / 10;
|
// Create fade regions
|
||||||
|
if (fade_in) {
|
||||||
this.updateRegions();
|
waveform.value?.addRegion(cue_in, fade_in + cue_in, 'hsla(351,100%,48%,0.4)');
|
||||||
},
|
}
|
||||||
updateRegions() {
|
if (fade_out) {
|
||||||
let duration = this.$refs.waveform.getDuration();
|
waveform.value?.addRegion(cue_out - fade_out, cue_out, 'hsla(351,100%,48%,0.4)');
|
||||||
|
|
||||||
let cue_in = this.form.cue_in || 0;
|
|
||||||
let cue_out = this.form.cue_out || duration;
|
|
||||||
|
|
||||||
let fade_overlap = this.form.fade_overlap;
|
|
||||||
let fade_in = this.form.fade_in;
|
|
||||||
let fade_out = this.form.fade_out;
|
|
||||||
|
|
||||||
this.$refs.waveform.clearRegions();
|
|
||||||
|
|
||||||
// Create cue region
|
|
||||||
this.$refs.waveform.addRegion(cue_in, cue_out, 'hsla(207,90%,54%,0.4)');
|
|
||||||
|
|
||||||
// Create overlap region
|
|
||||||
if (fade_overlap > cue_in) {
|
|
||||||
this.$refs.waveform.addRegion(cue_out - fade_overlap, cue_out, 'hsla(29,100%,48%,0.4)');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create fade regions
|
|
||||||
if (fade_in) {
|
|
||||||
this.$refs.waveform.addRegion(cue_in, fade_in + cue_in, 'hsla(351,100%,48%,0.4)');
|
|
||||||
}
|
|
||||||
if (fade_out) {
|
|
||||||
this.$refs.waveform.addRegion(cue_out - fade_out, cue_out, 'hsla(351,100%,48%,0.4)');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setCueIn = () => {
|
||||||
|
let currentTime = waveform.value?.getCurrentTime();
|
||||||
|
|
||||||
|
props.form.cue_in = Math.round((currentTime) * 10) / 10;
|
||||||
|
updateRegions();
|
||||||
|
};
|
||||||
|
|
||||||
|
const setCueOut = () => {
|
||||||
|
let currentTime = waveform.value?.getCurrentTime();
|
||||||
|
|
||||||
|
props.form.cue_out = Math.round((currentTime) * 10) / 10;
|
||||||
|
updateRegions();
|
||||||
|
};
|
||||||
|
|
||||||
|
const setFadeOverlap = () => {
|
||||||
|
let duration = waveform.value?.getDuration();
|
||||||
|
let currentTime = waveform.value?.getCurrentTime();
|
||||||
|
let cue_out = form.value?.cue_out ?? duration;
|
||||||
|
|
||||||
|
props.form.fade_overlap = Math.round((cue_out - currentTime) * 10) / 10;
|
||||||
|
updateRegions();
|
||||||
|
};
|
||||||
|
|
||||||
|
const setFadeIn = () => {
|
||||||
|
let currentTime = waveform.value?.getCurrentTime();
|
||||||
|
let cue_in = form.value?.cue_in ?? 0;
|
||||||
|
|
||||||
|
props.form.fade_in = Math.round((currentTime - cue_in) * 10) / 10;
|
||||||
|
updateRegions();
|
||||||
|
}
|
||||||
|
|
||||||
|
const setFadeOut = () => {
|
||||||
|
let currentTime = waveform.value?.getCurrentTime();
|
||||||
|
let duration = waveform.value?.getDuration();
|
||||||
|
let cue_out = form.value?.cue_out ?? duration;
|
||||||
|
|
||||||
|
props.form.fade_out = Math.round((cue_out - currentTime) * 10) / 10;
|
||||||
|
updateRegions();
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
Loading…
Reference in New Issue