Skip to content

Instantly share code, notes, and snippets.

@zironycho
Last active November 22, 2023 05:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zironycho/c99a02117d6ec5318c9d6d831c9f82ce to your computer and use it in GitHub Desktop.
Save zironycho/c99a02117d6ec5318c9d6d831c9f82ce to your computer and use it in GitHub Desktop.
Typecast api with textfile (such as news)
11월 7일 오후 (하지만 오전일 수도 있는?) 뉴스입니다.
<break time="1000ms" />
<unknown time="1000ms" />
입동을 하루 앞두고 전국 대부분 지역의 기온이 어제보다 10도 넘게 떨어졌습니다.
내일은 오늘보다 더 쌀쌀하겠고 모레 기온이 조금 오르지만, 주말부터 다시 추워질 전망입니다.
<break time="1000ms" />
병원에서 치료를 받다가 도망친 특수강도 피의자 김길수가 도주 70여 시간 만에 어젯밤 경기도 의정부에서 붙잡혔습니다.
김길수는 체포 직후 "도주계획이나 조력자가 없었다"고 주장했습니다.
<break time="1000ms" />
전 펜싱 국가대표 남현희 씨가 결혼 상대였던 전정조 씨의 사기 혐의 공범으로 입건돼 10시간 가까이 조사를 받고 귀가했습니다.
남씨는 경찰 조사에서 혐의를 부인하며 전씨의 사기 행각을 전혀 알지 못했단 취지로 진술한 것으로 전해졌습니다.
<break time="1000ms" />
구테흐스 유엔 사무총장이 이스라엘과 하마스 간 전쟁으로 "가자지구가 어린이의 무덤이 되고 있다"며 휴전을 촉구했습니다.
가자지구 참사에 대한 우려가 커지는 가운데 미 백악관은 바이든 대통령이 네타냐후 이스라엘 총리와 통화하고 교전 중지를 논의했다고 밝혔습니다.
<break time="500ms" />
이상 7일 오후 뉴스였습니다.
import requests
import time
import re
import xml.etree.ElementTree as ET
from io import BytesIO
from pydub import AudioSegment
API_TOKEN = '{{your api token}}'
HEADERS = {'Authorization': f'Bearer {API_TOKEN}'}
def _get_synthesis_audio(line, actor_id):
# request speech synthesis
r = requests.post('https://typecast.ai/api/speak', headers=HEADERS, json={
'lang': 'auto',
'style_label_version': 'latest',
'xapi_hd': True,
'actor_id': actor_id,
'text': line,
})
speak_url = r.json()['result']['speak_v2_url']
# polling the speech synthesis result
for _ in range(120):
r = requests.get(speak_url, headers=HEADERS)
ret = r.json()['result']
# audio is ready
if ret['status'] == 'done':
r = requests.get(ret['audio_download_url'])
return BytesIO(r.content)
# syntheis failed
elif ret['status'] == 'failed':
print('[ERRO]', 'synthesis wait request failed')
return None
else:
print('[INFO]', f"synthesis status: {ret['status']}, waiting 1 second")
time.sleep(1)
return None
def _str2msec(timestr):
# milliseconds
r = re.match('(\d+)ms', timestr)
if r:
return int(r.group(1))
# seconds
r = re.match('(\d+)s', timestr)
if r:
return int(int(r.group(1)) * 1000)
# exception
return 0
def _export_audio(out_audio_path, all_segments):
output = all_segments[0]
for a in all_segments[1:]:
output += a
output.export(out_audio_path, format='wav')
def _preprocess_text(line):
line = re.sub(r'\(.*?\)', '', line)
return line
def main(out_audio_path, script_path, actor_id):
# get script each line
with open(script_path) as f:
lines = [line.strip() for line in f.readlines()]
all_audio_segments = []
for line in lines:
print('[INFO]', f'-> source line: {line}')
# add break
try:
tree = ET.fromstring(line)
if tree.tag != 'break':
print('[WARN]', f'ignore this tag: {tree.tag}')
continue
break_msec = _str2msec(tree.attrib['time'].strip())
print('[INFO]', f'break: {break_msec} milliseconds')
all_audio_segments.append(AudioSegment.silent(duration=break_msec))
# add audio
except Exception:
line = _preprocess_text(line)
print('[INFO]', f'preprocessed text: {line}')
audio = _get_synthesis_audio(line, actor_id)
if audio:
all_audio_segments.append(AudioSegment.from_wav(audio))
else:
print('[ERRO]', 'failed to get audio')
return None
_export_audio(out_audio_path, all_audio_segments)
if '__main__' == __name__:
# choose your voice actor
r = requests.get('https://typecast.ai/api/actor', headers=HEADERS)
my_actors = r.json()['result']
my_first_actor = my_actors[0]
my_first_actor_id = my_first_actor['actor_id']
in_script_path = 'data/news.txt'
out_audio_path = '_out.wav'
main(out_audio_path, in_script_path, my_first_actor_id)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment