├── LICENSE ├── README.md ├── Screenshot.png └── SlickRecorder ├── SlickRecorder.py ├── __init__.py ├── c_audio_rec.py ├── captions.py ├── cursor1.png ├── cursor2.png ├── cursor3.png ├── cursor4.png ├── cursor5.png ├── cursor6.png ├── cursor7.png ├── gui.py ├── icoon.py ├── keyboard_text.png ├── m_audio_rec.py ├── main.py ├── recorder.py ├── red_dot.png ├── refine.py ├── start_icon.py └── title.png /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Cactochan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Slick Recorder 2 | 3 | views 4 | 5 | **Slick Recorder** is a lightweight and stylish recording application designed specifically for Linux. It offers a sleek user interface and powerful features to make your recording experience seamless and enjoyable. 6 | 7 | ![Slick Recorder](Screenshot.png) 8 | *Screenshot of Slick Recorder in action* 9 | 10 | ## Features 11 | 12 | - **Noise Reduction**: Automatically reduces background noise for clear audio recordings. 13 | - **Keyboard Input Display**: Show your keyboard inputs in the recordings. 14 | - **Subtitle Generation**: Generate subtitles for your recordings effortlessly. 15 | - **Lightweight GUI**: A cool and minimalistic graphical interface to manage all your recording tasks. 16 | - **Custom Cursors**: Personalize your recording experience with custom cursor options. 17 | 18 | ## Requirements 19 | 20 | To use Slick Recorder, ensure you have the following installed: 21 | 22 | - **PulseAudio**: For handling audio streams. 23 | - **FFmpeg**: For audio and video processing. 24 | 25 | ## Installation 26 | 27 | You can install Slick Recorder via pip. Run the following command to get started: 28 | 29 | ```bash 30 | pip install SlickRecorder 31 | ``` 32 | 33 | ## To Run! 34 | Just type in terminal: 35 | ```bash 36 | SlickRecorder 37 | ``` 38 | 39 | ## License 40 | 41 | Slick Recorder is released under the MIT License. See the [LICENSE](LICENSE) file for more information. 42 | 43 | ## Acknowledgments 44 | 45 | All Cursor PNG images used in this project are sourced from [flaticon.com](https://www.flaticon.com/). 46 | -------------------------------------------------------------------------------- /Screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/merwin-asm/SlickRecorder/1b695c8adafb8bf2c9ebd5c1e38837aed07d33e9/Screenshot.png -------------------------------------------------------------------------------- /SlickRecorder/SlickRecorder.py: -------------------------------------------------------------------------------- 1 | from moviepy.editor import VideoFileClip, AudioFileClip, CompositeAudioClip 2 | import time 3 | import os 4 | import threading 5 | import refine 6 | import atexit 7 | import json 8 | 9 | """ .settings.json file : 10 | 11 | { 12 | 'record_mic': self.recordMicCheckbox.isChecked(), 13 | 'record_pc': self.recordPCCheckbox.isChecked(), 14 | 'show_keyboard': self.showKeyboardCheckbox.isChecked(), 15 | 'codec': self.codecComboBox.currentText(), 16 | 'voice_caption': self.voiceCaptionCheckbox.isChecked(), 17 | 'cursor':cursor, 18 | 'saving_location': self.savingLocationInput.text(), 19 | 'monitor': self.monitorComboBox.currentText(), 20 | 'fullscreen': self.fullscreenCheckbox.isChecked(), 21 | 'x_start':....., 22 | 'x_end': self.xEndInput.text() if not self.fullscreenCheckbox.isChecked() else None, 23 | 'y_start': self.yStartInput.text() if not self.fullscreenCheckbox.isChecked() else None, 24 | 'y_end': self.yEndInput.text() if not self.fullscreenCheckbox.isChecked() else None 25 | } 26 | 27 | """ 28 | 29 | 30 | f = open(".settings.json", "r") 31 | data = json.loads(f.read()) 32 | f.close() 33 | 34 | 35 | 36 | mic = data["record_mic"] 37 | pc = data["record_pc"] 38 | saving = data['saving_location'] 39 | refine_times = 4 40 | 41 | caption = data['voice_caption'] 42 | cursor = data['cursor'] 43 | if data["show_keyboard"] == True: 44 | keyboard = 1 45 | else: 46 | keyboard = 0 47 | 48 | monitor = data['monitor'] 49 | if data['fullscreen']: 50 | x1, y1, x2, y2 = "None", 0,0,0 51 | else: 52 | x1 , y1 , x2, y2 = data['x_start'], data['y_start'], data['x_end'], data['y_end'] 53 | 54 | codec = data['codec'] 55 | 56 | D = [] 57 | T = 0 58 | src = "" 59 | 60 | 61 | 62 | def ending(): 63 | if (not mic) and pc: 64 | enable_mic() 65 | 66 | os.system("rm screen_recording_.mp4") 67 | 68 | if pc and mic: 69 | os.system("rm pc.wav") 70 | elif pc: 71 | os.system("rm pc.wav") 72 | elif mic: 73 | os.system("rm mic.wav") 74 | 75 | print("\nEXITED!") 76 | 77 | atexit.register(ending) 78 | 79 | def combine(): 80 | global refine_times 81 | 82 | if mic and pc: 83 | refine.refine_audio("pc.wav", times=refine_times) 84 | audio = AudioFileClip("pc.wav") 85 | elif mic: 86 | refine.refine_audio("mic.wav", times=refine_times) 87 | audio = AudioFileClip("mic.wav") 88 | elif pc: 89 | audio = AudioFileClip("pc.wav") 90 | 91 | if mic or pc: 92 | video = VideoFileClip('screen_recording_.mp4') 93 | # Set the combined audio to the video 94 | video = video.set_audio(audio) 95 | # Export the final video 96 | video.write_videofile('screen_recording.mp4', codec='libx264', audio_codec='aac') 97 | 98 | def current_path(): 99 | x = os.path.abspath(__file__).split('/') 100 | x.pop() 101 | return "/".join(x) 102 | 103 | # Define two functions to run simultaneously 104 | def function_one(): 105 | global x1, y1, x2, y2, monitor, keyboard, codec, cursor 106 | os.system(f"python3 {current_path()}/recorder.py {x1} {y1} {x2} {y2} {monitor} {keyboard} {codec} {cursor}") 107 | D.append(0) 108 | 109 | def function_two(): 110 | os.system(f"python3 {current_path()}/m_audio_rec.py") 111 | D.append(0) 112 | 113 | def function_three(): 114 | os.system(f"python3 {current_path()}/c_audio_rec.py") 115 | D.append(0) 116 | 117 | def dissable_mic(): 118 | global src 119 | 120 | os.system("pactl info | grep \"Default Source\" > .default_src_pactl") 121 | 122 | f = open(".default_src_pactl") 123 | data = f.read() 124 | f.close() 125 | 126 | os.system("rm .default_src_pactl") 127 | 128 | src = data.split(":")[1].replace("\n", "") 129 | os.system(f"pactl get-source-volume {src} > .prev_src_vol") 130 | os.system(f"pactl set-source-volume {src} 0%") 131 | print(f"Temp Dissabled {src}") 132 | 133 | def enable_mic(): 134 | global src 135 | 136 | f = open(".prev_src_vol") 137 | data = f.read() 138 | f.close() 139 | 140 | per = "100%" 141 | for word in data.split(): 142 | if word.endswith("%"): 143 | per = word 144 | break 145 | 146 | os.system(f"pactl set-source-volume {src} {per}") 147 | os.system("rm .prev_src_vol") 148 | print(f"Enabled {src} with {per}") 149 | 150 | def main(): 151 | global T 152 | # Create thread objects for each function 153 | thread_one = threading.Thread(target=function_one).start() 154 | T += 1 155 | 156 | if mic and pc: 157 | T += 1 158 | 159 | thread_three = threading.Thread(target=function_three) 160 | thread_three.start() 161 | #thread_three.join() 162 | elif mic and (not pc): 163 | T += 1 164 | 165 | thread_two = threading.Thread(target=function_two) 166 | thread_two.start() 167 | #thread_two.join() 168 | elif (not mic) and pc: 169 | T += 1 170 | 171 | dissable_mic() 172 | 173 | thread_three = threading.Thread(target=function_three) 174 | thread_three.start() 175 | #thread_three.join() 176 | 177 | 178 | while len(D) != T: 179 | time.sleep(0.3) 180 | 181 | print("Both functions have completed. Exiting the program.") 182 | 183 | 184 | main() 185 | 186 | os.system("ffmpeg -i screen_recording.mp4 -b:v 4M screen_recording_.mp4") 187 | 188 | combine() 189 | 190 | if caption: 191 | os.system("python3 {current_path()}/captions.py") 192 | 193 | import time 194 | 195 | 196 | if os.getcwd != saving: 197 | os.system(f"cp screen_recording.mp4 {saving}/screen_recording_{time.time()}.mp4") 198 | os.system("rm screen_recording.mp4") 199 | 200 | -------------------------------------------------------------------------------- /SlickRecorder/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/merwin-asm/SlickRecorder/1b695c8adafb8bf2c9ebd5c1e38837aed07d33e9/SlickRecorder/__init__.py -------------------------------------------------------------------------------- /SlickRecorder/c_audio_rec.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import pyaudio 3 | import wave 4 | from pynput import keyboard 5 | import threading 6 | import time 7 | import os 8 | 9 | 10 | # Audio recording parameters 11 | FORMAT = pyaudio.paInt16 12 | CHANNELS = 2 13 | RATE = 44100 14 | CHUNK = 1024 15 | RECORD_SECONDS = 0 # Set to 0 for indefinite recording 16 | 17 | # File to save the recording 18 | WAVE_OUTPUT_FILENAME = "pc.wav" 19 | 20 | # Variable to control recording 21 | recording = True 22 | 23 | def on_press(key): 24 | global recording 25 | if key == keyboard.Key.esc: 26 | recording = False 27 | 28 | def get_device_index_by_name(p, device_name): 29 | """Find the device index by device name.""" 30 | for i in range(p.get_device_count()): 31 | info = p.get_device_info_by_index(i) 32 | if device_name in info['name']: 33 | return i 34 | return None 35 | 36 | def record_audio(): 37 | global recording 38 | audio = pyaudio.PyAudio() 39 | 40 | 41 | device_name = 'virtual_sink.monitor' 42 | 43 | # Find the device index 44 | device_index = get_device_index_by_name(audio, device_name) 45 | 46 | # Open stream from the virtual sink 47 | stream = audio.open(format=FORMAT, 48 | channels=CHANNELS, 49 | rate=RATE, 50 | input=True, 51 | frames_per_buffer=CHUNK, 52 | input_device_index=device_index) 53 | 54 | frames = [] 55 | 56 | print("Recording... Press ESC to stop.") 57 | os.system("touch .started_recording") 58 | while recording: 59 | data = stream.read(CHUNK) 60 | frames.append(data) 61 | 62 | # Stop recording 63 | print("Stopped recording.") 64 | stream.stop_stream() 65 | stream.close() 66 | audio.terminate() 67 | 68 | # Save the recording 69 | with wave.open(WAVE_OUTPUT_FILENAME, 'wb') as wf: 70 | wf.setnchannels(CHANNELS) 71 | wf.setsampwidth(audio.get_sample_size(FORMAT)) 72 | wf.setframerate(RATE) 73 | wf.writeframes(b''.join(frames)) 74 | 75 | def setup_virtual_sink(): 76 | # Create a virtual sink and loopback module 77 | subprocess.run(["pactl", "load-module", "module-null-sink", "sink_name=virtual_sink"], check=True) 78 | subprocess.run(["pactl", "load-module", "module-loopback", "sink=virtual_sink"], check=True) 79 | 80 | def cleanup_virtual_sink(): 81 | # Unload the virtual sink and loopback module 82 | subprocess.run(["pactl", "unload-module", "module-loopback"], check=True) 83 | subprocess.run(["pactl", "unload-module", "module-null-sink"], check=True) 84 | 85 | def main(): 86 | setup_virtual_sink() 87 | 88 | # Wait a bit to ensure the sink is set up 89 | time.sleep(1) 90 | 91 | # Set up key listener 92 | listener = keyboard.Listener(on_press=on_press) 93 | listener.start() 94 | 95 | # Start recording in a separate thread 96 | record_thread = threading.Thread(target=record_audio) 97 | record_thread.start() 98 | 99 | # Wait for recording to finish 100 | record_thread.join() 101 | listener.stop() 102 | 103 | cleanup_virtual_sink() 104 | 105 | if __name__ == "__main__": 106 | main() 107 | 108 | -------------------------------------------------------------------------------- /SlickRecorder/captions.py: -------------------------------------------------------------------------------- 1 | import speech_recognition as sr 2 | from moviepy.editor import VideoFileClip, TextClip, CompositeVideoClip 3 | from pydub import AudioSegment 4 | 5 | def extract_audio_from_video(video_path, audio_path): 6 | # Extract audio from the video 7 | video = VideoFileClip(video_path) 8 | audio = video.audio 9 | audio.write_audiofile(audio_path) 10 | 11 | # Convert audio to WAV format using pydub (if necessary) 12 | audio_segment = AudioSegment.from_file(audio_path) 13 | audio_segment.export(audio_path, format="wav") 14 | import numpy as np 15 | import math 16 | 17 | def transcribe_audio(audio_path): 18 | # Initialize recognizer 19 | recognizer = sr.Recognizer() 20 | 21 | # Load audio file 22 | audio = AudioSegment.from_file(audio_path) 23 | duration_ms = len(audio) 24 | 25 | # Split audio into segments (e.g., 4 seconds each) 26 | segment_length_ms = 4000 # 4 seconds 27 | captions = [] 28 | 29 | for start_ms in range(0, duration_ms, segment_length_ms): 30 | end_ms = min(start_ms + segment_length_ms, duration_ms) 31 | segment = audio[start_ms:end_ms] 32 | segment_path = 'temp_segment.wav' 33 | segment.export(segment_path, format="wav") 34 | 35 | with sr.AudioFile(segment_path) as source: 36 | audio_data = recognizer.record(source) 37 | try: 38 | # Transcribe audio segment 39 | text = recognizer.recognize_google(audio_data) 40 | start_time = start_ms / 1000.0 # Convert milliseconds to seconds 41 | duration = (end_ms - start_ms) / 1000.0 # Convert milliseconds to seconds 42 | captions.append({ 43 | 'text': text, 44 | 'start_time': start_time, 45 | 'duration': duration 46 | }) 47 | except sr.UnknownValueError: 48 | print(f"Google Speech Recognition could not understand audio segment from {start_ms}ms to {end_ms}ms") 49 | except sr.RequestError as e: 50 | print(f"Could not request results from Google Speech Recognition service; {e}") 51 | 52 | return captions 53 | 54 | def transcribe_audio_(audio_path): 55 | # Initialize recognizer 56 | recognizer = sr.Recognizer() 57 | 58 | # Load audio file 59 | with sr.AudioFile(audio_path) as source: 60 | # Recognize the audio 61 | audio_data = recognizer.record(source) 62 | try: 63 | # Use Google's Web Speech API to transcribe audio 64 | text = recognizer.recognize_google(audio_data) 65 | except sr.UnknownValueError: 66 | print("Google Speech Recognition could not understand audio") 67 | text = "" 68 | except sr.RequestError as e: 69 | print(f"Could not request results from Google Speech Recognition service; {e}") 70 | text = "" 71 | 72 | # Return a single caption entry for the whole audio file 73 | return [{'text': text, 'start_time': 0, 'duration': 0}] 74 | """ 75 | def add_captions_to_video(video_path, output_path, captions): 76 | # Load the video file 77 | video = VideoFileClip(video_path) 78 | 79 | # Create text clips for each caption 80 | text_clips = [] 81 | for caption in captions: 82 | print(f"Creating text clip: '{caption['text']}' from {caption['start_time']} for {caption['duration']} seconds") 83 | 84 | text_clip = TextClip( 85 | caption['text'], 86 | fontsize=24, 87 | color='white', 88 | bg_color='transparent', 89 | size=video.size 90 | ) 91 | text_clip = text_clip.set_duration(caption['duration']).set_start(caption['start_time']) 92 | text_clip = text_clip.set_position(('center', 'bottom')) 93 | 94 | text_clips.append(text_clip) 95 | 96 | # Overlay the text clips on the video 97 | final_video = CompositeVideoClip([video] + text_clips) 98 | 99 | # Write the final video to the output path 100 | final_video.write_videofile(output_path, codec='libx264', fps=24) 101 | """ 102 | # Paths to the input and output files 103 | video_path = 'screen_recording.mp4' 104 | audio_path = 'audio.wav' 105 | output_path = 'screen_recording.mp4' 106 | 107 | # Extract audio from video 108 | extract_audio_from_video(video_path, audio_path) 109 | 110 | # Transcribe audio to get captions 111 | captions = transcribe_audio(audio_path) 112 | print(captions) 113 | def add_captions_to_video(video_path, output_path, captions): 114 | # Load the video file 115 | video = VideoFileClip(video_path) 116 | 117 | # Create text clips for each caption 118 | text_clips = [] 119 | for caption in captions: 120 | print(f"Creating text clip: '{caption['text']}' from {caption['start_time']} for {caption['duration']} seconds") 121 | 122 | text_clip = TextClip( 123 | caption['text'], 124 | fontsize=35, 125 | color='white', 126 | bg_color='transparent', 127 | size=video.size 128 | ) 129 | text_clip = text_clip.set_duration(caption['duration']).set_start(caption['start_time']) 130 | text_clip = text_clip.set_position(('center', 330)) 131 | 132 | text_clips.append(text_clip) 133 | 134 | # Overlay the text clips on the video 135 | final_video = CompositeVideoClip([video] + text_clips) 136 | 137 | # Write the final video to the output path 138 | final_video.write_videofile(output_path, codec='libx264', fps=24) 139 | # Add captions to the video 140 | add_captions_to_video(video_path, output_path, captions) 141 | 142 | -------------------------------------------------------------------------------- /SlickRecorder/cursor1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/merwin-asm/SlickRecorder/1b695c8adafb8bf2c9ebd5c1e38837aed07d33e9/SlickRecorder/cursor1.png -------------------------------------------------------------------------------- /SlickRecorder/cursor2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/merwin-asm/SlickRecorder/1b695c8adafb8bf2c9ebd5c1e38837aed07d33e9/SlickRecorder/cursor2.png -------------------------------------------------------------------------------- /SlickRecorder/cursor3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/merwin-asm/SlickRecorder/1b695c8adafb8bf2c9ebd5c1e38837aed07d33e9/SlickRecorder/cursor3.png -------------------------------------------------------------------------------- /SlickRecorder/cursor4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/merwin-asm/SlickRecorder/1b695c8adafb8bf2c9ebd5c1e38837aed07d33e9/SlickRecorder/cursor4.png -------------------------------------------------------------------------------- /SlickRecorder/cursor5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/merwin-asm/SlickRecorder/1b695c8adafb8bf2c9ebd5c1e38837aed07d33e9/SlickRecorder/cursor5.png -------------------------------------------------------------------------------- /SlickRecorder/cursor6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/merwin-asm/SlickRecorder/1b695c8adafb8bf2c9ebd5c1e38837aed07d33e9/SlickRecorder/cursor6.png -------------------------------------------------------------------------------- /SlickRecorder/cursor7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/merwin-asm/SlickRecorder/1b695c8adafb8bf2c9ebd5c1e38837aed07d33e9/SlickRecorder/cursor7.png -------------------------------------------------------------------------------- /SlickRecorder/gui.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import json 3 | import os 4 | import time 5 | import threading 6 | from screeninfo import get_monitors 7 | from PyQt5.QtWidgets import (QApplication, QWidget, QVBoxLayout, QHBoxLayout, QCheckBox, QLineEdit, QPushButton, QFileDialog, QLabel, QGridLayout, QScrollArea, QDialog, QComboBox) 8 | from PyQt5.QtGui import QPalette, QColor, QPixmap, QIcon 9 | from PyQt5.QtCore import Qt 10 | 11 | class DefaultCursorDialog(QDialog): 12 | def __init__(self, parent=None): 13 | super().__init__(parent) 14 | self.setWindowTitle('Select Default Cursor') 15 | self.setStyleSheet('background-color: white;') 16 | self.initUI() 17 | 18 | def current_path(self): 19 | x = os.path.abspath(__file__).split('/') 20 | x.pop() 21 | return "/".join(x) 22 | 23 | def initUI(self): 24 | gridLayout = QGridLayout() 25 | images = [f'{self.current_path()}/cursor{i}.png' for i in range(1, 8)] # cursor1.png to cursor7.png 26 | 27 | for index, img_file in enumerate(images): 28 | button = QPushButton() 29 | pixmap = QPixmap(img_file) 30 | icon = QIcon(pixmap) # Create a QIcon from QPixmap 31 | button.setIcon(icon) 32 | button.setIconSize(pixmap.size()) 33 | button.setFixedSize(pixmap.size()) 34 | button.clicked.connect(lambda checked, f=img_file: self.selectCursor(f)) 35 | 36 | # Set button background to white and text color to black 37 | button.setStyleSheet('background-color: white; color: black; border: 1px solid #ddd;') 38 | 39 | gridLayout.addWidget(button, index // 3, index % 3) 40 | 41 | scrollArea = QScrollArea() 42 | scrollArea.setWidgetResizable(True) 43 | container = QWidget() 44 | container.setLayout(gridLayout) 45 | scrollArea.setWidget(container) 46 | 47 | dialogLayout = QVBoxLayout() 48 | dialogLayout.addWidget(QLabel('Select Default Cursor'), 0, Qt.AlignTop) 49 | dialogLayout.addWidget(scrollArea) 50 | self.setLayout(dialogLayout) 51 | self.resize(500, 400) 52 | 53 | def selectCursor(self, file_name): 54 | print(f'Selected default cursor: {file_name}') 55 | self.cursor = file_name 56 | self.accept() # Close the dialog after selection 57 | 58 | class ScreenRecorderUI(QWidget): 59 | def __init__(self): 60 | super().__init__() 61 | self.cursor = "" 62 | self.rec = False 63 | self.initUI() 64 | 65 | def initUI(self): 66 | # Set up dark theme 67 | self.setWindowTitle('Slick Recorder') 68 | palette = QPalette() 69 | palette.setColor(QPalette.Background, QColor(30, 30, 30)) 70 | palette.setColor(QPalette.WindowText, QColor(240, 240, 240)) 71 | palette.setColor(QPalette.Button, QColor(50, 50, 50)) 72 | palette.setColor(QPalette.ButtonText, QColor(240, 240, 240)) 73 | palette.setColor(QPalette.Text, QColor(240, 240, 240)) 74 | self.setPalette(palette) 75 | 76 | # Layouts 77 | mainLayout = QVBoxLayout() 78 | imageLayout = QVBoxLayout() 79 | fpsLayout = QHBoxLayout() 80 | cursorLayout = QVBoxLayout() 81 | savingLocationLayout = QHBoxLayout() 82 | buttonLayout = QHBoxLayout() 83 | areaSelectionLayout = QHBoxLayout() 84 | 85 | # Add Image at the Top 86 | self.imageLabel = QLabel() 87 | self.imagePixmap = QPixmap(f'{self.current_path()}/title.png') # Change this to your image path 88 | self.imageLabel.setPixmap(self.imagePixmap) 89 | self.imageLabel.setScaledContents(True) 90 | self.imageLabel.setFixedHeight(100) # Fixed height for image 91 | imageLayout.addWidget(self.imageLabel) 92 | 93 | # UI Elements 94 | self.recordMicCheckbox = QCheckBox('Record from Mic') 95 | self.recordPCCheckbox = QCheckBox('Record from PC') 96 | self.showKeyboardCheckbox = QCheckBox('Show Keyboard Actions') 97 | """ 98 | self.dynamicFPSCheckbox = QCheckBox('Dynamic FPS') 99 | self.customFPSInput = QLineEdit() 100 | self.customFPSInput = QLineEdit() 101 | self.customFPSInput.setPlaceholderText('Enter Custom FPS') 102 | self.customFPSInput.setEnabled(False) 103 | """ 104 | self.codecComboBox = QComboBox() 105 | self.codecComboBox.addItems(['MP4V', 'MJPEG', 'XVID', 'DIVX']) 106 | 107 | self.voiceCaptionCheckbox = QCheckBox('Voice Caption Generation') 108 | """ 109 | self.dynamicFPSCheckbox.setChecked(True) 110 | self.dynamicFPSCheckbox.toggled.connect(self.toggleFPSInput) 111 | """ 112 | self.customCursorButton = QPushButton('Select Custom Cursor') 113 | self.changeDefaultCursorButton = QPushButton('Change Default Cursor') 114 | 115 | self.savingLocationInput = QLineEdit() 116 | self.browseButton = QPushButton('Browse') 117 | 118 | # Set default saving location to /home/{username}/Videos 119 | self.savingLocationInput.setText(os.path.expanduser('~/Videos')) 120 | 121 | self.startButton = QPushButton('Start Recording') 122 | self.stopButton = QPushButton('Stop (ESC)') 123 | 124 | self.monitorComboBox = QComboBox() 125 | 126 | # Get all monitors 127 | monitors = get_monitors() 128 | 129 | # Create a list of monitor IDs 130 | monitor_ids = [str(idx) for idx in range(len(monitors))] 131 | 132 | self.monitorComboBox.addItems(monitor_ids) # Update as needed 133 | 134 | self.fullscreenCheckbox = QCheckBox('Full Screen') 135 | self.fullscreenCheckbox.setChecked(True) # Enable fullscreen by default 136 | 137 | self.xStartInput = QLineEdit() 138 | self.xStartInput.setPlaceholderText('X Start') 139 | self.xEndInput = QLineEdit() 140 | self.xEndInput.setPlaceholderText('X End') 141 | self.yStartInput = QLineEdit() 142 | self.yStartInput.setPlaceholderText('Y Start') 143 | self.yEndInput = QLineEdit() 144 | self.yEndInput.setPlaceholderText('Y End') 145 | 146 | self.xStartInput.setEnabled(False) 147 | self.xEndInput.setEnabled(False) 148 | self.yStartInput.setEnabled(False) 149 | self.yEndInput.setEnabled(False) 150 | 151 | # Add elements to layout 152 | """ 153 | fpsLayout.addWidget(QLabel('FPS:'), 0, Qt.AlignLeft) 154 | fpsLayout.addWidget(self.dynamicFPSCheckbox) 155 | fpsLayout.addWidget(self.customFPSInput) 156 | """ 157 | fpsLayout.addWidget(QLabel('Codec:'), 0, Qt.AlignLeft) 158 | fpsLayout.addWidget(self.codecComboBox) 159 | 160 | cursorLayout.addWidget(QLabel('Default Cursor Settings'), 0, Qt.AlignTop) 161 | cursorLayout.addWidget(self.customCursorButton) 162 | cursorLayout.addWidget(self.changeDefaultCursorButton) 163 | 164 | savingLocationLayout.addWidget(self.savingLocationInput) 165 | savingLocationLayout.addWidget(self.browseButton) 166 | 167 | buttonLayout.addWidget(self.startButton) 168 | buttonLayout.addWidget(self.stopButton) 169 | 170 | areaSelectionLayout.addWidget(self.fullscreenCheckbox) 171 | areaSelectionLayout.addWidget(QLabel('X Start:')) 172 | areaSelectionLayout.addWidget(self.xStartInput) 173 | areaSelectionLayout.addWidget(QLabel('X End:')) 174 | areaSelectionLayout.addWidget(self.xEndInput) 175 | areaSelectionLayout.addWidget(QLabel('Y Start:')) 176 | areaSelectionLayout.addWidget(self.yStartInput) 177 | areaSelectionLayout.addWidget(QLabel('Y End:')) 178 | areaSelectionLayout.addWidget(self.yEndInput) 179 | 180 | mainLayout.addLayout(imageLayout) # Add image layout at the top 181 | mainLayout.addWidget(self.recordMicCheckbox) 182 | mainLayout.addWidget(self.recordPCCheckbox) 183 | mainLayout.addLayout(fpsLayout) 184 | mainLayout.addWidget(self.voiceCaptionCheckbox) 185 | mainLayout.addWidget(self.showKeyboardCheckbox) 186 | mainLayout.addLayout(cursorLayout) 187 | mainLayout.addLayout(savingLocationLayout) 188 | mainLayout.addWidget(QLabel('Monitor:')) 189 | mainLayout.addWidget(self.monitorComboBox) 190 | mainLayout.addLayout(areaSelectionLayout) # Add area layout with coordinates 191 | mainLayout.addLayout(buttonLayout) 192 | 193 | # Connect buttons to functions 194 | self.customCursorButton.clicked.connect(self.selectCustomCursor) 195 | self.changeDefaultCursorButton.clicked.connect(self.changeDefaultCursor) 196 | self.browseButton.clicked.connect(self.browseSavingLocation) 197 | self.startButton.clicked.connect(self.startRecording) 198 | self.stopButton.clicked.connect(self.stopRecording) 199 | self.fullscreenCheckbox.toggled.connect(self.toggleAreaInputs) 200 | 201 | self.setLayout(mainLayout) 202 | self.setFixedSize(800, 600) 203 | 204 | # Apply dark theme style 205 | self.setStyleSheet(""" 206 | QWidget { 207 | background-color: #2c2c2c; 208 | color: #f0f0f0; 209 | } 210 | QLabel { 211 | color: #f0f0f0; 212 | } 213 | QPushButton { 214 | border: 1px solid #555; 215 | border-radius: 5px; 216 | padding: 10px; 217 | color: #f0f0f0; 218 | } 219 | QPushButton#startButton { 220 | background-color: #4caf50; /* Green for start */ 221 | } 222 | QPushButton#stopButton { 223 | background-color: #f44336; /* Red for stop */ 224 | color: white; 225 | } 226 | QLineEdit { 227 | border: 1px solid #555; 228 | border-radius: 5px; 229 | padding: 5px; 230 | background-color: #333; 231 | color: #f0f0f0; 232 | } 233 | QCheckBox { 234 | color: #f0f0f0; 235 | } 236 | QComboBox { 237 | border: 1px solid #555; 238 | border-radius: 5px; 239 | padding: 5px; 240 | background-color: #333; 241 | color: #f0f0f0; 242 | } 243 | """) 244 | 245 | self.startButton.setStyleSheet("background-color: #4caf50; color: white;") 246 | #self.stopButton.setStyleSheet("background-color: red; color: white;") 247 | 248 | def toggleFPSInput(self, checked): 249 | self.customFPSInput.setEnabled(not checked) 250 | 251 | def selectCustomCursor(self): 252 | options = QFileDialog.Options() 253 | file_name, _ = QFileDialog.getOpenFileName(self, 'Select Custom Cursor', '', 'Images (*.png *.xpm *.jpg)', options=options) 254 | if file_name: 255 | self.cursor = file_name 256 | print(f'Selected custom cursor: {file_name}') 257 | 258 | def changeDefaultCursor(self): 259 | dialog = DefaultCursorDialog(self) 260 | if dialog.exec_() == QDialog.Accepted: 261 | self.cursor = dialog.cursor 262 | print(f'Selected default cursor: {self.cursor}') 263 | 264 | def browseSavingLocation(self): 265 | options = QFileDialog.Options() 266 | folder = QFileDialog.getExistingDirectory(self, 'Select Saving Location', options=options) 267 | if folder: 268 | self.savingLocationInput.setText(folder) 269 | 270 | def startRecording(self): 271 | # Collect data from UI elements 272 | if self.rec: 273 | return 0 274 | else: 275 | self.rec = True 276 | 277 | cursor = self.cursor 278 | data = { 279 | 'record_mic': self.recordMicCheckbox.isChecked(), 280 | 'record_pc': self.recordPCCheckbox.isChecked(), 281 | 'show_keyboard': self.showKeyboardCheckbox.isChecked(), 282 | 'codec': self.codecComboBox.currentText(), 283 | 'voice_caption': self.voiceCaptionCheckbox.isChecked(), 284 | 'cursor':cursor, 285 | 'saving_location': self.savingLocationInput.text(), 286 | 'monitor': self.monitorComboBox.currentText(), 287 | 'fullscreen': self.fullscreenCheckbox.isChecked(), 288 | 'x_start': self.xStartInput.text() if not self.fullscreenCheckbox.isChecked() else None, 289 | 'x_end': self.xEndInput.text() if not self.fullscreenCheckbox.isChecked() else None, 290 | 'y_start': self.yStartInput.text() if not self.fullscreenCheckbox.isChecked() else None, 291 | 'y_end': self.yEndInput.text() if not self.fullscreenCheckbox.isChecked() else None 292 | } 293 | 294 | # Print the collected data 295 | print("Recording Settings:") 296 | for key, value in data.items(): 297 | print(f"{key}: {value}") 298 | 299 | if (not data['record_pc']) and (not data['record_mic']): 300 | os.system("touch .started_recording") 301 | ttt = True 302 | else: 303 | ttt = False 304 | 305 | data = json.dumps(data) 306 | 307 | f = open(".settings.json", "w") 308 | f.write(data) 309 | f.close() 310 | 311 | x = lambda : os.system(f"python3 {self.current_path()}/SlickRecorder.py") 312 | y = lambda : os.system(f"python3 {self.current_path()}/start_icon.py") 313 | threading.Thread(target=x).start() 314 | 315 | while not os.path.isfile(".started_recording"): 316 | time.sleep(0.3) 317 | 318 | if ttt: 319 | time.sleep(4) 320 | 321 | threading.Thread(target=y).start() 322 | self.startButton.setStyleSheet("background-color: red; color: white;") 323 | 324 | def current_path(self): 325 | x = os.path.abspath(__file__).split('/') 326 | x.pop() 327 | return "/".join(x) 328 | 329 | def stopRecording(self): 330 | import pyautogui 331 | pyautogui.press('esc') 332 | self.rec = False 333 | self.startButton.setStyleSheet("background-color: #4caf50; color: white;") 334 | os.system("touch .stop_icon") 335 | os.system("rm .started_recording") 336 | 337 | def toggleAreaInputs(self, checked): 338 | self.xStartInput.setEnabled(not checked) 339 | self.xEndInput.setEnabled(not checked) 340 | self.yStartInput.setEnabled(not checked) 341 | self.yEndInput.setEnabled(not checked) 342 | 343 | if __name__ == '__main__': 344 | app = QApplication(sys.argv) 345 | window = ScreenRecorderUI() 346 | window.show() 347 | sys.exit(app.exec_()) 348 | 349 | -------------------------------------------------------------------------------- /SlickRecorder/icoon.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | import pystray 3 | from pystray import MenuItem as item 4 | import sys 5 | import os 6 | 7 | icon = None 8 | 9 | def current_path(): 10 | x = os.path.abspath(__file__).split('/') 11 | x.pop() 12 | return "/".join(x) 13 | 14 | def get_image(): 15 | image = Image.open(f'{current_path()}/red_dot.png') 16 | return image 17 | 18 | def setup(icon): 19 | icon.visible = True 20 | 21 | def start(): 22 | global icon 23 | image = get_image() 24 | icon = pystray.Icon("SlickRecorder", image, "Recording...") 25 | icon.run(setup) 26 | 27 | -------------------------------------------------------------------------------- /SlickRecorder/keyboard_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/merwin-asm/SlickRecorder/1b695c8adafb8bf2c9ebd5c1e38837aed07d33e9/SlickRecorder/keyboard_text.png -------------------------------------------------------------------------------- /SlickRecorder/m_audio_rec.py: -------------------------------------------------------------------------------- 1 | import pyaudio 2 | import wave 3 | import threading 4 | from pynput import keyboard 5 | from pydub import AudioSegment 6 | import numpy as np 7 | import time 8 | import os 9 | 10 | 11 | # Constants 12 | FORMAT = pyaudio.paInt16 13 | CHANNELS = 2 14 | RATE = 44100 15 | CHUNK = 1024 16 | OUTPUT_FILENAME = "mic.wav" 17 | recording = True 18 | 19 | def record_audio(): 20 | global recording 21 | audio = pyaudio.PyAudio() 22 | stream = audio.open(format=FORMAT, channels=CHANNELS, 23 | rate=RATE, input=True, 24 | frames_per_buffer=CHUNK) 25 | 26 | print("Recording... Press ESC to stop.") 27 | 28 | frames = [] 29 | 30 | os.system("touch .started_recording") 31 | 32 | while recording: 33 | data = stream.read(CHUNK) 34 | frames.append(data) 35 | 36 | print("Finished recording.") 37 | 38 | stream.stop_stream() 39 | stream.close() 40 | audio.terminate() 41 | 42 | # Save the recording as a WAV file 43 | with wave.open(OUTPUT_FILENAME, 'wb') as wf: 44 | wf.setnchannels(CHANNELS) 45 | wf.setsampwidth(audio.get_sample_size(FORMAT)) 46 | wf.setframerate(RATE) 47 | wf.writeframes(b''.join(frames)) 48 | quit() 49 | 50 | def on_press(key): 51 | global recording 52 | if key == keyboard.Key.esc: 53 | recording = False 54 | 55 | def listen_for_escape(): 56 | global recording 57 | with keyboard.Listener(on_press=on_press) as listener: 58 | while recording: 59 | time.sleep(0.3) 60 | 61 | if True: 62 | # Start the recording in a separate thread 63 | recording_thread = threading.Thread(target=record_audio) 64 | recording_thread.start() 65 | 66 | # Start listening for ESC key in the main thread 67 | listen_for_escape() 68 | 69 | -------------------------------------------------------------------------------- /SlickRecorder/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | def current_path(): 3 | x = os.path.abspath(__file__).split('/') 4 | x.pop() 5 | return "/".join(x) 6 | 7 | def main(): 8 | os.system(f"python3 {current_path()}/gui.py") 9 | if __name__ == "__main__": 10 | main() 11 | 12 | -------------------------------------------------------------------------------- /SlickRecorder/recorder.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import numpy as np 3 | import pyautogui 4 | from PIL import Image 5 | import time 6 | import threading 7 | from pynput import keyboard 8 | from PIL import Image, ImageDraw, ImageFont 9 | import sys 10 | import os 11 | 12 | 13 | a = sys.argv 14 | 15 | if a[1] != "None": 16 | x1, y1 = int(a[1]) , int(a[2]) 17 | x2, y2 = int(a[3]) , int(a[4]) 18 | 19 | def current_path(): 20 | x = os.path.abspath(__file__).split('/') 21 | x.pop() 22 | return "/".join(x) 23 | 24 | 25 | # Parameters 26 | keyboard_ = a[6] 27 | monitor_index = int(a[5]) 28 | output_file = 'screen_recording.mp4' 29 | try: 30 | cursor_image_path = a[8] 31 | except: 32 | cursor_image_path = f'{current_path()}/cursor7.png' 33 | cursor_size = (32, 32) # Standard cursor size 34 | codec = cv2.VideoWriter_fourcc(*a[7]) 35 | screen_size = pyautogui.size() 36 | 37 | # monitor 38 | from screeninfo import get_monitors 39 | monitors = get_monitors() 40 | monitor = monitors[monitor_index] 41 | x1, y1 = monitor.x, monitor.y 42 | x2, y2 = monitor.width + x1, monitor.height + y1 43 | 44 | 45 | # Function to draw the custom cursor on the frame 46 | def draw_cursor(frame, cursor_img_path, cursor_pos): 47 | cursor_img = Image.open(cursor_img_path).convert("RGBA") 48 | cursor_img = cursor_img.resize(cursor_size, Image.ANTIALIAS) # Resize cursor image 49 | cursor_img_np = np.array(cursor_img) 50 | cursor_h, cursor_w, _ = cursor_img_np.shape 51 | y, x = cursor_pos 52 | 53 | # Ensure the cursor is within bounds 54 | y = min(max(0, y), frame.shape[0] - cursor_h) 55 | x = min(max(0, x), frame.shape[1] - cursor_w) 56 | 57 | # Overlay the cursor image 58 | for i in range(cursor_h): 59 | for j in range(cursor_w): 60 | if cursor_img_np[i, j, 3] > 0: # Check alpha channel 61 | frame[y + i, x + j] = cursor_img_np[i, j, :3] 62 | 63 | 64 | recording = True 65 | pressed_keys = set() 66 | key_text = "" 67 | 68 | def on_press(key): 69 | global recording, pressed_keys, key_text 70 | 71 | try: 72 | key_name = key.char 73 | except AttributeError: 74 | key_name = str(key).replace('Key.', '') 75 | 76 | pressed_keys.add(key_name) 77 | 78 | # Record the key combination 79 | if pressed_keys: 80 | combination = '+'.join(sorted(pressed_keys)) 81 | key_text = combination 82 | 83 | if key == keyboard.Key.esc: 84 | recording = False 85 | 86 | def on_release(key): 87 | global pressed_keys 88 | 89 | try: 90 | key_name = key.char 91 | except AttributeError: 92 | key_name = str(key).replace('Key.', '') 93 | 94 | if key_name in pressed_keys: 95 | pressed_keys.remove(key_name) 96 | 97 | # Stop listener with ESC key 98 | if key == keyboard.Key.esc: 99 | return False 100 | 101 | 102 | def listen_for_escape(): 103 | with keyboard.Listener(on_press=on_press, on_release=on_release) as listener: 104 | listener.join() 105 | 106 | 107 | esc_thread = threading.Thread(target=listen_for_escape) 108 | esc_thread.start() 109 | 110 | print("Screen Recording started. Press 'ESC' to stop recording.") 111 | start_time = time.time() 112 | frames = [] 113 | frame_times = [] 114 | key_text_frames = 4 115 | key_text_counter = 0 116 | current_key_text = "" 117 | 118 | def add_key_text(frame, text): 119 | global keyboard_ 120 | 121 | if keyboard_ == "0": 122 | return frame 123 | 124 | x = frame.shape[1] - int(frame.shape[1]/2) - 150 125 | y = frame.shape[0] - int(frame.shape[0]/2) + 100 126 | 127 | font_scale = 1 128 | font_thickness = 2 129 | font_color = (0 , 0 , 0) 130 | # Load the overlay image 131 | overlay_img = cv2.imread(f"{current_path()}/keyboard_text.png", cv2.IMREAD_UNCHANGED) # Read image with alpha channel 132 | 133 | if overlay_img is None: 134 | raise FileNotFoundError(f"Image file 'keyboard_text.png' not found.") 135 | 136 | # Resize the overlay image to fit the frame if necessary 137 | h, w = overlay_img.shape[:2] 138 | if x + w > frame.shape[1] or y + h > frame.shape[0]: 139 | raise ValueError("The overlay image is out of bounds of the frame.") 140 | 141 | # Create a region of interest (ROI) in the frame 142 | roi = frame[y:y+h, x:x+w] 143 | 144 | # Split the overlay image into its color and alpha channels 145 | overlay_color = overlay_img[:, :, :3] 146 | overlay_alpha = overlay_img[:, :, 3] / 255.0 147 | 148 | # Blend the overlay image with the ROI 149 | for c in range(0, 3): 150 | roi[:, :, c] = (overlay_alpha * overlay_color[:, :, c] + (1 - overlay_alpha) * roi[:, :, c]) 151 | 152 | # Add text to the overlay image 153 | text_position = (overlay_img.shape[1] - int(overlay_img.shape[1]/2)-50, overlay_img.shape[0]- 90) 154 | #cv2.putText(roi, text, text_position, fontHeight=font_scale, color=font_color, thickness=font_thickness) 155 | cv2.putText(roi, text, text_position, cv2.FONT_HERSHEY_DUPLEX, font_scale, font_color,font_thickness, cv2.LINE_AA) 156 | 157 | # Place the overlay image back onto the frame 158 | frame[y:y+h, x:x+w] = roi 159 | 160 | return frame 161 | 162 | import os 163 | while not os.path.isfile(".started_recording"): 164 | time.sleep(0.3) 165 | while recording: 166 | # Record the start time for this frame 167 | frame_start_time = time.time() 168 | 169 | # Capture screen 170 | #screen = pyautogui.screenshot() 171 | screen = pyautogui.screenshot(region=(x1, y1, x2 - x1, y2 - y1)) 172 | 173 | screen_np = np.array(screen) 174 | frame = cv2.cvtColor(screen_np, cv2.COLOR_RGB2BGR) 175 | if a[1] != "None": 176 | frame = frame[y1:y2, x1:x2] 177 | 178 | # Get cursor position 179 | cursor_x, cursor_y = pyautogui.position() 180 | if a[1] == "None": 181 | # draw_cursor(frame, cursor_image_path, (cursor_y - cursor_size[1] // 2, cursor_x - cursor_size[0] // 2)) 182 | draw_cursor(frame, cursor_image_path, (cursor_y - cursor_size[1] // 2 - x1, cursor_x - cursor_size[0] // 2 - y1)) 183 | 184 | else: 185 | draw_cursor(frame, cursor_image_path, (cursor_y - cursor_size[1] // 2 - x1, cursor_x - cursor_size[0] // 2 - y1)) 186 | 187 | if key_text != "": 188 | if current_key_text != key_text: 189 | current_key_text = key_text 190 | key_text_counter = 0 191 | 192 | if key_text_counter == key_text_frames: 193 | key_text_counter = 0 194 | key_text = "" 195 | current_key_text = "" 196 | else: 197 | key_text_counter += 1 198 | print(key_text) 199 | frame = add_key_text(frame, key_text.title()) 200 | 201 | # Store the frame and the timestamp 202 | frames.append(frame) 203 | frame_times.append(frame_start_time - start_time) 204 | 205 | 206 | 207 | print("Recording stopped.") 208 | 209 | # Calculate the average frame rate based on actual times 210 | duration = frame_times[-1] if frame_times else 0 211 | average_frame_rate = len(frames) / duration if duration > 0 else 0 212 | print(f"Average frame rate: {average_frame_rate:.2f} fps") 213 | 214 | # Write video with variable frame rate 215 | if frames: 216 | first_frame = frames[0] 217 | height, width, _ = first_frame.shape 218 | video_writer = cv2.VideoWriter(output_file, codec, average_frame_rate, (width, height)) 219 | 220 | for frame in frames: 221 | video_writer.write(frame) 222 | 223 | video_writer.release() 224 | cv2.destroyAllWindows() 225 | 226 | -------------------------------------------------------------------------------- /SlickRecorder/red_dot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/merwin-asm/SlickRecorder/1b695c8adafb8bf2c9ebd5c1e38837aed07d33e9/SlickRecorder/red_dot.png -------------------------------------------------------------------------------- /SlickRecorder/refine.py: -------------------------------------------------------------------------------- 1 | from scipy.io import wavfile 2 | import noisereduce as nr 3 | import numpy as np 4 | import os 5 | 6 | def refine_audio(filename, times=2): 7 | n = filename.replace(".wav", "") 8 | 9 | for e in range(times): 10 | # load data 11 | if e == 0: 12 | rate, data = wavfile.read(filename) 13 | else: 14 | rate, data = wavfile.read(n+str(e-1)+".wav") 15 | 16 | orig_shape = data.shape 17 | data = np.reshape(data, (2, -1)) 18 | 19 | # perform noise reduction 20 | # optimized for speech 21 | reduced_noise = nr.reduce_noise( 22 | y=data, 23 | sr=rate, 24 | stationary=True 25 | ) 26 | 27 | wavfile.write(n+str(e)+".wav", rate, reduced_noise.reshape(orig_shape)) 28 | if e != 0: 29 | os.system(f"rm {n+str(e-1)}.wav") 30 | 31 | os.system(f"cp {n+str(times-1)}.wav {filename}") 32 | os.system(f"rm {n+str(times-1)}.wav") 33 | -------------------------------------------------------------------------------- /SlickRecorder/start_icon.py: -------------------------------------------------------------------------------- 1 | import icoon 2 | import threading 3 | import time 4 | import os 5 | 6 | os.system("rm .stop_icon") 7 | threading.Thread(target=lambda: icoon.start()).start() 8 | while True: 9 | time.sleep(0.3) 10 | if os.path.isfile(".stop_icon"): 11 | break 12 | 13 | icoon.icon.stop() 14 | os.system("rm .stop_icon") 15 | -------------------------------------------------------------------------------- /SlickRecorder/title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/merwin-asm/SlickRecorder/1b695c8adafb8bf2c9ebd5c1e38837aed07d33e9/SlickRecorder/title.png --------------------------------------------------------------------------------