Skip to content

Instantly share code, notes, and snippets.

@kasuganosora
Last active October 3, 2022 06:45
Show Gist options
  • Save kasuganosora/25f4df2b6f520c2981389117fbeb13cb to your computer and use it in GitHub Desktop.
Save kasuganosora/25f4df2b6f520c2981389117fbeb13cb to your computer and use it in GitHub Desktop.
提取近月少女的礼仪的语音和脚本变成vits的训练语料

用法

使用GARbro 提取游戏的

  • s文件放入到script目录中
  • ogg文件文件放入 voice 中
package main
import (
"fmt"
"github.com/faiface/beep"
"github.com/faiface/beep/vorbis"
"github.com/faiface/beep/wav"
"golang.org/x/text/encoding/japanese"
"golang.org/x/text/transform"
"io"
"log"
"math/rand"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
)
func main() {
if len(os.Args) < 2 {
log.Println("请输入角色名, 多个角色用 _ 分割")
return
}
characterName := os.Args[1]
characterId := -1
if len(os.Args) > 2 {
characterId, _ = strconv.Atoi(os.Args[2])
}
// 当前目录下面创建DUMMY_WAV文件夹
pwd, _ := os.Getwd()
// 当前文件夹新建 output 文件夹
outputDir := filepath.Join(pwd, "output")
if _, err := os.Stat(outputDir); os.IsNotExist(err) {
_ = os.Mkdir(outputDir, 0755)
}
dummyWavDir := filepath.Join(outputDir, "DUMMY_WAV")
if _, err := os.Stat(dummyWavDir); os.IsNotExist(err) {
_ = os.Mkdir(dummyWavDir, 0755)
}
scripts := getScripts(characterName)
log.Println("共有", len(scripts), "条台词, 现在进行音频转换并且写入文件列表...")
hashMap := make(map[string]bool) // 去除重复的台词
trainFilelist := make([]string, 0)
testFilelist := make([]string, 0)
for _, script := range scripts {
if _, ok := hashMap[script.Text]; ok {
continue
}
hashMap[script.Text] = true
oggFileName := filepath.Join(pwd, "voice", script.Voice+".ogg")
wavFileName := filepath.Join(dummyWavDir, script.Voice+".wav")
if err := oggToWav(oggFileName, wavFileName); err != nil {
log.Println(err)
continue
}
var fileLine string
if characterId == -1 {
fileLine = fmt.Sprintf("DUMMY_WAV/%s.wav|%s", script.Voice, script.Text)
} else {
fileLine = fmt.Sprintf("DUMMY_WAV/%s.wav|%d|%s", script.Voice, characterId, script.Text)
}
// 随机80%的数据用于训练, 20%的数据用于测试
randNum := rand.Intn(100)
if randNum < 80 {
trainFilelist = append(trainFilelist, fileLine)
} else {
testFilelist = append(testFilelist, fileLine)
}
}
if err := saveToFile(filepath.Join(outputDir, characterName+"_train_filelist.txt"), strings.Join(trainFilelist, "\n")); err != nil {
panic(err)
}
if err := saveToFile(filepath.Join(outputDir, characterName+"_test_filelist.txt"), strings.Join(testFilelist, "\n")); err != nil {
panic(err)
}
}
// Script 台词
type Script struct {
Text string
Voice string
}
// getScripts 获取台词
// 遍历 script 文件夹下的s文件, 获取对应角色的所有台词
func getScripts(characterName string) (ret []Script) {
ret = make([]Script, 0)
// 遍历 script 文件夹下的s文件
pwd, _ := os.Getwd()
scriptDir := filepath.Join(pwd, "script")
charactersName := strings.Split(characterName, "_")
err := filepath.Walk(scriptDir, func(path string, info os.FileInfo, err error) error {
if err != nil || info.IsDir() {
return nil
}
if !strings.HasSuffix(info.Name(), ".s") {
return nil
}
// 打开文件
f, err := os.Open(path)
if err != nil {
return err
}
log.Println("open file: ", path)
defer f.Close()
// 编码转换 从JIS转换为UTF-8
reader := transform.NewReader(f, japanese.ShiftJIS.NewDecoder())
content, err := io.ReadAll(reader)
if err != nil {
return err
}
// 用正则表达式匹配出所有的台词
// %v_yuu0182
// 【大蔵遊星】
// 「これでいい……?」
// ^message,show:true
// ^music01,file:BGM42
re := regexp.MustCompile(`%(.+?)\s+【(.+?)】\s+「([^」]+)」`)
matches := re.FindAllStringSubmatch(string(content), -1)
for _, match := range matches {
if len(match) < 3 {
continue
}
if strContains(match[2], charactersName) {
s := Script{
Text: DBC2SBC(strings.TrimSpace(match[3])),
Voice: strings.TrimSpace(match[1]),
}
if s.Voice == "" {
continue
}
// 如果 去掉 …… 后的台词为空, 则不添加
if strings.TrimSpace(strings.ReplaceAll(s.Text, "……", "")) == "" {
continue
}
ret = append(ret, s)
fmt.Printf("%s: %s\n", s.Text, s.Voice)
}
}
return nil
})
if err != nil {
log.Println(err)
return
}
return
}
func strContains(str string, substr []string) bool {
str = strings.TrimSpace(str)
for _, s := range substr {
if str == s {
return true
}
}
return false
}
func DBC2SBC(s string) string {
var strLst []string
for _, i := range s {
insideCode := i
if insideCode == 12288 {
insideCode = 32
} else {
insideCode -= 65248
}
if insideCode < 32 || insideCode > 126 {
strLst = append(strLst, string(i))
} else {
strLst = append(strLst, string(insideCode))
}
}
return strings.Join(strLst, "")
}
func oggToWav(oggFileName, wavFileName string) (err error) {
// 如果文件已经存在, 则不再转换
if _, err := os.Stat(wavFileName); err == nil {
return nil
}
f, err := os.Open(oggFileName)
if err != nil {
return err
}
defer f.Close()
s, format, err := vorbis.Decode(f)
if err != nil {
return err
}
defer s.Close()
sr := beep.SampleRate(22050)
// 保存为 wav 文件
f, err = os.Create(wavFileName)
if err != nil {
return err
}
format.SampleRate = sr
format.NumChannels = 1
if err = wav.Encode(f, s, format); err != nil {
return err
}
return
}
func saveToFile(fileName string, content string) error {
f, err := os.Create(fileName)
if err != nil {
return err
}
defer f.Close()
_, _ = io.WriteString(f, content)
return nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment