Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preset select before segment #93

Merged
merged 3 commits into from
Sep 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/backend/api/api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,19 @@ paths:
application/json:
schema:
type: object
required:
- filePath
properties:
filePath:
type: string
presetName:
type: string
enum:
- EXTRA_STRICT
- STRICT
- NORMAL
- LENIENT
- EXTRA_LENIENT
responses:
"200":
description: The segments of the file and potential metadata.
Expand Down
8 changes: 6 additions & 2 deletions src/backend/api/audio.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from flask import Blueprint, Response, jsonify, request, send_file
from modules.api_service import ApiService
from modules.audio_stream_io import read_audio_file_to_numpy, save_numpy_as_audio_file
from modules.segmentation import segment_file
from modules.segmentation import Preset, segment_file
from pathvalidate import sanitize_filename
from utils.env import get_env
from utils.file_name_formatter import format_file_name
Expand All @@ -22,9 +22,13 @@ def split():
"""
data = request.json
file_path = data["filePath"]
preset_name = data["presetName"]
preset = getattr(Preset, preset_name, Preset.NORMAL)

if not os.path.exists(file_path):
return "File does not exist!", 400
generator = segment_file(file_path)

generator = segment_file(file_path, preset)
segments, mismatch_offsets = ApiService().identify_all_from_generator(
generator, file_path
)
Expand Down
5 changes: 3 additions & 2 deletions src/backend/api/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,11 @@ def create():

project["files"].append(
{
"fileName": file_name,
"filePath": file_path,
"name": name,
"fileType": file_type,
"fileName": file_name,
"filePath": file_path,
"presetName": "NORMAL",
}
)

Expand Down
34 changes: 18 additions & 16 deletions src/frontend/components/BaseSelect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { onClickOutside } from '@vueuse/core'

interface Option { label: string; value: string }

defineProps<{
const props = defineProps<{
/**
* Placeholder for select
*/
Expand All @@ -12,16 +12,22 @@ defineProps<{
* Array of options for select
*/
options: Option[]
/**
* Is select disabled
*/
disabled?: boolean
}>()

const modelValue = defineModel({ default: '' })
const index = ref(props.options.findIndex(o => o.value === modelValue.value) ?? 0)
const isExpanded = ref(false)
const isMouseOverOpts = ref(false)
const button = ref<HTMLButtonElement>()
onClickOutside(button, () => isExpanded.value = false)

function handleChooseOption(v: string) {
function handleChooseOption(v: string, i: number) {
modelValue.value = v
index.value = i
isExpanded.value = false
button.value?.focus()
}
Expand All @@ -34,25 +40,21 @@ function handleChooseOption(v: string) {
type="button"
role="combobox"
:aria-expanded="isExpanded"
:disabled="disabled"
class="h-10 w-full flex items-center justify-between border border-input rounded-md bg-transparent px-3 py-2 text-sm ring-offset-background disabled:cursor-not-allowed placeholder:text-muted-foreground disabled:opacity-50 focus:ring-2 focus:ring-offset-2 focus:ring-ring"
@click="isExpanded = !isExpanded"
>
<span style="pointer-events: none">
<!-- @slot Label for option -->
<slot name="label">
{{ modelValue ?? placeholder }}
<!-- @slot Label for option
@binding {number} index current index
-->
<slot name="label" :index="index">
{{ options[index].label }}
</slot>
</span>
<span class="i-carbon-caret-down" />
</button>

<!-- <select v-model="modelValue" class="absolute h-1px w-1px overflow-hidden border-none p-0 -m-1px" aria-hidden="true" tabindex="-1" style="clip: rect(0px, 0px, 0px, 0px);">
<option value="" />
<option v-for="{ label, value } in options" :key="value" :value="value">
{{ label }}
</option>
</select> -->

<Transition
enter-active-class="transition-opacity"
leave-active-class="transition-opacity"
Expand All @@ -66,14 +68,14 @@ function handleChooseOption(v: string) {
@mouseleave="isMouseOverOpts = false"
>
<li
v-for="{ label, value } in options" :key="label"
v-for="{ label, value }, i in options" :key="label"
tabindex="0"
class="flex items-center gap-x-2 rounded-md px-3 py-2 hover:(bg-secondary text-secondary-foreground)"
:class="[!isMouseOverOpts && modelValue === value ? 'bg-secondary text-secondary-foreground' : 'bg-primary-foreground']"
role="button"
@click="handleChooseOption(value)"
@keydown.enter.prevent="handleChooseOption(value)"
@keydown.space.prevent="handleChooseOption(value)"
@click="handleChooseOption(value, i)"
@keydown.enter.prevent="handleChooseOption(value, i)"
@keydown.space.prevent="handleChooseOption(value, i)"
@keydown.escape.prevent="isExpanded = false"
>
<span
Expand Down
73 changes: 40 additions & 33 deletions src/frontend/components/ProjectItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import WaveSurfer from 'wavesurfer.js'
import Regions from 'wavesurfer.js/plugins/regions'
import type { Project, ProjectFileSegment } from '../models/types'
import type { Metadata } from '../models/api'
import type { Metadata, PostAudioSplitBodyPresetName } from '../models/api'
import { getAudioStreamSplittingAPI } from '../models/api'
import ConfirmModal from './dialogs/ConfirmModal.vue'
import EditSongModal from './dialogs/EditSongModal.vue'
Expand All @@ -13,6 +13,7 @@ const emits = defineEmits<{
(e: 'succeedProcess', v: ProjectFileSegment[]): void
(e: 'updatePeaks', v: number[][]): void
(e: 'changeMeta', songIndex: number, metaIndex: number): void
(e: 'changePresetName', presetName: PostAudioSplitBodyPresetName): void
}>()

const { postAudioSplit, postAudioStore } = getAudioStreamSplittingAPI()
Expand Down Expand Up @@ -50,34 +51,37 @@ onMounted(() => {
})
})

const presetNameOpts = [
{ value: 'EXTRA_STRICT', label: t('song.preset.extra_strict') },
{ value: 'STRICT', label: t('song.preset.strict') },
{ value: 'NORMAL', label: t('song.preset.normal') },
{ value: 'LENIENT', label: t('song.preset.lenient') },
{ value: 'EXTRA_LENIENT', label: t('song.preset.extra_lenient') },
]
const isProcessing = ref(false)
const presetName = ref(props.file.presetName ?? 'EXTRA_STRICT')
watch(presetName, () => emits('changePresetName', presetName.value))
async function handleProcess() {
isProcessing.value = true
try {
let segments: ProjectFileSegment[]
if (props.file.segments) {
segments = props.file.segments
}
else {
toast({ content: t('toast.long_process') })
const { data } = await postAudioSplit({ filePath: props.file.filePath })

if (!data.segments || !data.segments?.length)
throw new Error('This audio cannot be split')

data.segments = data.segments
.map((s) => {
const notNullOpts = s.metadataOptions?.filter(o => o.album || o.artist || o.title || o.year)
const notDuplicatedOpts = [...new Set((notNullOpts ?? []).map(o => JSON.stringify(o)))]
.map(o => JSON.parse(o))
return { ...s, metadataOptions: notDuplicatedOpts }
})

segments = data.segments.map(s => ({ ...s, metaIndex: 0 }))

emits('succeedProcess', segments)
}
regions.value?.clearRegions()

try {
toast({ content: t('toast.long_process') })
const { data } = await postAudioSplit({ filePath: props.file.filePath, presetName: presetName.value })

if (!data.segments || !data.segments?.length)
throw new Error('This audio cannot be split')

data.segments = data.segments
.map((s) => {
const notNullOpts = s.metadataOptions?.filter(o => o.album || o.artist || o.title || o.year)
const notDuplicatedOpts = [...new Set((notNullOpts ?? []).map(o => JSON.stringify(o)))]
.map(o => JSON.parse(o))
return { ...s, metadataOptions: notDuplicatedOpts }
})

const segments = data.segments.map(s => ({ ...s, metaIndex: 0 }))
emits('succeedProcess', segments)
addRegion(segments)
}
catch (e) {
Expand Down Expand Up @@ -189,15 +193,18 @@ function handleEdit(songIndex: number) {
</div>
</div>

<p v-if="file.fileType === 'webm'" class="text-center text-sm text-muted-foreground">
{{ t('song.no_webm') }}
</p>
<div class="flex items-center justify-between py-2">
<div class="space-y-1">
<BaseLabel>{{ t('song.preset.index') }}</BaseLabel>
<BaseSelect
v-model="presetName"
:disabled="isProcessing"
:options="presetNameOpts"
class="min-w-200px -ml-1"
/>
</div>

<div class="flex-center py-2">
<BaseButton
:disabled="file.fileType === 'webm' || isProcessing || file.segments"
@click="handleProcess"
>
<BaseButton :disabled="isProcessing" @click="handleProcess">
<BaseLoader
v-if="isProcessing"
class="mr-2 border-primary-foreground !border-2"
Expand Down
5 changes: 3 additions & 2 deletions src/frontend/composables/useConvertSecToMin.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export function useConvertSecToMin(secs: number) {
const rest = (secs % 60)
const min = (secs - rest) / 60
const _secs = Math.floor(secs)
const rest = _secs % 60
const min = (_secs - rest) / 60
return rest ? `${min}m ${rest}s` : `${min}m`
}
10 changes: 9 additions & 1 deletion src/frontend/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,15 @@
"duration": "Dauer",
"no_webm": ".webm-Dateien können nicht verarbeitet werden!",
"no_meta": "Keine Information",
"list_caption": "Gefundene Lieder"
"list_caption": "Gefundene Lieder",
"preset": {
"extra_lenient": "Extra locker",
"extra_strict": "Extra strikt",
"index": "Voreingestellt",
"lenient": "Locker",
"normal": "Normal",
"strict": "Strikt"
}
},
"toast": {
"empty_field": "Bitte geben Sie einen gültigen Wert ein!",
Expand Down
10 changes: 9 additions & 1 deletion src/frontend/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,15 @@
"duration": "Duration",
"no_webm": ".webm cannot be processed at the moment!",
"no_meta": "No information",
"list_caption": "Found songs"
"list_caption": "Found songs",
"preset": {
"index": "Preset",
"extra_strict": "Extra Strict",
"strict": "Strict",
"normal": "Normal",
"lenient": "Lenient",
"extra_lenient": "Extra Lenient"
}
},
"toast": {
"empty_field": "Please enter a valid value!",
Expand Down
14 changes: 13 additions & 1 deletion src/frontend/models/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,20 @@ export interface PostAudioSplit200 {
mismatchOffsets?: number[]
}

export type PostAudioSplitBodyPresetName = typeof PostAudioSplitBodyPresetName[keyof typeof PostAudioSplitBodyPresetName]

// eslint-disable-next-line @typescript-eslint/no-redeclare
export const PostAudioSplitBodyPresetName = {
EXTRA_STRICT: 'EXTRA_STRICT',
STRICT: 'STRICT',
NORMAL: 'NORMAL',
LENIENT: 'LENIENT',
EXTRA_LENIENT: 'EXTRA_LENIENT',
} as const

export interface PostAudioSplitBody {
filePath?: string
filePath: string
presetName?: PostAudioSplitBodyPresetName
}

export interface Metadata {
Expand Down
3 changes: 2 additions & 1 deletion src/frontend/models/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { PostAudioSplit200SegmentsItem } from '../models/api'
import type { PostAudioSplit200SegmentsItem, PostAudioSplitBodyPresetName } from '../models/api'

export interface Project {
id: string
Expand All @@ -11,6 +11,7 @@ export interface Project {
fileType: string
fileName: string
filePath: string
presetName: PostAudioSplitBodyPresetName
peaks?: number[][]
segments?: ProjectFileSegment[]
}[]
Expand Down
1 change: 1 addition & 0 deletions src/frontend/pages/project/[id].vue
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ function handleChangeMeta(fileIndex: number, songIndex: number, metaIndex: numbe
@succeed-process="(v) => updateFileProperty(fileIndex, 'segments', v)"
@update-peaks="(v) => updateFileProperty(fileIndex, 'peaks', v)"
@change-meta="(songIndex, metaIndex) => handleChangeMeta(fileIndex, songIndex, metaIndex)"
@change-preset-name="(v) => updateFileProperty(fileIndex, 'presetName', v)"
/>
</div>
</template>
Expand Down
6 changes: 1 addition & 5 deletions src/frontend/pages/settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,7 @@ function handleToggleDark() {
v-model="currentLocal"
:options="localOpts"
class="w-200px"
>
<template #label>
{{ LangMap[currentLocal] ?? '' }}
</template>
</BaseSelect>
/>
</div>

<SettingsClear />
Expand Down
Loading