Last active
August 10, 2019 23:38
-
-
Save dataserver/9a7bffda013aef59253dde9d2f7af9fb to your computer and use it in GitHub Desktop.
youtube-dl gui
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="UTF-8"?> | |
<ui version="4.0"> | |
<class>MainWindow</class> | |
<widget class="QMainWindow" name="MainWindow"> | |
<property name="geometry"> | |
<rect> | |
<x>0</x> | |
<y>0</y> | |
<width>454</width> | |
<height>507</height> | |
</rect> | |
</property> | |
<property name="windowTitle"> | |
<string>MainWindow</string> | |
</property> | |
<widget class="QWidget" name="centralwidget"> | |
<layout class="QGridLayout" name="gridLayout"> | |
<item row="0" column="0"> | |
<layout class="QVBoxLayout" name="verticalLayout"> | |
<property name="leftMargin"> | |
<number>10</number> | |
</property> | |
<property name="topMargin"> | |
<number>10</number> | |
</property> | |
<property name="rightMargin"> | |
<number>10</number> | |
</property> | |
<property name="bottomMargin"> | |
<number>10</number> | |
</property> | |
<item> | |
<widget class="QTableWidget" name="tableList"/> | |
</item> | |
<item> | |
<layout class="QHBoxLayout" name="horizontalLayout_2"> | |
<property name="topMargin"> | |
<number>10</number> | |
</property> | |
<property name="bottomMargin"> | |
<number>10</number> | |
</property> | |
<item> | |
<widget class="QLabel" name="label_2"> | |
<property name="sizePolicy"> | |
<sizepolicy hsizetype="Preferred" vsizetype="Preferred"> | |
<horstretch>0</horstretch> | |
<verstretch>0</verstretch> | |
</sizepolicy> | |
</property> | |
<property name="text"> | |
<string>Add Link : </string> | |
</property> | |
</widget> | |
</item> | |
<item> | |
<widget class="QLineEdit" name="inputLink"/> | |
</item> | |
<item> | |
<widget class="QPushButton" name="btnAddLink"> | |
<property name="text"> | |
<string>Add Link</string> | |
</property> | |
</widget> | |
</item> | |
</layout> | |
</item> | |
<item> | |
<spacer name="verticalSpacer"> | |
<property name="orientation"> | |
<enum>Qt::Vertical</enum> | |
</property> | |
<property name="sizeHint" stdset="0"> | |
<size> | |
<width>20</width> | |
<height>60</height> | |
</size> | |
</property> | |
</spacer> | |
</item> | |
<item> | |
<layout class="QHBoxLayout" name="horizontalLayout_6"> | |
<item> | |
<widget class="QCheckBox" name="checkOnlyAudio"> | |
<property name="text"> | |
<string>Only Audio (Convert video files to audio-only files)</string> | |
</property> | |
<property name="checked"> | |
<bool>true</bool> | |
</property> | |
</widget> | |
</item> | |
</layout> | |
</item> | |
<item> | |
<layout class="QHBoxLayout" name="horizontalLayout"> | |
<property name="topMargin"> | |
<number>10</number> | |
</property> | |
<property name="bottomMargin"> | |
<number>10</number> | |
</property> | |
<item> | |
<widget class="QLabel" name="label"> | |
<property name="text"> | |
<string>Output Directory : </string> | |
</property> | |
</widget> | |
</item> | |
<item> | |
<widget class="QLineEdit" name="fieldOutputDir"/> | |
</item> | |
<item> | |
<widget class="QPushButton" name="btnChangeOutputDir"> | |
<property name="text"> | |
<string>Change</string> | |
</property> | |
</widget> | |
</item> | |
</layout> | |
</item> | |
<item> | |
<layout class="QHBoxLayout" name="horizontalLayout_4"> | |
<item> | |
<widget class="QLabel" name="label_3"> | |
<property name="text"> | |
<string>Audio Format: </string> | |
</property> | |
</widget> | |
</item> | |
<item> | |
<widget class="QComboBox" name="dropDownAudioFormat"/> | |
</item> | |
</layout> | |
</item> | |
<item> | |
<spacer name="verticalSpacer_2"> | |
<property name="orientation"> | |
<enum>Qt::Vertical</enum> | |
</property> | |
<property name="sizeHint" stdset="0"> | |
<size> | |
<width>20</width> | |
<height>40</height> | |
</size> | |
</property> | |
</spacer> | |
</item> | |
<item> | |
<layout class="QHBoxLayout" name="horizontalLayout_3"> | |
<property name="topMargin"> | |
<number>20</number> | |
</property> | |
<item> | |
<widget class="QPushButton" name="btnStartDownload"> | |
<property name="text"> | |
<string>Download</string> | |
</property> | |
</widget> | |
</item> | |
<item> | |
<widget class="QPushButton" name="btnClose"> | |
<property name="text"> | |
<string>Close</string> | |
</property> | |
</widget> | |
</item> | |
</layout> | |
</item> | |
</layout> | |
</item> | |
</layout> | |
</widget> | |
<widget class="QMenuBar" name="menubar"> | |
<property name="geometry"> | |
<rect> | |
<x>0</x> | |
<y>0</y> | |
<width>454</width> | |
<height>21</height> | |
</rect> | |
</property> | |
</widget> | |
<widget class="QStatusBar" name="statusbar"/> | |
</widget> | |
<resources/> | |
<connections/> | |
</ui> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os | |
import sys | |
import glob | |
import shlex | |
import subprocess | |
import shutil | |
import json | |
from configparser import ConfigParser | |
from PyQt5 import QtCore, QtGui, QtWidgets | |
from PyQt5.QtCore import pyqtSlot, Qt, QThread, QSize, pyqtSignal, QCoreApplication, QRect | |
from PyQt5.QtWidgets import QMainWindow, QApplication, QMessageBox, QWidget, QAction, QTableWidget, QTableWidgetItem, QPushButton, QVBoxLayout, QLineEdit, QLabel | |
from PyQt5.QtGui import QIcon | |
from PyQt5.uic import loadUi | |
# https://github.com/ytdl-org/youtube-dl | |
""" | |
Original code: Faeris95 | |
https://github.com/Faeris95/YoutubeToMp3-PythonGUI | |
REQUIREMENT: | |
PyQt5 | |
ffmpeg https://ffmpeg.org/ | |
youtube-dl.exe https://ytdl-org.github.io/youtube-dl/download.html | |
""" | |
# subprocess returncode | |
# 0 = success | |
# -N unknow error | |
FETCH_SUCCESS = 0 | |
FETCH_FAIL = -1 | |
class Music: | |
def __init__(self, url, index=0): | |
self.name = None | |
self.setUrl(url) | |
self.title = '' | |
self.filename = '' | |
self.state = '' | |
self.idx = index | |
def getUrl(self): | |
return self.url | |
def setUrl(self, url): | |
self.url = url | |
def getName(self): | |
if(self.name): | |
return self.name | |
else: | |
return self.url | |
def setName(self, name): | |
self.name = name | |
def setState(self, state): | |
self.state = state | |
def getState(self): | |
return self.state | |
def getIndex(self): | |
return self.idx | |
def setTitle(self, title): | |
self.title = title | |
def getTitle(self): | |
return self.title | |
def setFilename(self, filename): | |
self.filename = filename | |
def getFilename(self): | |
return self.filename | |
def setId(self, id): | |
self.id = id | |
def getId(self): | |
return self.id | |
class mySignal(): | |
def __init__(self, nb, state, code=''): | |
self.nb = nb | |
self.state = state | |
self.code = code | |
def getNb(self): | |
return self.nb | |
def getState(self): | |
return self.state | |
def getCode(self): | |
return self.code | |
class InfoThread(QThread): | |
sig = pyqtSignal(Music) | |
def __init__(self, parent=None): | |
super(InfoThread, self).__init__(parent) | |
self.musicList = [] | |
def __del__(self): | |
self.wait() | |
def add(self, music): | |
self.musicList.append(music) | |
def run(self): | |
while(self.musicList): | |
self.music = self.musicList.pop(0) | |
self.music.setState('retriving info') | |
self.sig.emit(self.music) | |
info = self.getJsonInfo() | |
self.music.setId(info['id']) | |
self.music.setName(info['title']) | |
self.music.setFilename(info['title'] + ".mp3") | |
self.music.setState('ready to start') | |
self.sig.emit(self.music) | |
def getJsonInfo(self): | |
exe_path = os.path.join(os.getcwd(), 'youtube-dl.exe') | |
json_string = os.popen( | |
exe_path + ' --simulate --dump-json ' + self.music.getUrl(), 'r').read().rstrip() | |
data = json.loads(json_string) | |
# with open("dump.json", 'w') as f: | |
# json.dump(data, f) | |
return data | |
class DownloaderThread(QThread): | |
sig = pyqtSignal(mySignal) | |
def __init__(self, parent=None): | |
super(DownloaderThread, self).__init__(None) | |
self.parent = parent | |
def setDownloader(self, instance): | |
self.Downloader = instance | |
def setList(self, dic): | |
self.mp3List = dic | |
def addWidgetsList(self, dic): | |
self.lists_widget = dic | |
def run(self): | |
index = 0 | |
for music in self.mp3List: | |
music.setState("downloading...") | |
self.sig.emit(mySignal(index, "downloading...", 0)) | |
resultcode = self.Downloader.fetch(music) | |
if (resultcode == FETCH_SUCCESS): | |
music.setState("downloaded") | |
self.sig.emit( | |
mySignal(index, "downloaded", FETCH_SUCCESS)) | |
else: | |
self.sig.emit(mySignal(index, "failed", resultcode)) | |
index += 1 | |
# self.sig.emit(mySignal(-1, "asdf", FETCH_FAIL)) | |
def __del__(self): | |
self.wait() | |
class Downloader: | |
def __init__(self): | |
pass | |
def setOptions(self, options): | |
self.onlyAudio = options['onlyAudio'] | |
self.audioFormat = options['audioFormat'] | |
def buildCmd(self): | |
exe_path = os.path.join(os.getcwd(), 'youtube-dl.exe') | |
cmd = ' --quiet' | |
if self.onlyAudio == True: | |
cmd += ' --extract-audio' | |
audioFormat = str(self.audioFormat) | |
cmd += ' --audio-format ' + audioFormat | |
self.cmdArgs = shlex.split(cmd) | |
self.cmdArgs.insert(0, exe_path) # split fuckyup blackslash, fix it | |
def getOutputDir(self): | |
config = ConfigParser() | |
config.read('config.ini') | |
return config['DEFAULT']['outputdir'] | |
def setOutputDir(self, txt): | |
config = ConfigParser() | |
config.read('config.ini') | |
config['DEFAULT'] = {'outputdir': txt} | |
with open('config.ini', 'w') as configfile: | |
config.write(configfile) | |
def fetch(self, music): | |
self.buildCmd() | |
outputdir = self.getOutputDir() | |
url = music.getUrl() | |
if 'index' in url: | |
url = url[0:url.find('&index')] | |
if 'list' in url: | |
url = url[0:url.find('&list')] | |
self.cmdArgs.append(url) # last arg is Youtube URL | |
# print(self.cmdArgs) | |
sresult = subprocess.run(self.cmdArgs, creationflags=0x08000000, | |
stdout=subprocess.PIPE, stderr=subprocess.STDOUT) | |
# print(sresult) | |
if (sresult.returncode == FETCH_SUCCESS): | |
if self.audioFormat != "best": | |
self.file = glob.glob('*.' + self.audioFormat) | |
newFileName = self.file[0][:-16] + '.' + self.audioFormat | |
dest_path = os.path.join(outputdir, newFileName) | |
shutil.move(self.file[0], dest_path) | |
return sresult.returncode | |
else: | |
return sresult.returncode | |
class UI(QMainWindow): | |
def __init__(self): | |
super(UI, self).__init__() | |
loadUi("gui.ui", self) | |
self.hasConfig() | |
self.hasExe() | |
self.title = 'GUI Youtube-dl MP3 1.5' | |
self.left = 0 | |
self.top = 0 | |
self.width = 640 | |
self.height = 480 | |
self.rowId = 0 | |
self.lists_music = [] | |
self.lists_widget = [] | |
self.lists_widgetTxt = [] | |
self.Downloader = Downloader() | |
self.InfoThread = InfoThread() | |
self.InfoThread.sig[Music].connect(self.updateMusicList) | |
self.DownloaderThread = DownloaderThread(self) | |
self.DownloaderThread.sig[mySignal].connect(self.showFinishDialog) | |
self.initUI() | |
self.center() | |
def initUI(self): | |
self.setWindowIcon(QIcon('app.png')) | |
self.setGeometry(self.left, self.top, self.width, self.height) | |
columns = ["Title", "Status"] | |
self.tableList.setColumnCount(2) | |
self.tableList.setHorizontalHeaderLabels(columns) | |
self.tableList.setColumnWidth(0, 420) | |
self.tableList.setColumnWidth(1, 180) | |
self.fieldOutputDir.setText(self.Downloader.getOutputDir()) | |
self.btnStartDownload.clicked.connect(lambda: self.activateDownload()) | |
self.btnChangeOutputDir.clicked.connect( | |
lambda: self.actionChangeOutputDir()) | |
self.btnAddLink.clicked.connect(lambda: self.doAddLink()) | |
self.btnClose.clicked.connect(QCoreApplication.instance().quit) | |
audioformats = ["best", "aac", "flac", | |
"mp3", "m4a", "opus", "vorbis", "wav"] | |
for i in range(len(audioformats)): | |
self.dropDownAudioFormat.addItem(audioformats[i]) | |
index = self.dropDownAudioFormat.findText('mp3') | |
self.dropDownAudioFormat.setCurrentIndex(index) | |
self.setWindowTitle(self.title) | |
self.show() | |
def center(self): | |
qr = self.frameGeometry() | |
cp = QtWidgets.QDesktopWidget().availableGeometry().center() | |
qr.moveCenter(cp) | |
self.move(qr.topLeft()) | |
def enableWidget(self, button): | |
for but in button: | |
but.setEnabled(True) | |
def disableWidget(self, button): | |
for but in button: | |
but.setEnabled(False) | |
def showFinishDialog(self, signal): | |
# subprocess returncode | |
# 0 = success | |
# -N unknow error | |
if(signal.getCode() == -2): | |
QMessageBox.critical( | |
self, "Error", "Error downloading, wrong link or youtube-dl is out of date") | |
QCoreApplication.instance().quit | |
elif(signal.getCode() > -1): | |
self.lists_widget[signal.getNb()].setText(signal.getState()) | |
QtWidgets.QApplication.processEvents() | |
else: | |
self.enableWidget( | |
[self.btnChangeOutputDir, self.btnStartDownload, self.btnAddLink]) | |
QMessageBox.information( | |
self, "Completed", "All music downloaded !") | |
def hasConfig(self): | |
if not (os.path.isfile('config.ini')): | |
config = ConfigParser() | |
outputdir = os.path.join(os.getcwd(), 'download') | |
config['DEFAULT'] = {'outputdir': outputdir} | |
with open('config.ini', 'w') as configfile: | |
config.write(configfile) | |
def hasExe(self): | |
missings = [] | |
if not (os.path.isfile('youtube-dl.exe')): | |
missings.append('youtube-dl.exe') | |
if not (os.path.isfile('ffmpeg.exe')): | |
missings.append('ffmpeg.exe') | |
if not (os.path.isfile('ffplay.exe')): | |
missings.append('ffplay.exe') | |
if not (os.path.isfile('ffprobe.exe')): | |
missings.append('ffprobe.exe') | |
if len(missings) > 0: | |
QMessageBox.critical( | |
self, "Error", "Missing files: " + " , ".join(missings)) | |
sys.exit() | |
def activateDownload(self): | |
buttons = [self.btnStartDownload, | |
self.btnChangeOutputDir, self.btnAddLink] | |
self.disableWidget(buttons) | |
self.Downloader.setOptions({ | |
'onlyAudio': self.checkOnlyAudio.isChecked(), | |
'audioFormat': str(self.dropDownAudioFormat.currentText()) | |
}) | |
self.DownloaderThread.setDownloader(self.Downloader) | |
self.DownloaderThread.setList(self.lists_music) | |
self.DownloaderThread.addWidgetsList(self.lists_widget) | |
self.DownloaderThread.start() | |
def actionChangeOutputDir(self): | |
directory = str(QtWidgets.QFileDialog.getExistingDirectory( | |
self, "Select Folder")) | |
self.Downloader.setOutputDir(directory) | |
self.fieldOutputDir.setText(directory) | |
def clearInputLink(self): | |
self.inputLink.clear() | |
def doAddLink(self): | |
url = self.inputLink.text() | |
self.clearInputLink() | |
if (url == ""): | |
QMessageBox.warning(self, "Input Error", | |
"No link!") | |
elif not ("youtube" in url): | |
QMessageBox.warning(self, "Input Error", | |
"Add youtube link!") | |
else: | |
rowIdx = self.tableList.rowCount() | |
music = Music(url, rowIdx) | |
self.tableList.insertRow(rowIdx) | |
cell_0 = QtWidgets.QTableWidgetItem() | |
cell_1 = QtWidgets.QTableWidgetItem() | |
cell_0.setFlags(Qt.ItemIsEnabled) | |
cell_1.setFlags(Qt.ItemIsEnabled) | |
cell_0.setText(music.getName()) | |
cell_1.setText(music.getState()) | |
self.tableList.setItem(rowIdx, 0, cell_0) | |
self.tableList.setItem(rowIdx, 1, cell_1) | |
self.lists_music.append(music) | |
self.lists_widget.append(cell_1) | |
self.lists_widgetTxt.append(cell_0) | |
self.InfoThread.add(music) | |
self.InfoThread.start() | |
def updateMusicList(self, music): | |
self.lists_widgetTxt[music.getIndex()].setText(music.getName()) | |
self.lists_widget[music.getIndex()].setText(music.getState()) | |
if __name__ == '__main__': | |
app = QApplication(sys.argv) | |
ex = UI() | |
sys.exit(app.exec_()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment