代码改变世界

袅袅音源转UTAU音源的工具--nn2utau

2015-05-03 13:37  步登北邙阪  阅读(3550)  评论(0)    收藏  举报

Python写的,上一个脚本多加了几行代码,作用为,将袅袅音源的inf.d和voice.d两个文件拆解成UTAU音源,并且还原打包之前的wav文件和inf文件,可以用袅袅音源制作器打开。

拆解出来的的文件夹可以拖到UTAU目录作为音源使用,已经自带oto。需用UTAU的自动生成frq功能生成frq。

脚本源码及使用说明 https://files.cnblogs.com/files/bdbmb/nn2utau.zip

为了方便不想安装Python的同学,用py2exe制作了一个exe版,下载可以直接运行,请按说明操作。

exe版 https://files.cnblogs.com/files/bdbmb/nn2utau.rar

代码

# coding:utf-8

#袅袅音源拆解&袅袅音源转UTAU音源工具
#已知问题:红色区域覆盖范围固定,不按实际情况变化
#已知问题:绿线始终放在红线和开始之间二分之一处(这其实是袅袅处理采样之间过渡的方式)
#已知问题:扩张整音的音尾需要手动设定

import sys
import os
import base64
import binascii
INF_FILE = 'inf.d'      #inf打包文件
VOICE_FILE = 'voice.d'  #wav打包文件
NN_DIR = 'nn'           #解包文件放置的目录,是当前目录的子目录
OTO_TYPE = 0            #oto中右边蓝色区域的计算方式,0为整音,1为扩张整音,2为日语单独音
RED_AREA = 55           #红色区域离红线的距离,毫秒
EX_FWD = 20             #转成扩张整音时蓝色区域比尾帧向前移动的距离,毫秒

#将inf.d内容解码,按行返回。每行内容分割成一个列表。
def rparams():           
    f = open(INF_FILE,'r')
    params = f.readlines()
    del params[0:2]     #删掉那个v1和发音总数标记
    ctn = []
    for line in params:
        line = base64.decodestring(line)
        line = line.split()
        ctn.append(line)
    f.close()
    return ctn

#接受每一行转换成的列表,写入对应的inf文件
def infwriter(param):
    filename = param[0] + '.inf'
    f = open('nn\\' + filename,'wb')
    f.write('0 ')       #起始帧,因为音源制作器已经把空白切掉,所以都是0
    f.write(str(int(param[2])/2) + ' ')    #采样数
    for i in range(3,7):
        f.write(param[i] + ' ')
    f.write(param[7])                      #以上写入不需做处理的几个值和空格
    f.close()

#将字节数表示的长度转换成能写入wav文件头的表示方法。参见网上介绍wav文件头结构的文章。
def hexlength(length):
    a = hex(length)[2:].upper()            #去掉0x
    a = '0' * (8-len(a)) + a               #剩余位补0
    b = []
    for pn in [6,4,2,0]:                   #以两位为单位倒写
        b.append(a[pn:pn+2])
    return(binascii.a2b_hex(''.join(b)))   #字符串按含义转化为十六进制值

#按给定的属性列表写wav文件
def wavewriter(param):
    filename = param[0] + '.wav'
    f = open('nn\\' + filename,'wb')
    #写入文件头,由于袅袅要求16位单声道44100Hz的wave文件,所以文件头几乎是固定的,除了长度的标识
    #感谢网上介绍wav文件结构的文章~
    f.write('RIFF')
    f.write(hexlength(int(param[2])+44))
    f.write('WAVEfmt \x10\x00\x00\x00\x01\x00\x01\x00\x44\xac\x00\x00\x88\x58\x01\x00\x02\x00\x10\x00data')
    f.write(hexlength(int(param[2])))
    #按照给定的偏移量写入数据
    s = open(VOICE_FILE,'rb')
    s.seek(int(param[1]))
    temp = s.read(int(param[2]))
    f.write(temp)
    f.close()
    s.close()

#输入属性列表,返回oto文件的一行
def rotoline(param):
    def cvt(byte):                         #将字节表示的长度转换成毫秒
        return int(byte)*500/44100
    ctn = ['','0']                         #没有辅助记号,左边蓝色区域为0
    lt = cvt(param[2])
    s = 2 * cvt(param[3])                  #辅音开始
    ctn.append(str(s+RED_AREA))            #红色区域
    #按照生成模式不同,计算右边的蓝色区域
    #本人在做中文整音oto设定时经常把蓝色区域覆盖音尾的一半,这样不至于切掉音尾也不会造成较长的低音量时间
    if OTO_TYPE == 0:
        ctn.append(str(lt - cvt((int(param[2])+2*int(param[4]))/2)))
    #扩张整音将音尾完全切掉,并且用额外的音尾设定做X-Fade。额外的音尾设定需手动做,大约十几个
    elif OTO_TYPE == 1:
        ctn.append(str(lt - cvt(2*int(param[4])) + EX_FWD))
    #日语单独音,蓝色放在三分之一处一个点,个人经验
    elif OTO_TYPE == 2:
        ctn.append(str(lt - cvt((int(param[2])+4*int(param[4]))/3)))
    ctn.append(str(s))                     #辅音开始
    ctn.append(str(s/2))                   #绿线,偷懒按袅袅的方式处理了,请手动调整
    return param[0] + '.wav=' + ','.join(ctn) + '\n'

#主函数
def main():
    print u'袅袅音源库拆解&袅袅音源转UTAU音源工具'
    print u'请输入oto生成方式,0为中文整音,1为扩张整音(请手动补充几个音尾设定),2为日语单独音,直接回车默认中文整音'
    global OTO_TYPE
    OTO_TYPE = raw_input()
    if OTO_TYPE not in ['0','1','2']:
        OTO_TYPE = 0
    OTO_TYPE = int(OTO_TYPE)
    print u'正在处理,请稍等~'
    os.mkdir(NN_DIR)
    params = rparams()
    ctn = []
    for param in params:
        infwriter(param)
        wavewriter(param)
        ctn.append(rotoline(param))
    oto = open('nn\\' + 'oto.ini','w')
    oto.writelines(ctn)
    oto.close()
    print u'成功!按回车键退出'
    print u'现在您可以将nn目录直接做UTAU音源使用,或者用袅袅音源制作工具打开了'
    print u'不要忘了双击UTAU音源库设定对话框左下角两个按钮之间的空白区域,生成frq文件'
    raw_input()

if __name__ == '__main__':
    main()