main.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. import win32com.client
  4. import platform
  5. import wave
  6. import numpy as np
  7. from pydub import AudioSegment, silence
  8. from pydub.playback import play
  9. from playsound import playsound
  10. import re
  11. import librosa
  12. from pydub import effects
  13. import os
  14. class Voice:
  15. instance = None
  16. @staticmethod
  17. def getInstance():
  18. if Voice.instance==None:
  19. Voice.instance = Voice()
  20. return Voice.instance
  21. def __init__(self):
  22. self.sys = platform.system()
  23. if self.sys=="Windows":
  24. self.speaker = win32com.client.Dispatch("SAPI.SpVoice")
  25. else:
  26. self.speaker = None
  27. def Say(self, text, save:bool = True):
  28. if self.speaker != None:
  29. self.speaker.Rate = 0.0
  30. if save:
  31. stream = win32com.client.Dispatch("SAPI.SpFileStream")
  32. stream.Open("cache/spoken.wav", 3, False)
  33. defAudioOutput = self.speaker.AudioOutputStream
  34. self.speaker.AudioOutputStream = stream
  35. self.speaker.Speak(text)
  36. if save:
  37. stream.Close()
  38. self.Edit("cache/spoken.wav","cache/pitched.wav")
  39. playsound("cache/pitched.wav")
  40. self.speaker.AudioOutputStream = defAudioOutput
  41. else:
  42. self.__svox(text)
  43. def EmotionalSay(self, text):
  44. files = list()
  45. defAudioOutput = self.speaker.AudioOutputStream
  46. collection = re.finditer('(?:\[(?P<args>.*?)\])?(?P<text>[^\[]*)', text, re.UNICODE)
  47. count=0
  48. combined_wav = AudioSegment.empty()
  49. self.speaker.Rate = -1
  50. for x in collection:
  51. args = self.__getArgs(x.group("args"))
  52. itm = self.__sayToFile(x.group("text"),"cache/tmp_"+str(count)+".wav")
  53. # Remove silence at the beginning/end
  54. start_trim = silence.detect_leading_silence(itm)
  55. end_trim = silence.detect_leading_silence(itm.reverse())
  56. duration = len(itm)
  57. itm = itm[start_trim:-end_trim]
  58. if args != "":
  59. itm_e = self.EditSegment(itm, args)
  60. combined_wav += itm_e
  61. else:
  62. combined_wav += itm
  63. #os.remove("cache/tmp_"+str(count)+".wav")
  64. self.speaker.AudioOutputStream = defAudioOutput
  65. play(combined_wav)
  66. def __sayToFile(self, text:str, file:str):
  67. if text == "":
  68. return AudioSegment.empty()
  69. stream = win32com.client.Dispatch("SAPI.SpFileStream")
  70. stream.Open(file, 3, False)
  71. self.speaker.AudioOutputStream = stream
  72. self.speaker.Speak(text)
  73. stream.Close()
  74. return AudioSegment.from_file(file)
  75. def PlaySound(file:str):
  76. if self.sys=="Windows":
  77. import winsound
  78. winsound.PlaySound("SystemExit", winsound.SND_ALIAS)
  79. def EditSegment(self, segment, args):
  80. print(args)
  81. if "vol" in args:
  82. segment += args["vol"]
  83. if "pitch" in args:
  84. segment = self.pitch_shift(segment, args["pitch"])
  85. if "speedup" in args:
  86. segment = segment.speedup(args["speedup"])
  87. if "speed" in args and args["speed"]>0:
  88. segment = self.speed_change(segment, 1.15)
  89. #segment = self.speed_change(segment, args["speed"])
  90. if args["speed"]>1:
  91. segment = self.pitch_shift(segment, -(1-args["speed"]))
  92. else:
  93. #pitch = (1/((args["speed"]/12)))*1
  94. pitch = 6.55
  95. #print("Speed Pitch: "+str(pitch))
  96. #segment = self.pitch_shift(segment, 1)
  97. #segment = segment.speedup(2)
  98. if "fadein" in args:
  99. segment = segment.fade_in(args["fadein"])
  100. if "fadeout" in args:
  101. segment = segment.fade_out(args["fadeout"])
  102. if "break" in args and args["break"]>=1:
  103. pauseSegment=AudioSegment.silent(args["break"])
  104. segment = pauseSegment + segment
  105. if "effect" in args:
  106. if os.path.exists("sounds/"+args["effect"]):
  107. fxSound=AudioSegment.from_file(args["effect"])
  108. segment = fxSound + segment
  109. else:
  110. print(" Effect not found")
  111. return segment
  112. def removeSilence(self, sound):
  113. non_sil_times = detect_nonsilent(sound, min_silence_len=50, silence_thresh=sound.dBFS * 1.5)
  114. if len(non_sil_times) > 0:
  115. non_sil_times_concat = [non_sil_times[0]]
  116. if len(non_sil_times) > 1:
  117. for t in non_sil_times[1:]:
  118. if t[0] - non_sil_times_concat[-1][-1] < 200:
  119. non_sil_times_concat[-1][-1] = t[1]
  120. else:
  121. non_sil_times_concat.append(t)
  122. non_sil_times = [t for t in non_sil_times_concat if t[1] - t[0] > 350]
  123. return sound[non_sil_times[0][0]: non_sil_times[-1][1]]
  124. def pitch_shift(self, sound, n_steps:float):
  125. y = np.frombuffer(sound._data, dtype=np.int16).astype(np.float32)/2**15
  126. y = librosa.effects.pitch_shift(y, sound.frame_rate, n_steps=n_steps)
  127. a = AudioSegment(np.array(y * (1<<15), dtype=np.int16).tobytes(), frame_rate = sound.frame_rate, sample_width=2, channels = 1)
  128. return a
  129. def speed_change(self, sound, speed=1.0):
  130. # Manually override the frame_rate. This tells the computer how many
  131. # samples to play per second
  132. sound_with_altered_frame_rate = sound._spawn(sound.raw_data, overrides={
  133. "frame_rate": int(sound.frame_rate * speed)
  134. })
  135. # convert the sound with altered frame rate to a standard frame rate
  136. # so that regular playback programs will work right. They often only
  137. # know how to play audio at standard frame rate (like 44.1k)
  138. return sound_with_altered_frame_rate.set_frame_rate(sound.frame_rate)
  139. def detect_leading_silence(self, sound, silence_threshold=-50.0, chunk_size=10):
  140. '''
  141. sound is a pydub.AudioSegment
  142. silence_threshold in dB
  143. chunk_size in ms
  144. iterate over chunks until you find the first one with sound
  145. '''
  146. trim_ms = 0 # ms
  147. assert chunk_size > 0 # to avoid infinite loop
  148. while sound[trim_ms:trim_ms+chunk_size].dBFS < silence_threshold and trim_ms < len(sound):
  149. trim_ms += chunk_size
  150. return trim_ms
  151. def __getArgs(self, args:str):
  152. res = dict()
  153. if args is None or args=="":
  154. return res
  155. expl = args.split(",")
  156. for x in expl:
  157. x = x.strip()
  158. if ":" in x:
  159. k,v = x.split(":",2)
  160. print("getArgs -> '"+k+"' : '"+v+"'")
  161. if k.lower().strip()=="p" or k.lower().strip()=="pitch":
  162. res["pitch"]=float(v)
  163. elif k.lower().strip()=="v" or k.lower().strip()=="vol" or k.lower().strip()=="volumen":
  164. res["vol"]=int(v)
  165. elif k.lower().strip()=="su" or k.lower().strip()=="speedup":
  166. res["speedup"]=float(v)
  167. elif k.lower().strip()=="s" or k.lower().strip()=="slow" or k.lower().strip()=="speed":
  168. res["speed"]=float(v)
  169. elif k.lower().strip()=="b" or k.lower().strip()=="break" or k.lower().strip()=="pause" or k.lower().strip()=="p":
  170. res["break"]=float(v)
  171. elif k.lower().strip()=="fade" or k.lower().strip()=="f":
  172. res["fadein"]=int(v)
  173. res["fadeout"]=int(v)
  174. elif k.lower().strip()=="fadein" or k.lower().strip()=="fi" or k.lower().strip()=="fade in" or k.lower().strip()=="fade-in":
  175. res["fadein"]=int(v)
  176. elif k.lower().strip()=="fadeout" or k.lower().strip()=="fo" or k.lower().strip()=="fade out" or k.lower().strip()=="fade-out":
  177. res["fadeout"]=int(v)
  178. elif k.lower().strip()=="fx" or k.lower().strip()=="effect":
  179. res["effect"]=v
  180. else:
  181. if k.lower().strip()=="b" or k.lower().strip()=="break" or k.lower().strip()=="pause" or k.lower().strip()=="p":
  182. res["break"]=1500
  183. return res
  184. def RobotEffect(self, input:str, output:str, frequence:int = 2):
  185. wr = wave.open(input, 'r')
  186. par = list(wr.getparams())
  187. par[3] = 0 # The number of samples will be set by writeframes.
  188. par = tuple(par)
  189. ww = wave.open(output, 'w')
  190. ww.setparams(par)
  191. sz = wr.getframerate()//frequence # Read and process 1/fr second at a time.
  192. # A larger number for fr means less reverb.
  193. c = int(wr.getnframes()/sz) # count of the whole file
  194. shift = 100//frequence # shifting 100 Hz
  195. for num in range(c):
  196. da = np.fromstring(wr.readframes(sz), dtype=np.int16)
  197. left, right = da[0::2], da[1::2] # left and right channel
  198. lf, rf = np.fft.rfft(left), np.fft.rfft(right)
  199. lf, rf = np.roll(lf, shift), np.roll(rf, shift)
  200. lf[0:shift], rf[0:shift] = 0, 0
  201. nl, nr = np.fft.irfft(lf), np.fft.irfft(rf)
  202. ns = np.column_stack((nl, nr)).ravel().astype(np.int16)
  203. ww.writeframes(ns.tostring())
  204. wr.close()
  205. ww.close()
  206. def __svox(self, text):
  207. pass