本專案以wxPython寫成, 屬較舊的版本, 且也有些bug, 請改用新版PyQt5寫成的版本
MP3下載-PyQt5
方式
本程式先連線到youtube搜尋歌曲影片, 再把網址copy 到 youtube to mp4的網站, 進行轉碼後下載
程式執行的畫面如下
安裝套件
pip install pywin32 selenium wxPython
UI下載
代碼
import wx import wx.xrc from selenium import webdriver from selenium.webdriver.chrome.options import Options import threading, time import os import win32api import win32file class MainFrame(wx.Frame): def __init__(self, parent): wx.Frame.__init__(self, parent, id=wx.ID_ANY, title=u"MP3 Terminator", pos=wx.DefaultPosition, size=wx.Size(943, 518), style=wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL) self.SetSizeHints(wx.DefaultSize, wx.DefaultSize) self.SetBackgroundColour(wx.Colour(105, 179, 252)) bSizer1 = wx.BoxSizer(wx.VERTICAL) bSizer8 = wx.BoxSizer(wx.HORIZONTAL) self.m_panel3 = wx.Panel(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL) self.m_panel3.SetBackgroundColour(wx.Colour(255, 255, 128)) bSizer8.Add(self.m_panel3, 1, wx.EXPAND, 5) self.m_staticText3 = wx.StaticText(self, wx.ID_ANY, u"MP3 Terminator", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_CENTRE) self.m_staticText3.Wrap(-1) self.m_staticText3.SetFont(wx.Font(16, 70, 93, 90, False, wx.EmptyString)) self.m_staticText3.SetForegroundColour(wx.Colour(224, 57, 16)) self.m_staticText3.SetBackgroundColour(wx.Colour(255, 255, 128)) bSizer8.Add(self.m_staticText3, 1, wx.EXPAND, 5) self.m_staticText6 = wx.StaticText(self, wx.ID_ANY, u"Authorized : Thomas Wu(Ver 1.0.3)", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT) self.m_staticText6.Wrap(-1) self.m_staticText6.SetFont(wx.Font(10, 70, 93, 90, False, wx.EmptyString)) self.m_staticText6.SetForegroundColour(wx.Colour(0, 0, 255)) self.m_staticText6.SetBackgroundColour(wx.Colour(255, 255, 128)) bSizer8.Add(self.m_staticText6, 1, wx.EXPAND, 5) bSizer1.Add(bSizer8, 0, wx.EXPAND, 5) bSizer2 = wx.BoxSizer(wx.HORIZONTAL) self.m_staticText1 = wx.StaticText(self, wx.ID_ANY, u"歌名/歌手", wx.DefaultPosition, wx.DefaultSize, 0) self.m_staticText1.Wrap(-1) bSizer2.Add(self.m_staticText1, 0, wx.ALIGN_CENTER | wx.ALL, 5) self.txtSong = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.Size(170, -1), 0) bSizer2.Add(self.txtSong, 0, wx.ALIGN_CENTER | wx.ALL, 5) self.btnSearch = wx.Button(self, wx.ID_ANY, u"搜尋(&S)", wx.DefaultPosition, wx.DefaultSize, 0) bSizer2.Add(self.btnSearch, 0, wx.ALIGN_CENTER | wx.ALL, 5) bSizer1.Add(bSizer2, 0, wx.ALIGN_CENTER, 5) listBoxChoices = [] self.listBox = wx.CheckListBox(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, listBoxChoices, 0) self.listBox.SetForegroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DLIGHT)) self.listBox.SetBackgroundColour(wx.Colour(210, 210, 255)) bSizer1.Add(self.listBox, 1, wx.EXPAND, 5) bSizer5 = wx.BoxSizer(wx.HORIZONTAL) self.m_panel1 = wx.Panel(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL) bSizer5.Add(self.m_panel1, 1, wx.EXPAND | wx.ALL, 5) bSizer7 = wx.BoxSizer(wx.VERTICAL) self.btnDownload = wx.Button(self, wx.ID_ANY, u"下載(&D)", wx.DefaultPosition, wx.Size(150, -1), 0) bSizer7.Add(self.btnDownload, 1, wx.ALIGN_CENTER | wx.ALL, 5) bSizer5.Add(bSizer7, 1, wx.EXPAND, 5) bSizer6 = wx.BoxSizer(wx.HORIZONTAL) self.m_panel2 = wx.Panel(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL) bSizer6.Add(self.m_panel2, 1, wx.EXPAND | wx.ALL, 5) self.lblPath = wx.StaticText(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.Size(200, -1), wx.ALIGN_RIGHT) self.lblPath.Wrap(-1) bSizer6.Add(self.lblPath, 0, wx.ALIGN_CENTER, 5) self.btnPath = wx.Button(self, wx.ID_ANY, u"儲存目錄(&P)", wx.DefaultPosition, wx.Size(90, -1), 0) bSizer6.Add(self.btnPath, 0, wx.ALL | wx.RIGHT, 5) bSizer5.Add(bSizer6, 1, 0, 5) bSizer1.Add(bSizer5, 0, wx.EXPAND, 5) sizer = wx.BoxSizer(wx.VERTICAL) self.lblStatus = wx.StaticText(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.Size(400, -1), wx.ALIGN_CENTRE) self.lblStatus.Wrap(-1) self.lblStatus.SetFont(wx.Font(14, 70, 93, 90, False, wx.EmptyString)) self.lblStatus.SetForegroundColour(wx.Colour(0, 0, 255)) self.lblStatus.SetBackgroundColour(wx.Colour(255, 128, 255)) sizer.Add(self.lblStatus, 0, wx.EXPAND, 5) bSizer1.Add(sizer, 0, wx.EXPAND, 5) self.SetSizer(bSizer1) self.Layout() self.Centre(wx.BOTH) self.Init() def __del__(self): pass def Init(self): # 綁定按鈕事件 self.Bind(wx.EVT_BUTTON, self.BtnSearchClick, self.btnSearch) self.Bind(wx.EVT_BUTTON, self.BtnDownloadClick, self.btnDownload) self.Bind(wx.EVT_BUTTON, self.BtnPathClick, self.btnPath) self.Bind(wx.EVT_CLOSE, self.OnClose) # 設定快速鍵 entries = [wx.AcceleratorEntry() for i in range(3)] entries[0].Set(wx.ACCEL_ALT, ord('S'), self.btnSearch.GetId()) entries[1].Set(wx.ACCEL_ALT, ord('D'), self.btnDownload.GetId()) entries[2].Set(wx.ACCEL_ALT, ord('P'), self.btnPath.GetId()) table = wx.AcceleratorTable(entries) self.SetAcceleratorTable(table) # 選取硬碟 disk = [] for i in win32api.GetLogicalDriveStrings().split('\000'): if win32file.GetDriveType(i) == 3: disk.append(i[:-2]) self.lblPath.SetLabelText('{0}:\mp3_tmp'.format(disk[len(disk) - 1])) if not os.path.isdir(self.lblPath.GetLabelText()): os.mkdir(self.lblPath.GetLabelText()) # 載入Chromedriver options = Options() options.add_argument('--headless') options.add_argument('--disable-gpu') options.add_experimental_option('excludeSwitches', ['enable-logging']) self.chromePath = "{0}\chromedriver.exe".format(os.getcwd()) self.web = webdriver.Chrome(executable_path=self.chromePath, options=options) # 設定下載目錄 def BtnPathClick(self, event): dlg = wx.DirDialog(self, "Choose a directory:", style=wx.DD_DEFAULT_STYLE) if dlg.ShowModal() == wx.ID_OK: self.lblPath.SetLabelText(dlg.GetPath()) dlg.Destroy() def BtnSearchClick(self, event): self.listBox.Clear() self.song = self.txtSong.GetValue() if self.song == '': wx.MessageBox(u'請輸入歌名') return self.lblStatus.SetLabelText('Searching....') self.btnDownload.Enable(False) self.btnSearch.Enable(False) self.btnPath.Enable(False) self.Layout() t = threading.Thread(target=self.SearchMp3) t.start() def BtnDownloadClick(self, event): self.path = self.lblPath.GetLabelText() if self.path == '': wx.MessageBox(u'請選擇路徑') return self.btnDownload.Enable(False) self.btnSearch.Enable(False) self.btnPath.Enable(False) t = threading.Thread(target=self.DownloadMp3, args=(self.listBox.GetCheckedStrings(),)) t.start() def SearchMp3(self): url = "https://www.youtube.com/results?search_query={0}".format(self.song) self.web.get(url) tags = self.web.find_elements_by_tag_name('a') links = {} for tag in tags: href = tag.get_attribute('href') if 'watch' in str(href): title = tag.get_attribute('title') if title == '': try: title = tag.find_element_by_id('video-title').get_attribute('title') except: pass if title != '': links[href] = '{0} url={1}'.format(title, href) if len(links) == 0: return wx.CallAfter(self.AddList, links) def AddList(self, links): for key in links.keys(): self.listBox.Append(links[key]) self.lblStatus.SetLabelText('') self.btnSearch.Enable(True) self.btnDownload.Enable(True) self.btnPath.Enable(True) self.Layout() def DownloadMp3(self, items): # 開啟chromedriver允許下載設定 self.web.command_executor._commands["send_command"] = ("POST", '/session/$sessionId/chromium/send_command') params = {'cmd': 'Page.setDownloadBehavior', 'params': {'behavior': 'allow', 'downloadPath': self.path}} for itme in items: title = itme.split('url=')[0] wx.CallAfter(self.DrawUI, u'正在下載 : {0}'.format(title)) self.web.get('https://www.youtubeto.com/zh/') txt = self.web.find_element_by_id('url') txt.send_keys(itme.split('url=')[1]) btn = self.web.find_element_by_id('DownloadMP3_text') btn.click() command_result = self.web.execute("send_command", params) time.sleep(3) while self.web.page_source.find('allow-same-origin') == -1: time.sleep(0.1) wx.CallAfter(self.DrawUI, u"下載完成") wx.CallAfter(self.DownloadFinished) def DrawUI(self, s): self.lblStatus.SetLabel(s) self.Layout() def DownloadFinished(self): # 取消 CheckListBox選取 for i in range(self.listBox.GetCount()): self.listBox.Check(i, check=False) self.btnDownload.Enable(True) self.btnSearch.Enable(True) self.btnPath.Enable(True) self.Layout() def OnClose(self, event): self.web.quit() app.ExitMainLoop() if __name__ == '__main__': app = wx.App() frame = MainFrame(None) frame.Show() app.MainLoop()
新版
待解決 — 儲存位置, 需按一下youtubeto網站需再按一下 mp3
def Init(self): options = Options() #options.add_argument('--headless') options.add_argument('--disable-gpu') options.add_experimental_option('excludeSwitches', ['enable-logging']) self.browser = webdriver.Chrome('chromedriver.exe', options=options) self.lblPath.SetLabelText("c:/tmp_mp3") self.btnSearch.Bind(wx.EVT_BUTTON, self.btnSearch_Click) self.btnDownload.Bind(wx.EVT_BUTTON, self.btnDownload_Click) def btnSearch_Click(self, event): self.listBox.Clear() self.song=self.txtSong.GetValue() if self.song=='': wx.MessageBox('歌曲名稱不可為空白') return self.btnSearch.Enable(False) self.btnDownload.Enable(False) self.btnPath.Enable(False) self.lblStatus.SetLabelText("搜詢中.....") self.Layout() t=threading.Thread(target=self.Search) t.start() def Search(self): url = "https://www.youtube.com/results?search_query={0}".format(self.song) self.browser.get(url) tags = self.browser.find_elements_by_tag_name('a') links = {} for tag in tags: href = tag.get_attribute('href') if 'watch' in str(href): title=tag.get_attribute('title') if title=='': try: title=tag.find_element_by_tag_id('video-title').get_attribute('title') except: pass if title!='': links[href]='{0} url={1}'.format(title, href) if len(links) == 0: return wx.CallAfter(self.AddList, links) def AddList(self, links): for key in links.keys(): self.listBox.Append(links[key]) self.lblStatus.SetLabelText('') self.btnSearch.Enable(True) self.btnDownload.Enable(True) self.btnPath.Enable(True) self.Layout() def btnDownload_Click(self, event): self.path = self.lblPath.GetLabelText() if self.path == '': wx.MessageBox(u'請選擇路徑') return self.btnDownload.Enable(False) self.btnSearch.Enable(False) self.btnPath.Enable(False) t = threading.Thread(target=self.DownloadMp3, args=(self.listBox.GetCheckedStrings(),)) t.start() def DownloadMp3(self, items): #允許Chrome 可以下載檔案 self.browser.command_executor._commands["send_command"] = ("POST", '/session/$sessionId/chromium/send_command') params = {'cmd': 'Page.setDownloadBehavior', 'params': {'behavior': 'allow', 'downloadPath': self.path}} for item in items: title=item.split(' url=')[0] url=item.split(' url=')[1].replace('youtube', 'youtubeto') wx.CallAfter(self.DrawUi, title) self.browser.get(url) wx.CallAfter(self.DownloadFinished) def DrawUi(self, msg): self.lblStatus.SetLabelText('{0}下載中...'.format(msg)) def DownloadFinished(self): self.lblStatus.SetLabelText('下載完成') self.btnSearch.Enable(True) self.btnDownload.Enable(True) self.btnPath.Enable(True) app=wx.App() frame=MainFrame(None) frame.Show() app.MainLoop()