gutin
linxiao 2018-04-17 16:11:26 +08:00
commit d8e089f431
1928 changed files with 101985 additions and 0 deletions

50
DevicesInfo.py Normal file
View File

@ -0,0 +1,50 @@
import re
import RunCMD
from RunCMD import run_cmd
class DevicesInfo():
def __init__(self):
list_devices_cmd = 'ffmpeg -list_devices true -f dshow -i dummy'
# status, output = subprocess.getstatusoutput(list_devices_cmd)
output_err, output_str = run_cmd(list_devices_cmd)
self.video_devices , self.voice_devices = self.extract_devices_info(''.join(output_err))
def get_device_info(self, text_list):
device_list = []
if text_list and len(text_list) % 2 == 0:
i=0
while i < len(text_list):
step = 2
device = []
device_name = text_list[i].strip()
device.append(device_name.replace('"',''))
alternative_name_text = text_list[i+1]
alter_re = re.search(r'"(.+)"',alternative_name_text)
if alter_re:
device_alternative_name = alter_re.group(1)
device.append(device_alternative_name)
device_list.append(device)
i+=step
return device_list
def extract_devices_info(self,devices_txt):
device_line = []
# print(dir(re))
results = re.findall(r'\[[^\]]+\]([^\[]+)',devices_txt)
# results.pop(0)
video_devices_spos=-1
voice_devices_spos=-1
for i in range(len(results)):
txt = results[i]
# print(txt.strip())
if txt.find('DirectShow video devices') >= 0:
video_devices_spos = i
if txt.find('DirectShow audio devices') >=0:
voice_devices_spos = i
video_devices = self.get_device_info(results[video_devices_spos+1:voice_devices_spos])
voice_devices = self.get_device_info(results[voice_devices_spos+1:])
return video_devices , voice_devices

52
RecordConfig.py Normal file
View File

@ -0,0 +1,52 @@
import os,configparser
# import DevicesInfo
# from DevicesInfo import *
class RecordConfig():
def __init__(self, config_file_name = 'config.ini'):
self.file_name = config_file_name
self.encoding = 'gb2312'
self.load()
def load(self):
if os.path.exists(self.file_name):
self.config=configparser.SafeConfigParser()
self.config.read(self.file_name, encoding = self.encoding)
else:
self.write_default_config()
self.load()
def write_default_config(self):
print('初始化配置文件.')
conf = configparser.SafeConfigParser()
# di = DevicesInfo()
devices_section_name = 'devices'
conf.add_section(devices_section_name)
conf.set(devices_section_name,'camera_device_name','')
conf.set(devices_section_name,'voice_device_name','')
conf.set(devices_section_name,'screen_device_name','')
conf.set(devices_section_name,'system_voice_device_name','')
shortcut_section_name = 'shortcut'
conf.add_section(shortcut_section_name)
conf.set(shortcut_section_name,'camera','160,162,164,65')
conf.set(shortcut_section_name,'screen','160,162,164,66')
conf.set(shortcut_section_name,'stop','160,162,164,67')
record_section_name = 'record'
conf.add_section(record_section_name)
conf.set(record_section_name,'resolution','1920x1080')
conf.set(record_section_name,'vcodec','libx264')
conf.set(record_section_name,'frame_rate','7.0')
conf.set(record_section_name,'file_dir','.')
self.config = conf
self.write()
def write(self):
with open(self.file_name, 'wt', encoding = self.encoding) as configfp:
self.config.write(configfp)

84
RecordTrayIcon.py Normal file
View File

@ -0,0 +1,84 @@
import sys,os
from PyQt5 import QtWidgets,QtGui
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import RecordType
from RecordType import *
class RecordTrayIcon(QSystemTrayIcon):
def __init__(self, parent=None):
super(RecordTrayIcon, self).__init__(parent)
# print((p_menu.actions()))
# self.showAction1 = QAction("显示消息1", self, triggered=self.showM)
# print(type(self.parent))
# p_menu.insertAction(p_menu.actions()[len(p_menu.actions())-1], self.showAction1)
# self.showMenu()
self.createContextMenu(parent)
self.interactive()
# self.update_state(False,None)
def createContextMenu(self, parent):
p_menu = parent.contextMenu
self.setContextMenu(p_menu)
def interactive(self):
self.activated.connect(self.iconClicked)
self.toolTip()
def showMessage():
pass
def get_icon(self, file_name):
data_dir=''
if getattr(sys, 'frozen', False):
# The application is frozen
data_dir = os.path.dirname(sys.executable)
else:
# The application is not frozen
# Change this bit to match where you store your data files:
data_dir = os.path.dirname(__file__)
resource_dir = 'resource'
data_dir = os.path.join(os.path.abspath(data_dir), resource_dir)
print('resource dir: %s' % data_dir)
icon_file_path = os.path.join(data_dir, file_name)
if os.path.isfile(icon_file_path):
return QIcon(icon_file_path)
return None
def update_state(self, recording, record_type):
if recording:
if record_type == RecordType.Camera:
self.setIcon(self.get_icon('camera_recording_colorful.png'))
self.setToolTip('正在录制摄像头...')
elif record_type == RecordType.Screen:
self.setIcon(self.get_icon('screen_recording.png'))
self.setToolTip('正在录制屏幕...')
else:
self.setIcon(self.get_icon('stop.png'))
self.setToolTip('软件缩小在这里.')
else:
self.setIcon(self.get_icon('start.png'))
self.setToolTip('软件缩小在这里.')
def iconClicked(self, reason):
#"鼠标点击icon传递的信号会带有一个整形的值1是表示单击右键2是双击3是单击左键4是用鼠标中键点击"
if reason == 2 or reason == 3:
pw = self.parent()
if pw.isVisible() and pw.isActiveWindow():
pw.hide()
else:
pw.show()
# pw.raise_()
pw.activateWindow()
# print('parent is active window? %s' % pw.isActiveWindow())
# print(reason)

