diff --git a/sac.py b/sac.py new file mode 100644 index 0000000..57025b8 --- /dev/null +++ b/sac.py @@ -0,0 +1,126 @@ +import os +from PyQt5.QtCore import QProcess, Qt +from PyQt5.QtWidgets import QApplication, QWidget, QGridLayout, QLabel, QLineEdit, QPushButton, QFileDialog, QMessageBox, QComboBox, QRadioButton + +class SAC(QWidget): + def __init__(self): + super().__init__() + self.setWindowTitle('SAC - Simple Audio Converter') + self.source_file_btn = QPushButton('Browse') + self.dest_dir_btn = QPushButton('Browse') + self.convert_btn = QPushButton('Convert') + self.abort_btn = QPushButton('Abort') + self.out_codec_dropdown = QComboBox() + self.bitrate_dropdown = QComboBox() + self.stereo_radio = QRadioButton('Stereo') + self.mono_radio = QRadioButton('Mono') + self.source_file_input = QLineEdit() + self.dest_dir_input = QLineEdit() + + self.out_codec_dropdown.addItems(['mp3', 'aac', 'wav', 'flac', 'opus']) + self.out_codec_dropdown.setCurrentText('mp3') + self.bitrate_dropdown.addItems(['64K', '128K', '256K', '320K']) + self.bitrate_dropdown.setCurrentText('128K') + self.stereo_radio.setChecked(True) + + self.source_file_btn.clicked.connect(self.browse_source_file) + self.dest_dir_btn.clicked.connect(self.browse_dest_dir) + self.convert_btn.clicked.connect(self.convert_audio) + self.abort_btn.clicked.connect(self.abort_audio) + self.out_codec_dropdown.currentTextChanged.connect(self.toggle_bitrate) + self.source_file_input.setAcceptDrops(True) + self.dest_dir_input.setAcceptDrops(True) + + layout = QGridLayout() + layout.addWidget(QLabel('Source File'), 0, 0), layout.addWidget(self.source_file_input, 0, 1), layout.addWidget(self.source_file_btn, 0, 2) + layout.addWidget(QLabel('Destination Directory'), 1, 0), layout.addWidget(self.dest_dir_input, 1, 1), layout.addWidget(self.dest_dir_btn, 1, 2) + layout.addWidget(QLabel('Output Codec'), 2, 0), layout.addWidget(self.out_codec_dropdown, 2, 1) + layout.addWidget(QLabel('Bitrate'), 3, 0), layout.addWidget(self.bitrate_dropdown, 3, 1) + layout.addWidget(QLabel('Channel'), 4, 0), layout.addWidget(self.stereo_radio, 4, 1), layout.addWidget(self.mono_radio, 4, 2) + layout.addWidget(self.convert_btn, 5, 0), layout.addWidget(self.abort_btn, 5, 1) + + self.setLayout(layout) + self.process = None + self.abort_btn.setDisabled(True) + + def browse_source_file(self): + filename, _ = QFileDialog.getOpenFileName(self, 'Select Source File', os.getenv('HOME'), 'Audio File (*.*)') + if filename: + self.source_file_input.setText(filename) + self.dest_dir_input.setText(os.path.dirname(filename)) + + def browse_dest_dir(self): + dirpath = QFileDialog.getExistingDirectory(self, 'Select Destination Directory', self.dest_dir_input.text()) + if dirpath: + self.dest_dir_input.setText(dirpath) + + def convert_audio(self): + input_path = self.source_file_input.text() + output_dir = self.dest_dir_input.text() + out_codec = self.out_codec_dropdown.currentText() + bitrate = self.bitrate_dropdown.currentText() if not out_codec in ['flac'] else '' + channel = '2' if self.stereo_radio.isChecked() else '1' + + if not os.path.isfile(input_path) or not os.path.isdir(output_dir): return + + output_filename = os.path.splitext(os.path.basename(input_path))[0] + '.' + out_codec + output_path = os.path.join(output_dir, output_filename) + + if out_codec == 'opus': + cmd = f'ffmpeg -i "{input_path}" -ac {channel} -b:a {bitrate.lower()} -c:a libopus -application audio -strict -2 "{output_path}"' + elif out_codec == 'wav': + cmd = f'ffmpeg -i "{input_path}" -ac {channel} -c:a pcm_s16le "{output_path}"' + else: + cmd = f'ffmpeg -i "{input_path}" -ac {channel} "{output_path}"' + + [btn.setDisabled(True) for btn in [self.convert_btn, self.source_file_btn, self.dest_dir_btn, self.out_codec_dropdown, self.bitrate_dropdown, self.stereo_radio, self.mono_radio]] + self.abort_btn.setDisabled(False) + + try: + self.process = QProcess() + self.process.setProcessChannelMode(QProcess.MergedChannels) + self.process.start(cmd) + if not self.process.waitForStarted(): + self.show_error('Error', 'Failed to start process') + else: + while self.process.state() == QProcess.Running: + QApplication.processEvents() + if self.process.exitCode() == 0: + QMessageBox.information(self, 'Success', f'Audio file converted successfully: {output_path}') + else: + self.show_error('Error', f'Failed to convert audio file: {output_path}') + except Exception as e: + self.show_error('Error', str(e)) + finally: + [btn.setDisabled(False) for btn in [self.convert_btn, self.source_file_btn, self.dest_dir_btn, self.out_codec_dropdown, self.bitrate_dropdown, self.stereo_radio, self.mono_radio]] + self.abort_btn.setDisabled(True) + + def abort_audio(self): + self.process.terminate() + + def toggle_bitrate(self): + is_opus = self.out_codec_dropdown.currentText() == 'opus' + self.bitrate_dropdown.setDisabled(not is_opus) + + def show_error(self, title, message): + QMessageBox.critical(self, title, message) + + def dragEnterEvent(self, event): + if event.mimeData().hasUrls(): + event.acceptProposedAction() + + def dropEvent(self, event): + if event.mimeData().hasUrls(): + url = event.mimeData().urls()[0] + path = url.toLocalFile() + if os.path.isfile(path): + self.source_file_input.setText(path) + self.dest_dir_input.setText(os.path.dirname(path)) + event.acceptProposedAction() + +if __name__ == '__main__': + app = QApplication([]) + sac = SAC() + sac.show() + app.exec_() +