import sys import os import pathlib import shutil import errno from collections import OrderedDict from functools import partial from pytube import Playlist, YouTube, exceptions from DXMB_ui import Ui_dxmb from YTVidItem import QYTVidItem from PyQt5 import QtCore as qtc from PyQt5 import QtWidgets as qtw from PyQt5 import QtGui as qtg import resources ##ToolKlasse ButtonManager (Für SongbookTab) class BTN_MANAGER(): def copyAtoB(self,sourcePath_list,targetPath_list):#done # Pro SourcePath immer jeweils ein targetPath auch wenn das gleicher ort ist -> sonst error(oft permission error) #print("BTN Man: CopyAtoB") if len(sourcePath_list) != len(targetPath_list): print("Error: Path Count mismatch") return try: for index in range(0,len(sourcePath_list)) : sourcePath = sourcePath_list[index] targetPath = targetPath_list[index] try: shutil.copytree(sourcePath,targetPath,dirs_exist_ok=True) except OSError as e: # If the error was caused because the source wasn't a directory if e.errno == errno.ENOTDIR: shutil.copy(sourcePath,targetPath) else: print('Directory not copied. Error: %s' % e) except PermissionError: print("PermissionError. Try function in admin-role!") except Exception as e: try: print("Error: {}".format(e.what())) except: print("Unknown Error.") def moveAtoB(self,sourcePath_list,targetPath_list):#done #print("BTN Man: MoveAtoB") if (len(sourcePath_list) != len(targetPath_list)): print("Error: Path Count mismatch") print("Source: {} | Target:{} ".format(len(sourcePath_list),len(targetPath_list))) print("Source: {} ".format(sourcePath_list)) print("Target: {} ".format(targetPath_list)) return try: for index in range(0,len(sourcePath_list)) : sourcePath = sourcePath_list[index] targetPath = targetPath_list[index] shutil.move(sourcePath,targetPath) except PermissionError: print("PermissionError. Try function in admin-role!") except Exception as e: try: print("Error: ()".format(e.what())) except: print("Unknown Error.") def delete(self,targetPath):#done if os.path.isdir(targetPath): shutil.rmtree(targetPath,ignore_errors=True) else: if os.path.isfile(targetPath): os.remove(targetPath) else: #dürfte nie auftreten print("Error Delete: File doesn't exist anymore") ##DXM Bragi - Hauptprogramm class DXM_Bragi(qtw.QMainWindow): def __init__(self, *args, **kwargs): super().__init__(*args,**kwargs) self.main_window = Ui_dxmb() self.main_window.setupUi(self) self.btn_man = BTN_MANAGER() self.homePath = os.getenv('USERPROFILE') self.curr_filePath = os.path.dirname(os.path.abspath(__file__)) self.prepare_songbook_tab() self.prepare_learnTaste_tab() self.prepare_searchSongs_tab() self.ytVidODict = OrderedDict() #################################################################################### ## SongBook Tab (Filesystem für Song-Verwaltung Tab) def prepare_songbook_tab(self):#semi done (nur noch connecten zu Analyse) #Vorbereiten aller Felder und Funktionen für Songbook #Source TreeView (Links) #Default Source Path "Eigene Musik" von User (Wenn nicht da dann Home Order selbst) self.sourcePath = self.homePath if (self.homePath !=None): musicPath = os.path.join(self.homePath,'Music') if (os.path.isdir(musicPath)): self.sourcePath = musicPath self.model_source = qtw.QFileSystemModel() self.model_source.setRootPath(self.sourcePath) self.setTreeView("Source",self.main_window.treeView_source,self.model_source,self.sourcePath) #Target TreeView(Rechts) #Default Target Path in aktuellem File Ordner (Defualt in Audio-Library) #Wenn Ordner noch nicht exisitert -> Erstellen targetPath = os.path.join(self.curr_filePath, "Audio Library") if (not os.path.isdir(targetPath)): os.makedirs(targetPath) self.model_target = qtw.QFileSystemModel() self.model_target.setRootPath(targetPath) self.setTreeView("Target",self.main_window.treeView_target,self.model_target,targetPath) #Funktionsbuttons connecten #SourceTree(links) self.main_window.btn_copyToTarget.clicked.connect(self.copy_file_sourceToTarget) self.main_window.btn_copyAllToTarget.clicked.connect(self.copy_fileAll_sourceToTarget) self.main_window.btn_moveToTarget.clicked.connect(self.move_file_sourceToTarget) self.main_window.btn_moveAllToTarget.clicked.connect(self.move_fileAll_sourceToTarget) self.main_window.btn_deleteSourceItem.clicked.connect(self.delete_file_source) self.main_window.btn_makeDirSource.clicked.connect(self.make_dir_source) self.main_window.btn_analyseSource.clicked.connect(self.analyse_file_source) self.main_window.toolButton_source.clicked.connect(self.setNewRootPath_source) #TargetTree(rechts) self.main_window.btn_copyToSource.clicked.connect(self.copy_file_targetToSource) self.main_window.btn_copyAllToSource.clicked.connect(self.copy_fileAll_targetToSource) self.main_window.btn_moveToSource.clicked.connect(self.move_file_targetToSource) self.main_window.btn_moveAllToSource.clicked.connect(self.move_fileAll_targetToSource) self.main_window.btn_deleteTargetItem.clicked.connect(self.delete_file_target) self.main_window.btn_makeDirTarget.clicked.connect(self.make_dir_target) self.main_window.btn_analyseTarget.clicked.connect(self.analyse_file_target) self.main_window.toolButton_target.clicked.connect(self.setNewRootPath_target) #### ## Context Menü Funktionen #### def context_Menu_source(self):#semi-done(Add new menus) menu = qtw.QMenu() open = menu.addAction("Open") open.triggered.connect(self.open_file_source) cursor = qtg.QCursor() menu.exec_(cursor.pos()) def context_Menu_target(self):#semi-done(Add new menus) menu = qtw.QMenu() open = menu.addAction("Open") open.triggered.connect(self.open_file_target) cursor = qtg.QCursor() menu.exec_(cursor.pos()) def open_file_source(self):#done index = self.tree_source.currentIndex() tree_file_path = self.model_source.filePath(index) os.startfile(tree_file_path) def open_file_target(self):#done index = self.tree_target.currentIndex() tree_file_path = self.model_target.filePath(index) os.startfile(tree_file_path) #### ## Source TreeView Funktionen #### def copy_file_sourceToTarget(self):#done self.copy_file_AToB(self.tree_source,self.model_source,self.tree_target,self.model_target,self.main_window.lineEdit_target) def copy_fileAll_sourceToTarget(self):#done index_source = self.tree_source.currentIndex() if (os.path.isdir(self.model_source.filePath(index_source))): self.copy_file_AToB(self.tree_source,self.model_source,self.tree_target,self.model_target,self.main_window.lineEdit_target) else: self.copy_file_AToB(self.tree_source,self.model_source,self.tree_target,self.model_target,self.main_window.lineEdit_target,True) def move_file_sourceToTarget(self):#dome self.move_file_AToB(self.tree_source,self.model_source,self.tree_target,self.model_target,self.main_window.lineEdit_target) def move_fileAll_sourceToTarget(self):#done self.move_file_AToB(self.tree_source,self.model_source,self.tree_target,self.model_target,self.main_window.lineEdit_target,True) def delete_file_source(self):#done self.delete_file_inTree(self.tree_source,self.model_source) def make_dir_source(self):#done self.readDirName(self.tree_source,self.model_source,self.main_window.lineEdit_source) def setNewRootPath_source(self):#done self.setNewRootPath("Source",self.main_window.lineEdit_source.text()) def analyse_file_source(self): pass #### ## Target TreeView Funktionen #### def copy_file_targetToSource(self):#done self.copy_file_AToB(self.tree_target,self.model_target,self.tree_source,self.model_source,self.main_window.lineEdit_source) def copy_fileAll_targetToSource(self):#done index_target = self.tree_target.currentIndex() if (os.path.isdir(self.model_target.filePath(index_target))): self.copy_file_AToB(self.tree_target,self.model_target,self.tree_source,self.model_source,self.main_window.lineEdit_source) else: self.copy_file_AToB(self.tree_target,self.model_target,self.tree_source,self.model_source,self.main_window.lineEdit_source,True) def move_file_targetToSource(self):#done self.move_file_AToB(self.tree_target,self.model_target,self.tree_source,self.model_source,self.main_window.lineEdit_source) def move_fileAll_targetToSource(self):#done self.move_file_AToB(self.tree_target,self.model_target,self.tree_source,self.model_source,self.main_window.lineEdit_source,True) def delete_file_target(self):#done self.delete_file_inTree(self.tree_target,self.model_target) def make_dir_target(self):#done self.readDirName(self.tree_target,self.model_target,self.main_window.lineEdit_target) def setNewRootPath_target(self):#done self.setNewRootPath("Target",self.main_window.lineEdit_target.text()) def analyse_file_target(self): pass #### ## Allgemeine ToolFunktionen #### def setTreeView(self,profile ,treeView,model,newPath): #TODO iwann herausfinden wie columns über HeaderData in FileSystemModel geändert werden können #Vermutlich irgendwie über subclasse bilden if( profile == "Source"): self.tree_source = treeView self.tree_source.setModel(model) self.tree_source.setAlternatingRowColors(True) model.directoryLoaded.connect(self.tree_source.expandAll) self.tree_source.setRootIndex(model.index(newPath)) self.tree_source.setSortingEnabled(True) self.tree_source.setContextMenuPolicy(qtc.Qt.CustomContextMenu) self.tree_source.customContextMenuRequested.connect(self.context_Menu_source) self.tree_source.setColumnWidth(0,175) self.tree_source.setColumnWidth(1,50) self.main_window.lineEdit_source.setText(newPath) elif (profile == "Target"): self.tree_target = treeView self.tree_target.setModel(model) self.tree_target.setAlternatingRowColors(True) model.directoryLoaded.connect(self.tree_target.expandAll) self.tree_target.setRootIndex(model.index(newPath)) self.tree_target.setSortingEnabled(True) self.tree_target.setContextMenuPolicy(qtc.Qt.CustomContextMenu) self.tree_target.customContextMenuRequested.connect(self.context_Menu_target) self.tree_target.setColumnWidth(0,175) self.tree_target.setColumnWidth(1,50) self.main_window.lineEdit_target.setText(newPath) else: print("Error: Unknown TreeView") def copy_file_AToB(self,treeSource,modelSource,treeTarget,modelTarget,lineEditTarget,copyFromParentFolder=False):#done #Note Regeln: Wenn kein source index gewählt-> kein einzel Copy index_source = treeSource.currentIndex() if (modelSource.filePath(index_source) ==''): #Fehler :Ohne selektion kein einzel copy print("Error: Kein einzel copy ohne Selektion") return treeSource_filePath = list() if (copyFromParentFolder): treeSource_filePath.append(os.path.dirname(modelSource.filePath(index_source))) else: treeSource_filePath.append(modelSource.filePath(index_source)) index_target = treeTarget.currentIndex() treeTarget_filePath = list() if (modelTarget.filePath(index_target) ==''): treeTarget_filePath.append(lineEditTarget.text()) if (os.path.isdir(modelTarget.filePath(index_target))): treeTarget_dirName = modelTarget.filePath(index_target) else: treeTarget_dirName = os.path.dirname(modelTarget.filePath(index_target)) if (copyFromParentFolder): target_dirName = os.path.dirname(modelTarget.filePath(index_target)) treeTarget_filePath.append(target_dirName) else: treeSource_fileName = modelSource.fileName(index_source) treeTarget_filePath.append(os.path.join(treeTarget_dirName,treeSource_fileName)) #Auführen des Copy-Befehls von BTN-Man self.btn_man.copyAtoB(treeSource_filePath,treeTarget_filePath) def move_file_AToB(self,treeSource,modelSource,treeTarget,modelTarget,lineEditTarget,moveAllInFolder=False):#done #Note Regeln: Wenn kein Index gewählt-> Nichts bewegen(Keine Aktion) #Wenn Ordner als Index (Außer Root Ordner) -> Kompletten order + Inhalt zu Ziel Bewegen #Wenn MoveAll auf File als Index -> alle Elemente des Parent ordners OHNE Ordner selbst bewegen #Wenn MoveAll auf Ordner als Index -> Kompletten ordner + Inhalt zu Ziel bewegen index_source = treeSource.currentIndex() if (modelSource.filePath(index_source) ==''): #Fehler :Ohne selektion kein einzel copy print("Error: Kein einzel move ohne Selektion") return treeSource_filePath = list() treeSource_fileNames = list() sourceIsDir = False if (os.path.isdir(modelSource.filePath(index_source))):#Move/MoveAll auf Folder sourceIsDir = True #Wenn Index ein Folder ist dann ist moveAll Funktion = move Funktion moveAllInFolder = False treeSource_filePath.append(modelSource.filePath(index_source)) else: if (moveAllInFolder):#MoveAll auf File for file in os.listdir(os.path.dirname(modelSource.filePath(index_source))): treeSource_filePath.append(os.path.join(os.path.dirname(modelSource.filePath(index_source)),file)) treeSource_fileNames.append(file) else:#Move auf einzelnes File treeSource_filePath.append(modelSource.filePath(index_source)) treeSource_fileName = modelSource.fileName(index_source) index_target = treeTarget.currentIndex() treeTarget_filePath = list() treeTarget_dirName = str() targetIsDir = False if (modelTarget.filePath(index_target) ==''): treeTarget_dirName = lineEditTarget.text() #RootOrdner in LineEdit ist IMMER Folder targetIsDir = True else: if (os.path.isdir(modelTarget.filePath(index_target))): treeTarget_dirName = modelTarget.filePath(index_target) targetIsDir = True else: treeTarget_dirName = os.path.dirname(modelTarget.filePath(index_target)) if(moveAllInFolder):#MoveAll auf File in Source -> Einzel-Move-Paths vorbereiten for file in treeSource_fileNames: treeTarget_filePath.append(os.path.join(treeTarget_dirName,file)) else: if (sourceIsDir == True and targetIsDir == True): treeTarget_filePath.append(modelTarget.filePath(index_target)) elif(sourceIsDir == True and targetIsDir == False): treeTarget_filePath.append(treeTarget_dirName) else: treeTarget_filePath.append(os.path.join(treeTarget_dirName,treeSource_fileName)) #Ausführen BTN-Man Funktion self.btn_man.moveAtoB(treeSource_filePath,treeTarget_filePath) def delete_file_inTree(self,targetTree,targetModel):#done #Note Regeln: Wenn kein Index gewählt -> Kein File Löschen (Keine Aktion; vllt Warn-Print) #Wenn Ordner als Index (Außer Root Ordner) -> Kompletten order + Inhalt löschen index_source = targetTree.currentIndex() if (targetModel.filePath(index_source) ==''): #Fehler :Ohne selektion kein einzel delete print("Error: Kein einzel copy ohne Selektion") return # Ausführen BTN-Man Command self.btn_man.delete(targetModel.filePath(index_source)) def readDirName(self,tree,model,lineEdit):#done name, result = qtw.QInputDialog.getText(self, "Folder Name","Enter new folder name:") if(result): index_source = tree.currentIndex() if (model.filePath(index_source) ==''): #Kein Index gewählt-> LineEdit als Quell-Pfad filePath = os.path.join(lineEdit.text(),name) else: if (os.path.isdir(model.filePath(index_source))): filePath = os.path.join(model.filePath(index_source),name) else: dirName = os.path.dirname(model.filePath(index_source)) filePath = os.path.join(dirName,name) os.makedirs(filePath) else: print("Error: Eingabe Abbruch") def setNewRootPath(self,profile,currentPath):#done dialog = qtw.QFileDialog(self) dialog.setWindowTitle('Set Root Path for Tree') dialog.setDirectory(currentPath) dialog.setFileMode(qtw.QFileDialog.DirectoryOnly) if dialog.exec_() == qtw.QDialog.Accepted: file_full_path = str(dialog.selectedFiles()[0]) print(file_full_path) if (profile == "Source"): self.model_source = qtw.QFileSystemModel() self.model_source.setRootPath(file_full_path) self.setTreeView("Source",self.main_window.treeView_source,self.model_source,file_full_path) elif(profile == "Target"): self.model_target = qtw.QFileSystemModel() self.model_target.setRootPath(file_full_path) self.setTreeView("Target",self.main_window.treeView_target,self.model_target,file_full_path) else: print("Error: Unknown TreeView") else: print("Error: Abbruch SetNewRoot") def analyse_file(self,filePah): # Nutze ausgewähltes File für "Learn Taste Tab" und starte analyse pass ######################################################################################### ## Learn Taste tab (MachineLearning Tab) def prepare_learnTaste_tab(self): pass ######################################################################################### ## Search Songs tab (Youtube-Downloader & ML-Prediction Tab) def prepare_searchSongs_tab(self): self.targetPath = os.path.join(self.curr_filePath, "Downloads") if (not os.path.isdir(self.targetPath)): os.makedirs(self.targetPath) self.main_window.lineEdit_targetPath.setText(self.targetPath) #Funktionsbuttons connecten self.main_window.btn_insertUrl.clicked.connect(self.insertUrl) self.main_window.toolBtn_targetPath.clicked.connect(self.setTargetPath) self.main_window.toolBtn_addProfile.clicked.connect(self.addProfile) self.main_window.toolBtn_removeProfile.clicked.connect(self.removeProfile) def insertUrl(self): self.main_window.insert_url_msg.setText("") if (self.main_window.lineEdit_url.text() == ''): return else: newUrl = self.main_window.lineEdit_url.text() ##Checken ob Link bereits als Objekt vorliegt (2mal laden der gleichen URL führt zu absturz) if newUrl.strip() in self.ytVidODict: self.main_window.insert_url_msg.setText("Error: URL allready loaded") return #Test URL: Rick Roll https://www.youtube.com/watch?v=o-YBDTqX_ZU #Test URL2: BudS&TerH - Lala Remix: https://www.youtube.com/watch?v=lL7jwhWAU_k #Test URL3: Nee Junge: https://www.youtube.com/watch?v=1uNHA3pcDmQ item = QYTVidItem(self.main_window.scrollA) returnCode = item.setData(newUrl) #print("returnCode: ",returnCode ) if (returnCode == "OK"): row = self.main_window.url_scroller_grid.count() col = 0 self.main_window.url_scroller_grid.setRowStretch(row,0) self.main_window.url_scroller_grid.addWidget(item,row,col,0,0) #Setzen der externen Buttons item.btn_remove.clicked.connect(partial(self.removeYTVid,newUrl)) item.btn_download.clicked.connect(partial(self.downloadYTVid,newUrl)) #Speichern in der OrderedDict: self.ytVidODict[newUrl.strip()] = item else: self.main_window.insert_url_msg.setText(returnCode) self.main_window.lineEdit_url.setText("") def setTargetPath(self): pass def addProfile(self): pass def removeProfile(self): pass def downloadYTVid(self,url): targetPath = self.main_window.lineEdit_targetPath.text() print("Download nach {}".format(targetPath)) for vidItem in self.ytVidODict: if vidItem == url.strip(): self.ytVidODict[vidItem].downloadItem(targetPath) return print("Error: ungültiges Item") def removeYTVid(self,url): #Rauswerfen aus der Scrollarea (nachrücken aller folgenden Items im Grid) for vidItem in self.ytVidODict: if vidItem == url.strip(): self.ytVidODict[vidItem].deleteLater() self.ytVidODict.pop(url.strip()) break #danach gridlayout neu aufbauen for i in reversed(range(self.main_window.url_scroller_grid.count())): widgetToRemove = self.main_window.url_scroller_grid.itemAt( i ).widget() # remove it from the layout list self.main_window.url_scroller_grid.removeWidget( widgetToRemove ) # gridlayout neu aufbauen for vidItem in self.ytVidODict: row = self.main_window.url_scroller_grid.count() col = 0 self.main_window.url_scroller_grid.addWidget(self.ytVidODict[vidItem],row,col,1,1) #################################################################################### ## ChangeSongsDetails Tab (ID3-Tag Editor für Songs Tab) def changeSongDetails_tab(self):#to be implemented #Auslesen und Manipulieren aller ID3 Tags eines Songs pass ############################################################################################# if __name__ == "__main__": app = qtw.QApplication([]) ##Set "DarkMode" app.setStyle("Fusion") # Now use a palette to switch to dark colors: palette = qtg.QPalette() palette.setColor(qtg.QPalette.Window, qtg.QColor(53, 53, 53)) palette.setColor(qtg.QPalette.WindowText, qtc.Qt.white) palette.setColor(qtg.QPalette.Base, qtg.QColor(25, 25, 25)) palette.setColor(qtg.QPalette.AlternateBase, qtg.QColor(53, 53, 53)) palette.setColor(qtg.QPalette.ToolTipBase, qtc.Qt.black) palette.setColor(qtg.QPalette.ToolTipText, qtc.Qt.white) palette.setColor(qtg.QPalette.Text, qtc.Qt.white) palette.setColor(qtg.QPalette.Button, qtg.QColor(53, 53, 53)) palette.setColor(qtg.QPalette.ButtonText, qtc.Qt.white) palette.setColor(qtg.QPalette.BrightText, qtc.Qt.red) palette.setColor(qtg.QPalette.Link, qtg.QColor(42, 130, 218)) palette.setColor(qtg.QPalette.Highlight, qtg.QColor(42, 130, 218)) palette.setColor(qtg.QPalette.HighlightedText, qtc.Qt.black) app.setPalette(palette) mwindow = DXM_Bragi() mwindow.show() ##youtube test: https://www.youtube.com/watch?v=Lrj2Hq7xqQ8 #link = 'https://www.youtube.com/watch?v=Lrj2Hq7xqQ8' #yt_vid = YouTube(link) #filePath = os.path.join(os.path.dirname(os.path.abspath(__file__)),'TestYT') ##zeigt vorhandene stream items an (Verschiedene Versionen des links) #for video in yt_video.streams: # print(video) ##anzeigen Video daten #print(yt_vid.title) #print(yt_vid.thumbnail_url) #print(yt_vid.streams.filter(only_audio=True)) ##caption infos (Audio-Abschnitte[können auto generiert sein]) #print(yt_vid.captions) #caption = yt_vid.captions.get_by_language_code('a.en') #print(caption.xml_captions) #print(caption.generate_srt_captions()) # herunter laden von audio in bester qualy #yt_audioStream = yt_vid.streams.get_audio_only() #print(yt_audioStream.title) #yt_audioStream.download(filePath) app.exec_()