7
RecordType.py Normal file
View File

@ -0,0 +1,7 @@
from enum import Enum
class RecordType(Enum):
#摄像头
Camera = 0
#屏幕
Screen = 1

250
RecordVideo.py Normal file
View File

@ -0,0 +1,250 @@
import datetime,time,sys,os,signal,re
from datetime import datetime
import subprocess,threading
# from multiprocessing import Process
from threading import Thread
import ctypes,inspect
import RecordType
from RecordType import *
import RecordConfig
from RecordConfig import *
import logging
import RunCMD
from RunCMD import run_cmd
class RecordVideo():
'''
ffmpeg -f dshow -i video="@device_pnp_\\\\?\\usb#vid_04f2&pid_b354&mi_00#7&30d7ad30&0&0000#{65e8773d-8f56-11d0-a3b9-00a0c9223196}\global":audio="@device_cm_{33D9A762-90C8-11D0-BD43-00A0C911CE86}\wave_{571529B3-7DB3-42A3-ADEF-BBD82925C15D}" -acodec libmp3lame -vcodec libx265 -preset:v ultrafast -tune:v zerolatency -s 1920x1080 -r 7 -y record_camera_20180408_182541.mkv
'''
def __init__(self, record_video=True, record_voice=True):
# print('视频录制初始化中...')
# self.record_video=record_video
# self.record_voice=record_voice
#录制状态
self.recording=False
self.record_type=RecordType.Camera
#文件名称
self.file_name='record'
#文件后缀
self.file_suffix='.mkv'
self.process = None
self.record_thread_name='record'
self.record_thread=None
self.file_dir = ''
self.load_config()
def load_config(self):
#日志
self.logger = logging.getLogger(__name__)
self.logger.setLevel(level = logging.INFO)
handler = logging.FileHandler('log.txt')
handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
self.logger.addHandler(handler)
rc = RecordConfig()
self.config = rc.config
#摄像头名称
self.camera_name=rc.config.get('devices','camera_device_name')
#麦克风名称
self.voice_device_name=rc.config.get('devices','voice_device_name')
#录制屏幕名称
self.screen_name=rc.config.get('devices','screen_device_name')
#系统声音设备名称
self.system_voice_device_name=rc.config.get('devices','system_voice_device_name')
#视频编码
self.video_codec=rc.config.get('record','vcodec')
#分辨率
self.resolution=rc.config.get('record','resolution')
#帧率
self.brate=rc.config.getfloat('record','frame_rate')
#文件目录
self.file_dir= os.path.abspath(rc.config.get('record','file_dir'))
self.logger.info('camera device name: %s' % self.camera_name)
self.logger.info('voice device name: %s' % self.voice_device_name)
self.logger.info('screen device name: %s' % self.screen_name)
self.logger.info('system voice device name: %s' % self.system_voice_device_name)
self.logger.info('vcodec: %s' % self.video_codec)
self.logger.info('resolution: %s' % self.resolution)
self.logger.info('frame rate: %s' % self.brate)
self.logger.info('save dir: %s' % self.file_dir)
def start_ffmpeg(self,cmd, shell = True):
print('录制中...')
self.logger.info('录制中...')
# print('cmd:\n%s' % cmd)
start_time = datetime.now()
self.process=subprocess.Popen(cmd, shell=shell, universal_newlines = True, stdin = subprocess.PIPE, stderr = subprocess.STDOUT, stdout = subprocess.PIPE)
# print(self.process.stdin)
line = ''
while self.recording:
line += self.process.stdout.readline()
now = datetime.now()
if (now - start_time).total_seconds() >2:
self.logger.info(line)
line = ''
start_time = now
print(line)
if not self.recording:
self.process.stdin.write('q')
print(self.process.communicate())
break
# self.logger.info(self.process.communicate())
# print('done.')
def record(self,cmd='ffmpeg -h', target = None):
if target:
print('cmd: \n%s' % cmd)
self.logger.info('record cmd:\n %s' % cmd)
th=Thread(name=self.record_thread_name, target= target, args = (cmd,), daemon=True)
th.start()
self.record_thread=th
print('record thread,ident:%d' % self.record_thread.ident)
self.recording=True
def stop_record(self):
# print('threading active thread count:%d' % threading.active_count())
try:
self.recording = False
self.logger.info('ffmpeg pid: %d' % self.process.pid)
self.logger.info('录制将停止...')
self.logger.info('ffmpeg进程状态: %s' % (self.process.poll() is not None))
self.recording=False
if self.record_thread.is_alive():
self.record_thread.join(1)
print('record thread status: %s' % self.record_thread.is_alive())
except (Exception,KeyboardInterrupt) as e:
print('kill exception:\n %s' % e)
self.logger.warning('kill exception:\n %s' % e)
def record_camera(self):
if self.camera_name and self.voice_device_name:
self.record_type=RecordType.Camera
record_cmd='ffmpeg -f dshow -i video=\"%s\":audio=\"%s\" -acodec libmp3lame -vcodec %s -preset:v ultrafast -tune:v zerolatency -s %s -r %d -y %s' %(
self.deal_with_device_name(self.camera_name),
self.deal_with_device_name(self.voice_device_name),
self.video_codec,
self.resolution,
self.brate,
self.get_file_name()
)
# print(record_cmd)
self.record(record_cmd, self.start_ffmpeg)
def record_screen(self):
if self.screen_name and self.system_voice_device_name:
self.record_type=RecordType.Screen
record_cmd='ffmpeg -f dshow -i video="{}":audio="{}" -acodec libmp3lame -vcodec {} -preset:v ultrafast -tune:v zerolatency -s {} -r {} -y {}'.format(
self.deal_with_device_name(self.screen_name),
self.deal_with_device_name(self.system_voice_device_name),
self.video_codec,
self.resolution,
self.brate,
self.get_file_name()
)
self.record(record_cmd, self.start_ffmpeg)
def debug_camera(self):
try:
play_cmd = ['ffplay','-f','dshow','-i','video={}'.format(self.camera_name),'-window_title','按q退出','-noborder']
self.record(play_cmd, self.play)
except Exception as e:
print(e)
def play(self, cmd):
try:
t_process=subprocess.Popen(cmd, shell= False, universal_newlines = True, stderr = subprocess.STDOUT, stdout = subprocess.PIPE)
while True:
line = t_process.stdout.readline()
print(line)
if line == '':
if t_process.poll() is not None:
break
t_process.communicate()
except (Exception, KeyboardInterrupt) as e:
print(e)
def deal_with_device_name(self,device_name):
# print(device_name)
# new_name=device_name.replace('\\','\\\\')
# print(new_name)
# return new_name
return device_name
def get_file_name(self):
time_str=datetime.now().strftime('%Y%m%d_%H%M%S')
video_type = ''
if self.record_type == RecordType.Camera:
video_type = '摄像头'
if self.record_type == RecordType.Screen:
video_type = '屏幕'
file_name = os.path.join(self.file_dir, '{}_{}{}'.format(video_type, time_str, self.file_suffix))
print('recording file name: %s' % file_name)
return file_name
def _async_raise(self, tid, exctype):
"""raises the exception, performs cleanup if needed"""
tid = ctypes.c_long(tid)
if not inspect.isclass(exctype):
exctype = type(exctype)
res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))
print('async_raise res value:%d' % res)
if res == 0:
raise ValueError("invalid thread id")
elif res != 1:
# """if it returns a number greater than one, you're in trouble,
# and you should call it again with exc=NULL to revert the effect"""
ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
raise SystemError("PyThreadState_SetAsyncExc failed")
def kill_process(self, process_name='ffmpeg'):
cmd='tasklist | findstr {}'.format(process_name)
output_strs, output_errs=self.run_cmd(cmd)
pid=[]
if output_strs:
print('find "%s" result: \n%s' % (process_name, ''.join(output_strs)))
for output_str in output_strs:
find_re=re.search(r'({}.+?)\s*([0-9]+)'.format(process_name),output_str)
if find_re:
full_process_name=find_re.group(1).strip()
pid=find_re.group(2).strip()
print('计划结束任务:{}@pid {}'.format( full_process_name, pid ))
task_kill_cmd = 'taskkill /T /F /pid {}'.format(pid)
# status, output = subprocess.getstatusoutput(task_kill_cmd)
# if status == 1:
# print('任务成功被结束:')
# else:
# print('任务结束失败:')
# print(output)
else:
print('not found task about "%s" in tasklist' % process_name )
# def stop_thread(self,thread):
# self._async_raise(thread.ident, SystemExit)

