How to change volume of stem files while playing using python - python

I'm attempting to write a python project that plays multiple parts of a song at the same time.
For background information, a song is split into "stems", and then each stem is played simultaneously to recreate the full song. What I am trying to achieve is using potentiometers to control the volume of each stem, so that the user can mix songs differently. For a product relation, the StemPlayer from Kanye West is what I am trying to achieve.
I can change the volume of the overlayed song at the end, but what I want to do is change the volume of each stem using a potentiometer while the song is playing. Is this even possible using pyDub? Below is the code I have right now.
from pydub import AudioSegment
from pydub.playback import play
vocals = AudioSegment.from_file("walkin_vocals.mp3")
drums = AudioSegment.from_file("walkin_drums.mp3")
bass = AudioSegment.from_file("walkin_bass.mp3")
vocalsDrums = vocals.overlay(drums)
bassVocalsDrums = vocalsDrums.overlay(bass)
songQuiet = bassVocalsDrums - 20
play(songQuiet)

Solved this question, I ended up using pyaudio instead of pydub.
With pyaudio, I was able to define a custom stream_callback function. Within this callback function, I multiply each stem by a modifier, then add each stem to one audio output.
def callback(in_data, frame_count, time_info, status):
global drumsMod, vocalsMod, bassMod, otherMod
drums = drumsWF.readframes(frame_count)
vocals = vocalsWF.readframes(frame_count)
bass = bassWF.readframes(frame_count)
other = otherWF.readframes(frame_count)
decodedDrums = numpy.frombuffer(drums, numpy.int16)
decodedVocals = numpy.frombuffer(vocals, numpy.int16)
decodedBass = numpy.frombuffer(bass, numpy.int16)
decodedOther = numpy.frombuffer(other, numpy.int16)
newdata = (decodedDrums*drumsMod + decodedVocals*vocalsMod + decodedBass*bassMod + decodedOther*otherMod).astype(numpy.int16)
return (newdata.tobytes(), pyaudio.paContinue)

Related

How to display text on the screen it is said over the audio

As a personal project, I decided to create one of the reddit text-to-speech bot.
I pulled all the data from reddit with praw
import praw, random
def scrapeData(subredditName):
# Instantiate praw
reddit = praw.Reddit()
# Get subreddit
subreddit = reddit.subreddit(subredditName)
# Get a bunch of posts and convert them into a list
posts = list(subreddit.new(limit=100))
# Get random number
randomNumber = random.randint(0, 100)
# Store post's title and description in variables
postTitle = posts[randomNumber].title
postDesc = posts[randomNumber].selftext
return postTitle + " " + postDesc
Then, I converted it to speech stored in a .mp3 file with gTTS.
from google.cloud import texttospeech
def convertTextToSpeech(textString):
# Instantiate TTS
client = texttospeech.TextToSpeechClient().from_service_account_json("path/to/json")
# Set text input to be synthesized
synthesisInput = texttospeech.SynthesisInput(text=textString)
# Build the voice request
voice = texttospeech.VoiceSelectionParams(language_code = "en-us",
ssml_gender = texttospeech.SsmlVoiceGender.MALE)
# Select the type of audio file
audioConfig = texttospeech.AudioConfig(audio_encoding =
texttospeech.AudioEncoding.MP3)
# Perform the TTS request on the text input
response = client.synthesize_speech(input = synthesisInput, voice =
voice, audio_config= audioConfig)
# Convert from binary to mp3
with open("output.mp3", "wb") as out:
out.write(response.audio_content)
I've created an .mp4 with moviepy that has generic footage in the background with the audio synced over it,
from moviepy.editor import *
from moviepy.video.tools.subtitles import SubtitlesClip
# get vide and audio source files
clip = VideoFileClip("background.mp4").subclip(20,30)
audio = AudioFileClip("output.mp3").subclip(0, 10)
# Set audio and create final video
videoClip = clip.set_audio(audio)
videoClip.write_videofile("output.mp4")
but my issue is I can't find a way to have only the current word or sentence displayed on screen as a subtitle, rather than the entire post.

MoviePy multiple textClips one after another

