I am looking for a small pause of 3 secound after each mp3 file made in the batch folder. I tried to modify the script to do this but I didn't succeed
import requests, base64, random, argparse, os, playsound, time, re, textwrap
#
voices = [
# ENGLISH VOICES
'en_au_001', # English AU - Female
]
def tts(session_id: str, text_speaker: str = "en_us_002", req_text: str = "TikTok Text To Speech", filename: str = 'voice.mp3', play: bool = False):
req_text = req_text.replace("+", "plus")
req_text = req_text.replace(" ", "+")
req_text = req_text.replace("&", "and")
headers = {
'User-Agent': 'com.zhiliaoapp.musically/2022600030 (Linux; U; Android 7.1.2; es_ES; SM-G988N; Build/NRD90M;tt-ok/3.12.13.1)',
'Cookie': f'sessionid={session_id}'
}
url = f"https://api22-normal-c-useast1a.tiktokv.com/media/api/text/speech/invoke/?text_speaker={text_speaker}&req_text={req_text}&speaker_map_type=0&aid=1233"
r = requests.post(url, headers = headers)
if r.json()["message"] == "Couldn't load speech. Try again.":
output_data = {"status": "Session ID is invalid", "status_code": 5}
print(output_data)
return output_data
vstr = [r.json()["data"]["v_str"]][0]
msg = [r.json()["message"]][0]
scode = [r.json()["status_code"]][0]
log = [r.json()["extra"]["log_id"]][0]
dur = [r.json()["data"]["duration"]][0]
spkr = [r.json()["data"]["speaker"]][0]
b64d = base64.b64decode(vstr)
with open(filename, "wb") as out:
out.write(b64d)
output_data = {
"status": msg.capitalize(),
"status_code": scode,
"duration": dur,
"speaker": spkr,
"log": log
}
print(output_data)
if play is True:
playsound.playsound(filename)
os.remove(filename)
return output_data
def tts_batch(session_id: str, text_speaker: str = 'en_us_002', req_text: str = 'TikTok Text to Speech', filename: str = 'voice.mp3'):
req_text = req_text.replace("+", "plus")
req_text = req_text.replace(" ", "+")
req_text = req_text.replace("&", "and")
headers = {
'User-Agent': 'com.zhiliaoapp.musically/2022600030 (Linux; U; Android 7.1.2; es_ES; SM-G988N; Build/NRD90M;tt-ok/3.12.13.1)',
'Cookie': f'sessionid={session_id}'
}
url = f"https://api22-normal-c-useast1a.tiktokv.com/media/api/text/speech/invoke/?text_speaker={text_speaker}&req_text={req_text}&speaker_map_type=0&aid=1233"
r = requests.post(url, headers=headers)
if r.json()["message"] == "Couldn't load speech. Try again.":
output_data = {"status": "Session ID is invalid", "status_code": 5}
print(output_data)
return output_data
vstr = [r.json()["data"]["v_str"]][0]
msg = [r.json()["message"]][0]
scode = [r.json()["status_code"]][0]
log = [r.json()["extra"]["log_id"]][0]
dur = [r.json()["data"]["duration"]][0]
spkr = [r.json()["data"]["speaker"]][0]
b64d = base64.b64decode(vstr)
with open(filename, "wb") as out:
out.write(b64d)
output_data = {
"status": msg.capitalize(),
"status_code": scode,
"duration": dur,
"speaker": spkr,
"log": log
}
print(output_data)
return output_data
def batch_create(filename: str = 'voice.mp3'):
out = open(filename, 'wb')
def sorted_alphanumeric(data):
convert = lambda text: int(text) if text.isdigit() else text.lower()
alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)]
return sorted(data, key=alphanum_key)
for item in sorted_alphanumeric(os.listdir('./batch/')):
filestuff = open('./batch/' + item, 'rb').read()
out.write(filestuff)
out.close()
def main():
parser = argparse.ArgumentParser(description = "Simple Python script to interact with the TikTok TTS API")
parser.add_argument("-v", "--voice", help = "the code of the desired voice")
parser.add_argument("-t", "--text", help = "the text to be read")
parser.add_argument("-s", "--session", help = "account session id")
parser.add_argument("-f", "--file", help = "use this if you wanna use 'text.txt'")
parser.add_argument("-n", "--name", help = "The name for the output file (.mp3)")
parser.add_argument("-p", "--play", action='store_true', help = "use this if you want to play your output")
args = parser.parse_args()
text_speaker = args.voice
if args.file is not None:
req_text = open(args.file, 'r', errors='ignore', encoding='utf-8').read()
else:
if args.text == None:
req_text = 'TikTok Text To Speech'
print('You need to have one form of text! (See README.md)')
else:
req_text = args.text
if args.play is not None:
play = args.play
if args.voice == None:
text_speaker = 'en_us_002'
print('You need to have a voice! (See README.md)')
if text_speaker == "random":
text_speaker = randomvoice()
if args.name is not None:
filename = args.name
else:
filename = 'voice.mp3'
if args.session is None:
print('FATAL: You need to have a TikTok session ID!')
exit(1)
if args.file is not None:
chunk_size = 200
textlist = textwrap.wrap(req_text, width=chunk_size, break_long_words=True, break_on_hyphens=False)
os.makedirs('./batch/')
for i, item in enumerate(textlist):
tts_batch(args.session, text_speaker, item, f'./batch/{i}.mp3')
batch_create(filename)
for item in os.listdir('./batch/'):
os.remove('./batch/' + item)
os.removedirs('./batch/')
return
tts(args.session, text_speaker, req_text, filename, play)
def randomvoice():
count = random.randint(0, 15)
text_speaker = voices[count]
return text_speaker
def sampler():
for item in voices:
text_speaker = item
filename = item
print(item)
req_text = 'TikTok Text To Speech Sample'
tts(text_speaker, req_text, filename)
if __name__ == "__main__":
main()
if I add more dots and commas in the text file then I manage to get breaks. but they are not good enough and I don't think this is the best solution
Code below is my test that has no pauses, sadly.. Any ideas or suggestions?
Related
I'm creating a backend function so that users of App A can import their details to App B. The flow is like this:
User uploads a zip file on the website. This zip contains csv files.
This zip file flows into S3.
Once in S3, it triggers a Lambda function.
The Lambda function then picks the zip file and starts processing the data inside the csv files.
I've completed Step 1,2 and 3. But in 4, the Lambda function is not able to read/process the file.
The python file works fine on my local device, so I think the issue is that it is not able to "get" the object from S3 correctly and so the read_zip doesn't work.
Relevant code below:
import <relevant libs>
s3Client = boto3.client('s3')
def lambda_handler(event,context):
bucket = event['Records'][0]['s3']['bucket']['name']
filename = event['Records'][0]['s3']['object']['key']
#tried printing these, displaying correctly
#response = s3Client.get_object(Bucket=bucket, Key=filename)
usefile = 'https://' + bucket + '.s3.ap-south-1.amazonaws.com/' + filename
print(usefile)
#printing correct filename
def read_csv(file):
to_return = []
reader = csv.DictReader(TextIOWrapper(file, 'utf-8'))
for row in reader:
to_return.append(row)
return to_return
def read_zip(usefile):
with ZipFile(usefile, 'r') as APPA_file:
with APPA_file.open("file1.csv", mode='r') as f:
file1 = read_csv(f)
with APPA_file.open("file2.csv", mode='r') as f:
file2 = read_csv(f)
return file1, file2
def get_APPB_url(APPA_uri):
resp = requests.get(APPA_uri)
if resp.status_code != 200:
return None
# extract the APPB url
re_match = re.findall('href="(https://www.X.org/.+/)"', resp.text)
if not re_match:
return None
print(resp.text)
return re_match[0]
def rate_on_APPB(APPB_url, rating):
re_match = re.findall('X.org/(.+)/', APPB_url)
if not re_match:
return None
APPB_id = re_match[0]
req_body = {
"query": <query used>,
"operationName": "<xyz>",
"variables": <variables>
}
headers = {
"content-type": "application/json",
"x-hasura-admin-secret": "XXX"
}
resp = requests.post("XXX", json=req_body, headers=headers)
if resp.status_code != 200:
raise ValueError(f"Hasura query failed. Code: {resp.status_code}")
else: print(APPB_id)
json_resp = resp.json()
if 'errors' in json_resp and len(json_resp['errors']) > 0:
first_error_msg = json_resp['errors'][0]['message']
if 'Authentication' in first_error_msg:
print(f"Failed to authenticate with cookie")
exit(1)
else:
raise ValueError(first_error_msg)
def APPA_to_APPB(APPA_dict):
APPB_url = get_APPB_url(APPA_dict['APPA URI'])
if APPB_url is None:
raise ValueError("Cannot find APPB title")
rate_on_APPB(APPB_url, int(float(APPA_dict['Rating']) * 2))
def main():
file1, file2 = read_zip(usefile)
success = []
errors = []
with tqdm(total=len(file1)) as pbar:
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
future_to_url = {
executor.submit(APPA_to_APPB, APPA_dict): APPA_dict for APPA_dict in file1
}
try:
for future in concurrent.futures.as_completed(future_to_url):
APPA_dict = future_to_url[future]
pbar.update(1)
try:
success.append(future.result())
except Exception as e:
errors.append({"APPA_dict": APPA_dict, "error": e})
except KeyboardInterrupt:
executor._threads.clear()
concurrent.futures.thread._threads_queues.clear()
print(f"Successfully rated: {len(success)} ")
print(f"{len(errors)} Errors")
for error in errors:
print(f"\t{error['APPA_dict']['Name']} ({error['APPA_dict']['Year']}): {error['error']}")
if __name__ == '__main__':
main()
CloudWatch log basically says Event started and ended successfully, nothing else. I was able to verify that the trigger was working fine by printing the file name and it appears in the log. But that's about it.
I'm new to programming and Fastapi and i have a problem when trying to run a script from a Postman. After first sentence from list, runAndWait() gets stuck and does not continue. When I run a script alone from for example tkinter it works fine.
I have threading because i want to pause, resume, etc. reading.
This is my script
import PyPDF2
import pyttsx3
import threading
import re
alphabets = "([A-Za-z])"
prefixes = "(Mr|St|Mrs|Ms|Dr)[.]"
suffixes = "(Inc|Ltd|Jr|Sr|Co)"
starters = "(Mr|Mrs|Ms|Dr|He\\s|She\\s|It\\s|They\\s|Their\\s|Our\\s|We\\s|But\\s|However\\s|That\\s|This\\s|Wherever)"
acronyms = "([A-Z][.][A-Z][.](?:[A-Z][.])?)"
websites = "[.](com|net|org|io|gov)"
digits = "([0-9])"
class Speaking(threading.Thread):
def __init__(self, list_of_sentences, **kw):
super().__init__(**kw)
self.sentences = list_of_sentences
self.paused = False
self.speaker = pyttsx3.init()
self.speaker.setProperty('rate', 160)
self.voices = self.speaker.getProperty('voices')
self.speaker.setProperty('voice', self.voices[0].id)
def run(self):
self.running = True
while self.sentences and self.running:
if not self.paused:
sentence = self.sentences.pop(0)
print(sentence)
self.speaker.say(sentence)
self.speaker.runAndWait()
print(self.running)
print("finished")
self.running = False
def stop(self):
self.running = False
def pause(self):
self.paused = True
def resume(self):
self.paused = False
speak = None
def read():
global speak
if speak is None or not speak.running:
speak = Speaking(book, daemon=True)
speak.start()
def stop():
global speak
if speak:
speak.stop()
speak = None
def pause():
if speak:
speak.pause()
def unpause():
if speak:
speak.resume()
def split_into_sentences(text):
text = " " + text + " "
text = text.replace("\n", " ")
text = re.sub(prefixes, "\\1<prd>", text)
text = re.sub(websites, "<prd>\\1", text)
text = re.sub(digits + "[.]" + digits, "\\1<prd>\\2", text)
if "..." in text: text = text.replace("...", "<prd><prd><prd>")
if "Ph.D" in text: text = text.replace("Ph.D.", "Ph<prd>D<prd>")
if "e.g." in text: text = text.replace("e.g.", "e<prd>g<prd>")
if "i.e." in text: text = text.replace("i.e.", "i<prd>e<prd>")
text = re.sub("\\s" + alphabets + "[.] ", " \\1<prd> ", text)
text = re.sub(acronyms + " " + starters, "\\1<stop> \\2", text)
text = re.sub(alphabets + "[.]" + alphabets + "[.]" + alphabets + "[.]", "\\1<prd>\\2<prd>\\3<prd>", text)
text = re.sub(alphabets + "[.]" + alphabets + "[.]", "\\1<prd>\\2<prd>", text)
text = re.sub(" "+suffixes+"[.] "+starters, " \\1<stop> \\2", text)
text = re.sub(" "+suffixes+"[.]", " \\1<prd>", text)
text = re.sub(" " + alphabets + "[.]", " \\1<prd>", text)
if "”" in text: text = text.replace(".”", "”.")
if "\"" in text: text = text.replace(".\"", "\".")
if "!" in text: text = text.replace("!\"", "\"!")
if "?" in text: text = text.replace("?\"", "\"?")
text = text.replace(".", ".<stop>")
text = text.replace("?", "?<stop>")
text = text.replace("!", "!<stop>")
text = text.replace("<prd>", ".")
sentences = text.split("<stop>")
sentences = sentences[:-1]
sentences = [s.strip() for s in sentences]
return sentences
book = None
def read_book(path, page_num):
global book
file = ""
page = page_num
# path = 'D:\Books\Hobbit.pdf'
pdfReader = PyPDF2.PdfFileReader(open(rf"{path}", 'rb'))
num_of_pages = pdfReader.numPages
for count in range(page, num_of_pages):
pageObj = pdfReader.getPage(count)
file += pageObj.extractText()
book = split_into_sentences(file)
And this is my API
from fastapi import HTTPException, Depends, APIRouter, status, Request, Form
from fastapi.responses import HTMLResponse
from sqlalchemy.orm import Session
from .. import schemas, models, oauth2
from ..database import get_db
from typing import Optional, List
from .. import audiobook
from fastapi.templating import Jinja2Templates
templates = Jinja2Templates(directory="app/templates")
#router.get("/{id}", response_class=HTMLResponse)
def get_book(request: Request, id: int, db: Session = Depends(get_db)):
book = db.query(models.Book).filter(models.Book.id == id).first()
if not book:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"book with id {id} is not found")
image = book.img
name = book.name
audiobook.read_book(book.path, 0)
return templates.TemplateResponse("audiobook.html", {"request": request, "image": image, "name": name})
#router.put("/{id}")
def get_book(to_do: schemas.BookPlay, id: int, db: Session = Depends(get_db)):
book = db.query(models.Book).filter(models.Book.id == id).first()
if not book:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"book with id {id} is not found")
if to_do.play == 0:
audiobook.read()
elif to_do.play == 1:
audiobook.pause()
elif to_do.play == 2:
audiobook.unpause()
elif to_do.play == 3:
audiobook.stop()
return HTMLResponse(status_code=status.HTTP_204_NO_CONTENT)
Any advice would be helpful. Thanks in advance.
Problem:
Telegram bot doesn't recognise seen messages and keeps responding to the latest message until I send "quit" or crtl-c in command line.
Completely new to python. There may be a flaw in my programming logic.
In 'for last_update_id in updates["result"]' I tried to add 1 to the last_update_id variable after each loop. But the variable doesn't seem to update.
# chatbot.py not included. It trains NN model.
import json
import requests
import time
import urllib
import telegram
TOKEN = "xxx"
URL = "https://api.telegram.org/bot{}/".format(TOKEN)
def get_url(url):
response = requests.get(url)
content = response.content.decode("utf8")
return content
def get_json_from_url(url):
content = get_url(url)
js = json.loads(content)
return js
def get_updates(offset): #gets json file from URL
url = URL + "getUpdates"
if offset:
url += "?offset={}".format(offset)
js = get_json_from_url(url)
return js
def get_last_update_id(updates):
update_ids = []
for update in updates["result"]:
update_ids.append(int(update["update_id"]))
return max(update_ids)
def get_last_chat_text(updates):
num_updates = len(updates["result"])
last_update = num_updates - 1
text = updates["result"][last_update]["message"]["text"] #text input
return text
def get_last_chat_id(updates):
chat_id = updates["result"][-1]["message"]["chat"]["id"]
return chat_id
def send_message(output,chat_id):
bot = telegram.Bot(token=TOKEN)
bot.sendMessage(chat_id=chat_id, text = output)
def main():
input_text = get_last_chat_text(updates)
return input_text
print("Let's chat! (type 'quit' to exit)")
last_update_id = None
while True:
updates = get_updates(last_update_id) #returns json file
last_update_id = get_last_update_id(updates) #returns max_update_id
for last_update_id in updates["result"]:
main()
input_text = main()
if input_text == "quit":
break
input_text = tokenize(input_text)
X = bag_of_words(input_text, all_words)
X = X.reshape(1, X.shape[0])
X = torch.from_numpy(X).to(device)
output = model(X)
_, predicted = torch.max(output, dim=1)
tag = tags[predicted.item()]
probs = torch.softmax(output, dim=1)
prob = probs[0][predicted.item()]
if prob.item() > 0.75:
for intent in intents['intents']:
if tag == intent["tag"]:
output = f"{random.choice(intent['responses'])}"
else:
output = f"{bot_name}: I do not understand..."
print(output)
chat_id = get_last_chat_id(updates)
print(chat_id)
send_message(output, chat_id)
time.sleep(0.1)
last_update_id =+ 1 #returns max_id in the json file and adds 1
continue
I managed to fix this problem by adding a break in the loop so it loops back to the outside 'while' loop. Below is the edited code:
# chatbot.py module imported above this line not included. It trains NN model.
import json
import requests
import time
import urllib
import telegram
TOKEN = "XXX"
URL = "https://api.telegram.org/bot{}/".format(TOKEN)
def get_url(url):
response = requests.get(url)
content = response.content.decode("utf8")
return content
def get_json_from_url(url):
content = get_url(url)
js = json.loads(content)
return js
def get_updates(offset): #gets json file from URL
url = URL + "getUpdates"
if offset:
url += "?offset={}".format(offset)
js = get_json_from_url(url)
return js
def get_last_update_id(updates):
update_ids = []
for update in updates["result"]:
update_ids.append(update["update_id"])
return max(update_ids, default = last_update_id)
def get_last_chat_text(updates):
# num_updates = len(updates["result"])
# last_update = num_updates - 1
text = updates["result"][-1]["message"]["text"] #text input
return text
def get_last_chat_id(updates):
chat_id = updates["result"][-1]["message"]["chat"]["id"]
return chat_id
def send_message(output,chat_id):
bot = telegram.Bot(token=TOKEN)
bot.sendMessage(chat_id=chat_id, text = output)
def main():
input_text = get_last_chat_text(updates)
return input_text
bot_name = "XXX"
print("Let's chat! (type 'quit' to exit)")
last_update_id = 0
while True:
updates = get_updates(last_update_id) #returns json file
for last_update_id in updates["result"]:
main()
input_text = main()
if input_text == "quit":
break
input_text = tokenize(input_text)
X = bag_of_words(input_text, all_words)
X = X.reshape(1, X.shape[0])
X = torch.from_numpy(X).to(device)
output = model(X)
_, predicted = torch.max(output, dim=1)
tag = tags[predicted.item()]
probs = torch.softmax(output, dim=1)
prob = probs[0][predicted.item()]
if prob.item() > 0.75:
for intent in intents['intents']:
if tag == intent["tag"]:
output = f"{random.choice(intent['responses'])}"
else:
output = f"{bot_name}: I do not understand..."
print(output)
chat_id = get_last_chat_id(updates)
print(chat_id)
send_message(output, chat_id)
time.sleep(0.1)
break
last_update_id = get_last_update_id(updates) + 1 #returns max_id in the json file and adds 1
I have a function that gets the profile data of an user:
API.py
def getProfileData(self):
data = json.dumps({
'_uuid' : self.uuid,
'_uid' : self.username_id,
'_csrftoken' : self.token
})
return self.SendRequest('accounts/current_user/?edit=true', self.generateSignature(data))
I want to print the returned request in the terminal, so I did this:
test.py
from API import API
API = API("username", "password")
API.login() # login
print(API.getProfileData())
But nothing is logged in the console.
Maybe I'm doing it the JavaScript way, since that's my background.
What's the correct way to do it?
EDIT:
This is what's inside SendRequest:
def SendRequest(self, endpoint, post = None, login = False):
if (not self.isLoggedIn and not login):
raise Exception("Not logged in!\n")
return;
self.s.headers.update ({'Connection' : 'close',
'Accept' : '*/*',
'Content-type' : 'application/x-www-form-urlencoded; charset=UTF-8',
'Cookie2' : '$Version=1',
'Accept-Language' : 'en-US',
'User-Agent' : self.USER_AGENT})
if (post != None): # POST
response = self.s.post(self.API_URL + endpoint, data=post) # , verify=False
else: # GET
response = self.s.get(self.API_URL + endpoint) # , verify=False
if response.status_code == 200:
self.LastResponse = response
self.LastJson = json.loads(response.text)
return True
else:
print ("Request return " + str(response.status_code) + " error!")
# for debugging
try:
self.LastResponse = response
self.LastJson = json.loads(response.text)
except:
pass
return False
def getTotalFollowers(self,usernameId):
followers = []
next_max_id = ''
while 1:
self.getUserFollowers(usernameId,next_max_id)
temp = self.LastJson
for item in temp["users"]:
followers.append(item)
if temp["big_list"] == False:
return followers
next_max_id = temp["next_max_id"]
def getTotalFollowings(self,usernameId):
followers = []
next_max_id = ''
while 1:
self.getUserFollowings(usernameId,next_max_id)
temp = self.LastJson
for item in temp["users"]:
followers.append(item)
if temp["big_list"] == False:
return followers
next_max_id = temp["next_max_id"]
def getTotalUserFeed(self, usernameId, minTimestamp = None):
user_feed = []
next_max_id = ''
while 1:
self.getUserFeed(usernameId, next_max_id, minTimestamp)
temp = self.LastJson
for item in temp["items"]:
user_feed.append(item)
if temp["more_available"] == False:
return user_feed
next_max_id = temp["next_max_id"]
If all you want to do is print the response that you get back, you can do that in SendRequest, but I suspect tha tyour real problem is that you are self-serializing your post data when requests does that for you. In any case, since your question is about printing:
if response.status_code == 200:
print('Yay, my response was: %s' % response.content)
self.LastResponse = response
self.LastJson = json.loads(response.text)
return True
else:
print ("Request return " + str(response.status_code) + " error!")
# for debugging
try:
self.LastResponse = response
self.LastJson = json.loads(response.text)
except:
pass
return False
I'm using the pyvona package (from July 3rd 2016). I have all dependencies installed. It works correctly when I call it to speak for the first time. But if I run the command again, it gives me the local unbound error:
>>> import pyvona
>>> v = pyvona.create_voice('<key>', '<secret>')
>>> v.speak('hello')
>>> v.speak('hello')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "C:\Python34\lib\site-packages\pyvona.py", line 159, in speak
channel.play(sound)
UnboundLocalError: local variable 'channel' referenced before assignment
>>>
Here's the pyvona.py script:
#!/usr/bin/env python
# encoding: utf-8
"""Pyvona : an IVONA python library
Author: Zachary Bears
Contact Email: bears.zachary#gmail.com
Note: Full operation of this library requires the requests and pygame libraries
"""
import datetime
import hashlib
import hmac
import json
import tempfile
import contextlib
import os
class PyvonaException(Exception):
pass
try:
import pygame
except ImportError:
pygame_available = False
else:
pygame_available = True
try:
import requests
requests.packages.urllib3.disable_warnings()
except ImportError:
msg = 'The requests library is essential for Pyvona operation. '
msg += 'Without it, Pyvona will not function correctly.'
raise PyvonaException(msg)
_amazon_date_format = '%Y%m%dT%H%M%SZ'
_date_format = '%Y%m%d'
def create_voice(access_key, secret_key):
"""Creates and returns a voice object to interact with
"""
return Voice(access_key, secret_key)
class Voice(object):
"""An object that contains all the required methods for interacting
with the IVONA text-to-speech system
"""
voice_name = None
language = None
gender = None
speech_rate = None
sentence_break = None
paragraph_break = None
_codec = "ogg"
region_options = {
'us-east': 'us-east-1',
'us-west': 'us-west-2',
'eu-west': 'eu-west-1',
}
access_key = None
secret_key = None
algorithm = 'AWS4-HMAC-SHA256'
signed_headers = 'content-type;host;x-amz-content-sha256;x-amz-date'
_region = None
_host = None
#property
def region(self):
return self._region
#region.setter
def region(self, region_name):
self._region = self.region_options.get(region_name, 'us-east-1')
self._host = 'tts.{}.ivonacloud.com'.format(self._region)
#property
def codec(self):
return self._codec
#codec.setter
def codec(self, codec):
if codec not in ["mp3", "ogg"]:
raise PyvonaException(
"Invalid codec specified. Please choose 'mp3' or 'ogg'")
self._codec = codec
#contextlib.contextmanager
def use_ogg_codec(self):
current_codec = self.codec
self.codec = "ogg"
try:
yield
finally:
self.codec = current_codec
def fetch_voice_ogg(self, text_to_speak, filename):
"""Fetch an ogg file for given text and save it to the given file name
"""
with self.use_ogg_codec():
self.fetch_voice(text_to_speak, filename)
def fetch_voice(self, text_to_speak, filename):
"""Fetch a voice file for given text and save it to the given file name
"""
file_extension = ".{codec}".format(codec=self.codec)
filename += file_extension if not filename.endswith(
file_extension) else ""
with open(filename, 'wb') as f:
self.fetch_voice_fp(text_to_speak, f)
def fetch_voice_fp(self, text_to_speak, fp):
"""Fetch a voice file for given text and save it to the given file pointer
"""
r = self._send_amazon_auth_packet_v4(
'POST', 'tts', 'application/json', '/CreateSpeech', '',
self._generate_payload(text_to_speak), self._region, self._host)
if r.content.startswith(b'{'):
raise PyvonaException('Error fetching voice: {}'.format(r.content))
else:
fp.write(r.content)
def speak(self, text_to_speak, use_cache=False):
"""Speak a given text
"""
if not pygame_available:
raise PyvonaException(
"Pygame not installed. Please install to use speech.")
if not pygame.mixer.get_init():
pygame.mixer.init()
channel = pygame.mixer.Channel(5)
if use_cache is False:
with tempfile.SpooledTemporaryFile() as f:
with self.use_ogg_codec():
self.fetch_voice_fp(text_to_speak, f)
f.seek(0)
sound = pygame.mixer.Sound(f)
else:
cache_f = hashlib.md5(text_to_speak).hexdigest() + '.ogg'
speech_cache_dir = os.getcwd() + '/speech_cache/'
if not os.path.isdir(speech_cache_dir):
os.makedirs(speech_cache_dir)
if not os.path.isfile(speech_cache_dir + cache_f):
with self.use_ogg_codec():
self.fetch_voice(text_to_speak, 'speech_cache/' + cache_f)
f = speech_cache_dir + cache_f
sound = pygame.mixer.Sound(f)
channel.play(sound)
while channel.get_busy():
pass
def list_voices(self):
"""Returns all the possible voices
"""
r = self._send_amazon_auth_packet_v4(
'POST', 'tts', 'application/json', '/ListVoices', '', '',
self._region, self._host)
return r.json()
def _generate_payload(self, text_to_speak):
return json.dumps({
'Input': {
"Type":"application/ssml+xml",
'Data': text_to_speak
},
'OutputFormat': {
'Codec': self.codec.upper()
},
'Parameters': {
'Rate': self.speech_rate,
'SentenceBreak': self.sentence_break,
'ParagraphBreak': self.paragraph_break
},
'Voice': {
'Name': self.voice_name,
'Language': self.language,
'Gender': self.gender
}
})
def _send_amazon_auth_packet_v4(self, method, service, content_type,
canonical_uri, canonical_querystring,
request_parameters, region, host):
"""Send a packet to a given amazon server using Amazon's signature Version 4,
Returns the resulting response object
"""
# Create date for headers and the credential string
t = datetime.datetime.utcnow()
amazon_date = t.strftime(_amazon_date_format)
date_stamp = t.strftime(_date_format)
# Step 1: Create canonical request
payload_hash = self._sha_hash(request_parameters)
canonical_headers = 'content-type:{}\n'.format(content_type)
canonical_headers += 'host:{}\n'.format(host)
canonical_headers += 'x-amz-content-sha256:{}\n'.format(payload_hash)
canonical_headers += 'x-amz-date:{}\n'.format(amazon_date)
canonical_request = '\n'.join([
method, canonical_uri, canonical_querystring, canonical_headers,
self.signed_headers, payload_hash])
# Step 2: Create the string to sign
credential_scope = '{}/{}/{}/aws4_request'.format(
date_stamp, region, service)
string_to_sign = '\n'.join([
self.algorithm, amazon_date, credential_scope,
self._sha_hash(canonical_request)])
# Step 3: Calculate the signature
signing_key = self._get_signature_key(
self.secret_key, date_stamp, region, service)
signature = hmac.new(
signing_key, string_to_sign.encode('utf-8'),
hashlib.sha256).hexdigest()
# Step 4: Create the signed packet
endpoint = 'https://{}{}'.format(host, canonical_uri)
authorization_header = '{} Credential={}/{}, ' +\
'SignedHeaders={}, Signature={}'
authorization_header = authorization_header.format(
self.algorithm, self.access_key, credential_scope,
self.signed_headers, signature)
headers = {
'Host': host,
'Content-type': content_type,
'X-Amz-Date': amazon_date,
'Authorization': authorization_header,
'x-amz-content-sha256': payload_hash,
'Content-Length': len(request_parameters)
}
# Send the packet and return the response
return requests.post(endpoint, data=request_parameters,
headers=headers)
def _sha_hash(self, to_hash):
return hashlib.sha256(to_hash.encode('utf-8')).hexdigest()
def _sign(self, key, msg):
return hmac.new(key, msg.encode('utf-8'), hashlib.sha256).digest()
def _get_signature_key(self, key, date_stamp, region_name, service_name):
k_date = self._sign(('AWS4{}'.format(key)).encode('utf-8'), date_stamp)
k_region = self._sign(k_date, region_name)
k_service = self._sign(k_region, service_name)
k_signing = self._sign(k_service, 'aws4_request')
return k_signing
def __init__(self, access_key, secret_key):
"""Set initial voice object parameters
"""
self.region = 'us-east'
self.voice_name = 'Brian'
self.access_key = access_key
self.secret_key = secret_key
self.speech_rate = 'medium'
self.sentence_break = 400
self.paragraph_break = 650
Any help is highly appreciated.
Was able to find a workaround by adding:
pygame.mixer.init()
channel = pygame.mixer.Channel(5)
immediately after defining the speak function:
def speak(self, text_to_speak, use_cache=False):
def speak(self, text_to_speak, use_cache=False):
"""Speak a given text
"""
pygame.mixer.init()
channel = pygame.mixer.Channel(5)
Usually UnboundLocalError relates to Scopes and Namespaces in Python Scopes and NameSpaces ,
but in Your case:
In function speak() You create channel in code
if not pygame.mixer.get_init():
pygame.mixer.init()
channel = pygame.mixer.Channel(5)
In case this code does not execute channel does not bound to any object.
For example, You can check this situation by this code sample:
def test_if():
if True:
# Bound
channel_1 = "Channel_1"
if False:
# Not bound
channel_2 = "Channel_2"
print(channel_1)
print(channel_2)
test_if()