380
RecordWindow.py Normal file
View File

@ -0,0 +1,380 @@
import sys, datetime
import PyQt5
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt, QCoreApplication
from PyQt5.QtWidgets import QMessageBox
import RecordVideo,RecordType
from RecordVideo import *
from RecordType import *
import SettingWindow
from SettingWindow import *
import Shortcut
from Shortcut import *
import RecordTrayIcon
from RecordTrayIcon import *
import RecordConfig
from RecordConfig import *
class RecordWindow(QtWidgets.QWidget):
def __init__(self, parent = None):
super(RecordWindow,self).__init__(parent)
self.setupUi()
self.load_modules()
#更新设置
self.need_update_config = False
#初始化状态
print('初始化状态...')
self.update_state()
def load_modules(self):
#录制
self.rv=RecordVideo()
#鼠标拖动
self.m_drag = False
#托盘图标
self.rti = RecordTrayIcon(self)
self.rti.update_state(self.recording, self.record_type)
self.rc = RecordConfig()
self.file_dir = self.rc.config.get('record','file_dir')
self.debugCameraAction.triggered.connect(self.rv.debug_camera)
def closeEvent(self, event):
# print('close window.')
# self.close_signal.emit()
# self.close()
# if self.recording:
# question = QMessageBox(self)
# question.setText('系统正在录制中,确定要退出吗?')
# question.setWindowTitle('提示')
# question.setIcon(QMessageBox.Question)
# question.addButton(QMessageBox.Yes)
# question.addButton(QMessageBox.No)
# question.setDefaultButton(QMessageBox.No)
# ret = question.exec()
# if ret == QMessageBox.Yes:
# self.stop_record()
# QCoreApplication.instance().quit()
# else:
# question = QMessageBox()
# question.setText('确定要退出吗?')
# question.setWindowTitle('提示')
# question.setIcon(QMessageBox.Question)
# question.addButton(QMessageBox.Yes)
# question.addButton(QMessageBox.No)
# question.setDefaultButton(QMessageBox.No)
# ret = question.exec()
# if ret == QMessageBox.Yes:
# print('软件将退出.')
QCoreApplication.instance().quit()
# event.ignore()
def setupUi(self):
self.setObjectName("RecordWindow")
self.resize(94, 81)
self.move(1100,600)
self.pushButton = QtWidgets.QPushButton(self)
self.pushButton.setGeometry(QtCore.QRect(0, 30, 91, 51))
self.pushButton.setObjectName("pushButton")
# self.pushButton.mousePressEvent.connect(self.mousePressEvent)
# print(dir(self.pushButton))
#添加右键菜单
self.createContextMenu()
#添加计时器
self.lcd = QLCDNumber(self)
self.lcd.setDigitCount(10)
self.lcd.setMode(QLCDNumber.Dec)
self.lcd.setGeometry(QtCore.QRect(0, 0, 91, 31))
self.lcd.setSegmentStyle(QLCDNumber.Flat)
self.init_lcd()
#新建一个QTimer对象
self.timer = QTimer()
self.timer.setInterval(1000)
self.timer.timeout.connect(self.onTimerOut)
self.retranslateUi()
#调整窗体属性
self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.Tool)
# self.setFocusPolicy(QtCore.Qt.StrongFocus)
self.installEventFilter(self)
def retranslateUi(self):
_translate = QtCore.QCoreApplication.translate
self.setWindowTitle(_translate("RecordWindow", "RecordWindow"))
self.pushButton.setText(_translate("RecordWindow", "开始"))
def init_lcd(self):
self.lcd.display('0:00:00')
def start_timer(self):
self.start_time = datetime.now()
self.init_lcd()
self.timer.start()
def stop_timer(self):
self.timer.stop()
self.init_lcd()
# 刷新录制时间
def onTimerOut(self):
self.lcd.display(self.get_display_time(self.start_time))
def get_display_time(self,old_time):
delta_time = datetime.now() - old_time
delta_time_str = str(delta_time)
pos = delta_time_str.find('.')
time_text = delta_time_str[0:pos]
return time_text
#打开文件目录
def open_file_dir(self):
dirpath = self.file_dir
if os.path.isdir(dirpath):
os.startfile(dirpath)
else:
print('错误的文件目录:%s' % dirpath)
#显示菜单
def showContextMenu(self):
self.contextMenu.exec_(QtGui.QCursor.pos())
#添加右键菜单
def createContextMenu(self):
#更改右键菜单为自定义
print('初始化右键菜单...')
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.showContextMenu)
self.contextMenu = QtWidgets.QMenu(self)
#停止/开始录制
self.recordSwitchAction = self.contextMenu.addAction('开始/停止录制')
#开始录制摄像头
self.recordCameraAction = self.contextMenu.addAction('开始录制摄像头')
#录制屏幕
self.recordScreenAction = self.contextMenu.addAction('开始录制屏幕')
#分隔栏
self.separatorAction = self.contextMenu.addAction('分隔栏')
self.separatorAction.setSeparator(True)
self.openFileDirAction = self.contextMenu.addAction('打开文件目录')
self.openFileDirAction.triggered.connect(self.open_file_dir)
#调试摄像头
self.debugCameraAction = self.contextMenu.addAction('调试摄像头')
self.debugCameraAction.setVisible(False)
#设置
self.settingAction=self.contextMenu.addAction('设置')
self.sw = SettingWindow()
self.settingAction.triggered.connect(self.sw.showSettingWindow)
#帮助
self.aboutAction=self.contextMenu.addAction('帮助')
#退出
self.exitAction = self.contextMenu.addAction('退出')
self.exitAction.triggered.connect(self.close)
'''
功能事件
'''
@property
def recording(self):
return self.rv.recording
@property
def record_type(self):
return self.rv.record_type
def register_slot(self, event_obj, new_action, disconnect = True):
if disconnect:
try:
event_obj.disconnect()
except Exception as e:
pass
event_obj.connect(new_action)
def update_state(self):
if self.recording:
print('录制状态:录制中.')
if self.record_type == RecordType.Camera:
print('正在录制摄像头.')
else:
print('正在录制屏幕.')
print('recording:%s' % self.recording)
print('record_type:%s' % self.record_type)
if self.recording:
self.recordSwitchAction.setText('停止录制')
self.register_slot(self.pushButton.clicked, self.stop_record)
self.register_slot(self.recordSwitchAction.triggered, self.stop_record)
#正在录制摄像头
if self.record_type == RecordType.Camera:
self.pushButton.setText('正在录制摄像头\n点击停止')
self.recordCameraAction.setText('停止录制摄像头')
self.register_slot(self.recordCameraAction.triggered, self.stop_record)
self.recordScreenAction.setText('开始录制屏幕')
self.register_slot(self.recordScreenAction.triggered, lambda: self.record(RecordType.Screen))
#正在录制屏幕
if self.record_type == RecordType.Screen:
self.pushButton.setText('正在录制屏幕\n点击停止')
self.recordScreenAction.setText('停止录制屏幕')
self.register_slot(self.recordScreenAction.triggered, self.stop_record)
self.recordCameraAction.setText('开始录制摄像头')
self.register_slot(self.recordCameraAction.triggered, lambda: self.record(RecordType.Camera))
else:
self.pushButton.setText('开始')
self.recordSwitchAction.setText('开始录制')
self.register_slot(self.pushButton.clicked, lambda: self.record(self.record_type))
self.register_slot(self.recordSwitchAction.triggered, lambda: self.record(self.record_type))
self.recordScreenAction.setText('开始录制屏幕')
self.register_slot(self.recordScreenAction.triggered, lambda: self.record(RecordType.Screen))
self.recordCameraAction.setText('开始录制摄像头')
self.register_slot(self.recordCameraAction.triggered, lambda: self.record(RecordType.Camera))
self.rti.update_state(self.recording, self.record_type)
def stop_record(self):
if self.recording:
self.stop_timer()
if self.record_type == RecordType.Camera:
print('停止录制摄像头.')
if self.record_type == RecordType.Screen:
print('停止录制屏幕.')
try:
self.rv.stop_record()
except KeyboardInterrupt as e:
print(e)
finally:
self.update_state()
else:
#退出系统
self.close()
def record(self, rtype):
if self.recording:
if rtype == self.record_type:
return True
print('检测到正在录制,录制类型将切换...')
self.stop_record()
#开始录制
if rtype == RecordType.Camera:
print('开始录制摄像头...')
self.rv.record_camera()
elif rtype == RecordType.Screen:
print('开始录制屏幕...')
self.rv.record_screen()
self.start_timer()
self.update_state()
''''
鼠标拖动窗体
'''
def mousePressEvent(self, e):
if not isinstance(self,RecordWindow):
e.ignore()
else:
if e.button() == Qt.LeftButton:
self.m_drag = True
self.m_DragPosition = e.globalPos() - self.pos()
e.accept()
self.setCursor(QtGui.QCursor(Qt.OpenHandCursor))
def mouseReleaseEvent(self, e):
if not isinstance(self,RecordWindow):
e.ignore()
else:
if e.button() == Qt.LeftButton:
self.m_drag = False
self.setCursor(QtGui.QCursor(Qt.ArrowCursor))
def mouseMoveEvent(self, e):
if not isinstance(self,RecordWindow):
e.ignore()
else:
if Qt.LeftButton and self.m_drag:
self.move(e.globalPos() - self.m_DragPosition)
e.accept()
# print('(x:%d/y:%d)' % (self.x(),self.y()))
'''
快捷键监听
'''
def monitor_shortcut(self):
sc = Shortcut()
camera_key_group = self.rc.config.get('shortcut','camera')
screen_key_group = self.rc.config.get('shortcut','screen')
stop_record_key_group = self.rc.config.get('shortcut','stop')
camera_shortcut = [int(key) for key in camera_key_group.split(',')]
screen_shortcut = [int(key) for key in screen_key_group.split(',')]
stop_shortcut = [int(key) for key in stop_record_key_group.split(',')]
print('camera shortcut: %s' % camera_shortcut)
print('screen shortcut: %s' % screen_shortcut)
print('stop shortcut: %s' % stop_shortcut)
if camera_key_group:
sc.add(1, camera_shortcut, lambda: self.record(RecordType.Camera))
if screen_key_group:
sc.add(2, screen_shortcut, lambda: self.record(RecordType.Screen))
if stop_record_key_group:
sc.add(3, stop_shortcut, self.stop_record)
sc.monitor()
'''
窗体事件
'''
def eventFilter(self, obj, event):
# print('e type: %s' % event.type())
# print('deactivate type id :%d' % QEvent.WindowDeactivate)
if event.type() == QEvent.WindowDeactivate:
# print('丢失焦点')
self.setVisible(False)
return False
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
rw = RecordWindow()
rw.monitor_shortcut()
rw.rti.show()
rw.show()
sys.exit(app.exec_())

