diff --git a/DevicesInfo.py b/DevicesInfo.py index 3b297e9..39abe3d 100644 --- a/DevicesInfo.py +++ b/DevicesInfo.py @@ -2,10 +2,22 @@ import re import RunCMD from RunCMD import run_cmd import cchardet +import logging class DevicesInfo(): def __init__(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) + 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, universal_newlines = False) @@ -33,11 +45,15 @@ class DevicesInfo(): device_line = [] devices_output_copy = devices_output[0:] devices_txt = '' + self.logger.info('devices_text:') for txt in devices_output_copy: # charset = cchardet.detect(txt) # if charset - devices_txt += txt.decode('utf-8','ignore').replace(r'\r\n','') + self.logger.info(txt) + devices_txt += txt.decode('utf-8').replace(r'\r\n','') # print(dir(re)) + self.logger.info('decode result:') + self.logger.info(devices_txt) print(devices_txt) results = re.findall(r'\[[^\]]+\]([^\[]+)',devices_txt) # results.pop(0) diff --git a/RecordConfig.py b/RecordConfig.py index 9bf882e..64182d7 100644 --- a/RecordConfig.py +++ b/RecordConfig.py @@ -3,7 +3,7 @@ # from DevicesInfo import * class RecordConfig(): - def __init__(self, config_file_name = 'config.ini'): + def __init__(self, config_file_name = 'configByLinxiao.ini'): self.file_name = config_file_name self.encoding = 'gb2312' self.load() @@ -41,6 +41,11 @@ class RecordConfig(): conf.set(record_section_name,'vcodec','libx264') conf.set(record_section_name,'frame_rate','7.0') conf.set(record_section_name,'file_dir','.') + conf.set(record_section_name,'threads', '4') + + conf.add_section('author') + conf.set('author', 'name', 'linxiao') + conf.set('author', 'mail', '940950943@qq.com') self.config = conf self.write() diff --git a/RecordHelp.py b/RecordHelp.py index 2da186d..d3391b5 100644 --- a/RecordHelp.py +++ b/RecordHelp.py @@ -19,22 +19,20 @@ class RecordHelp(QWidget): self.setObjectName("Form") self.resize(386, 238) icon = QtGui.QIcon() - icon.addPixmap(QtGui.QPixmap("resource/gutin.ico"), QtGui.QIcon.Normal, QtGui.QIcon.Off) + icon.addPixmap(QtGui.QPixmap("../record-camera-and-screen/resource/gutin.ico"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.setWindowIcon(icon) 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.gridLayoutWidget = QtWidgets.QWidget(self) - self.gridLayoutWidget.setGeometry(QtCore.QRect(19, 19, 341, 201)) - self.gridLayoutWidget.setObjectName("gridLayoutWidget") - self.gridLayout = QtWidgets.QGridLayout(self.gridLayoutWidget) - self.gridLayout.setContentsMargins(0, 0, 0, 0) - self.gridLayout.setObjectName("gridLayout") - self.verticalLayout = QtWidgets.QVBoxLayout() + self.verticalLayoutWidget = QtWidgets.QWidget(self) + self.verticalLayoutWidget.setGeometry(QtCore.QRect(0, 0, 391, 241)) + self.verticalLayoutWidget.setObjectName("verticalLayoutWidget") + self.verticalLayout = QtWidgets.QVBoxLayout(self.verticalLayoutWidget) + self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.verticalLayout.setObjectName("verticalLayout") - self.label = QtWidgets.QLabel(self.gridLayoutWidget) + self.label = QtWidgets.QLabel(self.verticalLayoutWidget) font = QtGui.QFont() font.setPointSize(14) font.setBold(True) @@ -43,31 +41,50 @@ class RecordHelp(QWidget): self.label.setAlignment(QtCore.Qt.AlignCenter) self.label.setObjectName("label") self.verticalLayout.addWidget(self.label) - self.label_2 = QtWidgets.QLabel(self.gridLayoutWidget) + self.label_2 = QtWidgets.QLabel(self.verticalLayoutWidget) font = QtGui.QFont() font.setPointSize(12) font.setBold(True) font.setWeight(75) self.label_2.setFont(font) + self.label_2.setAlignment(QtCore.Qt.AlignCenter) self.label_2.setObjectName("label_2") self.verticalLayout.addWidget(self.label_2) - self.label_3 = QtWidgets.QLabel(self.gridLayoutWidget) + self.label_3 = QtWidgets.QLabel(self.verticalLayoutWidget) font = QtGui.QFont() + font.setFamily("Agency FB") font.setPointSize(12) font.setBold(True) font.setWeight(75) self.label_3.setFont(font) + self.label_3.setAlignment(QtCore.Qt.AlignCenter) self.label_3.setObjectName("label_3") self.verticalLayout.addWidget(self.label_3) - self.label_4 = QtWidgets.QLabel(self.gridLayoutWidget) + self.label_4 = QtWidgets.QLabel(self.verticalLayoutWidget) + self.label_4.setBaseSize(QtCore.QSize(0, 0)) font = QtGui.QFont() - font.setPointSize(12) font.setBold(True) font.setWeight(75) self.label_4.setFont(font) + self.label_4.setAlignment(QtCore.Qt.AlignCenter) self.label_4.setObjectName("label_4") self.verticalLayout.addWidget(self.label_4) - self.gridLayout.addLayout(self.verticalLayout, 0, 0, 1, 1) + self.label_5 = QtWidgets.QLabel(self.verticalLayoutWidget) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.label_5.setFont(font) + self.label_5.setAlignment(QtCore.Qt.AlignCenter) + self.label_5.setObjectName("label_5") + self.verticalLayout.addWidget(self.label_5) + self.label_6 = QtWidgets.QLabel(self.verticalLayoutWidget) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.label_6.setFont(font) + self.label_6.setAlignment(QtCore.Qt.AlignCenter) + self.label_6.setObjectName("label_6") + self.verticalLayout.addWidget(self.label_6) self.retranslateUi() QtCore.QMetaObject.connectSlotsByName(self) @@ -77,8 +94,10 @@ class RecordHelp(QWidget): self.setWindowTitle(_translate("Form", "帮助")) self.label.setText(_translate("Form", "Gutin谷田会议视频录播管理系统")) self.label_2.setText(_translate("Form", "主要功能:录制屏幕和摄像头")) - self.label_3.setText(_translate("Form", "开发者:林潇 ")) - self.label_4.setText(_translate("Form", "联系方式:940950943@qq.com")) + self.label_3.setText(_translate("Form", "默认快捷键:")) + self.label_4.setText(_translate("Form", "录制摄像头:Ctrl + Shfit + Alt + A")) + self.label_5.setText(_translate("Form", "录制屏幕:Ctrl + Shift + Alt + B")) + self.label_6.setText(_translate("Form", "停止录制:Ctrl + Shift + Alt + C")) def showWindow(self): if not self.isVisible(): diff --git a/RecordVideo.py b/RecordVideo.py index 43cc4f0..f43430f 100644 --- a/RecordVideo.py +++ b/RecordVideo.py @@ -11,7 +11,7 @@ import RecordConfig from RecordConfig import * import logging import RunCMD -from RunCMD import run_cmd +from RunCMD import get_ffmpeg_path class RecordVideo(): @@ -72,6 +72,8 @@ class RecordVideo(): self.brate=rc.config.getfloat('record','frame_rate') #文件目录 self.file_dir= os.path.abspath(rc.config.get('record','file_dir')) + #线程数 + self.threads = rc.config.getint('record','threads') self.logger.info('camera device name: %s' % self.camera_name) self.logger.info('voice device name: %s' % self.voice_device_name) @@ -83,27 +85,6 @@ class RecordVideo(): self.logger.info('save dir: %s' % self.file_dir) - def get_ffmpeg_path(self): - - datadir = '' - subdir = os.path.join('ffmpeg-shared','bin') - - if getattr(sys, 'frozen', False): - # The application is frozen - datadir = os.path.dirname(sys.executable) - else: - # The application is not frozen - # Change this bit to match where you store your data files: - datadir = os.path.dirname(__file__) - datadir = os.path.join(datadir, subdir) - datadir = os.path.abspath(datadir) - print(datadir) - #如果存在ffmpeg.exe - if os.path.isfile(os.path.join(datadir, 'ffmpeg.exe')): - return datadir - #兼容环境变量设置 - return '' - def start_ffmpeg(self,cmd, shell = True): try: @@ -159,17 +140,17 @@ class RecordVideo(): # self.logger.info(self.process.communicate()) # print('done.') - def record(self,cmd='ffmpeg -h', target = None): + def record(self, cmd='ffmpeg -h', target = None): if target: - cmd = os.path.join(self.get_ffmpeg_path(), cmd) + cmd = os.path.join(get_ffmpeg_path(), cmd) 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) + self.record_thread = Thread(name=self.record_thread_name, target= target, args = (cmd,), daemon=True) th.start() self.recording=True self.exception_exit = False - self.record_thread=th + print('record thread,ident:%d' % self.record_thread.ident) # th.join() @@ -199,12 +180,13 @@ class RecordVideo(): 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' %( + record_cmd='ffmpeg -f dshow -i video=\"%s\":audio=\"%s\" -acodec libmp3lame -vcodec %s -preset:v ultrafast -tune:v zerolatency -s %s -r %d -threads %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.threads, self.get_file_name() ) # print(record_cmd) @@ -213,16 +195,43 @@ class RecordVideo(): 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( + record_cmd='ffmpeg -f dshow -i video="{}":audio="{}" -acodec libmp3lame -vcodec {} -preset:v ultrafast -tune:v zerolatency -s {} -r {} -threads {} -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, + '1024x768', #屏幕录制分辨率固定 self.brate, + self.threads, self.get_file_name() ) self.record(record_cmd, self.start_ffmpeg) - + + def check_device(self): + #简单验证摄像头设置是否为空 + ready = True + l_msg = '' + if not self.camera_name: + ready = False + l_msg += '摄像头设备为空\n' + + if not self.voice_device_name: + ready = False + l_msg += '麦克风设备为空\n' + + if not self.screen_name: + ready = False + l_msg += '屏幕录制驱动为空\n' + + if not self.system_voice_device_name: + ready = False + l_msg += '系统声音录制驱动为空\n' + + if ready: + l_msg = '设备检测正常' + print(l_msg) + self.logger.info(l_msg) + return ready + def debug_camera(self): try: @@ -252,14 +261,18 @@ class RecordVideo(): return device_name def get_file_name(self): - time_str=datetime.now().strftime('%Y%m%d_%H%M%S') - video_type = '' + date_dir = datetime.now().strftime('%Y-%m-%d') + time_str = datetime.now().strftime('%H_%M') + video_type_name = '' if self.record_type == RecordType.Camera: - video_type = '摄像头' + video_type_name = '摄像头' if self.record_type == RecordType.Screen: - video_type = '屏幕' + video_type_name = '屏幕' - file_name = os.path.join(self.file_dir, '{}_{}{}'.format(video_type, time_str, self.file_suffix)) + today_file_dir = os.path.join(self.file_dir, date_dir) + if not os.path.exists(today_file_dir): + os.mkdir(today_file_dir) + file_name = os.path.join(today_file_dir, '{}-{}-{}{}'.format(video_type_name, time_str, self.resolution, self.file_suffix)) print('recording file name: %s' % file_name) return file_name diff --git a/RecordWindow.py b/RecordWindow.py index 76d5d75..3bb02be 100644 --- a/RecordWindow.py +++ b/RecordWindow.py @@ -303,16 +303,30 @@ class RecordWindow(QtWidgets.QWidget): 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() + if self.rv.check_device(): + #开始录制 + if rtype == RecordType.Camera: + print('开始录制摄像头...') + self.rv.record_camera() + elif rtype == RecordType.Screen: + print('开始录制屏幕...') + self.rv.record_screen() + + self.start_timer() + self.update_state() + else: + + question = QMessageBox.information(self, '提示', '检测到录制设备缺失,无法进行录制,请先完善设备设置。', QMessageBox.Yes) + # 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('软件将退出.') '''' 鼠标拖动窗体 diff --git a/RunCMD.py b/RunCMD.py index 161e01d..2b3ec52 100644 --- a/RunCMD.py +++ b/RunCMD.py @@ -1,8 +1,10 @@ -import subprocess +import subprocess,os,sys def run_cmd(cmd, shell = True, universal_newlines = False): output_info=[] output_err=[] + cmd = os.path.join(get_ffmpeg_path(), cmd) + print('执行cmd:\n%s' % cmd) with subprocess.Popen(cmd, shell = shell, universal_newlines = universal_newlines, @@ -26,4 +28,26 @@ def run_cmd(cmd, shell = True, universal_newlines = False): return output_err, output_info + +def get_ffmpeg_path(): + + datadir = '' + subdir = os.path.join('ffmpeg-shared','bin') + + if getattr(sys, 'frozen', False): + # The application is frozen + datadir = os.path.dirname(sys.executable) + else: + # The application is not frozen + # Change this bit to match where you store your data files: + datadir = os.path.dirname(__file__) + datadir = os.path.join(datadir, subdir) + datadir = os.path.abspath(datadir) + print(datadir) + #如果存在ffmpeg.exe + if os.path.isfile(os.path.join(datadir, 'ffmpeg.exe')): + return datadir + #兼容环境变量设置 + return '' + \ No newline at end of file diff --git a/SettingWindow.py b/SettingWindow.py index 62389c8..92321a4 100644 --- a/SettingWindow.py +++ b/SettingWindow.py @@ -173,7 +173,7 @@ class SettingWindow(QDialog): def retranslateUi(self): _translate = QtCore.QCoreApplication.translate - self.setWindowTitle(_translate("Form", "设置-Development By Linxiao")) + self.setWindowTitle(_translate("Form", "设置")) self.label_3.setText(_translate("Form", "屏幕录制设备:")) self.label_2.setText(_translate("Form", "声音输入设备:")) self.label.setText(_translate("Form", "摄像头名称:")) diff --git a/configByLinxiao.ini b/configByLinxiao.ini new file mode 100644 index 0000000..211af45 --- /dev/null +++ b/configByLinxiao.ini @@ -0,0 +1,22 @@ +[devices] +camera_device_name = +voice_device_name = +screen_device_name = +system_voice_device_name = + +[shortcut] +camera = 160,162,164,65 +screen = 160,162,164,66 +stop = 160,162,164,67 + +[record] +resolution = 1920x1080 +vcodec = libx264 +frame_rate = 7.0 +file_dir = . +threads = 4 + +[author] +name = linxiao +mail = 940950943@qq.com + diff --git a/csetup.py b/csetup.py index 12aa5bd..f865b64 100644 --- a/csetup.py +++ b/csetup.py @@ -2,8 +2,8 @@ import sys from cx_Freeze import setup, Executable # Dependencies are automatically detected, but it might need fine tuning. -build_exe_options = { 'include_files':['resource','ffmpeg-shared']} -install_exe_options = { 'install_dir' : 'd:\\record-camera-and-screen', 'build_dir':'build', 'install_exe':'d:\\record-camera-and-screen'} +build_exe_options = { 'include_files':['resource','ffmpeg-shared'], 'build_exe' : 'd:/dev/record/record-win'} +install_exe_options = { 'install_dir' : 'd:/dev/record/record-win', 'build_dir':'build', 'install_exe':'d:\\record-camera-and-screen'} # GUI applications require a different base on Windows (the default is for a # console application). @@ -12,7 +12,7 @@ if sys.platform == "win32": base = "Win32GUI" executables = [ - Executable('RecordWindow.py', base=base, icon = 'resource/gutin.ico', targetName = 'RecordWindow.exe') + Executable('RecordWindow.py', base=base, icon = 'resource/gutin.ico') ] setup( name = "Gutin谷田会议视频录播管理系统", version = "0.2",