├── .DS_Store ├── .gitignore ├── EveryDay ├── .DS_Store ├── AboutAI.py ├── AboutEveryDay.py ├── AboutJoke.py ├── AboutTime.py ├── AboutWeather.py ├── raw │ └── city.json └── utils │ ├── .DS_Store │ ├── chat_utils.py │ └── date_utils.py ├── LICENSE ├── Python执行JS总结 ├── .idea │ ├── exec_js_summary.iml │ ├── misc.xml │ ├── modules.xml │ └── workspace.xml ├── js2py_demo.py ├── js_code.py ├── node_demo.py ├── norm.js ├── norm1.js ├── py_exec_js_demo.py └── py_v8_demo.py ├── README.md ├── android ├── .DS_Store ├── appium_demo │ ├── config_nantian.py │ └── nantian_keyboard.py ├── delete_build.py ├── replace_apk_resource │ ├── fileutils.py │ ├── readme.md │ ├── replace_apk_resource.py │ └── ziputils.py └── replace_apk_resource_pro │ ├── .DS_Store │ ├── apktool.jar │ ├── file_utils.py │ ├── jarsigner │ ├── logo_white.png │ ├── readme.md │ └── replace_source.py ├── raw └── qr.jpeg ├── video_auto ├── .DS_Store ├── 二次剪辑 │ ├── .DS_Store │ ├── .idea │ │ ├── misc.xml │ │ ├── modules.xml │ │ ├── vcs.xml │ │ ├── workspace.xml │ │ └── 视频剪辑.iml │ ├── fonts │ │ ├── .DS_Store │ │ └── STHeiti Medium.ttc │ ├── source │ │ └── .DS_Store │ ├── util │ │ ├── .DS_Store │ │ ├── file_utils.py │ │ ├── img_utils.py │ │ └── video_utils.py │ └── 二次创作.py ├── 制作GIF视频 │ ├── .DS_Store │ ├── gif_make_video.py │ ├── gifs │ │ └── .DS_Store │ └── utils │ │ ├── file_utils.py │ │ ├── image_utils.py │ │ ├── math_utils.py │ │ └── video_utils.py └── 卡点视频 │ ├── .DS_Store │ ├── images │ └── .DS_Store │ ├── readme.md │ ├── utils │ ├── .DS_Store │ ├── file_utils.py │ └── image_utils.py │ └── video_cut_cv2.py ├── 一键升级 ├── .DS_Store ├── auto_rar.au3 └── auto_rar.exe ├── 发送邮件 ├── .idea │ ├── misc.xml │ ├── modules.xml │ ├── vcs.xml │ ├── workspace.xml │ └── 发送邮件.iml ├── attachments │ └── report.png ├── email_by_smtplib.py ├── email_by_yagmail.py └── email_by_zmail.py ├── 图片视频九宫格 ├── .idea │ ├── inspectionProfiles │ │ └── Project_Default.xml │ ├── misc.xml │ ├── modules.xml │ ├── vcs.xml │ ├── workspace.xml │ └── 图片视频九宫格.iml ├── ninecell_image.py ├── ninecell_video.py ├── output │ └── result.mp4 ├── raw │ ├── pic.jpg │ └── 至今无人超越的3个动作.mp4 └── utils │ ├── file_utils.py │ ├── math_utils.py │ └── video_utils.py ├── 截图快捷翻译 ├── .idea │ ├── misc.xml │ ├── modules.xml │ ├── vcs.xml │ ├── workspace.xml │ └── 截图快捷翻译.iml ├── main.py └── trans.py ├── 批处理脚本 ├── del_all_build.bat ├── del_system_gc.bat ├── documents_classifying.bat ├── raw │ └── Bat_To_Exe_Converter.exe ├── run_python_loop.bat └── 提交代码 │ └── 提交代码.bat ├── 批量生成手机号码 ├── .DS_Store ├── generate_area_phone_util.py └── utils │ ├── .DS_Store │ └── addr_utils.py ├── 朋友圈防折叠 ├── .DS_Store ├── NoCollapseInput │ ├── .gitignore │ ├── .idea │ │ ├── codeStyles │ │ │ └── Project.xml │ │ ├── gradle.xml │ │ ├── misc.xml │ │ └── runConfigurations.xml │ ├── .project │ ├── .settings │ │ └── org.eclipse.buildship.core.prefs │ ├── .vscode │ │ └── settings.json │ ├── app │ │ ├── .classpath │ │ ├── .gitignore │ │ ├── .project │ │ ├── .settings │ │ │ └── org.eclipse.buildship.core.prefs │ │ ├── build.gradle │ │ ├── proguard-rules.pro │ │ └── src │ │ │ ├── androidTest │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── xingag │ │ │ │ └── preventrubbishapp │ │ │ │ └── ExampleInstrumentedTest.java │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ │ └── com │ │ │ │ │ └── xingag │ │ │ │ │ ├── preventrubbishapp │ │ │ │ │ ├── App.java │ │ │ │ │ └── MainActivity.java │ │ │ │ │ ├── service │ │ │ │ │ ├── BaseService.java │ │ │ │ │ └── PreventService.java │ │ │ │ │ └── utils │ │ │ │ │ ├── AccessibilityUtil.java │ │ │ │ │ └── AppUtil.java │ │ │ └── res │ │ │ │ ├── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ │ ├── drawable │ │ │ │ ├── btn_bg.xml │ │ │ │ └── ic_launcher_background.xml │ │ │ │ ├── layout │ │ │ │ ├── activity_main.xml │ │ │ │ ├── dialog_bottom_alertmsg.xml │ │ │ │ ├── item_app_select.xml │ │ │ │ └── title_bar.xml │ │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_back.png │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_sure.png │ │ │ │ ├── values │ │ │ │ ├── colors.xml │ │ │ │ ├── strings.xml │ │ │ │ └── styles.xml │ │ │ │ └── xml │ │ │ │ └── accessibility_settings.xml │ │ │ └── test │ │ │ └── java │ │ │ └── com │ │ │ └── xingag │ │ │ └── preventrubbishapp │ │ │ └── ExampleUnitTest.java │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── readme.md │ └── settings.gradle └── 防折叠服务.apk ├── 视频特殊处理.py └── 音频处理 └── 音频尾部处理.py /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xingag/tools_python/f01d315c46a619828d02ed9327f4264ba3a382d8/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | .DS_Store 106 | -------------------------------------------------------------------------------- /EveryDay/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xingag/tools_python/f01d315c46a619828d02ed9327f4264ba3a382d8/EveryDay/.DS_Store -------------------------------------------------------------------------------- /EveryDay/AboutAI.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: v1.0 6 | @author: xag 7 | @license: Apache Licence 8 | @contact: xinganguo@gmail.com 9 | @site: http://www.xingag.top 10 | @software: PyCharm 11 | @file: About_AI.py 12 | @time: 4/18/19 19:55 13 | @description:百度AI语音合成 14 | """ 15 | 16 | # 依赖:pip3 install baidu-aip 17 | 18 | from aip import AipSpeech 19 | 20 | """ 你的 APPID AK SK """ 21 | APP_ID = '你的APP_ID' 22 | API_KEY = '你的API_KEY' 23 | SECRET_KEY = '你的SECRET_KEY' 24 | 25 | client = AipSpeech(APP_ID, API_KEY, SECRET_KEY) 26 | 27 | 28 | def gene_mp3(content, filename): 29 | # 百度声音 30 | result = client.synthesis(content, 'zh', 1, { 31 | 'vol': 7, 32 | 'spd': 4, 33 | 'per': 4, 34 | 35 | }) 36 | 37 | # 识别正确返回语音二进制 错误则返回dict 参照下面错误码 38 | if not isinstance(result, dict): 39 | with open('%s.mp3' % filename, 'wb') as f: 40 | f.write(result) 41 | -------------------------------------------------------------------------------- /EveryDay/AboutEveryDay.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: v1.0 6 | @author: xag 7 | @license: Apache Licence 8 | @contact: xinganguo@gmail.com 9 | @site: http://www.xingag.top 10 | @software: PyCharm 11 | @file: About_EveryDay.py 12 | @time: 4/18/19 19:50 13 | @description:每天运行 14 | """ 15 | 16 | from AboutTime import * 17 | from AboutWeather import * 18 | from AboutAI import * 19 | from AboutJoke import * 20 | import itchat 21 | from utils.chat_utils import * 22 | from utils.date_utils import * 23 | import schedule 24 | 25 | 26 | # 依赖: pip3 install schedule 27 | 28 | 29 | class EveryDay(object): 30 | def __init__(self): 31 | self.time_start = '' 32 | self.time_end = '' 33 | self.weather = '' 34 | 35 | # mp3名称 36 | self.file_name = 'everyday_%s' % get_today_ymd() 37 | 38 | def start(self): 39 | self.time_start, self.time_end = get_time() 40 | self.weather = get_weather() 41 | 42 | self.__run() 43 | 44 | def __run(self): 45 | # 最后要发送的内容 46 | result_word = self.time_start + "\n\n" + self.weather + "\n" + self.time_end 47 | print(result_word) 48 | 49 | # 语音内容 50 | result_mp3 = ( 51 | self.time_start + "。\n\n" + self.weather + "\n" + self.time_end + "你们工作吧,我要休息了!我们明天再见!拜拜~").replace( 52 | ',', '。') 53 | 54 | gene_mp3(result_mp3, self.file_name) 55 | 56 | # self.send_msg(result_word) 57 | 58 | self.send_word('微信上某位联系人', result_word) 59 | 60 | self.send_file('微信上某位联系人') 61 | 62 | def send_msg(self, result_word): 63 | # =================================================== 64 | target_peoples = [ 65 | '微信上某位联系人', 66 | '微信上某位联系人2' 67 | ] 68 | 69 | for target in target_peoples: 70 | send_word_to_person(target, result_word) 71 | send_file_to_person(target, '%s.mp3' % self.file_name) 72 | 73 | print('单聊信息发送完成') 74 | 75 | # =================================================== 76 | target_group_names = [ 77 | '微信上某个群聊1', 78 | '微信上某个群聊2', 79 | '微信上某个群聊3', 80 | '微信上某个群聊4' 81 | ] 82 | 83 | for target2 in target_group_names: 84 | send_word_to_group(target2, result_word) 85 | send_file_to_group(target2, '%s.mp3' % self.file_name) 86 | 87 | print('全部内容发送完成!') 88 | 89 | def send_word(self, target_name, result_word): 90 | """ 91 | 发送文字 92 | :param result_word: 93 | :return: 94 | """ 95 | send_word_to_person(target_name, result_word) 96 | 97 | def send_file(self, target_name): 98 | """ 99 | 发送文件 100 | :return: 101 | """ 102 | send_file_to_person(target_name, '%s.mp3' % self.file_name) 103 | 104 | 105 | if __name__ == '__main__': 106 | every_day = EveryDay() 107 | 108 | # 准备调用itchat发送图片 109 | itchat.auto_login(hotReload=True) 110 | # itchat.auto_login(hotReload=True,enableCmdQR=2) 111 | 112 | # 每天早上8:00执行 113 | # 发送Norm信息【一天一次】 114 | schedule.every().day.at("08:00").do(every_day.start) 115 | 116 | # 每隔3分钟执行一次 117 | # schedule.every(3).minutes.do(every_day.start) 118 | 119 | # 分段发几个笑话【时间段:整点发送】 120 | target_name = '微信发送的对象' 121 | schedule.every().day.at("09:30").do(every_day.send_word, target_name, gene_joke(0)) 122 | schedule.every().day.at("10:30").do(every_day.send_word, target_name, gene_joke(1)) 123 | schedule.every().day.at("11:30").do(every_day.send_word, target_name, gene_joke(2)) 124 | schedule.every().day.at("12:30").do(every_day.send_word, target_name, gene_joke(3)) 125 | schedule.every().day.at("13:30").do(every_day.send_word, target_name, gene_joke(4)) 126 | schedule.every().day.at("14:30").do(every_day.send_word, target_name, gene_joke(5)) 127 | schedule.every().day.at("15:30").do(every_day.send_word, target_name, gene_joke(6)) 128 | schedule.every().day.at("16:30").do(every_day.send_word, target_name, gene_joke(7)) 129 | schedule.every().day.at("17:30").do(every_day.send_word, target_name, gene_joke(8)) 130 | schedule.every().day.at("18:30").do(every_day.send_word, target_name, gene_joke(9)) 131 | schedule.every().day.at("19:30").do(every_day.send_word, target_name, gene_joke(10)) 132 | schedule.every().day.at("20:30").do(every_day.send_word, target_name, gene_joke(11)) 133 | schedule.every().day.at("21:30").do(every_day.send_word, target_name, gene_joke(12)) 134 | schedule.every().day.at("22:30").do(every_day.send_word, target_name, gene_joke(13)) 135 | schedule.every().day.at("23:30").do(every_day.send_word, target_name, gene_joke(14)) 136 | 137 | while True: 138 | schedule.run_pending() 139 | time.sleep(1) 140 | -------------------------------------------------------------------------------- /EveryDay/AboutJoke.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: v1.0 6 | @author: xag 7 | @license: Apache Licence 8 | @contact: xinganguo@gmail.com 9 | @site: http://www.xingag.top 10 | @software: PyCharm 11 | @file: AboutJoke.py 12 | @time: 4/19/19 16:30 13 | @description:TODO 14 | """ 15 | 16 | import requests 17 | from random import randint 18 | import re 19 | import schedule 20 | import time 21 | 22 | 23 | def gene_joke(index): 24 | joke_url = 'http://api.laifudao.com/open/xiaohua.json' 25 | 26 | resp = requests.get(joke_url) 27 | 28 | resp_content = resp.json() 29 | 30 | # 每天20个笑话 31 | # index = randint(0, len(resp_content)) 32 | 33 | joke_random = resp_content[index] 34 | 35 | # 标题 36 | joke_title = joke_random['title'] 37 | 38 | # 笑话 39 | joke_content = re.sub('/*

