From ec0abd190273b7b636c7085d7caed8220ab09172 Mon Sep 17 00:00:00 2001 From: tassaron Date: Sun, 16 Jul 2017 14:06:11 -0400 Subject: apply complex filters to audio streams from components tons of sound options could be given now, + installation using setup.py --- src/core.py | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 86 insertions(+), 17 deletions(-) (limited to 'src/core.py') diff --git a/src/core.py b/src/core.py index 4c12209..324b04f 100644 --- a/src/core.py +++ b/src/core.py @@ -468,7 +468,8 @@ class Core: ''' 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 encoders = toolkit.checkOutput( @@ -526,35 +527,99 @@ class Core: '-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 = [ comp.audio() for comp in self.selectedComponents if 'audio' in comp.properties() ] - if extraAudio: - unwantedVideoStreams = [] - for streamNo, params in enumerate(extraAudio): + if extraAudio or globalFilters > 0: + # Add -i options for extra input files + extraFilters = {} + for streamNo, params in enumerate(reversed(extraAudio)): extraInputFile, params = params ffmpegCommand.extend([ - '-t', duration, + '-t', safeDuration, '-i', extraInputFile ]) - if 'map' in params and params['map'] == '-v': - # a video stream to remove - unwantedVideoStreams.append(streamNo + 1) + # Construct dataset of extra filters we'll need to add later + for ffmpegFilter in params: + if streamNo + 2 not in extraFilters: + extraFilters[streamNo + 2] = [] + extraFilters[streamNo + 2].append(( + ffmpegFilter, params[ffmpegFilter] + )) + + # Start creating avfilters! + extraFilterCommand = [] + + if globalFilters <= 0: + # 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]) + ) + ) - if unwantedVideoStreams: - ffmpegCommand.extend(['-map', '0']) - for streamNo in unwantedVideoStreams: - ffmpegCommand.extend([ - '-map', '-%s:v' % str(streamNo) - ]) + # Join all the filters together and combine into 1 stream + extraFilterCommand = "; ".join(extraFilterCommand) + '; ' \ + if tmpInputs else '' ffmpegCommand.extend([ '-filter_complex', - 'amix=inputs=%s:duration=first:dropout_transition=3' % str( - len(extraAudio) + 1 + extraFilterCommand + + '%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([ # OUTPUT '-vcodec', vencoder, @@ -573,7 +638,7 @@ class Core: ffmpegCommand.append(outputFile) return ffmpegCommand - def readAudioFile(self, filename, parent): + def getAudioDuration(self, filename): command = [self.FFMPEG_BIN, '-i', filename] try: @@ -588,6 +653,10 @@ class Core: d = d.split(' ')[3] d = d.split(':') duration = float(d[0])*3600 + float(d[1])*60 + float(d[2]) + return duration + + def readAudioFile(self, filename, parent): + duration = self.getAudioDuration(filename) command = [ self.FFMPEG_BIN, -- cgit v1.2.3 From aa464632c64725201dc7584ebf6bf2c3d06b47b6 Mon Sep 17 00:00:00 2001 From: tassaron Date: Sun, 16 Jul 2017 23:13:00 -0400 Subject: new hotkey to preview the ffmpeg command --- setup.py | 2 +- src/components/video.py | 4 ++-- src/core.py | 6 +++++- src/mainwindow.py | 19 ++++++++++++++++++- 4 files changed, 26 insertions(+), 5 deletions(-) (limited to 'src/core.py') diff --git a/setup.py b/setup.py index 71dc51f..6ef688a 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,7 @@ setup( 'Topic :: Multimedia :: Video :: Non-Linear Editor', ], keywords=['visualizer', 'visualization', 'commandline video', - 'video editor', 'ffmpeg', 'podcast'] + 'video editor', 'ffmpeg', 'podcast'], packages=[ 'avpython', 'avpython.components' diff --git a/src/components/video.py b/src/components/video.py index e1f182c..9e3db30 100644 --- a/src/components/video.py +++ b/src/components/video.py @@ -45,7 +45,7 @@ class Video: '-i', self.videoPath, '-f', 'image2pipe', '-pix_fmt', 'rgba', - '-filter:v', 'scale=%s:%s' % scale( + '-filter_complex', '[0:v] scale=%s:%s' % scale( self.scale, self.width, self.height, str), '-vcodec', 'rawvideo', '-', ] @@ -272,7 +272,7 @@ class Component(Component): '-i', self.videoPath, '-f', 'image2pipe', '-pix_fmt', 'rgba', - '-filter:v', 'scale=%s:%s' % scale( + '-filter_complex', '[0:v] scale=%s:%s' % scale( self.scale, width, height, str), '-vcodec', 'rawvideo', '-', '-ss', '90', diff --git a/src/core.py b/src/core.py index 324b04f..a0a028b 100644 --- a/src/core.py +++ b/src/core.py @@ -541,6 +541,10 @@ class Core: extraInputFile, params = params ffmpegCommand.extend([ '-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 ]) # Construct dataset of extra filters we'll need to add later @@ -551,7 +555,7 @@ class Core: ffmpegFilter, params[ffmpegFilter] )) - # Start creating avfilters! + # Start creating avfilters! Popen-style, so don't use semicolons; extraFilterCommand = [] if globalFilters <= 0: diff --git a/src/mainwindow.py b/src/mainwindow.py index 76ed179..ca8e697 100644 --- a/src/mainwindow.py +++ b/src/mainwindow.py @@ -305,7 +305,12 @@ class MainWindow(QtWidgets.QMainWindow): QtWidgets.QShortcut("Ctrl+A", self.window, self.openSaveProjectDialog) QtWidgets.QShortcut("Ctrl+O", self.window, self.openOpenProjectDialog) 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( "Ctrl+T", self.window, @@ -580,6 +585,18 @@ class MainWindow(QtWidgets.QMainWindow): def showPreviewImage(self, 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): componentList = self.window.listWidget_componentList stackedWidget = self.window.stackedWidget -- cgit v1.2.3