I have an audio file and its script file, that looks like this
One day I was playing Fortnite
solos until I got a strange fend
invites I never seen before I joned
it an he said time to play
a game I sruged it of like
no big deal I regreted doing that
but he kept saying time to play
over and over and over ugen
My goal is to make a video where voice is followed by text appearing on the screen (next line appears, previous disappears). The way I do it is obviously wrong and it just renders all lines at the beginning of the video stacked on each other + I can not know how much lines of script there will be so doing texts[0], texts[1]... is not an option. Please send help!
My code:
videoclip = VideoFileClip("Satisfying Minecraft Parkour.mp4")
audioclip = AudioFileClip(f"audio.mp3")
new_audioclip = CompositeAudioClip([audioclip])
videoclip.audio = new_audioclip
texts = []
with open(f'text.txt', 'r') as f:
for line in f:
txt_clip = TextClip(line, fontsize = 55, color = 'white')
txt_clip = txt_clip.set_pos('center')
txt_clip = txt_clip.set_duration(audio_in_seconds/len(str(text))*len(line))
texts.append(txt_clip)
video = CompositeVideoClip([videoclip, texts[0]])
video = CompositeVideoClip([video, texts[1]])

Threading with pytube while searching

This is my code, I'm trying to download a playlist from youtube.In order to prevent the app from taking too long, I use threading. But it is taking too long and I'm literally not getting any reaction form the window. Simply it's not working.Does anyone know what the problem is? (Note that I am new to python).Thank you in advance.
Here is my code:
def playlist_search():
global pl
url = playlist_link_entry.get()
try:
#Create a PlayList object
#Downloading is gonna happen with pytube
pl = Playlist(url)
except:
messagebox.showerror("Error","Either link is not functioning or connection broken".title())
#Gotta get the links to every single video
#We name it 'video_link_playlist'
#for that specific url we do our shit(get itag, size and ... thats exactly our previous code)
#We map the playlist_actual_search to pl.video_urls
with concurrent.futures.ThreadPoolExecutor() as executor:
executor.map(playlist_actual_search, pl.video_urls)
We create this function to do the search concurrently
def playlist_actual_search(pl_link):
#Saving All Links to a list to use it later
playlist_link_list.append(pl_link)
#Getting itag number is gonna happen with pafy
#Create a pafy object
video = pafy.new(pl_link)
#Get all streams from youtube
streams = video.allstreams
#Create a YouTube object to get every video individually
yt = YouTube(pl_link)
#Add yt to the yt.list
yt_list.append(yt)
#Get those streams that are normal(addaptive)
for stream in streams:
if "normal" in str(stream):
playlist_normal_stream_list.append(stream)
#Get itag value of the addaptive video
for normal_stream in playlist_normal_stream_list:
if "720" in str(normal_stream):
itag720 = normal_stream.itag
playlist_itag_value_dic["itag720"] = itag720
normal_stream = yt.streams.get_by_itag(itag720)
playlist720_video_size += normal_stream.filesize_approx
if "480" in str(normal_stream):
itag480 = normal_stream.itag
playlist_itag_value_dic["itag480"] = itag480
normal_stream = yt.streams.get_by_itag(itag480)
playlist480_video_size += normal_stream.filesize_approx
if "360" in str(normal_stream):
itag360 = normal_stream.itag
playlist_itag_value_dic["itag360"] = itag360
normal_stream = yt.streams.get_by_itag(itag360)
playlist360_video_size += normal_stream.filesize_approx

Playing a Lot of Sounds at Once

