Last active
May 17, 2024 06:30
-
-
Save lukakostic/2f8008f61f2c4c80a43d9465ad1d9c47 to your computer and use it in GitHub Desktop.
Python screencap and screenshot gui tool
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
""" | |
uses following cli tools: | |
slop - let user area select on screen | |
maim - cli screenshot tool , uses slop | |
recordMyDesktop - cli tool to screencap videos | |
zenity - cli tool for dialogs (save as dialog) | |
xclip - push things to clipboard from stdin | |
xdotool - find x window id by PID | |
########################### | |
If clipboard ("Copy") was used then image data gets put into clipboard | |
If tmp or save-as then file path gets put into clipboard (and file saved) | |
also prints file path to stdout | |
if clipboard then prints nothing | |
""" | |
import subprocess | |
import time | |
import tkinter as tk | |
from tkinter import ttk | |
RECORDING = False # are we recording a video | |
RECORDING_PID = None #recordMyDesktop pid | |
RECORDING_PROC = None #subprocess.Popen instance of r.m.d. | |
OUT_FILE = None #output file | |
app = None #tk app | |
root = None #tk root | |
def exec(cmd,readOutp=True): | |
cmd = cmd.strip() | |
if(readOutp): | |
result = subprocess.run(cmd, shell=True, text=True, stdout=subprocess.PIPE) | |
return result.stdout.strip() | |
else: | |
result = subprocess.run(cmd, shell=True) | |
return None | |
# exec in background, with optional killLine at which we return out | |
# if killLine empty, we return on first print. | |
# if program doesnt print anything we are stuck. | |
def execAsync(cmd,killLine): | |
proc = subprocess.Popen(cmd,shell=True, text=True,stdout=subprocess.PIPE) | |
outp = '' | |
while True: | |
line = proc.stdout.readline() | |
if not line: | |
break | |
#the real code does filtering here | |
outp = outp + line | |
if(line.strip() == killLine or killLine==""): return (outp,proc) | |
return (outp,proc) | |
# slop - cli tool to ask user to draw area on screen and return it in stdout | |
def slop(): | |
out = exec('slop -q -f "%w %h %x %y"') | |
if(out==''): return None | |
raw = out | |
out = tuple(map(lambda x:int(x),out.split(' '))) | |
return { "width": out[0], "height": out[1], "x":out[2], "y":out[3], "raw":raw } | |
## exec cmd and push it to background, get its pid and find its window, make it topmost. | |
def pushTopCmd(cmd): | |
return f"""{cmd} & pid=$! && sleep 0.2 && wmctrl -i -a `xdotool search --pid "$pid" | tail -1` -b add,above && wait "$pid" """ | |
# zenity - dialogs cli tool | |
def saveDialog(defName='img'): | |
return exec(pushTopCmd(f'zenity --file-selection --save --confirm-overwrite --title="Save as" --filename="{defName}"')) | |
class CaptureApp: | |
def __init__(self, root): | |
self.root = root | |
self.root.title("MyScreenshot") | |
self.root.geometry("400x300") | |
self.root.resizable(False, False) | |
self.center_window() | |
self.create_widgets() | |
def center_window(self): | |
window_width = 400 | |
window_height = 300 | |
screen_width = self.root.winfo_screenwidth() | |
screen_height = self.root.winfo_screenheight() | |
position_top = int(screen_height / 2 - window_height / 2) | |
position_right = int(screen_width / 2 - window_width / 2) | |
self.root.geometry(f'{window_width}x{window_height}+{position_right}+{position_top}') | |
def create_widgets(self): | |
self.mode_var = tk.StringVar(value="Screenshot") | |
self.area_var = tk.StringVar(value="Area") | |
self.action_var = tk.StringVar(value="Copy") | |
self.framerate_var = tk.IntVar(value=20) | |
# Common button style | |
button_style = { | |
"font": ("Arial", 12), | |
"relief": "solid", | |
"bd": 2, | |
"bg": "#f0f0f0", | |
"activebackground": "#d9d9d9", | |
"width": 10, | |
"height": 2, | |
} | |
# Screenshot / Video Buttons | |
self.mode_frame = tk.Frame(self.root) | |
self.mode_frame.pack(fill=tk.X, pady=4) | |
self.screenshot_button = tk.Radiobutton(self.mode_frame, text="Screenshot", variable=self.mode_var, value="Screenshot", command=self.toggle_video_options, **button_style) | |
self.screenshot_button.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) | |
self.video_button = tk.Radiobutton(self.mode_frame, text="Video", variable=self.mode_var, value="Video", command=self.toggle_video_options, **button_style) | |
self.video_button.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) | |
# Area / Window / Fullscreen Buttons | |
self.area_frame = tk.Frame(self.root) | |
self.area_frame.pack(fill=tk.X, pady=4) | |
self.area_button = tk.Radiobutton(self.area_frame, text="Area", variable=self.area_var, value="Area", **button_style) | |
self.area_button.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) | |
#self.window_button = tk.Radiobutton(self.area_frame, text="Window", variable=self.area_var, value="Window", **button_style) | |
#self.window_button.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) | |
self.fullscreen_button = tk.Radiobutton(self.area_frame, text="Fullscreen", variable=self.area_var, value="Fullscreen", **button_style) | |
self.fullscreen_button.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) | |
# Copy / File / Save as | |
self.action_frame = tk.Frame(self.root) | |
self.action_frame.pack(fill=tk.X, pady=4) | |
self.copy_button = tk.Radiobutton(self.action_frame, text="Clipboard", variable=self.action_var, value="Copy", **button_style) | |
self.copy_button.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) | |
self.file_button = tk.Radiobutton(self.action_frame, text="Tmp file", variable=self.action_var, value="Tmp", **button_style) | |
self.file_button.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) | |
self.save_button = tk.Radiobutton(self.action_frame, text="Save as", variable=self.action_var, value="Save", **button_style) | |
self.save_button.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) | |
# Framerate Dropdown | |
self.framerate_frame = tk.Frame(self.root) | |
self.framerate_label = tk.Label(self.framerate_frame, text="Framerate:", font=("Arial", 12)) | |
self.framerate_label.pack(side=tk.LEFT, padx=5) | |
self.framerate_dropdown = ttk.Combobox(self.framerate_frame, textvariable=self.framerate_var, values=[15, 20, 25, 30, 45, 50, 60]) | |
self.framerate_dropdown.pack(side=tk.LEFT, padx=5) | |
self.framerate_frame.pack(pady=10) | |
self.framerate_frame.pack_forget() # Hide initially | |
# Capture Button | |
self.capture_button = tk.Button(self.root, text="CAPTURE", font=("Arial", 16), bg="green", fg="white", height=2, command=self.capture_action) | |
self.capture_button.pack(pady=10, fill=tk.X) | |
def toggle_video_options(self): | |
if self.mode_var.get() == "Video": | |
self.framerate_frame.pack(pady=10) | |
else: | |
self.framerate_frame.pack_forget() | |
def GoQuit(self): | |
if RECORDING: | |
outp = execAsync(f'kill -2 "{RECORDING_PID}" && sleep 1 && kill -9 "{RECORDING_PID}"',"") | |
time.sleep(0.2) | |
#while True: | |
# line = RECORDING_PROC.stdout.readline() | |
# if not line: | |
# break | |
# print(f"!!!{line}!!!") | |
exec(f'echo -n "{OUT_FILE}" | xclip -selection c',False) | |
if(OUT_FILE): print(OUT_FILE) | |
self.root.destroy() | |
def capture_action(self): | |
global RECORDING, RECORDING_PID, OUT_FILE, RECORDING_PROC | |
if RECORDING: | |
self.GoQuit() | |
return | |
scOrVideo = self.mode_var.get() | |
sizeMode = self.area_var.get() | |
fileAction = self.action_var.get() | |
fps = self.framerate_var.get() | |
if(scOrVideo == 'Video' and fileAction == 'Copy'): | |
exec(pushTopCmd('zenity --error --text="Cannot record video to clipboard."'),False) | |
return | |
if(fileAction == 'Tmp'): | |
OUT_FILE = exec('mktemp') | |
if(OUT_FILE==''): | |
return | |
elif(fileAction == 'Save'): | |
OUT_FILE = saveDialog() | |
if(OUT_FILE==''): | |
return | |
self.root.withdraw() | |
time.sleep(0.1) | |
area = None | |
if(sizeMode == 'Area'): | |
area = slop() | |
if(area is None): | |
self.root.deiconify() | |
return | |
if(scOrVideo=='Screenshot'): | |
cmd = 'maim ' | |
if(sizeMode == 'Area'): | |
cmd = cmd + f"--geometry={area['width']}x{area['height']}+{area['x']}+{area['y']} " | |
if(fileAction != 'Copy'): | |
cmd = cmd + f'| tee "{OUT_FILE}" ' | |
#else: | |
cmd = cmd + " | xclip -selection clipboard -t image/png " | |
exec(cmd,False) | |
elif(scOrVideo=='Video'): | |
if(OUT_FILE.endswith('.ogv') == False): | |
OUT_FILE = OUT_FILE + '.ogv' | |
cmd = f"recordmydesktop --no-frame --overwrite --on-the-fly-encoding --fps {fps} " | |
if(sizeMode == 'Area'): | |
cmd = cmd + f"-x {area['x']} -y {area['y']} --width {area['width']} --height {area['height']} " | |
if(fileAction == 'Copy'): | |
raise Exception("Cannot copy a video into clipboard") | |
return | |
else: | |
cmd = cmd + ' -o "'+OUT_FILE+'" ' | |
RECORDING = True | |
cmd = cmd + ' & pid=$! && echo "$pid" ' | |
self.root.deiconify() | |
outp = execAsync(cmd,"") | |
RECORDING_PID = outp[0] | |
RECORDING_PROC = outp[1] | |
self.capture_button.configure(text="STOP RECORDING") | |
if(OUT_FILE): print(OUT_FILE) | |
print(f"PID!!:{RECORDING_PID}") | |
return | |
#RECORDING = False | |
self.GoQuit() | |
def keyPress(event): | |
if(event.keysym == 'Return'): | |
app.capture_action() | |
if(event.keysym == 'Escape'): | |
app.GoQuit() | |
#print(event.keysym) | |
if __name__ == "__main__": | |
root = tk.Tk() | |
app = CaptureApp(root) | |
root.bind("<Key>", keyPress) | |
root.mainloop() |
Author
lukakostic
commented
May 17, 2024
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment