Merge pull request #46 from djfun/extra-sound-options

Extra sound options
This commit is contained in:
Brianna 2017-07-16 23:16:35 -04:00 committed by GitHub
commit 4becfe3b51
16 changed files with 354 additions and 79 deletions

View File

@ -1,28 +1,31 @@
audio-visualizer-python audio-visualizer-python
======================= =======================
**We need a good name that is not as generic as "audio-visualizer-python"!**
This is a little GUI tool which creates an audio visualization video from an input audio file. Different components can be added and layered to change the resulting video and add images, videos, gradients, text, etc. The component setup can be saved as a Project and exporting can be automated using commandline options. This is a little GUI tool which creates an audio visualization video from an input audio file. Different components can be added and layered to change the resulting video and add images, videos, gradients, text, etc. Encoding options can be changed with a variety of different output containers.
The program works on Linux, macOS, and Windows. If you encounter problems running it or have other bug reports or features that you wish to see implemented, please fork the project and send me a pull request and/or file an issue on this project. Projects can be created from the GUI and used in commandline mode for easy automation of video production. Create a template project named `template` with your typical visualizers and watermarks, and add text to the top layer from commandline:
`avp template -c 99 text "title=Episode 371" -i /this/weeks/audio.ogg -o out`
I also need a good name that is not as generic as "audio-visualizer-python"! For more information use `avp --help` or for help with a particular component use `avp -c 0 componentName help`.
The program works on Linux, macOS, and Windows. If you encounter problems running it or have other bug reports or features that you wish to see implemented, please fork the project and submit a pull request and/or file an issue on this project.
Dependencies Dependencies
------------ ------------
Python 3, PyQt5, pillow-simd, numpy, and ffmpeg 3.3 Python 3.4, FFmpeg 3.3, PyQt5, Pillow-SIMD, NumPy
**Note:** Pillow may be used as a drop-in replacement for Pillow-SIMD if problems are encountered installing. However this will result in much slower video export times. **Note:** Pillow may be used as a drop-in replacement for Pillow-SIMD if problems are encountered installing. However this will result in much slower video export times. For help troubleshooting installation problems, the * For any problems with installing Pillow-SIMD, see the [Pillow installation guide](http://pillow.readthedocs.io/en/3.1.x/installation.html).
Installation Installation
------------ ------------
### Manual installation on Ubuntu 16.04 ### Manual installation on Ubuntu 16.04
* Install pip: `sudo apt-get install python3-pip` * Install pip: `sudo apt-get install python3-pip`
* Install [prerequisites to compile Pillow](http://pillow.readthedocs.io/en/3.1.x/installation.html#building-on-linux):`sudo apt-get install python3-dev python3-setuptools libtiff5-dev libjpeg8-dev zlib1g-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python-tk` * If Pillow is installed, it must be removed. Nothing should break because Pillow-SIMD is simply a drop-in replacement with better performance.
* Prerequisites on **Fedora**:`sudo dnf install python3-devel redhat-rpm-config libtiff-devel libjpeg-devel libzip-devel freetype-devel lcms2-devel libwebp-devel tcl-devel tk-devel` * Download audio-visualizer-python from this repository and run `sudo pip3 install .` in this directory
* Install dependencies from PyPI: `sudo pip3 install pyqt5 numpy pillow-simd`
* Install `ffmpeg` from the [website](http://ffmpeg.org/) or from a PPA (e.g. [https://launchpad.net/~jonathonf/+archive/ubuntu/ffmpeg-3](https://launchpad.net/~jonathonf/+archive/ubuntu/ffmpeg-3)). NOTE: `ffmpeg` in the standard repos is too old (v2.8). Old versions and `avconv` may be used but full functionality is only guaranteed with `ffmpeg` 3.3 or higher. * Install `ffmpeg` from the [website](http://ffmpeg.org/) or from a PPA (e.g. [https://launchpad.net/~jonathonf/+archive/ubuntu/ffmpeg-3](https://launchpad.net/~jonathonf/+archive/ubuntu/ffmpeg-3)). NOTE: `ffmpeg` in the standard repos is too old (v2.8). Old versions and `avconv` may be used but full functionality is only guaranteed with `ffmpeg` 3.3 or higher.
Download audio-visualizer-python from this repository and run it with `python3 main.py`. Run the program with `avp` or `python3 -m avpython`
### Manual installation on Windows ### Manual installation on Windows
* **Warning:** [Compiling Pillow is difficult on Windows](http://pillow.readthedocs.io/en/3.1.x/installation.html#building-on-windows) and required for the best experience. * **Warning:** [Compiling Pillow is difficult on Windows](http://pillow.readthedocs.io/en/3.1.x/installation.html#building-on-windows) and required for the best experience.

View File

@ -1,19 +1,48 @@
+from setuptools import setup, find_packages from setuptools import setup
import os
-# Dependencies are automatically detected, but it might need +setup(name='audio_visualizer_python',
-# fine tuning. + version='1.0',
-buildOptions = dict(packages = [], excludes = [ + description='a little GUI tool to render visualization \ def package_files(directory):
- "apport", + videos of audio files', paths = []
- "apt", + license='MIT', for (path, directories, filenames) in os.walk(directory):
- "ctypes", + url='https://github.com/djfun/audio-visualizer-python', for filename in filenames:
- "curses", + packages=find_packages(), paths.append(os.path.join('..', path, filename))
- "distutils", + package_data={ return paths
- "email", + 'src': ['*'],
- "html", + },
- "http", + install_requires=['pillow-simd', 'numpy', ''], setup(
- "json", + entry_points={ name='audio_visualizer_python',
- "xmlrpc", + 'gui_scripts': [ version='2.0.0rc1',
- "nose" + 'audio-visualizer-python = avpython.main:main' url='https://github.com/djfun/audio-visualizer-python/tree/feature-newgui',
- ], include_files = ["main.ui"]) + ] license='MIT',
- + } description='Create audio visualization videos from a GUI or commandline',
-import sys + ) long_description="Create customized audio visualization videos and save "
"them as Projects to continue editing later. Different components can "
"be added and layered to add visualizers, images, videos, gradients, "
"text, etc. Use Projects created in the GUI with commandline mode to "
"automate your video production workflow without learning any complex "
"syntax.",
classifiers=[
'Development Status :: 4 - Beta',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 3 :: Only',
'Intended Audience :: End Users/Desktop',
'Topic :: Multimedia :: Video :: Non-Linear Editor',
],
keywords=['visualizer', 'visualization', 'commandline video',
'video editor', 'ffmpeg', 'podcast'],
packages=[
'avpython',
'avpython.components'
],
package_dir={'avpython': 'src'},
package_data={
'avpython': package_files('src'),
},
install_requires=['Pillow-SIMD', 'PyQt5', 'numpy'],
entry_points={
'gui_scripts': [
'avp = avpython.main:main'
],
}
)

0
src/__init__.py Normal file
View File

3
src/__main__.py Normal file
View File

@ -0,0 +1,3 @@
from avpython.main import main
main()

View File

@ -178,8 +178,9 @@ class Component(QtCore.QObject):
The first element can be: The first element can be:
- A string (path to audio file), - A string (path to audio file),
- Or an object that returns audio data through a pipe - Or an object that returns audio data through a pipe
The second element must be a dictionary of ffmpeg parameters The second element must be a dictionary of ffmpeg filters/options
to apply to the input stream. to apply to the input stream. See the filter docs for ideas:
https://ffmpeg.org/ffmpeg-filters.html
\''' \'''
@classmethod @classmethod

View File

@ -17,12 +17,18 @@ class Component(Component):
page.lineEdit_sound.textChanged.connect(self.update) page.lineEdit_sound.textChanged.connect(self.update)
page.pushButton_sound.clicked.connect(self.pickSound) page.pushButton_sound.clicked.connect(self.pickSound)
page.checkBox_chorus.stateChanged.connect(self.update)
page.spinBox_delay.valueChanged.connect(self.update)
page.spinBox_volume.valueChanged.connect(self.update)
self.page = page self.page = page
return page return page
def update(self): def update(self):
self.sound = self.page.lineEdit_sound.text() self.sound = self.page.lineEdit_sound.text()
self.delay = self.page.spinBox_delay.value()
self.volume = self.page.spinBox_volume.value()
self.chorus = self.page.checkBox_chorus.isChecked()
super().update() super().update()
def previewRender(self, previewWorker): def previewRender(self, previewWorker):
@ -46,7 +52,16 @@ class Component(Component):
return "The audio file selected no longer exists!" return "The audio file selected no longer exists!"
def audio(self): def audio(self):
return (self.sound, {}) params = {}
if self.delay != 0.0:
params['adelay'] = '=%s' % str(int(self.delay * 1000.00))
if self.chorus:
params['chorus'] = \
'=0.5:0.9:50|60|40:0.4|0.32|0.3:0.25|0.4|0.3:2|2.3|1.3'
if self.volume != 1.0:
params['volume'] = '=%s:replaygain_noclip=0' % str(self.volume)
return (self.sound, params)
def pickSound(self): def pickSound(self):
sndDir = self.settings.value("componentDir", os.path.expanduser("~")) sndDir = self.settings.value("componentDir", os.path.expanduser("~"))
@ -66,10 +81,16 @@ class Component(Component):
def loadPreset(self, pr, presetName=None): def loadPreset(self, pr, presetName=None):
super().loadPreset(pr, presetName) super().loadPreset(pr, presetName)
self.page.lineEdit_sound.setText(pr['sound']) self.page.lineEdit_sound.setText(pr['sound'])
self.page.checkBox_chorus.setChecked(pr['chorus'])
self.page.spinBox_delay.setValue(pr['delay'])
self.page.spinBox_volume.setValue(pr['volume'])
def savePreset(self): def savePreset(self):
return { return {
'sound': self.sound, 'sound': self.sound,
'chorus': self.chorus,
'delay': self.delay,
'volume': self.volume,
} }
def commandHelp(self): def commandHelp(self):

View File

@ -87,6 +87,29 @@
</item> </item>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_2"> <layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Volume</string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="spinBox_volume">
<property name="suffix">
<string>x</string>
</property>
<property name="maximum">
<double>10.000000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
<property name="value">
<double>1.000000000000000</double>
</property>
</widget>
</item>
<item> <item>
<spacer name="horizontalSpacer_2"> <spacer name="horizontalSpacer_2">
<property name="orientation"> <property name="orientation">
@ -100,6 +123,33 @@
</property> </property>
</spacer> </spacer>
</item> </item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Delay</string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="spinBox_delay">
<property name="suffix">
<string>s</string>
</property>
<property name="maximum">
<double>9999999.990000000223517</double>
</property>
<property name="singleStep">
<double>0.500000000000000</double>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBox_chorus">
<property name="text">
<string>Chorus</string>
</property>
</widget>
</item>
</layout> </layout>
</item> </item>
<item> <item>

View File

@ -45,7 +45,7 @@ class Video:
'-i', self.videoPath, '-i', self.videoPath,
'-f', 'image2pipe', '-f', 'image2pipe',
'-pix_fmt', 'rgba', '-pix_fmt', 'rgba',
'-filter:v', 'scale=%s:%s' % scale( '-filter_complex', '[0:v] scale=%s:%s' % scale(
self.scale, self.width, self.height, str), self.scale, self.width, self.height, str),
'-vcodec', 'rawvideo', '-', '-vcodec', 'rawvideo', '-',
] ]
@ -127,6 +127,7 @@ class Component(Component):
page.checkBox_distort.stateChanged.connect(self.update) page.checkBox_distort.stateChanged.connect(self.update)
page.checkBox_useAudio.stateChanged.connect(self.update) page.checkBox_useAudio.stateChanged.connect(self.update)
page.spinBox_scale.valueChanged.connect(self.update) page.spinBox_scale.valueChanged.connect(self.update)
page.spinBox_volume.valueChanged.connect(self.update)
page.spinBox_x.valueChanged.connect(self.update) page.spinBox_x.valueChanged.connect(self.update)
page.spinBox_y.valueChanged.connect(self.update) page.spinBox_y.valueChanged.connect(self.update)
@ -139,9 +140,17 @@ class Component(Component):
self.useAudio = self.page.checkBox_useAudio.isChecked() self.useAudio = self.page.checkBox_useAudio.isChecked()
self.distort = self.page.checkBox_distort.isChecked() self.distort = self.page.checkBox_distort.isChecked()
self.scale = self.page.spinBox_scale.value() self.scale = self.page.spinBox_scale.value()
self.volume = self.page.spinBox_volume.value()
self.xPosition = self.page.spinBox_x.value() self.xPosition = self.page.spinBox_x.value()
self.yPosition = self.page.spinBox_y.value() self.yPosition = self.page.spinBox_y.value()
if self.useAudio:
self.page.label_volume.setEnabled(True)
self.page.spinBox_volume.setEnabled(True)
else:
self.page.label_volume.setEnabled(False)
self.page.spinBox_volume.setEnabled(False)
super().update() super().update()
def previewRender(self, previewWorker): def previewRender(self, previewWorker):
@ -193,7 +202,10 @@ class Component(Component):
self.badAudio = False self.badAudio = False
def audio(self): def audio(self):
return (self.videoPath, {'map': '-v'}) params = {}
if self.volume != 1.0:
params['volume'] = '=%s:replaygain_noclip=0' % str(self.volume)
return (self.videoPath, params)
def preFrameRender(self, **kwargs): def preFrameRender(self, **kwargs):
super().preFrameRender(**kwargs) super().preFrameRender(**kwargs)
@ -222,6 +234,7 @@ class Component(Component):
self.page.checkBox_useAudio.setChecked(pr['useAudio']) self.page.checkBox_useAudio.setChecked(pr['useAudio'])
self.page.checkBox_distort.setChecked(pr['distort']) self.page.checkBox_distort.setChecked(pr['distort'])
self.page.spinBox_scale.setValue(pr['scale']) self.page.spinBox_scale.setValue(pr['scale'])
self.page.spinBox_volume.setValue(pr['volume'])
self.page.spinBox_x.setValue(pr['x']) self.page.spinBox_x.setValue(pr['x'])
self.page.spinBox_y.setValue(pr['y']) self.page.spinBox_y.setValue(pr['y'])
@ -233,6 +246,7 @@ class Component(Component):
'useAudio': self.useAudio, 'useAudio': self.useAudio,
'distort': self.distort, 'distort': self.distort,
'scale': self.scale, 'scale': self.scale,
'volume': self.volume,
'x': self.xPosition, 'x': self.xPosition,
'y': self.yPosition, 'y': self.yPosition,
} }
@ -258,7 +272,7 @@ class Component(Component):
'-i', self.videoPath, '-i', self.videoPath,
'-f', 'image2pipe', '-f', 'image2pipe',
'-pix_fmt', 'rgba', '-pix_fmt', 'rgba',
'-filter:v', 'scale=%s:%s' % scale( '-filter_complex', '[0:v] scale=%s:%s' % scale(
self.scale, width, height, str), self.scale, width, height, str),
'-vcodec', 'rawvideo', '-', '-vcodec', 'rawvideo', '-',
'-ss', '90', '-ss', '90',

View File

@ -10,6 +10,18 @@
<height>197</height> <height>197</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>197</height>
</size>
</property>
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string>Form</string>
</property> </property>
@ -189,13 +201,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QCheckBox" name="checkBox_useAudio">
<property name="text">
<string>Use Audio</string>
</property>
</widget>
</item>
<item> <item>
<spacer name="horizontalSpacer"> <spacer name="horizontalSpacer">
<property name="orientation"> <property name="orientation">
@ -247,6 +252,62 @@
</item> </item>
</layout> </layout>
</item> </item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_10">
<item>
<widget class="QCheckBox" name="checkBox_useAudio">
<property name="text">
<string>Use Audio</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_volume">
<property name="text">
<string>Volume</string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="spinBox_volume">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="suffix">
<string>x</string>
</property>
<property name="minimum">
<double>0.000000000000000</double>
</property>
<property name="maximum">
<double>10.000000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
<property name="value">
<double>1.000000000000000</double>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item> <item>
<spacer name="verticalSpacer"> <spacer name="verticalSpacer">
<property name="orientation"> <property name="orientation">

View File

@ -468,7 +468,8 @@ class Core:
''' '''
Constructs the major ffmpeg command used to export the video Constructs the major ffmpeg command used to export the video
''' '''
duration = str(duration) safeDuration = "{0:.3f}".format(duration - 0.05) # used by filters
duration = "{0:.3f}".format(duration + 0.1) # used by input sources
# Test if user has libfdk_aac # Test if user has libfdk_aac
encoders = toolkit.checkOutput( encoders = toolkit.checkOutput(
@ -526,35 +527,103 @@ class Core:
'-i', inputFile '-i', inputFile
] ]
# Add extra audio inputs and any needed avfilters
# NOTE: Global filters are currently hard-coded here for debugging use
globalFilters = 0 # increase to add global filters
extraAudio = [ extraAudio = [
comp.audio() for comp in self.selectedComponents comp.audio() for comp in self.selectedComponents
if 'audio' in comp.properties() if 'audio' in comp.properties()
] ]
if extraAudio: if extraAudio or globalFilters > 0:
unwantedVideoStreams = [] # Add -i options for extra input files
for streamNo, params in enumerate(extraAudio): extraFilters = {}
for streamNo, params in enumerate(reversed(extraAudio)):
extraInputFile, params = params extraInputFile, params = params
ffmpegCommand.extend([ ffmpegCommand.extend([
'-t', duration, '-t', safeDuration,
# Tell ffmpeg about shorter clips (seemingly not needed)
# streamDuration = self.getAudioDuration(extraInputFile)
# if streamDuration > float(safeDuration)
# else "{0:.3f}".format(streamDuration),
'-i', extraInputFile '-i', extraInputFile
]) ])
if 'map' in params and params['map'] == '-v': # Construct dataset of extra filters we'll need to add later
# a video stream to remove for ffmpegFilter in params:
unwantedVideoStreams.append(streamNo + 1) if streamNo + 2 not in extraFilters:
extraFilters[streamNo + 2] = []
extraFilters[streamNo + 2].append((
ffmpegFilter, params[ffmpegFilter]
))
if unwantedVideoStreams: # Start creating avfilters! Popen-style, so don't use semicolons;
ffmpegCommand.extend(['-map', '0']) extraFilterCommand = []
for streamNo in unwantedVideoStreams:
ffmpegCommand.extend([ if globalFilters <= 0:
'-map', '-%s:v' % str(streamNo) # Dictionary of last-used tmp labels for a given stream number
]) tmpInputs = {streamNo: -1 for streamNo in extraFilters}
else:
# Insert blank entries for global filters into extraFilters
# so the per-stream filters know what input to source later
for streamNo in range(len(extraAudio), 0, -1):
if streamNo + 1 not in extraFilters:
extraFilters[streamNo + 1] = []
# Also filter the primary audio track
extraFilters[1] = []
tmpInputs = {
streamNo: globalFilters - 1
for streamNo in extraFilters
}
# Add the global filters!
# NOTE: list length must = globalFilters, currently hardcoded
if tmpInputs:
extraFilterCommand.extend([
'[%s:a] ashowinfo [%stmp0]' % (
str(streamNo),
str(streamNo)
)
for streamNo in tmpInputs
])
# Now add the per-stream filters!
for streamNo, paramList in extraFilters.items():
for param in paramList:
source = '[%s:a]' % str(streamNo) \
if tmpInputs[streamNo] == -1 else \
'[%stmp%s]' % (
str(streamNo), str(tmpInputs[streamNo])
)
tmpInputs[streamNo] = tmpInputs[streamNo] + 1
extraFilterCommand.append(
'%s %s%s [%stmp%s]' % (
source, param[0], param[1], str(streamNo),
str(tmpInputs[streamNo])
)
)
# Join all the filters together and combine into 1 stream
extraFilterCommand = "; ".join(extraFilterCommand) + '; ' \
if tmpInputs else ''
ffmpegCommand.extend([ ffmpegCommand.extend([
'-filter_complex', '-filter_complex',
'amix=inputs=%s:duration=first:dropout_transition=3' % str( extraFilterCommand +
len(extraAudio) + 1 '%s amix=inputs=%s:duration=first [a]'
% (
"".join([
'[%stmp%s]' % (str(i), tmpInputs[i])
if i in extraFilters else '[%s:a]' % str(i)
for i in range(1, len(extraAudio) + 2)
]),
str(len(extraAudio) + 1)
), ),
]) ])
# Only map audio from the filters, and video from the pipe
ffmpegCommand.extend([
'-map', '0:v',
'-map', '[a]',
])
ffmpegCommand.extend([ ffmpegCommand.extend([
# OUTPUT # OUTPUT
'-vcodec', vencoder, '-vcodec', vencoder,
@ -573,7 +642,7 @@ class Core:
ffmpegCommand.append(outputFile) ffmpegCommand.append(outputFile)
return ffmpegCommand return ffmpegCommand
def readAudioFile(self, filename, parent): def getAudioDuration(self, filename):
command = [self.FFMPEG_BIN, '-i', filename] command = [self.FFMPEG_BIN, '-i', filename]
try: try:
@ -588,6 +657,10 @@ class Core:
d = d.split(' ')[3] d = d.split(' ')[3]
d = d.split(':') d = d.split(':')
duration = float(d[0])*3600 + float(d[1])*60 + float(d[2]) duration = float(d[0])*3600 + float(d[1])*60 + float(d[2])
return duration
def readAudioFile(self, filename, parent):
duration = self.getAudioDuration(filename)
command = [ command = [
self.FFMPEG_BIN, self.FFMPEG_BIN,

View File

@ -2,12 +2,18 @@ from PyQt5 import uic, QtWidgets
import sys import sys
import os import os
import core
import preview_thread
import video_thread
def main():
if getattr(sys, 'frozen', False):
# frozen
wd = os.path.dirname(sys.executable)
else:
# unfrozen
wd = os.path.dirname(os.path.realpath(__file__))
# make local imports work everywhere
sys.path.insert(0, wd)
if __name__ == "__main__":
mode = 'GUI' mode = 'GUI'
if len(sys.argv) > 2: if len(sys.argv) > 2:
mode = 'commandline' mode = 'commandline'
@ -28,22 +34,15 @@ if __name__ == "__main__":
# app.setOrganizationName("audio-visualizer") # app.setOrganizationName("audio-visualizer")
if mode == 'commandline': if mode == 'commandline':
from command import * from command import Command
main = Command() main = Command()
elif mode == 'GUI': elif mode == 'GUI':
from mainwindow import * from mainwindow import MainWindow
import atexit import atexit
import signal import signal
if getattr(sys, 'frozen', False):
# frozen
wd = os.path.dirname(sys.executable)
else:
# unfrozen
wd = os.path.dirname(os.path.realpath(__file__))
window = uic.loadUi(os.path.join(wd, "mainwindow.ui")) window = uic.loadUi(os.path.join(wd, "mainwindow.ui"))
# window.adjustSize() # window.adjustSize()
desc = QtWidgets.QDesktopWidget() desc = QtWidgets.QDesktopWidget()
@ -64,3 +63,7 @@ if __name__ == "__main__":
# applicable to both modes # applicable to both modes
sys.exit(app.exec_()) sys.exit(app.exec_())
if __name__ == "__main__":
main()

View File

@ -305,7 +305,12 @@ class MainWindow(QtWidgets.QMainWindow):
QtWidgets.QShortcut("Ctrl+A", self.window, self.openSaveProjectDialog) QtWidgets.QShortcut("Ctrl+A", self.window, self.openSaveProjectDialog)
QtWidgets.QShortcut("Ctrl+O", self.window, self.openOpenProjectDialog) QtWidgets.QShortcut("Ctrl+O", self.window, self.openOpenProjectDialog)
QtWidgets.QShortcut("Ctrl+N", self.window, self.createNewProject) QtWidgets.QShortcut("Ctrl+N", self.window, self.createNewProject)
QtWidgets.QShortcut("Ctrl+Alt+Shift+R", self.window, self.drawPreview) QtWidgets.QShortcut(
"Ctrl+Alt+Shift+R", self.window, self.drawPreview
)
QtWidgets.QShortcut(
"Ctrl+Alt+Shift+F", self.window, self.showFfmpegCommand
)
QtWidgets.QShortcut( QtWidgets.QShortcut(
"Ctrl+T", self.window, "Ctrl+T", self.window,
@ -580,6 +585,18 @@ class MainWindow(QtWidgets.QMainWindow):
def showPreviewImage(self, image): def showPreviewImage(self, image):
self.previewWindow.changePixmap(image) self.previewWindow.changePixmap(image)
def showFfmpegCommand(self):
from textwrap import wrap
command = self.core.createFfmpegCommand(
self.window.lineEdit_audioFile.text(),
self.window.lineEdit_outputFile.text(),
self.core.getAudioDuration(self.window.lineEdit_audioFile.text())
)
lines = wrap(" ".join(command), 49)
self.showMessage(
msg="Current FFmpeg command:\n\n %s" % " ".join(lines)
)
def insertComponent(self, index): def insertComponent(self, index):
componentList = self.window.listWidget_componentList componentList = self.window.listWidget_componentList
stackedWidget = self.window.stackedWidget stackedWidget = self.window.stackedWidget

View File

@ -6,7 +6,6 @@ from PyQt5 import QtCore, QtWidgets
import string import string
import os import os
import core
import toolkit import toolkit

View File

@ -6,7 +6,6 @@ from PyQt5 import QtCore, QtGui, uic
from PyQt5.QtCore import pyqtSignal, pyqtSlot from PyQt5.QtCore import pyqtSignal, pyqtSlot
from PIL import Image from PIL import Image
from PIL.ImageQt import ImageQt from PIL.ImageQt import ImageQt
import core
from queue import Queue, Empty from queue import Queue, Empty
import os import os

View File

@ -13,11 +13,14 @@ def badName(name):
return any([letter in string.punctuation for letter in name]) return any([letter in string.punctuation for letter in name])
def alphabetizeDict(dictionary):
'''Alphabetizes a dict into OrderedDict '''
return OrderedDict(sorted(dictionary.items(), key=lambda t: t[0]))
def presetToString(dictionary): def presetToString(dictionary):
'''Alphabetizes a dict into OrderedDict & returns string repr''' '''Returns string repr of a preset'''
return repr( return repr(alphabetizeDict(dictionary))
OrderedDict(sorted(dictionary.items(), key=lambda t: t[0]))
)
def presetFromString(string): def presetFromString(string):

View File

@ -18,7 +18,6 @@ from threading import Thread, Event
import time import time
import signal import signal
import core
from toolkit import openPipe from toolkit import openPipe
from frame import Checkerboard from frame import Checkerboard