I am attempting to create a program in python that plays a particular harpsichord note when a certain key is pressed. I want it to remain responsive so you can continue to play more notes (kind of like a normal electric piano.) However, because the wav files that the notes are stored in are about 7-10 seconds long I am experiencing some issues. I can press at least 10 keys per second. So, over the duration of one note I could have around 100 different wav files playing at once. I tried to use winsound, but it was unable to play multiple wav files at once. I then moved on to PyAudio and it works kind of. The only way that I found to accomplish what I wanted was this:
from msvcrt import getch
import pyaudio
import wave
import multiprocessing as mp
#This function is just code for playing a sound in PyAudio
def playNote(filename):
CHUNK = 1024
wf = wave.open(filename, 'rb')
p = pyaudio.PyAudio()
stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),
channels=wf.getnchannels(),
rate=wf.getframerate(),
output=True)
data = wf.readframes(CHUNK)
while data != '':
stream.write(data)
data = wf.readframes(CHUNK)
stream.stop_stream()
stream.close()
p.terminate()
if __name__ == "__main__":
while True:
#If the 'a' key is pressed: start a new process that calls playNote
#and pass in the file name for a note.
if ord(getch()) == 97: #a
mp.Process(target=playNote, args=("F:\Project Harpsichord\The wavs\A1.wav",)).start()
#If the 's' key is pressed: start a new process that calls playNote
#and pass in the file name for another note.
if ord(getch()) == 115: #s
mp.Process(target=playNote, args=("F:\Project Harpsichord\The wavs\A0.wav",)).start()
Basically whenever I want to play a new wav, I have to start a new process that runs the code in the playNote function. As I already stated I can potentially have up to 100 of these playing at once. Suffice it to say, one hundred copies of the python interpreter all running at once almost crashed my computer. I also tried a similar approach with multi-threading, but had the same problems.
This post shows a way to mix multiple wav files together so they can be played at the same time, but since my program will not necessarily be starting the sounds at the same time I am unsure if this will work.
I need an efficient way to play multiple notes at the same time. Whether this comes in the form of another library, or even a different language I really don't care.
I checked out pygame like J.F Sebastian suggested. It ended up being exactly what I needed. I used pygame.mixer.Sound() in conjunction with pygame.mixer.set_num_channels(). Here's what I came up with.
import pygame as pg
import time
pg.mixer.init()
pg.init()
a1Note = pg.mixer.Sound("F:\Project Harpsichord\The wavs\A1.wav")
a2Note = pg.mixer.Sound("F:\Project Harpsichord\The wavs\A0.wav")
pg.mixer.set_num_channels(50)
for i in range(25):
a1Note.play()
time.sleep(0.3)
a2Note.play()
time.sleep(0.3)
This doesn't really solve your problem, but it's too long for the comments, and it may be useful. I gave it a bash, got defeated on a few fronts - giving up and going for pizza. Audio is really not my thing, but it was quite a lot of fun playing around with it.
Give Pydub a look. I've Played around with a couple of methods, but haven't had any satisfactory success. This answer here explains quite a few things regarding adding two signals together nicely. I assume that the static you have is because of clipping.
Sorry that I didn't deliver, but I may as well post all the things I've created in case you or someone else wants to grab something from it:
#using python 2.7
#example animal sounds from http://www.wavsource.com/animals/animals.htm
#note that those sounds have lots of different sampling rates and encoding types. Causes problems.
#required installs:
#numpy
#scipy
#matplotlib
#pyaudio -sudo apt-get install python-pyaudio
#pydub: -pip install pydub
def example():
"example sounds and random inputs"
sExampleSoundsDir = "/home/roman/All/Code/sound_files"
sExampleFile1 = 'bird.wav'
sExampleFile2 = 'frog.wav'
oJ = Jurgenmeister(sExampleSoundsDir)
#load audio into numpy array
dSound1 = oJ.audio2array(sExampleFile1)
dSound2 = oJ.audio2array(sExampleFile2)
#Simply adding the arrays is noisy...
dResSound1 = oJ.resample(dSound1)
dResSound2 = oJ.resample(dSound2)
dJoined = oJ.add_sounds(dResSound1, dResSound2)
#pydub method
oJ.overlay_sounds(sExampleFile1, sExampleFile2)
#listen to the audio - mixed success with these sounds.
oJ.play_array(dSound1)
oJ.play_array(dSound2)
oJ.play_array(dResSound1)
oJ.play_array(dResSound2)
oJ.play_array(dJoined)
#see what the waveform looks like
oJ.plot_audio(dJoined)
class Jurgenmeister:
"""
Methods to play as many sounds on command as necessary
Named in honour of op, and its as good a name as I can come up with myself.
"""
def __init__(self, sSoundsDir):
import os
import random
lAllSounds = os.listdir(sSoundsDir)
self.sSoundsDir = sSoundsDir
self.lAllSounds = lAllSounds
self.sRandSoundName = lAllSounds[random.randint(0, len(lAllSounds)-1)]
def play_wave(self, sFileName):
"""PyAudio play a wave file."""
import pyaudio
import wave
iChunk = 1024
sDir = "{}/{}".format(self.sSoundsDir, sFileName)
oWave = wave.open(sDir, 'rb')
oPyaudio = pyaudio.PyAudio()
oStream = oPyaudio.open(
format = oPyaudio.get_format_from_width(oWave.getsampwidth()),
channels = oWave.getnchannels(),
rate = oWave.getframerate(),
output = True
)
sData = oWave.readframes(iChunk)
while sData != '':
oStream.write(sData)
sData = oWave.readframes(iChunk)
oStream.stop_stream()
oStream.close()
oPyaudio.terminate()
def audio2array(self, sFileName):
"""
Returns monotone data for a wav audio file in form:
iSampleRate, aNumpySignalArray, aNumpyTimeArray
Should perhaps do this with scipy again, but I threw that code away because I wanted
to try the pyaudio package because of its streaming functions. They defeated me.
"""
import wave
import numpy as np
sDir = "{}/{}".format(self.sSoundsDir, sFileName)
oWave = wave.open(sDir,"rb")
tParams = oWave.getparams()
iSampleRate = tParams[2] #frames per second
iLen = tParams[3] # number of frames
#depending on the type of encoding of the file. Usually 16
try:
sSound = oWave.readframes(iLen)
oWave.close()
aSound = np.fromstring(sSound, np.int16)
except ValueError:
raise ValueError("""wave package seems to want all wav incodings to be in int16, else it throws a mysterious error.
Short way around it: find audio encoded in the right format. Or use scipy.io.wavfile.
""")
aTime = np.array( [float(i)/iSampleRate for i in range(len(aSound))] )
dRet = {
'iSampleRate': iSampleRate,
'aTime': aTime,
'aSound': aSound,
'tParams': tParams
}
return dRet
def resample(self, dSound, iResampleRate=11025):
"""resample audio arrays
common audio sample rates are 44100, 22050, 11025, 8000
#creates very noisy results sometimes.
"""
from scipy import interpolate
import numpy as np
aSound = np.array(dSound['aSound'])
iOldRate = dSound['iSampleRate']
iOldLen = len(aSound)
rPeriod = float(iOldLen)/iOldRate
iNewLen = int(rPeriod*iResampleRate)
aTime = np.arange(0, rPeriod, 1.0/iOldRate)
aTime = aTime[0:iOldLen]
oInterp = interpolate.interp1d(aTime, aSound)
aResTime = np.arange(0, aTime[-1], 1.0/iResampleRate)
aTime = aTime[0:iNewLen]
aResSound = oInterp(aResTime)
aResSound = np.array(aResSound, np.int16)
tParams = list(x for x in dSound['tParams'])
tParams[2] = iResampleRate
tParams[3] = iNewLen
tParams = tuple(tParams)
dResSound = {
'iSampleRate': iResampleRate,
'aTime': aResTime,
'aSound': aResSound,
'tParams': tParams
}
return dResSound
def add_sounds(self, dSound1, dSound2):
"""join two sounds together and return new array
This method creates a lot of clipping. Not sure how to get around that.
"""
if dSound1['iSampleRate'] != dSound2['iSampleRate']:
raise ValueError('sample rates must be the same. Please resample first.')
import numpy as np
aSound1 = dSound1['aSound']
aSound2 = dSound2['aSound']
if len(aSound1) < len(aSound2):
aRet = aSound2.copy()
aRet[:len(aSound1)] += aSound1
aTime = dSound2['aTime']
tParams = dSound2['tParams']
else:
aRet = aSound1.copy()
aRet[:len(aSound2)] += aSound2
aTime = dSound1['aTime']
tParams = dSound1['tParams']
aRet = np.array(aRet, np.int16)
dRet = {
'iSampleRate': dSound1['iSampleRate'],
'aTime': aTime,
'aSound': aRet,
'tParams': tParams
}
return dRet
def overlay_sounds(self, sFileName1, sFileName2):
"I think this method warrants a bit more exploration
Also very noisy."
from pydub import AudioSegment
sDir1 = "{}/{}".format(self.sSoundsDir, sFileName1)
sDir2 = "{}/{}".format(self.sSoundsDir, sFileName2)
sound1 = AudioSegment.from_wav(sDir1)
sound2 = AudioSegment.from_wav(sDir2)
# mix sound2 with sound1, starting at 0ms into sound1)
output = sound1.overlay(sound2, position=0)
# save the result
sDir = "{}/{}".format(self.sSoundsDir, 'OUTPUT.wav')
output.export(sDir, format="wav")
def array2audio(self, dSound, sDir=None):
"""
writes an .wav audio file to disk from an array
"""
import struct
import wave
if sDir == None:
sDir = "{}/{}".format(self.sSoundsDir, 'OUTPUT.wav')
aSound = dSound['aSound']
tParams = dSound['tParams']
sSound = struct.pack('h'*len(aSound), *aSound)
oWave = wave.open(sDir,"wb")
oWave.setparams(tParams)
oWave.writeframes(sSound)
oWave.close()
def play_array(self, dSound):
"""Tried to use use pyaudio to play array by just streaming it. It didn't behave, and I moved on.
I'm just not getting the pyaudio stream to play without weird distortion
when not loading from file. Perhaps you have more luck.
"""
self.array2audio(dSound)
self.play_wave('OUTPUT.wav')
def plot_audio(self, dSound):
"just plots the audio array. Nice to see plots when things are going wrong."
import matplotlib.pyplot as plt
plt.plot(dSound['aTime'], dSound['aSound'])
plt.show()
if __name__ == "__main__":
example()
I also get this error when I use wave. It still works, so I just ignore it.
Problem seems to be widespread. Error lines:
ALSA lib pcm_dsnoop.c:618:(snd_pcm_dsnoop_open) unable to open slave
ALSA lib pcm_dmix.c:1022:(snd_pcm_dmix_open) unable to open slave
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.rear
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.center_lfe
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.side
bt_audio_service_open: connect() failed: Connection refused (111)
bt_audio_service_open: connect() failed: Connection refused (111)
bt_audio_service_open: connect() failed: Connection refused (111)
bt_audio_service_open: connect() failed: Connection refused (111)
ALSA lib pcm_dmix.c:1022:(snd_pcm_dmix_open) unable to open slave
Cannot connect to server socket err = No such file or directory
Cannot connect to server request channel
jack server is not running or cannot be started
Good luck!

