Тема: Вирівнювання часу запису відео з екрана
З python я не дуже знайомий, тільки почав потроху вивчати.
З'явилась потреба в програмі, яка буде записувати відео з конкретного вікна.
Спочатку була думка робити це через OBS, але OBS сильно навантажує систему (ноут слабенький), тому почав шукати рішення на python.
Знайшов відео, і почав робити.
В принципі все зробив, але є нюанси.
На моєму ноуті нормально працює тільки fps 25, якщо ж вище - тоді вихідне відео пришвидшене.
На тому ноуті, де це повинно працювати взагалі доводиться ставити 10 fps, щоб вихідне відео було нормальної швидкості, хоча той ноут набагато слабкіший.
Вікно програми виводиться на tkinter, наче було все нормально, але з'явився баг - після запуску програми її вікно замість 300x130 має розмір 375x200, а після старту запису приймає потрібний розмір.
Власне потрібна допомога в нормалізації швидкості вихідного відео незалежно від вказаного FPS, ChatGPT за добу страждань не зміг у цьому допомогти
Буду дуже вдячний за поради й пояснення по коду програми, й допомогу з вирішенням проблем!
Код програми:
from tkinter import*
from datetime import datetime, timedelta
import cv2
import numpy as np
import mss
import pygetwindow as gw
import psutil
from playsound3 import *
import threading
import os
import time
screen = Tk()
temp = 0
after_id = ''
is_recording = False
out = None
recording_thread = None
cont = False
total_time = 0
wtf = None
wtf_open = False
def vars(filename):
variables = {}
if not os.path.isfile(filename):
print("Файл не знайдено.")
return variables
try:
with open(filename, 'r') as file:
for line in file:
line = line.strip()
if '=' in line:
key, value = line.split('=', 1)
key = key.strip()
value = value.strip()
try:
value = int(value)
except ValueError:
try:
value = float(value)
except ValueError:
pass
variables[key] = value
except IOError:
print("Помилка вводу/виводу.")
return variables
variables = vars('scr.conf')
proc_name = variables.get('proc_name')
win_name = variables.get('win_name')
fps = variables.get('fps')
rs = variables.get('rs')
scale = variables.get('scale')
max_time = variables.get('max_time')
err1 = variables.get('err1')
err2 = variables.get('err2')
greeting = variables.get('greeting')
shelp = variables.get('shelp')
def calculate_bitrate(fps, target_bitrate=116803):
base_fps = 25
if fps <= 0:
raise ValueError("FPS має бути більше нуля")
bitrate = target_bitrate * (fps / base_fps)
return int(bitrate)
def tick():
global temp, after_id
after_id = screen.after(1000, tick)
f_temp = datetime.fromtimestamp(temp).strftime('%M:%S')
timer.configure(text=str(f_temp))
temp += 1
def check_proc(proc_name):
res = ''
for proc in psutil.process_iter():
if proc.name() == proc_name:
res = 'ok'
break
if (res == 'ok'):
return True
else:
return False
def check_wn(name):
all_windows = gw.getAllWindows()
for window in all_windows:
if window.title == name:
return window
return None
def rec(state, pn, wn):
global is_recording, out, cont, total_time
if (state == 'start' and not is_recording):
if (check_proc(pn) == False):
text.configure(text=err1, bg='#d95961')
playsound('media/error.mp3')
elif (check_wn(wn) == False):
text.configure(text=err2, bg='#d95961')
playsound('media/error.mp3')
else:
is_recording = True
fourcc = cv2.VideoWriter_fourcc(*'XVID')
now = datetime.now()
dt_string = now.strftime("%d-%m-%Y_%H-%M-%S")
w = gw.getWindowsWithTitle(wn)[0]
if w.isMinimized:
w.restore()
w.activate()
new_size = (w.width*scale, w.height*scale)
out = cv2.VideoWriter('videos/'+wn+'_'+dt_string+'.avi', fourcc, fps, new_size)
bitrate = calculate_bitrate(fps)
try:
out.set(cv2.CAP_PROP_BITRATE, bitrate)
except Exception as e:
print("Не вдалося встановити бітрейт через OpenCV. Ігноруємо помилку:", e)
if (cont == False):
tick()
text.configure(text='Запис розпочато', bg='#72e665')
print('Запис розпочато')
playsound('media/ok.mp3')
else:
print('Відео збережено, запис продовжується')
elapsed_time = 0
start_time = datetime.now()
sct = mss.mss()
while is_recording:
current_time = datetime.now()
elapsed_time = (current_time - start_time).total_seconds()
monitor = {"top": w.top, "left": w.left, "width": w.width, "height": w.height}
img = sct.grab(monitor)
frame = np.array(img)
frame = cv2.cvtColor(frame, cv2.COLOR_BGRA2BGR)
frame = cv2.resize(frame, new_size, interpolation=cv2.INTER_LANCZOS4)
out.write(frame)
if (total_time >= max_time):
click('stop')
elif (elapsed_time >= rs+1):
total_time += elapsed_time
out.release()
is_recording = False
cont = True
threading.Thread(target=rec, args=('start', proc_name, win_name)).start()
if (cont == False):
text.configure(text='Запис закінчено', bg='#e67065')
print('Запис зупинено')
if (out is not None):
out.release()
cv2.destroyAllWindows()
is_recording = False
def func_wtf():
global wtf_open, wtf
if (wtf_open == False):
wtf = Toplevel(screen)
wtf.title(shelp)
wtf.geometry('300x200')
wtf.iconbitmap('media/icon.ico')
wtf.resizable(width=False, height=False)
wtf_text = 'Це програма\nдля відеозапису\nвікна виводу зображення\nз пульта!\n'
wtf_text += 'Шо тут не зрозуміло?'
wtext = Label(wtf,
bg='#cfd7e6',
text=wtf_text
)
wtext.place(x=10, y=10, width=280, height=180)
wtf_open = True
else:
wtf.lift()
wtf.focus_force()
def click(args):
if args == 'start':
threading.Thread(target=rec, args=('start', proc_name, win_name)).start()
if args == 'stop':
global is_recording, cont, total_time
is_recording = False
cont = False
total_time = 0
if recording_thread is not None:
recording_thread.join()
screen.after_cancel(after_id)
global temp
temp = 0
timer.configure(text='00:00')
if args == 'wtf':
func_wtf()
def wtf_on_closing():
global wtf_open
if wtf_open == True:
wtf.destroy()
wtf_open = False
def on_closing():
if (wtf_open == True):
wtf.destroy()
screen.destroy()
screen.title('Відеозапис')
screen.geometry('300x130')
screen.resizable(width=False, height=False)
screen.config(bg='#cfd7e6')
screen.iconbitmap('media/icon.ico')
text = Label(screen,
bg='#cfd7e6',
text=greeting
)
text.place(x=10, y=10, width=280, height=20)
rec_image = PhotoImage(file='media/rec1.png')
stop_image = PhotoImage(file='media/stop1.png')
start = Button(screen,
image = rec_image,
relief = RAISED,
bd=1,
cursor='hand2',
command = lambda:click('start')
)
start.place(x=10, y=40, width=85, height=40)
stop = Button(screen,
image = stop_image,
relief = RAISED,
bd=1,
cursor='hand2',
command = lambda:click('stop')
)
stop.place(x=105, y=40, width=85, height=40)
timer = Label(screen, bg='#cfd7e6', text = '00:00', font=(35))
timer.place(x=200, y=40, width=90, height=40)
wtf = Button(screen,
text=shelp,
relief = RAISED,
bd=1,
cursor='hand2',
command = lambda:click('wtf')
)
wtf.place(x=10, y=90, width=280, height=30)
if (wtf_open == True):
wtf.protocol("WM_DELETE_WINDOW", wtf_on_closing)
screen.protocol("WM_DELETE_WINDOW", on_closing)
screen.mainloop()
Файл scr.conf
proc_name=scrcpy.exe
win_name=DJI RC Pro
fps=60
rs=300
max_time=2100
scale=2
err1=Запусти SCRCPY!
err2=Підключи пульт до ноута!
greeting=Наче працює...
shelp=Шо це таке?