From e2cf518d26121f76498c0272ee81dbc4a2d1a051 Mon Sep 17 00:00:00 2001 From: ZSAIm Date: Sun, 28 Apr 2019 16:40:16 +0800 Subject: [PATCH] update --- CommonVar.py | 14 +- GUIEventBinder.py | 212 ++++++++++++++------ README.md | 84 ++++---- core/bilibili.py | 112 +++++++---- core/common.py | 30 ++- core/iqiyi.py | 106 +++++++--- flow.py | 214 ++++++++++++++++----- gui/__init__.py | 30 +-- gui/about.py | 142 +++++++------- gui/{frame_main.py => frame_downloader.py} | 28 +-- gui/frame_merger.py | 144 ++++++++++++++ gui/{frame_parse.py => frame_parser.py} | 116 ++++++++--- gui/listctrl.py | 33 +++- gui/merger_output.py | 49 ----- gui/settings.py | 148 ++++++++++++++ gui/tool_request.py | 12 +- handler/downloader.py | 71 +++++-- handler/merger.py | 177 +++++++++++------ handler/settings.py | 6 +- main.py | 4 +- nbdler/DLHandler.py | 2 +- nbdler/DLInfos.py | 22 ++- nbdler/DLProcessor.py | 49 +++-- nbdler/DLProgress.py | 13 +- nbdler/README.MD | 3 + 25 files changed, 1294 insertions(+), 527 deletions(-) rename gui/{frame_main.py => frame_downloader.py} (93%) create mode 100644 gui/frame_merger.py rename gui/{frame_parse.py => frame_parser.py} (57%) delete mode 100644 gui/merger_output.py create mode 100644 gui/settings.py diff --git a/CommonVar.py b/CommonVar.py index 91748d8..0fbae31 100644 --- a/CommonVar.py +++ b/CommonVar.py @@ -2,7 +2,7 @@ # status SHUTDOWN = False - +ALLTASKDONE = False # parse @@ -11,10 +11,11 @@ # download MAX_CONN = 5 -MAX_TASK = 5 +MAX_TASK = 3 +BUFFER_SIZE = 20 FILEPATH = '' -FFMPEG_PATH = '' +BLOCK_SIZE = 512 UNDONE_JOB = '' @@ -23,6 +24,13 @@ MERGER_SIMPLE = 'simple' MERGER_FFMPEG = 'ffmpeg' + +FFMPEG_PATH = '' + + + + + import wx # wxID diff --git a/GUIEventBinder.py b/GUIEventBinder.py index 5a7c4bc..981b8e6 100644 --- a/GUIEventBinder.py +++ b/GUIEventBinder.py @@ -7,101 +7,191 @@ import flow def init(): - FrameMain_Menu_File_Handler.bindEvent() - FrameParse_Button_Handler.bindEvent() - FrameMain_Menu_Help_Handler.bindEvent() - FrameMain_Close_Handler.bindEvent() - # Message_Dialog_Handler.bindEvent() + FrameMain.bindEvent() + FrameParser.bindEvent() + FrameMerger.bindEvent() -def bindMenuItems(handler_parent, source_parent, items_name): - for i in items_name: - gui.frame_main.Bind(wx.EVT_MENU, getattr(handler_parent, i), getattr(source_parent, i)) - -class FrameMain_Menu_File_Handler: +class FrameMain: @staticmethod def bindEvent(): - items = ('parse', 'settings', 'exit') - bindMenuItems(FrameMain_Menu_File_Handler, gui.frame_main.menu_bar.file, items) - + gui.frame_downloader.Bind(wx.EVT_CLOSE, FrameMain.win_close) + FrameMain.MenuBar.bindEvent() @staticmethod - def parse(event): - pass + def win_close(event): + flow.ShutDown.frame_downloader_close(event) - @staticmethod - def settings(event): - pass + class MenuBar: + @staticmethod + def bindEvent(): + FrameMain.MenuBar.File.bineEvent() + FrameMain.MenuBar.Help.bindEvent() + class File: + @staticmethod + def bineEvent(): + items = ('parse', 'settings', 'exit') + FrameMain.MenuBar.batchBind(FrameMain.MenuBar.File, gui.frame_downloader.menu_bar.file, items) + + @staticmethod + def parse(event): + pass + + @staticmethod + def settings(event): + dlg = gui.DialogSettings(gui.frame_parse) + dlg.ShowModal() + + @staticmethod + def exit(event): + pass + + + class Help: + @staticmethod + def bindEvent(): + items = ('about',) + FrameMain.MenuBar.batchBind(FrameMain.MenuBar.Help, gui.frame_downloader.menu_bar.help, items) + + @staticmethod + def about(event): + dlg = gui.DialogAbout(gui.frame_downloader) + dlg.ShowModal() + dlg.Destroy() + + @staticmethod + def batchBind(handler_parent, source_parent, items_name): + for i in items_name: + gui.frame_downloader.Bind(wx.EVT_MENU, getattr(handler_parent, i), getattr(source_parent, i)) - @staticmethod - def exit(event): - pass -class FrameMain_Menu_Help_Handler: +class FrameParser: @staticmethod def bindEvent(): - items = ('about', ) - bindMenuItems(FrameMain_Menu_Help_Handler, gui.frame_main.menu_bar.help, items) - + gui.frame_parse.Bind(wx.EVT_CLOSE, FrameParser.win_close) + FrameParser.TextCtrl.bindEvent() + FrameParser.Button.bindEvent() + FrameParser.MemuBar.bindEvent() @staticmethod - def about(event): - dlg = gui.About_Dialog(gui.frame_main) - dlg.ShowModal() - dlg.Destroy() + def win_close(event): + flow.ShutDown.frame_parser_close(event) -def bindButtonEvent(handler_parent, source_parent, items_name): - for i in items_name: - gui.frame_parse.Bind(wx.EVT_BUTTON, getattr(handler_parent, i), getattr(source_parent, 'button_' + i)) + class TextCtrl: + @staticmethod + def bindEvent(): + items = ('godownload', 'copylinks') + FrameParser.MemuBar.batchBind(FrameParser.TextCtrl, gui.frame_parse.listctrl_parse.menu, items) + @staticmethod + def godownload(event): + flow.FrameParser.MenuGoDownload.handle() + @staticmethod + def copylinks(event): + flow.FrameParser.MenuCopyLinks.handle() -class FrameParse_Button_Handler: - @staticmethod - def bindEvent(): - items = ('parse', 'path', 'godownload', 'copyurl') - bindButtonEvent(FrameParse_Button_Handler, gui.frame_parse, items) + class MemuBar: + @staticmethod + def bindEvent(): + FrameParser.MemuBar.File.bindEvent() + FrameParser.MemuBar.Help.bindEvent() + class File: + @staticmethod + def bindEvent(): + items = ('settings',) + FrameParser.MemuBar.batchBind(FrameParser.MemuBar.File, gui.frame_parse.menu_bar.file, items) - @staticmethod - def parse(event): - flow.FrameParser.ButtonParse.handle() + @staticmethod + def settings(event): + dlg = gui.DialogSettings(gui.frame_parse) + dlg.ShowModal() - @staticmethod - def path(event): - flow.FrameParser.ButtonPath.handle() + class Help: + @staticmethod + def bindEvent(): + items = ('about',) + FrameParser.MemuBar.batchBind(FrameParser.MemuBar.Help, gui.frame_parse.menu_bar.help, items) - @staticmethod - def godownload(event): - flow.FrameParser.ButtonGoDownload.handle() + @staticmethod + def about(event): + dlg = gui.DialogAbout(gui.frame_downloader) + dlg.ShowModal() + dlg.Destroy() + @staticmethod + def batchBind(handler_parent, source_parent, items_name): + for i in items_name: + gui.frame_parse.Bind(wx.EVT_MENU, getattr(handler_parent, i), getattr(source_parent, i)) - @staticmethod - def copyurl(event): - flow.FrameParser.ButtonCopy.handle() + class Button: + @staticmethod + def bindEvent(): + items = ('parse',) + FrameParser.Button.batchBind(FrameParser.Button, gui.frame_parse, items) + + @staticmethod + def parse(event): + flow.FrameParser.ButtonParse.handle() + @staticmethod + def batchBind(handler_parent, source_parent, items_name): + for i in items_name: + gui.frame_parse.Bind(wx.EVT_BUTTON, getattr(handler_parent, i), getattr(source_parent, 'button_' + i)) -class FrameMain_Close_Handler: +class FrameMerger: @staticmethod def bindEvent(): - gui.frame_main.Bind(wx.EVT_CLOSE, FrameMain_Close_Handler.close) + gui.frame_merger.Bind(wx.EVT_CLOSE, FrameMerger.win_close) + FrameMerger.MenuBar.bindEvent() @staticmethod - def close(event): - def _(): - cv.SHUTDOWN = True - handler.merger.shutdown() - handler.downloader.shutdown() - - gui.frame_main.Hide() - threading.Thread(target=_).start() - event.Skip() - + def win_close(event): + flow.ShutDown.frame_merger_close(event) + + class MenuBar: + @staticmethod + def bindEvent(): + FrameMerger.MenuBar.File.bineEvent() + FrameMerger.MenuBar.Help.bindEvent() + + class File: + @staticmethod + def bineEvent(): + items = ('exit',) + FrameMerger.MenuBar.batchBind(FrameMerger.MenuBar.File, gui.frame_merger.menu_bar.file, items) + + @staticmethod + def settings(event): + pass + + @staticmethod + def exit(event): + flow.ShutDown.handle() + + + class Help: + @staticmethod + def bindEvent(): + items = ('about',) + FrameMerger.MenuBar.batchBind(FrameMerger.MenuBar.Help, gui.frame_merger.menu_bar.help, items) + + @staticmethod + def about(event): + dlg = gui.DialogAbout(gui.frame_downloader) + dlg.ShowModal() + dlg.Destroy() + + @staticmethod + def batchBind(handler_parent, source_parent, items_name): + for i in items_name: + gui.frame_merger.Bind(wx.EVT_MENU, getattr(handler_parent, i), getattr(source_parent, i)) diff --git a/README.md b/README.md index fda8582..7029d6b 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,16 @@ ## 更新说明 +* **2019/04/28** + * 支持断点续传下载。 + * 添加下载器的多种参数调节(可通过设置菜单进行修改)。 + * 更换gui部分布局。(设置选项移到了menubar的file菜单,下载选中项移到了listctrl的右键菜单) + * 爱奇艺添加解析f4v资源。 + * 修复爱奇艺f4v多线程下载进度卡住问题。 + * 修复bilibili一部分视频无法解析问题。(至于大会员视频能不能下载还不清楚) + * 使用ffmpeg来对视频进行合并的操作。 + * 加入merger框来显示ffmpeg合并进度和输出信息。 + * **2019/04/25** * 重构项目。 * 添加支持下载Bilibili视频。 @@ -15,6 +25,43 @@ * **2019/04/13** * 修复下载M3U8视频多线程数据重复错误问题(由于M3U8多线程下载字段不一致问题)。 + + +## 使用说明 + +* 打开程序 main.exe,然后自己摸索。。。 +* 目录下的"cookies/*.txt"可以导入Cookie。(爱奇艺导入iqiyi.txt,哔哩哔哩导入bilibili.txt) + + +## TO-DO + +* [x] 加入简略GUI交互。 +* [x] 支持导入Cookie。 +* [x] 解耦解析器。 +* [x] 更换JS执行引擎。 +* [x] 修复下载器Bug。 +* [x] 优化下载器程序。 +* [x] 优化执行流程。 + + + +## NOT-TO-DO + +* [x] 批量视频解析下载。 + + +## 引用项目 + +* __``Nbdler``__: https://github.com/ZSAIm/Nbdler +* __``PyJSCaller``__: https://github.com/ZSAIm/PyJSCaller + +*** + +## 项目地址 + github: https://github.com/ZSAIm/iqiyi-parser + +## 历史更新 + * **2019/04/12** * 添加未完成任务提醒功能。 * 添加设置配置文件(保存上次下载目录、同时最大下载任务等)。 @@ -51,40 +98,3 @@ * **2018/09/07** * 修复若干bug。 - - -## 使用说明 - -* 打开程序 main.exe,然后自己摸索。。。 -* 目录下的"cookies/*.txt"可以导入Cookie。(爱奇艺导入iqiyi.txt,哔哩哔哩导入bilibili.txt) - - -## TO-DO - -* [x] 加入简略GUI交互。 -* [x] 支持导入Cookie。 -* [x] 解耦解析器。 -* [x] 更换JS执行引擎。 -* [x] 修复下载器Bug。 -* [x] 优化下载器程序。 -* [x] 优化执行流程。 - - -## TO-DO or NOT-TO-DO - -* [ ] 美化GUI界面。 - -## NOT-TO-DO - -* [x] 批量视频解析下载。 - - -## 引用项目 - -* __``Nbdler``__: https://github.com/ZSAIm/Nbdler -* __``PyJSCaller``__: https://github.com/ZSAIm/PyJSCaller - -*** - -## 项目地址 - github: https://github.com/ZSAIm/iqiyi-parser diff --git a/core/bilibili.py b/core/bilibili.py index 8cd6a98..71d596a 100644 --- a/core/bilibili.py +++ b/core/bilibili.py @@ -11,7 +11,7 @@ LASTRES = None HEADERS = { - 'Host': 'www.bilibili.com', + # 'Host': 'www.bilibili.com', 'Connection': 'keep-alive', 'Upgrade-Insecure-Requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36', @@ -33,7 +33,7 @@ API = { 'durl': 'https://api.bilibili.com/x/player/playurl?', - 'dash': '' + 'dash': 'https://api.bilibili.com/pgc/player/web/playurl?', } durl_params = { @@ -43,11 +43,23 @@ 'type': '', 'otype': 'json', 'fnver': '0', - 'fnval': '', + 'fnval': '0', 'session': '' } +dash_params = { + 'avid': '', + 'cid': '', + 'qn': '', # quality + 'type': '', + 'otype': 'json', + 'fnver': '0', + 'fnval': '16', + 'session': '' +} + + """ r.fetchPlayurl({ avid: n.aid, @@ -60,6 +72,9 @@ }) """ + + + class Bilibili(BasicParser): def __init__(self): BasicParser.__init__(self) @@ -87,23 +102,35 @@ def parse(self, url, qualitys): if tmp: inital_state = tmp.group(1) - playinfo_json = json.loads(playinfo) + playinfo_json = json.loads(playinfo) if playinfo else {} inital_state_json = json.loads(inital_state) video_res = [] - for i in qualitys: - full_json = self.durl_parse(playinfo_json, inital_state_json, i) - video_info = BasicVideoInfo(url, title, full_json['data']['quality']) - video_res.append(BilibiliRespond(self, full_json, full_json['data'], video_info, True)) + add_params = self.get_info_dict(playinfo_json, inital_state_json, QUALITY[6]) + + if playinfo_json: + dash_full_json = playinfo_json + if not dash_full_json['data'].get('dash'): + add_params['qn'] = str(QUALITY[6]) + dash_full_json = self.api_parse(dash_params, add_params) + if dash_full_json['data'].get('dash'): + audios_info = self.get_audios_info(url, dash_full_json) + for i in dash_full_json['data']['dash']['video']: + extra_info = BasicVideoInfo(url, title, i['id']) + video_res.append(BilibiliRespond(self, dash_full_json, i, extra_info, False, audios_info)) + elif dash_full_json['data'].get('durl'): + if QUALITY[6] in qualitys: + extra_info = BasicVideoInfo(url, title, dash_full_json['data']['quality']) + video_res.append(BilibiliRespond(self, dash_full_json, dash_full_json['data'], extra_info, True)) + qualitys.remove(QUALITY[6]) - if playinfo_json['data'].get('dash'): - - audios_info = self.get_audios_info(url, playinfo_json) - - for i in playinfo_json['data']['dash']['video']: - video_info = BasicVideoInfo(url, title, i['id']) - video_res.append(BilibiliRespond(self, playinfo_json, i, video_info, False, audios_info)) + for i in qualitys: + add_params['qn'] = str(i) + full_json = self.api_parse(durl_params, add_params) + if full_json['data']: + extra_info = BasicVideoInfo(url, title, full_json['data']['quality']) + video_res.append(BilibiliRespond(self, full_json, full_json['data'], extra_info, True)) return video_res @@ -126,14 +153,11 @@ def get_audios_info(self, url, full_json): info = 'bandwidth: %10s size: %10s' % (i['bandwidth'], format_byte(size)) - audios_info.append(BasicAudioInfo(urls, size, info)) return audios_info - - def durl_parse(self, playinfo_json, inital_state_json, quality): - """from api: https://api.bilibili.com/x/player/playurl?""" + def get_info_dict(self, playinfo_json, inital_state_json, quality): if inital_state_json.get('videoData'): aid = inital_state_json['videoData']['aid'] cid = inital_state_json['videoData']['cid'] @@ -143,19 +167,23 @@ def durl_parse(self, playinfo_json, inital_state_json, quality): else: raise ValueError('unknown type') - session = playinfo_json['session'] - - req_params = durl_params.copy() + session = playinfo_json.get('session', '') - new_params = { + _params = { 'avid': str(aid), 'cid': str(cid), 'qn': str(quality), # quality 'session': str(session) } - req_params.update(new_params) + return _params + + def api_parse(self, base_params, add_params): + """from api: https://api.bilibili.com/x/player/playurl? + """ + req_params = base_params.copy() + req_params.update(add_params) playurl = make_query(API['durl'], req_params) text = self.request(playurl) @@ -164,11 +192,9 @@ def durl_parse(self, playinfo_json, inital_state_json, quality): - - class BilibiliRespond(BasicRespond): - def __init__(self, parent, full_json, res_json, video_info, isdurl, audio_info=None): - BasicRespond.__init__(self, parent, full_json, res_json, video_info) + def __init__(self, parent, full_json, res_json, extra_info, isdurl, audio_info=None): + BasicRespond.__init__(self, parent, full_json, res_json, extra_info) self._videosize = -1 self._audiosize = -1 # selected one @@ -179,8 +205,11 @@ def __init__(self, parent, full_json, res_json, video_info, isdurl, audio_info=N self._audios_info = audio_info + self._video_len = -1 + self.__extract__() + def __extract__(self): if self.isdurl: # target video urls @@ -196,6 +225,8 @@ def __extract__(self): size += i['size'] self._videosize = size + + else: # target video urls if self.res_json['backupUrl']: @@ -206,6 +237,8 @@ def __extract__(self): # video size self._videosize = self._get_file_size_from_url_(self._target_video_urls[0][0]) + self._video_len = self.full_json['data']['timelength'] + def getVideoTotal(self): return len(self._target_video_urls) @@ -224,7 +257,10 @@ def getAudioSize(self): def getFileFormat(self): if self.isdurl: - return 'flv' + if 'flv' in self.res_json['format']: + return 'flv' + else: + return self.res_json['format'].replace('/', '').replace('\\', '') if CONTENT_TYPE_MAP.get(self.res_json['mimeType'].strip()): return CONTENT_TYPE_MAP.get(self.res_json['mimeType'].strip()) else: @@ -249,7 +285,7 @@ def getReqHeaders(self): 'Accept-Encoding': 'gzip, deflate, br', 'Accept-Language': 'en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7', 'Range': 'bytes=0-0', - 'Referer': self.video_info.url, + 'Referer': self.getBaseUrl(), } def setSelAudio(self, index): @@ -278,6 +314,7 @@ def matchFeature(self, feature): return feature['quality'] == self.getQuality() and feature['screensize'] == self.getScreenSize() + def _get_file_size_from_url_(self, url): res = self.parent.requestRaw(url=url, headers=self.getReqHeaders()) return int(res.info().get('Content-Range').split('/')[-1]) @@ -289,13 +326,15 @@ def init(): BILIBILI = Bilibili() load_cookie() -def parse(url, qualitys): + +def parse(base_url, qualitys): global BILIBILI, QUALITY - return BILIBILI.parse(url, [QUALITY[i] for i in qualitys]) + return BILIBILI.parse(base_url, [QUALITY[i] for i in qualitys]) -def matchParse(url, quality, features): + +def matchParse(base_url, quality, features): global BILIBILI - res = BILIBILI.parse(url, [quality]) + res = BILIBILI.parse(base_url, [quality]) for i in res: if i.matchFeature(features): return i @@ -320,8 +359,7 @@ def load_cookie(): if __name__ == '__main__': init() - - res = parse('https://www.bilibili.com/video/av49951276', [1, 2, 3, 4, 5, 6]) - + load_cookie() + print(1) pass diff --git a/core/common.py b/core/common.py index 048efc9..f248c3e 100644 --- a/core/common.py +++ b/core/common.py @@ -5,7 +5,7 @@ import socket from urllib.error import URLError from ssl import SSLError -import time +import time, re import CommonVar as cv def raw_decompress(data, headers_msg): @@ -127,15 +127,17 @@ def parse(self, *args): class BasicRespond: - def __init__(self, parent, full_json, res_json, video_info): + def __init__(self, parent, full_json, res_json, extra_info): self.parent = parent self.full_json = full_json self.res_json = res_json - self.video_info = video_info + self.extra_info = extra_info self._videosize = -1 self._audiosize = -1 + self._video_len = -1 + self._target_video_urls = [] self._target_audio_urls = [] @@ -161,16 +163,16 @@ def getFileFormat(self): return 'Unknown' def getVideoTitle(self): - return self.video_info.title + return self.extra_info.title def getRangeFormat(self): return 'Range: bytes=%d-%d' def getBaseUrl(self): - return self.video_info.url + return self.extra_info.base_url def getQuality(self): - return self.video_info.quality + return self.extra_info.quality def getScreenSize(self): return None @@ -197,12 +199,18 @@ def getReqHeaders(self): } def getConcatMerger(self): - return cv.MERGER_SIMPLE + return cv.MERGER_FFMPEG def getAllAudioInfo(self): return [] + def getVideoTimeLength(self): + """ ms """ + return self._video_len + + + def getFeatures(self): return { @@ -218,9 +226,13 @@ def __str__(self): return '[%s]-(%s)-(%s)' % (self.getQuality(), self.getScreenSize(), format_byte(self.getTotalFileSize())) + def getVideoLegalTitle(self): + return re.sub('[\\/:\?<>\|\*"]', ' ', self.getVideoTitle()) + + class BasicVideoInfo: - def __init__(self, url, title, quality, **extra_info): - self.url = url + def __init__(self, base_url, title, quality, **extra_info): + self.base_url = base_url self.title = title self.quality = quality self.extra_info = extra_info diff --git a/core/iqiyi.py b/core/iqiyi.py index eaba302..176510a 100644 --- a/core/iqiyi.py +++ b/core/iqiyi.py @@ -3,7 +3,7 @@ import time, random import re import json, os - +import CommonVar as cv from bs4 import BeautifulSoup from urllib.parse import unquote @@ -83,7 +83,7 @@ # new params 2019/04/05 # 'k_ft1': '', - 'k_ft4': '4' # k_ft = 4: prefer *.ts ,else *.f4v + # 'k_ft4': '4' # k_ft = 4: prefer *.ts ,else *.f4v } @@ -114,17 +114,24 @@ def parse(self, url, bids=[600]): videos_res = [] for bid in bids: - target_url = self.makeTargetUrl(tvid, vid, bid) + target_ts_url, target_f4v_url = self.makeTargetUrl(tvid, vid, bid) + + res_json_ts = self.getTargetJson(target_ts_url) - res_json = self.getTargetJson(target_url) + if res_json_ts: + extra_info_ts = BasicVideoInfo(url, BeautifulSoup(text, features='html.parser').title.string, + res_json_ts['data']['ctl']['bid']) + videos_res.append(IqiyiRespond(self, res_json_ts, res_json_ts, extra_info_ts)) - if not res_json: - continue + if not videos_res[-1].getM3U8(): + continue - video_info = BasicVideoInfo(url, BeautifulSoup(text, features='html.parser').title.string, - res_json['data']['ctl']['bid']) + res_json_f4v = self.getTargetJson(target_f4v_url) + if res_json_ts: + extra_info_f4v = BasicVideoInfo(url, BeautifulSoup(text, features='html.parser').title.string, + res_json_f4v['data']['ctl']['bid']) - videos_res.append(IqiyiRespond(self, res_json, res_json, video_info)) + videos_res.append(IqiyiRespond(self, res_json_f4v, res_json_f4v, extra_info_f4v)) return videos_res @@ -151,23 +158,40 @@ def makeTargetUrl(self, tvid, vid, bid): 'authKey': authkey(authkey('') + time_str + tvid), } - new_params = global_params.copy() - new_params.update(params) + ts_params = global_params.copy() + ts_params['k_ft4'] = '4' + ts_params.update(params) + + ts_req_path = self.sess_make_req_path(sess, ts_params) + + f4v_params = global_params.copy() + f4v_params.update(params) + + f4v_req_path = self.sess_make_req_path(sess, f4v_params) + + sess.call(ts_req_path) + sess.call(f4v_req_path) + + return ['http://cache.video.iqiyi.com/' + ts_req_path.getValue().lstrip('/'), + 'http://cache.video.iqiyi.com/' + f4v_req_path.getValue().lstrip('/')] + + + def sess_make_req_path(self, sess, req_params): - querystring = require('querystring') - querystring.require('stringify') - params_encode = querystring.stringify(new_params) + querystring = sess.locals.require('querystring') + querystring.require('stringify') - params_encode.operands.args[0].getExprText() + params_encode = querystring.stringify(req_params) - req_path = '/jp/dash?' + params_encode + params_encode.operands.args[0].getExprText() - vf = cmd5x(req_path) + req_path = '/jp/dash?' + params_encode - req_path += '&vf=' + vf - sess.call(req_path) + vf = sess.locals.cmd5x(req_path) - return 'http://cache.video.iqiyi.com/' + req_path.getValue().lstrip('/') + req_path += '&vf=' + vf + # sess.call(req_path) + return req_path def getTargetJson(self, req_url): @@ -208,24 +232,36 @@ def extract(self, cookie_str): class IqiyiRespond(BasicRespond): - def __init__(self, parent, full_json, res_json, video_info): - BasicRespond.__init__(self, parent, full_json, res_json, video_info) + def __init__(self, parent, full_json, res_json, extra_info): + BasicRespond.__init__(self, parent, full_json, res_json, extra_info) self.program = None + self._video_len = -1 + self._target_video_urls = [] self.__extract__(res_json) + def __extract__(self, res_json): self.program = res_json['data'].get('program', None) self.sel_video = self.get_sel_video(self.program) + if self.getM3U8(): + self._target_video_urls = self.__extract_m3u8__(self.getM3U8()) + else: + time_len = 0 + for i in self.get_sel_fs(): + time_len += i['d'] + self._video_len = time_len + def getRangeFormat(self): if self.getM3U8(): return '&start=%d&end=%d' else: - return '&ranges=%d-%d' + return 'Range: bytes=%d-%d' + # return '&range=%d-%d' def getM3U8(self): return self.sel_video.get('m3u8') @@ -241,6 +277,7 @@ def getVideoSize(self): def getVideoTotal(self): m3u8 = self.getM3U8() + # return len(self._target_video_urls) if m3u8: self._target_video_urls = self.__extract_m3u8__(self.getM3U8()) return len(self._target_video_urls) @@ -255,7 +292,7 @@ def getReqHeaders(self): 'Connection': 'keep-alive', 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36', 'Accept': '*/*', - 'Referer': 'http://www.iqiyi.com/', + # 'Referer': 'http://www.iqiyi.com/', 'Accept-Encoding': 'gzip, deflate', 'Accept-Language': 'zh-CN,zh;q=0.9' } @@ -269,11 +306,19 @@ def getFeatures(self): return { 'quality': self.getQuality(), 'screensize': self.getScreenSize(), + 'file_format': self.getFileFormat() } def matchFeature(self, feature): - return feature['quality'] == self.getQuality() and feature['screensize'] == self.getScreenSize() + return feature['quality'] == self.getQuality() and \ + feature['screensize'] == self.getScreenSize() and \ + feature['file_format'] == self.getFileFormat() + # def getConcatMerger(self): + # if self.getM3U8(): + # return cv.MERGER_FFMPEG + # else: + # return cv.MERGER_SIMPLE def get_sel_video(self, program): @@ -321,18 +366,23 @@ def get_vid(self): def __extract_m3u8__(self, m3u8): if m3u8: - m3u8_parts = re.compile('#EXTINF:\d+,\s+(http://data.video.iqiyi.com/videos/\S+)').findall(m3u8) + m3u8_parts = re.compile('#EXTINF:(\d+),\s+(http://data.video.iqiyi.com/videos/\S+)').findall(m3u8) rex_filename = re.compile('/([a-z|A-Z|0-9]+)\.([A-Z|a-z|0-9]+)\?') filenames = [] reverse_parts = m3u8_parts[::-1] reverse_part_tails = [] + + video_len_counter = 0 for i in reverse_parts: - res = rex_filename.search(i) + video_len_counter += int(i[0]) + + res = rex_filename.search(i[1]) if res.group(1) not in filenames: filenames.append(res.group(1)) - reverse_part_tails.append(i) + reverse_part_tails.append(i[1]) + self._video_len = video_len_counter * 1000 # complete total file part_tails = reverse_part_tails[::-1] ret = [] diff --git a/flow.py b/flow.py index 6f79b8b..a830719 100644 --- a/flow.py +++ b/flow.py @@ -56,7 +56,7 @@ def handle(): if ToolReq.handle(): UndoneJob.handle() else: - gui.frame_main.Destroy() + ShutDown.handle() class ToolReq: @staticmethod @@ -64,7 +64,8 @@ def handle(): if not ToolReq.checkNode(): return False - ToolReq.checkFfmpeg() + if not ToolReq.checkFfmpeg(): + return False return True @staticmethod @@ -83,10 +84,14 @@ def unzip_ffmpeg(zipfile): def checkFfmpeg(): dlm = nbdler.Manager() if (not os.path.exists('ffmpeg.exe') or os.path.exists('ffmpeg.exe.nbdler')) and not os.path.exists(cv.FFMPEG_PATH): + dlg = wx.MessageDialog(None, u'该程序需要ffmpeg.exe才能完成工作,是否要下载?', u'提示', wx.YES_NO | wx.ICON_INFORMATION) + if dlg.ShowModal() != wx.ID_YES: + return False + dl = nbdler.open(urls=[TOOL_REQ_URL['ffmpeg']], max_conn=16, filename='ffmpeg.zip') dlm.addHandler(dl) - dlg = gui.DialogToolReq(gui.frame_main, u'正在下载 Ffmpeg 3.2.zip', dl.getFileSize(), dlm) + dlg = gui.DialogToolReq(gui.frame_downloader, u'正在下载 Ffmpeg 3.2.zip', dl.getFileSize(), dlm) dlg.Bind(wx.EVT_TIMER, ToolReq._process, dlg.timer) dlg.timer.Start(50, oneShot=False) @@ -94,6 +99,7 @@ def checkFfmpeg(): msg = dlg.ShowModal() if not dlm.isEnd(): dlm.shutdown() + dlg.Destroy() return False ToolReq.unzip_ffmpeg('ffmpeg.zip') if msg == wx.ID_OK: @@ -107,16 +113,20 @@ def checkFfmpeg(): def checkNode(): dlm = nbdler.Manager() if not os.path.exists('node.exe') or os.path.exists('node.exe.nbdler'): + dlg = wx.MessageDialog(None, u'该程序需要node.exe才能完成工作,是否要下载?', u'提示', wx.YES_NO | wx.ICON_INFORMATION) + if dlg.ShowModal() != wx.ID_YES: + return False dl = nbdler.open(urls=[TOOL_REQ_URL['node']], max_conn=16, filename='node.exe') dlm.addHandler(dl) - dlg = gui.DialogToolReq(gui.frame_main, u'正在下载 Nodejs v0.12.18', dl.getFileSize(), dlm) + dlg = gui.DialogToolReq(gui.frame_downloader, u'正在下载 Nodejs v0.12.18', dl.getFileSize(), dlm) dlg.Bind(wx.EVT_TIMER, ToolReq._process, dlg.timer) dlg.timer.Start(50, oneShot=False) dlm.run() msg = dlg.ShowModal() dlm.shutdown() + dlg.Destroy() if msg == wx.ID_OK: return True else: @@ -159,7 +169,7 @@ def handle(): UndoneJob.do() else: UndoneJob.skip() - + dlg.Destroy() else: FrameParser.handle() @@ -171,9 +181,9 @@ def do(): def _do(): def __(sel_res): if not sel_res: - gui.frame_main.Destroy() + ShutDown.handle() return - if FrameParser.ButtonGoDownload.handler_audio(sel_res): + if FrameParser.MenuGoDownload.handler_audio(sel_res): FrameDownload.handle() else: FrameParser.handle() @@ -198,6 +208,7 @@ def timeout(): UndoneJob.do() else: UndoneJob.skip() + dlg.Destroy() @staticmethod def skip(): @@ -211,10 +222,11 @@ class FrameParser: @staticmethod def handle(): - if gui.frame_parse.ShowModal() == cv.ID_PARSER_GODOWNLOAD: - FrameDownload.handle() - else: - gui.frame_main.Destroy() + gui.frame_parse.Show() + # if gui.frame_parse.ShowModal() == cv.ID_PARSER_GODOWNLOAD: + # FrameDownload.handle() + # else: + # ShutDown.handle() @@ -236,6 +248,13 @@ def handle(): def timeout(): dlg = wx.MessageDialog(gui.frame_parse, u'请求超时,请重试!', u'错误', wx.OK | wx.ICON_ERROR) dlg.ShowModal() + dlg.Destroy() + + @staticmethod + def empty(): + dlg = wx.MessageDialog(gui.frame_parse, u'数据返回为空。', u'错误', wx.OK | wx.ICON_ERROR) + dlg.ShowModal() + dlg.Destroy() @staticmethod def _parse(url, qualitys): @@ -244,7 +263,10 @@ def _parse(url, qualitys): except (socket.timeout, URLError, SSLError): wx.CallAfter(FrameParser.ButtonParse.timeout) else: - wx.CallAfter(FrameParser.ButtonParse.appendItem, res) + if not res: + wx.CallAfter(FrameParser.ButtonParse.empty) + else: + wx.CallAfter(FrameParser.ButtonParse.appendItem, res) finally: wx.CallAfter(gui.frame_parse.button_parse.Enable, True) @@ -267,7 +289,7 @@ def appendItem(res): gui.frame_parse.listctrl_parse.Append(data) - gui.frame_parse.SetTitle(res[0].getVideoTitle()) + gui.frame_parse.SetTitle(res[0].getVideoLegalTitle()) class ButtonPath: @@ -278,13 +300,14 @@ def handle(): if dlg.ShowModal() == wx.ID_OK: gui.frame_parse.textctrl_path.SetValue(dlg.GetPath()) cv.FILEPATH = dlg.GetPath() + dlg.Destroy() - class ButtonCopy: + class MenuCopyLinks: """Frame Parser Button-[Copy] Handler""" @staticmethod def handle(): - gui.frame_parse.button_copyurl.Enable(False) + # gui.frame_parse.button_copyurl.Enable(False) index = int(gui.frame_parse.listctrl_parse.GetFirstSelected()) if index != -1: sel_res = parser.getRespond()[index] @@ -294,23 +317,30 @@ def handle(): u'该视频提供了M3U8,是否复制M3U8到剪切板?\n选【No】将复制所有片段的下载地址。', u'提示', wx.YES_NO | wx.ICON_INFORMATION) msg = dlg.ShowModal() - threading.Thread(target=FrameParser.ButtonCopy._copy_fullurl, + threading.Thread(target=FrameParser.MenuCopyLinks._copy_fullurl, args=(sel_res, msg), daemon=True).start() + dlg.Destroy() else: - cpy_url = str('\n'.join(sel_res.getVideosFullUrl())) + + + # for i in sel_res.getVideoUrls(): + # for + # [ for i in sel_res.getVideoUrls()] + cpy_url = str('\n'.join(sel_res.getVideoUrls())) pyperclip.copy(cpy_url) - FrameParser.ButtonCopy.success() + FrameParser.MenuCopyLinks.success() @staticmethod def success(): - dlg = wx.MessageDialog(gui.frame_parse, u'写入到剪切板成功!', u'完成', wx.OK | wx.ICON_INFORMATION) + dlg = wx.MessageDialog(gui.frame_parse, u'写入到剪切板成功!', u'完成') dlg.ShowModal() + dlg.Destroy() @staticmethod def timeout(): dlg = wx.MessageDialog(gui.frame_parse, u'请求超时,请重试!', u'错误', wx.OK | wx.ICON_ERROR) dlg.ShowModal() - + dlg.Destroy() @staticmethod def _copy_fullurl(sel_res, dlg_msg): @@ -320,25 +350,25 @@ def _copy_fullurl(sel_res, dlg_msg): try: cpy_url = str('\n'.join(sel_res.getVideoUrls())) except (socket.timeout, URLError, SSLError): - wx.CallAfter(FrameParser.ButtonCopy.timeout) + wx.CallAfter(FrameParser.MenuCopyLinks.timeout) return pyperclip.copy(cpy_url) - wx.CallAfter(gui.frame_parse.button_copyurl.Enable, True) - wx.CallAfter(FrameParser.ButtonCopy.success) + # wx.CallAfter(gui.frame_parse.button_copyurl.Enable, True) + wx.CallAfter(FrameParser.MenuCopyLinks.success) - class ButtonGoDownload: + class MenuGoDownload: """Frame Parser Button-[GoDownload] Handler""" @staticmethod def handle(): - gui.frame_parse.button_godownload.Enable(False) + gui.frame_parse.listctrl_parse.menu.godownload.Enable(False) index = gui.frame_parse.listctrl_parse.GetFirstSelected() if index != -1: sel_res = parser.getRespond()[index] - if FrameParser.ButtonGoDownload.handler_audio(sel_res): - threading.Thread(target=FrameParser.ButtonGoDownload._download, args=(sel_res,)).start() + if FrameParser.MenuGoDownload.handler_audio(sel_res): + threading.Thread(target=FrameParser.MenuGoDownload._download, args=(sel_res,)).start() @staticmethod def handler_audio(sel_res): @@ -348,8 +378,9 @@ def handler_audio(sel_res): if dlg.ShowModal() == wx.ID_OK: index = audio_info.index(dlg.GetStringSelection()) sel_res.setSelAudio(index) + dlg.Destroy() else: - gui.frame_parse.button_godownload.Enable(True) + dlg.Destroy() return False return True @@ -359,18 +390,20 @@ def handler_audio(sel_res): def timeout(): dlg = wx.MessageDialog(gui.frame_parse, u'Msg:\"请求被服务器中止或网络超时。\"', u'错误', wx.OK | wx.ICON_ERROR) dlg.ShowModal() - gui.frame_parse.button_godownload.Enable(True) + dlg.Destroy() + # gui.frame_parse.button_godownload.Enable(True) @staticmethod def _download(sel_res): try: sel_res.getVideoUrls() except: - wx.CallAfter(FrameParser.ButtonGoDownload.timeout) + wx.CallAfter(FrameParser.MenuGoDownload.timeout) else: cv.SEL_RES = sel_res - wx.CallAfter(gui.frame_parse.EndModal, cv.ID_PARSER_GODOWNLOAD) - + wx.CallAfter(FrameDownload.handle) + finally: + gui.frame_parse.listctrl_parse.menu.godownload.Enable(True) @@ -378,6 +411,7 @@ class FrameDownload: """Frame Download Handler""" @staticmethod def handle(): + gui.frame_parse.Hide() FrameDownload.Download.handle() class Download: @@ -392,17 +426,17 @@ def handle(): @staticmethod def prepare(): downloader.prepare(cv.SEL_RES) - gui.frame_main.setTitleName(cv.SEL_RES.getVideoTitle()) - gui.frame_main.initTotal(cv.SEL_RES.getTotalFileSize()) + gui.frame_downloader.setTitleName(cv.SEL_RES.getVideoLegalTitle()) + gui.frame_downloader.initTotal(cv.SEL_RES.getTotalFileSize()) for i in range(cv.SEL_RES.getVideoTotal()): - gui.frame_main.insertBlock(i) + gui.frame_downloader.insertBlock(i) for i in range(cv.SEL_RES.getAudioTotal()): - gui.frame_main.insertBlock(i + cv.SEL_RES.getVideoTotal()) + gui.frame_downloader.insertBlock(i + cv.SEL_RES.getVideoTotal()) gui.setTimerHandler(downloader.getProcessEvent()) gui.runTimer(300, False) - gui.frame_main.Show(True) + gui.frame_downloader.Show(True) @staticmethod def _download_insp(): @@ -411,12 +445,11 @@ def _download_insp(): if cv.SHUTDOWN: url = cv.SEL_RES.getBaseUrl() quality = cv.SEL_RES.getQuality() - title = cv.SEL_RES.getVideoTitle() + title = cv.SEL_RES.getVideoLegalTitle() settings.setUndoneJob(url, title, quality, cv.SEL_RES.getFeatures()) settings.saveConfig() - - wx.CallAfter(gui.frame_main.Destroy) + # wx.CallAfter(ShutDown.handle) else: wx.CallAfter(Merge.handle) @@ -426,16 +459,21 @@ class Merge: """Frame Download Handler""" @staticmethod def handle(): - if not downloader.isAllDone(): Merge.fileNotAllFound() else: + Merge.prepare() Merge.do() + @staticmethod + def prepare(): + gui.frame_downloader.Hide() + gui.frame_merger.Show() + @staticmethod def do(): - if downloader.getAllAudioFilePath(): - wx.CallAfter(gui.frame_merger.Show) + # if downloader.getAllAudioFilePath(): + # wx.CallAfter(gui.frame_merger.Show) threading.Thread(target=Merge._do).start() @@ -448,14 +486,20 @@ def _do(): audio_dst = downloader.getDstAudioFilePath() if video_src: - mer = merger.make(video_dst, video_src, method=merger.MET_CONCAT) - mer.start() - mer.join() + if len(video_src) == 1: + shutil.move(video_src[0], video_dst) + else: + mer = merger.make(video_dst, video_src, method=merger.MET_CONCAT, merger=cv.SEL_RES.getConcatMerger()) + mer.start() + mer.join() if audio_src: - mer = merger.make(audio_dst, audio_src, method=merger.MET_CONCAT) - mer.start() - mer.join() + if len(audio_src) == 1: + shutil.move(audio_src[0], audio_dst) + else: + mer = merger.make(audio_dst, audio_src, method=merger.MET_CONCAT, merger=cv.SEL_RES.getConcatMerger()) + mer.start() + mer.join() if video_src and audio_src: src = [video_dst, audio_dst] @@ -475,23 +519,27 @@ def _do(): @staticmethod def fail(): - dlg = wx.MessageDialog(gui.frame_main, '发生未知错误,无法生成最终视频!', '错误', wx.OK | wx.ICON_ERROR) + dlg = wx.MessageDialog(gui.frame_downloader, '发生未知错误,无法生成最终视频!', '错误', wx.OK | wx.ICON_ERROR) dlg.ShowModal() + dlg.Destroy() @staticmethod def fileNotAllFound(): - dlg = wx.MessageDialog(gui.frame_main, '未找到所有分段文件,请重启程序重试!', '错误', wx.OK | wx.ICON_ERROR) + dlg = wx.MessageDialog(gui.frame_downloader, '未找到所有分段文件,请重启程序重试!', '错误', wx.OK | wx.ICON_ERROR) dlg.ShowModal() + dlg.Destroy() @staticmethod def success(): - dlg = wx.MessageDialog(gui.frame_main, u'视频已经合并完成,是否删除分段文件?', u'提示', wx.YES_NO | wx.ICON_INFORMATION) + cv.ALLTASKDONE = True + dlg = wx.MessageDialog(gui.frame_downloader, u'视频已经合并完成,是否删除分段文件?', u'提示', wx.YES_NO | wx.ICON_INFORMATION) if dlg.ShowModal() == wx.ID_YES: merger.del_src_files() - dlg = wx.MessageDialog(gui.frame_main, u'分段文件删除完成。', u'提示', wx.OK | wx.ICON_INFORMATION) + dlg = wx.MessageDialog(gui.frame_downloader, u'分段文件删除完成。', u'提示') dlg.ShowModal() + dlg.Destroy() @@ -502,3 +550,63 @@ def fail(): settings.initConfig() dlg = wx.MessageDialog(gui.frame_parse, 'config.ini文件错误。', '错误', wx.OK | wx.ICON_ERROR) dlg.ShowModal() + dlg.Destroy() + + + +class ShutDown: + @staticmethod + def handle(): + thr = threading.Thread(target=ShutDown._shutdown) + thr.start() + thr.join() + ShutDown.destroy_frame() + + @staticmethod + def destroy_frame(): + gui.frame_parse.Destroy() + gui.frame_downloader.Destroy() + gui.frame_merger.Destroy() + + + @staticmethod + def _shutdown(): + cv.SHUTDOWN = True + merger.shutdown() + downloader.shutdown() + + + @staticmethod + def frame_parser_close(event): + if cv.SHUTDOWN: + event.Skip() + else: + gui.frame_parse.Hide() + ShutDown.handle() + + @staticmethod + def frame_downloader_close(event): + if cv.SHUTDOWN: + event.Skip() + else: + dlg = wx.MessageDialog(gui.frame_downloader, u'你确定要中止下载吗?', u'提示', style=wx.YES_NO | wx.ICON_INFORMATION) + if dlg.ShowModal() == wx.ID_YES: + gui.frame_downloader.Hide() + ShutDown.handle() + dlg.Destroy() + + + @staticmethod + def frame_merger_close(event): + if cv.SHUTDOWN: + event.Skip() + else: + if not cv.ALLTASKDONE: + dlg = wx.MessageDialog(gui.frame_merger, u'你确定要中止操作吗?', u'提示', style=wx.YES_NO | wx.ICON_INFORMATION) + if dlg.ShowModal() == wx.ID_YES: + gui.frame_merger.Hide() + ShutDown.handle() + dlg.Destroy() + else: + gui.frame_merger.Hide() + ShutDown.handle() \ No newline at end of file diff --git a/gui/__init__.py b/gui/__init__.py index 1db0d21..4018778 100644 --- a/gui/__init__.py +++ b/gui/__init__.py @@ -1,37 +1,37 @@ import wx # from gui.frame import FrameMain -from gui.frame_main import * -from gui.frame_parse import DialogParser -from gui.about import About_Dialog -from gui.merger_output import MergerOutput +from gui.frame_downloader import * +from gui.frame_parser import FrameParser +from gui.about import DialogAbout +from gui.frame_merger import FrameMerger from gui.tool_request import DialogToolReq +from gui.settings import DialogSettings app = None -frame_main = None +frame_downloader = None frame_parse = None timer = None frame_merger = None def init(): - global app, frame_main, frame_parse, frame_merger + global app, frame_downloader, frame_parse, frame_merger app = wx.App() - frame_main = FrameMain(None) - frame_main.Show() - frame_parse = DialogParser(frame_main) - frame_merger = MergerOutput(frame_main) + frame_downloader = FrameMain(None) + frame_parse = FrameParser(None) + frame_merger = FrameMerger(None) def MainLoop(): global app app.MainLoop() def setTimerHandler(handler): - if frame_main.timer.IsRunning(): - frame_main.timer.Stop() - frame_main.Bind(wx.EVT_TIMER, handler, frame_main.timer) + if frame_downloader.timer.IsRunning(): + frame_downloader.timer.Stop() + frame_downloader.Bind(wx.EVT_TIMER, handler, frame_downloader.timer) def runTimer(ms, oneShot=False): - frame_main.timer.Start(ms, oneShot=oneShot) + frame_downloader.timer.Start(ms, oneShot=oneShot) def stopTimer(): - frame_main.timer.Stop() \ No newline at end of file + frame_downloader.timer.Stop() \ No newline at end of file diff --git a/gui/about.py b/gui/about.py index 62133ac..65d837b 100644 --- a/gui/about.py +++ b/gui/about.py @@ -1,115 +1,123 @@ -# -*- coding: utf-8 -*- import wx import wx.adv -class About_Dialog(wx.Dialog): +class DialogAbout(wx.Dialog): def __init__(self, parent): - wx.Dialog.__init__(self, parent, id=wx.ID_ANY, title=u"About", pos=wx.DefaultPosition, size=wx.Size(348, 385), - style=wx.DEFAULT_DIALOG_STYLE | wx.STAY_ON_TOP) + wx.Dialog.__init__(self, parent, id=wx.ID_ANY, title=u"About", pos=wx.DefaultPosition, + size=wx.Size(350, 400), style=wx.DEFAULT_DIALOG_STYLE) self.SetSizeHints(wx.DefaultSize, wx.DefaultSize) - self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)) - bSizer9 = wx.BoxSizer(wx.VERTICAL) + global_sizer = wx.BoxSizer(wx.VERTICAL) + global_sizer.SetMinSize(wx.Size(350, 350)) - panel3 = wx.Panel(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, + global_panel = wx.Panel(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.BORDER_RAISED | wx.TAB_TRAVERSAL) - panel3.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)) - bSizer10 = wx.BoxSizer(wx.VERTICAL) + sizer_panel = wx.BoxSizer(wx.VERTICAL) + sizer_panel.Add((0, 20), 0, wx.EXPAND, 5) - staticText181 = wx.StaticText(panel3, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, - wx.DefaultSize, 0) - staticText181.Wrap(-1) - - bSizer10.Add(staticText181, 0, wx.ALL, 5) + # ************************************** title - staticText17 = wx.StaticText(panel3, wx.ID_ANY, u"爱奇艺解析下载器", wx.DefaultPosition, + sizer_title = wx.BoxSizer(wx.HORIZONTAL) + text_title = wx.StaticText(global_panel, wx.ID_ANY, u"视频解析下载器", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_CENTER_HORIZONTAL) - staticText17.Wrap(-1) - - staticText17.SetFont( + text_title.Wrap(-1) + text_title.SetFont( wx.Font(20, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, wx.EmptyString)) + sizer_title.Add(text_title, 1, wx.ALIGN_CENTER | wx.ALL, 5) - bSizer10.Add(staticText17, 0, wx.ALIGN_CENTER | wx.ALL, 5) + # ************************************** Supported Sites - staticText18 = wx.StaticText(panel3, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, - wx.DefaultSize, 0) - staticText18.Wrap(-1) + sizer_support = wx.BoxSizer(wx.VERTICAL) + text_support = wx.StaticText(global_panel, wx.ID_ANY, u"Supported Sites", wx.DefaultPosition, + wx.DefaultSize, 0) + text_support.Wrap(-1) + text_support.SetFont( + wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, wx.EmptyString)) - bSizer10.Add(staticText18, 0, wx.ALL, 5) - staticText10 = wx.StaticText(panel3, wx.ID_ANY, u"Developed by:", wx.DefaultPosition, - wx.DefaultSize, wx.ALIGN_CENTER_HORIZONTAL) - staticText10.Wrap(-1) + text_sites = wx.StaticText(global_panel, wx.ID_ANY, u"爱奇艺、哔哩哔哩", wx.DefaultPosition, + wx.DefaultSize, 0) + text_sites.Wrap(-1) - staticText10.SetFont( - wx.Font(15, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, wx.EmptyString)) + text_sites.SetFont( + wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, wx.EmptyString)) - bSizer10.Add(staticText10, 0, wx.ALIGN_CENTER | wx.ALL | wx.EXPAND, 5) + sizer_support.Add(text_support, 0, wx.ALIGN_CENTER | wx.ALL, 5) + sizer_support.Add(text_sites, 0, wx.ALIGN_CENTER | wx.ALL, 5) - staticText101 = wx.StaticText(panel3, wx.ID_ANY, u"ZSAIm", wx.DefaultPosition, wx.DefaultSize, - wx.ALIGN_CENTER_HORIZONTAL) - staticText101.Wrap(-1) + # ************************************** Developed by + sizer_develop = wx.BoxSizer(wx.VERTICAL) - staticText101.SetFont( - wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, wx.EmptyString)) + text_develop = wx.StaticText(global_panel, wx.ID_ANY, u"Developed by:", wx.DefaultPosition, + wx.DefaultSize, 0) + text_develop.Wrap(-1) - bSizer10.Add(staticText101, 0, wx.ALIGN_CENTER | wx.ALL, 5) + text_develop.SetFont( + wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, wx.EmptyString)) - staticText19 = wx.StaticText(panel3, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, - wx.DefaultSize, 0) - staticText19.Wrap(-1) + text_developed_by = wx.StaticText(global_panel, wx.ID_ANY, u"ZSAIm", wx.DefaultPosition, + wx.DefaultSize, 0) + text_developed_by.Wrap(-1) - bSizer10.Add(staticText19, 0, wx.ALL, 5) + text_developed_by.SetFont( + wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, wx.EmptyString)) - staticText102 = wx.StaticText(panel3, wx.ID_ANY, u"Github:", wx.DefaultPosition, wx.DefaultSize, - wx.ALIGN_CENTER_HORIZONTAL) - staticText102.Wrap(-1) + sizer_develop.Add(text_develop, 0, wx.ALIGN_CENTER | wx.ALL, 5) + sizer_develop.Add(text_developed_by, 0, wx.ALIGN_CENTER | wx.ALL, 5) - staticText102.SetFont( - wx.Font(15, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, wx.EmptyString)) + # ************************************** Github links + sizer_github = wx.BoxSizer(wx.VERTICAL) - bSizer10.Add(staticText102, 0, wx.ALIGN_CENTER | wx.ALL, 5) + text_github = wx.StaticText(global_panel, wx.ID_ANY, u"Github:", wx.DefaultPosition, + wx.DefaultSize, 0) + text_github.Wrap(-1) - hyperlink1 = wx.adv.HyperlinkCtrl(panel3, wx.ID_ANY, - u"https://github.com/ZSAIm/iqiyi-parser", - u"https://github.com/ZSAIm/iqiyi-parser", wx.DefaultPosition, - wx.DefaultSize, wx.adv.HL_ALIGN_CENTRE | wx.adv.HL_DEFAULT_STYLE) - hyperlink1.SetFont( - wx.Font(wx.NORMAL_FONT.GetPointSize(), wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, - False, wx.EmptyString)) - hyperlink1.SetForegroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)) + text_github.SetFont( + wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, wx.EmptyString)) - bSizer10.Add(hyperlink1, 0, wx.ALIGN_CENTER | wx.ALL, 5) + sizer_github.Add(text_github, 0, wx.ALIGN_CENTER | wx.ALL, 5) - staticText21 = wx.StaticText(panel3, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, - wx.DefaultSize, 0) - staticText21.Wrap(-1) + link_github = wx.adv.HyperlinkCtrl(global_panel, wx.ID_ANY, u"https://github.com/ZSAIm/iqiyi-parser", + u"https://github.com/ZSAIm/iqiyi-parser", wx.DefaultPosition, + wx.DefaultSize, wx.adv.HL_DEFAULT_STYLE) + sizer_github.Add(link_github, 0, wx.ALIGN_CENTER | wx.ALL, 5) - bSizer10.Add(staticText21, 0, wx.ALL, 5) - panel3.SetSizer(bSizer10) - panel3.Layout() - bSizer10.Fit(panel3) - bSizer9.Add(panel3, 1, wx.EXPAND | wx.ALL, 10) + sizer_panel.Add(sizer_title, 1, wx.EXPAND, 5) + sizer_panel.Add((0, 20), 0, wx.EXPAND, 5) + sizer_panel.Add(sizer_support, 1, wx.EXPAND, 5) + sizer_panel.Add((0, 20), 0, wx.EXPAND, 5) + sizer_panel.Add(sizer_develop, 1, wx.EXPAND, 5) + sizer_panel.Add(sizer_github, 1, wx.EXPAND, 5) + sizer_panel.Add((0, 20), 1, wx.EXPAND, 5) - button7 = wx.Button(self, wx.ID_ANY, u"OK", wx.DefaultPosition, wx.DefaultSize, 0) - bSizer9.Add(button7, 0, wx.ALIGN_CENTER | wx.ALL, 5) + global_panel.SetSizer(sizer_panel) + global_panel.Layout() + sizer_panel.Fit(global_panel) + global_sizer.Add(global_panel, 1, wx.EXPAND | wx.ALL, 5) - self.Bind(wx.EVT_BUTTON, self.close, button7) + button_ok = wx.Button(self, wx.ID_ANY, u"OK", wx.DefaultPosition, wx.DefaultSize, 0) + global_sizer.Add(button_ok, 0, wx.ALIGN_CENTER | wx.ALL, 5) - self.SetSizer(bSizer9) + self.SetSizer(global_sizer) self.Layout() + self.Bind(wx.EVT_BUTTON, self.button_close, button_ok) + self.Centre(wx.BOTH) - def close(self, event): + def button_close(self, event): self.Destroy() + def __del__(self): pass +# app = wx.App() +# About_Dialog(None).ShowModal() + diff --git a/gui/frame_main.py b/gui/frame_downloader.py similarity index 93% rename from gui/frame_main.py rename to gui/frame_downloader.py index 8e92333..56ee675 100644 --- a/gui/frame_main.py +++ b/gui/frame_downloader.py @@ -3,6 +3,7 @@ import wx from gui.item_sizer import ItemBoxSizer, format_byte import time +import CommonVar as cv COLOR_OK = 1 @@ -13,7 +14,7 @@ class FrameMain(wx.Frame): def __init__(self, parent): - wx.Frame.__init__(self, parent, id=wx.ID_ANY, title='IQIYI视频下载器', pos=wx.DefaultPosition, + wx.Frame.__init__(self, parent, id=wx.ID_ANY, title='视频下载器', pos=wx.DefaultPosition, size=wx.Size(-1, 420), style=wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL) self.SetSizeHints(wx.DefaultSize, wx.DefaultSize) @@ -72,6 +73,7 @@ def __init__(self, parent): self.timer.SetOwner(self, wx.ID_ANY) + def initTotal(self, total): self.total = total if total > 0 else 0 self.gauge_total = wx.Gauge(self, wx.ID_ANY, 10000, wx.DefaultPosition, wx.DefaultSize, @@ -99,16 +101,16 @@ def updateTotal(self, current_byte, speed_byte): self.gauge_total.SetValue(int(percent*100)) self.text_progress.SetLabelText(progress) - self.sizer_total.Layout() - self.sizer_top.Layout() + # self.sizer_total.Layout() + # self.sizer_top.Layout() def initTotal_Merge(self, total): self.total = total self.text_percent.SetLabelText('0%') self.gauge_total.SetValue(0) self.text_speed.SetLabelText('0/0') - self.sizer_total.Layout() - self.sizer_top.Layout() + # self.sizer_total.Layout() + # self.sizer_top.Layout() def updateMerge(self, cur_index): progress = '%d/%d' % (cur_index, self.total) @@ -131,7 +133,7 @@ def insertItem(self, id, total_byte, cur_byte=0, speed_byte=0): self.sizer_items.Add(item, 1, wx.ALL | wx.EXPAND, 1) - self.sizer_items.Layout() + # self.sizer_items.Layout() def getItem(self, id): return self.items_dict.get(id, None) @@ -147,8 +149,8 @@ def insertBlock(self, id): block.SetForegroundColour(wx.Colour(255, 255, 255, 255)) self.block_list.append(block) self.sizer_blocks.Add(block, 0, wx.ALL, 5) - self.sizer_items.Layout() - self.Layout() + # self.sizer_items.Layout() + # self.Layout() def updateBlock(self, id, type): @@ -161,7 +163,7 @@ def updateBlock(self, id, type): else: self.block_list[id].SetBackgroundColour(wx.Colour(255, 0, 0, 255)) - self.Layout() + # self.Layout() def deleteItem(self, id, clear_widget=True): if self.items_list: @@ -171,12 +173,14 @@ def deleteItem(self, id, clear_widget=True): if clear_widget: item.Clear(True) self.sizer_items.Remove(item) - self.Layout() + # self.Layout() def setTitleName(self, text): self.text_title.SetLabelText(text) + + class MainMenuBar(wx.MenuBar): def __init__(self, style): wx.MenuBar.__init__(self, style) @@ -202,7 +206,7 @@ def initMenuItems(self): self.parse = wx.MenuItem(self, wx.ID_ANY, 'Parse', wx.EmptyString, wx.ITEM_NORMAL) self.parse.Enable(False) self.settings = wx.MenuItem(self, wx.ID_ANY, 'Settings', wx.EmptyString, wx.ITEM_NORMAL) - self.settings.Enable(False) + # self.settings.Enable(False) self.exit = wx.MenuItem(self, wx.ID_ANY, 'Exit', wx.EmptyString, wx.ITEM_NORMAL) self.Append(self.parse) @@ -222,5 +226,5 @@ def __init__(self, *args): self.initMenuItems() def initMenuItems(self): - self.about = wx.MenuItem(self, wx.ID_ANY, 'About', wx.EmptyString, wx.ITEM_NORMAL) + self.about = wx.MenuItem(self, wx.ID_ANY, '&About\tF1', wx.EmptyString, wx.ITEM_NORMAL) self.Append(self.about) \ No newline at end of file diff --git a/gui/frame_merger.py b/gui/frame_merger.py new file mode 100644 index 0000000..d045879 --- /dev/null +++ b/gui/frame_merger.py @@ -0,0 +1,144 @@ +# -*- coding: utf-8 -*- + +import wx, time + +EVT_OUTPUT_APPEND = wx.NewId() +EVT_OUTPUT_UPDATE = wx.NewId() + +class FrameMerger(wx.Frame): + def __init__(self, parent): + wx.Frame.__init__(self, parent, id=wx.ID_ANY, title=u'FFMPEG 输出窗口', pos=wx.DefaultPosition, + size=wx.Size(427, 450), style=wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL) + + self.SetSizeHints(wx.Size(427, 381), wx.DefaultSize) + self.SetBackgroundColour(wx.Colour(240, 240, 240)) + + sizer = wx.BoxSizer(wx.VERTICAL) + + self.textctrl_output = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.Size(427, 381), + wx.TE_AUTO_URL | wx.TE_MULTILINE | wx.TE_PROCESS_ENTER | wx.TE_PROCESS_TAB) + + self.textctrl_output.SetFont(wx.Font(8, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, "宋体")) + + self.staticline = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL) + + self.text_remain = wx.StaticText(self, wx.ID_ANY, u"估计还剩 00:00:00", wx.DefaultPosition, wx.DefaultSize, + wx.ALIGN_RIGHT) + self.text_remain.Wrap(-1) + + self.gauge_progress = wx.Gauge(self, wx.ID_ANY, 10000, wx.DefaultPosition, wx.DefaultSize, wx.GA_HORIZONTAL) + self.gauge_progress.SetValue(0) + + sizer_1 = wx.BoxSizer(wx.HORIZONTAL) + + self.text_percent = wx.StaticText(self, wx.ID_ANY, u"0.0%", wx.DefaultPosition, wx.DefaultSize, 0) + self.text_percent.Wrap(-1) + + self.text_size = wx.StaticText(self, wx.ID_ANY, u"0kb", wx.DefaultPosition, wx.DefaultSize, + wx.ALIGN_RIGHT) + self.text_size.Wrap(-1) + + sizer_1.Add(self.text_percent, 1, wx.ALL | wx.EXPAND, 2) + sizer_1.Add(self.text_size, 1, wx.ALL | wx.EXPAND, 2) + + sizer.Add(self.textctrl_output, 1, wx.ALL | wx.EXPAND, 2) + sizer.Add(self.staticline, 0, wx.EXPAND | wx.ALL, 5) + sizer.Add(self.text_remain, 0, wx.ALL | wx.EXPAND, 2) + sizer.Add(self.gauge_progress, 0, wx.ALL | wx.EXPAND, 2) + sizer.Add(sizer_1, 0, wx.ALL | wx.EXPAND, 3) + + self.menu_bar = MergerMenuBar(0) + self.SetMenuBar(self.menu_bar) + + + self.SetSizer(sizer) + self.Layout() + + self.Centre(wx.BOTH) + + self.textctrl_output.Connect(-1, -1, EVT_OUTPUT_APPEND, self.AppendText) + self.gauge_progress.Connect(-1, -1, EVT_OUTPUT_UPDATE, self.update) + + + def __del__(self): + pass + + def AppendText(self, event): + self.textctrl_output.AppendText(event.text) + + def Clear(self): + self.textctrl_output.Clear() + + + def update(self, event): + percent = event.cur_len / event.total_len * 100.0 + + self.text_percent.SetLabelText('%f%%' % percent) + self.gauge_progress.SetValue(int(percent*100)) + + self.text_remain.SetLabelText('估计还剩 %s' % event.remain_time_str) + self.text_size.SetLabelText(event.cur_byte_str) + self.Layout() + +class MergerMenuBar(wx.MenuBar): + def __init__(self, style): + wx.MenuBar.__init__(self, style) + + self.file = Menu_File() + self.help = Menu_Help() + + self.Append(self.file, 'File') + self.Append(self.help, 'Help') + + +class Menu_File(wx.Menu): + def __init__(self, *args): + wx.Menu.__init__(self, *args) + + self.exit = None + + self.initMenuItems() + + def initMenuItems(self): + + self.exit = wx.MenuItem(self, wx.ID_ANY, 'Exit', wx.EmptyString, wx.ITEM_NORMAL) + + self.Append(self.exit) + + +class Menu_Help(wx.Menu): + def __init__(self, *args): + wx.Menu.__init__(self, *args) + + self.about = None + + self.initMenuItems() + + def initMenuItems(self): + self.about = wx.MenuItem(self, wx.ID_ANY, '&About\tF1', wx.EmptyString, wx.ITEM_NORMAL) + self.Append(self.about) + + + +class MergerOutputAppendEvent(wx.PyEvent): + def __init__(self, text): + wx.PyEvent.__init__(self) + self.SetEventType(EVT_OUTPUT_APPEND) + self.text = text + +class MergerOutputUpdateEvent(wx.PyEvent): + def __init__(self, cur_len, total_len, cur_byte_str, remain_time_str): + wx.PyEvent.__init__(self) + self.SetEventType(EVT_OUTPUT_UPDATE) + self.cur_len = cur_len + self.total_len = total_len + self.cur_byte_str = cur_byte_str + self.remain_time_str = remain_time_str + + + +# app = wx.App() +# frame_main = FrameMerger(None) +# frame_main.Show() +# dlg.ShowModal() +# app.MainLoop() diff --git a/gui/frame_parse.py b/gui/frame_parser.py similarity index 57% rename from gui/frame_parse.py rename to gui/frame_parser.py index 54ffee3..902fcba 100644 --- a/gui/frame_parse.py +++ b/gui/frame_parser.py @@ -6,11 +6,11 @@ ID_BUTTON_PARSE_EVENT = wx.NewId() -class DialogParser(wx.Dialog): +class FrameParser(wx.Frame): def __init__(self, parent, *args): - wx.Dialog.__init__(self, parent, id=wx.ID_ANY, title=u'IQIYI视频解析器', pos=wx.DefaultPosition, + wx.Frame.__init__(self, parent, id=wx.ID_ANY, title=u'视频解析器', pos=wx.DefaultPosition, size=wx.Size(420, 460), style=wx.DEFAULT_DIALOG_STYLE) - + self.SetBackgroundColour(wx.Colour(240, 240, 240)) text_resolution = wx.StaticText(self, wx.ID_ANY, u"Quality:", wx.DefaultPosition, wx.DefaultSize, 0) text_resolution.Wrap(-1) @@ -32,28 +32,28 @@ def __init__(self, parent, *args): sizer_resolution = wx.BoxSizer(wx.HORIZONTAL) sizer_resolution.Add(text_resolution, 0, wx.ALL, 5) - sizer_resolution.Add(self.checkbox_1, wx.EXPAND, wx.ALIGN_CENTER |wx.ALL, 2) - sizer_resolution.Add(self.checkbox_2, wx.EXPAND, wx.ALIGN_CENTER |wx.ALL, 2) - sizer_resolution.Add(self.checkbox_3, wx.EXPAND, wx.ALIGN_CENTER |wx.ALL, 2) - sizer_resolution.Add(self.checkbox_4, wx.EXPAND, wx.ALIGN_CENTER |wx.ALL, 2) - sizer_resolution.Add(self.checkbox_5, wx.EXPAND, wx.ALIGN_CENTER |wx.ALL, 2) - sizer_resolution.Add(self.checkbox_6, wx.EXPAND, wx.ALIGN_CENTER |wx.ALL, 2) + sizer_resolution.Add(self.checkbox_1, wx.EXPAND, wx.ALIGN_CENTER |wx.ALL, 0) + sizer_resolution.Add(self.checkbox_2, wx.EXPAND, wx.ALIGN_CENTER |wx.ALL, 0) + sizer_resolution.Add(self.checkbox_3, wx.EXPAND, wx.ALIGN_CENTER |wx.ALL, 0) + sizer_resolution.Add(self.checkbox_4, wx.EXPAND, wx.ALIGN_CENTER |wx.ALL, 0) + sizer_resolution.Add(self.checkbox_5, wx.EXPAND, wx.ALIGN_CENTER |wx.ALL, 0) + sizer_resolution.Add(self.checkbox_6, wx.EXPAND, wx.ALIGN_CENTER |wx.ALL, 0) staticline1 = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL) staticline2 = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL) staticline4 = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL) - staticline3 = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL) + # staticline3 = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL) text_url = wx.StaticText(self, wx.ID_ANY, u"Url", wx.DefaultPosition, wx.DefaultSize, 0) text_url.Wrap(-1) self.textctrl_url = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0) - self.button_parse = wx.Button(self, wx.ID_ANY, u"解析", wx.DefaultPosition, wx.DefaultSize, 0) + self.button_parse = wx.Button(self, wx.ID_ANY, u"解析", wx.DefaultPosition, wx.Size(80, -1), 0) # self.button_parse.Connect(-1, -1, ID_BUTTON_PARSE_EVENT, self.button_parse_retrycounter) sizer_url = wx.BoxSizer(wx.HORIZONTAL) - sizer_url.Add(text_url, 0, wx.ALIGN_CENTER | wx.ALL, 2) + sizer_url.Add(text_url, 0, wx.ALIGN_CENTER | wx.ALL, 5) sizer_url.Add(self.textctrl_url, 5, wx.ALIGN_CENTER | wx.ALL, 2) sizer_url.Add(self.button_parse, 0, wx.ALIGN_CENTER | wx.ALL, 2) # ================================================================= @@ -65,34 +65,37 @@ def __init__(self, parent, *args): # ================================================================= - self.button_copyurl = wx.Button(self, wx.ID_ANY, u"复制下载链接", wx.DefaultPosition, wx.DefaultSize, 0) - self.button_godownload = wx.Button(self, wx.ID_ANY, u"下载选中项", wx.DefaultPosition, wx.DefaultSize, 0) + # self.button_copyurl = wx.Button(self, wx.ID_ANY, u"复制下载链接", wx.DefaultPosition, wx.DefaultSize, 0) + # self.button_godownload = wx.Button(self, wx.ID_ANY, u"下载选中项", wx.DefaultPosition, wx.DefaultSize, 0) # self.button_godownload.Enable(False) - sizer_control = wx.BoxSizer(wx.HORIZONTAL) - sizer_control.Add(self.button_copyurl, 0, wx.ALL, 5) - sizer_control.Add(self.button_godownload, 1, wx.ALIGN_RIGHT | wx.ALL, 5) + # sizer_control = wx.BoxSizer(wx.HORIZONTAL) + # sizer_control.Add(self.button_copyurl, 0, wx.ALL, 5) + # sizer_control.Add(self.button_godownload, 1, wx.ALIGN_RIGHT | wx.ALL, 5) # ================================================================= - text_path = wx.StaticText(self, wx.ID_ANY, u"PATH:", wx.DefaultPosition, wx.DefaultSize, 0) - text_path.Wrap(-1) - self.textctrl_path = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0) - self.button_path = wx.Button(self, wx.ID_ANY, u"...", wx.DefaultPosition, wx.Size(50, -1), 0) + # text_path = wx.StaticText(self, wx.ID_ANY, u"PATH:", wx.DefaultPosition, wx.DefaultSize, 0) + # text_path.Wrap(-1) + # self.textctrl_path = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0) + # self.button_path = wx.Button(self, wx.ID_ANY, u"...", wx.DefaultPosition, wx.Size(50, -1), 0) - sizer_path = wx.BoxSizer(wx.HORIZONTAL) - sizer_path.Add(text_path, 0, wx.ALIGN_CENTER | wx.ALL, 2) - sizer_path.Add(self.textctrl_path, 1, wx.ALIGN_CENTER | wx.ALL, 2) - sizer_path.Add(self.button_path, 0, wx.ALIGN_CENTER | wx.ALL, 2) + # sizer_path = wx.BoxSizer(wx.HORIZONTAL) + # sizer_path.Add(text_path, 0, wx.ALIGN_CENTER | wx.ALL, 2) + # sizer_path.Add(self.textctrl_path, 1, wx.ALIGN_CENTER | wx.ALL, 2) + # sizer_path.Add(self.button_path, 0, wx.ALIGN_CENTER | wx.ALL, 2) sizer_global = wx.BoxSizer(wx.VERTICAL) - sizer_global.Add(sizer_resolution, 0, wx.EXPAND, 3) - sizer_global.Add(staticline1, 0, wx.EXPAND | wx.ALL, 3) + # sizer_global.Add(staticline1, 0, wx.EXPAND | wx.ALL, 3) sizer_global.Add(sizer_url, 0, wx.EXPAND, 3) sizer_global.Add(staticline2, 0, wx.EXPAND | wx.ALL, 3) sizer_global.Add(sizer_listctrl, 1, wx.EXPAND, 3) - sizer_global.Add(staticline3, 0, wx.EXPAND | wx.ALL, 3) - sizer_global.Add(sizer_control, 0, wx.EXPAND, 2) + # sizer_global.Add(staticline3, 0, wx.EXPAND | wx.ALL, 3) + # sizer_global.Add(sizer_control, 0, wx.EXPAND, 2) sizer_global.Add(staticline4, 0, wx.EXPAND | wx.ALL, 3) - sizer_global.Add(sizer_path, 0, wx.EXPAND, 0) + sizer_global.Add(sizer_resolution, 0, wx.EXPAND, 3) + # sizer_global.Add(sizer_path, 0, wx.EXPAND, 0) + + self.menu_bar = ParserMenuBar(0) + self.SetMenuBar(self.menu_bar) self.SetSizer(sizer_global) self.Layout() @@ -103,6 +106,54 @@ def button_parse_retrycounter(self, event): self.button_parse.SetLabelText('第 %d 次重试' % event.counter) + +class ParserMenuBar(wx.MenuBar): + def __init__(self, style): + wx.MenuBar.__init__(self, style) + + self.file = Menu_File() + self.help = Menu_Help() + + self.Append(self.file, 'File') + self.Append(self.help, 'Help') + + +class Menu_File(wx.Menu): + def __init__(self, *args): + wx.Menu.__init__(self, *args) + + self.settings = None + self.exit = None + + self.initMenuItems() + + def initMenuItems(self): + + self.settings = wx.MenuItem(self, wx.ID_ANY, 'Settings', wx.EmptyString, wx.ITEM_NORMAL) + + self.exit = wx.MenuItem(self, wx.ID_ANY, 'Exit', wx.EmptyString, wx.ITEM_NORMAL) + + self.Append(self.settings) + self.AppendSeparator() + self.Append(self.exit) + + +class Menu_Help(wx.Menu): + def __init__(self, *args): + wx.Menu.__init__(self, *args) + + self.about = None + + self.initMenuItems() + + def initMenuItems(self): + self.about = wx.MenuItem(self, wx.ID_ANY, '&About\tF1', wx.EmptyString, wx.ITEM_NORMAL) + self.Append(self.about) + + + + + class ButtonParseRetryEvent(wx.PyEvent): def __init__(self, counter): wx.PyEvent.__init__(self) @@ -112,3 +163,8 @@ def __init__(self, counter): +# app = wx.App() +# frame_main = FrameParser(None) +# wx.TextEntryDialog(frame_main, 'a', 'as').ShowModal() +# frame_main.Show() +# app.MainLoop() diff --git a/gui/listctrl.py b/gui/listctrl.py index 7c27c09..1b83c04 100644 --- a/gui/listctrl.py +++ b/gui/listctrl.py @@ -9,13 +9,15 @@ class ListCtrl_Parser(wx.ListCtrl): def __init__(self, *args): wx.ListCtrl.__init__(self, *args) - self.initColumn() + self.menu = Menu_Parser() + self.Bind(wx.EVT_RIGHT_DOWN, self.OnContextMenu) + def initColumn(self): - self.AppendColumn('Quality', format=wx.LIST_FORMAT_RIGHT, width=55) + self.AppendColumn('Q', format=wx.LIST_FORMAT_RIGHT, width=50) self.AppendColumn('分辨率', format=wx.LIST_FORMAT_CENTER, width=80) - self.AppendColumn('Num', format=wx.LIST_FORMAT_RIGHT, width=40) + self.AppendColumn('N', format=wx.LIST_FORMAT_RIGHT, width=40) self.AppendColumn('视频大小', width=80, format=wx.LIST_FORMAT_RIGHT) self.AppendColumn('音频', width=50, format=wx.LIST_FORMAT_CENTER) self.AppendColumn('格式', width=50, format=wx.LIST_FORMAT_LEFT) @@ -32,8 +34,6 @@ def Append(self, entry, fgcolor=None): self.SetItemTextColour(item_count-1, wx.Colour(fgcolor)) - - def DeleteItem(self, item): wx.ListCtrl.DeleteItem(self, item) item_count = self.GetItemCount() @@ -45,3 +45,26 @@ def DeleteItem(self, item): odd = not odd + def OnContextMenu(self, event): + if self.GetFirstSelected() != -1: + self.PopupMenu(self.menu, event.GetPosition()) + + +class Menu_Parser(wx.Menu): + def __init__(self, *args): + wx.Menu.__init__(self, *args) + self.godownload = None + self.copylinks = None + + self.initItems() + + def initItems(self): + self.godownload = wx.MenuItem(self, wx.ID_ANY, u'下载所选项', wx.EmptyString, wx.ITEM_NORMAL) + self.Append(self.godownload) + + self.AppendSeparator() + + self.copylinks = wx.MenuItem(self, wx.ID_ANY, u'复制下载链接', wx.EmptyString, wx.ITEM_NORMAL) + self.Append(self.copylinks) + + self.copylinks.Enable(False) diff --git a/gui/merger_output.py b/gui/merger_output.py deleted file mode 100644 index 1f61a64..0000000 --- a/gui/merger_output.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- - -import wx - -EVT_OUTPUT_APPEND = wx.NewId() - -class MergerOutput(wx.Dialog): - def __init__(self, parent): - wx.Dialog.__init__(self, parent, id=wx.ID_ANY, title=u'FFMPEG 输出窗口', pos=wx.DefaultPosition, - size=wx.Size(427, 381), style=wx.DEFAULT_DIALOG_STYLE) - - self.SetSizeHints(wx.Size(427, 381), wx.DefaultSize) - - sizer = wx.BoxSizer(wx.VERTICAL) - - self.output = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.Size(427, 381), - wx.TE_AUTO_URL | wx.TE_MULTILINE | wx.TE_PROCESS_ENTER | wx.TE_PROCESS_TAB) - - self.output.SetFont(wx.Font(8, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, "宋体")) - - sizer.Add(self.output, 1, wx.ALL | wx.EXPAND, 5) - self.SetSizer(sizer) - self.Layout() - - self.Centre(wx.BOTH) - - self.output.Connect(-1, -1, EVT_OUTPUT_APPEND, self.AppendText) - - self.Bind(wx.EVT_CLOSE, self.onClose) - - def __del__(self): - pass - - def AppendText(self, event): - self.output.AppendText(event.text) - - def Clear(self): - self.output.Clear() - - def onClose(self, event): - self.Hide() - - -class MergerOutputAppendEvent(wx.PyEvent): - def __init__(self, text): - wx.PyEvent.__init__(self) - self.SetEventType(EVT_OUTPUT_APPEND) - self.text = text - diff --git a/gui/settings.py b/gui/settings.py new file mode 100644 index 0000000..2c8a503 --- /dev/null +++ b/gui/settings.py @@ -0,0 +1,148 @@ +# -*- coding: utf-8 -*- + +import wx +import CommonVar as cv +from handler import settings + +class DialogSettings(wx.Dialog): + + def __init__(self, parent): + wx.Dialog.__init__(self, parent, id=wx.ID_ANY, title=u'设置', pos=wx.DefaultPosition, + size=wx.Size(376, 370), style=wx.DEFAULT_DIALOG_STYLE) + + self.SetSizeHints(wx.DefaultSize, wx.DefaultSize) + + global_sizer = wx.BoxSizer(wx.VERTICAL) + + global_sizer.SetMinSize(wx.Size(400, 200)) + + sizer_download = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, u"下载器设置"), wx.VERTICAL) + + + + sizer_max_conn = wx.BoxSizer(wx.HORIZONTAL) + + text_max_conn = wx.StaticText(sizer_download.GetStaticBox(), wx.ID_ANY, u"最大连接数:", wx.DefaultPosition, + wx.DefaultSize, wx.ALIGN_LEFT) + text_max_conn.Wrap(-1) + + self.slider_max_conn = wx.Slider(sizer_download.GetStaticBox(), wx.ID_ANY, cv.MAX_CONN, 0, 10, wx.DefaultPosition, wx.Size(-1, -1), + wx.SL_AUTOTICKS | wx.SL_HORIZONTAL | wx.SL_MIN_MAX_LABELS | wx.SL_SELRANGE) + + sizer_max_conn.Add(text_max_conn, 1, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.ALL, 5) + sizer_max_conn.Add(self.slider_max_conn, 2, wx.ALIGN_CENTER | wx.ALL | wx.EXPAND, 5) + + sizer_max_task = wx.BoxSizer(wx.HORIZONTAL) + + text_max_task = wx.StaticText(sizer_download.GetStaticBox(), wx.ID_ANY, u"最大任务数:", wx.DefaultPosition, + wx.DefaultSize, wx.ALIGN_LEFT) + text_max_task.Wrap(-1) + + self.slider_max_task = wx.Slider(sizer_download.GetStaticBox(), wx.ID_ANY, cv.MAX_TASK, 0, 5, wx.DefaultPosition, wx.DefaultSize, + wx.SL_AUTOTICKS | wx.SL_HORIZONTAL | wx.SL_MIN_MAX_LABELS | wx.SL_SELRANGE) + + sizer_max_task.Add(text_max_task, 1, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.ALL, 5) + sizer_max_task.Add(self.slider_max_task, 2, wx.ALIGN_CENTER | wx.ALL | wx.EXPAND, 5) + + + sizer_buffsize = wx.BoxSizer(wx.HORIZONTAL) + + text_buffsize = wx.StaticText(sizer_download.GetStaticBox(), wx.ID_ANY, u"缓冲器大小(MB):", wx.DefaultPosition, + wx.DefaultSize, wx.ALIGN_LEFT) + text_buffsize.Wrap(-1) + + self.slider_buffsize = wx.Slider(sizer_download.GetStaticBox(), wx.ID_ANY, cv.BUFFER_SIZE, 10, 50, wx.DefaultPosition, wx.DefaultSize, + wx.SL_AUTOTICKS | wx.SL_HORIZONTAL | wx.SL_MIN_MAX_LABELS | wx.SL_SELRANGE | wx.SL_VALUE_LABEL) + + sizer_buffsize.Add(text_buffsize, 1, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.ALL, 5) + sizer_buffsize.Add(self.slider_buffsize, 2, wx.ALIGN_CENTER | wx.ALL | wx.EXPAND, 5) + + sizer_blocksize = wx.BoxSizer(wx.HORIZONTAL) + + text_blocksize = wx.StaticText(sizer_download.GetStaticBox(), wx.ID_ANY, u"块大小(KB):", wx.DefaultPosition, + wx.DefaultSize, wx.ALIGN_LEFT) + text_blocksize.Wrap(-1) + + self.slider_blocksize = wx.Slider(sizer_download.GetStaticBox(), wx.ID_ANY, cv.BLOCK_SIZE, 1, 1024, + wx.DefaultPosition, wx.DefaultSize, + wx.SL_AUTOTICKS | wx.SL_HORIZONTAL | wx.SL_MIN_MAX_LABELS | wx.SL_SELRANGE | wx.SL_VALUE_LABEL) + + sizer_blocksize.Add(text_blocksize, 1, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.ALL, 5) + sizer_blocksize.Add(self.slider_blocksize, 2, wx.ALIGN_CENTER | wx.ALL | wx.EXPAND, 5) + + + sizer_download.Add(sizer_max_conn, 1, wx.EXPAND, 5) + sizer_download.Add(sizer_max_task, 1, wx.EXPAND, 5) + sizer_download.Add(sizer_buffsize, 1, wx.EXPAND, 5) + sizer_download.Add(sizer_blocksize, 1, wx.EXPAND, 5) + + sizer_path = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, u"文件存放路径"), wx.HORIZONTAL) + + self.textctrl_path = wx.TextCtrl(sizer_path.GetStaticBox(), wx.ID_ANY, cv.FILEPATH, wx.DefaultPosition, + wx.DefaultSize, 0) + + self.button_path = wx.Button(sizer_path.GetStaticBox(), wx.ID_ANY, u"选择", wx.DefaultPosition, wx.Size(60, -1), 0) + self.button_path.SetMinSize(wx.Size(60, -1)) + self.button_path.SetMaxSize(wx.Size(60, -1)) + + sizer_path.Add(self.textctrl_path, 1, wx.ALIGN_CENTER | wx.ALL, 5) + sizer_path.Add(self.button_path, 0, wx.ALIGN_CENTER | wx.ALL, 5) + + + + sizer_save = wx.BoxSizer(wx.HORIZONTAL) + + self.buttom_save = wx.Button(self, wx.ID_ANY, u"保存设置", wx.DefaultPosition, wx.DefaultSize, 0) + + sizer_save.Add((0, 0), 1, wx.EXPAND, 5) + sizer_save.Add(self.buttom_save, 0, wx.ALL, 5) + + + global_sizer.Add(sizer_download, 1, wx.EXPAND, 5) + global_sizer.Add(sizer_path, 0, wx.EXPAND, 5) + global_sizer.Add(sizer_save, 0, wx.EXPAND, 5) + + self.SetSizer(global_sizer) + self.Layout() + + self.Centre(wx.BOTH) + self.bindEvent() + + + def bindEvent(self): + self.Bind(wx.EVT_BUTTON, self.event_button_path, self.button_path) + self.Bind(wx.EVT_BUTTON, self.event_button_save, self.buttom_save) + + def event_button_path(self, event): + dlg = wx.DirDialog(self, style=wx.FD_DEFAULT_STYLE) + if dlg.ShowModal() == wx.ID_OK: + self.textctrl_path.SetValue(dlg.GetPath()) + + + def event_button_save(self, event): + cv.FILEPATH = self.textctrl_path.GetValue().strip() + cv.MAX_CONN = self.slider_max_conn.GetValue() + cv.MAX_TASK = self.slider_max_task.GetValue() + cv.BUFFER_SIZE = self.slider_buffsize.GetValue() + cv.BLOCK_SIZE = self.slider_blocksize.GetValue() + settings.saveConfig() + dlg = wx.MessageDialog(self, u'设置保存成功!', u'成功', wx.OK | wx.ICON_INFORMATION) + dlg.ShowModal() + self.Destroy() + + + def __del__(self): + pass + + + + + + + +# app = wx.App() +# # # # frame_main = FrameParser(None) +# +# # DialogSettings(None).Show() +# # # # frame_main.Show() +# app.MainLoop() diff --git a/gui/tool_request.py b/gui/tool_request.py index a0087d1..cf0d2e3 100644 --- a/gui/tool_request.py +++ b/gui/tool_request.py @@ -10,11 +10,11 @@ def __init__(self, parent, title, total_byte, dlm): style=wx.DEFAULT_DIALOG_STYLE) self.SetSizeHints(wx.DefaultSize, wx.DefaultSize) - global_sizer = wx.BoxSizer(wx.VERTICAL) + self.global_sizer = wx.BoxSizer(wx.VERTICAL) self.gauge_progress = wx.Gauge(self, wx.ID_ANY, 10000, wx.DefaultPosition, wx.DefaultSize, wx.GA_HORIZONTAL) self.gauge_progress.SetValue(524) - global_sizer.Add(self.gauge_progress, 0, wx.ALL | wx.EXPAND, 5) + self.global_sizer.Add(self.gauge_progress, 0, wx.ALL | wx.EXPAND, 5) sizer_info = wx.BoxSizer(wx.HORIZONTAL) @@ -33,11 +33,11 @@ def __init__(self, parent, title, total_byte, dlm): sizer_info.Add(self.text_progress, 1, wx.ALIGN_RIGHT | wx.ALL, 5) - global_sizer.Add(sizer_info, 1, wx.EXPAND, 5) + self.global_sizer.Add(sizer_info, 1, wx.EXPAND, 5) - self.SetSizer(global_sizer) + self.SetSizer(self.global_sizer) self.Layout() - global_sizer.Fit(self) + self.global_sizer.Fit(self) self.Centre(wx.BOTH) self.Bind(wx.EVT_CLOSE, self.onClose) @@ -59,7 +59,7 @@ def update(self, cur_byte, total_bytes=0): self.text_progress.SetLabelText(text_progress) self.gauge_progress.SetValue(int(percent*100)) - self.Layout() + self.global_sizer.Layout() def onClose(self, event): dlg = wx.MessageDialog(self, u'你确定要中止下载吗?', u'提示', style=wx.YES_NO | wx.ICON_INFORMATION) diff --git a/handler/downloader.py b/handler/downloader.py index f57bc8e..65a1c73 100644 --- a/handler/downloader.py +++ b/handler/downloader.py @@ -46,7 +46,7 @@ def prepare(self, res): self.video_urls = res.getVideoUrls() self.audio_urls = res.getAudioUrls() - self._title = res.getVideoTitle() + self._title = res.getVideoLegalTitle() self._filenum = len(self.video_urls) self._ext = res.getFileFormat() self._range_format = res.getRangeFormat() @@ -58,6 +58,7 @@ def run(self): def shutdown(self): self.dlm.shutdown() + self.dlm.join() def __run__(self): self.dlm.config(max_task=self.max_task) @@ -66,7 +67,17 @@ def __run__(self): path = os.path.join(self.filepath, self._title) for i, j in enumerate(self.video_filenames): filepath = os.path.join(self.filepath, self._title, j) - if os.path.exists(filepath + '.nbdler') or not os.path.exists(filepath): + if os.path.exists(filepath + '.nbdler') and os.path.exists(filepath): + dl = nbdler.open(filepath) + kwargs = { + 'urls': [*list(self.video_urls[i])] if isinstance(self.video_urls[i], list) or isinstance( + self.video_urls[i], tuple) else [self.video_urls[i]], + 'range_formats': [self._range_format], + 'headers': [self._headers], + } + dl.batchAdd(**kwargs) + self.dlm.addHandler(dl, name=i) + elif os.path.exists(filepath + '.nbdler') or not os.path.exists(filepath): kwargs = { 'filename': j, 'filepath': path, @@ -74,18 +85,30 @@ def __run__(self): 'urls': [*list(self.video_urls[i])] if isinstance(self.video_urls[i], list) or isinstance( self.video_urls[i], tuple) else [self.video_urls[i]], 'range_formats': [self._range_format], - 'headers': [self._headers] + 'headers': [self._headers], + 'buffer_size': cv.BUFFER_SIZE * 1024 * 1024, + 'block_size': cv.BLOCK_SIZE * 1024 } dl = nbdler.open(**kwargs) self.dlm.addHandler(dl, name=i) else: - gui.frame_main.updateBlock(i, gui.COLOR_OK) + gui.frame_downloader.updateBlock(i, gui.COLOR_OK) self._inc_progress += os.path.getsize(filepath) for i, j in enumerate(self.audio_filenames): filepath = os.path.join(self.filepath, self._title, j) - if os.path.exists(filepath + '.nbdler') or not os.path.exists(filepath): + if os.path.exists(filepath + '.nbdler') and os.path.exists(filepath): + dl = nbdler.open(filepath) + kwargs = { + 'urls': [*list(self.video_urls[i])] if isinstance(self.video_urls[i], list) or isinstance( + self.video_urls[i], tuple) else [self.video_urls[i]], + 'range_formats': [self._range_format], + 'headers': [self._headers], + } + dl.batchAdd(**kwargs) + self.dlm.addHandler(dl, name=i) + elif os.path.exists(filepath + '.nbdler') or not os.path.exists(filepath): kwargs = { 'filename': j, 'filepath': path, @@ -93,13 +116,15 @@ def __run__(self): 'urls': [*list(self.audio_urls[i])] if isinstance(self.audio_urls[i], list) or isinstance( self.audio_urls[i], tuple) else [self.audio_urls[i]], 'range_formats': [self._range_format], - 'headers': [self._headers] + 'headers': [self._headers], + 'buffer_size': cv.BUFFER_SIZE * 1024 * 1024, + 'block_size': cv.BLOCK_SIZE * 1024 } dl = nbdler.open(**kwargs) self.dlm.addHandler(dl, name=i) else: - gui.frame_main.updateBlock(len(self.video_filenames) + i, gui.COLOR_OK) + gui.frame_downloader.updateBlock(len(self.video_filenames) + i, gui.COLOR_OK) self._inc_progress += os.path.getsize(filepath) self.dlm.run() @@ -155,33 +180,42 @@ def is_all_files_done(self): return False def insert_new_item(self, run_queue): - new = list(filter(lambda x: self.dlm.getNameFromId(x) not in gui.frame_main.getItemsDict(), + new = list(filter(lambda x: self.dlm.getNameFromId(x) not in gui.frame_downloader.getItemsDict(), run_queue)) for i in new: dl = self.dlm.getHandler(id=i) size = dl.getFileSize() - gui.frame_main.insertItem(self.dlm.getNameFromId(i), size) - gui.frame_main.updateBlock(self.dlm.getNameFromId(i), gui.COLOR_RUN) + cur_name = self.dlm.getNameFromId(i) + gui.frame_downloader.insertItem(cur_name, size) + gui.frame_downloader.updateBlock(cur_name, gui.COLOR_RUN) + + if new: + gui.frame_downloader.Layout() + # gui.frame_main.sizer_items.Layout() def delete_end_item(self, done_queue): end = list(filter(lambda x: self.dlm.getIdFromName(x) in done_queue, - gui.frame_main.getItemsDict())) + gui.frame_downloader.getItemsDict())) for i in end: dl = self.dlm.getHandler(name=i) size = dl.getFileSize() - item = gui.frame_main.getItem(i) + item = gui.frame_downloader.getItem(i) item.update(size, dl.getInsSpeed(), size) - gui.frame_main.deleteItem(i, True if len(gui.frame_main.getItemsDict()) > self.max_task else False) - gui.frame_main.updateBlock(i, gui.COLOR_OK) + gui.frame_downloader.deleteItem(i, True if len(gui.frame_downloader.getItemsDict()) > self.max_task else False) + gui.frame_downloader.updateBlock(i, gui.COLOR_OK) + + if end: + gui.frame_downloader.Layout() + # gui.frame_main.sizer_items.Layout() def update_item(self, run_queue): for i in run_queue: dl = self.dlm.getHandler(id=i) inc_byte = dl.getIncByte() size = dl.getFileSize() - item = gui.frame_main.getItem(self.dlm.getNameFromId(i)) + item = gui.frame_downloader.getItem(self.dlm.getNameFromId(i)) if item: item.update(inc_byte, dl.getInsSpeed(), size) @@ -194,7 +228,7 @@ def update_total(self, run_queue, done_queue): dl = self.dlm.getHandler(id=i) cur_inc += dl.getIncByte() - gui.frame_main.updateTotal(cur_inc + self._inc_progress, self.dlm.getInsSpeed()) + gui.frame_downloader.updateTotal(cur_inc + self._inc_progress, self.dlm.getInsSpeed()) def process_event(self, event): done_queue = self.dlm.getDoneQueue() @@ -207,6 +241,8 @@ def process_event(self, event): self.update_total(run_queue, done_queue) + # gui.frame_main.Layout() + if not run_queue: if self.is_all_files_done(): gui.stopTimer() @@ -242,7 +278,8 @@ def isAllDone(): def shutdown(): global HANDLER, SHUTDOWN SHUTDOWN = True - HANDLER.shutdown() + if HANDLER: + HANDLER.shutdown() def getProcessEvent(): diff --git a/handler/merger.py b/handler/merger.py index 5c8397b..bea80ec 100644 --- a/handler/merger.py +++ b/handler/merger.py @@ -5,7 +5,9 @@ import subprocess import re import wx -from gui.merger_output import MergerOutputAppendEvent +from gui.frame_merger import MergerOutputAppendEvent, MergerOutputUpdateEvent +import signal + MERGER_SIMPLE = 'simple' MERGER_FFMPEG = 'ffmpeg' @@ -22,7 +24,7 @@ SHUTDOWN = False class SimpleBinMerger(threading.Thread): - def __init__(self, dst, src): + def __init__(self, dst, src, *args): threading.Thread.__init__(self) self.dst = dst self.src = src @@ -47,14 +49,18 @@ def getDest(self): def _progressthread(self): global SHUTDOWN while True: - gui.frame_main.updateMerge(self.current) + gui.frame_downloader.updateMerge(self.current) time.sleep(0.05) if self.current == self.total: - gui.frame_main.updateMerge(self.current) + gui.frame_downloader.updateMerge(self.current) break if SHUTDOWN: break + def shutdown(self): + self.join() + + class CustomMethod: def __init__(self, method, format_set): @@ -81,88 +87,112 @@ def __init__(self, dst, src, method): self.stdout = None self.stderr = None + self.proc = None + def run(self): if self.method == MET_MERGE_VIDEO_AUDIO: self.make_video_audio_merge() + elif self.method == MET_CONCAT: + self.make_concat() else: self.convert_mp4() def customMethod(self): cmdline = self.method.getCMDLine() - proc = subprocess.Popen(cmdline, stdin=subprocess.PIPE, stdout=subprocess.PIPE, shell=True, - stderr=subprocess.PIPE) - proc.stdin.close() - - self.stdout = io.TextIOWrapper( - proc.stdout, - encoding='utf-8' - ) - # somehow message comes out from stderr while not stdout - - self.stderr = io.TextIOWrapper( - proc.stderr, - encoding='utf-8' - ) + self.pipe_open(cmdline) def convert_mp4(self): + cmdline = '"{ffmpeg_path}" -i "{src}" -i "{audio}" -vcodec copy -acodec copy "{output}"' cmdline = '"ffmpeg.exe" -i "out.mp4" -c:v libx264 "out1.mp4"' - proc = subprocess.Popen(cmdline, stdin=subprocess.PIPE, stdout=subprocess.PIPE, shell=True, - stderr=subprocess.PIPE) - - self.stdout = io.TextIOWrapper( - proc.stdout, - encoding='utf-8' - ) - - self.stderr = io.TextIOWrapper( - proc.stderr, - encoding='utf-8' - ) - - proc.stdin.close() - self._stdout_buff = [] - stdout_thr = threading.Thread(target=self._readerthread, args=(self.stdout, self._stdout_buff), daemon=True) - stdout_thr.start() - - self._stderr_buff = [] - stderr_thr = threading.Thread(target=self._readerthread, args=(self.stderr, self._stderr_buff), daemon=True) - stderr_thr.start() - - self.handle_output(self._stderr_buff, stderr_thr) + self.pipe_open(cmdline) def handle_output(self, _buff, _thread): - # rex = re.compile( - # 'frame=\s*\s*(.*?)\s*\s*fps=\s*\s*(.*?)\s*\s*q=\s*\s*(.*?)\s*size=\s*(.*?)\s*time=\s*(.*?)\s*bitrate=\s*(.*?)\s*speed=\s*(.*)') - + rex_prog = re.compile( + 'frame=\s*\s*(.*?)\s*\s*' + 'fps=\s*\s*(.*?)\s*\s*' + 'q=\s*\s*(.*?)\s*' + 'size=\s*(.*?)\s*' + 'time=\s*(.*?)\s*' + 'bitrate=\s*(.*?)\s*' + 'speed=\s*(.*)') + rex_done = re.compile( + 'video:\s*\s*(.*?)\s*\s*' + 'audio:\s*\s*(.*?)\s*\s*' + 'subtitle:\s*\s*(.*?)\s*' + 'other streams:\s*(.*?)\s*' + 'global headers:\s*(.*?)\s*' + 'muxing overhead:\s*(.*)') + + total_len = cv.SEL_RES.getVideoTimeLength() + start_time = time.time() + # last_len = 0 next_cur = 0 + non_monotonous_counter = 0 while True: + if cv.SHUTDOWN: + break if len(_buff) > next_cur: text_line = _buff[next_cur] - wx.PostEvent(gui.frame_merger.output, MergerOutputAppendEvent(text_line)) + if 'Non-monotonous DTS in output stream' in text_line: + non_monotonous_counter += 1 + if non_monotonous_counter <= 1: + wx.PostEvent(gui.frame_merger.textctrl_output, MergerOutputAppendEvent(text_line)) + + else: + if non_monotonous_counter > 1: + msg = '*** 以上忽略(%d)条连续Non-monotonous信息, 视频可能存在不完整错误! ***\n' % (non_monotonous_counter - 1) + wx.PostEvent(gui.frame_merger.textctrl_output, MergerOutputAppendEvent(msg)) + non_monotonous_counter = 0 + wx.PostEvent(gui.frame_merger.textctrl_output, MergerOutputAppendEvent(text_line)) + time.sleep(0.01) + + res = rex_prog.search(text_line) + if res: + + tm = time.strptime(res.group(5), '%H:%M:%S.%y') + cur_len = (tm.tm_hour * 60 * 60 + tm.tm_min * 60 + tm.tm_sec) * 1000 + int(str(tm.tm_year)[2:]) * 10 + + cur_byte_str = res.group(4) + remain = (total_len - cur_len) / (cur_len / (time.time() - start_time)) + hour = int(remain / 60 / 60) + minute = int((remain % (60*60)) / 60) + second = int(remain % 60) + remain_time_str = '%02d:%02d:%02d' % (hour, minute, second) + wx.PostEvent(gui.frame_merger.gauge_progress, + MergerOutputUpdateEvent(cur_len=cur_len, total_len=total_len, + cur_byte_str=cur_byte_str, remain_time_str=remain_time_str)) + + else: + res = rex_done.search(text_line) + if res: + wx.PostEvent(gui.frame_merger.gauge_progress, + MergerOutputUpdateEvent(cur_len=total_len, total_len=total_len, cur_byte_str=str( + round(os.path.getsize(self.dst) / 1024)) + 'kb', remain_time_str='00:00:00')) + next_cur += 1 else: if not _thread.isAlive(): break - time.sleep(0.01) + time.sleep(0.01) - def make_video_audio_merge(self): - cmdline = '"{ffmpeg_path}" -i "{video}" -i "{audio}" -vcodec copy -acodec copy "{output}"' - cmdline = cmdline.format(video=self.src[0], audio=self.src[1], output=self.dst, ffmpeg_path=cv.FFMPEG_PATH) - proc = subprocess.Popen(cmdline, stdin=subprocess.PIPE, stdout=subprocess.PIPE, shell=True, + pass + + def pipe_open(self, cmdline): + self.proc = subprocess.Popen(cmdline, stdin=subprocess.PIPE, stdout=subprocess.PIPE, shell=True, stderr=subprocess.PIPE) - proc.stdin.close() + self.proc.stdin.close() self.stdout = io.TextIOWrapper( - proc.stdout, + self.proc.stdout, encoding='utf-8' ) self.stderr = io.TextIOWrapper( - proc.stderr, + self.proc.stderr, encoding='utf-8' ) @@ -176,6 +206,17 @@ def make_video_audio_merge(self): self.handle_output(self._stderr_buff, stderr_thr) + def make_video_audio_merge(self): + cmdline = '"{ffmpeg_path}" -i "{video}" -i "{audio}" -vcodec copy -acodec copy "{output}"' + cmdline = cmdline.format(video=self.src[0], audio=self.src[1], output=self.dst, ffmpeg_path=cv.FFMPEG_PATH) + self.pipe_open(cmdline) + + def make_concat(self): + videos = '|'.join([i for i in self.src]) + cmdline = '"{ffmpeg_path}" -i concat:"{videos}" -c copy "{output}"' + cmdline = cmdline.format(videos=videos, output=self.dst, ffmpeg_path=cv.FFMPEG_PATH) + self.pipe_open(cmdline) + def getSource(self): return self.src @@ -190,6 +231,18 @@ def _readerthread(self, fh, buffer): buffer.append(out) fh.close() + def shutdown(self): + cmdline = 'taskkill /pid {pid} -t -f'.format(pid=self.proc.pid) + proc = subprocess.Popen(cmdline, stdin=subprocess.PIPE, stdout=subprocess.PIPE, shell=True, + stderr=subprocess.PIPE) + a = proc.communicate() + # os.kill(0, signal.SIGQUIT) + # signal.SIGQUIT + # self.proc.send_signal(signal.CTRL_C_EVENT) + self.proc.kill() + self.proc.terminate() + + MERGER = { 'ffmpeg': Ffmpeg, @@ -202,15 +255,11 @@ def _readerthread(self, fh, buffer): -def make(dst, src, method): +def make(dst, src, method, merger='ffmpeg'): global MER_TASK if method == MET_CONCAT: - sel_merger = MERGER['simple'] - task = sel_merger(dst, src) - - wx.CallAfter(gui.frame_main.initTotal_Merge, len(src)) - - threading.Thread(target=task._progressthread).start() + sel_merger = MERGER[merger] + task = sel_merger(dst, src, method) elif method == MET_MERGE_VIDEO_AUDIO: sel_merger = MERGER['ffmpeg'] task = sel_merger(dst, src, MET_MERGE_VIDEO_AUDIO) @@ -218,14 +267,16 @@ def make(dst, src, method): sel_merger = MERGER['ffmpeg'] task = sel_merger(dst, src, method) MER_TASK.append(task) - + # wx.CallAfter(gui.frame_main.initTotal_Merge, len(src)) return task def shutdown(): global SHUTDOWN - join() SHUTDOWN = True + for i in MER_TASK: + i.shutdown() + join() def isClosed(): global SHUTDOWN @@ -237,7 +288,7 @@ def del_src_files(): for j in i.getSource(): os.remove(os.path.join(cv.FILEPATH, j).lstrip('/').lstrip('\\')) - os.removedirs(os.path.join(cv.FILEPATH, cv.SEL_RES.getVideoTitle())) + os.removedirs(os.path.join(cv.FILEPATH, cv.SEL_RES.getVideoLegalTitle())) def join(): diff --git a/handler/settings.py b/handler/settings.py index 769e793..66f5300 100644 --- a/handler/settings.py +++ b/handler/settings.py @@ -38,6 +38,8 @@ def saveConfig(): config.set('Settings', 'max_task_num', str(cv.MAX_TASK)) config.set('Settings', 'max_thread_num', str(cv.MAX_CONN)) + config.set('Settings', 'buffer_size', str(cv.BUFFER_SIZE)) + config.set('Settings', 'block_size', str(cv.BLOCK_SIZE)) config.set('Settings', 'last_save_path', str(cv.FILEPATH)) config.set('Settings', 'undone_job', str(cv.UNDONE_JOB)) config.set('Settings', 'ffmpeg', str(cv.FFMPEG_PATH)) @@ -56,6 +58,8 @@ def __loadConfig__(): try: cv.MAX_TASK = int(config.get('Settings', 'max_task_num')) cv.MAX_CONN = int(config.get('Settings', 'max_thread_num')) + cv.BUFFER_SIZE = int(config.get('Settings', 'buffer_size')) + cv.BLOCK_SIZE = int(config.get('Settings', 'block_size')) cv.FILEPATH = config.get('Settings', 'last_save_path') undonejob = config.get('Settings', 'undone_job') cv.FFMPEG_PATH = config.get('Settings', 'ffmpeg') @@ -67,5 +71,5 @@ def __loadConfig__(): def loadConfig(): __loadConfig__() - gui.frame_parse.textctrl_path.SetValue(cv.FILEPATH) + # gui.frame_parse.textctrl_path.SetValue(cv.FILEPATH) diff --git a/main.py b/main.py index 40b03f3..cac754f 100644 --- a/main.py +++ b/main.py @@ -9,13 +9,11 @@ import gui -import wx +import wx, time, sys import GUIEventBinder import socket import threading import flow -import sys -import time import handler socket.setdefaulttimeout(3) diff --git a/nbdler/DLHandler.py b/nbdler/DLHandler.py index 2c03b31..b75a764 100644 --- a/nbdler/DLHandler.py +++ b/nbdler/DLHandler.py @@ -253,7 +253,7 @@ def shutdown(self): def __packet_params__(self): - return ['url', 'file', '__auto_global__'] + return ['url', 'file', '__globalprog__'] def getFileName(self): diff --git a/nbdler/DLInfos.py b/nbdler/DLInfos.py index c55f122..cfbc89b 100644 --- a/nbdler/DLInfos.py +++ b/nbdler/DLInfos.py @@ -87,6 +87,8 @@ def addNode(self, id=-1, url='', cookie='', headers=HEADERS_CHROME, id = self.newID() urlobj = Url(id, url, cookie, headers, host, port, path, protocol, proxy, max_thread, range_format) + if urlobj in self.dict.values(): + return self.list.append(urlobj) self.dict[id] = urlobj @@ -124,6 +126,8 @@ def getNextId(self, cur_id): next_id = 0 if self.id_map[next_id]: break + else: + next_id += 1 return next_id def getAllUrl(self): @@ -132,8 +136,8 @@ def getAllUrl(self): def getUrl(self, Urlid): return self.dict[Urlid] - def hasUrl(self, url): - return url in self.dict.keys() + def hasUrl(self, Url): + return Url in self.dict.keys() def newID(self): for i, j in enumerate(self.id_map): @@ -165,7 +169,7 @@ def getFileName(self, index=0): def __packet_params__(self): - return ['dict', 'id_map', 'max_conn', 'max_speed'] + return ['list', 'dict', 'id_map', 'max_conn', 'max_speed'] def unpack(self, packet): Packer.unpack(self, packet) @@ -174,6 +178,8 @@ def unpack(self, packet): url = Url(-1, '') url.unpack(j) self.dict[i] = url + # for i, j in enumerate(self.list[:]): + # self.list[i] = j class Target(object): @@ -244,6 +250,14 @@ def __init__(self, id, url, cookie='', headers=HEADERS_CHROME, self.range_format = range_format + def __eq__(self, other): + if isinstance(other, Url): + return self.url == other.url and \ + self.cookie == other.cookie and \ + self.proxy == other.proxy and \ + self.range_format == other.range_format + else: + object.__eq__(self, other) def config(self): pass @@ -323,7 +337,7 @@ def __request__(self): res = opener.open(req) break except Exception as e: - traceback.print_exc() + # traceback.print_exc() error_counter += 1 time.sleep(0.5) else: diff --git a/nbdler/DLProcessor.py b/nbdler/DLProcessor.py index dfe921f..d899ab4 100644 --- a/nbdler/DLProcessor.py +++ b/nbdler/DLProcessor.py @@ -43,8 +43,8 @@ class ErrorCounter(object): _404__THRESHOLD = 5 _302__THRESHOLD = 20 - RECV_TIMEOUT_THRESHOLD = 5 - SOCKET_ERROR_THRESHOLD = 5 + RECV_TIMEOUT_THRESHOLD = 10 + SOCKET_ERROR_THRESHOLD = 10 def __init__(self): self._404_ = 0 @@ -182,13 +182,9 @@ def __getdata__(self): sock, buff = self.makeSocket() if not sock: - # msg = 'SocketNotBuilt: ->rerun.' - # extra = {'progress': '%-10d-%10d' % (self.progress.begin, self.progress.end), - # 'urlid': self.urlid} - # logger.warning(msg, extra=extra) self.error_counter.socket_error += 1 - time.sleep(2) + time.sleep(0.5) self.run() return else: @@ -202,8 +198,12 @@ def __getdata__(self): self.__206__(sock, buff) elif status == 302: self.__302__(sock) + elif status == 405: + self.__405__(sock) elif status == 404: self.__404__(sock) + # elif status == 416: + # self.__416__(sock) elif status != 206 and status != 200: self.__404__(sock) @@ -224,7 +224,7 @@ def makeSocket(self): if self.target.protocol == 'https': sock = ssl.wrap_socket(socket.socket()) - # ssl.SSLError: [SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failure (_ssl.c:646) + # ssl.SSLError: [SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failure sock.server_hostname = self.target.host elif self.target.protocol == 'http': sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -278,15 +278,29 @@ def __200__(self, sock, buff): self.progress.go(len(buff)) self.__recv_loop__(sock, buff) + def __405__(self, sock): + self.getSwitch() + time.sleep(0.1) + self.run() def __404__(self, sock): self.error_counter._404_ += 1 + if self.error_counter._404_ > 3: + self.url.reload() - self.url.reload() - - time.sleep(2) + time.sleep(0.3) self.run() + def __other__(self, sock): + pass + + + def __416__(self, sock): + self.error_counter._404_ += 1 + self.progress.go_in = 0 + self.progress.done_inc = 0 + time.sleep(0.1) + self.run() def __recv_loop__(self, sock, buff): @@ -444,15 +458,12 @@ def cutRequest(self, Range): self.opareq.cut = [Range[0], Range[1]] - # msg = 'CutRequest: %010d-%010d' % (Range[0], Range[1]) - # extra = {'progress': '%-10d-%10d' % (self.progress.begin, self.progress.end), - # 'urlid': self.urlid} - # logger.info(msg, extra=extra) while True: - if (self.isReady() and not self.isRunning() and + if self.isEnd() or ((self.isReady() and not self.isRunning() and not self.getHandler().thrpool.getThreadsFromName('Nbdler-SelfCheck')) or \ - not self.opareq.cut: + not self.opareq.cut): break + time.sleep(0.1) return [self.progress.end, last_range[1]] if last_range[1] != self.progress.end else [] @@ -470,10 +481,6 @@ def getCut(self): if retrange: self.progress.globalprog.cut(self.progress, retrange) - # msg = 'GetCut: %010d-%010d' % (self.opareq.cut[0], self.opareq.cut[1]) - # extra = {'progress': '%-10d-%10d' % (self.progress.begin, self.progress.end), - # 'urlid': self.urlid} - # logger.info(msg, extra=extra) self.opareq.cut = [] diff --git a/nbdler/DLProgress.py b/nbdler/DLProgress.py index a454c29..4c4f9ba 100644 --- a/nbdler/DLProgress.py +++ b/nbdler/DLProgress.py @@ -112,16 +112,19 @@ def __setattr__(self, key, value): self.length = getattr(self, 'end', 0) - getattr(self, 'begin', 0) elif key == 'go_inc': if getattr(self, 'status', None): + # if self.go_inc > self.length: + # raise Exception('ProgressGoExceed') if self.go_inc > self.length: - raise Exception('ProgressGoExceed') - elif self.go_inc == self.length: - self.endGo() + self.go_inc = self.length + if self.go_inc == self.length: + self.endGo() else: self.status.go_end = False elif key == 'done_inc': if getattr(self, 'status', None): if self.done_inc > self.length: - raise Exception('ProgressDoneExceed') + self.done_inc = self.length + # raise Exception('ProgressDoneExceed') elif self.done_inc == self.length: self.endDone() else: @@ -510,7 +513,7 @@ def checkBuffer(self, bytelen): def save(self): if not self.__packet_frame__: self.__packet_frame__ = self.handler.pack() - self.__packet_frame__['__auto_global__'] = self.pack() + self.__packet_frame__['__globalprog__'] = self.pack() packet = zlib.compress(str.encode(str(self.__packet_frame__)), zlib.Z_BEST_COMPRESSION) diff --git a/nbdler/README.MD b/nbdler/README.MD index f36f39f..a6109f3 100644 --- a/nbdler/README.MD +++ b/nbdler/README.MD @@ -17,6 +17,9 @@ * 自动分片控制管理。 ## 更新说明 +* **2019/04/28** + * 修复断点续传功能无法使用问题。 + * **2019/04/25** * 添加方法:线程阻塞join,安全关闭线程shutdown。 * 支持多线程字段自定义,可通过参数range_format控制,以适应多种多线程方式。(默认: Range: bytes=%d-%d)