28
RunCMD.py Normal file
View File

@ -0,0 +1,28 @@
import subprocess
def run_cmd(cmd, shell = True, universal_newlines = False):
output_info=[]
output_err=[]
with subprocess.Popen(cmd,
shell = shell,
universal_newlines = universal_newlines,
stdout = subprocess.PIPE,
stdin = subprocess.PIPE,
stderr = subprocess.PIPE
) as p:
while True:
info=str(p.stdout.read(), encoding ='utf-8')
# print(dir(p.stderr))
# print(info)
err=str(p.stderr.read(), encoding = 'utf-8')
# print(err)
if not info and not err:
if p.poll() is not None:
break
else:
output_info.append(info)
output_err.append(err)
return output_err, output_info

402
SettingWindow.py Normal file
View File

@ -0,0 +1,402 @@
from PyQt5 import QtWidgets,QtCore
from PyQt5.QtWidgets import QFileDialog
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import DevicesInfo
from DevicesInfo import *
import RecordConfig
from RecordConfig import *
from PyQt5.QtWidgets import QMessageBox
class SettingWindow(QDialog):
def __init__(self, parent = None):
super(SettingWindow,self).__init__(parent)
self.setupUi()
self.load()
def setupUi(self):
self.setObjectName("SettingWindow")
self.resize(386, 238)
self.tabWidget = QtWidgets.QTabWidget(self)
self.tabWidget.setGeometry(QtCore.QRect(10, 10, 371, 221))
self.tabWidget.setObjectName("tabWidget")
self.tab_device = QtWidgets.QWidget()
self.tab_device.setObjectName("tab_device")
self.gridLayoutWidget_3 = QtWidgets.QWidget(self.tab_device)
self.gridLayoutWidget_3.setGeometry(QtCore.QRect(10, 10, 321, 171))
self.gridLayoutWidget_3.setObjectName("gridLayoutWidget_3")
self.gridLayout_3 = QtWidgets.QGridLayout(self.gridLayoutWidget_3)
self.gridLayout_3.setContentsMargins(0, 0, 0, 0)
self.gridLayout_3.setObjectName("gridLayout_3")
self.label_3 = QtWidgets.QLabel(self.gridLayoutWidget_3)
self.label_3.setLayoutDirection(QtCore.Qt.LeftToRight)
self.label_3.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.label_3.setWordWrap(False)
self.label_3.setObjectName("label_3")
self.gridLayout_3.addWidget(self.label_3, 2, 0, 1, 1)
self.cb_camera_devices = QtWidgets.QComboBox(self.gridLayoutWidget_3)
self.cb_camera_devices.setObjectName("devices.camera_device_name")
self.gridLayout_3.addWidget(self.cb_camera_devices, 0, 1, 1, 1)
self.cb_voice_devices = QtWidgets.QComboBox(self.gridLayoutWidget_3)
self.cb_voice_devices.setObjectName("devices.voice_device_name")
self.gridLayout_3.addWidget(self.cb_voice_devices, 1, 1, 1, 1)
self.cb_screen_devices = QtWidgets.QComboBox(self.gridLayoutWidget_3)
self.cb_screen_devices.setObjectName("devices.screen_device_name")
self.gridLayout_3.addWidget(self.cb_screen_devices, 2, 1, 1, 1)
self.label_2 = QtWidgets.QLabel(self.gridLayoutWidget_3)
self.label_2.setLayoutDirection(QtCore.Qt.LeftToRight)
self.label_2.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.label_2.setWordWrap(False)
self.label_2.setObjectName("label_2")
self.gridLayout_3.addWidget(self.label_2, 1, 0, 1, 1)
self.label = QtWidgets.QLabel(self.gridLayoutWidget_3)
self.label.setLayoutDirection(QtCore.Qt.RightToLeft)
self.label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.label.setWordWrap(False)
self.label.setObjectName("label")
self.gridLayout_3.addWidget(self.label, 0, 0, 1, 1)
self.label_4 = QtWidgets.QLabel(self.gridLayoutWidget_3)
self.label_4.setLayoutDirection(QtCore.Qt.LeftToRight)
self.label_4.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.label_4.setWordWrap(False)
self.label_4.setObjectName("label_4")
self.gridLayout_3.addWidget(self.label_4, 3, 0, 1, 1)
self.cb_system_voice_devices = QtWidgets.QComboBox(self.gridLayoutWidget_3)
self.cb_system_voice_devices.setObjectName("devices.system_voice_device_name")
self.gridLayout_3.addWidget(self.cb_system_voice_devices, 3, 1, 1, 1)
self.tabWidget.addTab(self.tab_device, "")
self.tab_key = QtWidgets.QWidget()
self.tab_key.setObjectName("tab_key")
self.gridLayoutWidget_2 = QtWidgets.QWidget(self.tab_key)
self.gridLayoutWidget_2.setGeometry(QtCore.QRect(10, 10, 321, 121))
self.gridLayoutWidget_2.setObjectName("gridLayoutWidget_2")
self.gridLayout_2 = QtWidgets.QGridLayout(self.gridLayoutWidget_2)
self.gridLayout_2.setContentsMargins(0, 0, 0, 0)
self.gridLayout_2.setObjectName("gridLayout_2")
self.le_start_record_screen_shortcut = QtWidgets.QLineEdit(self.gridLayoutWidget_2)
self.le_start_record_screen_shortcut.setObjectName("shortcut.screen")
self.gridLayout_2.addWidget(self.le_start_record_screen_shortcut, 1, 1, 1, 1)
self.label_10 = QtWidgets.QLabel(self.gridLayoutWidget_2)
self.label_10.setLayoutDirection(QtCore.Qt.RightToLeft)
self.label_10.setTextFormat(QtCore.Qt.AutoText)
self.label_10.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.label_10.setObjectName("label_10")
self.gridLayout_2.addWidget(self.label_10, 1, 0, 1, 1)
self.le_start_record_camera_shortcut = QtWidgets.QLineEdit(self.gridLayoutWidget_2)
self.le_start_record_camera_shortcut.setObjectName("shortcut.camera")
self.gridLayout_2.addWidget(self.le_start_record_camera_shortcut, 0, 1, 1, 1)
self.label_9 = QtWidgets.QLabel(self.gridLayoutWidget_2)
self.label_9.setLayoutDirection(QtCore.Qt.RightToLeft)
self.label_9.setTextFormat(QtCore.Qt.AutoText)
self.label_9.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.label_9.setObjectName("label_9")
self.gridLayout_2.addWidget(self.label_9, 0, 0, 1, 1)
self.label_11 = QtWidgets.QLabel(self.gridLayoutWidget_2)
self.label_11.setLayoutDirection(QtCore.Qt.RightToLeft)
self.label_11.setTextFormat(QtCore.Qt.AutoText)
self.label_11.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.label_11.setObjectName("label_11")
self.gridLayout_2.addWidget(self.label_11, 2, 0, 1, 1)
self.le_start_stop_exit_shortcut = QtWidgets.QLineEdit(self.gridLayoutWidget_2)
self.le_start_stop_exit_shortcut.setObjectName("shortcut.stop")
self.gridLayout_2.addWidget(self.le_start_stop_exit_shortcut, 2, 1, 1, 1)
self.tabWidget.addTab(self.tab_key, "")
self.tab_record = QtWidgets.QWidget()
self.tab_record.setObjectName("tab_record")
self.gridLayoutWidget = QtWidgets.QWidget(self.tab_record)
self.gridLayoutWidget.setGeometry(QtCore.QRect(10, 10, 321, 161))
self.gridLayoutWidget.setObjectName("gridLayoutWidget")
self.gridLayout = QtWidgets.QGridLayout(self.gridLayoutWidget)
self.gridLayout.setContentsMargins(0, 0, 0, 0)
self.gridLayout.setHorizontalSpacing(0)
self.gridLayout.setObjectName("gridLayout")
self.label_14 = QtWidgets.QLabel(self.gridLayoutWidget)
self.label_14.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.label_14.setObjectName("label_14")
self.gridLayout.addWidget(self.label_14, 1, 0, 1, 1)
self.cb_resolution = QtWidgets.QComboBox(self.gridLayoutWidget)
self.cb_resolution.setObjectName("record.resolution")
self.gridLayout.addWidget(self.cb_resolution, 0, 1, 1, 1)
self.cb_vcodec = QtWidgets.QComboBox(self.gridLayoutWidget)
self.cb_vcodec.setObjectName("record.vcodec")
self.gridLayout.addWidget(self.cb_vcodec, 1, 1, 1, 1)
self.dsb_frame_rate = QtWidgets.QDoubleSpinBox(self.gridLayoutWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.dsb_frame_rate.sizePolicy().hasHeightForWidth())
self.dsb_frame_rate.setSizePolicy(sizePolicy)
self.dsb_frame_rate.setObjectName("record.frame_rate")
self.gridLayout.addWidget(self.dsb_frame_rate, 2, 1, 1, 1)
self.label_15 = QtWidgets.QLabel(self.gridLayoutWidget)
self.label_15.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.label_15.setObjectName("label_15")
self.gridLayout.addWidget(self.label_15, 2, 0, 1, 1)
self.le_file_path = QtWidgets.QLineEdit(self.gridLayoutWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.le_file_path.sizePolicy().hasHeightForWidth())
self.le_file_path.setSizePolicy(sizePolicy)
self.le_file_path.setObjectName("record.file_dir")
self.gridLayout.addWidget(self.le_file_path, 4, 1, 1, 1)
self.label_13 = QtWidgets.QLabel(self.gridLayoutWidget)
self.label_13.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.label_13.setObjectName("label_13")
self.gridLayout.addWidget(self.label_13, 4, 0, 1, 1)
self.label_12 = QtWidgets.QLabel(self.gridLayoutWidget)
self.label_12.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.label_12.setObjectName("label_12")
self.gridLayout.addWidget(self.label_12, 0, 0, 1, 1)
self.btn_file_dir = QtWidgets.QToolButton(self.gridLayoutWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.btn_file_dir.sizePolicy().hasHeightForWidth())
self.btn_file_dir.setSizePolicy(sizePolicy)
self.btn_file_dir.setObjectName("btn_file_dir")
self.gridLayout.addWidget(self.btn_file_dir, 4, 2, 1, 1)
self.tabWidget.addTab(self.tab_record, "")
self.frame = QtWidgets.QFrame(self)
self.frame.setGeometry(QtCore.QRect(60, 330, 229, 10))
self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
self.frame.setFrameShadow(QtWidgets.QFrame.Raised)
self.frame.setObjectName("frame")
self.retranslateUi()
self.tabWidget.setCurrentIndex(0)
QtCore.QMetaObject.connectSlotsByName(self)
def retranslateUi(self):
_translate = QtCore.QCoreApplication.translate
self.setWindowTitle(_translate("Form", "设置-Development By Linxiao"))
self.label_3.setText(_translate("Form", "屏幕录制设备:"))
self.label_2.setText(_translate("Form", "声音输入设备:"))
self.label.setText(_translate("Form", "摄像头名称:"))
self.label_4.setText(_translate("Form", "系统声音设备:"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_device), _translate("Form", "设备参数配置"))
self.label_10.setText(_translate("Form", "录制屏幕:"))
self.label_9.setText(_translate("Form", "录制摄像头:"))
self.label_11.setText(_translate("Form", "启动/退出:"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_key), _translate("Form", "快捷键设置"))
self.label_14.setText(_translate("Form", "编码格式:"))
self.label_15.setText(_translate("Form", "帧率:"))
self.label_13.setText(_translate("Form", "文件保存目录:"))
self.label_12.setText(_translate("Form", "分辨率:"))
self.btn_file_dir.setText(_translate("Form", "选择文件目录"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_record), _translate("Form", "录制参数"))
# def set_combobox_select_index_by_name_or_data(self, combobox, name_or_data):
# for i in range(len()
def load_combobox_data(self, combox, data):
for obj in data:
name = obj[0]
value = name
if len(obj) > 1:
value = obj[1]
combox.addItem(name, value)
def get_key_group_name(self, key_tuple):
return key_tuple.replace(r'(','').replace(r')','')
def load(self):
rc = RecordConfig()
self.rc = rc
self.changed = False
'''
数据初始化
'''
#设备
di = DevicesInfo()
self.load_combobox_data(self.cb_camera_devices, di.video_devices)
self.load_combobox_data(self.cb_screen_devices, di.video_devices)
self.load_combobox_data(self.cb_voice_devices, di.voice_devices)
self.load_combobox_data(self.cb_system_voice_devices, di.voice_devices)
#录制
resolutions = [
['1920x1080'],
['1280x1024'],
['1024x768'],
['640x480']
]
vcodec = [
['H.264','libx264'],
['H.265','libx265'],
]
self.load_combobox_data(self.cb_resolution, resolutions)
self.load_combobox_data(self.cb_vcodec, vcodec)
# print(self.cb_camera_devices.currentData())
# print('视频设备列表:')
# print(di.video_devices)
# print('音频设备列表:')
# print(di.voice_devices)
'''
加载设置
'''
#设备
video_device_name = rc.config.get('devices','camera_device_name')
voice_device_name = rc.config.get('devices','voice_device_name')
screen_device_name = rc.config.get('devices','screen_device_name')
system_voice_device_name = rc.config.get('devices','system_voice_device_name')
# self.cb_camera_devices.setCurrentIndex(1)
video_cur_index = self.cb_camera_devices.findData(video_device_name)
# print('video name:\n%s\ndevice index:%d' % (video_device_name, video_cur_index))
self.cb_camera_devices.setCurrentIndex(video_cur_index)
self.cb_voice_devices.setCurrentIndex(self.cb_voice_devices.findData(voice_device_name))
self.cb_screen_devices.setCurrentIndex(self.cb_screen_devices.findData(screen_device_name))
self.cb_system_voice_devices.setCurrentIndex(self.cb_system_voice_devices.findData(system_voice_device_name))
#快捷键
record_camera_key_group = rc.config.get('shortcut','camera')
record_screen_key_group = rc.config.get('shortcut','screen')
record_stop_key_group = rc.config.get('shortcut','stop')
record_camera_key_group_name = self.get_key_group_name(record_camera_key_group)
record_screen_key_group_name = self.get_key_group_name(record_screen_key_group)
record_stop_key_group_name = self.get_key_group_name(record_stop_key_group)
self.le_start_record_camera_shortcut.setText(record_camera_key_group_name)
self.le_start_record_screen_shortcut.setText(record_screen_key_group_name)
self.le_start_stop_exit_shortcut.setText(record_stop_key_group_name)
#录制参数
record_resolution = self.rc.config.get('record','resolution')
record_vcodec = self.rc.config.get('record','vcodec')
record_frame_rate = self.rc.config.getfloat('record','frame_rate')
record_file_dir = self.rc.config.get('record','file_dir')
record_file_dir = os.path.abspath(record_file_dir)
self.cb_resolution.setCurrentIndex(self.cb_resolution.findData(record_resolution))
self.cb_vcodec.setCurrentIndex(self.cb_vcodec.findData(record_vcodec))
self.dsb_frame_rate.setDecimals(1)
self.dsb_frame_rate.setValue(record_frame_rate)
self.le_file_path.setText(record_file_dir)
'''
关联事件
'''
#设备
self.cb_camera_devices.currentIndexChanged.connect(lambda: self.indexChangedEvent(self, self.cb_camera_devices))
self.cb_voice_devices.currentIndexChanged.connect(lambda: self.indexChangedEvent(self, self.cb_voice_devices))
self.cb_screen_devices.currentIndexChanged.connect(lambda: self.indexChangedEvent(self, self.cb_screen_devices))
self.cb_system_voice_devices.currentIndexChanged.connect(lambda: self.indexChangedEvent(self, self.cb_system_voice_devices))
#快捷键
self.le_start_record_camera_shortcut.textChanged.connect(lambda: self.textChangedEvent(self.le_start_record_camera_shortcut))
self.le_start_record_screen_shortcut.textChanged.connect(lambda: self.textChangedEvent(self.le_start_record_screen_shortcut))
self.le_start_stop_exit_shortcut.textChanged.connect(lambda: self.textChangedEvent(self.le_start_stop_exit_shortcut))
#录制参数
self.cb_resolution.currentIndexChanged.connect(lambda: self.indexChangedEvent(self, self.cb_resolution))
self.cb_vcodec.currentIndexChanged.connect(lambda: self.indexChangedEvent(self, self.cb_vcodec))
self.dsb_frame_rate.valueChanged.connect(self.valueChangedEvent)
self.le_file_path.textChanged.connect(lambda: self.textChangedEvent(self.le_file_path))
# self.le_start_record_camera_shortcut.keyPressEvent = self.record_keypress
# print(dir(self.le_start_record_camera_shortcut.keyPressEvent))
# self.le_file_path.setText()
self.btn_file_dir.clicked.connect(self.file_dir_select)
def record_keypress(self, event):
print('text:%s' %event.text())
print('key:%d' % event.key())
print('modifiers: %s' % event.modifiers())
print('nativeVirtualKey:%s' % event.nativeVirtualKey())
def file_dir_select(self, event):
current_dir = self.le_file_path.text()
if os.path.exists(current_dir):
selected_dir = QFileDialog.getExistingDirectory(self, '选择录像保存目录', current_dir, QFileDialog.ShowDirsOnly)
self.le_file_path.setText(selected_dir)
def showSettingWindow(self):
if not self.isVisible():
self.exec_()
def closeEvent(self, event):
print('changed?%s' % self.changed)
if self.changed:
self.save_setting()
question = QMessageBox(self)
question.setText('设置已保存,重新启动软件生效。')
question.setWindowTitle('提示')
question.setIcon(QMessageBox.Question)
question.addButton(QMessageBox.Yes)
question.exec()
print('parent is None?%s' % (self.parent is None))
self.changed = False
self.setVisible(False)
event.ignore()
def save_setting(self):
self.rc.write()
def setting(self, obj_name, value):
section = ''
name = ''
if obj_name.find('.') >= 0:
section = obj_name.split('.')[0]
name = obj_name.split('.')[1]
if type(value) == type(''):
value = value.strip()
print('check param in index changed:')
print('section:%s' % section)
print('name:%s' % name )
print('value:%s' % value)
if self.rc.config.has_option(section, name):
self.changed = True
self.rc.config.set(section, name, value)
else:
pass
print('not found this section or name.')
def indexChangedEvent(self, index, obj):
# print('in data changed event.')
print(index)
print('event obj:%s' % type(obj))
print('obj name:%s' % obj.objectName())
value = obj.currentData()
obj_name = obj.objectName()
self.setting(obj_name, value)
def textChangedEvent(self, obj):
# print('type of self:%s' % type(self))
# print('type of text:%s' % type(text))
# print('type of obj:%s' % type(obj))
value = obj.text()
obj_name = obj.objectName()
self.setting(obj_name, value)
def valueChangedEvent(self):
print('in value changed event.')
obj = self.dsb_frame_rate
value = obj.value()
print('value:%1.f' % value)
obj_name = obj.objectName()
self.setting(obj_name, str(value))