', r'\n', joke_random['content']) 40 | 41 | # print(joke_content) 42 | 43 | return "***某某童鞋,你家老公给你准备了笑话一则***\n"+joke_content.strip() 44 | 45 | -------------------------------------------------------------------------------- /EveryDay/AboutTime.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: v1.0 6 | @author: xag 7 | @license: Apache Licence 8 | @contact: xinganguo@gmail.com 9 | @site: http://www.xingag.top 10 | @software: PyCharm 11 | @file: AboutTime.py 12 | @time: 4/18/19 19:07 13 | @description:关于时间 14 | """ 15 | 16 | import time 17 | import calendar 18 | 19 | week = ['日', '一', '二', '三', '四', '五', '六'] 20 | 21 | 22 | def get_time(): 23 | """ 24 | 获取时间 25 | :return: 26 | """ 27 | # time.strftime("%F-%u-%j") 28 | # F:年月日;u:星期几;j:一年第几天 29 | 30 | # 时间数据 31 | # [2019, 4, 18, 4, 108] 32 | time_datas = [int(i) for i in time.strftime("%F-%u-%j").split('-')] 33 | 34 | # 年 35 | year = time_datas[0] 36 | # 月 37 | month = time_datas[1] 38 | # 日 39 | day = time_datas[2] 40 | # 星期几 41 | week_d = week[time_datas[3]] 42 | # 一年中的第几天 43 | time_d = time_datas[4] 44 | 45 | # 判断是否是闰年;闰年:366天,平年:365 46 | if calendar.isleap(year): 47 | percent = round(time_d * 100 / 366, 2) 48 | else: 49 | percent = round(time_d * 100 / 365, 2) 50 | 51 | # time_content = '今天是:%d 年 %d 月 %d 日,星期%s\n报告主人!今年 %.2f%% 时间已流逝。' % (year, month, day, week_d, percent) 52 | time_content = ('各位帅哥美女们,大家早上好!\n\n今天是:%d 年 %d 月 %d 日,星期%s~' % (year, month, day, week_d), 53 | '\n\n报告主人!报告主人!报告主人!\n\n%d 年,岁月已陪伴主人走过:%.2f%%。' % (year, percent) + "余下时光,请主人们温柔以待哦!") 54 | return time_content 55 | 56 | # print(get_time()) 57 | -------------------------------------------------------------------------------- /EveryDay/AboutWeather.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: v1.0 6 | @author: xag 7 | @license: Apache Licence 8 | @contact: xinganguo@gmail.com 9 | @site: http://www.xingag.top 10 | @software: PyCharm 11 | @file: AboutWeather.py 12 | @time: 4/18/19 19:32 13 | @description:天气 14 | """ 15 | 16 | import requests 17 | 18 | 19 | def get_weather(): 20 | url = 'https://www.tianqiapi.com/api/?version=v1&cityid=101280601' 21 | 22 | result = requests.get(url).json().get('data')[0] 23 | 24 | city = '深圳天气:' 25 | 26 | # 天气 27 | wea = result.get('wea') 28 | 29 | # 空气质量 30 | air_level = result.get('air_level') 31 | 32 | # 最高温度 33 | tem_high = result.get('tem1') 34 | 35 | # 最低温度 36 | tem_low = result.get('tem2') 37 | 38 | result = city + str(wea) + ",最低温度:" + str(tem_low) + ",最高温度:" + str(tem_high) + ",空气质量:" + str(air_level) + "。" 39 | 40 | return result 41 | -------------------------------------------------------------------------------- /EveryDay/utils/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xingag/tools_python/f01d315c46a619828d02ed9327f4264ba3a382d8/EveryDay/utils/.DS_Store -------------------------------------------------------------------------------- /EveryDay/utils/chat_utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: v1.0 6 | @author: xag 7 | @license: Apache Licence 8 | @contact: xinganguo@gmail.com 9 | @site: http://www.xingag.top 10 | @software: PyCharm 11 | @file: chat_utils.py 12 | @time: 4/18/19 20:21 13 | @description:itchat 工具类 14 | """ 15 | import itchat 16 | 17 | 18 | # 注意:默认获取到的群聊:保存在通讯录中的群聊 19 | 20 | 21 | def send_word_helper(content): 22 | """ 23 | 向文件传输助手发送文字 24 | :param content: 25 | :return: 26 | """ 27 | itchat.send(content, toUserName='filehelper') 28 | 29 | 30 | def send_word_to_person(name, content): 31 | """ 32 | 发送消息给某个人 33 | :param name: 34 | :param content: 35 | :return: 36 | """ 37 | users = itchat.search_friends(name) 38 | userName = users[0]['UserName'] 39 | itchat.send(content, toUserName=userName) 40 | 41 | 42 | def send_file_to_person(name, filename): 43 | """ 44 | 发送文件给某个人 45 | :param name: 46 | :param filename: 47 | :return: 48 | """ 49 | users = itchat.search_friends(name) 50 | userName = users[0]['UserName'] 51 | itchat.send_file(filename, toUserName=userName) 52 | 53 | 54 | def send_word_to_group(group_name, word): 55 | """ 56 | 群聊中发送文字 57 | :param group_name: 58 | :param content: 59 | :return: 60 | """ 61 | # 获取所有群聊 62 | # 显示所有的群聊信息,默认是返回保存到通讯录中的群聊 63 | itchat.dump_login_status() 64 | 65 | target_rooms = itchat.search_chatrooms(name=group_name) 66 | 67 | if target_rooms and len(target_rooms) > 0: 68 | target_rooms[0].send_msg(word) 69 | else: 70 | print('【发送文字】抱歉,不存在这个群聊:%s' % group_name) 71 | 72 | 73 | def send_file_to_group(group_name, file_name): 74 | """ 75 | 发送文件到群聊 76 | :param group_name: 77 | :param file: 78 | :return: 79 | """ 80 | itchat.dump_login_status() 81 | 82 | target_rooms = itchat.search_chatrooms(name=group_name) 83 | 84 | if target_rooms and len(target_rooms) > 0: 85 | target_rooms[0].send_file(file_name) 86 | else: 87 | print('【发送文件】抱歉,不存在这个群聊:%s' % group_name) 88 | -------------------------------------------------------------------------------- /EveryDay/utils/date_utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: v1.0 6 | @author: xag 7 | @license: Apache Licence 8 | @contact: xinganguo@gmail.com 9 | @site: http://www.xingag.top 10 | @software: PyCharm 11 | @file: date_utils.py 12 | @time: 4/16/19 23:59 13 | @description:日期工具类 14 | """ 15 | 16 | import datetime 17 | 18 | 19 | def get_today(): 20 | # 获取今天的日期 21 | # 22 | today = datetime.date.today() 23 | return today 24 | 25 | 26 | def get_today_day(): 27 | """ 28 | 获取年月日 29 | :return: int类型 30 | """ 31 | # datetime.datetime.now().year 32 | # datetime.datetime.now().month 33 | return datetime.datetime.now().day 34 | 35 | 36 | def get_today_ymd(): 37 | """ 38 | 获取年月日 39 | :return: int类型 40 | """ 41 | year = datetime.datetime.now().year 42 | month = datetime.datetime.now().month 43 | day = datetime.datetime.now().day 44 | 45 | return "%d_%d_%d" % (year, month, day) 46 | 47 | 48 | def get_current_time(): 49 | # 当前时间 50 | current_time = datetime.datetime.now() 51 | # 时 52 | current_hour = current_time.hour 53 | # 分 54 | current_minute = current_time.minute 55 | # 秒 56 | current_second = current_time.second 57 | 58 | # print('当前时间:%d:%d:%d' % (current_hour, current_minute, current_second)) 59 | 60 | return current_hour, current_minute, current_second 61 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Python执行JS总结/.idea/exec_js_summary.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /Python执行JS总结/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /Python执行JS总结/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Python执行JS总结/js2py_demo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: v1.0 6 | @author: xag 7 | @license: Apache Licence 8 | @contact: xinganguo@gmail.com 9 | @site: http://www.xingag.top 10 | @software: PyCharm 11 | @file: js2py_demo.py 12 | @time: 2020-07-22 17:49 13 | @description:js2py 14 | """ 15 | 16 | # 依赖 17 | # pip3 install js2py 18 | 19 | import js2py 20 | 21 | from js_code import * 22 | 23 | 24 | def test_simple(): 25 | """ 26 | 简单 27 | :return: 28 | """ 29 | # 将js代码转为python 30 | add = js2py.eval_js(js_simple()) 31 | 32 | # 当做python函数调用 33 | result = add(1, 2) 34 | 35 | print(result) 36 | 37 | 38 | def test_js_from_file(): 39 | """ 40 | 从文件中读取js进行执行 41 | :return: 42 | """ 43 | 44 | # 从文件中读取js代码 45 | js_content = js_from_file('./norm.js') 46 | 47 | # 使用获取上下js2py生成一个上下文环境 48 | context = js2py.EvalJs() 49 | 50 | # 执行整段JS代码 51 | context.execute(js_content) 52 | 53 | # 使用context调用具体的函数 54 | result = context.add(1, 2) 55 | 56 | print(result) 57 | 58 | 59 | if __name__ == '__main__': 60 | # test_simple() 61 | test_js_from_file() 62 | -------------------------------------------------------------------------------- /Python执行JS总结/js_code.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: v1.0 6 | @author: xag 7 | @license: Apache Licence 8 | @contact: xinganguo@gmail.com 9 | @site: http://www.xingag.top 10 | @software: PyCharm 11 | @file: js_code.py 12 | @time: 2020-07-22 13:30 13 | @description:待执行的JS代码 14 | """ 15 | 16 | 17 | def js_simple(): 18 | """ 19 | 返回简单的js代码 20 | :return: 21 | """ 22 | return """ 23 | function add(num1 , num2){ 24 | return num1 + num2; 25 | } 26 | """ 27 | 28 | 29 | def js_from_file(file_name): 30 | """ 31 | 读取js文件 32 | :return: 33 | """ 34 | with open(file_name, 'r', encoding='UTF-8') as file: 35 | result = file.read() 36 | 37 | return result 38 | -------------------------------------------------------------------------------- /Python执行JS总结/node_demo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: v1.0 6 | @author: xag 7 | @license: Apache Licence 8 | @contact: xinganguo@gmail.com 9 | @site: http://www.xingag.top 10 | @software: PyCharm 11 | @file: node_demo.py 12 | @time: 2020-07-22 22:19 13 | @description:使用node命令调用js 14 | """ 15 | 16 | import os 17 | 18 | 19 | def func1(): 20 | # 组成调用js的命令 21 | cmd = 'node -e "require(\\"%s\\").init(%s,%s)"' % ('./norm1', 3, 5) 22 | 23 | pipeline = os.popen(cmd) 24 | 25 | # 读取结果 26 | result = pipeline.read() 27 | 28 | print('结果是:', result) 29 | 30 | 31 | if __name__ == '__main__': 32 | func1() 33 | -------------------------------------------------------------------------------- /Python执行JS总结/norm.js: -------------------------------------------------------------------------------- 1 | //计算两个数的和 2 | function add(num1, num2) { 3 | return num1 + num2; 4 | } 5 | -------------------------------------------------------------------------------- /Python执行JS总结/norm1.js: -------------------------------------------------------------------------------- 1 | //计算两个数的和 2 | function add(num1, num2) { 3 | return num1 + num2; 4 | } 5 | 6 | //新增一个导出函数(node方式) 7 | module.exports.init = function (arg1, arg2) { 8 | //调用函数 9 | console.log(add(arg1, arg2)); 10 | }; -------------------------------------------------------------------------------- /Python执行JS总结/py_exec_js_demo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: v1.0 6 | @author: xag 7 | @license: Apache Licence 8 | @contact: xinganguo@gmail.com 9 | @site: http://www.xingag.top 10 | @software: PyCharm 11 | @file: py_exec_js_demo.py 12 | @time: 2020-07-22 13:24 13 | @description:使用PyExecJS调用JS代码 14 | """ 15 | 16 | # 依赖 17 | # pip3 install PyExecJS 18 | 19 | import execjs 20 | 21 | from js_code import * 22 | 23 | # 1、简单的JS字符串 24 | # 编译并加载 js 文件内容,方便执行里面的方法 25 | context = execjs.compile(js_simple()) 26 | 27 | # 使用call()函数调用js内部函数 28 | result = context.call("add", 2, 3) 29 | 30 | print(result) 31 | 32 | # 2、js文件 33 | # 编译加载js字符串 34 | context1 = execjs.compile(js_from_file('./norm.js')) 35 | 36 | # 调用js代码中的add()方法,参数为2和3 37 | result1 = context1.call("add", 2, 3) 38 | 39 | print(result1) 40 | -------------------------------------------------------------------------------- /Python执行JS总结/py_v8_demo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: v1.0 6 | @author: xag 7 | @license: Apache Licence 8 | @contact: xinganguo@gmail.com 9 | @site: http://www.xingag.top 10 | @software: PyCharm 11 | @file: py_v8_demo.py 12 | @time: 2020-07-22 14:53 13 | @description:PyV8调用JS 14 | """ 15 | 16 | import PyV8 17 | from js_code import js_from_file 18 | 19 | with PyV8.JSContext() as ctx: 20 | ctx.eval(js_from_file('./norm.js')) 21 | 22 | # 调用js函数,指定参数 23 | ctx.locals.add(1, 2) 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tools Python 2 | 3 | ## 详细教程 4 | 5 | 如果想查看详细教程,请关注微信公众号:**AirPython** 6 | 7 | 8 | 9 | ![](./raw/qr.jpeg) 10 | 11 | 12 | 13 | ## 文章列表 14 | 15 | * [制作截图快捷翻译](./截图快捷翻译) 16 | * [朋友圈防折叠](./朋友圈防折叠/) 17 | * [批量生成手机号码](./批量生成手机号码) 18 | * [短视频二次创作](./video_auto/二次剪辑) 19 | * [使用 Python 制作 GIF 动画视频](./video_auto/制作GIF视频/) 20 | * [使用 Python 制作一个卡点视频](./video_auto/卡点视频/readme.md) 21 | * [替换 `apk` 的资源并重新打包](./android/replace_apk_resource/readme.md) 22 | * [替换 `apk` 的资源并重新打包全解析](./android/replace_apk_resource_pro/readme.md) 23 | * [`appium` 配合压力测试 `app` 应用](./android/appium_demo/) 24 | * [每天固定时间发送天气、笑话给微信个人和群,并部署到云服务器](./EveryDay/) 25 | * [视频特殊处理,保证唯一性](./视频特殊处理.py) 26 | * [发送邮件的几种方式](./发送邮件) 27 | * [Python 调用 JS 的 4 种方式](./Python执行JS总结) 28 | * [批处理脚本](./批处理脚本) 29 | * [生成九宫格视频+生成九宫格朋友圈图片](./图片视频九宫格/) 30 | 31 | -------------------------------------------------------------------------------- /android/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xingag/tools_python/f01d315c46a619828d02ed9327f4264ba3a382d8/android/.DS_Store -------------------------------------------------------------------------------- /android/appium_demo/config_nantian.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: v1.0 6 | @author: xag 7 | @license: Apache Licence 8 | @contact: xinganguo@gmail.com 9 | @site: http://www.xingag.top 10 | @software: PyCharm 11 | @file: config.py 12 | @time: 1/14/19 22:20 13 | @description:配置文件 14 | """ 15 | 16 | import os 17 | 18 | PLATFORM = 'Android' 19 | 20 | DEVICE_NAME = 'BP8910_S' 21 | 22 | DRIVER = 'automationName2' 23 | 24 | # APP包名 25 | APP_PACKAGE = 'com.nantian.home' 26 | 27 | # 入口类名 28 | APP_ACTIVITY = 'com.van.view.MainActivity' 29 | 30 | ANDROID_VERSION = '5.1.1' 31 | 32 | # 权限授予 33 | AUTO_GRANT_PERMISSIONS = True 34 | 35 | # ======================================== 36 | 37 | # Appium地址 38 | DRIVER_SERVER = 'http://localhost:4723/wd/hub' 39 | 40 | # 等待元素加载时间 41 | TIMEOUT = 60 42 | 43 | 44 | # =========================================== 45 | 46 | 47 | -------------------------------------------------------------------------------- /android/appium_demo/nantian_keyboard.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: v1.0 6 | @author: xag 7 | @license: Apache Licence 8 | @contact: xinganguo@gmail.com 9 | @site: http://www.xingag.top 10 | @software: PyCharm 11 | @file: nantian_keyboard.py 12 | @time: 1/18/19 10:05 13 | @description:设备App测试密码键盘 14 | """ 15 | 16 | from config_nantian import * 17 | from appium import webdriver 18 | from selenium.webdriver.support.ui import WebDriverWait 19 | from selenium.webdriver.common.by import By 20 | from selenium.webdriver.support import expected_conditions as EC 21 | import time 22 | from element_utils import * 23 | from random import randint 24 | 25 | 26 | class Keyboard(object): 27 | 28 | def __init__(self): 29 | self.caps = { 30 | 'automationName': DRIVER, 31 | 'platformName': PLATFORM, 32 | 'deviceName': DEVICE_NAME, 33 | 'appPackage': APP_PACKAGE, 34 | 'appActivity': APP_ACTIVITY, 35 | 'platformVersion': ANDROID_VERSION, 36 | 'autoGrantPermissions': AUTO_GRANT_PERMISSIONS, 37 | 'unicodeKeyboard': True, 38 | 'resetKeyboard': True 39 | } 40 | self.driver = webdriver.Remote(DRIVER_SERVER, self.caps) 41 | self.wait = WebDriverWait(self.driver, TIMEOUT) 42 | 43 | def login_and_web(self): 44 | """ 45 | 输入密码和信息交互 46 | :return: 47 | """ 48 | while True: 49 | keyboard_input_element = is_element_exist(self.driver, 'com.nantian.home:id/etPassword') 50 | web_sure_element = is_element_exist(self.driver, 'com.nantian.home:id/web_btnOK') 51 | web_cancel_element = is_element_exist(self.driver, 'com.nantian.home:id/btnCancel') 52 | 53 | # 输入框 54 | if keyboard_input_element: 55 | print('找到元素,准备输入密码') 56 | keyboard_input_element.send_keys('123456') 57 | # Web页面 58 | elif web_sure_element: 59 | print('找到元素,准备点击确认按钮,或者取消按钮') 60 | web_sure_element.click() if randint(0, 1) == 0 else web_cancel_element.click() 61 | else: 62 | print('没有找到元素,继续') 63 | time.sleep(1) 64 | 65 | def run(self): 66 | self.login_and_web() 67 | 68 | 69 | if __name__ == '__main__': 70 | keyboard = Keyboard() 71 | keyboard.run() 72 | -------------------------------------------------------------------------------- /android/delete_build.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: v1.0 6 | @author: xag 7 | @license: Apache Licence 8 | @contact: xinganguo@gmail.com 9 | @site: http://www.xingag.top 10 | @software: PyCharm 11 | @file: delete_build.py 12 | @time: 11/14/18 16:33 13 | @description:删除build文件夹 14 | """ 15 | 16 | import os 17 | 18 | # 待删除的目录 19 | path = './' 20 | 21 | 22 | def get_file_name(file_full_path): 23 | """ 24 | :param file_full_path: 文件的完整路径 25 | :return: 上层父目录、文件名【带后缀】、文件名【不带后缀】、文件后缀 26 | """ 27 | # 上层父目录、文件名【带后缀】 28 | (filepath, tempfilename) = os.path.split(file_full_path) 29 | 30 | # 文件名【不带后缀】,文件名 31 | (filename, extension) = os.path.splitext(tempfilename) 32 | 33 | return (filepath, tempfilename, filename, extension) 34 | 35 | 36 | def remove_dir(dir_path): 37 | print("删除文件夹的目录是:"+dir_path) 38 | 39 | # 如果是空文件夹 40 | if not os.listdir(dir_path): 41 | os.removedirs(dir_path) 42 | 43 | for root, dirs, files in os.walk(dir_path, topdown=False): 44 | for name in files: 45 | os.remove(os.path.join(root, name)) 46 | for name in dirs: 47 | os.rmdir(os.path.join(root, name)) 48 | 49 | 50 | def traverse_files(path): 51 | """ 52 | 遍历 53 | :param path: 54 | :return: 55 | """ 56 | for item in os.scandir(path): 57 | 58 | file_name = get_file_name(item.path)[2] 59 | # 判断是文件夹还是文件 60 | if item.is_dir(): 61 | # print(item.path) 62 | # 删除build文件夹 63 | if file_name == 'build': 64 | remove_dir(item.path) 65 | else: 66 | traverse_files(item.path) 67 | 68 | 69 | traverse_files(path) 70 | -------------------------------------------------------------------------------- /android/replace_apk_resource/fileutils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: v1.0 6 | @author: xag 7 | @license: Apache Licence 8 | @contact: xinganguo@gmail.com 9 | @site: http://www.xingag.top 10 | @software: PyCharm 11 | @file: fileutils.py 12 | @time: 2018/9/13 17:44 13 | @description:文件工具 14 | """ 15 | import os 16 | 17 | 18 | def rename_file_suffix(name, suffix): 19 | """ 20 | 更改文件的后缀名 21 | :param name: 源文件名称 22 | :param suffix: 新的后缀名 23 | :return: 新的文件名称 24 | """ 25 | base = os.path.splitext(name)[0] 26 | apk_name = base + suffix 27 | os.rename(name, apk_name) 28 | return apk_name 29 | -------------------------------------------------------------------------------- /android/replace_apk_resource/readme.md: -------------------------------------------------------------------------------- 1 | # 利用 `Python` 去更换 `apk` 资源 2 | 3 | ### 思路 4 | 5 | 1. 解压 `apk` 包。 6 | 2. 删除源 `apk` 的签名文件夹 - `META_INF/` 7 | 3. 把需要更新的资源文件替换进去,然后压缩成新的 `apk` 包 8 | 4. 利用签名文件 `*.keystore` ,对上面的 `apk` 进行重新签名 9 | 5. 利用 `sdk` 中的 `zipalign` 工具进行对齐 10 | 6. 使用 `jarsigner` 对产生的 `apk` 进行验证 11 | 12 | 13 | 14 | 15 | ### 注意 16 | 17 | * 保证签名文件、`apk` 在脚本文件同目录下 18 | 19 | * 更新资源的时候,必须指定需要被替换的绝对路径 20 | 21 | * 生成压缩文件 `*.apk` 的时候,必须将脚本 `ziputils.py` 添加到压缩根目录执行脚本 22 | 23 | ​ 24 | 25 | -------------------------------------------------------------------------------- /android/replace_apk_resource/replace_apk_resource.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | """ 4 | @version: v1.0 5 | @author: xag 6 | @license: Apache Licence 7 | @contact: xinganguo@gmail.com 8 | @site: http://www.xingag.top 9 | @software: PyCharm 10 | @file: ReplaceApkResource.py 11 | @time: 2018/9/13 12:24 12 | @description:利用python替换apk的资源后重新打包 13 | """ 14 | 15 | import shutil 16 | import os 17 | import zipfile 18 | import fileutils 19 | 20 | # 设置签名信息 21 | keystore_path = "**.keystore" 22 | keystore_pass = "**" 23 | keystore_alias = "**" 24 | keystore_alias_pass = "**" 25 | 26 | # 脚本根目录 27 | root = os.getcwd() 28 | 29 | # 创建一个临时目录 30 | tmp_path = root + "/tmp/" 31 | 32 | # 传入apk的名称,并更改后缀名为zip 33 | apk_name_temp = input('请输入apk的名称:') 34 | 35 | apk_name = fileutils.rename_file_suffix(apk_name_temp, 'zip') 36 | 37 | # 保证tmp文件夹目录存在 38 | if not os.path.exists(tmp_path): 39 | os.system("mkdir tmp") 40 | 41 | # copy apk文件到临时文件夹内 42 | shutil.copyfile(apk_name, tmp_path + apk_name) 43 | 44 | print(tmp_path + apk_name) 45 | 46 | tmp_path_all = tmp_path + apk_name 47 | 48 | # 获取压缩包路径 49 | azip = zipfile.ZipFile(tmp_path_all) 50 | 51 | 52 | # 解压zip文件【到指定目录下】 53 | azip.extractall(tmp_path) 54 | 55 | # 注意:此处需要关闭对tmp_path的引用 56 | azip.close() 57 | 58 | # 删除签名信息和源zip【apk】 59 | if os.path.exists(tmp_path + 'META-INF/'): 60 | shutil.rmtree(tmp_path + 'META-INF') 61 | 62 | if os.path.exists(tmp_path_all): 63 | os.remove(tmp_path_all) 64 | 65 | # 更新资源【图片替换到目标文件当中去】 66 | shutil.copyfile('ic_logo.png', tmp_path + "res\drawable-mdpi-v4\ic_logo.png") 67 | 68 | 69 | # 移动python文件到tmp目录下,然后显示执行这条Python脚本 70 | shutil.copyfile('ziputils.py', r'./tmp/ziputils.py') 71 | 72 | # 切换到./tmp/目录下,执行压缩文件为 73 | os.chdir(tmp_path) 74 | os.system('python ziputils.py') 75 | 76 | # 切换到根目录下 77 | os.chdir(root) 78 | 79 | # 利用keystore文件进行签名 80 | os.system( 81 | "jarsigner -verbose -digestalg SHA1 -sigalg MD5withRSA -keystore " + keystore_path + " -storepass " + keystore_pass + " -signedjar release-sign.apk release-nosign.apk " + keystore_alias + " -keypass " + keystore_alias_pass) 82 | 83 | # 压缩对齐【利用zipalign去对齐应用apk】 84 | os.system( 85 | "D:/Android/sdk/build-tools/28.0.1/zipalign -v 4 release-sign.apk release.apk") 86 | 87 | # 删除临时文件和多余的apk文件 88 | shutil.rmtree(tmp_path) 89 | os.remove('release-nosign.apk') 90 | os.remove('release-sign.apk') 91 | 92 | # 验证签名 93 | os.system("jarsigner -verify -certs release.apk") 94 | 95 | print('恭喜,终于成功啦!') 96 | -------------------------------------------------------------------------------- /android/replace_apk_resource/ziputils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: v1.0 6 | @author: xag 7 | @license: Apache Licence 8 | @contact: xinganguo@gmail.com 9 | @site: http://www.xingag.top 10 | @software: PyCharm 11 | @file: ziputils.py 12 | @time: 2018/9/13 14:54 13 | @description:zip 压缩工具 14 | """ 15 | import os 16 | import zipfile 17 | from os.path import isfile, isdir, join 18 | 19 | 20 | def dfs_get_zip_file(input_path, result): 21 | files = os.listdir(input_path) 22 | for file in files: 23 | # 排除掉py文件 24 | if file == 'ziputils.py': 25 | continue 26 | if os.path.isdir(input_path + '/' + file): 27 | dfs_get_zip_file(input_path + '/' + file, result) 28 | else: 29 | result.append(input_path + '/' + file) 30 | 31 | 32 | def zip_path(input_path, output_file_full): 33 | """ 34 | 35 | :param input_path: 待压缩的文件夹 36 | :param output_file_full: 压缩的完整目录,包含文件名 37 | :return: 38 | """ 39 | f = zipfile.ZipFile(output_file_full, 'w', zipfile.ZIP_DEFLATED) 40 | filelists = [] 41 | dfs_get_zip_file(input_path, filelists) 42 | for file in filelists: 43 | # 写入到zip目录下 44 | f.write(file) 45 | f.close() 46 | return output_file_full 47 | 48 | 49 | if __name__ == '__main__': 50 | 51 | # 根目录下打包所有文件 52 | zip_path(r"./", r'../release-nosign.apk') 53 | -------------------------------------------------------------------------------- /android/replace_apk_resource_pro/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xingag/tools_python/f01d315c46a619828d02ed9327f4264ba3a382d8/android/replace_apk_resource_pro/.DS_Store -------------------------------------------------------------------------------- /android/replace_apk_resource_pro/apktool.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xingag/tools_python/f01d315c46a619828d02ed9327f4264ba3a382d8/android/replace_apk_resource_pro/apktool.jar -------------------------------------------------------------------------------- /android/replace_apk_resource_pro/file_utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: v1.0 6 | @author: xag 7 | @license: Apache Licence 8 | @contact: xinganguo@gmail.com 9 | @site: http://www.xingag.top 10 | @software: PyCharm 11 | @file: file_utils.py 12 | @time: 4/25/19 11:17 13 | @description:文件管理器 14 | """ 15 | 16 | import os 17 | import shutil 18 | 19 | 20 | def get_current_folder_file(file_type): 21 | """ 22 | 通过文件类型,获取到当前目录下的文件 filename = get_current_folder_file('apk') 23 | :param file_type: 24 | :return: 25 | """ 26 | # 当前目录下所有的文件 27 | all_files = os.listdir('.') 28 | 29 | # 满足要求的文件列表 30 | target_files = [] 31 | for file in all_files: 32 | if file.split('.')[-1] == file_type: 33 | target_files.append(file) 34 | 35 | # 返回第一项 36 | return target_files[0] if len(target_files) > 0 else "" 37 | 38 | 39 | def rename_current_file(file_type, file_name): 40 | """ 41 | 重命令文件夹 42 | :param file_type:文件类型 43 | :param file_name:准备替换的名称 44 | :return: 45 | """ 46 | file = get_current_folder_file(file_type) 47 | 48 | # 重命令操作 49 | os.rename(os.path.join('./', file), os.path.join('./', file_name)) 50 | 51 | 52 | def replace_file(file_from_path, file_to_path): 53 | """ 54 | 替换文件 55 | :param file_from_path:待替换的文件 56 | :param file_to_path:待被替换的文件 57 | :return: 58 | """ 59 | shutil.copy(file_from_path, file_to_path) 60 | -------------------------------------------------------------------------------- /android/replace_apk_resource_pro/jarsigner: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xingag/tools_python/f01d315c46a619828d02ed9327f4264ba3a382d8/android/replace_apk_resource_pro/jarsigner -------------------------------------------------------------------------------- /android/replace_apk_resource_pro/logo_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xingag/tools_python/f01d315c46a619828d02ed9327f4264ba3a382d8/android/replace_apk_resource_pro/logo_white.png -------------------------------------------------------------------------------- /android/replace_apk_resource_pro/readme.md: -------------------------------------------------------------------------------- 1 | # 替换 APK 里面的资源 2 | 3 | ## 思路 4 | 5 | 1. 使用 `apktool.jar` 解压 `apk` 6 | 2. 替换资源 7 | 3. 利用 `apktool.jar` 重新打包 8 | 4. 使用 `jarsigner` 对 `apk` 进行重新签名 9 | 5. 打包成 `exe` 可执行文件 10 | 11 | 12 | 13 | ## 解压 14 | 15 | ``` 16 | def __unzip_apk(self): 17 | """ 18 | 解压当前目录下的apk文件 19 | :return: 20 | """ 21 | # 文件名称,包含后缀名 22 | file_name = get_current_folder_file('apk') 23 | 24 | # 文件名称,不包含后缀名 25 | file_name_pre = file_name.split('.')[0] 26 | 27 | os.system('java -jar apktool.jar d %s' % file_name) 28 | 29 | print('第1步:解压成功~') 30 | 31 | return file_name_pre 32 | ``` 33 | 34 | 35 | 36 | ## 替换资源 37 | 38 | ``` 39 | def __replace_source(self, file_name_pre): 40 | """ 41 | 替换资源 42 | @:param file_name_pre 文件夹的名称 43 | :return: 44 | """ 45 | print('生成文件夹的名字是:%s' % file_name_pre) 46 | 47 | # 重命令当前目录下的文件 48 | rename_current_file("png", self.file_name) 49 | 50 | # 待替换的完成路径是 51 | logo_file_path = './%s/res/drawable-mdpi/logo_white.png' % file_name_pre 52 | 53 | # 开始替换文件 54 | replace_file('./%s' % self.file_name, logo_file_path) 55 | 56 | print('第2步:替换资源图片成功~') 57 | ``` 58 | 59 | 60 | 61 | ## 重新打包 62 | 63 | ``` 64 | def __rezip_apk(self, folder_name): 65 | """ 66 | 重新打包成apk 67 | @:param folder_name 文件夹的名称 source 68 | :return: 69 | """ 70 | 71 | # 重新打包成apk 72 | os.system('java -jar apktool.jar b %s -o %s' % (folder_name, self.target_apk_name)) 73 | 74 | # 删除临时文件夹 75 | shutil.rmtree('./%s/' % folder_name) 76 | 77 | print('第3步:重新打包成功~') 78 | ``` 79 | 80 | 81 | 82 | ## 重新签名 83 | 84 | ``` 85 | def __re_sign(self): 86 | """ 87 | 重新签名 88 | :return: 89 | """ 90 | # 重新签名 91 | cmd = 'jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore **.keystore -storepass ** %s **' % self.target_apk_name 92 | p = Popen(cmd, stdout=PIPE, stdin=PIPE, stderr=STDOUT, shell=True) 93 | 94 | # 输入参数 95 | p.communicate(input=b'nantian') 96 | print('第4步:重新签名成功~') 97 | ``` 98 | 99 | 100 | 101 | ## 可执行文件 102 | 103 | 使用 [auto-py-to-exe](https://github.com/brentvollebregt/auto-py-to-exe) 生成可执行文件。 104 | 105 | -------------------------------------------------------------------------------- /android/replace_apk_resource_pro/replace_source.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: v1.0 6 | @author: xag 7 | @license: Apache Licence 8 | @contact: xinganguo@gmail.com 9 | @site: http://www.xingag.top 10 | @software: PyCharm 11 | @file: replace_source.py 12 | @time: 4/25/19 10:46 13 | @description:替换apk的资源文件 14 | """ 15 | 16 | from file_utils import * 17 | import os 18 | from subprocess import Popen, PIPE, STDOUT 19 | 20 | 21 | class ReplaceApkSource(object): 22 | def __init__(self): 23 | self.file_name = 'logo_white.png' 24 | 25 | # 目标apk的名称 26 | self.target_apk_name = 'new.apk' 27 | 28 | def start(self): 29 | # 1.使用apktool.jar解压apk 30 | file_name_pre = self.__unzip_apk() 31 | 32 | # 2.替换资源 33 | self.__replace_source(file_name_pre) 34 | 35 | # 3.重新打包 36 | self.__rezip_apk(file_name_pre) 37 | 38 | # 4.再次签名 39 | self.__re_sign() 40 | 41 | def __unzip_apk(self): 42 | """ 43 | 解压当前目录下的apk文件 44 | :return: 45 | """ 46 | # 文件名称,包含后缀名 47 | file_name = get_current_folder_file('apk') 48 | 49 | # 文件名称,不包含后缀名 50 | file_name_pre = file_name.split('.')[0] 51 | 52 | os.system('java -jar apktool.jar d %s' % file_name) 53 | 54 | print('第1步:解压成功~') 55 | 56 | return file_name_pre 57 | 58 | def __replace_source(self, file_name_pre): 59 | """ 60 | 替换资源 61 | @:param file_name_pre 文件夹的名称 62 | :return: 63 | """ 64 | print('生成文件夹的名字是:%s' % file_name_pre) 65 | 66 | # 重命令当前目录下的文件 67 | rename_current_file("png", self.file_name) 68 | 69 | # 待替换的完成路径是 70 | logo_file_path = './%s/res/drawable-mdpi/logo_white.png' % file_name_pre 71 | 72 | # 开始替换文件 73 | replace_file('./%s' % self.file_name, logo_file_path) 74 | 75 | print('第2步:替换资源图片成功~') 76 | 77 | def __rezip_apk(self, folder_name): 78 | """ 79 | 重新打包成apk 80 | @:param folder_name 文件夹的名称 source 81 | :return: 82 | """ 83 | 84 | # 重新打包成apk 85 | os.system('java -jar apktool.jar b %s -o %s' % (folder_name, self.target_apk_name)) 86 | 87 | # 删除临时文件夹 88 | shutil.rmtree('./%s/' % folder_name) 89 | 90 | print('第3步:重新打包成功~') 91 | 92 | def __re_sign(self): 93 | """ 94 | 重新签名 95 | :return: 96 | """ 97 | # 重新签名 98 | cmd = 'jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore **.keystore -storepass ** %s **' % self.target_apk_name 99 | p = Popen(cmd, stdout=PIPE, stdin=PIPE, stderr=STDOUT, shell=True) 100 | 101 | # 输入参数 102 | p.communicate(input=b'nantian') 103 | print('第4步:重新签名成功~') 104 | 105 | 106 | if __name__ == '__main__': 107 | replace_apk_source = ReplaceApkSource() 108 | replace_apk_source.start() 109 | 110 | print('恭喜!完成操作~') 111 | -------------------------------------------------------------------------------- /raw/qr.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xingag/tools_python/f01d315c46a619828d02ed9327f4264ba3a382d8/raw/qr.jpeg -------------------------------------------------------------------------------- /video_auto/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xingag/tools_python/f01d315c46a619828d02ed9327f4264ba3a382d8/video_auto/.DS_Store -------------------------------------------------------------------------------- /video_auto/二次剪辑/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xingag/tools_python/f01d315c46a619828d02ed9327f4264ba3a382d8/video_auto/二次剪辑/.DS_Store -------------------------------------------------------------------------------- /video_auto/二次剪辑/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /video_auto/二次剪辑/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /video_auto/二次剪辑/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /video_auto/二次剪辑/.idea/视频剪辑.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /video_auto/二次剪辑/fonts/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xingag/tools_python/f01d315c46a619828d02ed9327f4264ba3a382d8/video_auto/二次剪辑/fonts/.DS_Store -------------------------------------------------------------------------------- /video_auto/二次剪辑/fonts/STHeiti Medium.ttc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xingag/tools_python/f01d315c46a619828d02ed9327f4264ba3a382d8/video_auto/二次剪辑/fonts/STHeiti Medium.ttc -------------------------------------------------------------------------------- /video_auto/二次剪辑/source/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xingag/tools_python/f01d315c46a619828d02ed9327f4264ba3a382d8/video_auto/二次剪辑/source/.DS_Store -------------------------------------------------------------------------------- /video_auto/二次剪辑/util/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xingag/tools_python/f01d315c46a619828d02ed9327f4264ba3a382d8/video_auto/二次剪辑/util/.DS_Store -------------------------------------------------------------------------------- /video_auto/二次剪辑/util/file_utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: v1.0 6 | @author: xag 7 | @license: Apache Licence 8 | @contact: xinganguo@gmail.com 9 | @site: http://www.xingag.top 10 | @software: PyCharm 11 | @file: file_utils.py 12 | @time: 2019-12-29 11:30 13 | @description:TODO 14 | """ 15 | 16 | import os 17 | 18 | 19 | def del_temp_file(path): 20 | """ 21 | 删除目录下的临时文件 22 | :param path: 23 | :return: 24 | """ 25 | # 删除临时文件 26 | g = os.walk(path) 27 | 28 | for path, dir_list, file_list in g: 29 | print(path) 30 | for file_name in file_list: 31 | print(file_name) 32 | if file_name.startswith('temp'): 33 | os.remove(path + file_name) 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /video_auto/二次剪辑/util/img_utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: v1.0 6 | @author: xag 7 | @license: Apache Licence 8 | @contact: xinganguo@gmail.com 9 | @site: http://www.xingag.top 10 | @software: PyCharm 11 | @file: img_utils.py 12 | @time: 2019-12-25 14:23 13 | @description:图片工具类 14 | """ 15 | import cv2 16 | from moviepy.video.VideoClip import ImageClip 17 | from moviepy.editor import VideoFileClip 18 | 19 | 20 | def one_pic_to_video(image_path, output_video_path, fps, time): 21 | """ 22 | 一张图片合成视频 23 | one_pic_to_video('./../source/1.jpeg', './../source/output.mp4', 25, 10) 24 | :param path: 图片文件路径 25 | :param output_video_path:合成视频的路径 26 | :param fps:帧率 27 | :param time:时长 28 | :return: 29 | """ 30 | 31 | image_clip = ImageClip(image_path) 32 | img_width, img_height = image_clip.w, image_clip.h 33 | 34 | # 总共的帧数 35 | frame_num = (int)(fps * time) 36 | 37 | img_size = (int(img_width), int(img_height)) 38 | 39 | fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v') 40 | 41 | video = cv2.VideoWriter(output_video_path, fourcc, fps, img_size) 42 | 43 | for index in range(frame_num): 44 | frame = cv2.imread(image_path) 45 | # 直接缩放到指定大小 46 | frame_suitable = cv2.resize(frame, (img_size[0], img_size[1]), interpolation=cv2.INTER_CUBIC) 47 | 48 | # 把图片写进视频 49 | # 重复写入多少次 50 | video.write(frame_suitable) 51 | 52 | # 释放资源 53 | video.release() 54 | 55 | return VideoFileClip(output_video_path) 56 | -------------------------------------------------------------------------------- /video_auto/二次剪辑/util/video_utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: v1.0 6 | @author: xag 7 | @license: Apache Licence 8 | @contact: xinganguo@gmail.com 9 | @site: http://www.xingag.top 10 | @software: PyCharm 11 | @file: video_utils.py 12 | @time: 2019-12-26 21:58 13 | @description:视频工具类 14 | """ 15 | 16 | from moviepy.editor import * 17 | 18 | 19 | def time_to_hms(seconds_time): 20 | """ 21 | 时间转为时分秒 22 | :param seconds_time: 秒数 23 | :return: 24 | """ 25 | m, s = divmod(seconds_time, 60) 26 | h, m = divmod(m, 60) 27 | return "%02d:%02d:%02d" % (h, m, s) 28 | 29 | 30 | def synthetic_video(video1_clip, video2_clip2): 31 | """ 32 | 合成两段视频,生成视频的宽高以第一段视频为准 33 | :param video1_clip: 34 | :param video2_clip2: 35 | :return: 36 | """ 37 | # 最后生成视频的宽、高 38 | width, height = video1_clip.w, video1_clip.h 39 | 40 | # 第二段视频的实际宽、高 41 | video_width, video_height = video2_clip2.w, video2_clip2.h 42 | 43 | # 最第二段视频进行缩放 44 | video_clip1 = video2_clip2.resize((width, width * video_height / video_width)) 45 | 46 | # 合成视频的路径 47 | synthetic_video_clip = CompositeVideoClip([video1_clip, video_clip1.set_pos("center")]) 48 | 49 | synthetic_video_clip.write_videofile( 50 | './source/temp_synthetic_video.mp4') 51 | 52 | return synthetic_video_clip 53 | 54 | 55 | def video_with_text(synthetic_video_clip, desc_text_clip): 56 | """ 57 | 视频中加入文字信息 58 | :param synthetic_video_clip: 59 | :param param: 60 | :return: 61 | """ 62 | 63 | video_with_text_clip = CompositeVideoClip([synthetic_video_clip, desc_text_clip.set_start(0)]) 64 | video_with_text_clip.write_videofile( 65 | './source/temp_video_with_text.mp4') 66 | 67 | return video_with_text_clip 68 | 69 | # video_with_text_clip.write_videofile( 70 | # './source/temp_video_with_text.mp4') 71 | 72 | 73 | def get_frame_from_video(video_name, frame_time, img_path): 74 | """ 75 | 获取视频某个时间的帧图片,保存在本地 76 | :param video_name: 视频路径 77 | :param frame_time: 截取帧的时间位置(s) 78 | :param img_path:生成图片的完整路径 79 | :return: 80 | """ 81 | # 秒转为时、分、秒 82 | time_pre = time_to_hms(frame_time) 83 | 84 | os.system('ffmpeg -ss %s -i %s -frames:v 1 %s' % (time_pre, video_name, img_path)) 85 | 86 | 87 | def generate_text_clip(text_content, font_params, duration): 88 | """ 89 | 产生字幕 90 | :return: 91 | """ 92 | # 显示位置 93 | position = font_params.get('position') 94 | position_text = 'top' if position == 0 else 'bottom' 95 | 96 | return TextClip(text_content, font='./fonts/STHeiti Medium.ttc', 97 | fontsize=font_params.get('size'), kerning=font_params.get('kerning'), 98 | color=font_params.get('color')).set_position(("center", 150)).set_duration(duration) 99 | 100 | -------------------------------------------------------------------------------- /video_auto/二次剪辑/二次创作.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: v1.0 6 | @author: xag 7 | @license: Apache Licence 8 | @contact: xinganguo@gmail.com 9 | @site: http://www.xingag.top 10 | @software: PyCharm 11 | @file: video_rework.py 12 | @time: 2019-12-25 15:47 13 | @description:抖音视频二次创作 14 | """ 15 | 16 | from moviepy.video import fx 17 | 18 | from util.img_utils import * 19 | from util.video_utils import * 20 | from util.file_utils import * 21 | 22 | 23 | class Video_Re(object): 24 | 25 | def __init__(self, video_raw_path, desc): 26 | # 原始素材 27 | self.video_raw_path = video_raw_path 28 | 29 | # 描述信息 30 | self.desc = desc 31 | 32 | # 视频和音频 33 | self.video_clip = None 34 | self.audio = None 35 | 36 | # 视频的宽、高 37 | self.video_width = 0 38 | self.video_height = 0 39 | 40 | # 视频的默认帧率、时长 41 | self.fps = 25 42 | self.time = 10 43 | 44 | # 文字字体属性,包含:字体大小、字体颜色、字体位置,一般是上(0)下(1) 45 | self.font_params = { 46 | 'size': 50, 47 | 'color': 'red', 48 | 'kerning': 5, 49 | 'position': 0 50 | } 51 | 52 | def run(self): 53 | # 1、获取视频的属性数据,并裁掉抖音最后加入的素材数据(抖音记录美好生活,一般为3s) 54 | self.__pre() 55 | 56 | # 2、获取视频的某一帧,去PS量取视频的左上角、右下角坐标 57 | position1, position2 = self.__get_frame(3) 58 | 59 | # 3、对原视频进行一次剪辑,重新产生一个横向的视频 60 | croped_video_clip = self.video_crop(position1, position2, './source/temp_source_croped.mp4') 61 | 62 | print(f'视频宽为:{self.video_width},高为:{self.video_height},帧率:{self.fps},时长:{self.time}') 63 | 64 | # 4、一张图片组成背景视频,和视频同帧率、同时长 65 | image_video_clip = one_pic_to_video('./source/1.jpeg', './source/temp_img_video.mp4', self.fps, self.time) 66 | 67 | # 5、合成两段视频 68 | synthetic_video_clip = synthetic_video(image_video_clip, croped_video_clip) 69 | 70 | # 6、生成描述信息 71 | desc_text_clip = generate_text_clip(self.desc, self.font_params, synthetic_video_clip.duration) 72 | 73 | # 7、视频加入描述信息 74 | video_with_text_clip = video_with_text(synthetic_video_clip, desc_text_clip) 75 | 76 | # 8、视频加入音频,并删除临时文件 77 | self.video_with_audio(video_with_text_clip) 78 | 79 | def video_crop(self, position1, position2, croped_video_path): 80 | """ 81 | 视频裁剪 82 | :return: 83 | """ 84 | # 裁剪的坐标,包含左上角x轴和y轴;右下角x轴和y轴 85 | clip2 = fx.all.crop(self.video_clip, x1=position1[0], y1=position1[1], x2=position2[0], y2=position2[1]) 86 | 87 | # 保存文件 88 | clip2.write_videofile(croped_video_path) 89 | 90 | # 时长 91 | self.time = clip2.duration 92 | 93 | return clip2 94 | 95 | def __pre(self): 96 | """ 97 | 准备工作 98 | :return: 99 | """ 100 | self.video_raw_clip = VideoFileClip(self.video_raw_path) 101 | 102 | # 视频宽、高 103 | self.video_width, self.video_height = self.video_raw_clip.w, self.video_raw_clip.h 104 | 105 | self.fps = self.video_raw_clip.fps 106 | 107 | # 分离出音频 108 | self.audio = self.video_raw_clip.audio.subclip(0, self.video_raw_clip.duration - 4) 109 | 110 | # 裁剪尾部的视频素材 111 | temp_video_clip = self.video_raw_clip.subclip(0, self.video_raw_clip.duration - 4) 112 | 113 | # 生成新的视频,并保存到本地 114 | temp_video_clip.set_audio(self.audio) 115 | 116 | video_path = './source/temp_source_video.mp4' 117 | 118 | temp_video_clip.write_videofile(video_path, codec='libx264', 119 | audio_codec='aac', 120 | temp_audiofile='temp-audio.m4a', 121 | remove_temp=True) 122 | 123 | # 重新初始化VideoFileClip,便于后面剪辑 124 | self.video_clip = VideoFileClip(video_path) 125 | 126 | def __get_frame(self, time): 127 | """ 128 | 获取视频的某一帧 129 | :param time: 130 | :return: 131 | """ 132 | # get_frame_from_video(self.path_video_source, time, './output.jpg') 133 | 134 | # 获取要截取视频的左上坐标、右下坐标(328 631) 135 | # position1 = (0, 328) 136 | # position2 = (self.video_width, 631) 137 | 138 | # 注意:坐标值必须为偶数,不然会裁剪失败 139 | position1 = (0, 328) 140 | position2 = (self.video_width, 630) 141 | 142 | return position1, position2 143 | 144 | def video_with_audio(self, video_with_text_clip): 145 | """ 146 | 视频合成音频,并删除临时文件 147 | :return: 148 | """ 149 | # 设置视频音频,并写入到文件中去 150 | video_with_text_clip.set_audio(self.audio).write_videofile("output.mp4", 151 | codec='libx264', 152 | audio_codec='aac', 153 | temp_audiofile='temp-audio.m4a', 154 | remove_temp=True 155 | ) 156 | # 删除所有的临时文件 157 | del_temp_file("./source/") 158 | 159 | 160 | if __name__ == '__main__': 161 | # 原视频文件 162 | video_source = './source/source.mp4' 163 | desc = '我是描述信息' 164 | 165 | video_re = Video_Re(video_source, desc) 166 | video_re.run() 167 | -------------------------------------------------------------------------------- /video_auto/制作GIF视频/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xingag/tools_python/f01d315c46a619828d02ed9327f4264ba3a382d8/video_auto/制作GIF视频/.DS_Store -------------------------------------------------------------------------------- /video_auto/制作GIF视频/gif_make_video.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: v1.0 6 | @author: xag 7 | @license: Apache Licence 8 | @contact: xinganguo@gmail.com 9 | @site: http://www.xingag.top 10 | @software: PyCharm 11 | @file: gif_make_video.py 12 | @time: 5/26/19 22:22 13 | @description:抓取一些gif,转为视频 14 | """ 15 | 16 | from moviepy.editor import * 17 | import cv2 18 | from utils.image_utils import * 19 | import math 20 | 21 | 22 | class Gif_TO_VIDEO(object): 23 | def __init__(self, gifs_path, bgm_path): 24 | # gif目录 25 | self.gifs_path = gifs_path 26 | 27 | # BGM目录 28 | self.bgm_path = bgm_path 29 | 30 | # 视频帧率 31 | self.fps = 10 32 | 33 | self.img_size = (int(1280), int(720)) 34 | 35 | # gif文件临时目录 36 | self.temp_gif_path = './gif_temp/' 37 | 38 | # 视频文件临时目录 39 | self.temp_video_path = './video_temp/' 40 | 41 | # 合成的视频文件完整目录 42 | self.video_output_temp = './video_temp/target_temp.mp4' 43 | 44 | # 最后生成的视频文件目录 45 | self.video_output = './target.mp4' 46 | 47 | def run(self): 48 | # 1.获取所有gif的文件 49 | gifs_path = self.__get_gifs() 50 | 51 | # 2.遍历文件,分别把每个gif转成一个视频 52 | videos_output = [] 53 | for gif_path in gifs_path: 54 | print('要处理的gif文件完整路径是:%s' % gif_path) 55 | 56 | # 获取文件名,不带后缀 57 | filename = get_filePath_fileName_all(gif_path)[2] 58 | 59 | # 输出的文件名 60 | video_name = self.temp_video_path + filename + ".mp4" 61 | 62 | self.__gif_to_video(gif_path, video_name) 63 | 64 | # 所有输入视频片段的完整路径 65 | videos_output.append(video_name) 66 | 67 | # 清空gif文件夹 68 | clean_a_folder(self.temp_gif_path) 69 | 70 | # 3.遍历视频目录,合并成一个视频 71 | self.compound_a_video(videos_output) 72 | print('合成单个视频成功~') 73 | 74 | # 4.合并音视频 75 | self.__add_bgm_to_video() 76 | print('恭喜,合成一条包含BGM的视频成功!!!') 77 | 78 | # 5.清空临时视频文件夹 79 | clean_a_folder(self.temp_video_path) 80 | 81 | def __get_gifs(self): 82 | """ 83 | 获取所有gif列表 84 | :return: 85 | """ 86 | 87 | list_temp = list(map(lambda x: self.gifs_path + x, os.listdir(self.gifs_path))) 88 | 89 | # 排序 90 | list_temp.sort() 91 | 92 | print('排序之后的gif文件列表数据为:') 93 | print(list_temp) 94 | 95 | # 过滤一次,保证获取到的是gif图片 96 | return list(filter(lambda gif_path: gif_path.endswith('.gif'), list_temp)) 97 | 98 | def __gif_to_video(self, gif_path, video_name): 99 | """ 100 | gif转为视频 101 | :param gif_path:源gif的完整路径 102 | :param video_name:视频输出的完整目录 103 | :return: 104 | """ 105 | # 1.把gif图片转为帧图片 106 | get_gif_frames(gif_path, self.temp_gif_path) 107 | 108 | # 2.把帧图片转为固定分辨率的图片 109 | file_paths_pre = os.listdir(self.temp_gif_path) 110 | 111 | file_paths = list(filter(lambda gif_path: gif_path.endswith('.png'), file_paths_pre)) 112 | 113 | for file_path in file_paths: 114 | resize_image(self.temp_gif_path + file_path, self.img_size) 115 | print('图片分辨率批量修改成功!') 116 | 117 | # 3.把图片合并成一个视频 118 | # 注意:如果直接对图片采用固定的帧率处理,会导致最后生成的视频不流畅 119 | source_frame_rate, source_duration = get_gif_info(gif_path) 120 | 121 | print('源gif的帧率为:%f,时长为:%fs' % (source_frame_rate, source_duration)) 122 | 123 | # 4.获取当前视频应该播放的时长 124 | duration = source_duration / (self.fps / source_frame_rate) 125 | 126 | # print('现有视频,帧率设置为:%d,时长应该设置为:%f' % (self.fps, duration)) 127 | 128 | pics_to_video(self.temp_gif_path, video_name, self.fps, duration) 129 | 130 | print('合成视频素材:%s成功' % video_name) 131 | 132 | def compound_a_video(self, videos_path): 133 | """ 134 | 合成一个视频 135 | :param videos_output:视频集合的完整目录 136 | :return: 137 | """ 138 | # 定义一个数组 139 | L = [] 140 | 141 | for video_path in videos_path: 142 | # 载入视频 143 | video = VideoFileClip(video_path) 144 | # 添加到数组 145 | L.append(video) 146 | 147 | # 拼接视频 148 | final_clip = concatenate_videoclips(L) 149 | 150 | # 生成目标视频文件 151 | final_clip.to_videofile(self.video_output_temp, fps=self.fps, remove_temp=False) 152 | 153 | def __add_bgm_to_video(self): 154 | """ 155 | 针对合成的视频,新增BGM 156 | :return: 157 | """ 158 | # 1.音频文件 159 | audioclip = AudioFileClip(self.bgm_path) 160 | 161 | # 2.视频文件 162 | videoclip = VideoFileClip(self.video_output_temp) 163 | 164 | # 3.获取视频和音频的时长 165 | video_time = videoclip.duration 166 | audio_time = audioclip.duration 167 | 168 | print('视频时长:%f,音频时长:%f' % (video_time, audio_time)) 169 | 170 | # 4.对视频或者音频进行裁剪 171 | if video_time > audio_time: 172 | # 视频时长>音频时长,对视频进行截取 173 | videoclip_new = videoclip.subclip(0, audio_time) 174 | audioclip_new = audioclip 175 | else: 176 | # 音频时长>视频时长,对音频进行截取 177 | videoclip_new = videoclip 178 | audioclip_new = audioclip.subclip(0, video_time) 179 | 180 | # 5.视频中加入音频 181 | video_with_new_audio = videoclip_new.set_audio(audioclip_new) 182 | 183 | # 6.写入到新的视频文件中 184 | video_with_new_audio.write_videofile("mp4_with_audio.mp4", 185 | codec='libx264', 186 | audio_codec='aac', 187 | temp_audiofile='temp-audio.m4a', 188 | remove_temp=True 189 | ) 190 | 191 | 192 | if __name__ == '__main__': 193 | # gif目录 194 | gif_path = './gifs/' 195 | 196 | # BGM目录 197 | bgm_path = './backup/dancy.mp3' 198 | 199 | gif_to_video = Gif_TO_VIDEO(gif_path, bgm_path) 200 | 201 | # 把所有gif转换为一个视频 202 | gif_to_video.run() 203 | -------------------------------------------------------------------------------- /video_auto/制作GIF视频/gifs/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xingag/tools_python/f01d315c46a619828d02ed9327f4264ba3a382d8/video_auto/制作GIF视频/gifs/.DS_Store -------------------------------------------------------------------------------- /video_auto/制作GIF视频/utils/file_utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: v1.0 6 | @author: xag 7 | @license: Apache Licence 8 | @contact: xinganguo@gmail.com 9 | @site: http://www.xingag.top 10 | @software: PyCharm 11 | @file: file_utils.py 12 | @time: 5/17/19 10:15 13 | @description:文件操作工具类 14 | """ 15 | import os 16 | import shutil 17 | 18 | 19 | def rename(str): 20 | os.rename(str, './../temp_new1.aac') 21 | 22 | 23 | def get_temp_path(file_path, temp_name): 24 | """ 25 | 获取同一级目录下临时文件的完整路径 26 | :param str: 27 | :return: 28 | """ 29 | filepath, filename_with_extension, filename_without_extension, extension = get_filePath_fileName_all(file_path) 30 | 31 | return filepath + "/" + temp_name + extension 32 | 33 | 34 | def get_filePath_fileName_all(filename): 35 | """ 36 | 获取文件的路径、文件名【带后缀】、文件名【不带后缀】、后缀名 37 | :param filename: 38 | :return: 39 | """ 40 | (filepath, filename_with_extension) = os.path.split(filename) 41 | (filename_without_extension, extension) = os.path.splitext(filename_with_extension) 42 | 43 | return filepath, filename_with_extension, filename_without_extension, extension 44 | 45 | 46 | def create_a_folder(path): 47 | """ 48 | 创建一个文件夹 49 | :param path: 50 | :return: 51 | """ 52 | temp_folder = os.path.exists(path) 53 | if not temp_folder: 54 | os.makedirs(path) 55 | 56 | 57 | def clean_a_folder(path): 58 | """ 59 | 清空某个目录下的所有文件 60 | :param path: 61 | :return: 62 | """ 63 | shutil.rmtree(path) 64 | os.mkdir(path) 65 | 66 | 67 | def time_convert(size): 68 | M, H = 60, 60 ** 2 69 | if size < M: 70 | return str(size) + u'秒' 71 | if size < H: 72 | return u'%s分钟%s秒' % (int(size / M), int(size % M)) 73 | else: 74 | hour = int(size / H) 75 | mine = int(size % H / M) 76 | second = int(size % H % M) 77 | tim_srt = u'%s小时%s分钟%s秒' % (hour, mine, second) 78 | return tim_srt 79 | -------------------------------------------------------------------------------- /video_auto/制作GIF视频/utils/image_utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: v1.0 6 | @author: xag 7 | @license: Apache Licence 8 | @contact: xinganguo@gmail.com 9 | @site: http://www.xingag.top 10 | @software: PyCharm 11 | @file: image_utils.py 12 | @time: 5/17/19 13:03 13 | @description:图像工具类 14 | """ 15 | from PIL import Image 16 | import os 17 | from utils.file_utils import * 18 | from utils.video_utils import * 19 | from imgpy import Img 20 | import json 21 | import re 22 | 23 | # pip3 install imgpy 24 | 25 | 26 | def analyseImage(path): 27 | """ 28 | 分析图片 29 | :param path: 30 | :return: 31 | """ 32 | im = Image.open(path) 33 | results = { 34 | 'size': im.size, 35 | 'mode': 'full', 36 | } 37 | try: 38 | while True: 39 | if im.tile: 40 | tile = im.tile[0] 41 | update_region = tile[1] 42 | update_region_dimensions = update_region[2:] 43 | if update_region_dimensions != im.size: 44 | results['mode'] = 'partial' 45 | break 46 | im.seek(im.tell() + 1) 47 | except EOFError: 48 | pass 49 | return results 50 | 51 | 52 | def get_gif_frames(gif_path, temp_path): 53 | """ 54 | 获取一段GIf图片下的所有静态帧 55 | get_gif_frames('./../gifs/3.gif', './../gif_temp/') 56 | :param gif_path: 57 | :return: 58 | """ 59 | 60 | # 分析gif图片 61 | mode = analyseImage(gif_path)['mode'] 62 | 63 | im = Image.open(gif_path) 64 | 65 | i = 1 66 | p = im.getpalette() 67 | last_frame = im.convert('RGBA') 68 | 69 | try: 70 | while True: 71 | # print("saving %s (%s) frame %d, %s %s" % (gif_path, mode, i, im.size, im.tile)) 72 | 73 | ''' 74 | If the GIF uses local colour tables, each frame will have its own palette. 75 | If not, we need to apply the global palette to the new frame. 76 | ''' 77 | if not im.getpalette(): 78 | im.putpalette(p) 79 | 80 | new_frame = Image.new('RGBA', im.size) 81 | 82 | ''' 83 | Is this file a "partial"-mode GIF where frames update a region of a different size to the entire image? 84 | If so, we need to construct the new frame by pasting it on top of the preceding frames. 85 | ''' 86 | if mode == 'partial': 87 | new_frame.paste(last_frame) 88 | 89 | new_frame.paste(im, (0, 0), im.convert('RGBA')) 90 | new_frame.save(temp_path + '/%s-%d.png' % (''.join(os.path.basename(gif_path).split('.')[:-1]), i), 'PNG') 91 | 92 | i += 1 93 | last_frame = new_frame 94 | im.seek(im.tell() + 1) 95 | except EOFError: 96 | # print('产生EOFError!!!') 97 | pass 98 | 99 | 100 | def get_gif_info(gif_path): 101 | """ 102 | 获取gif文件的详细信息 103 | 每一个gif的帧率不一样,有的<10fps;有的>10fps 104 | :param gif_path: 105 | :return: 106 | """ 107 | with Img(fp=gif_path) as im: 108 | # 1.有多少帧 109 | frame_count = im.frame_count 110 | 111 | # 2.帧列表-PIL.Image.Image 112 | # print(im.frames) 113 | 114 | # 3.未知 115 | # print(im.exif) 116 | 117 | # 4.GIF 118 | # print(im.format) 119 | 120 | # 5.图片信息 121 | # {'version': b'GIF89a', 'background': 31, 'duration': 70, 'extension': (b'NETSCAPE2.0', 795), 'loop': 0} 122 | duration_pre = im.info.get('duration') 123 | 124 | # 根据规律,除以7位实际的播放时长 125 | duration = duration_pre / 7 126 | 127 | # 6.color palette 128 | # print(im.mode_desc) 129 | 130 | # print((frame_count, duration)) 131 | 132 | # 返回帧率和时长 133 | return (frame_count / duration), duration -------------------------------------------------------------------------------- /video_auto/制作GIF视频/utils/math_utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: v1.0 6 | @author: xag 7 | @license: Apache Licence 8 | @contact: xinganguo@gmail.com 9 | @site: http://www.xingag.top 10 | @software: PyCharm 11 | @file: math_utils.py 12 | @time: 5/29/19 21:01 13 | @description:数学公式工具类 14 | """ 15 | 16 | import re 17 | 18 | re_digits = re.compile(r'(\d+)') 19 | 20 | 21 | def emb_numbers(s): 22 | pieces = re_digits.split(s) 23 | pieces[1::2] = map(int, pieces[1::2]) 24 | return pieces 25 | 26 | 27 | # 根据字符串中的数字排序,如f10应该在f2后面 28 | def sort_strings_with_emb_numbers(alist): 29 | """ 30 | DSU排序 31 | :param alist: 32 | :return: 33 | """ 34 | aux = [(emb_numbers(s), s) for s in alist] 35 | aux.sort() 36 | return [s for __, s in aux] 37 | 38 | 39 | def sort_strings_with_emb_numbers2(alist): 40 | """ 41 | 内置DSU排序 42 | :param alist: 43 | :return: 44 | """ 45 | return sorted(alist, key=emb_numbers) 46 | -------------------------------------------------------------------------------- /video_auto/制作GIF视频/utils/video_utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: v1.0 6 | @author: xag 7 | @license: Apache Licence 8 | @contact: xinganguo@gmail.com 9 | @site: http://www.xingag.top 10 | @software: PyCharm 11 | @file: video_utils.py 12 | @time: 5/28/19 21:08 13 | @description:视频工具类 14 | """ 15 | 16 | from moviepy.editor import * 17 | from utils.math_utils import * 18 | 19 | 20 | def pics_to_video(pics_path, output_path, fps, duration): 21 | """ 22 | 图片转为视频 23 | pics_to_video('./../gif_temp/', './../video_temp/temp1.mp4', 20) 24 | :param pics_path: 25 | :param output_path: 26 | :return: 27 | """ 28 | image_paths = list(map(lambda x: pics_path + x, os.listdir(pics_path))) 29 | 30 | # 注意:这里必须进行一次排序,保证所有帧的顺序是一致 31 | image_paths = sort_strings_with_emb_numbers(image_paths) 32 | 33 | # 过滤掉非图片 34 | image_paths = list(filter(lambda image_path: image_path.endswith('.png'), image_paths)) 35 | 36 | # 图片剪辑类 37 | clip = ImageSequenceClip(image_paths, 38 | fps=fps) 39 | 40 | # 写成视频之前,需要把gif都转成同一个分辨率 41 | clip.write_videofile(output_path) 42 | -------------------------------------------------------------------------------- /video_auto/卡点视频/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xingag/tools_python/f01d315c46a619828d02ed9327f4264ba3a382d8/video_auto/卡点视频/.DS_Store -------------------------------------------------------------------------------- /video_auto/卡点视频/images/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xingag/tools_python/f01d315c46a619828d02ed9327f4264ba3a382d8/video_auto/卡点视频/images/.DS_Store -------------------------------------------------------------------------------- /video_auto/卡点视频/readme.md: -------------------------------------------------------------------------------- 1 | # 使用 Python 制作卡点视频 2 | 3 | ## 准备 4 | 5 | 1. 一段视频 6 | 2. 一段卡点的 BGM 7 | 3. 多张图片 8 | 9 | 10 | 11 | ## 依赖 12 | 13 | ``` 14 | pip3 install opencv-python 15 | 16 | pip3 install ffmpeg 17 | ``` 18 | 19 | 20 | 21 | ## 步骤 22 | 23 | 1. 剪辑视频的某部分 2s 24 | 2. 16 张静态图片生成一段视频 8s 25 | 3. 上面两段视频合成一段视频 26 | 4. 添加水印 27 | 5. 剪辑背景音乐,保证是视频时长一致 28 | 6. 合并视频和背景音乐,生成一个卡点视频 -------------------------------------------------------------------------------- /video_auto/卡点视频/utils/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xingag/tools_python/f01d315c46a619828d02ed9327f4264ba3a382d8/video_auto/卡点视频/utils/.DS_Store -------------------------------------------------------------------------------- /video_auto/卡点视频/utils/file_utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: v1.0 6 | @author: xag 7 | @license: Apache Licence 8 | @contact: xinganguo@gmail.com 9 | @site: http://www.xingag.top 10 | @software: PyCharm 11 | @file: file_utils.py 12 | @time: 5/17/19 10:15 13 | @description:TODO 14 | """ 15 | import os 16 | 17 | 18 | def rename(str): 19 | os.rename(str, './../temp_new1.aac') 20 | 21 | 22 | def get_temp_path(file_path, temp_name): 23 | """ 24 | 获取同一级目录下临时文件的完整路径 25 | :param str: 26 | :return: 27 | """ 28 | filepath, filename_with_extension, filename_without_extension, extension = get_filePath_fileName_all(file_path) 29 | 30 | return filepath + "/" + temp_name + extension 31 | 32 | 33 | def get_filePath_fileName_all(filename): 34 | """ 35 | 获取文件的路径、文件名【带后缀】、文件名【不带后缀】、后缀名 36 | :param filename: 37 | :return: 38 | """ 39 | (filepath, filename_with_extension) = os.path.split(filename) 40 | (filename_without_extension, extension) = os.path.splitext(filename_with_extension) 41 | 42 | return filepath, filename_with_extension, filename_without_extension, extension 43 | -------------------------------------------------------------------------------- /video_auto/卡点视频/utils/image_utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: v1.0 6 | @author: xag 7 | @license: Apache Licence 8 | @contact: xinganguo@gmail.com 9 | @site: http://www.xingag.top 10 | @software: PyCharm 11 | @file: image_utils.py 12 | @time: 5/17/19 13:03 13 | @description:图像工具类 14 | """ 15 | from PIL import Image 16 | 17 | 18 | def resize_image(target_image_path, target_size): 19 | """ 20 | 调整图片大小,缺失的部分用黑色填充 21 | :param target_image_path: 图片路径 22 | :param target_size: 分辨率大小 23 | :return: 24 | """ 25 | image = Image.open(target_image_path) 26 | 27 | iw, ih = image.size # 原始图像的尺寸 28 | w, h = target_size # 目标图像的尺寸 29 | scale = min(w / iw, h / ih) # 转换的最小比例 30 | 31 | # 保证长或宽,至少一个符合目标图像的尺寸 32 | nw = int(iw * scale) 33 | nh = int(ih * scale) 34 | 35 | image = image.resize((nw, nh), Image.BICUBIC) # 缩小图像 36 | # image.show() 37 | 38 | new_image = Image.new('RGB', target_size, (0, 0, 0, 0)) # 生成黑色图像 39 | # // 为整数除法,计算图像的位置 40 | new_image.paste(image, ((w - nw) // 2, (h - nh) // 2)) # 将图像填充为中间图像,两侧为灰色的样式 41 | # new_image.show() 42 | 43 | # 覆盖原图片 44 | new_image.save(target_image_path) 45 | -------------------------------------------------------------------------------- /video_auto/卡点视频/video_cut_cv2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: v1.0 6 | @author: xag 7 | @license: Apache Licence 8 | @contact: xinganguo@gmail.com 9 | @site: http://www.xingag.top 10 | @software: PyCharm 11 | @file: video_cut.py 12 | @time: 5/16/19 17:18 13 | @description:Python 生成一段10s的卡点视频 14 | """ 15 | 16 | import cv2 17 | import numpy as np 18 | import time 19 | import os 20 | import math 21 | from utils.file_utils import * 22 | from utils.image_utils import * 23 | from PIL import Image 24 | 25 | # 最后影片的分辨率片,根据视频来设置,默认是1920*1080 26 | img_size = (int(1920), int(1080)) 27 | 28 | 29 | def cut_film(soure_filename, output_filename, start_time, peroid): 30 | """ 31 | 裁剪视频 32 | :param start_time: 开始时间【s】 33 | :param peroid: 持续时间【s】 34 | :param soure_filename 源视频 35 | :param output_filename 输出视频 36 | :return: 37 | """ 38 | global img_size 39 | 40 | # 数据源 41 | videoCapture = cv2.VideoCapture(soure_filename) 42 | 43 | if videoCapture.isOpened(): 44 | print('open success') 45 | else: 46 | print('open fail') 47 | 48 | fps = videoCapture.get(cv2.CAP_PROP_FPS) 49 | 50 | # 获取大小(1920*1080) 51 | img_size = (int(videoCapture.get(cv2.CAP_PROP_FRAME_WIDTH)), 52 | int(videoCapture.get(cv2.CAP_PROP_FRAME_HEIGHT))) 53 | 54 | video_writer = cv2.VideoWriter(output_filename, cv2.VideoWriter_fourcc('X', 'V', 'I', 'D'), fps, img_size) 55 | 56 | i = 0 57 | 58 | # 开始帧和结束帧 59 | start_frame = fps * start_time 60 | end_frame = start_frame + peroid * fps 61 | 62 | print(type(start_frame)) 63 | print('即将裁剪的开始帧为:%f,结束帧为:%f' % (start_frame, end_frame)) 64 | 65 | while True: 66 | success, frame = videoCapture.read() # 循环读取下一帧 67 | 68 | if success: 69 | i += 1 70 | if start_frame <= i <= end_frame: 71 | # 将截取到的画面写入“新视频” 72 | video_writer.write(frame) 73 | else: 74 | print('end') 75 | break 76 | 77 | videoCapture.release() 78 | 79 | return fps 80 | 81 | 82 | def compound_film(film_names, video_output_path): 83 | """ 84 | 合并视频 85 | :param film_names: 86 | :return: 87 | """ 88 | print('一共要合成%d段视频' % len(film_names)) 89 | print(film_names) 90 | 91 | # 通过第一个视频,获取帧率、宽、高 92 | videoCaptureNorm = cv2.VideoCapture(film_names[0]) 93 | 94 | # 帧率、宽、高 95 | fps = videoCaptureNorm.get(cv2.CAP_PROP_FPS) 96 | width = (int(videoCaptureNorm.get(cv2.CAP_PROP_FRAME_WIDTH))) 97 | height = (int(videoCaptureNorm.get(cv2.CAP_PROP_FRAME_HEIGHT))) 98 | 99 | # ===================================================================== 100 | # 待写入对象,写入到result.mp4文件中 101 | videoWriter = cv2.VideoWriter(video_output_path, cv2.VideoWriter_fourcc('m', 'p', '4', 'v'), fps, (width, height)) 102 | 103 | for index, film_name in enumerate(film_names): 104 | print('视频名称:%s' % film_name) 105 | videoCapture = cv2.VideoCapture(film_name) 106 | 107 | # 循环写入数据 108 | while True: 109 | success, frame = videoCapture.read() 110 | 111 | # 视频必须保证分辨率一致,才能合并 112 | # frame_suitable = cv2.resizeWindow(frame, (width, height), interpolation=cv2.INTER_CUBIC) 113 | videoWriter.write(frame) 114 | if success: 115 | continue 116 | else: 117 | break 118 | 119 | videoCapture.release() 120 | print('第%d个视频合成完成~' % (index + 1)) 121 | 122 | videoWriter.release() 123 | videoCaptureNorm.release() 124 | 125 | 126 | def compound_pic_special(images_path, output_video_path, fps): 127 | """ 128 | 图片合成视频【卡点视频】 129 | :param path: 图片文件路径 130 | :param output_video_path:合成视频的路径 131 | :param size: 132 | :return: 133 | """ 134 | 135 | # 获取该目录下的所有文件名 136 | filelist = os.listdir(images_path) 137 | 138 | print('一共有%d张图片' % len(filelist)) 139 | 140 | fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v') 141 | 142 | video = cv2.VideoWriter(output_video_path, fourcc, fps, img_size) 143 | 144 | total_count = math.ceil(fps / 2) 145 | print('每张图片都需要重复写入%d帧' % total_count) 146 | 147 | for item in filelist: 148 | if item.endswith('.jpg'): # 判断图片后缀是否是.jpg 149 | image_path = images_path + '/' + item 150 | 151 | # 缩放图片到合适的分辨率,并覆盖源文件 152 | resize_image(image_path, img_size) 153 | 154 | frame = cv2.imread(image_path) 155 | 156 | # 直接缩放到指定大小 157 | frame_suitable = cv2.resize(frame, (img_size[0], img_size[1]), interpolation=cv2.INTER_CUBIC) 158 | 159 | # 把图片写进视频 160 | # 重复写入多少次 161 | count = 0 162 | while count < total_count: 163 | video.write(frame_suitable) 164 | count += 1 165 | else: 166 | print('名称为:%s,文件格式不对,过滤掉~' % item) 167 | # 释放资源 168 | video.release() 169 | 170 | 171 | def compound_bgm(video_path, bgm_path): 172 | """ 173 | 通过视频、BGM 合成一段视频 174 | :param video_path: 视频路径 175 | :param bgm_path: BGM路径 176 | :return: 177 | """ 178 | # $ ffmpeg -i 2_003_014.mp4 -vn -y -acodec copy 3.aac 179 | # 1.提前temp.mp4这个视频的BGM,文件结果为:temp.aac 180 | # os.system('ffmpeg -i temp.mp4 -vn -y -acodec copy temp.aac') 181 | 182 | # 2.获取视频的长度 183 | cap = cv2.VideoCapture(video_path) 184 | # 帧率 185 | fps = cap.get(cv2.CAP_PROP_FPS) 186 | # 总帧数 187 | frame_count = cap.get(cv2.CAP_PROP_FRAME_COUNT) 188 | # 总时长-秒,这里做取整操作 【浮点类型】 189 | time_count = math.floor(frame_count / fps) 190 | 191 | print('帧率:%f,总帧数:%d' % (fps, frame_count)) 192 | print(time_count) 193 | 194 | # 3.截取音频 195 | # 为了简单,这里一般不会超过一分钟 196 | bgm_temp_path = get_temp_path(bgm_path, 'temp_new') 197 | os.system('ffmpeg -i %s -ss 00:00:00 -t 00:00:%d -acodec copy %s' % (bgm_path, time_count, bgm_temp_path)) 198 | 199 | # 3.1 删除源音频并重命令当前文件 200 | os.remove(bgm_path) 201 | os.rename(bgm_temp_path, bgm_path) 202 | 203 | # 4.视频、音频合二为一 204 | video_temp_path = get_temp_path(video_path, 'temp') 205 | os.system('ffmpeg -i %s -i %s -vcodec copy -acodec copy %s' % (video_path, bgm_path, video_temp_path)) 206 | os.remove(video_path) 207 | os.rename(video_temp_path, video_path) 208 | 209 | print('音视频合成完成~') 210 | 211 | 212 | def add_water_mask(video_path, mask_word): 213 | """ 214 | 给视频增加水印 215 | :param video_part3: 视频源 216 | :param mask_word: 水印文字 217 | :return: 218 | """ 219 | cap = cv2.VideoCapture(video_path) 220 | fps = cap.get(cv2.CAP_PROP_FPS) 221 | 222 | # 保证帧率不变 223 | fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v') 224 | video_temp_path = get_temp_path(video_path, 'temp') 225 | video_writer = cv2.VideoWriter(video_temp_path, fourcc, fps, img_size) 226 | 227 | ret, frame = cap.read() 228 | 229 | while ret: 230 | # 文字在图中的坐标(注意:这里的坐标原点是图片左上角) 231 | x, y = img_size[0] - 200, img_size[1] - 50 232 | 233 | cv2.putText(img=frame, text=mask_word, 234 | org=(x, y), fontFace=cv2.FONT_HERSHEY_COMPLEX_SMALL, 235 | fontScale=1, color=(255, 255, 255)) 236 | 237 | video_writer.write(frame) 238 | ret, frame = cap.read() 239 | 240 | # 删除源文件,并重命名临时文件 241 | os.remove(video_path) 242 | os.rename(video_temp_path, video_path) 243 | 244 | print('水印添加完成~') 245 | video_writer.release() 246 | cap.release() 247 | 248 | 249 | def make_sticke_point_video(source_film_path, source_image_path, source_bgm_path): 250 | """ 251 | 制作卡点视频 252 | :return: 253 | """ 254 | # 说明:一共10s 255 | # 素材:一段2s的视频,然后准备16张图片,每张图片显示0.5s 256 | 257 | # 1.从一段视频中剪辑前2s的视频 258 | # 也可以指定开始帧和持续时间 259 | # 生成的第一段视频2s 260 | video_part1 = './part1.mp4' 261 | fps = cut_film(source_film_path, video_part1, 3, 2) 262 | print('第一段视频剪辑完成~') 263 | print('第一段视频的帧率是:%f' % fps) 264 | 265 | # 2.把所有图片合成一个视频 266 | # 分析:一张图片0.5s,一共16张图片,一共8s;1张图片1帧,fps:2帧/s 267 | # fps:2 1秒2帧 268 | # 注意:由于最后合成的一段视频帧率为同一个值,这里设置需要一张图片写入30帧,即30次 269 | video_part2 = './part2.mp4' 270 | compound_pic_special(source_image_path, video_part2, fps) 271 | print('第二段视频合成完成~') 272 | 273 | # 3.把两段视频拼接在一起 274 | video_part3 = './output.mp4' 275 | compound_film([video_part1, video_part2], video_part3) 276 | print('上面两段视频合并完成~') 277 | 278 | # 4.给视频加入水印 279 | add_water_mask(video_part3, '@xingag') 280 | 281 | # 5.加入背景音乐 282 | compound_bgm(video_part3, source_bgm_path) 283 | 284 | 285 | if __name__ == '__main__': 286 | # 制作卡点视频 287 | source_film_path = './1.mp4' 288 | source_image_path = './images' 289 | source_bgm_path = './bgm.aac' 290 | make_sticke_point_video(source_film_path, source_image_path, source_bgm_path) 291 | -------------------------------------------------------------------------------- /一键升级/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xingag/tools_python/f01d315c46a619828d02ed9327f4264ba3a382d8/一键升级/.DS_Store -------------------------------------------------------------------------------- /一键升级/auto_rar.au3: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | ; 注意:对于非标准控件,没法利用Autoit的拿到控件的属性 4 | 5 | ; 选中窗口 6 | ;$handle = WinGetHandle("一键升级","") 7 | $handle = WinGetHandle("source","") 8 | 9 | WinActivate($handle) 10 | 11 | ;全选 12 | Send("^a") 13 | 14 | 15 | ;右键 16 | MouseClick("right") 17 | 18 | ; 点击A,相当于选择[添加到压缩文件] 19 | Send('A') 20 | 21 | 22 | $handle = WinGetHandle("压缩文件名和参数","") 23 | WinActivate($handle) 24 | 25 | Sleep(1000) 26 | 27 | ; 压缩选中自解压文件 28 | ControlClick("压缩文件名和参数","","Button9") 29 | 30 | 31 | Sleep(1000) 32 | 33 | 34 | ;跳到高级Tab 35 | Send("^{TAB}") 36 | 37 | Sleep(1000) 38 | 39 | ;点击自解压选项 40 | ControlClick("压缩文件名和参数","","Button11") 41 | 42 | Sleep(1000) 43 | 44 | ;跳到设置Tab 45 | Send("^{TAB}") 46 | 47 | Sleep(1000) 48 | 49 | ;提取后运行的程序 50 | ControlSetText("高级自解压选项","","Edit1","DGUpdata.exe") 51 | 52 | Sleep(1000) 53 | 54 | ;等待退出 55 | ControlClick("高级自解压选项","","Button2") 56 | 57 | Sleep(1000) 58 | 59 | ;跳到模式Tab 60 | Send("^{TAB}") 61 | 62 | Sleep(1000) 63 | 64 | ;临时文件夹及隐藏 65 | ControlClick("高级自解压选项","","Button2") 66 | 67 | Sleep(1000) 68 | 69 | ControlClick("高级自解压选项","","Button6") 70 | 71 | Sleep(1000) 72 | 73 | 74 | ; 调到文本和图标Tab,执行3次Tab键 75 | Send("^{TAB 3}") 76 | 77 | Sleep(1000) 78 | 79 | ;点击浏览按钮 80 | ControlClick("高级自解压选项","","Button4") 81 | 82 | ;选择文件 update.ico 83 | ControlFocus("选择图标","","Edit1") 84 | 85 | 86 | Sleep(1000) 87 | 88 | ControlSetText("选择图标","","Edit1","update.ico") 89 | 90 | Sleep(1000) 91 | 92 | ; 确定选择文件 93 | ControlClick("选择图标","","Button1") 94 | 95 | ;调到许可Tab 96 | Sleep(1000) 97 | Send("^{TAB}") 98 | 99 | 100 | ;模拟点击回车键,执行压缩操作 101 | Send("{ENTER}") 102 | 103 | Send("^{TAB}") 104 | 105 | Send("{ENTER}") 106 | 107 | ; 点击确定 108 | ; 注意:由于【确定按钮】的ClassNameNN经常会变化,没有办法用下面的方法执行 109 | ; ControlClick("高级自解压选项","","Button7") 110 | 111 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /一键升级/auto_rar.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xingag/tools_python/f01d315c46a619828d02ed9327f4264ba3a382d8/一键升级/auto_rar.exe -------------------------------------------------------------------------------- /发送邮件/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /发送邮件/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /发送邮件/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /发送邮件/.idea/发送邮件.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /发送邮件/attachments/report.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xingag/tools_python/f01d315c46a619828d02ed9327f4264ba3a382d8/发送邮件/attachments/report.png -------------------------------------------------------------------------------- /发送邮件/email_by_smtplib.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: v1.0 6 | @author: xag 7 | @license: Apache Licence 8 | @contact: xinganguo@gmail.com 9 | @site: http://www.xingag.top 10 | @software: PyCharm 11 | @file: email_by_smtplib.py 12 | @time: 2020-07-09 21:30 13 | @description:smtplib发送邮件 14 | """ 15 | 16 | import os 17 | import smtplib 18 | from email.mime.multipart import MIMEMultipart 19 | from email.mime.text import MIMEText 20 | 21 | 22 | class SmtplibObject(object): 23 | 24 | def __init__(self): 25 | # 初始化 26 | self.smtp = smtplib.SMTP() 27 | # 连接邮箱服务器地址 28 | self.smtp.connect('smtp.126.com') 29 | 30 | # 加入主题和附件,邮件体 31 | self.email_body = MIMEMultipart('mixed') 32 | 33 | # 发件人地址及授权码 34 | self.email_from_username = '**@126.com' 35 | self.email_from_password = '授权码' 36 | 37 | def send_email(self, email_to_list, email_title, email_content, attchment_path, files): 38 | """ 39 | 发送邮件 40 | :return: 41 | """ 42 | # 组成邮件体 43 | self.generate_email_body(email_to_list, email_title, email_content, attchment_path, files) 44 | 45 | # 登录邮箱 46 | # 参数为账号和密码(授权码) 47 | self.smtp.login(self.email_from_username, self.email_from_password) 48 | 49 | # 发送邮件 50 | # 注意:此处必须同时指定发件人与收件人,否则会当作垃圾邮件处理掉 51 | self.smtp.sendmail(self.email_from_username, email_to_list, self.email_body.as_string()) 52 | 53 | def generate_email_body(self, email_to_list, email_title, email_content, attchment_path, files): 54 | """ 55 | 组成邮件体 56 | :param email_to_list:收件人列表 57 | :param email_title:邮件标题 58 | :param email_content:邮件正文内容 59 | :param attchment_path:附件的路径 60 | :param files:附件文件名列表 61 | :return: 62 | """ 63 | self.email_body['Subject'] = email_title 64 | self.email_body['From'] = self.email_from_username 65 | self.email_body['To'] = ",".join(email_to_list) 66 | 67 | for file in files: 68 | file_path = attchment_path + '/' + file 69 | if os.path.isfile(file_path): 70 | # 构建一个附件对象 71 | att = MIMEText(open(file_path, 'rb').read(), 'base64', 'utf-8') 72 | att["Content-Type"] = 'application/octet-stream' 73 | att.add_header("Content-Disposition", "attachment", filename=("gbk", "", file)) 74 | self.email_body.attach(att) 75 | 76 | text_plain = MIMEText(email_content, 'plain', 'utf-8') 77 | self.email_body.attach(text_plain) 78 | 79 | def exit(self): 80 | """ 81 | 退出服务 82 | :return: 83 | """ 84 | self.smtp.quit() 85 | 86 | 87 | if __name__ == '__main__': 88 | # 收件人列表 89 | email_to_list = ['**@qq.com'] 90 | email_title = "测试报告" 91 | email_content = '这是测试报告具体内容' 92 | # 附件路径 93 | attchment_path = './attachments/' 94 | # 附件文件列表 95 | attchment_files = ['report.png', 'config.json'] 96 | 97 | # 发送邮件 98 | smtplib_object = SmtplibObject() 99 | smtplib_object.send_email(email_to_list, email_title, email_content, attchment_path, attchment_files) 100 | 101 | # 退出 102 | smtplib_object.exit() 103 | -------------------------------------------------------------------------------- /发送邮件/email_by_yagmail.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: v1.0 6 | @author: xag 7 | @license: Apache Licence 8 | @contact: xinganguo@gmail.com 9 | @site: http://www.xingag.top 10 | @software: PyCharm 11 | @file: email_by_yagmail.py 12 | @time: 2020-07-09 21:07 13 | @description:yagmail发送/接受邮件 14 | """ 15 | 16 | # 依赖:pip3 install yagmail 17 | # 公众号:AirPython 18 | 19 | import yagmail 20 | 21 | # 连接服务器 22 | # 用户名、授权码、服务器地址 23 | yag_server = yagmail.SMTP(user='**@126.com', password='授权码', host='smtp.126.com') 24 | 25 | # 发送邮件 26 | # 发送对象列表 27 | email_to = ['**@qq.com', ] 28 | email_title = '测试报告' 29 | email_content = "这是测试报告的具体内容" 30 | # 附件列表 31 | email_attachments = ['./attachments/report.png', ] 32 | yag_server.send(email_to, email_title, email_content, email_attachments) 33 | 34 | # 关闭连接 35 | yag_server.close() 36 | -------------------------------------------------------------------------------- /发送邮件/email_by_zmail.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: v1.0 6 | @author: xag 7 | @license: Apache Licence 8 | @contact: xinganguo@gmail.com 9 | @site: http://www.xingag.top 10 | @software: PyCharm 11 | @file: email_by_zmail.py 12 | @time: 2020-07-09 19:57 13 | @description:zmail发送邮件 14 | """ 15 | 16 | # 依赖 17 | # pip3 install zmail 18 | # 公众号:AirPython 19 | 20 | import zmail 21 | 22 | 23 | class ZMailObject(object): 24 | 25 | def __init__(self): 26 | # 邮箱账号 27 | self.username = '**@126.com' 28 | 29 | # 邮箱授权码 30 | self.authorization_code = '授权码' 31 | 32 | # 构建一个邮箱服务对象 33 | self.server = zmail.server(self.username, self.authorization_code) 34 | 35 | def send_email(self, mail_to, mail_body): 36 | """ 37 | 发送邮件 38 | :param mail_to 发送对象 39 | :param mail: 发送主题、内容及附件 40 | :return: 41 | """ 42 | 43 | if self.__check_pop_enable() and self.__check_smtp_enable(): 44 | self.server.send_mail(mail_to, mail_body) 45 | else: 46 | pass 47 | 48 | def receive_email(self): 49 | """ 50 | 接受邮件 51 | :return: 52 | """ 53 | try: 54 | # 接受邮件 55 | last_mail = self.server.get_latest() 56 | # last_mail = self.server.get_mail(2) 57 | # zmail.show(last_mail) 58 | for k, v in last_mail.items(): 59 | print(k, v) 60 | except Exception as e: 61 | # 收件箱为空,则会报错 62 | print('接受异常异常') 63 | 64 | def __check_smtp_enable(self): 65 | """ 66 | 检查smtp是否正常 67 | :return: 68 | """ 69 | return self.server.smtp_able() 70 | 71 | def __check_pop_enable(self): 72 | """ 73 | 检查pop功能是否正常 74 | :return: 75 | """ 76 | return self.server.pop_able() 77 | 78 | 79 | if __name__ == '__main__': 80 | zmail_obj = ZMailObject() 81 | 82 | # 发送内容及附件 83 | mail_to = '**@qq.com' 84 | 85 | # 邮件主体 86 | mail_body = { 87 | 'subject': '测试报告', 88 | 'content_text': '这是一个测试报告', # 纯文本或者HTML内容 89 | 'attachments': ['./attachments/report.png'], 90 | } 91 | 92 | # 发送邮件 93 | zmail_obj.send_email(mail_to, mail_body) 94 | 95 | # 接受邮件 96 | # zmail_obj.receive_email() 97 | -------------------------------------------------------------------------------- /图片视频九宫格/.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | -------------------------------------------------------------------------------- /图片视频九宫格/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /图片视频九宫格/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /图片视频九宫格/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /图片视频九宫格/.idea/图片视频九宫格.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /图片视频九宫格/ninecell_image.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: v1.0 6 | @author: xag 7 | @license: Apache Licence 8 | @contact: xinganguo@gmail.com 9 | @site: http://www.xingag.top 10 | @software: PyCharm 11 | @file: ninecell_image.py 12 | @time: 2020-08-28 22:20 13 | @description:九宫格图片朋友圈 14 | """ 15 | 16 | from PIL import Image 17 | 18 | 19 | class ImageObj(object): 20 | def __init__(self): 21 | pass 22 | 23 | def start(self, file_path): 24 | # 1、打开图片 25 | image = Image.open(file_path) 26 | 27 | # 2、将图片填充为正方形,组成:原图+百底+正方形 28 | image = self.__fill_image(image) 29 | 30 | # 保存图片 31 | image.save('temp.jpg') 32 | 33 | # 3、裁剪合成后图片,为9张图片 34 | nine_images = self.__cut_image(image) 35 | 36 | # 4、保存9张图片到本地 37 | self.save_images(nine_images) 38 | 39 | def __cut_image(self, image): 40 | """ 41 | 把一张图片裁剪成9张图片的坐标值,然后进行裁剪成小图片 42 | :return: 43 | """ 44 | width, height = image.size 45 | 46 | # 6000 47 | print('原图的宽/高分别为:', width, height) 48 | 49 | # 2000 50 | # 每张图片的宽度 51 | item_width = int(width / 3) 52 | 53 | print('裁剪后的宽度为:', item_width) 54 | 55 | box_list = [] 56 | 57 | for i in range(0, 3): 58 | for j in range(0, 3): 59 | # 坐标值分别是:左、上、右、底 60 | box = (j * item_width, i * item_width, (j + 1) * item_width, (i + 1) * item_width) 61 | print(box) 62 | box_list.append(box) 63 | 64 | # 裁剪图片 65 | image_list = [image.crop(box) for box in box_list] 66 | 67 | return image_list 68 | 69 | def __fill_image(self, image): 70 | """ 71 | 将图片填充为正方形 72 | :param image: 73 | :return: 74 | """ 75 | width, height = image.size 76 | 77 | # 长和宽较大值,作为新图片的宽高 78 | new_image_length = width if width > height else height 79 | 80 | # 生成新图片[纯白底] 81 | new_image = Image.new(image.mode, (new_image_length, new_image_length), color='white') 82 | 83 | # 将之前的图粘贴在新图上,居中 84 | # 如果原图宽大于高(横图),则填充图片的竖直维度 85 | if width > height: 86 | # (x,y)二元组表示粘贴上图相对下图的起始位置 87 | new_image.paste(image, (0, int((new_image_length - height) / 2))) 88 | else: 89 | # 如果原图宽小于高(竖图),则填充图片的水平纬度 90 | new_image.paste(image, (int((new_image_length - width) / 2), 0)) 91 | return new_image 92 | 93 | def save_images(self, nine_images): 94 | """ 95 | 保存图片 96 | :param nine_images: 97 | :return: 98 | """ 99 | index = 1 100 | for image in nine_images: 101 | image.save('result/' + str(index) + '.jpg') 102 | index += 1 103 | 104 | 105 | if __name__ == '__main__': 106 | # 图片路径 107 | image_file_path = './raw/pic.jpg' 108 | image_obj = ImageObj() 109 | image_obj.start(image_file_path) 110 | -------------------------------------------------------------------------------- /图片视频九宫格/ninecell_video.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: v1.0 6 | @author: xag 7 | @license: Apache Licence 8 | @contact: xinganguo@gmail.com 9 | @site: http://www.xingag.top 10 | @software: PyCharm 11 | @file: ninecell_video.py 12 | @time: 2020-08-28 23:16 13 | @description:九宫格视频朋友圈 14 | """ 15 | 16 | from PIL import Image 17 | 18 | from utils.video_utils import * 19 | 20 | 21 | class VideoObj(object): 22 | def __init__(self, file_path): 23 | self.video_raw_clip = VideoFileClip(file_path) 24 | 25 | # 宽、高 26 | self.video_width, self.video_height = self.video_raw_clip.w, self.video_raw_clip.h 27 | 28 | # 帧率 29 | self.fps = self.video_raw_clip.fps 30 | 31 | # 视频时长 32 | self.during = self.video_raw_clip.duration 33 | 34 | # 白色间隙宽度,用于分割图片 35 | self.item_space = 10 36 | 37 | # 视频原路径 38 | self.path_video_raw = file_path 39 | 40 | # 临时文件夹,用于保存处理后的帧图片 41 | self.path_temp = './temp/' 42 | 43 | # 输出文件夹 44 | self.path_output = './output/' 45 | 46 | # 音频文件保存路径 47 | self.path_bgm = self.path_temp + 'bg.wav' 48 | 49 | # 临时保存的视频文件 50 | self.path_video_temp = self.path_temp + 'temp.mp4' 51 | 52 | # 最终输出的视频文件 53 | self.path_video_output = self.path_output + 'result.mp4' 54 | 55 | def pre(self): 56 | """ 57 | 准备工作 58 | :return: 59 | """ 60 | # 新建临时文件夹和输出文件夹 61 | mkdir_folder(self.path_temp) 62 | mkdir_folder(self.path_output) 63 | 64 | def start(self): 65 | 66 | # 1、准备工作 67 | self.pre() 68 | 69 | print('视频宽/高为:', self.video_width, '/', self.video_height, '帧率/时长:', self.fps, '/', self.during) 70 | 71 | # 2、获取音频文件保存到本地 72 | get_audio_from_video(self.video_raw_clip, self.path_bgm) 73 | 74 | # 3、保存所有的帧图片到临时文件夹 75 | self.save_frames() 76 | 77 | # 4、将一篮子图片重新合成视频 78 | pics_to_video(self.path_temp, self.path_video_temp, self.fps) 79 | 80 | # 5、视频加入BGM 81 | video_with_audio(self.path_video_temp, self.path_bgm, self.path_video_output) 82 | 83 | # 6、删除临时文件 84 | self.teardown() 85 | 86 | def save_frames(self): 87 | """ 88 | 保存视频的所有帧,并做处理 89 | :return: 90 | """ 91 | i = 1 92 | for frame in self.video_raw_clip.iter_frames(): 93 | image = Image.fromarray(frame) 94 | 95 | # 视频帧图片保存的临时路径(完整路径) 96 | frame_file_complete_path = self.path_temp + "%04d.jpg" % i 97 | 98 | # 对图片进一步处理,加入白色间隔距离 99 | self.handle_frame(image, frame_file_complete_path) 100 | 101 | i += 1 102 | 103 | def handle_frame(self, image, frame_file_complete_path): 104 | """ 105 | 对帧图片进一步的处理 106 | 包含,裁剪图片、新建扩张画布、粘贴裁剪图片、保存新图片到本地 107 | :return: 108 | """ 109 | # 1、剪成9张图片,计算每张图片的宽、高 110 | item_width = int(self.video_width / 3) 111 | item_height = int(self.video_height / 3) 112 | 113 | # 2、新的宽、高 114 | item_width_new = self.video_width + self.item_space * 2 115 | item_height_new = self.video_height + self.item_space * 2 116 | 117 | # 3、重新建一个画布背景 118 | new_image = Image.new(image.mode, (item_width_new, item_height_new), 119 | color='white') 120 | 121 | # 4、裁剪图片,然后粘贴到新的画布中去 122 | # i:横向、j:纵向 123 | for i in range(0, 3): 124 | for j in range(0, 3): 125 | # 裁剪区域 126 | box = (j * item_width, i * item_height, (j + 1) * item_width, (i + 1) * item_height) 127 | 128 | # 坐标,左、顶部、右、底 129 | # print(box) 130 | 131 | # 根据区域,裁剪图片 132 | crop_image = image.crop(box) 133 | 134 | # 横向、纵向第2块和第3块,要加上偏移距离 135 | x = 0 if j == 0 else (item_width + self.item_space) * j 136 | y = 0 if i == 0 else (item_height + self.item_space) * i 137 | 138 | # 将9张图片,按照上面计算的坐标值,粘贴到背景中去 139 | new_image.paste(crop_image, (int(x), int(y))) 140 | 141 | # 保存图片到本地 142 | new_image.save(frame_file_complete_path) 143 | 144 | def teardown(self): 145 | """ 146 | 最后处理 147 | :return: 148 | """ 149 | # 删除临时文件 150 | remove_folder(self.path_temp) 151 | 152 | 153 | if __name__ == '__main__': 154 | # 待处理的文件 155 | file_path = './raw/至今无人超越的3个动作.mp4' 156 | video_obj = VideoObj(file_path) 157 | 158 | # 处理 159 | video_obj.start() 160 | -------------------------------------------------------------------------------- /图片视频九宫格/output/result.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xingag/tools_python/f01d315c46a619828d02ed9327f4264ba3a382d8/图片视频九宫格/output/result.mp4 -------------------------------------------------------------------------------- /图片视频九宫格/raw/pic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xingag/tools_python/f01d315c46a619828d02ed9327f4264ba3a382d8/图片视频九宫格/raw/pic.jpg -------------------------------------------------------------------------------- /图片视频九宫格/raw/至今无人超越的3个动作.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xingag/tools_python/f01d315c46a619828d02ed9327f4264ba3a382d8/图片视频九宫格/raw/至今无人超越的3个动作.mp4 -------------------------------------------------------------------------------- /图片视频九宫格/utils/file_utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: v1.0 6 | @author: xag 7 | @license: Apache Licence 8 | @contact: xinganguo@gmail.com 9 | @site: http://www.xingag.top 10 | @software: PyCharm 11 | @file: file_utils.py 12 | @time: 2020-02-01 23:05 13 | @description:TODO 14 | """ 15 | 16 | import os 17 | import shutil 18 | 19 | 20 | def get_file_path_and_name(filename): 21 | """ 22 | 获取文件的路径、文件名(不含后缀),后缀名 23 | :param filename: 24 | :return: 25 | """ 26 | (filepath, tempfilename) = os.path.split(filename) 27 | (shotname, extension) = os.path.splitext(tempfilename) 28 | return filepath, shotname, extension 29 | 30 | 31 | def rename_file(path, filename): 32 | """ 33 | 更改文件名(处理文件名中包含空格的情况) 34 | :param path: 35 | :param filename: 36 | :return: 37 | """ 38 | os.rename(os.path.join(path, filename), os.path.join(path, filename.replace(' ', '_'))) 39 | 40 | 41 | def mkdir_folder(file_path): 42 | """ 43 | 创建一个文件夹,如果不存在就创建;否则不做处理 44 | :param file_path: 45 | :return: 46 | """ 47 | if os.path.exists(file_path): 48 | return 49 | 50 | os.mkdir(file_path) 51 | 52 | 53 | def remove_folder(file_path): 54 | """ 55 | 删除文件夹 56 | :param file_path: 57 | :return: 58 | """ 59 | shutil.rmtree(file_path) 60 | -------------------------------------------------------------------------------- /图片视频九宫格/utils/math_utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: v1.0 6 | @author: xag 7 | @license: Apache Licence 8 | @contact: xinganguo@gmail.com 9 | @site: http://www.xingag.top 10 | @software: PyCharm 11 | @file: math_utils.py 12 | @time: 2020-08-29 10:14 13 | @description:TODO 14 | """ 15 | 16 | 17 | import re 18 | 19 | re_digits = re.compile(r'(\d+)') 20 | 21 | 22 | def emb_numbers(s): 23 | pieces = re_digits.split(s) 24 | pieces[1::2] = map(int, pieces[1::2]) 25 | return pieces 26 | 27 | 28 | def sort_strings_with_emb_numbers(alist): 29 | """ 30 | DSU排序 31 | :param alist: 32 | :return: 33 | """ 34 | aux = [(emb_numbers(s), s) for s in alist] 35 | aux.sort() 36 | return [s for __, s in aux] 37 | 38 | 39 | def sort_strings_with_emb_numbers2(alist): 40 | """ 41 | 内置DSU排序 42 | :param alist: 43 | :return: 44 | """ 45 | return sorted(alist, key=emb_numbers) -------------------------------------------------------------------------------- /图片视频九宫格/utils/video_utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """ 5 | @version: v1.0 6 | @author: xag 7 | @license: Apache Licence 8 | @contact: xinganguo@gmail.com 9 | @site: http://www.xingag.top 10 | @software: PyCharm 11 | @file: video_utils.py 12 | @time: 2020-02-01 22:32 13 | @description:公众号:AirPython 14 | """ 15 | 16 | import random 17 | 18 | from moviepy.editor import * 19 | 20 | from utils.file_utils import * 21 | from utils.math_utils import * 22 | 23 | 24 | def gene_random(): 25 | """ 26 | 生成随机数字 27 | :return: 28 | """ 29 | str = "" 30 | for i in range(6): 31 | ch = chr(random.randrange(ord('0'), ord('9') + 1)) 32 | str += ch 33 | return str 34 | 35 | 36 | def get_audio_from_video(video_raw_clip, output_path): 37 | """ 38 | 从视频中提取音频 39 | :param video_raw_clip: 视频Clip对象 40 | :param output_path: 输出音频文件完整路径 41 | :return: 42 | """ 43 | audio = video_raw_clip.audio 44 | audio.write_audiofile(output_path) 45 | 46 | return output_path 47 | 48 | 49 | def mov2mp4(video_path, output_path): 50 | """ 51 | mov格式转为mp4 52 | 注意:原视频文件名不能包含空格 53 | :param video_path: 54 | :param output_path: 55 | :return: 56 | """ 57 | filename = gene_random() 58 | try: 59 | filename = get_file_path_and_name(video_path)[1] 60 | except Exception as e: 61 | pass 62 | 63 | output_path = output_path + filename + ".mp4" 64 | 65 | # 格式转换 66 | os.system('ffmpeg -i %s -qscale 0 %s' % (video_path, output_path)) 67 | 68 | 69 | def pics_to_video(pics_path, output_path, fps): 70 | """ 71 | 图片转为视频 72 | pics_to_video('./../gif_temp/', './../video_temp/temp1.mp4', 20) 73 | :param pics_path: 74 | :param output_path: 75 | :return: 76 | """ 77 | image_paths = list(map(lambda x: pics_path + x, os.listdir(pics_path))) 78 | 79 | # 注意:这里必须进行一次排序,保证所有帧的顺序是一致 80 | image_paths = sort_strings_with_emb_numbers(image_paths) 81 | 82 | # 过滤掉非图片 83 | image_paths = list(filter(lambda image_path: image_path.endswith('.jpg'), image_paths)) 84 | 85 | # 图片剪辑类 86 | clip = ImageSequenceClip(image_paths, 87 | fps=fps) 88 | 89 | clip.write_videofile(output_path) 90 | 91 | 92 | def video_with_other_audio(path_video_raw, path_bgm_raw, output): 93 | """ 94 | 视频合成音频(混合音效) 95 | :return: 96 | """ 97 | videoclip = VideoFileClip(path_video_raw) 98 | audioclip = AudioFileClip(path_bgm_raw) 99 | 100 | new_audioclip = CompositeAudioClip([videoclip.audio, audioclip]) 101 | videoclip.audio = new_audioclip 102 | videoclip.write_videofile(output) 103 | 104 | 105 | def video_with_audio(path_video_raw, path_bgm_raw, output): 106 | """ 107 | 视频合成音频 108 | :return: 109 | """ 110 | videoclip = VideoFileClip(path_video_raw) 111 | audioclip = AudioFileClip(path_bgm_raw) 112 | 113 | # 设置视频音频,并写入到文件中去 114 | videoclip.set_audio(audioclip).write_videofile(output, 115 | codec='libx264', 116 | audio_codec='aac', 117 | temp_audiofile='temp-audio.m4a', 118 | remove_temp=True 119 | ) 120 | -------------------------------------------------------------------------------- /截图快捷翻译/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /截图快捷翻译/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /截图快捷翻译/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /截图快捷翻译/.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 47 | 48 | 49 | 51 | 52 | 58 | 59 | 60 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 |