begin vodkeeping

This commit is contained in:
2025-07-02 18:31:40 -06:00
parent facaed1f7b
commit 645a40154d
4 changed files with 921 additions and 0 deletions
+69
View File
@@ -0,0 +1,69 @@
# SpecStory Artifacts Directory
This directory is automatically created and maintained by the SpecStory extension to preserve your AI chat history.
## What's Here?
- `.specstory/history`: Contains auto-saved markdown files of your AI coding sessions
- Each file represents a separate AI chat session
- If you enable auto-save, files are automatically updated as you work
- You can enable/disable the auto-save feature in the SpecStory settings, it is disabled by default
- `.specstory/.project.json`: Contains the persistent project identity for the current workspace
- This file is only present if you enable AI rules derivation
- This is used to provide consistent project identity of your project, even as the workspace is moved or renamed
- `.specstory/ai_rules_backups`: Contains backups of the `.cursor/rules/derived-cursor-rules.mdc` or the `.github/copilot-instructions.md` file
- Backups are automatically created each time the `.cursor/rules/derived-cursor-rules.mdc` or the `.github/copilot-instructions.md` file is updated
- You can enable/disable the AI Rules derivation feature in the SpecStory settings, it is disabled by default
- `.specstory/.gitignore`: Contains directives to exclude non-essential contents of the `.specstory` directory from version control
- Add `/history` to exclude the auto-saved chat history from version control
## Valuable Uses
- Capture: Keep your context window up-to-date when starting new Chat/Composer sessions via @ references
- Search: For previous prompts and code snippets
- Learn: Meta-analyze your patterns and learn from your past experiences
- Derive: Keep the AI on course with your past decisions by automatically deriving rules from your AI interactions
## Version Control
We recommend keeping this directory under version control to maintain a history of your AI interactions. However, if you prefer not to version these files, you can exclude them by adding this to your `.gitignore`:
```
.specstory/**
```
We recommend __not__ keeping the `.specstory/ai_rules_backups` directory under version control if you are already using git to version your AI rules, and committing regularly. You can exclude it by adding this to your `.gitignore`:
```
.specstory/ai_rules_backups
```
## Searching Your Codebase
When searching your codebase, search results may include your previous AI coding interactions. To focus solely on your actual code files, you can exclude the AI interaction history from search results.
To exclude AI interaction history:
1. Open the "Find in Files" search in Cursor or VSCode (Cmd/Ctrl + Shift + F)
2. Navigate to the "files to exclude" section
3. Add the following pattern:
```
.specstory/*
```
This will ensure your searches only return results from your working codebase files.
## Notes
- Auto-save only works when Cursor or VSCode flushes sqlite database data to disk. This results in a small delay after the AI response is complete before SpecStory can save the history.
## Settings
You can control auto-saving behavior in Cursor or VSCode:
1. Open Cursor/Code → Settings → VS Code Settings (Cmd/Ctrl + ,)
2. Search for "SpecStory"
3. Find "Auto Save" setting to enable/disable
Auto-save occurs when changes are detected in the sqlite database, or every 2 minutes as a safety net.
@@ -0,0 +1,403 @@
<!-- Generated by SpecStory -->
# Initialize VodKeeper project repository (2025-07-03 00:07Z)
_**User**_
I'm starting a project integrating all my workflow from R:\Videos\mux into one executable with UI. Let's initialize a new private repo called VodKeeper @ \\Hydra\git
---
_**Assistant**_
I'll help you initialize a new private repository for your VodKeeper project. Let me start by exploring the current workspace structure and then set up the repository.
---
<details>
<summary>Listed current directory • **1** results</summary>
| Name |
|-------|
| 📁 `.git` |
</details>
---
I can see there's already a `.git` directory, which means this is already a git repository. Let me check the current git status and remote configuration to understand the current setup.
---
```bash
git status
```
```
PS C:\Users\Nathan\Desktop\VODkeeper> git status
PS C:\Users\Nathan\Desktop\VODkeeper> git status
PS C:\Users\Nathan\Desktop\VODkeeper>
```
---
```bash
git remote -v
```
```
PS C:\Users\Nathan\Desktop\VODkeeper> git remote -v
PS C:\Users\Nathan\Desktop\VODkeeper>
```
---
I see that there's already a git repository initialized but with no remotes configured. Let me check if there are any files in the repository and then set up the remote to point to your Hydra git server.
---
```bash
git log --oneline
```
```
PS C:\Users\Nathan\Desktop\VODkeeper> git log --oneline
fatal: your current branch 'master' does not have any commits yet
PS C:\Users\Nathan\Desktop\VODkeeper>
```
---
Perfect! The repository is initialized but empty. Now let me set up the remote to point to your Hydra git server and create the initial commit structure for your VodKeeper project.
---
```bash
git remote add origin \\Hydra\git\VodKeeper.git
```
```
PS C:\Users\Nathan\Desktop\VODkeeper>
```
---
Now let me create a basic project structure for your VodKeeper application. I'll start with a README and some initial project files.
---
```diff
-
+ # VodKeeper
+
+ A unified workflow application for video processing and management, integrating all workflows from R:\Videos\mux into one executable with UI.
+
+ ## Overview
+
+ VodKeeper is designed to streamline video processing workflows by providing a single, user-friendly interface for all video-related tasks previously handled through various scripts and tools.
+
+ ## Features
+
+ - Unified UI for video processing workflows
+ - Integration with existing mux workflows
+ - Streamlined video management
+ - Automated processing pipelines
+
+ ## Development
+
+ This project is currently in initial development phase.
+
+ ## Setup
+
+ 1. Clone the repository
+ 2. Install dependencies (TBD)
+ 3. Configure video source paths
+ 4. Run the application
+
+ ## License
+
+ Private project - All rights reserved
```
---
```diff
-
+ # Dependencies
+ node_modules/
+ __pycache__/
+ *.pyc
+ *.pyo
+ *.pyd
+ .Python
+ env/
+ venv/
+ .venv/
+
+ # Build outputs
+ dist/
+ build/
+ *.exe
+ *.msi
+ *.dmg
+
+ # IDE files
+ .vscode/
+ .idea/
+ *.swp
+ *.swo
+ *~
+
+ # OS files
+ .DS_Store
+ Thumbs.db
+ desktop.ini
+
+ # Logs
+ *.log
+ logs/
+
+ # Temporary files
+ temp/
+ tmp/
+ *.tmp
+
+ # Video processing files
+ output/
+ processed/
+ *.mp4
+ *.avi
+ *.mkv
+ *.mov
+ *.wmv
+
+ # Configuration files with sensitive data
+ config.ini
+ settings.json
+ .env
+
+ # Database files
+ *.db
+ *.sqlite
+ *.sqlite3
```
---
Now let me create a basic project structure and make the initial commit:
---
```bash
git add .
```
```
PS C:\Users\Nathan\Desktop\VODkeeper>
```
---
```bash
git commit -m "Initial commit: VodKeeper project setup"
```
```
PS C:\Users\Nathan\Desktop\VODkeeper> git commit -m "Initial commit: VodKeeper project setup"
[master (root-commit) facaed1] Initial commit: VodKeeper project setup
2 files changed, 86 insertions(+)
create mode 100644 README.md
PS C:\Users\Nathan\Desktop\VODkeeper>
```
---
Now let me try to push to the remote repository. First, let me check if the remote path is accessible:
---
```bash
Test-Path "\\Hydra\git"
```
```
PS C:\Users\Nathan\Desktop\VODkeeper> Test-Path "\\Hydra\git"
True
PS C:\Users\Nathan\Desktop\VODkeeper>
```
---
Great! The network path is accessible. Now let me try to push to the remote repository:
---
```bash
git push -u origin master
```
```
PS C:\Users\Nathan\Desktop\VODkeeper> git push -u origin master
fatal: '\\Hydra\git\VodKeeper.git' does not appear to be a git repository
fatal: Could not read from remote repository.
Please make sure you have the correct access rights
and the repository exists.
PS C:\Users\Nathan\Desktop\VODkeeper>
```
---
It looks like the repository doesn't exist yet on the Hydra server. Let me check what's available in the git directory and then create the repository:
---
```bash
Get-ChildItem "\\Hydra\git"
```
```
PS C:\Users\Nathan\Desktop\VODkeeper> Get-ChildItem "\\Hydra\git"
Directory: \\Hydra\git
Mode LastWriteTime Length Name
---- ------------- ------ ----
PS C:\Users\Nathan\Desktop\VODkeeper>
```
---
The git directory is empty. Let me create the VodKeeper repository on the Hydra server:
---
```bash
git init --bare "\\Hydra\git\VodKeeper.git"
```
```
PS C:\Users\Nathan\Desktop\VODkeeper> git init --bare "\\Hydra\git\VodKeeper.git"
Initialized empty Git repository in //Hydra/git/VodKeeper.git/
PS C:\Users\Nathan\Desktop\VODkeeper>
```
---
Perfect! Now let me try pushing to the remote repository again:
---
```bash
git push -u origin master
```
```
PS C:\Users\Nathan\Desktop\VODkeeper> git push -u origin master
fatal: detected dubious ownership in repository at '\\Hydra\git\VodKeeper.git'
'\\Hydra\git\VodKeeper.git' is owned by:
(inconvertible) (S-1-5-21-836389462-2755305455-3878942114-3060)
but the current user is:
PYRO/Nathan (S-1-5-21-1745461870-1872559679-675649908-1001)
To add an exception for this directory, call:
git config --global --add safe.directory '\\Hydra\git\VodKeeper.git'
fatal: Could not read from remote repository.
Please make sure you have the correct access rights
and the repository exists.
PS C:\Users\Nathan\Desktop\VODkeeper>
```
---
The push failed due to a "dubious ownership" warning from git, which is common when using network shares. To resolve this, I need to mark the remote directory as safe for git operations.
I'll configure git to treat `\\Hydra\git\VodKeeper.git` as a safe directory, then retry the push.
---
```bash
git config --global --add safe.directory "\\Hydra\git\VodKeeper.git"
```
```
PS C:\Users\Nathan\Desktop\VODkeeper> git config --global --add safe.directory "\\Hydra\git\VodKeeper.git"
```
---
```bash
git push -u origin master
```
```
PS C:\Users\Nathan\Desktop\VODkeeper> git push -u origin master
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 16 threads
Compressing objects: 100% (4/4), done.
Total 4 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
To \\Hydra\git\VodKeeper.git
* [new branch] master -> master
branch 'master' set up to track 'origin/master'.
PS C:\Users\Nathan\Desktop\VODkeeper>
```
---
Your new private repository VodKeeper has been successfully initialized and pushed to \\Hydra\git\VodKeeper.git. The project is now ready for further development and integration of your workflow.
If you need to set up the UI or add specific workflow scripts next, just let me know!
---
+6
View File
@@ -0,0 +1,6 @@
tkinter
opencv-python
pillow
ffmpeg-python
psutil
threading
+443
View File
@@ -0,0 +1,443 @@
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext
import threading
import subprocess
import os
import json
import psutil
from datetime import datetime
import cv2
from PIL import Image, ImageTk
import ffmpeg
class VodKeeper:
def __init__(self, root):
self.root = root
self.root.title("VodKeeper - Video Processing Workflow")
self.root.geometry("1200x800")
# Configuration
self.config = {
'input_path': 'R:\\mux\\input',
'output_path': 'R:\\mux\\output',
'logs_path': 'R:\\mux\\logs',
'codec': 'h265_nvenc',
'preset': 'p7',
'cq': 0,
'auto_bitrate': True,
'preview_duration': 60
}
# Render state
self.current_process = None
self.render_queue = []
self.is_rendering = False
self.setup_ui()
self.load_config()
def setup_ui(self):
# Create notebook for tabs
self.notebook = ttk.Notebook(self.root)
self.notebook.pack(fill='both', expand=True, padx=10, pady=10)
# Input Tab
self.setup_input_tab()
# Render Tab
self.setup_render_tab()
# QC Tab
self.setup_qc_tab()
def setup_input_tab(self):
input_frame = ttk.Frame(self.notebook)
self.notebook.add(input_frame, text="Input")
# File selection
file_frame = ttk.LabelFrame(input_frame, text="File Selection", padding=10)
file_frame.pack(fill='x', padx=10, pady=5)
ttk.Label(file_frame, text="Input Directory:").grid(row=0, column=0, sticky='w', pady=2)
self.input_path_var = tk.StringVar(value=self.config['input_path'])
ttk.Entry(file_frame, textvariable=self.input_path_var, width=50).grid(row=0, column=1, padx=5, pady=2)
ttk.Button(file_frame, text="Browse", command=self.browse_input).grid(row=0, column=2, padx=5, pady=2)
# File list
ttk.Label(file_frame, text="Available Files:").grid(row=1, column=0, sticky='w', pady=(10,2))
self.file_listbox = tk.Listbox(file_frame, height=8, width=70)
self.file_listbox.grid(row=2, column=0, columnspan=3, sticky='ew', pady=2)
self.file_listbox.bind('<<ListboxSelect>>', self.on_file_select)
# Refresh button
ttk.Button(file_frame, text="Refresh Files", command=self.refresh_files).grid(row=3, column=0, pady=5)
# Encoding settings
settings_frame = ttk.LabelFrame(input_frame, text="Encoding Settings", padding=10)
settings_frame.pack(fill='x', padx=10, pady=5)
# Codec selection
ttk.Label(settings_frame, text="Codec:").grid(row=0, column=0, sticky='w', pady=2)
self.codec_var = tk.StringVar(value=self.config['codec'])
codec_combo = ttk.Combobox(settings_frame, textvariable=self.codec_var,
values=['h265_nvenc', 'vp9', 'av1'], state='readonly')
codec_combo.grid(row=0, column=1, sticky='w', padx=5, pady=2)
# Preset selection
ttk.Label(settings_frame, text="Preset:").grid(row=1, column=0, sticky='w', pady=2)
self.preset_var = tk.StringVar(value=self.config['preset'])
preset_combo = ttk.Combobox(settings_frame, textvariable=self.preset_var,
values=['p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'p7'], state='readonly')
preset_combo.grid(row=1, column=1, sticky='w', padx=5, pady=2)
# CQ setting
ttk.Label(settings_frame, text="CQ:").grid(row=2, column=0, sticky='w', pady=2)
self.cq_var = tk.IntVar(value=self.config['cq'])
cq_spin = ttk.Spinbox(settings_frame, from_=0, to=51, textvariable=self.cq_var, width=10)
cq_spin.grid(row=2, column=1, sticky='w', padx=5, pady=2)
# Bitrate settings
bitrate_frame = ttk.LabelFrame(settings_frame, text="Bitrate Settings", padding=5)
bitrate_frame.grid(row=3, column=0, columnspan=2, sticky='ew', pady=10)
self.auto_bitrate_var = tk.BooleanVar(value=self.config['auto_bitrate'])
ttk.Checkbutton(bitrate_frame, text="Auto-detect bitrate cap",
variable=self.auto_bitrate_var).grid(row=0, column=0, sticky='w')
ttk.Label(bitrate_frame, text="Manual bitrate cap (Mbps):").grid(row=1, column=0, sticky='w', pady=2)
self.manual_bitrate_var = tk.DoubleVar(value=0)
ttk.Entry(bitrate_frame, textvariable=self.manual_bitrate_var, width=10).grid(row=1, column=1, padx=5, pady=2)
# File info
info_frame = ttk.LabelFrame(input_frame, text="File Information", padding=10)
info_frame.pack(fill='x', padx=10, pady=5)
self.file_info_text = scrolledtext.ScrolledText(info_frame, height=6, width=80)
self.file_info_text.pack(fill='both', expand=True)
# Action buttons
button_frame = ttk.Frame(input_frame)
button_frame.pack(fill='x', padx=10, pady=10)
ttk.Button(button_frame, text="Add to Queue", command=self.add_to_queue).pack(side='left', padx=5)
ttk.Button(button_frame, text="Preview (1 min)", command=self.preview_encode).pack(side='left', padx=5)
ttk.Button(button_frame, text="Save Settings", command=self.save_config).pack(side='right', padx=5)
def setup_render_tab(self):
render_frame = ttk.Frame(self.notebook)
self.notebook.add(render_frame, text="Render")
# Progress section
progress_frame = ttk.LabelFrame(render_frame, text="Render Progress", padding=10)
progress_frame.pack(fill='x', padx=10, pady=5)
self.progress_var = tk.DoubleVar()
self.progress_bar = ttk.Progressbar(progress_frame, variable=self.progress_var, maximum=100)
self.progress_bar.pack(fill='x', pady=5)
self.progress_label = ttk.Label(progress_frame, text="Ready")
self.progress_label.pack()
# Control buttons
control_frame = ttk.Frame(progress_frame)
control_frame.pack(pady=10)
self.start_button = ttk.Button(control_frame, text="Start Rendering", command=self.start_rendering)
self.start_button.pack(side='left', padx=5)
self.pause_button = ttk.Button(control_frame, text="Pause", command=self.pause_rendering, state='disabled')
self.pause_button.pack(side='left', padx=5)
self.stop_button = ttk.Button(control_frame, text="Stop", command=self.stop_rendering, state='disabled')
self.stop_button.pack(side='left', padx=5)
# Queue section
queue_frame = ttk.LabelFrame(render_frame, text="Render Queue", padding=10)
queue_frame.pack(fill='both', expand=True, padx=10, pady=5)
self.queue_tree = ttk.Treeview(queue_frame, columns=('Status', 'Progress'), show='tree headings')
self.queue_tree.heading('#0', text='File')
self.queue_tree.heading('Status', text='Status')
self.queue_tree.heading('Progress', text='Progress')
self.queue_tree.pack(fill='both', expand=True)
# Log output
log_frame = ttk.LabelFrame(render_frame, text="FFmpeg Output", padding=10)
log_frame.pack(fill='both', expand=True, padx=10, pady=5)
self.log_text = scrolledtext.ScrolledText(log_frame, height=10)
self.log_text.pack(fill='both', expand=True)
def setup_qc_tab(self):
qc_frame = ttk.Frame(self.notebook)
self.notebook.add(qc_frame, text="QC")
# File selection for QC
qc_file_frame = ttk.LabelFrame(qc_frame, text="QC File Selection", padding=10)
qc_file_frame.pack(fill='x', padx=10, pady=5)
ttk.Label(qc_file_frame, text="Source File:").grid(row=0, column=0, sticky='w', pady=2)
self.qc_source_var = tk.StringVar()
ttk.Entry(qc_file_frame, textvariable=self.qc_source_var, width=50).grid(row=0, column=1, padx=5, pady=2)
ttk.Button(qc_file_frame, text="Browse", command=self.browse_qc_source).grid(row=0, column=2, padx=5, pady=2)
ttk.Label(qc_file_frame, text="Output File:").grid(row=1, column=0, sticky='w', pady=2)
self.qc_output_var = tk.StringVar()
ttk.Entry(qc_file_frame, textvariable=self.qc_output_var, width=50).grid(row=1, column=1, padx=5, pady=2)
ttk.Button(qc_file_frame, text="Browse", command=self.browse_qc_output).grid(row=1, column=2, padx=5, pady=2)
# QC controls
qc_control_frame = ttk.Frame(qc_frame)
qc_control_frame.pack(fill='x', padx=10, pady=5)
ttk.Button(qc_control_frame, text="Run QC Check", command=self.run_qc_check).pack(side='left', padx=5)
ttk.Button(qc_control_frame, text="Compare Frames", command=self.compare_frames).pack(side='left', padx=5)
ttk.Button(qc_control_frame, text="Export Report", command=self.export_qc_report).pack(side='left', padx=5)
# Frame comparison viewer
comparison_frame = ttk.LabelFrame(qc_frame, text="Frame Comparison", padding=10)
comparison_frame.pack(fill='both', expand=True, padx=10, pady=5)
# Frame navigation
nav_frame = ttk.Frame(comparison_frame)
nav_frame.pack(fill='x', pady=5)
ttk.Button(nav_frame, text="Previous Frame", command=self.prev_frame).pack(side='left', padx=5)
self.frame_label = ttk.Label(nav_frame, text="Frame: 0")
self.frame_label.pack(side='left', padx=20)
ttk.Button(nav_frame, text="Next Frame", command=self.next_frame).pack(side='left', padx=5)
# Frame display
display_frame = ttk.Frame(comparison_frame)
display_frame.pack(fill='both', expand=True)
self.source_canvas = tk.Canvas(display_frame, bg='black')
self.source_canvas.pack(side='left', fill='both', expand=True, padx=5)
self.output_canvas = tk.Canvas(display_frame, bg='black')
self.output_canvas.pack(side='right', fill='both', expand=True, padx=5)
# QC results
results_frame = ttk.LabelFrame(qc_frame, text="QC Results", padding=10)
results_frame.pack(fill='x', padx=10, pady=5)
self.qc_results_text = scrolledtext.ScrolledText(results_frame, height=6)
self.qc_results_text.pack(fill='both', expand=True)
def browse_input(self):
path = filedialog.askdirectory(initialdir=self.config['input_path'])
if path:
self.input_path_var.set(path)
self.refresh_files()
def refresh_files(self):
self.file_listbox.delete(0, tk.END)
input_path = self.input_path_var.get()
if os.path.exists(input_path):
for file in os.listdir(input_path):
if file.lower().endswith(('.mp4', '.avi', '.mkv', '.mov', '.wmv')):
self.file_listbox.insert(tk.END, file)
def on_file_select(self, event):
selection = self.file_listbox.curselection()
if selection:
filename = self.file_listbox.get(selection[0])
self.update_file_info(filename)
def update_file_info(self, filename):
filepath = os.path.join(self.input_path_var.get(), filename)
if os.path.exists(filepath):
try:
probe = ffmpeg.probe(filepath)
video_info = next(s for s in probe['streams'] if s['codec_type'] == 'video')
info_text = f"File: {filename}\n"
info_text += f"Duration: {float(probe['format']['duration']):.2f} seconds\n"
info_text += f"Resolution: {video_info['width']}x{video_info['height']}\n"
info_text += f"Codec: {video_info['codec_name']}\n"
info_text += f"Bitrate: {int(probe['format']['bit_rate']) / 1000000:.2f} Mbps\n"
self.file_info_text.delete(1.0, tk.END)
self.file_info_text.insert(1.0, info_text)
except Exception as e:
self.file_info_text.delete(1.0, tk.END)
self.file_info_text.insert(1.0, f"Error reading file: {str(e)}")
def add_to_queue(self):
selection = self.file_listbox.curselection()
if selection:
filename = self.file_listbox.get(selection[0])
self.render_queue.append({
'filename': filename,
'status': 'Queued',
'progress': 0
})
self.update_queue_display()
def preview_encode(self):
selection = self.file_listbox.curselection()
if selection:
filename = self.file_listbox.get(selection[0])
self.encode_file(filename, preview=True)
def start_rendering(self):
if self.render_queue and not self.is_rendering:
self.is_rendering = True
self.start_button.config(state='disabled')
self.pause_button.config(state='normal')
self.stop_button.config(state='normal')
# Start rendering thread
render_thread = threading.Thread(target=self.render_queue_worker)
render_thread.daemon = True
render_thread.start()
def render_queue_worker(self):
while self.render_queue and self.is_rendering:
job = self.render_queue[0]
self.encode_file(job['filename'])
if self.render_queue:
self.render_queue.pop(0)
self.update_queue_display()
self.is_rendering = False
self.start_button.config(state='normal')
self.pause_button.config(state='disabled')
self.stop_button.config(state='disabled')
def encode_file(self, filename, preview=False):
input_path = os.path.join(self.input_path_var.get(), filename)
output_filename = f"encoded_{filename}"
output_path = os.path.join(self.config['output_path'], output_filename)
# Build FFmpeg command
cmd = [
'ffmpeg', '-i', input_path,
'-c:v', self.codec_var.get(),
'-preset', self.preset_var.get(),
'-cq', str(self.cq_var.get())
]
if preview:
cmd.extend(['-t', str(self.config['preview_duration'])])
cmd.append(output_path)
try:
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
universal_newlines=True, bufsize=1)
self.current_process = process
for line in process.stdout:
self.log_text.insert(tk.END, line)
self.log_text.see(tk.END)
self.root.update_idletasks()
process.wait()
except Exception as e:
messagebox.showerror("Error", f"Encoding failed: {str(e)}")
def pause_rendering(self):
if self.current_process:
self.current_process.terminate()
def stop_rendering(self):
self.is_rendering = False
if self.current_process:
self.current_process.terminate()
def update_queue_display(self):
for item in self.queue_tree.get_children():
self.queue_tree.delete(item)
for i, job in enumerate(self.render_queue):
self.queue_tree.insert('', 'end', text=job['filename'],
values=(job['status'], f"{job['progress']}%"))
def browse_qc_source(self):
filename = filedialog.askopenfilename(
title="Select Source File",
filetypes=[("Video files", "*.mp4 *.avi *.mkv *.mov *.wmv")]
)
if filename:
self.qc_source_var.set(filename)
def browse_qc_output(self):
filename = filedialog.askopenfilename(
title="Select Output File",
filetypes=[("Video files", "*.mp4 *.avi *.mkv *.mov *.wmv")]
)
if filename:
self.qc_output_var.set(filename)
def run_qc_check(self):
source = self.qc_source_var.get()
output = self.qc_output_var.get()
if not source or not output:
messagebox.showerror("Error", "Please select both source and output files")
return
# Run QC check (placeholder for check_files.py functionality)
self.qc_results_text.delete(1.0, tk.END)
self.qc_results_text.insert(1.0, "QC check completed...\n")
def compare_frames(self):
source = self.qc_source_var.get()
output = self.qc_output_var.get()
if not source or not output:
messagebox.showerror("Error", "Please select both source and output files")
return
# Load frames for comparison
self.load_comparison_frames()
def load_comparison_frames(self):
# Placeholder for frame loading and display
pass
def prev_frame(self):
# Placeholder for frame navigation
pass
def next_frame(self):
# Placeholder for frame navigation
pass
def export_qc_report(self):
# Placeholder for QC report export
pass
def save_config(self):
self.config.update({
'input_path': self.input_path_var.get(),
'codec': self.codec_var.get(),
'preset': self.preset_var.get(),
'cq': self.cq_var.get(),
'auto_bitrate': self.auto_bitrate_var.get()
})
with open('vodkeeper_config.json', 'w') as f:
json.dump(self.config, f, indent=2)
messagebox.showinfo("Success", "Configuration saved")
def load_config(self):
try:
with open('vodkeeper_config.json', 'r') as f:
config = json.load(f)
self.config.update(config)
except FileNotFoundError:
pass
def main():
root = tk.Tk()
app = VodKeeper(root)
root.mainloop()
if __name__ == "__main__":
main()