複数の文字を一括で変換する(SSML作成)

**塩漬け記事第二弾**
あまりに塩漬けしすぎたので、もう本人もよく覚えていません


# 序文(かなり長くてどうでもいい)
[読み飛ばす](#本文)
### 昔
SAPIってのがありました。確かWindowsXPからだったと思いましたがもっと前からかもしれません。Speech APIでPCを喋らせることが出来ました。
ですが、日本語に対応はしていたものの、日本語音声は含まれていなかったので、サードパーティーから音声合成ソフトと銘打っていろんなものが発売されました。

この中の１つがAquosTalkです。今でもゆっくりとして使われています
### ちょっと昔
SAPIは4.0位までバージョンアップしたあと、MSSPってのになりました。使い方はあんまり変わらないんですが、データが変わったらしく、SAPIに対応した音声をそのまま使用はできなくなりました。
そして、その間Microsoft Ayumiとか言うデータが標準で付属するようになりました。

### 今
Azure Cognitive Servicesのspeechサービスと言う物になりました。Azureと付くとおり、これは有料のサービスです。ですが、音声認識も使えるようになっているようです

### 今でもSAPI
MSSPは音声データがMicrosoftのしか無かったので、全然一般的にはなりませんでした。Azureに関しては音声合成だけの為に有料サービスを使うのは余りにもコスパが悪いので、音声合成としてはやっぱり普及していません

### SAPIはちょっと
頭が悪いです。普通に読ませようとしてもよくわからない読み方をします。同じ漢字でも状況によって別の読み方をしたりします。
そういうときの為に、lexiconとか言う読み方辞書が使えるらしいのですが、SAPIはバグでこれがまともに動作しません。
そして、古いAPIなのでMSは直す気がありません。
なので、VOICEROIDなどでは、独自の音声合成エンジンを使っているようです。

### で、どうするかと言うと
SSMLってので、しゃべり方を細かく指定してやる必要があります。
SSMLはXMLなので、テキストからSSMLに変換するには、それなりに文章を置換する必要があります

# 本文
Pythonは速度が遅いので、置換などの文字列走査を行うときには、標準の関数やメソッドを使うのが良いのですが、これらは基本１回の走査で１つの検索を前提にしているので、複数の物を検索するときには同じ文字列を何度も走査することになります。

また、操作後に置換などの処理をすると、置換の順番によっては、置換後の文字列が再び置換されてしまいます。

例えば、AAABCCD を複数の置換をするとき
A → B
B → C
C → D
D → E
を順番に置換すると
EEEEEEE になってしまいます。

まあ、そんな感じで複数の置換候補を一括で置換するPythonスクリプトです

```Python:SSML.py
# Text to SSML
#
# def crcutter( text , replace chars ) :
#   Cut CR,LF, CRLF
#
#   text : text
#   replace chars : replaced characters
# Retuens
#   text : text
# def miltireplace ( text , Replace List) :
#   text : text
#    replace list: [ [ old words, new words, 'Token|Sub|Phoneme' ], ... ]
#
# Return ( new text , Replace Textlist)
#   new text : text
#   Replace Textlist : [ [ start char , size , Old Text,'Token|Sub|Phoneme' ], ... ]
#
# replistcheck(Replace List) :
# replace list required XML
#
# makessml :
#
#


"""
def make_nosign_list(text : str, sign_chars : str):
    r = []
    for i, c in enumerate(text):
        if c not in sign_chars:
            r.append(i)
    return r

def cut_text(text : str, nosign_list, start, size):
    return text[nosign_list[start] : nosign_list[start] + size]

def cut_text_without_sign(text : str, nosign_list, sign_chars, start, size):
    if len(text) < start:
        raise ValueError("string over")
    r = ""
    sk = 0
    for i,c in enumerate(text[start : start + size]):
        if c in sign_chars:
            sk += 1
        ad = start + i + sk
        if i < len(text):
            r += text[ad]
    return r
"""

def crcutter( text , crs):
    return text.replace('\r\n',crs).replace('\n',crs).replace('\r',crs)

def sortlong(lst):
    return sorted(lst, key=lambda x: len(x[0]))[::-1]

def multireplace(text, replist):
    replist = replist +[
        ['"',"&quot;",''],
        ['\'',"&apos;",''],
        ['<',"&lt;",''],
        ['>',"&gt;",''],
        ['&',"&amp;",'']]
    sr = sortlong(replist)
    rt = ""
    cr = []
    c = 0
    while c < len(text):
        flg = False
        for l in sr:
            if text[c:c+len(l[0])] == l[0]:
                cr.append([len(rt),len(l[1]),text[c:c+len(l[0])],l[2]])
                rt += l[1]
                c += len(l[0])
                flg = True
        if not flg:
            rt += text[c]
            c += 1
    return rt,cr

def makessml(text, rephistory, locale, brk):
    xmlp = '"\'<>&'
    crs = '

'
    brt = '<break time="'+str(brk)+'ms" />'

    rephistory = sorted(rephistory)
    rt = '<speak version="1.0" xml:lang="'+locale+'">
'
    p = 0
    for i in rephistory:
        rt += text[p:i[0]]
        p = i[0]+i[1]
        if i[2] in xmlp:
            rt += text[i[0]:i[0]+i[1]]
        elif i[1] == 0:
            pass
        else:
            if i[3] == "Token":
                rt += '<token>' + text[i[0]:i[0]+i[1]] + '</token>'
            elif i[3] == "Sub":
                rt += '<sub alias="' + i[2] + '">' + text[i[0]:i[0]+i[1]] + '</sub>'
            elif i[3] == "Phoneme":
                rt += '<phoneme ph="' + i[2] + '">' + text[i[0]:i[0]+i[1]] + '</phoneme>'
            else:
                rt += text[i[0]:i[0]+i[1]]
    rt += text[p:]

    rt = rt.replace('\r\n',crs).replace('\n',crs).replace('\r',crs)
    rt += '
\n</speak>'
    rt = rt.replace('

','')
    rt = rt.replace('
','
\n' + brt + '\n' )
    return rt



def replistcheck(replist):
    r = []
    for i in replist:
        rl = ""
        for j in i[1]:
            if j == '"': rl += "&quot;"
            elif j == '\'': rl += "&apos;"
            elif j == '<': rl += "&lt;"
            elif j == '>': rl += "&gt;"
            elif j == '&': rl += "&amp;"
            elif j == '"': rl += "&quot;"
            else: rl += j
        r += [[i[0] , rl , i[2]]]
    return r

def SSML( text , replist ,locale, brk):
    r = replistcheck( replist )
    t,l = multireplace(text, r)
    return makessml( t, l , locale, brk)

if __name__ == "__main__":
    pass
```