#!/usr/bin/env python # -*- coding: utf-8 -*- """ PyQvod_v0.5: a python wrapper for Qvod downloader + wine Author: yu239 Date: 06/06/2012 PyQvod.py The mainframe GUI for user interatcion """ import os import wx import re import thread import Queue import time import downloader import downloadviewer import listenURL # Generate a bunch of button ids _ID_BTN_ADD_ = wx.NewId() _ID_BTN_DELETE_ = wx.NewId() _ID_BTN_START_ = wx.NewId() _ID_BTN_EXIT_ = wx.NewId() _ID_BTN_PLAY_ = wx.NewId() _ID_BTN_CONFIG_ = wx.NewId() _ID_BTN_VIDEO_ = wx.NewId() _ID_BTN_LICENSE_ = wx.NewId() _ID_BTN_PAUSE_ = wx.NewId() _ID_BTN_DELETEALL_ = wx.NewId() _LICENSE_ = 'This software is under MIT license,\n' + \ 'essentially a "do whatever you want with this software,\n' + \ 'just don\'t blame me if something goes wrong" license.\n' + \ 'http://en.wikipedia.org/wiki/MIT_License' class FileNameDialog(wx.Dialog): def __init__(self, parent, title, string): self.width, self.height = (350, 120) wx.Dialog.__init__(self, parent=parent, title=title, size=(self.width, self.height)) self.string = string panel = wx.Panel(self) delta = 10 bn_width, bn_height = 80, 25 rt_label = wx.Rect(delta, delta, self.width-2*delta, 20) st_label = wx.StaticText(panel, pos=rt_label.GetPosition(), size=rt_label.GetSize(), label='Change filename to:') rt_filename = wx.Rect(delta, rt_label.GetBottom()+delta, rt_label.GetWidth(), 25) self.tc_filename = wx.TextCtrl(panel, pos=rt_filename.GetPosition(), size=rt_filename.GetSize()) self.tc_filename.WriteText(self.string) rt_ok = wx.Rect(delta, rt_filename.GetBottom()+2*delta, bn_width, bn_height) bn_ok = wx.Button(panel, pos=rt_ok.GetPosition(), size=rt_ok.GetSize(), id=wx.ID_OK) rt_cancel = wx.Rect(rt_ok.GetRight()+delta/2, rt_ok.GetTop(), bn_width, bn_height) bn_cancel = wx.Button(panel, pos=rt_cancel.GetPosition(), size=rt_cancel.GetSize(), id=wx.ID_CANCEL) bn_ok.Bind(wx.EVT_BUTTON, self.OnClose) bn_cancel.Bind(wx.EVT_BUTTON, self.OnClose) bn_ok.SetFocus() def OnClose(self, e): if e.GetId() == wx.ID_OK: tmp_str = self.tc_filename.GetValue() if tmp_str.strip() != '': self.string = tmp_str self.Destroy() class MainFrame(wx.Frame): def __init__(self, title): self.width, self.height = (600, 330) # fixed size, nonresizable wx.Frame.__init__(self, None, title=title, size=(self.width, self.height), style=wx.DEFAULT_FRAME_STYLE^wx.RESIZE_BORDER) self._InitUI() self._Init() self.Center() self.Show() # Initialize def _Init(self): # All the jobs information added into the list, each element a turple (thread_id, url, queue) # thread_id is the thread that manages the job. If the job hasn't started, it's -1 # url is the Qvod URL with respect to the job # queue is used to kill a downloader thread self.jobs_info = [] # Start a thread to listen the javascript qvodurlfinder self.url_queue = Queue.Queue(0) listenURL._URL_QUEUE_ = self.url_queue thread.start_new_thread(listenURL.listenURL, ()) # Set a timer for checking url queue self.url_timer = wx.Timer(self) self.Bind(wx.EVT_TIMER, self.OnCheckURLQueue, self.url_timer) self.url_timer.Start(100) # Check every 100 ms # Set a timer for updating job progress self.job_queue = Queue.Queue(0) downloader._JOB_QUEUE_ = self.job_queue self.job_timer = wx.Timer(self) self.Bind(wx.EVT_TIMER, self.OnCheckJobQueue, self.job_timer) self.job_timer.Start(100) # Read the configure file and do a check self.conf = downloader.read_config() if not self.conf: self._Alert('Configure file invalid!') # Initialize UI def _InitUI(self): # Put a panel ontop of mainframe first panel = wx.Panel(self) delta = 10 bn_width, bn_height = 80, 25 dv_height = self.height - 130 # Create a noneditable text control to display Qvod URL rt_url = wx.Rect(delta, delta, self.width-2*delta, 20) self.tc_url = wx.TextCtrl(panel, pos=rt_url.GetPosition(), size=rt_url.GetSize(), style=wx.BORDER_NONE|wx.TE_DONTWRAP) self.tc_url.WriteText('qvod://108941476|E756EFBB467FFD37A36225B180CF29F1BE8BEAB0|[www.qvod123.com]名侦探柯南_662.rmvb|') # Add a download manager rt_dv = wx.Rect(delta, rt_url.GetBottom()+2*delta, rt_url.GetWidth(), dv_height) self.lc_dv = downloadviewer.DownloadViewer(panel, pos=rt_dv.GetPosition(), size=rt_dv.GetSize()) # Add a 'add' button rt_add = wx.Rect(delta/2, rt_dv.GetBottom()+2*delta, bn_width, bn_height) self.bn_add = wx.Button(panel, pos=rt_add.GetPosition(), size=rt_add.GetSize(), id=_ID_BTN_ADD_, label='Add url') self.bn_add.Bind(wx.EVT_BUTTON, self.OnAddTask) rt_prev = rt_add # Add a 'start' button rt_start = wx.Rect(rt_prev.GetRight()+delta/2, rt_prev.GetTop(), bn_width-delta, bn_height) self.bn_start = wx.Button(panel, pos=rt_start.GetPosition(), size=rt_start.GetSize(), id=_ID_BTN_START_, label='Start') self.bn_start.Bind(wx.EVT_BUTTON, self.OnStart) rt_prev = rt_start # Add a 'pause' button rt_pause = wx.Rect(rt_prev.GetRight()+delta/2, rt_prev.GetTop(), bn_width-delta, bn_height) self.bn_pause = wx.Button(panel, pos=rt_pause.GetPosition(), size=rt_pause.GetSize(), id=_ID_BTN_PAUSE_, label='Pause') self.bn_pause.Bind(wx.EVT_BUTTON, self.OnPause) rt_prev = rt_pause # Add a 'delete' button rt_del = wx.Rect(rt_prev.GetRight()+delta/2, rt_prev.GetTop(), bn_width, bn_height) self.bn_del = wx.Button(panel, pos=rt_del.GetPosition(), size=rt_del.GetSize(), id=_ID_BTN_DELETE_, label='Delete') self.bn_del.Bind(wx.EVT_BUTTON, self.OnDeleteTasks) rt_prev = rt_del # Add a 'deleteall' button rt_delall = wx.Rect(rt_prev.GetRight()+delta/2, rt_prev.GetTop(), bn_width+delta, bn_height) self.bn_delall = wx.Button(panel, pos=rt_delall.GetPosition(), size=rt_delall.GetSize(), id=_ID_BTN_DELETEALL_, label='Delete All') self.bn_delall.Bind(wx.EVT_BUTTON, self.OnDeleteTasks) rt_prev = rt_delall # Add a 'video' button rt_video = wx.Rect(rt_prev.GetRight()+delta/2, rt_prev.GetTop(), bn_width, bn_height) self.bn_video = wx.Button(panel, pos=rt_video.GetPosition(), size=rt_video.GetSize(), id=_ID_BTN_VIDEO_, label='Videos') self.bn_video.Bind(wx.EVT_BUTTON, self.OnOpenVideos) rt_prev = rt_video # Add a 'configure' button rt_conf = wx.Rect(rt_prev.GetRight()+delta/2, rt_prev.GetTop(), bn_width+delta, bn_height) self.bn_conf = wx.Button(panel, pos=rt_conf.GetPosition(), size=rt_conf.GetSize(), id=_ID_BTN_CONFIG_, label='Congifure') self.bn_conf.Bind(wx.EVT_BUTTON, self.OnConfigure) rt_prev = rt_conf # Add a 'license' button rt_license = wx.Rect(rt_add.GetLeft(), rt_prev.GetBottom()+delta/2, bn_width, bn_height) self.bn_license = wx.Button(panel, pos=rt_license.GetPosition(), size=rt_license.GetSize(), id=_ID_BTN_LICENSE_, label='License') self.bn_license.Bind(wx.EVT_BUTTON, self.OnShowLicense) rt_prev = rt_license # Add a 'Play' button rt_play = wx.Rect(rt_video.GetLeft(), rt_prev.GetTop(), bn_width, bn_height) self.bn_play = wx.Button(panel, pos=rt_play.GetPosition(), size=rt_play.GetSize(), id=_ID_BTN_PLAY_, label='Play') self.bn_play.Bind(wx.EVT_BUTTON, self.OnPlay) rt_prev = rt_play # Add a 'exit' button rt_exit = wx.Rect(rt_video.GetRight()+delta/2, rt_prev.GetTop(), bn_width+delta, bn_height) self.bn_exit = wx.Button(panel, pos=rt_exit.GetPosition(), size=rt_exit.GetSize(), id=_ID_BTN_EXIT_, label='Exit') self.bn_exit.Bind(wx.EVT_BUTTON, self.OnExit) rt_prev = rt_exit # Add an 'info' static text rt_info = wx.Rect(rt_license.GetRight()+delta, rt_license.GetTop(), rt_exit.GetLeft()-rt_license.GetRight()-2*delta, bn_height+delta) self.st_info = wx.StaticText(panel, pos=rt_info.GetPosition(), size=rt_info.GetSize(), label='') self.st_info.SetFont(wx.Font(8, wx.DEFAULT, wx.DEFAULT, wx.DEFAULT)) def _Alert(self, msg): wx.MessageBox(msg, 'error', wx.OK | wx.ICON_ERROR) def _AddUrl(self, url): self.tc_url.Clear() self.tc_url.WriteText(url) def OnAddTask(self, e): url = self.tc_url.GetValue() trunks = downloader.valid_url(url) if not trunks: self._Alert('Invalid URL!') elif url in [t[1] for t in self.jobs_info]: self._Alert('You have already added an identical URL!') else: self.filename = trunks[2] dlg_filename = FileNameDialog(None, 'Video name', self.filename) dlg_filename.ShowModal() self.filename = dlg_filename.string dlg_filename.Destroy() self.jobs_info.append((-1, url, Queue.Queue(0))) # Create a new queue for this thread self.lc_dv._AddItem(self.filename) # Add a task with the video name def OnDeleteTasks(self, e): if e.GetId() == _ID_BTN_DELETEALL_: del_lst = self.lc_dv._DeleteAllItems() else: del_lst = self.lc_dv._DeleteItem() # Send a kill signal to worker thread for idx in del_lst: self.jobs_info[idx][2].put(0) self.jobs_info = [t for i, t in enumerate(self.jobs_info) if not i in del_lst] def OnConfigure(self, e): os.system('xdg-open ../config 1> /dev/null 2> /dev/null') def OnStart(self, e): lst = self.lc_dv._GetAllSelectedItems() for idx in lst: job_id, url, que = self.jobs_info[idx] if job_id != -1: continue # If this job has been started, skip it job_id = thread.start_new_thread(downloader.download, (url, que, self.filename, )) self.jobs_info[idx] = (job_id, url, que) def OnPause(self, e): lst = self.lc_dv._GetAllSelectedItems() for idx in lst: self.jobs_info[idx][2].put(-1) self.jobs_info[idx] = (-1, self.jobs_info[idx][1], Queue.Queue(0)) # A queue can be used for only once self.lc_dv._UpdateItemProgress(idx, -1, 'Paused') def OnPlay(self, e): lst = self.lc_dv._GetAllSelectedItems() for idx in lst: self.jobs_info[idx][2].put(-2) def OnExit(self, e): for job_id, url, que in self.jobs_info: que.put(-1) time.sleep(1.5) self.Close() def OnOpenVideos(self, e): ret = os.system('xdg-open ' + self.conf['VIDEO_PATH'] + ' 1> /dev/null 2> /dev/null') if ret != 0: self._Alert('Make sure you have configured a valid video path!') def OnCheckURLQueue(self, e): if not self.url_queue.empty(): url = self.url_queue.get() self._AddUrl(url) def OnCheckJobQueue(self, e): if not self.job_queue.empty(): msg = self.job_queue.get() code, string = msg.split('$') if int(code) == 0: if string.startswith('*'): self.st_info.SetLabel(string) else: self._Alert(string) else: res = re.search('([0-9]*.[0-9]*)%', string) if not res is None: progress = int(float(res.group(1))) else: progress = -1 # Detail doesn't contain progress, just set it to -1 job_ids = [t[0] for t in self.jobs_info] assert int(code) in job_ids idx = job_ids.index(int(code)) if res is None: # Enable this task again self.jobs_info[idx] = (-1, self.jobs_info[idx][1], Queue.Queue(0)) self.lc_dv._UpdateItemProgress(idx, progress, string) def OnShowLicense(self, e): wx.MessageBox(_LICENSE_, 'LICENSE', wx.OK | wx.ICON_INFORMATION) if __name__ == '__main__': # Create a new app, and start the main loop app = wx.App(False) MainFrame("PyQvod_v1.0\t\t\t Qvod for linux!") app.MainLoop()