# srec: A simple GUI for screen recording # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the # Free Software Foundation, version 3. from dataclasses import dataclass from datetime import datetime import os import subprocess from typing import Callable import gi gi.require_version("Gtk", "4.0") from gi.repository import Gtk # nopep8 pylint: disable=wrong-import-position @dataclass class Recording: filename: str process: subprocess.Popen[bytes] recording: Recording | None = None def make_filename() -> str: directory = os.environ.get( 'XDG_VIDEOS_DIR', os.path.expanduser('~/Videos')) os.makedirs(directory, exist_ok=True) timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') return os.path.join(directory, f'srec {timestamp}.mkv') def video_source(stack: Gtk.Stack) -> list[str]: if stack.get_child_by_name('not_recording').get_first_child().get_active(): return ['-f', 'v4l2', '-i', '/dev/video0'] return ['-f', 'x11grab', '-i', ':0.0+0,0'] def on_start_recording(_: Gtk.Button, stack: Gtk.Stack) -> None: global recording # pylint: disable=global-statement assert recording is None filename = make_filename() command = (['ffmpeg', '-framerate', '25'] + video_source(stack) + ['-f', 'pulse', '-ac', '2', '-i', 'default', filename]) # pylint: disable=consider-using-with recording = Recording( filename=filename, process=subprocess.Popen(command, stdin=subprocess.PIPE)) stack.set_visible_child_name("recording") def on_stop_recording(_: Gtk.Button, stack: Gtk.Stack) -> None: global recording # pylint: disable=global-statement assert recording is not None stdin = recording.process.stdin assert stdin is not None stdin.write(b'q') stdin.flush() recording.process.wait() recording = None stack.set_visible_child_name("not_recording") def make_button(label: str, action: Callable[[ Gtk.Button, Gtk.Stack], None], stack: Gtk.Stack) -> Gtk.Button: button = Gtk.Button(label=label) button.connect('clicked', action, stack) button.set_margin_top(10) button.set_margin_start(10) button.set_margin_end(10) button.set_margin_bottom(10) return button def on_activate(app: Gtk.Application) -> None: win = Gtk.ApplicationWindow(application=app) win.set_title('SRec') stack = Gtk.Stack() nr_box = Gtk.Box() nr_box.set_orientation(Gtk.Orientation.VERTICAL) webcam = Gtk.CheckButton(label='Webcam', active=True) nr_box.append(webcam) nr_box.append(Gtk.CheckButton(label='Screen', group=webcam)) nr_box.append(make_button("Start Recording", on_start_recording, stack)) stack.add_named(nr_box, "not_recording") r_box = Gtk.Box() r_box.set_orientation(Gtk.Orientation.VERTICAL) r_box.append(make_button("Stop Recording", on_stop_recording, stack)) stack.add_named(r_box, "recording") win.set_child(stack) win.present() def main() -> None: app = Gtk.Application(application_id='net.chkno.srec') app.connect('activate', on_activate) app.run(None) if __name__ == '__main__': main()