I use PyQt-thread for parallel conversion of mp3 files to aac via ffmpeg. Here is my code:
class SubprocessThread(QThread):
signal = pyqtSignal('PyQt_PyObject')
def __init__(self, command, args):
QThread.__init__(self)
self.command = command
self.args = args
def __del__(self):
self.wait()
def run(self):
output = subprocess.check_output('{0} {1}'.format(self.command, self.args), shell=True).split()
self.signal.emit(output)
And here is example of usage:
threads = []
for part in parts.keys():
args = "-i \'{0}.mp3\' -c:a aac -b:a {1}k \'{2}.m4a\'".format(
os.path.join(tmp_dir, str(part)),
int(self.bitrate_cbx.currentText()),
os.path.join(tmp_dir, str(part)))
print(args) # debug
ffmpeg_thread = SubprocessThread('ffmpeg', args)
ffmpeg_thread.signal.connect(self.on_data_ready)
threads.append(ffmpeg_thread)
ffmpeg_thread.start()
self.threads_count += 1
I want to make progress bar, based on conversion, but ffmpeg always updates last string in his output (when conversion in progress). Here is an example of ffmpeg output while files are converting:
user@host$ ffmpeg -i '/home/user/001.mp3' -c:a aac -b:a 128k -vn '/home/user/test.m4a'
ffmpeg version n4.2.1 Copyright (c) 2000-2019 the FFmpeg developers
built with gcc 9.2.0 (GCC)
configuration: --prefix=/usr --disable-debug --disable-static --disable-stripping --enable-fontconfig --enable-gmp --enable-gnutls --enable-gpl --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libdav1d --enable-libdrm --enable-libfreetype --enable-libfribidi --enable-libgsm --enable-libiec61883 --enable-libjack --enable-libmodplug --enable-libmp3lame --enable-libopencore_amrnb --enable-libopencore_amrwb --enable-libopenjpeg --enable-libopus --enable-libpulse --enable-libsoxr --enable-libspeex --enable-libssh --enable-libtheora --enable-libv4l2 --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxcb --enable-libxml2 --enable-libxvid --enable-nvdec --enable-nvenc --enable-omx --enable-shared --enable-version3
libavutil 56. 31.100 / 56. 31.100
libavcodec 58. 54.100 / 58. 54.100
libavformat 58. 29.100 / 58. 29.100
libavdevice 58. 8.100 / 58. 8.100
libavfilter 7. 57.100 / 7. 57.100
libswscale 5. 5.100 / 5. 5.100
libswresample 3. 5.100 / 3. 5.100
libpostproc 55. 5.100 / 55. 5.100
Input #0, mp3, from '/home/user/001.mp3':
Metadata:
encoder : Lavf57.41.100
title : test
artist : test
album_artist : test
album : test
composer : test
genre : test
date : 2018
Duration: 00:12:38.02, start: 0.025056, bitrate: 192 kb/s
Stream #0:0: Audio: mp3, 44100 Hz, stereo, fltp, 192 kb/s
Metadata:
encoder : Lavc57.48
Stream #0:1: Video: mjpeg (Baseline), yuvj420p(pc, bt470bg/unknown/unknown), 500x500 [SAR 1:1 DAR 1:1], 90k tbr, 90k tbn, 90k tbc (attached pic)
Metadata:
comment : Cover (front)
Stream mapping:
Stream #0:0 -> #0:0 (mp3 (mp3float) -> aac (native))
Press [q] to stop, [?] for help
Output #0, ipod, to '/home/user/test.m4a':
Metadata:
date : test
title : test
artist : test
album_artist : test
album : test
composer : test
genre : test
encoder : Lavf58.29.100
Stream #0:0: Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 128 kb/s
Metadata:
encoder : Lavc58.54.100 aac
size= 12107kB time=00:12:38.01 bitrate= 130.8kbits/s speed=79.2x
How can I receive this data (string, that begins from "size=...") from my parallel QThreads to calculate overall progress?
You do not have to use subprocess.check_output()
+ QThread
since as you see you only get the log when the execution is finished. Instead, use QProcess
that notifies the log while the conversion is still running.
class Converter(QObject):
dataChanged = pyqtSignal(object)
def __init__(self, parent=None):
super().__init__(parent)
self._process = QProcess()
self._process.setProcessChannelMode(QProcess.MergedChannels)
self._process.readyReadStandardOutput.connect(self.on_readyReadStandardOutput)
self._process.setProgram("ffmpeg")
def convert(self, source, destination, bitrate):
# remove if destination exist
QFile.remove(destination)
args = ["-i", source, "-c:a", "aac", "-b:a", bitrate, "-vn", destination]
self._process.setArguments(args)
self._process.start()
@pyqtSlot()
def on_readyReadStandardOutput(self):
keys = ("size", "time", "bitrate", "speed")
data = self._process.readAllStandardOutput()
msg = data.data().decode()
for line in msg.splitlines():
line = line.replace(" ", "").replace("=", "")
if all(key in line for key in keys):
values = []
for left, right in zip(keys[:-1], keys[1:]):
# https://stackoverflow.com/a/51456576/6622587
value = line[line.index(left) + len(left) : line.index(right)]
values.append(value)
value = line[line.index(keys[-1]) + len(keys[-1]) :]
values.append(value)
d = dict(zip(keys, values))
self.dataChanged.emit(d)
And here is example of usage:
# ...
converters = []
for part in parts.keys():
converter = Converter()
converter.dataChanged.connect(self.on_data_ready)
converter.convert(
"{}.mp3".format(part),
"{}.m4a".format(part),
"{}k".format(self.bitrate_cbx.currentText()),
)
converters.append(converter)
# ...
# ...
@pyqtSlot(object)
def on_data_ready(self, data):
print(data)
Output:
{'size': '256kB', 'time': '00:00:17.94', 'bitrate': '116.9kbits/s', 'speed': '35.9x'}
{'size': '512kB', 'time': '00:00:44.62', 'bitrate': '94.0kbits/s', 'speed': '44.6x'}
{'size': '1024kB', 'time': '00:01:11.21', 'bitrate': '117.8kbits/s', 'speed': '47.5x'}
{'size': '1280kB', 'time': '00:01:37.66', 'bitrate': '107.4kbits/s', 'speed': '48.8x'}
{'size': '1941kB', 'time': '00:02:02.46', 'bitrate': '129.8kbits/s', 'speed': '49.6x'}