diff options
| author | grothedev <grothedev@gmail.com> | 2025-07-20 11:54:44 -0400 |
|---|---|---|
| committer | grothedev <grothedev@gmail.com> | 2025-07-20 11:54:44 -0400 |
| commit | b0d1f580c8883a8402a2380e896c120189250658 (patch) | |
| tree | 6d4d5fe4ab2e929486bb91b89c85ba4c91371091 | |
| parent | 82b5ea0eb32a68394ac33a0f8767acfd6ccb77b2 (diff) | |
changes from laptop
| -rw-r--r-- | ffmpeg-repl.py | 129 | ||||
| -rw-r--r-- | ffmpeg-suite.sh | 36 | ||||
| -rw-r--r-- | videdit.py | 286 | ||||
| -rw-r--r-- | wow3.py | 98 |
4 files changed, 549 insertions, 0 deletions
diff --git a/ffmpeg-repl.py b/ffmpeg-repl.py new file mode 100644 index 0000000..f63b4ce --- /dev/null +++ b/ffmpeg-repl.py @@ -0,0 +1,129 @@ +#!/bin/python3 + +import readline # Add this at the top +# ... rest of the code + +import sys + +class SimpleREPL: + def __init__(self): + self.prompt = ">> " + self.commands = { + "help": self._show_help, + "exit": self._exit_repl, + "quit": self._exit_repl, # Alias for exit + "echo": self._echo, + "stack": self._stack, #vertical or horizontal + +ffmpeg -i video1.mp4 -i video2.mp4 -filter_complex hstack output.mp4 + +#audio from first vid +ffmpeg -i video1.mp4 -i video2.mp4 -filter_complex "[0:v][1:v]hstack[v]" -map "[v]" -map 0:a -c:a copy output.mp4 + +#resize videos to same height +ffmpeg -i video1.mp4 -i video2.mp4 -filter_complex "[0:v]scale=-1:720[v0];[1:v]scale=-1:720[v1];[v0][v1]hstack[v]" -map "[v]" -map 0:a -c:a copy output.mp4 + +#padding between vids +ffmpeg -i video1.mp4 -i video2.mp4 -filter_complex "[0:v]pad=iw+10:ih[v0];[v0][1:v]hstack[v]" -map "[v]" -map 0:a output.mp4 + +#shorter duration vid +ffmpeg -i video1.mp4 -i video2.mp4 -filter_complex "[0:v][1:v]hstack=shortest=1[v]" -map "[v]" -map 0:a output.mp4 + +#mix audio from both +ffmpeg -i video1.mp4 -i video2.mp4 -filter_complex "[0:v][1:v]hstack=shortest=1[v]" -map "[v]" -map 0:a output.mp4 + +#force same dimensions +ffmpeg -i video1.mp4 -i video2.mp4 -filter_complex "[0:v][1:v]hstack[v];[0:a][1:a]amix[a]" -map "[v]" -map "[a]" output.mp4 + +#vertical stack +ffmpeg -i video1.mp4 -i video2.mp4 -filter_complex "[0:v]scale=640:480[v0];[1:v]scale=640:480[v1];[v0][v1]hstack[v]" -map "[v]" -map 0:a output.mp4 + +#3 or more vids sidebyside +ffmpeg -i video1.mp4 -i video2.mp4 -filter_complex vstack output.mp4 + +# Three videos +ffmpeg -i video1.mp4 -i video2.mp4 -i video3.mp4 -filter_complex "[0:v][1:v][2:v]hstack=inputs=3[v]" -map "[v]" -map 0:a output.mp4 + +# Four videos (2x2 grid) +ffmpeg -i video1.mp4 -i video2.mp4 -i video3.mp4 -i video4.mp4 -filter_complex "[0:v][1:v]hstack[top];[2:v][3:v]hstack[bottom];[top][bottom]vstack[v]" -map "[v]" -map 0:a output.mp4 + + def _show_help(self, *args): + """Displays available commands.""" + print("Available commands:") + for cmd, func in self.commands.items(): + docstring = func.__doc__ or "No description available." + print(f" {cmd:<15} - {docstring.strip().splitlines()[0]}") # Show first line of docstring + return None # No printable result for help + + def _exit_repl(self, *args): + """Exits the REPL.""" + print("Exiting REPL. Goodbye!") + sys.exit(0) + + def _echo(self, *args): + """Echoes the input arguments back to the user. + Usage: echo <arg1> <arg2> ... + """ + if not args: + return "Echo: Nothing to echo!" + return "Echo: " + " ".join(args) + + # --- Placeholder for your custom commands --- + # def _handle_my_command(self, arg1, arg2=None, *other_args): + # """ + # Description of my_command. + # Usage: my_command <required_arg> [optional_arg] ... + # """ + # # Your command logic here + # # Example: + # # self.app_state['something'] = arg1 + # # return f"Processed {arg1} and {arg2}" + # pass + + + def evaluate(self, line): + """Evaluates a single line of input.""" + parts = line.strip().split() + if not parts: + return None # Empty line + + command_name = parts[0].lower() # Case-insensitive command matching + args = parts[1:] + + if command_name in self.commands: + command_func = self.commands[command_name] + try: + return command_func(*args) + except TypeError as e: + # Catch errors related to incorrect number of arguments + return f"Error: Invalid arguments for '{command_name}'. Usage: {command_func.__doc__ or ''}" + except Exception as e: + return f"Error executing '{command_name}': {e}" + else: #todo check for partial str match + + return f"Unknown command: '{command_name}'. Type 'help' for available commands." + + def run(self): + """Runs the REPL main loop.""" + print("Welcome to SimpleREPL! Type 'help' for commands or 'exit' to quit.") + while True: + try: + line = input(self.prompt) + if line.strip(): # Process only if line is not empty + result = self.evaluate(line) + if result is not None: # Print only if there's a result + print(result) + except EOFError: # Ctrl+D + print("\nExiting REPL (EOF).") + break + except KeyboardInterrupt: # Ctrl+C + print("\nInterrupted. Type 'exit' or 'quit' to exit.") + # Optionally, you might want to clear any partial input here + # or reset some state if an operation was interrupted. + except Exception as e: + print(f"An unexpected error occurred: {e}") + # Depending on severity, you might want to break or continue + +if __name__ == "__main__": + repl_app = SimpleREPL() + repl_app.run() diff --git a/ffmpeg-suite.sh b/ffmpeg-suite.sh new file mode 100644 index 0000000..c3623c2 --- /dev/null +++ b/ffmpeg-suite.sh @@ -0,0 +1,36 @@ +#!/bin/bash + + +#videos side by side +ffmpeg -i video1.mp4 -i video2.mp4 -filter_complex hstack output.mp4 + +#audio from first vid +ffmpeg -i video1.mp4 -i video2.mp4 -filter_complex "[0:v][1:v]hstack[v]" -map "[v]" -map 0:a -c:a copy output.mp4 + +#resize videos to same height +ffmpeg -i video1.mp4 -i video2.mp4 -filter_complex "[0:v]scale=-1:720[v0];[1:v]scale=-1:720[v1];[v0][v1]hstack[v]" -map "[v]" -map 0:a -c:a copy output.mp4 + +#padding between vids +ffmpeg -i video1.mp4 -i video2.mp4 -filter_complex "[0:v]pad=iw+10:ih[v0];[v0][1:v]hstack[v]" -map "[v]" -map 0:a output.mp4 + +#shorter duration vid +ffmpeg -i video1.mp4 -i video2.mp4 -filter_complex "[0:v][1:v]hstack=shortest=1[v]" -map "[v]" -map 0:a output.mp4 + +#mix audio from both +ffmpeg -i video1.mp4 -i video2.mp4 -filter_complex "[0:v][1:v]hstack=shortest=1[v]" -map "[v]" -map 0:a output.mp4 + +#force same dimensions +ffmpeg -i video1.mp4 -i video2.mp4 -filter_complex "[0:v][1:v]hstack[v];[0:a][1:a]amix[a]" -map "[v]" -map "[a]" output.mp4 + +#vertical stack +ffmpeg -i video1.mp4 -i video2.mp4 -filter_complex "[0:v]scale=640:480[v0];[1:v]scale=640:480[v1];[v0][v1]hstack[v]" -map "[v]" -map 0:a output.mp4 + +#3 or more vids sidebyside +ffmpeg -i video1.mp4 -i video2.mp4 -filter_complex vstack output.mp4 + +# Three videos +ffmpeg -i video1.mp4 -i video2.mp4 -i video3.mp4 -filter_complex "[0:v][1:v][2:v]hstack=inputs=3[v]" -map "[v]" -map 0:a output.mp4 + +# Four videos (2x2 grid) +ffmpeg -i video1.mp4 -i video2.mp4 -i video3.mp4 -i video4.mp4 -filter_complex "[0:v][1:v]hstack[top];[2:v][3:v]hstack[bottom];[top][bottom]vstack[v]" -map "[v]" -map 0:a output.mp4 + diff --git a/videdit.py b/videdit.py new file mode 100644 index 0000000..698ce86 --- /dev/null +++ b/videdit.py @@ -0,0 +1,286 @@ +#!/usr/bin/env python3 +""" +Video Clip Extractor GUI +A simple GUI application for extracting video clips using ffmpeg +""" + +import tkinter as tk +from tkinter import filedialog, messagebox, ttk +import subprocess +import os +import threading +from pathlib import Path +import re + +class VideoClipperGUI: + def __init__(self, root): + self.root = root + self.root.title("Video Clip Extractor") + self.root.geometry("600x400") + + # Variables + self.input_file = tk.StringVar() + self.start_time = tk.StringVar(value="00:00:00") + self.end_time = tk.StringVar(value="00:00:10") + self.output_dir = tk.StringVar(value=os.getcwd()) + + self.setup_ui() + + def setup_ui(self): + # Main frame + main_frame = ttk.Frame(self.root, padding="10") + main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) + + # Configure grid weights + self.root.columnconfigure(0, weight=1) + self.root.rowconfigure(0, weight=1) + main_frame.columnconfigure(1, weight=1) + + # Input file selection + ttk.Label(main_frame, text="Input Video File:").grid(row=0, column=0, sticky=tk.W, pady=5) + + file_frame = ttk.Frame(main_frame) + file_frame.grid(row=0, column=1, columnspan=2, sticky=(tk.W, tk.E), pady=5) + file_frame.columnconfigure(0, weight=1) + + self.file_entry = ttk.Entry(file_frame, textvariable=self.input_file, state="readonly") + self.file_entry.grid(row=0, column=0, sticky=(tk.W, tk.E), padx=(0, 5)) + + ttk.Button(file_frame, text="Browse", command=self.browse_file).grid(row=0, column=1) + + # Time inputs + ttk.Label(main_frame, text="Start Time:").grid(row=1, column=0, sticky=tk.W, pady=5) + self.start_entry = ttk.Entry(main_frame, textvariable=self.start_time, width=15) + self.start_entry.grid(row=1, column=1, sticky=tk.W, pady=5) + ttk.Label(main_frame, text="(HH:MM:SS or seconds)").grid(row=1, column=2, sticky=tk.W, padx=(5, 0)) + + ttk.Label(main_frame, text="End Time:").grid(row=2, column=0, sticky=tk.W, pady=5) + self.end_entry = ttk.Entry(main_frame, textvariable=self.end_time, width=15) + self.end_entry.grid(row=2, column=1, sticky=tk.W, pady=5) + ttk.Label(main_frame, text="(HH:MM:SS or seconds)").grid(row=2, column=2, sticky=tk.W, padx=(5, 0)) + + # Output directory + ttk.Label(main_frame, text="Output Directory:").grid(row=3, column=0, sticky=tk.W, pady=5) + + output_frame = ttk.Frame(main_frame) + output_frame.grid(row=3, column=1, columnspan=2, sticky=(tk.W, tk.E), pady=5) + output_frame.columnconfigure(0, weight=1) + + self.output_entry = ttk.Entry(output_frame, textvariable=self.output_dir) + self.output_entry.grid(row=0, column=0, sticky=(tk.W, tk.E), padx=(0, 5)) + + ttk.Button(output_frame, text="Browse", command=self.browse_output_dir).grid(row=0, column=1) + + # Options frame + options_frame = ttk.LabelFrame(main_frame, text="Options", padding="5") + options_frame.grid(row=4, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=10) + options_frame.columnconfigure(1, weight=1) + + # Quality/encoding options + ttk.Label(options_frame, text="Quality:").grid(row=0, column=0, sticky=tk.W, padx=(0, 5)) + self.quality_var = tk.StringVar(value="copy") + quality_combo = ttk.Combobox(options_frame, textvariable=self.quality_var, width=15) + quality_combo['values'] = ("copy (fastest)", "high", "medium", "low") + quality_combo.grid(row=0, column=1, sticky=tk.W) + + # Extract button + self.extract_btn = ttk.Button(main_frame, text="Extract Clip", command=self.extract_clip) + self.extract_btn.grid(row=5, column=0, columnspan=3, pady=20) + + # Progress bar + self.progress = ttk.Progressbar(main_frame, mode='indeterminate') + self.progress.grid(row=6, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=5) + + # Status/output text + text_frame = ttk.LabelFrame(main_frame, text="Output", padding="5") + text_frame.grid(row=7, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5) + text_frame.columnconfigure(0, weight=1) + text_frame.rowconfigure(0, weight=1) + main_frame.rowconfigure(7, weight=1) + + self.output_text = tk.Text(text_frame, height=8, wrap=tk.WORD) + scrollbar = ttk.Scrollbar(text_frame, orient=tk.VERTICAL, command=self.output_text.yview) + self.output_text.configure(yscrollcommand=scrollbar.set) + + self.output_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) + scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S)) + + # Initial message + self.log_message("Ready to extract video clips. Select a video file to begin.") + + def browse_file(self): + """Open file dialog to select input video file""" + filetypes = [ + ("Video files", "*.mp4 *.avi *.mkv *.mov *.wmv *.flv *.webm *.m4v"), + ("All files", "*.*") + ] + + filename = filedialog.askopenfilename( + title="Select Video File", + filetypes=filetypes + ) + + if filename: + self.input_file.set(filename) + self.log_message(f"Selected input file: {os.path.basename(filename)}") + + def browse_output_dir(self): + """Open directory dialog to select output directory""" + directory = filedialog.askdirectory(title="Select Output Directory") + if directory: + self.output_dir.set(directory) + + def validate_time_format(self, time_str): + """Validate and normalize time format""" + time_str = time_str.strip() + + # If it's just numbers, treat as seconds + if re.match(r'^\d+(\.\d+)?$', time_str): + return float(time_str) + + # If it's HH:MM:SS format + if re.match(r'^\d{1,2}:\d{2}:\d{2}(\.\d+)?$', time_str): + return time_str + + # If it's MM:SS format, convert to HH:MM:SS + if re.match(r'^\d{1,2}:\d{2}(\.\d+)?$', time_str): + return f"00:{time_str}" + + raise ValueError(f"Invalid time format: {time_str}") + + def log_message(self, message): + """Add message to output text widget""" + self.output_text.insert(tk.END, f"{message}\n") + self.output_text.see(tk.END) + self.root.update_idletasks() + + def get_ffmpeg_quality_params(self): + """Get ffmpeg parameters based on quality setting""" + quality = self.quality_var.get() + + if "copy" in quality: + return ["-c", "copy"] + elif "high" in quality: + return ["-c:v", "libx264", "-crf", "18", "-c:a", "aac", "-b:a", "192k"] + elif "medium" in quality: + return ["-c:v", "libx264", "-crf", "23", "-c:a", "aac", "-b:a", "128k"] + elif "low" in quality: + return ["-c:v", "libx264", "-crf", "28", "-c:a", "aac", "-b:a", "96k"] + else: + return ["-c", "copy"] + + def extract_clip(self): + """Extract video clip using ffmpeg""" + # Validation + if not self.input_file.get(): + messagebox.showerror("Error", "Please select an input video file") + return + + if not os.path.isfile(self.input_file.get()): + messagebox.showerror("Error", "Input file does not exist") + return + + try: + start_time = self.validate_time_format(self.start_time.get()) + end_time = self.validate_time_format(self.end_time.get()) + except ValueError as e: + messagebox.showerror("Error", str(e)) + return + + # Disable extract button and start progress + self.extract_btn.config(state="disabled") + self.progress.start() + + # Run extraction in separate thread + thread = threading.Thread(target=self._run_extraction, args=(start_time, end_time)) + thread.daemon = True + thread.start() + + def _run_extraction(self, start_time, end_time): + """Run ffmpeg extraction in background thread""" + try: + # Generate output filename + input_path = Path(self.input_file.get()) + timestamp = str(int(os.path.getctime(self.input_file.get()))) + output_filename = f"{input_path.stem}_clip_{timestamp}{input_path.suffix}" + output_path = os.path.join(self.output_dir.get(), output_filename) + + # Build ffmpeg command + cmd = ["ffmpeg", "-i", self.input_file.get()] + + # Add start time + if isinstance(start_time, (int, float)) and start_time > 0: + cmd.extend(["-ss", str(start_time)]) + elif isinstance(start_time, str) and start_time != "00:00:00": + cmd.extend(["-ss", start_time]) + + # Add duration or end time + if isinstance(end_time, (int, float)) and isinstance(start_time, (int, float)): + duration = end_time - start_time + cmd.extend(["-t", str(duration)]) + elif isinstance(end_time, str): + cmd.extend(["-to", end_time]) + + # Add quality parameters + cmd.extend(self.get_ffmpeg_quality_params()) + + # Add output file + cmd.append(output_path) + + self.log_message(f"Starting extraction...") + self.log_message(f"Command: {' '.join(cmd)}") + + # Run ffmpeg + process = subprocess.run( + cmd, + capture_output=True, + text=True, + cwd=self.output_dir.get() + ) + + # Handle results + if process.returncode == 0: + self.log_message(f"✓ Successfully extracted clip to: {output_filename}") + self.log_message(f"Output file size: {self._get_file_size(output_path)}") + else: + self.log_message(f"✗ Error during extraction:") + self.log_message(process.stderr) + + except Exception as e: + self.log_message(f"✗ Exception occurred: {str(e)}") + + finally: + # Re-enable button and stop progress + self.root.after(0, self._extraction_finished) + + def _extraction_finished(self): + """Called when extraction is finished""" + self.progress.stop() + self.extract_btn.config(state="normal") + + def _get_file_size(self, filepath): + """Get human-readable file size""" + try: + size = os.path.getsize(filepath) + for unit in ['B', 'KB', 'MB', 'GB']: + if size < 1024.0: + return f"{size:.1f} {unit}" + size /= 1024.0 + return f"{size:.1f} TB" + except: + return "Unknown" + +def main(): + # Check if ffmpeg is available + try: + subprocess.run(["ffmpeg", "-version"], capture_output=True, check=True) + except (subprocess.CalledProcessError, FileNotFoundError): + messagebox.showerror("Error", "ffmpeg not found. Please install ffmpeg and ensure it's in your PATH.") + return + + root = tk.Tk() + app = VideoClipperGUI(root) + root.mainloop() + +if __name__ == "__main__": + main()
\ No newline at end of file @@ -0,0 +1,98 @@ +#!/usr/bin/python3 + +import argparse +import sys +import time +import os +#Words Of Wisdom +# output some random text from some given collection of files, +# - primarily used to grab some random "words of wisdom" from my journals and writings + +paths=[] #the paths to scan recursively for files from which to grab text +samplefiles=[] #the paths of the individual files from which we want to grab text +matchpattern='' #if we want to filter the files by some text pattern that the filename must match +time_min = -1 #threshold time. dont use files that are older +v=False +def attemptReadSampleFile(filepath): + if v: print('checking {}'.format(filepath)) + if os.path.isfile(filepath): + try: + with open(filepath, 'rb') as f: + ftype = filetype.guess(filepath) + if v: print('filetype: {}'.format(str(ftype))) + if ftype is not None: + if ftype.extension in ['py', 'c', 'cc', 'h', 'hh', 'java', 'rst', 'css', 'html', 'htm', 'js', 'php', 'sh']: #don't want code in the sample data + if v: print('this file is code') + return None + if ftype.extension == 'odt' and filepath[-1] != '#': #openoffice doc and not a lock file + subproc = subprocess.run(['odt2txt', filepath], encoding='utf-8', stdout=subprocess.PIPE) + return subproc.stdout + if ftype.extension == 'txt': + return str(f.read(), encoding='utf-8') + else: + fb = f.read() #file data (bytes) to detect encoding + enc = str(chardet.detect(fb)['encoding']) + if v: print('encoding: {}'.format(enc)) + if enc in ['ascii', 'utf-8']: + return str(fb, encoding=enc) + else: + return None + except Exception as e: + print(f"Error reading file {filepath}: {e}") + return None + else: + if v: print('not a file') + return None + +def parseArgs(): + parser = argparse.ArgumentParser(description='output some random text from some given collection of files') + parser.add_argument('-v', '--verbose', action='store_true', help='verbose') + + parser.add_argument('-p', '--path', type=str, required=False, help='a path to scan', action='append', default=['~/doc']) + parser.add_argument('-o', '--output', type=str, required=False, help='output file') + args = parser.parse_args() + return args + +def main(): + args = parseArgs() + if args.verbose: + print(f"Input file: {args.input}") + if args.output: + print(f"Output file: {args.output}") + + if args.path: + paths.append(args.path) + + + tStart = time.time() + for p in paths: + if v: print('path {}'.format(p)) + if os.path.isdir(p): + for root,dirs,files in os.walk(p): + if v: print('walk {}: {} files, {} dirs'.format(root, len(files), len(dirs))) + for f in files: + samplefiles.append(root + '/' + f) + else: + samplefiles.append(p) + tEnd = time.time() + tDuration = tEnd - tStart + print('gathered {} candidate files in {} seconds, from paths {}'.format(len(samplefiles), tDuration, str(paths))) + #pick random file until we get an acceptable one + fi = random.randint(0, len(samplefiles)) + t = attemptReadSampleFile(samplefiles[fi]) + while t == None: + del samplefiles[fi] + fi = random.randint(0, len(samplefiles)) + t = attemptReadSampleFile(samplefiles[fi]) + + mt = time.ctime(os.path.getmtime(samplefiles[fi])) + print('{} ;\n last modified {} :\n {}'.format(samplefiles[fi], mt, t)) + + lines = t.splitlines() + li = random.randint(0, len(t)) #line index + #ci = random.randint(0, len(t)) #character index + res = '\n'.join(lines[li: li+7]) + print(res) + +if __name__ == '__main__': + main()
\ No newline at end of file |
