| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228 |
- #!/usr/bin/env python
- # -*- coding: utf-8 -*-
- import win32com.client
- import platform
- import wave
- import numpy as np
- from pydub import AudioSegment, silence
- from pydub.playback import play
- from playsound import playsound
- import re
- import librosa
- from pydub import effects
- import os
- class Voice:
-
- instance = None
- @staticmethod
- def getInstance():
- if Voice.instance==None:
- Voice.instance = Voice()
- return Voice.instance
-
- def __init__(self):
- self.sys = platform.system()
- if self.sys=="Windows":
- self.speaker = win32com.client.Dispatch("SAPI.SpVoice")
- else:
- self.speaker = None
- def Say(self, text, save:bool = True):
- if self.speaker != None:
- self.speaker.Rate = 0.0
- if save:
- stream = win32com.client.Dispatch("SAPI.SpFileStream")
- stream.Open("cache/spoken.wav", 3, False)
- defAudioOutput = self.speaker.AudioOutputStream
- self.speaker.AudioOutputStream = stream
- self.speaker.Speak(text)
- if save:
- stream.Close()
- self.Edit("cache/spoken.wav","cache/pitched.wav")
- playsound("cache/pitched.wav")
- self.speaker.AudioOutputStream = defAudioOutput
- else:
- self.__svox(text)
- def EmotionalSay(self, text):
- files = list()
- defAudioOutput = self.speaker.AudioOutputStream
- collection = re.finditer('(?:\[(?P<args>.*?)\])?(?P<text>[^\[]*)', text, re.UNICODE)
- count=0
- combined_wav = AudioSegment.empty()
- self.speaker.Rate = -1
- for x in collection:
- args = self.__getArgs(x.group("args"))
- itm = self.__sayToFile(x.group("text"),"cache/tmp_"+str(count)+".wav")
- # Remove silence at the beginning/end
- start_trim = silence.detect_leading_silence(itm)
- end_trim = silence.detect_leading_silence(itm.reverse())
- duration = len(itm)
- itm = itm[start_trim:-end_trim]
- if args != "":
- itm_e = self.EditSegment(itm, args)
- combined_wav += itm_e
- else:
- combined_wav += itm
- #os.remove("cache/tmp_"+str(count)+".wav")
- self.speaker.AudioOutputStream = defAudioOutput
- play(combined_wav)
- def __sayToFile(self, text:str, file:str):
- if text == "":
- return AudioSegment.empty()
- stream = win32com.client.Dispatch("SAPI.SpFileStream")
- stream.Open(file, 3, False)
- self.speaker.AudioOutputStream = stream
- self.speaker.Speak(text)
- stream.Close()
- return AudioSegment.from_file(file)
- def PlaySound(file:str):
- if self.sys=="Windows":
- import winsound
- winsound.PlaySound("SystemExit", winsound.SND_ALIAS)
- def EditSegment(self, segment, args):
- print(args)
- if "vol" in args:
- segment += args["vol"]
- if "pitch" in args:
- segment = self.pitch_shift(segment, args["pitch"])
- if "speedup" in args:
- segment = segment.speedup(args["speedup"])
- if "speed" in args and args["speed"]>0:
- segment = self.speed_change(segment, 1.15)
- #segment = self.speed_change(segment, args["speed"])
- if args["speed"]>1:
- segment = self.pitch_shift(segment, -(1-args["speed"]))
- else:
- #pitch = (1/((args["speed"]/12)))*1
- pitch = 6.55
- #print("Speed Pitch: "+str(pitch))
- #segment = self.pitch_shift(segment, 1)
- #segment = segment.speedup(2)
- if "fadein" in args:
- segment = segment.fade_in(args["fadein"])
- if "fadeout" in args:
- segment = segment.fade_out(args["fadeout"])
- if "break" in args and args["break"]>=1:
- pauseSegment=AudioSegment.silent(args["break"])
- segment = pauseSegment + segment
- if "effect" in args:
- if os.path.exists("sounds/"+args["effect"]):
- fxSound=AudioSegment.from_file(args["effect"])
- segment = fxSound + segment
- else:
- print(" Effect not found")
- return segment
- def removeSilence(self, sound):
- non_sil_times = detect_nonsilent(sound, min_silence_len=50, silence_thresh=sound.dBFS * 1.5)
- if len(non_sil_times) > 0:
- non_sil_times_concat = [non_sil_times[0]]
- if len(non_sil_times) > 1:
- for t in non_sil_times[1:]:
- if t[0] - non_sil_times_concat[-1][-1] < 200:
- non_sil_times_concat[-1][-1] = t[1]
- else:
- non_sil_times_concat.append(t)
- non_sil_times = [t for t in non_sil_times_concat if t[1] - t[0] > 350]
- return sound[non_sil_times[0][0]: non_sil_times[-1][1]]
- def pitch_shift(self, sound, n_steps:float):
- y = np.frombuffer(sound._data, dtype=np.int16).astype(np.float32)/2**15
- y = librosa.effects.pitch_shift(y, sound.frame_rate, n_steps=n_steps)
- a = AudioSegment(np.array(y * (1<<15), dtype=np.int16).tobytes(), frame_rate = sound.frame_rate, sample_width=2, channels = 1)
- return a
- def speed_change(self, sound, speed=1.0):
- # Manually override the frame_rate. This tells the computer how many
- # samples to play per second
- sound_with_altered_frame_rate = sound._spawn(sound.raw_data, overrides={
- "frame_rate": int(sound.frame_rate * speed)
- })
- # convert the sound with altered frame rate to a standard frame rate
- # so that regular playback programs will work right. They often only
- # know how to play audio at standard frame rate (like 44.1k)
- return sound_with_altered_frame_rate.set_frame_rate(sound.frame_rate)
- def detect_leading_silence(self, sound, silence_threshold=-50.0, chunk_size=10):
- '''
- sound is a pydub.AudioSegment
- silence_threshold in dB
- chunk_size in ms
- iterate over chunks until you find the first one with sound
- '''
- trim_ms = 0 # ms
- assert chunk_size > 0 # to avoid infinite loop
- while sound[trim_ms:trim_ms+chunk_size].dBFS < silence_threshold and trim_ms < len(sound):
- trim_ms += chunk_size
- return trim_ms
- def __getArgs(self, args:str):
- res = dict()
- if args is None or args=="":
- return res
- expl = args.split(",")
- for x in expl:
- x = x.strip()
- if ":" in x:
- k,v = x.split(":",2)
- print("getArgs -> '"+k+"' : '"+v+"'")
- if k.lower().strip()=="p" or k.lower().strip()=="pitch":
- res["pitch"]=float(v)
- elif k.lower().strip()=="v" or k.lower().strip()=="vol" or k.lower().strip()=="volumen":
- res["vol"]=int(v)
- elif k.lower().strip()=="su" or k.lower().strip()=="speedup":
- res["speedup"]=float(v)
- elif k.lower().strip()=="s" or k.lower().strip()=="slow" or k.lower().strip()=="speed":
- res["speed"]=float(v)
- elif k.lower().strip()=="b" or k.lower().strip()=="break" or k.lower().strip()=="pause" or k.lower().strip()=="p":
- res["break"]=float(v)
- elif k.lower().strip()=="fade" or k.lower().strip()=="f":
- res["fadein"]=int(v)
- res["fadeout"]=int(v)
- elif k.lower().strip()=="fadein" or k.lower().strip()=="fi" or k.lower().strip()=="fade in" or k.lower().strip()=="fade-in":
- res["fadein"]=int(v)
- elif k.lower().strip()=="fadeout" or k.lower().strip()=="fo" or k.lower().strip()=="fade out" or k.lower().strip()=="fade-out":
- res["fadeout"]=int(v)
- elif k.lower().strip()=="fx" or k.lower().strip()=="effect":
- res["effect"]=v
- else:
- if k.lower().strip()=="b" or k.lower().strip()=="break" or k.lower().strip()=="pause" or k.lower().strip()=="p":
- res["break"]=1500
- return res
- def RobotEffect(self, input:str, output:str, frequence:int = 2):
- wr = wave.open(input, 'r')
- par = list(wr.getparams())
- par[3] = 0 # The number of samples will be set by writeframes.
- par = tuple(par)
- ww = wave.open(output, 'w')
- ww.setparams(par)
- sz = wr.getframerate()//frequence # Read and process 1/fr second at a time.
- # A larger number for fr means less reverb.
- c = int(wr.getnframes()/sz) # count of the whole file
- shift = 100//frequence # shifting 100 Hz
- for num in range(c):
- da = np.fromstring(wr.readframes(sz), dtype=np.int16)
- left, right = da[0::2], da[1::2] # left and right channel
- lf, rf = np.fft.rfft(left), np.fft.rfft(right)
- lf, rf = np.roll(lf, shift), np.roll(rf, shift)
- lf[0:shift], rf[0:shift] = 0, 0
- nl, nr = np.fft.irfft(lf), np.fft.irfft(rf)
- ns = np.column_stack((nl, nr)).ravel().astype(np.int16)
- ww.writeframes(ns.tostring())
- wr.close()
- ww.close()
- def __svox(self, text):
- pass
|