91
Shortcut.py Normal file
View File

@ -0,0 +1,91 @@
import PyHook3
import pythoncom
class Shortcut():
global HOTKEYS
global ACTIONS
global KEY_STATUS
global PRESSED_COUNT
HOTKEYS = {}
ACTIONS = {}
KEY_STATUS = {}
PRESSED_COUNT = {}
def __init__(self):
pass
def add(self, key_group_id , key_group, action):
global HOTKEYS
global ACTIONS
HOTKEYS[key_group_id] = key_group
ACTIONS[key_group_id] = action
def KeyDownEvent(self, event):
global KEY_STATUS
global HOTKEYS
global PRESSED_COUNT
global ACTIONS
#标记状态
KEY_STATUS[event.KeyID] = True
# print('HOTKEYS: \n%s' % HOTKEYS)
# print('KEY DOWN/press key status: \n %s' % KEY_STATUS)
for action_id, key_group in HOTKEYS.items():
#检查每个按键状态
pressed = True
for i in range(len(key_group)):
key = key_group[i]
if key not in KEY_STATUS:
pressed = False
break
if pressed:
# if action_id not in PRESSED_COUNT.keys():
# PRESSED_COUNT[action_id] =1
# else:
# PRESSED_COUNT[action_id] +=1
print('match.')
#限制一直按下快捷键 动作执行的次数
if action_id not in PRESSED_COUNT.keys():
action=ACTIONS[action_id]
if action:
action()
PRESSED_COUNT[action_id] =1
return True
def KeyUpEvent(self, event):
global KEY_STATUS
global HOTKEYS
global PRESSED_COUNT
global ACTIONS
if event.KeyID in KEY_STATUS.keys():
KEY_STATUS.pop(event.KeyID)
# print('KEY UP/press key status: \n %s' % KEY_STATUS)
#清空组合按键记录
for action_id, key_group in HOTKEYS.items():
if event.KeyID in key_group:
if action_id in PRESSED_COUNT.keys():
PRESSED_COUNT.pop(action_id)
return True
def monitor(self):
# create the hook mananger
hm = PyHook3.HookManager()
# hm.MouseAllButtonsDown = OnMouseEvent
hm.KeyDown = self.KeyDownEvent
hm.KeyUp = self.KeyUpEvent
hm.HookKeyboard()
# pythoncom.PumpMessages()

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
build/exe.win-amd64-3.6.7z Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More