Use decodebin with adder

I'm trying to create an audio stream that has a constant audio source (in this case, audiotestsrc) to which I can occasionally add sounds from files (of various formats, that's why I'm using decodebin) through the play_file() method. I use an adder for that purpose. However, for some reason, I cannot add the second sound correctly. Not only does the program play the sound incorrectly, it also completely stops the original audiotestsrc. Here's my code so far:
import gst; import gobject; gobject.threads_init()
pipe = gst.Pipeline()
adder = gst.element_factory_make("adder", "adder")
first_sink = adder.get_request_pad('sink%d')
pipe.add(adder)
test = gst.element_factory_make("audiotestsrc", "test")
test.set_property('freq', 100)
pipe.add(test)
testsrc = test.get_pad("src")
testsrc.link(first_sink)
output = gst.element_factory_make("alsasink", "output")
pipe.add(output)
adder.link(output)
pipe.set_state(gst.STATE_PLAYING)
raw_input('Press key to play sound')
def play_file(filename):
adder_sink = adder.get_request_pad('sink%d')
audiofile = gst.element_factory_make('filesrc', 'audiofile')
audiofile.set_property('location', filename)
decoder = gst.element_factory_make('decodebin', 'decoder')
def on_new_decoded_pad(element, pad, last):
pad.link(adder_sink)
decoder.connect('new-decoded-pad', on_new_decoded_pad)
pipe.add(audiofile)
pipe.add(decoder)
audiofile.link(decoder)
pipe.set_state(gst.STATE_PAUSED)
pipe.set_state(gst.STATE_PLAYING)
play_file('sample.wav')
while True:
pass
Thanks to moch on #gstreamer, I realized that all adder sources should have the same format. I modified the above script so as to have the caps "audio/x-raw-int, endianness=(int)1234, channels=(int)1, width=(int)16, depth=(int)16, signed=(boolean)true, rate=(int)11025" (example) go before every input in the adder.

Categories

Resources