Last active
November 22, 2023 05:39
-
-
Save zironycho/c99a02117d6ec5318c9d6d831c9f82ce to your computer and use it in GitHub Desktop.
Typecast api with textfile (such as news)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
11월 7일 오후 (하지만 오전일 수도 있는?) 뉴스입니다. | |
<break time="1000ms" /> | |
<unknown time="1000ms" /> | |
입동을 하루 앞두고 전국 대부분 지역의 기온이 어제보다 10도 넘게 떨어졌습니다. | |
내일은 오늘보다 더 쌀쌀하겠고 모레 기온이 조금 오르지만, 주말부터 다시 추워질 전망입니다. | |
<break time="1000ms" /> | |
병원에서 치료를 받다가 도망친 특수강도 피의자 김길수가 도주 70여 시간 만에 어젯밤 경기도 의정부에서 붙잡혔습니다. | |
김길수는 체포 직후 "도주계획이나 조력자가 없었다"고 주장했습니다. | |
<break time="1000ms" /> | |
전 펜싱 국가대표 남현희 씨가 결혼 상대였던 전정조 씨의 사기 혐의 공범으로 입건돼 10시간 가까이 조사를 받고 귀가했습니다. | |
남씨는 경찰 조사에서 혐의를 부인하며 전씨의 사기 행각을 전혀 알지 못했단 취지로 진술한 것으로 전해졌습니다. | |
<break time="1000ms" /> | |
구테흐스 유엔 사무총장이 이스라엘과 하마스 간 전쟁으로 "가자지구가 어린이의 무덤이 되고 있다"며 휴전을 촉구했습니다. | |
가자지구 참사에 대한 우려가 커지는 가운데 미 백악관은 바이든 대통령이 네타냐후 이스라엘 총리와 통화하고 교전 중지를 논의했다고 밝혔습니다. | |
<break time="500ms" /> | |
이상 7일 오후 뉴스였습니다. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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