WIP: keep uploaded models

This commit is contained in:
wataru 2023-05-16 10:38:23 +09:00
parent ffd2894d5e
commit 44593719f5
33 changed files with 523 additions and 1829 deletions

View File

@ -71,6 +71,10 @@
"fileKind": "rvcIndex"
}
},
{
"name": "sampleModelSelect",
"options": {}
},
{
"name": "defaultTuneRow2",
"options": {}

View File

@ -1,10 +1 @@
<!DOCTYPE html>
<html style="width: 100%; height: 100%; overflow: hidden">
<head>
<meta charset="utf-8" />
<title>Voice Changer Client Demo</title>
<script defer src="index.js"></script></head>
<body style="width: 100%; height: 100%; margin: 0px">
<div id="app" style="width: 100%; height: 100%"></div>
</body>
</html>
<!doctype html><html style="width:100%;height:100%;overflow:hidden"><head><meta charset="utf-8"/><title>Voice Changer Client Demo</title><script defer="defer" src="index.js"></script></head><body style="width:100%;height:100%;margin:0"><div id="app" style="width:100%;height:100%"></div></body></html>

File diff suppressed because one or more lines are too long

31
client/demo/dist/index.js.LICENSE.txt vendored Normal file
View File

@ -0,0 +1,31 @@
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
/**
* @license React
* react-dom.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* scheduler.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

View File

@ -9,7 +9,7 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@dannadori/voice-changer-client-js": "^1.0.126",
"@dannadori/voice-changer-client-js": "^1.0.127",
"@fortawesome/fontawesome-svg-core": "^6.4.0",
"@fortawesome/free-brands-svg-icons": "^6.4.0",
"@fortawesome/free-regular-svg-icons": "^6.4.0",
@ -24,7 +24,7 @@
"@babel/preset-env": "^7.21.5",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.21.5",
"@types/node": "^20.1.4",
"@types/node": "^20.1.5",
"@types/react": "^18.2.6",
"@types/react-dom": "^18.2.4",
"autoprefixer": "^10.4.14",
@ -3170,9 +3170,9 @@
}
},
"node_modules/@dannadori/voice-changer-client-js": {
"version": "1.0.126",
"resolved": "https://registry.npmjs.org/@dannadori/voice-changer-client-js/-/voice-changer-client-js-1.0.126.tgz",
"integrity": "sha512-vIXXu0rPlbd220r30SsAVduFlK2jCM2985pHQ/biVeVM7l+qXEIY+Qbr5H1Usx5jHnVCnpjULbRQKhByPeoHTA==",
"version": "1.0.127",
"resolved": "https://registry.npmjs.org/@dannadori/voice-changer-client-js/-/voice-changer-client-js-1.0.127.tgz",
"integrity": "sha512-EkzqkUy8/QO3VwZAR6MZh8zgwG7Oqx4uwzmnujfqlywjqdiYggDzp5n7innJJaJE/z+cUp32dsX7l7l9s2PiGw==",
"dependencies": {
"@types/readable-stream": "^2.3.15",
"amazon-chime-sdk-js": "^3.14.0",
@ -3833,9 +3833,9 @@
"dev": true
},
"node_modules/@types/node": {
"version": "20.1.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.1.4.tgz",
"integrity": "sha512-At4pvmIOki8yuwLtd7BNHl3CiWNbtclUbNtScGx4OHfBd4/oWoJC8KRCIxXwkdndzhxOsPXihrsOoydxBjlE9Q=="
"version": "20.1.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.1.5.tgz",
"integrity": "sha512-IvGD1CD/nego63ySR7vrAKEX3AJTcmrAN2kn+/sDNLi1Ff5kBzDeEdqWDplK+0HAEoLYej137Sk0cUU8OLOlMg=="
},
"node_modules/@types/prop-types": {
"version": "15.7.5",
@ -13995,9 +13995,9 @@
}
},
"@dannadori/voice-changer-client-js": {
"version": "1.0.126",
"resolved": "https://registry.npmjs.org/@dannadori/voice-changer-client-js/-/voice-changer-client-js-1.0.126.tgz",
"integrity": "sha512-vIXXu0rPlbd220r30SsAVduFlK2jCM2985pHQ/biVeVM7l+qXEIY+Qbr5H1Usx5jHnVCnpjULbRQKhByPeoHTA==",
"version": "1.0.127",
"resolved": "https://registry.npmjs.org/@dannadori/voice-changer-client-js/-/voice-changer-client-js-1.0.127.tgz",
"integrity": "sha512-EkzqkUy8/QO3VwZAR6MZh8zgwG7Oqx4uwzmnujfqlywjqdiYggDzp5n7innJJaJE/z+cUp32dsX7l7l9s2PiGw==",
"requires": {
"@types/readable-stream": "^2.3.15",
"amazon-chime-sdk-js": "^3.14.0",
@ -14542,9 +14542,9 @@
"dev": true
},
"@types/node": {
"version": "20.1.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.1.4.tgz",
"integrity": "sha512-At4pvmIOki8yuwLtd7BNHl3CiWNbtclUbNtScGx4OHfBd4/oWoJC8KRCIxXwkdndzhxOsPXihrsOoydxBjlE9Q=="
"version": "20.1.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.1.5.tgz",
"integrity": "sha512-IvGD1CD/nego63ySR7vrAKEX3AJTcmrAN2kn+/sDNLi1Ff5kBzDeEdqWDplK+0HAEoLYej137Sk0cUU8OLOlMg=="
},
"@types/prop-types": {
"version": "15.7.5",

View File

@ -24,7 +24,7 @@
"@babel/preset-env": "^7.21.5",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.21.5",
"@types/node": "^20.1.4",
"@types/node": "^20.1.5",
"@types/react": "^18.2.6",
"@types/react-dom": "^18.2.4",
"autoprefixer": "^10.4.14",
@ -52,7 +52,7 @@
"webpack-dev-server": "^4.15.0"
},
"dependencies": {
"@dannadori/voice-changer-client-js": "^1.0.126",
"@dannadori/voice-changer-client-js": "^1.0.127",
"@fortawesome/fontawesome-svg-core": "^6.4.0",
"@fortawesome/free-brands-svg-icons": "^6.4.0",
"@fortawesome/free-regular-svg-icons": "^6.4.0",

View File

@ -71,6 +71,10 @@
"fileKind": "rvcIndex"
}
},
{
"name": "sampleModelSelect",
"options": {}
},
{
"name": "defaultTuneRow2",
"options": {}

View File

@ -22,6 +22,21 @@ export type AppGuiDemoSetting = {
}
}
// export type AppGuiDemoSetting2 = {
// type: "demo",
// id: ClientType,
// front: GuiSectionSetting[],
// dialogs: {
// "license": { title: string, auther: string, contact: string, url: string, license: string }[]
// }
// }
// export type GuiSectionSetting = {
// "title": string,
// "components": GuiComponentSetting[]
// }
export type GuiComponentSetting = {
"name": string,
"options": any

View File

@ -58,6 +58,7 @@ type GuiStateAndMethod = {
modelSlotNum: number
setModelSlotNum: (val: number) => void
}
const GuiStateContext = React.createContext<GuiStateAndMethod | null>(null);
@ -85,7 +86,6 @@ export const GuiStateProvider = ({ children }: Props) => {
const [fileInputEchoback, setFileInputEchoback] = useState<boolean>(false)//最初のmuteが有効になるように。undefined <-- ??? falseしておけばよさそう。undefinedだとwarningがでる。
const [audioOutputForAnalyzer, setAudioOutputForAnalyzer] = useState<string>("default")
const reloadDeviceInfo = async () => {
try {
const ms = await navigator.mediaDevices.getUserMedia({ video: false, audio: true });
@ -208,7 +208,8 @@ export const GuiStateProvider = ({ children }: Props) => {
setAudioOutputForAnalyzer,
modelSlotNum,
setModelSlotNum
setModelSlotNum,
};
return <GuiStateContext.Provider value={providerValue}>{children}</GuiStateContext.Provider>;
};

View File

@ -54,6 +54,7 @@ import { DiffEnablerRow, DiffEnablerRowProps } from "./components/611_DiffEnable
import { DiffSettingRow, DiffSettingRowProps } from "./components/612_DiffSettingRow"
import { DiffMethodRow, DiffMethodRowProps } from "./components/613_DiffMethodRow"
import { ServerOpertationRow, ServerOpertationRowProps } from "./components/207_ServerOpertationRow"
import { SampleModelSelectRow, SampleModelSelectRowProps } from "./components/301-j_SampleModelSelectRow"
export const catalog: { [key: string]: (props: any) => JSX.Element } = {}
@ -92,6 +93,8 @@ const initialize = () => {
addToCatalog("correspondenceSelectRow2", (props: CorrespondenceSelectRow2Props) => { return <CorrespondenceSelectRow2 {...props} /> })
addToCatalog("modelSlotRow2", (props: ModelSlotRow2Props) => { return <ModelSlotRow2 {...props} /> })
addToCatalog("defaultTuneRow2", (props: DefaultTuneRow2Props) => { return <DefaultTuneRow2 {...props} /> })
addToCatalog("sampleModelSelect", (props: SampleModelSelectRowProps) => { return <SampleModelSelectRow {...props} /> })

View File

@ -10,7 +10,6 @@ import { SpeakerSetting } from "./600_SpeakerSetting";
import { ConverterSetting } from "./700_ConverterSetting";
import { AdvancedSetting } from "./800_AdvancedSetting";
import { Lab } from "./a00_Lab";
import { useAppRoot } from "../../001_provider/001_AppRootProvider";
export const Demo = () => {

View File

@ -37,7 +37,7 @@ export const ModelSwitchRow = (_props: ModelSwitchRowProps) => {
const tuning = `tune:${x.defaultTrans}`
const useIndex = x.indexFile != null && x.featureFile != null ? `index:true` : `index:false`
const subMetadata = `(${tuning},${useIndex})`
const displayName = `${metadata} ${filename} ${subMetadata}`
const displayName = `${metadata} ${x.name || filename} ${subMetadata}`
return (

View File

@ -38,8 +38,14 @@ export const CommonFileSelectRow = (props: CommonFileSelectRowProps) => {
const guiState = useGuiState()
const commonFileSelectRow = useMemo(() => {
const slot = guiState.modelSlotNum
if (!appState.serverSetting.fileUploadSettings[slot]) {
return <></>
}
if (appState.serverSetting.fileUploadSettings[slot].isSampleMode == true) {
return <></>
}
const getTargetModelData = () => {
const targetSlot = appState.serverSetting.fileUploadSettings[slot]
@ -66,12 +72,14 @@ export const CommonFileSelectRow = (props: CommonFileSelectRowProps) => {
return
}
appState.serverSetting.fileUploadSettings[slot][props.fileKind]! = { file: file }
appState.serverSetting.fileUploadSettings[slot].sampleId = null
appState.serverSetting.setFileUploadSetting(slot, {
...appState.serverSetting.fileUploadSettings[slot]
})
}
const onFileClearClicked = () => {
appState.serverSetting.fileUploadSettings[slot][props.fileKind] = null
appState.serverSetting.fileUploadSettings[slot].sampleId = null
appState.serverSetting.setFileUploadSetting(slot, {
...appState.serverSetting.fileUploadSettings[slot],
})

View File

@ -11,15 +11,20 @@ export const ModelUploadButtonRow2 = (_props: ModelUploadButtonRow2Props) => {
const guiState = useGuiState()
const modelUploadButtonRow = useMemo(() => {
const slot = guiState.modelSlotNum
if (!appState.serverSetting.fileUploadSettings[slot]) {
return <></>
}
const onModelUploadClicked = async () => {
appState.serverSetting.loadModel(slot)
}
const buttonText = appState.serverSetting.fileUploadSettings[slot].isSampleMode ? "select" : "upload"
const uploadButtonClassName = appState.serverSetting.isUploading ? "body-button-disabled" : "body-button"
const uploadButtonAction = appState.serverSetting.isUploading ? () => { } : onModelUploadClicked
const uploadButtonLabel = appState.serverSetting.isUploading ? "wait..." : "upload"
const uploadButtonLabel = appState.serverSetting.isUploading ? "wait..." : buttonText
const uploadingStatus = appState.serverSetting.isUploading ?
appState.serverSetting.uploadProgress == 0 ? `loading model...(wait about 20sec)` : `uploading.... ${appState.serverSetting.uploadProgress.toFixed(1)}%` : ""
appState.serverSetting.uploadProgress == 0 ? `loading model...(wait about 20sec)` : `processing.... ${appState.serverSetting.uploadProgress.toFixed(1)}%` : ""
const uploadedText = appState.serverSetting.fileUploadSettings[slot] == undefined ? "" : appState.serverSetting.fileUploadSettings[slot].uploaded ? "" : "not uploaded"

View File

@ -1,29 +1,57 @@
import { MAX_MODEL_SLOT_NUM } from "@dannadori/voice-changer-client-js"
import React, { useMemo } from "react"
import { useGuiState } from "../001_GuiStateProvider"
import { useAppState } from "../../../001_provider/001_AppStateProvider"
export type ModelSlotRow2Props = {}
export const ModelSlotRow2 = (_prop: ModelSlotRow2Props) => {
const guiState = useGuiState()
const appState = useAppState()
const modelSlotRow = useMemo(() => {
const slot = guiState.modelSlotNum
if (!appState.serverSetting.fileUploadSettings[slot]) {
return <></>
}
const onModelSlotChanged = (val: number) => {
guiState.setModelSlotNum(val)
}
const onModeChanged = (val: boolean) => {
appState.serverSetting.fileUploadSettings[slot].isSampleMode = val
appState.serverSetting.setFileUploadSetting(slot, {
...appState.serverSetting.fileUploadSettings[slot],
})
}
const isSampleMode = appState.serverSetting.fileUploadSettings[slot].isSampleMode
return (
<div className="body-row split-3-7 left-padding-1 guided">
<div className="body-row split-3-3-4 left-padding-1 guided">
<div className="body-item-title left-padding-2">Model Slot</div>
<div className="body-input-container">
<select value={slot} onChange={(e) => { onModelSlotChanged(Number(e.target.value)) }}>
{Array(MAX_MODEL_SLOT_NUM).fill(0).map((_x, index) => {
return <option key={index} value={index} >{index}</option>
})}
</select>
<div className="left-padding-1">
<input className="left-padding-1" type="radio" id="from-file" name="sample-mode" checked={isSampleMode == false} onChange={() => { onModeChanged(false) }} />
<label className="left-padding-05" htmlFor="from-file">file</label>
</div>
<div className="left-padding-1">
<input className="left-padding-1" type="radio" id="from-net" name="sample-mode" checked={isSampleMode == true} onChange={() => { onModeChanged(true) }} />
<label className="left-padding-05" htmlFor="from-net">from net</label>
</div>
</div>
<div></div>
</div>
)
}, [guiState.modelSlotNum])
}, [guiState.modelSlotNum, appState.serverSetting.fileUploadSettings])
return modelSlotRow
}

View File

@ -17,7 +17,7 @@ export const DefaultTuneRow2 = (_props: DefaultTuneRow2Props) => {
const onDefaultTuneChanged = (val: number) => {
appState.serverSetting.setFileUploadSetting(slot, {
...appState.serverSetting.fileUploadSettings[slot],
defaultTune: val
defaultTune: val,
})
}

View File

@ -0,0 +1,53 @@
import React, { useMemo } from "react"
import { useAppState } from "../../../001_provider/001_AppStateProvider"
import { useGuiState } from "../001_GuiStateProvider"
export type SampleModelSelectRowProps = {}
export const SampleModelSelectRow = (_props: SampleModelSelectRowProps) => {
const appState = useAppState()
const guiState = useGuiState()
const sampleModelSelectRow = useMemo(() => {
const slot = guiState.modelSlotNum
const fileUploadSetting = appState.serverSetting.fileUploadSettings[slot]
if (!fileUploadSetting) {
return <></>
}
if (fileUploadSetting.isSampleMode == false) {
return <></>
}
const options = (
appState.serverSetting.serverSetting.sampleModels.map(x => {
return <option key={x.id} value={x.id}>{x.name}</option>
})
)
const selectedSample = appState.serverSetting.serverSetting.sampleModels.find(x => { return x.id == fileUploadSetting.sampleId })
const creditText = selectedSample ? `credit:${selectedSample.credit}` : ""
const termOfUseLink = selectedSample ? <a href={selectedSample.termOfUseUrl} target="_blank" rel="noopener noreferrer" className="body-item-text-small">[term of use]</a> : <></>
const onModelSelected = (val: string) => {
appState.serverSetting.setFileUploadSetting(slot, {
...appState.serverSetting.fileUploadSettings[slot], sampleId: val
})
}
return (
<div className="body-row split-3-3-4 left-padding-1 guided">
<div className="body-item-title left-padding-2 ">Select Model</div>
<div>
<select value={fileUploadSetting.sampleId || ""} onChange={(e) => { onModelSelected(e.target.value) }}>
<option disabled value={""}> -- select model -- </option>
{options}
</select>
</div>
<div className="body-item-text">
{creditText}{termOfUseLink}
</div>
</div>
)
}, [appState.serverSetting.fileUploadSettings, guiState.modelSlotNum])
return sampleModelSelectRow
}

View File

@ -494,6 +494,9 @@ body {
.underline {
border-bottom: 3px solid #333;
}
.left-padding-05 {
padding-left: 0.5rem;
}
.left-padding-1 {
padding-left: 1rem;
}

View File

@ -1,12 +1,12 @@
{
"name": "@dannadori/voice-changer-client-js",
"version": "1.0.126",
"version": "1.0.127",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@dannadori/voice-changer-client-js",
"version": "1.0.126",
"version": "1.0.127",
"license": "ISC",
"dependencies": {
"@types/readable-stream": "^2.3.15",
@ -19,7 +19,7 @@
},
"devDependencies": {
"@types/audioworklet": "^0.0.46",
"@types/node": "^20.1.4",
"@types/node": "^20.1.5",
"@types/react": "18.2.6",
"@types/react-dom": "18.2.4",
"eslint": "^8.40.0",
@ -1878,9 +1878,9 @@
"dev": true
},
"node_modules/@types/node": {
"version": "20.1.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.1.4.tgz",
"integrity": "sha512-At4pvmIOki8yuwLtd7BNHl3CiWNbtclUbNtScGx4OHfBd4/oWoJC8KRCIxXwkdndzhxOsPXihrsOoydxBjlE9Q=="
"version": "20.1.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.1.5.tgz",
"integrity": "sha512-IvGD1CD/nego63ySR7vrAKEX3AJTcmrAN2kn+/sDNLi1Ff5kBzDeEdqWDplK+0HAEoLYej137Sk0cUU8OLOlMg=="
},
"node_modules/@types/prop-types": {
"version": "15.7.5",
@ -9870,9 +9870,9 @@
"dev": true
},
"@types/node": {
"version": "20.1.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.1.4.tgz",
"integrity": "sha512-At4pvmIOki8yuwLtd7BNHl3CiWNbtclUbNtScGx4OHfBd4/oWoJC8KRCIxXwkdndzhxOsPXihrsOoydxBjlE9Q=="
"version": "20.1.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.1.5.tgz",
"integrity": "sha512-IvGD1CD/nego63ySR7vrAKEX3AJTcmrAN2kn+/sDNLi1Ff5kBzDeEdqWDplK+0HAEoLYej137Sk0cUU8OLOlMg=="
},
"@types/prop-types": {
"version": "15.7.5",

View File

@ -1,6 +1,6 @@
{
"name": "@dannadori/voice-changer-client-js",
"version": "1.0.126",
"version": "1.0.127",
"description": "",
"main": "dist/index.js",
"directories": {
@ -27,7 +27,7 @@
"license": "ISC",
"devDependencies": {
"@types/audioworklet": "^0.0.46",
"@types/node": "^20.1.4",
"@types/node": "^20.1.5",
"@types/react": "18.2.6",
"@types/react-dom": "18.2.4",
"eslint": "^8.40.0",

View File

@ -192,6 +192,12 @@ type ModelSlot = {
f0: boolean,
samplingRate: number
deprecated: boolean
name: string,
description: string,
credit: string,
termsOfUseUrl: string,
}
type ServerAudioDevice = {
@ -210,6 +216,7 @@ export type ServerInfo = VoiceChangerServerSetting & {
modelSlots: ModelSlot[]
serverAudioInputDevices: ServerAudioDevice[]
serverAudioOutputDevices: ServerAudioDevice[]
sampleModels: RVCSampleModel[]
}
@ -217,6 +224,17 @@ export type ServerInfoSoVitsSVC = ServerInfo & {
speakers: { [key: string]: number }
}
export type RVCSampleModel = {
id: string
name: string
modelUrl: string
indexUrl: string
featureUrl: string
termOfUseUrl: string
credit: string
description: string
}
export const DefaultServerSetting: ServerInfo = {
// VC Common
inputSampleRate: 48000,
@ -260,6 +278,7 @@ export const DefaultServerSetting: ServerInfo = {
modelSamplingRate: 48000,
silenceFront: 1,
modelSlotIndex: 0,
sampleModels: [],
useEnhancer: 0,
useDiff: 1,

View File

@ -31,6 +31,9 @@ export type FileUploadSetting = {
rvcFeature: ModelData | null
rvcIndex: ModelData | null
isSampleMode: boolean
sampleId: string | null
ddspSvcModel: ModelData | null
ddspSvcModelConfig: ModelData | null
ddspSvcDiffusion: ModelData | null
@ -59,6 +62,9 @@ const InitialFileUploadSetting: FileUploadSetting = {
rvcFeature: null,
rvcIndex: null,
isSampleMode: false,
sampleId: null,
ddspSvcModel: null,
ddspSvcModelConfig: null,
ddspSvcDiffusion: null,
@ -226,73 +232,84 @@ export const useServerSetting = (props: UseServerSettingProps): ServerSettingSta
const loadModel = useMemo(() => {
return async (slot: number) => {
if (props.clientType == "MMVCv13") {
if (!fileUploadSettings[slot].mmvcv13Config) {
alert("Configファイルを指定する必要があります。")
const fileUploadSetting = fileUploadSettings[slot]
if (fileUploadSetting.isSampleMode == false) {
if (props.clientType == "MMVCv13") {
if (!fileUploadSetting.mmvcv13Config) {
alert("Configファイルを指定する必要があります。")
return
}
if (!fileUploadSetting.mmvcv13Model) {
alert("モデルファイルを指定する必要があります。")
return
}
} else if (props.clientType == "MMVCv15") {
if (!fileUploadSetting.mmvcv15Config) {
alert("Configファイルを指定する必要があります。")
return
}
if (!fileUploadSetting.mmvcv15Model) {
alert("モデルファイルを指定する必要があります。")
return
}
} else if (props.clientType == "so-vits-svc-40") {
if (!fileUploadSetting.soVitsSvc40Config) {
alert("Configファイルを指定する必要があります。")
return
}
if (!fileUploadSetting.soVitsSvc40Model) {
alert("モデルファイルを指定する必要があります。")
return
}
} else if (props.clientType == "so-vits-svc-40v2") {
if (!fileUploadSetting.soVitsSvc40v2Config) {
alert("Configファイルを指定する必要があります。")
return
}
if (!fileUploadSetting.soVitsSvc40v2Model) {
alert("モデルファイルを指定する必要があります。")
return
}
} else if (props.clientType == "RVC") {
if (!fileUploadSetting.rvcModel) {
alert("モデルファイルを指定する必要があります。")
return
}
} else if (props.clientType == "DDSP-SVC") {
if (!fileUploadSetting.ddspSvcModel) {
alert("DDSPモデルを指定する必要があります。")
return
}
if (!fileUploadSetting.ddspSvcModelConfig) {
alert("DDSP Configファイルを指定する必要があります。")
return
}
if (!fileUploadSetting.ddspSvcDiffusion) {
alert("Diffusionモデルを指定する必要があります。")
return
}
if (!fileUploadSetting.ddspSvcDiffusionConfig) {
alert("Diffusion Configファイルを指定する必要があります。")
return
}
} else {
}
} else {//Sampleモード
if (!fileUploadSetting.sampleId) {
alert("Sample IDを指定する必要があります。")
return
}
if (!fileUploadSettings[slot].mmvcv13Model) {
alert("モデルファイルを指定する必要があります。")
return
}
} else if (props.clientType == "MMVCv15") {
if (!fileUploadSettings[slot].mmvcv15Config) {
alert("Configファイルを指定する必要があります。")
return
}
if (!fileUploadSettings[slot].mmvcv15Model) {
alert("モデルファイルを指定する必要があります。")
return
}
} else if (props.clientType == "so-vits-svc-40") {
if (!fileUploadSettings[slot].soVitsSvc40Config) {
alert("Configファイルを指定する必要があります。")
return
}
if (!fileUploadSettings[slot].soVitsSvc40Model) {
alert("モデルファイルを指定する必要があります。")
return
}
} else if (props.clientType == "so-vits-svc-40v2") {
if (!fileUploadSettings[slot].soVitsSvc40v2Config) {
alert("Configファイルを指定する必要があります。")
return
}
if (!fileUploadSettings[slot].soVitsSvc40v2Model) {
alert("モデルファイルを指定する必要があります。")
return
}
} else if (props.clientType == "RVC") {
if (!fileUploadSettings[slot].rvcModel) {
alert("モデルファイルを指定する必要があります。")
return
}
} else if (props.clientType == "DDSP-SVC") {
if (!fileUploadSettings[slot].ddspSvcModel) {
alert("DDSPモデルを指定する必要があります。")
return
}
if (!fileUploadSettings[slot].ddspSvcModelConfig) {
alert("DDSP Configファイルを指定する必要があります。")
return
}
if (!fileUploadSettings[slot].ddspSvcDiffusion) {
alert("Diffusionモデルを指定する必要があります。")
return
}
if (!fileUploadSettings[slot].ddspSvcDiffusionConfig) {
alert("Diffusion Configファイルを指定する必要があります。")
return
}
} else {
}
if (!props.voiceChangerClient) return
setUploadProgress(0)
setIsUploading(true)
const fileUploadSetting = fileUploadSettings[slot]
// MMVCv13
const normalModels = [
@ -317,12 +334,14 @@ export const useServerSetting = (props: UseServerSettingProps): ServerSettingSta
normalModels[i].filename = await normalModels[i].file!.name
}
}
for (let i = 0; i < normalModels.length; i++) {
const progRate = 1 / normalModels.length
const progOffset = 100 * i * progRate
await _uploadFile(normalModels[i], (progress: number, _end: boolean) => {
setUploadProgress(progress * progRate + progOffset)
})
if (fileUploadSetting.isSampleMode == false) {
for (let i = 0; i < normalModels.length; i++) {
const progRate = 1 / normalModels.length
const progOffset = 100 * i * progRate
await _uploadFile(normalModels[i], (progress: number, _end: boolean) => {
setUploadProgress(progress * progRate + progOffset)
})
}
}
// DDSP-SVC
@ -333,19 +352,22 @@ export const useServerSetting = (props: UseServerSettingProps): ServerSettingSta
ddspSvcModels[i].filename = await ddspSvcModels[i].file!.name
}
}
for (let i = 0; i < ddspSvcModels.length; i++) {
const progRate = 1 / ddspSvcModels.length
const progOffset = 100 * i * progRate
const dir = i == 0 || i == 1 ? "ddsp_mod/" : "ddsp_diff/"
await _uploadFile(ddspSvcModels[i], (progress: number, _end: boolean) => {
setUploadProgress(progress * progRate + progOffset)
}, dir)
if (fileUploadSetting.isSampleMode == false) {
for (let i = 0; i < ddspSvcModels.length; i++) {
const progRate = 1 / ddspSvcModels.length
const progOffset = 100 * i * progRate
const dir = i == 0 || i == 1 ? "ddsp_mod/" : "ddsp_diff/"
await _uploadFile(ddspSvcModels[i], (progress: number, _end: boolean) => {
setUploadProgress(progress * progRate + progOffset)
}, dir)
}
}
// const configFileName = fileUploadSetting.configFile?.filename || "-"
const params = JSON.stringify({
trans: fileUploadSetting.defaultTune || 0,
files: {
sampleId: fileUploadSetting.isSampleMode ? fileUploadSetting.sampleId || "" : "",
files: fileUploadSetting.isSampleMode ? {} : {
mmvcv13Config: fileUploadSetting.mmvcv13Config?.filename || "",
mmvcv13Model: fileUploadSetting.mmvcv13Model?.filename || "",
mmvcv15Config: fileUploadSetting.mmvcv15Config?.filename || "",
@ -360,32 +382,35 @@ export const useServerSetting = (props: UseServerSettingProps): ServerSettingSta
rvcIndex: fileUploadSetting.rvcIndex?.filename || "",
rvcFeature: fileUploadSetting.rvcFeature?.filename || "",
ddspSvcModel: fileUploadSetting.ddspSvcModel?.filename ? "ddsp_mod/" + fileUploadSetting.ddspSvcModel?.filename : "",
ddspSvcModelConfig: fileUploadSetting.ddspSvcModelConfig?.filename ? "ddsp_mod/" + fileUploadSetting.ddspSvcModelConfig?.filename : "",
ddspSvcDiffusion: fileUploadSetting.ddspSvcDiffusion?.filename ? "ddsp_diff/" + fileUploadSetting.ddspSvcDiffusion?.filename : "",
ddspSvcDiffusionConfig: fileUploadSetting.ddspSvcDiffusionConfig?.filename ? "ddsp_diff/" + fileUploadSetting.ddspSvcDiffusionConfig.filename : "",
}
})
if (fileUploadSetting.isHalf == undefined) {
fileUploadSetting.isHalf = false
}
console.log("PARAMS:", params)
const loadPromise = props.voiceChangerClient.loadModel(
slot,
fileUploadSetting.isHalf,
params,
)
// サーバでロード中にキャッシュにセーブ
storeToCache(slot, fileUploadSetting)
await loadPromise
fileUploadSetting.uploaded = true
fileUploadSettings[slot] = fileUploadSetting
setFileUploadSettings([...fileUploadSettings])
setUploadProgress(0)
setIsUploading(false)
reloadServerInfo()
@ -420,6 +445,9 @@ export const useServerSetting = (props: UseServerSettingProps): ServerSettingSta
ddspSvcModelConfig: fileUploadSetting.ddspSvcModelConfig ? { data: fileUploadSetting.ddspSvcModelConfig.data, filename: fileUploadSetting.ddspSvcModelConfig.filename } : null,
ddspSvcDiffusion: fileUploadSetting.ddspSvcDiffusion ? { data: fileUploadSetting.ddspSvcDiffusion.data, filename: fileUploadSetting.ddspSvcDiffusion.filename } : null,
ddspSvcDiffusionConfig: fileUploadSetting.ddspSvcDiffusionConfig ? { data: fileUploadSetting.ddspSvcDiffusionConfig.data, filename: fileUploadSetting.ddspSvcDiffusionConfig.filename } : null,
isSampleMode: fileUploadSetting.isSampleMode,
sampleId: fileUploadSetting.sampleId,
}
setItem(`${INDEXEDDB_KEY_MODEL_DATA}_${slot}`, saveData)
} catch (e) {

View File

@ -17,7 +17,12 @@ from mods.ssl import create_self_signed_cert
from voice_changer.VoiceChangerManager import VoiceChangerManager
from sio.MMVC_SocketIOApp import MMVC_SocketIOApp
from restapi.MMVC_Rest import MMVC_Rest
from const import NATIVE_CLIENT_FILE_MAC, NATIVE_CLIENT_FILE_WIN, SSL_KEY_DIR
from const import (
NATIVE_CLIENT_FILE_MAC,
NATIVE_CLIENT_FILE_WIN,
SAMPLES_JSON,
SSL_KEY_DIR,
)
import subprocess
import multiprocessing as mp
from misc.log_control import setup_loggers
@ -41,6 +46,8 @@ def setupArgParser():
default=True,
help="generate self-signed certificate",
)
parser.add_argument("--samples", type=str, help="path to samples")
parser.add_argument("--model_dir", type=str, help="path to model files")
parser.add_argument(
@ -214,6 +221,7 @@ if __name__ == "MMVCServerSIO":
mp.freeze_support()
voiceChangerParams = VoiceChangerParams(
model_dir=args.model_dir,
samples=args.samples,
content_vec_500=args.content_vec_500,
content_vec_500_onnx=args.content_vec_500_onnx,
content_vec_500_onnx_on=args.content_vec_500_onnx_on,
@ -223,6 +231,11 @@ if __name__ == "MMVCServerSIO":
nsf_hifigan=args.nsf_hifigan,
)
try:
download({"url": SAMPLES_JSON, "saveTo": args.samples, "position": 0})
except Exception as e:
print("[Voice Changer] loading sample failed", e)
if (
os.path.exists(voiceChangerParams.hubert_base) is False
or os.path.exists(voiceChangerParams.hubert_base_jp) is False
@ -244,6 +257,7 @@ if __name__ == "__main__":
printMessage("Voice Changerを起動しています。", level=2)
downloadWeight()
os.makedirs(args.model_dir, exist_ok=True)
PORT = args.p

36
server/ModelSample.py Normal file
View File

@ -0,0 +1,36 @@
from dataclasses import dataclass
import json
from const import ModelType
@dataclass
class RVCModelSample:
id: str = ""
name: str = ""
modelUrl: str = ""
indexUrl: str = ""
featureUrl: str = ""
termOfUseUrl: str = ""
credit: str = ""
description: str = ""
def getModelSamples(jsonPath: str, modelType: ModelType):
try:
with open(jsonPath, "r", encoding="utf-8") as f:
jsonDict = json.load(f)
modelList = jsonDict[modelType]
if modelType == "RVC":
samples: list[RVCModelSample] = []
for s in modelList:
modelSample = RVCModelSample(**s)
samples.append(modelSample)
return samples
else:
raise RuntimeError(f"Unknown model type {modelType}")
except Exception as e:
print("[Voice Changer] loading sample info error:", e)
return None

View File

@ -95,3 +95,6 @@ class EnumFrameworkTypes(Enum):
class ServerAudioDeviceTypes(Enum):
audioinput = "audioinput"
audiooutput = "audiooutput"
SAMPLES_JSON = "https://huggingface.co/wok000/vcclient_model/raw/main/samples.json"

View File

@ -78,7 +78,7 @@ class MMVC_Rest_Fileuploader:
params: str = Form(...),
):
paramDict = json.loads(params)
print("paramDict", paramDict)
# print("paramDict", paramDict)
# Change Filepath
newFilesDict = {}

44
server/samples.json Normal file
View File

@ -0,0 +1,44 @@
{
"RVC": [
{
"id": "KikotoKurage_Song",
"name": "黄琴海月(Song)",
"modelUrl": "https://huggingface.co/wok000/vcclient_model/resolve/main/rvc/kikoto_kurage_song/KikotoKurage_Song.pth",
"indexUrl": "https://huggingface.co/wok000/vcclient_model/resolve/main/rvc/kikoto_kurage_song/added_IVF2252_Flat_nprobe_10.index.bin",
"featureUrl": "https://huggingface.co/wok000/vcclient_model/resolve/main/rvc/kikoto_kurage_song/total_fea.npy",
"termOfUseUrl": "https://huggingface.co/wok000/vcclient_model/raw/main/rvc/kikoto_kurage_song/term_of_use.txt",
"credit": "黄琴海月",
"description": ""
},
{
"id": "KikotoKurage_Talk",
"name": "黄琴海月(Talk)",
"modelUrl": "https://huggingface.co/wok000/vcclient_model/resolve/main/rvc/kikoto_kurage_talk/KikotoKurage_Talk.pth",
"indexUrl": "https://huggingface.co/wok000/vcclient_model/resolve/main/rvc/kikoto_kurage_talk/added_IVF1022_Flat_nprobe_7.index.bin",
"featureUrl": "https://huggingface.co/wok000/vcclient_model/resolve/main/rvc/kikoto_kurage_talk/total_fea.npy",
"termOfUseUrl": "https://huggingface.co/wok000/vcclient_model/raw/main/rvc/kikoto_kurage_talk/term_of_use.txt",
"credit": "黄琴海月",
"description": ""
},
{
"id": "KikotoMahiro_Song",
"name": "黄琴まひろ(Song)",
"modelUrl": "https://huggingface.co/wok000/vcclient_model/resolve/main/rvc/kikoto_mahiro_song/KikotoMahiro_Song.pth",
"indexUrl": "https://huggingface.co/wok000/vcclient_model/resolve/main/rvc/kikoto_mahiro_song/added_IVF3621_Flat_nprobe_11.index.bin",
"featureUrl": "https://huggingface.co/wok000/vcclient_model/resolve/main/rvc/kikoto_mahiro_song/total_fea.npy",
"termOfUseUrl": "https://huggingface.co/wok000/vcclient_model/blob/main/rvc/kikoto_mahiro_song/term_of_use.txt",
"credit": "黄琴まひろ",
"description": ""
},
{
"id": "KikotoMahiro_Talk",
"name": "黄琴まひろ(Talk)",
"modelUrl": "https://huggingface.co/wok000/vcclient_model/resolve/main/rvc/kikoto_mahiro_talk/KikotoMahiro_Talk.pth",
"indexUrl": "https://huggingface.co/wok000/vcclient_model/resolve/main/rvc/kikoto_mahiro_talk/added_IVF1591_Flat_nprobe_9.index.bin",
"featureUrl": "https://huggingface.co/wok000/vcclient_model/resolve/main/rvc/kikoto_mahiro_talk/total_fea.npy",
"termOfUseUrl": "https://huggingface.co/wok000/vcclient_model/raw/main/rvc/kikoto_mahiro_talk/term_of_use.txt",
"credit": "黄琴まひろ",
"description": ""
}
]
}

View File

@ -20,9 +20,9 @@ def list_audio_device():
outputDeviceList = [d for d in audioDeviceList if d["max_output_channels"] > 0]
hostapis = sd.query_hostapis()
print("input:", inputAudioDeviceList)
print("output:", outputDeviceList)
print("hostapis", hostapis)
# print("input:", inputAudioDeviceList)
# print("output:", outputDeviceList)
# print("hostapis", hostapis)
serverAudioInputDevices = []
serverAudioOutputDevices = []

View File

@ -18,3 +18,8 @@ class ModelSlot:
embChannels: int = 256
deprecated: bool = False
embedder: EnumEmbedderTypes = EnumEmbedderTypes.hubert
name: str = ""
description: str = ""
credit: str = ""
termsOfUseUrl: str = ""

View File

@ -32,6 +32,12 @@ def generateModelSlot(slotDir: str):
modelSlot.indexFile = None
modelSlot.defaultTrans = params["trans"] if "trans" in params else 0
modelSlot.name = params["name"] if "name" in params else None
modelSlot.description = params["description"] if "description" in params else None
modelSlot.credit = params["credit"] if "credit" in params else None
modelSlot.termsOfUseUrl = (
params["termsOfUseUrl"] if "termsOfUseUrl" in params else None
)
modelSlot.isONNX = modelSlot.modelFile.endswith(".onnx")

View File

@ -1,3 +1,4 @@
from concurrent.futures import ThreadPoolExecutor
import sys
import os
import resampy
@ -5,6 +6,8 @@ from dataclasses import asdict
from typing import cast
import numpy as np
import torch
from MMVCServerSIO import download
from ModelSample import RVCModelSample, getModelSamples
# avoiding parse arg error in RVC
@ -35,7 +38,7 @@ from voice_changer.RVC.deviceManager.DeviceManager import DeviceManager
from voice_changer.RVC.pipeline.Pipeline import Pipeline
from Exceptions import NoModeLoadedException
from const import UPLOAD_DIR
from const import TMP_DIR, UPLOAD_DIR
import shutil
import json
@ -73,11 +76,86 @@ class RVC:
self.loadSlots()
print("RVC initialization: ", params)
sampleModels = getModelSamples(params.samples, "RVC")
if sampleModels is not None:
self.settings.sampleModels = sampleModels
# 起動時にスロットにモデルがある場合はロードしておく
if len(self.settings.modelSlots) > 0:
for i, slot in enumerate(self.settings.modelSlots):
if len(slot.modelFile) > 0:
self.prepareModel(i)
self.settings.modelSlotIndex = i
self.switchModel()
self.initialLoad = False
break
def getSampleInfo(self, id: str):
sampleInfos = list(filter(lambda x: x.id == id, self.settings.sampleModels))
if len(sampleInfos) > 0:
return sampleInfos[0]
else:
None
def downloadModelFiles(self, sampleInfo: RVCModelSample):
downloadParams = []
modelPath = os.path.join(TMP_DIR, os.path.basename(sampleInfo.modelUrl))
downloadParams.append(
{
"url": sampleInfo.modelUrl,
"saveTo": modelPath,
"position": 0,
}
)
indexPath = None
if hasattr(sampleInfo, "indexUrl") and sampleInfo.indexUrl != "":
indexPath = os.path.join(TMP_DIR, os.path.basename(sampleInfo.indexUrl))
downloadParams.append(
{
"url": sampleInfo.indexUrl,
"saveTo": indexPath,
"position": 1,
}
)
featurePath = None
if hasattr(sampleInfo, "featureUrl") or sampleInfo.featureUrl != "":
featurePath = os.path.join(TMP_DIR, os.path.basename(sampleInfo.featureUrl))
downloadParams.append(
{
"url": sampleInfo.featureUrl,
"saveTo": featurePath,
"position": 2,
}
)
with ThreadPoolExecutor() as pool:
pool.map(download, downloadParams)
return modelPath, indexPath, featurePath
def loadModel(self, props: LoadModelParams):
target_slot_idx = props.slot
params = props.params
# modelName = os.path.splitext(os.path.basename(params["files"]["rvcModel"]))[0]
print("loadModel", params)
if len(params["sampleId"]) > 0:
sampleInfo = self.getSampleInfo(params["sampleId"])
if sampleInfo is None:
print("[Voice Changer] sampleInfo is None")
return
modelPath, indexPath, featurePath = self.downloadModelFiles(sampleInfo)
params["files"]["rvcModel"] = modelPath
if indexPath is not None:
params["files"]["rvcIndex"] = indexPath
if featurePath is not None:
params["files"]["rvcFeature"] = featurePath
params["credit"] = sampleInfo.credit
params["description"] = sampleInfo.description
params["name"] = sampleInfo.name
params["sampleId"] = sampleInfo.id
params["termOfUseUrl"] = sampleInfo.termOfUseUrl
slotDir = os.path.join(
self.params.model_dir, RVC_MODEL_DIRNAME, str(target_slot_idx)
)
@ -95,6 +173,16 @@ class RVC:
shutil.move(f, dst)
json.dump(params, open(os.path.join(slotDir, "params.json"), "w"))
self.loadSlots()
# 初回のみロード(起動時にスロットにモデルがあった場合はinitialLoadはFalseになっている)
if self.initialLoad:
self.prepareModel(target_slot_idx)
self.settings.modelSlotIndex = target_slot_idx
self.switchModel()
self.initialLoad = False
elif target_slot_idx == self.currentSlot:
self.prepareModel(target_slot_idx)
return self.get_info()
def loadSlots(self):
@ -110,24 +198,6 @@ class RVC:
modelSlot = generateModelSlot(slotDir)
self.settings.modelSlots.append(modelSlot)
# modelSlot = generateModelSlot(params)
# self.settings.modelSlots[target_slot_idx] = modelSlot
# print(
# f"[Voice Changer] RVC new model is uploaded,{target_slot_idx}",
# asdict(modelSlot),
# )
# # 初回のみロード
# if self.initialLoad:
# self.prepareModel(target_slot_idx)
# self.settings.modelSlotIndex = target_slot_idx
# self.switchModel()
# self.initialLoad = False
# elif target_slot_idx == self.currentSlot:
# self.prepareModel(target_slot_idx)
# return self.get_info()
def update_settings(self, key: str, val: int | float | str):
if key in self.settings.intData:
# 設定前処理

View File

@ -1,4 +1,5 @@
from dataclasses import dataclass, field
from ModelSample import RVCModelSample
from voice_changer.RVC.ModelSlot import ModelSlot
@ -25,6 +26,8 @@ class RVCSettings:
ModelSlot(), # 6(merged)
]
)
sampleModels: list[RVCModelSample] = field(default_factory=lambda: [])
indexRatio: float = 0
rvcQuality: int = 0
silenceFront: int = 1 # 0:off, 1:on

View File

@ -4,6 +4,7 @@ from dataclasses import dataclass
@dataclass
class VoiceChangerParams:
model_dir: str
samples: str
content_vec_500: str
content_vec_500_onnx: str
content_vec_500_onnx_on: bool