This repository has been archived on 2020-08-22. You can view files and clone it, but cannot push or open issues or pull requests.
pyaudviz/core.py

211 lines
6.4 KiB
Python
Raw Permalink Normal View History

import sys, io, os
from PySide2 import QtCore, QtGui
from PySide2.QtGui import QPainter, QColor
2015-03-02 16:47:52 -05:00
from os.path import expanduser
import subprocess as sp
import numpy
from PIL import Image, ImageDraw, ImageFont
from PIL.ImageQt import ImageQt
import tempfile
from shutil import rmtree
import atexit
2015-03-02 16:47:52 -05:00
class Core():
def __init__(self):
self.lastBackgroundImage = ""
self._image = None
self.FFMPEG_BIN = self.findFfmpeg()
self.tempDir = None
atexit.register(self.deleteTempDir)
def findFfmpeg(self):
2015-03-02 16:47:52 -05:00
if sys.platform == "win32":
return "ffmpeg.exe"
2015-03-02 16:47:52 -05:00
else:
try:
with open(os.devnull, "w") as f:
sp.check_call(['ffmpeg', '-version'], stdout=f, stderr=f)
return "ffmpeg"
except:
return "avconv"
2015-03-02 16:47:52 -05:00
def parseBaseImage(self, backgroundImage, preview=False):
''' determines if the base image is a single frame or list of frames '''
2015-03-02 16:47:52 -05:00
if backgroundImage == "":
return []
2015-03-02 16:47:52 -05:00
else:
_, bgExt = os.path.splitext(backgroundImage)
if not bgExt == '.mp4':
return [backgroundImage]
else:
return self.getVideoFrames(backgroundImage, preview)
def drawBaseImage(self, backgroundFile, titleText, titleFont, fontSize, alignment,\
xOffset, yOffset, textColor, visColor):
if backgroundFile == '':
im = Image.new("RGB", (1280, 720), "black")
else:
im = Image.open(backgroundFile)
if self._image == None or not self.lastBackgroundImage == backgroundFile:
self.lastBackgroundImage = backgroundFile
2015-03-02 16:47:52 -05:00
# resize if necessary
if not im.size == (1280, 720):
im = im.resize((1280, 720), Image.ANTIALIAS)
self._image = ImageQt(im)
2015-03-02 16:47:52 -05:00
self._image1 = QtGui.QImage(self._image)
painter = QPainter(self._image1)
font = titleFont
font.setPixelSize(fontSize)
2015-03-02 16:47:52 -05:00
painter.setFont(font)
painter.setPen(QColor(*textColor))
2015-03-02 16:47:52 -05:00
2017-04-20 13:01:19 -04:00
yPosition = yOffset
2017-04-20 12:47:14 -04:00
2017-04-18 07:35:29 -04:00
fm = QtGui.QFontMetrics(font)
if alignment == 0: #Left
2017-04-20 13:01:19 -04:00
xPosition = xOffset
if alignment == 1: #Middle
2017-04-20 13:01:19 -04:00
xPosition = xOffset - fm.width(titleText)/2
if alignment == 2: #Right
2017-04-20 13:01:19 -04:00
xPosition = xOffset - fm.width(titleText)
2017-04-20 12:47:14 -04:00
painter.drawText(xPosition, yPosition, titleText)
2015-03-02 16:47:52 -05:00
painter.end()
buffer = QtCore.QBuffer()
buffer.open(QtCore.QIODevice.ReadWrite)
self._image1.save(buffer, "PNG")
strio = io.BytesIO()
strio.write(buffer.data())
buffer.close()
strio.seek(0)
return Image.open(strio)
def drawBars(self, spectrum, image, color):
2015-03-02 16:47:52 -05:00
imTop = Image.new("RGBA", (1280, 360))
draw = ImageDraw.Draw(imTop)
r, g, b = color
color2 = (r, g, b, 50)
2015-03-02 16:47:52 -05:00
for j in range(0, 63):
draw.rectangle((10 + j * 20, 325, 10 + j * 20 + 20, 325 - spectrum[j * 4] * 1 - 10), fill=color2)
draw.rectangle((15 + j * 20, 320, 15 + j * 20 + 10, 320 - spectrum[j * 4] * 1), fill=color)
2015-03-02 16:47:52 -05:00
imBottom = imTop.transpose(Image.FLIP_TOP_BOTTOM)
2015-03-02 16:47:52 -05:00
im = Image.new("RGB", (1280, 720), "black")
im.paste(image, (0, 0))
im.paste(imTop, (0, 0), mask=imTop)
im.paste(imBottom, (0, 360), mask=imBottom)
return im
def readAudioFile(self, filename):
command = [ self.FFMPEG_BIN,
'-i', filename,
'-f', 's16le',
'-acodec', 'pcm_s16le',
'-ar', '44100', # ouput will have 44100 Hz
'-ac', '1', # mono (set to '2' for stereo)
'-']
in_pipe = sp.Popen(command, stdout=sp.PIPE, stderr=sp.DEVNULL, bufsize=10**8)
2015-03-02 16:47:52 -05:00
completeAudioArray = numpy.empty(0, dtype="int16")
while True:
# read 2 seconds of audio
raw_audio = in_pipe.stdout.read(88200*4)
if len(raw_audio) == 0:
break
audio_array = numpy.fromstring(raw_audio, dtype="int16")
completeAudioArray = numpy.append(completeAudioArray, audio_array)
# print(audio_array)
in_pipe.kill()
in_pipe.wait()
# add 0s the end
completeAudioArrayCopy = numpy.zeros(len(completeAudioArray) + 44100, dtype="int16")
completeAudioArrayCopy[:len(completeAudioArray)] = completeAudioArray
completeAudioArray = completeAudioArrayCopy
return completeAudioArray
def transformData(self, i, completeAudioArray, sampleSize, smoothConstantDown, smoothConstantUp, lastSpectrum):
if len(completeAudioArray) < (i + sampleSize):
sampleSize = len(completeAudioArray) - i
window = numpy.hanning(sampleSize)
data = completeAudioArray[i:i+sampleSize][::1] * window
paddedSampleSize = 2048
paddedData = numpy.pad(data, (0, paddedSampleSize - sampleSize), 'constant')
spectrum = numpy.fft.fft(paddedData)
sample_rate = 44100
frequencies = numpy.fft.fftfreq(len(spectrum), 1./sample_rate)
2017-04-17 06:55:58 -04:00
y = abs(spectrum[0:int(paddedSampleSize/2) - 1])
2015-03-02 16:47:52 -05:00
# filter the noise away
# y[y<80] = 0
y = 20 * numpy.log10(y)
y[numpy.isinf(y)] = 0
if lastSpectrum is not None:
lastSpectrum[y < lastSpectrum] = y[y < lastSpectrum] * smoothConstantDown + lastSpectrum[y < lastSpectrum] * (1 - smoothConstantDown)
lastSpectrum[y >= lastSpectrum] = y[y >= lastSpectrum] * smoothConstantUp + lastSpectrum[y >= lastSpectrum] * (1 - smoothConstantUp)
else:
lastSpectrum = y
2017-04-17 06:55:58 -04:00
x = frequencies[0:int(paddedSampleSize/2) - 1]
2015-03-02 16:47:52 -05:00
2017-04-17 06:55:58 -04:00
return lastSpectrum
def deleteTempDir(self):
if self.tempDir and os.path.exists(self.tempDir):
rmtree(self.tempDir)
def getVideoFrames(self, videoPath, firstOnly=False):
self.tempDir = os.path.join(tempfile.gettempdir(), 'audio-visualizer-python-data')
# recreate the temporary directory so it is empty
self.deleteTempDir()
os.mkdir(self.tempDir)
if firstOnly:
filename = 'preview%s.jpg' % os.path.basename(videoPath).split('.', 1)[0]
options = '-ss 10 -vframes 1'
else:
filename = '$frame%05d.jpg'
options = ''
sp.call( \
'%s -i "%s" -y %s "%s"' % ( \
self.FFMPEG_BIN,
videoPath,
options,
os.path.join(self.tempDir, filename)
),
shell=True
)
return sorted([os.path.join(self.tempDir, f) for f in os.listdir(self.tempDir)])
@staticmethod
def RGBFromString(string):
''' turns an RGB string like "255, 255, 255" into a tuple '''
try:
tup = tuple([int(i) for i in string.split(',')])
if len(tup) != 3:
raise ValueError
for i in tup:
if i > 255 or i < 0:
raise ValueError
return tup
except:
return (255, 255, 255)