Showcase Hub

King Corp Technology

Projects, downloads, code, and modular showcase data.

Projects

2 items
0
Active Builds
4
Public Artifacts
6
Tech Themes

What Belongs Here

  • Problem statement and target user
  • Current status, risks, and next milestone
  • Outcome and impact once shipped

Project Narratives

Ghost

Desktop workflow automation for repetitive tasks
Released-Updates Coming

Ghost focuses on replacing repetitive desktop routines with fast, replayable automations and reliable execution controls.

Teams and solo operators lose hours each week to repetitive click and keyboard workflows that are easy to mis-execute.

Build a sequence recorder with channel-based playback, configurable delays, and hotkey triggers for repeatable runtime behavior.

Stabilizing multi-profile execution behavior and reducing setup friction for first-time users.

Users recover consistent time blocks by turning repeat tasks into one-trigger automations.

Automation Desktop Productivity

Project Code Snippets

1 file
sample.txt
Text - Data Parsing and Analytics - 33.69 KB
import os
import sys
import json
import time
import math
import random
import threading
import tkinter as tk
from tkinter import ttk, messagebox, colorchooser
import ctypes
from ctypes import wintypes
from datetime import datetime
import traceback
from collections import deque

# =========================
# Debug function
# =========================

def debug_print(msg):
    """Print debug messages"""
    print(f"[DEBUG] {msg}")

# =========================
# Version and Paths
# =========================

APP_NAME = "Ghost"
APP_VERSION = "2.4.0"  # Updated version
CONFIG_VERSION = "2.4"

def is_frozen():
    return getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS")

BASE_DIR = os.path.dirname(sys.executable if is_frozen() else os.path.abspath(__file__))
CONFIG_PATH = os.path.join(BASE_DIR, "config.json")

debug_print(f"Base directory: {BASE_DIR}")
debug_print(f"Config path: {CONFIG_PATH}")

# =========================
# Win32 Primitives with Relative Movement for Games
# =========================

if not hasattr(wintypes, "ULONG_PTR"):
    wintypes.ULONG_PTR = ctypes.c_ulonglong if ctypes.sizeof(ctypes.c_void_p) == 8 else ctypes.c_ulong

user32 = ctypes.WinDLL("user32", use_last_error=True)

# Mouse event constants
MOUSEEVENTF_MOVE = 0x0001
MOUSEEVENTF_LEFTDOWN = 0x0002
MOUSEEVENTF_LEFTUP = 0x0004
MOUSEEVENTF_RIGHTDOWN = 0x0008
MOUSEEVENTF_RIGHTUP = 0x0010
MOUSEEVENTF_MIDDLEDOWN = 0x0020
MOUSEEVENTF_MIDDLEUP = 0x0040
MOUSEEVENTF_ABSOLUTE = 0x8000
MOUSEEVENTF_VIRTUALDESK = 0x4000

# Max steps before cutoff - CENTRALIZED CONSTANT
MAX_SEQUENCE_STEPS = 50000

# Keyboard constants
KEYEVENTF_KEYUP = 0x0002

# Original functions we still need
GetAsyncKeyState = user32.GetAsyncKeyState
GetAsyncKeyState.argtypes = [wintypes.INT]
GetAsyncKeyState.restype = wintypes.SHORT

GetCursorPos = user32.GetCursorPos
GetCursorPos.argtypes = [ctypes.POINTER(wintypes.POINT)]
GetCursorPos.restype = wintypes.BOOL

SetCursorPos = user32.SetCursorPos
SetCursorPos.argtypes = [wintypes.INT, wintypes.INT]
SetCursorPos.restype = wintypes.BOOL

MapVirtualKeyW = user32.MapVirtualKeyW
MapVirtualKeyW.argtypes = [wintypes.UINT, wintypes.UINT]
MapVirtualKeyW.restype = wintypes.UINT

VkKeyScanW = user32.VkKeyScanW
VkKeyScanW.argtypes = [wintypes.WCHAR]
VkKeyScanW.restype = wintypes.SHORT

mouse_event = user32.mouse_event
mouse_event.argtypes = [wintypes.DWORD, wintypes.DWORD, wintypes.DWORD, wintypes.DWORD, wintypes.ULONG_PTR]
mouse_event.restype = None

keybd_event = user32.keybd_event
keybd_event.argtypes = [wintypes.BYTE, wintypes.BYTE, wintypes.DWORD, wintypes.ULONG_PTR]
keybd_event.restype = None

# Virtual key codes (minimal set for UI display and hotkey parsing)
VK = {
    "BACKSPACE": 0x08, "TAB": 0x09, "ENTER": 0x0D,
    "SHIFT": 0x10, "CTRL": 0x11, "ALT": 0x12,
    "PAUSE": 0x13, "CAPSLOCK": 0x14,
    "ESC": 0x1B, "SPACE": 0x20,
    "PAGEUP": 0x21, "PAGEDOWN": 0x22,
    "END": 0x23, "HOME": 0x24,
    "LEFT": 0x25, "UP": 0x26, "RIGHT": 0x27, "DOWN": 0x28,
    "INSERT": 0x2D, "DELETE": 0x2E,
    "LWIN": 0x5B, "RWIN": 0x5C,
    "NUMPAD0": 0x60, "NUMPAD1": 0x61, "NUMPAD2": 0x62, "NUMPAD3": 0x63,
    "NUMPAD4": 0x64, "NUMPAD5": 0x65, "NUMPAD6": 0x66, "NUMPAD7": 0x67,
    "NUMPAD8": 0x68, "NUMPAD9": 0x69,
    "MULTIPLY": 0x6A, "ADD": 0x6B, "SEPARATOR": 0x6C, "SUBTRACT": 0x6D,
    "DECIMAL": 0x6E, "DIVIDE": 0x6F,
    "F1": 0x70, "F2": 0x71, "F3": 0x72, "F4": 0x73, "F5": 0x74,
    "F6": 0x75, "F7": 0x76, "F8": 0x77, "F9": 0x78, "F10": 0x79,
    "F11": 0x7A, "F12": 0x7B, "F13": 0x7C, "F14": 0x7D, "F15": 0x7E,
    "F16": 0x7F, "F17": 0x80, "F18": 0x81, "F19": 0x82, "F20": 0x83,
    "F21": 0x84, "F22": 0x85, "F23": 0x86, "F24": 0x87,
    "NUMLOCK": 0x90, "SCROLL": 0x91,
    "LSHIFT": 0xA0, "RSHIFT": 0xA1, "LCONTROL": 0xA2, "RCONTROL": 0xA3,
    "LMENU": 0xA4, "RMENU": 0xA5,
    "SEMICOLON": 0xBA, "PLUS": 0xBB, "COMMA": 0xBC, "MINUS": 0xBD,
    "PERIOD": 0xBE, "SLASH": 0xBF, "TILDE": 0xC0,
    "LBRACKET": 0xDB, "BACKSLASH": 0xDC, "RBRACKET": 0xDD,
    "QUOTE": 0xDE,
}

for i in range(1, 25):
    VK[f"F{i}"] = 0x6F + i
for d in range(10):
    VK[str(d)] = 0x30 + d
for c in range(ord("A"), ord("Z") + 1):
    VK[chr(c)] = c

# =========================
# Mouse Functions for Games (Relative Movement)
# =========================

def get_cursor_pos():
    point = wintypes.POINT()
    GetCursorPos(ctypes.byref(point))
    return point.x, point.y

def is_vk_down(vk: int) -> bool:
    return (GetAsyncKeyState(vk) & 0x8000) != 0

def tap_keybd_event(vk: int, hold_ms: int):
    """Tap a key using keybd_event"""
    scan = MapVirtualKeyW(vk, 0) & 0xFF
    keybd_event(vk, scan, 0, 0)
    if hold_ms > 0:
        time.sleep(hold_ms / 1000.0)
    keybd_event(vk, scan, KEYEVENTF_KEYUP, 0)

def mouse_click(button: str, hold_ms: int):
    """Click using mouse_event"""
    if button == "left":
        down_flag = MOUSEEVENTF_LEFTDOWN
        up_flag = MOUSEEVENTF_LEFTUP
    elif button == "right":
        down_flag = MOUSEEVENTF_RIGHTDOWN
        up_flag = MOUSEEVENTF_RIGHTUP
    elif button == "middle":
        down_flag = MOUSEEVENTF_MIDDLEDOWN
        up_flag = MOUSEEVENTF_MIDDLEUP
    else:
        return
    mouse_event(down_flag, 0, 0, 0, 0)
    if hold_ms > 0:
        time.sleep(hold_ms / 1000.0)
    mouse_event(up_flag, 0, 0, 0, 0)

def mouse_move_relative(dx: int, dy: int):
    """Relative movement using mouse_event (works in games like Fallout 76)"""
    if dx != 0 or dy != 0:
        mouse_event(MOUSEEVENTF_MOVE, dx, dy, 0, 0)

def mouse_move_to(x: int, y: int, speed_px_per_sec: int = 500, smooth: bool = True, steps: int = 20, app=None):
    """
    Move mouse to specified coordinates using relative movements with fluid motion
    """
    if not smooth:
        # Fast movement - still use chunks but with minimal delay
        current_x, current_y = get_cursor_pos()
        dx = x - current_x
        dy = y - current_y
        distance = math.sqrt(dx * dx + dy * dy)
        
        if distance < 1:
            return
            
        # Use larger chunks for faster movement
        chunk_size = 50
        num_chunks = max(1, int(distance / chunk_size))
        
        # Calculate chunk movements
        chunk_dx = dx / num_chunks
        chunk_dy = dy / num_chunks
        
        for i in range(num_chunks):
            if app and app.stop_event.is_set():
                return
            
            # Move in chunks
            if i < num_chunks - 1:
                move_dx = int(chunk_dx)
                move_dy = int(chunk_dy)
            else:
                # Last chunk - go exactly to target
                curr_x, curr_y = get_cursor_pos()
                move_dx = x - curr_x
                move_dy = y - curr_y
            
            if abs(move_dx) > 0 or abs(move_dy) > 0:
                mouse_move_relative(move_dx, move_dy)
            
            # Very small delay between chunks
            time.sleep(0.001)
        
        return
    
    # Smooth, fluid movement
    current_x, current_y = get_cursor_pos()
    dx = x - current_x
    dy = y - current_y
    distance = math.sqrt(dx * dx + dy * dy)
    
    if distance < 1:
        return
    
    # Calculate optimal number of steps for fluid motion
    # More steps for longer distances, but cap it
    base_steps = max(20, min(100, int(distance / 2)))
    if steps > 0:
        num_steps = min(base_steps, steps)
    else:
        num_steps = base_steps
    
    # Use a more natural easing curve (smooth start, smooth stop)
    for i in range(1, num_steps + 1):
        if app and app.stop_event.is_set():
            return
        
        # Smoother easing: sin^2 for very smooth acceleration/deceleration
        t = i / num_steps
        # Easing function: sin^2 gives smooth start and stop
        eased_t = math.sin(t * math.pi / 2) ** 2
        
        target_x = int(current_x + dx * eased_t)
        target_y = int(current_y + dy * eased_t)
        
        # Get current position
        curr_x, curr_y = get_cursor_pos()
        move_dx = target_x - curr_x
        move_dy = target_y - curr_y
        
        # Only move if needed
        if abs(move_dx) > 0 or abs(move_dy) > 0:
            mouse_move_relative(move_dx, move_dy)
        
        # Dynamic delay based on speed and distance
        if speed_px_per_sec > 0 and distance > 0:
            move_time = distance / speed_px_per_sec
            # Vary delay slightly for more natural feel
            base_delay = move_time / num_steps
            # Add tiny random variation (0.9-1.1x)
            delay = base_delay * (0.9 + random.random() * 0.2)
            time.sleep(max(0.001, delay))
    
    # Final position adjustment
    final_x, final_y = get_cursor_pos()
    final_dx = x - final_x
    final_dy = y - final_y
    if abs(final_dx) > 1 or abs(final_dy) > 1:
        mouse_move_relative(final_dx, final_dy)

def key_to_vk(key_str: str):
    """Convert key name to VK code (for UI and hotkey parsing only)"""
    ks = (key_str or "").strip().upper()
    if not ks:
        return None
    if ks in VK:
        return VK[ks]
    if len(ks) == 1:
        vks = VkKeyScanW(ks)
        if vks == -1:
            return None
        return vks & 0xFF
    return None

def normalize_hotkey_string(s: str) -> str:
    s = (s or "").strip().upper().replace("-", "+")
    s = "+".join([p.strip() for p in s.split("+") if p.strip()])
    return s

def parse_hotkey(s: str):
    s = normalize_hotkey_string(s)
    if not s:
        return None, None
    parts = s.split("+")
    mods = set()
    key_part = parts[-1]

    for p in parts[:-1]:
        if p in ("CTRL", "CONTROL"):
            mods.add("CTRL")
        elif p == "SHIFT":
            mods.add("SHIFT")
        elif p == "ALT":
            mods.add("ALT")
        elif p in ("WIN", "WINDOWS"):
            mods.add("WIN")

    vk = key_to_vk(key_part)
    return mods, vk

def current_mods_down():
    mods = set()
    if is_vk_down(VK["CTRL"]):
        mods.add("CTRL")
    if is_vk_down(VK["SHIFT"]):
        mods.add("SHIFT")
    if is_vk_down(VK["ALT"]):
        mods.add("ALT")
    if is_vk_down(VK["LWIN"]) or is_vk_down(VK["RWIN"]):
        mods.add("WIN")
    return mods

def get_human_interval(base_ms: int, state: dict):
    lvl = float(state.get("level", 0.0))
    up = int(state.get("up", 20))
    down = int(state.get("down", 10))
    step = float(state.get("step", 0.1))

    if random.randrange(0, 100) < up:
        lvl = min(1.0, lvl + step)
    if random.randrange(0, 100) < down:
        lvl = max(0.0, lvl - step)

    state["level"] = lvl
    use_wide = (random.random() < lvl)
    variation = random.uniform(0.8, 1.2) if not use_wide else random.uniform(0.5, 2.0)
    return max(1, int(base_ms * variation))

# =========================
# Theme Presets (Immutable)
# =========================

THEME_PRESETS = {
    "Midnight": {
        "bg": "#0F1115",
        "panel": "#141824",
        "panel2": "#10131C",
        "entry_bg": "#111827",
        "general_fg": "#D5DBE7",
        "entry_fg": "#EAF0FF",
        "button_fg": "#D5DBE7",
        "log_fg": "#D5DBE7",
        "muted": "#A9B0BE",
        "border": "#2B3245",
        "accent": "#1B2235",
        "sel": "#223052",
        "button_bg": "#1B2235",
        "log_bg": "#111827",
    },
    "Slate": {
        "bg": "#111318",
        "panel": "#171A21",
        "panel2": "#141723",
        "entry_bg": "#121521",
        "general_fg": "#E4E7EF",
        "entry_fg": "#F3F6FF",
        "button_fg": "#E4E7EF",
        "log_fg": "#E4E7EF",
        "muted": "#B6BDCA",
        "border": "#32384A",
        "accent": "#1E2536",
        "sel": "#2A3758",
        "button_bg": "#1E2536",
        "log_bg": "#121521",
    },
    "OLED": {
        "bg": "#000000",
        "panel": "#0A0A0A",
        "panel2": "#070707",
        "entry_bg": "#0B0B0B",
        "general_fg": "#EDEDED",
        "entry_fg": "#FFFFFF",
        "button_fg": "#EDEDED",
        "log_fg": "#EDEDED",
        "muted": "#B0B0B0",
        "border": "#2A2A2A",
        "accent": "#101010",
        "sel": "#1C1C1C",
        "button_bg": "#101010",
        "log_bg": "#0B0B0B",
    },
    "Light": {
        "bg": "#F5F7FB",
        "panel": "#FFFFFF",
        "panel2": "#F0F2F7",
        "entry_bg": "#FFFFFF",
        "general_fg": "#1B1F2A",
        "entry_fg": "#111111",
        "button_fg": "#1B1F2A",
        "log_fg": "#1B1F2A",
        "muted": "#5A6375",
        "border": "#D0D6E3",
        "accent": "#E8ECF6",
        "sel": "#DCE5FF",
        "button_bg": "#E8ECF6",
        "log_bg": "#FFFFFF",
    },
}

# =========================
# Default Config
# =========================

def get_default_config():
    """Get the default configuration"""
    return {
        "version": CONFIG_VERSION,
        "app_name": APP_NAME,
        "created": datetime.now().isoformat(),
        "last_modified": datetime.now().isoformat(),
        "last_loaded": None,
        "load_count": 0,
        
        # Core settings
        "dark_mode": True,
        "start_maximized": True,
        
        # Window settings
        "window": {
            "state": "zoomed",
            "x": None,
            "y": None,
            "width": 1400,
            "height": 900,
            "maximized": True
        },
        
        # Hotkeys
        "hotkeys": {
            "toggle": "F8",
            "stop": "ESC",
            "lock": "F9",
            "record": "F7",
            "pause": "F6"
        },
        
        # Timing defaults
        "timing": {
            "global_start_delay_ms": 0,
            "global_end_delay_ms": 0,
            "default_hold_ms": 70,
            "loop_sequence": True,
            "max_loops": 0
        },
        
        # Mouse settings
        "mouse": {
            "default_move_speed": 500,
            "default_click_hold_ms": 50,
            "return_to_original": False,
            "smooth_movement": True,
            "movement_steps": 20,
            "mouse_acceleration": False,
            "acceleration_factor": 1.5
        },
        
        # Theme settings
        "theme": {
            "preset": "Midnight",
            "colors": {},
            "per_channel": [{
                "general_fg": None,
                "entry_bg": None,
                "entry_fg": None,
                "button_bg": None,
                "button_fg": None
            } for _ in range(16)],
            "custom_presets": {}
        },
        
        # Recording settings - NEW FIELDS
        "recording": {
            "mode": "balanced",  # "full", "balanced", "lightweight"
            "capture_mouse_movement": True,
            "mouse_sample_rate_ms": 10,
            "capture_modifiers": True,
            "filter_duplicates": True,  # Now default True
            "filter_jitter": True,  # NEW: filter mouse jitter
            "jitter_threshold": 3,  # NEW: pixels of movement to ignore as jitter
            "min_gap_ms": 2,
            "movement_compression": True,  # NEW: compress nearby movements
            "compression_threshold": 5,  # NEW: pixels threshold for compression
            "max_sequence_steps": 50000,  # NEW: configurable max steps
        },
        
        # Channels (16 channels)
        "channels": [{
            "enabled": False,
            "key": "E",
            "times": 1,
            "interval_ms": 1000,
            "start_delay_ms": 0,
            "end_delay_ms": 0,
            "humanize": False,
            "sequenced": False,
            "sequence": [],
            "sequence_name": "",
            "notes": "",
            "created": None,
            "modified": None
        } for _ in range(16)],
        
        # Saved sequences library
        "saved_sequences": {},
        
        # Advanced settings
        "advanced": {
            "thread_priority": "normal",
            "process_priority": "normal",
            "debug_mode": False,
            "log_level": "info",
            "max_undo_steps": 50,
            "concurrent_execution": True
        },
        
        # UI state
        "ui_state": {
            "last_tab": 0,
            "panel_sash_pos": None,
            "channel_order": list(range(16)),
            "hidden_channels": [],
            "expanded_sections": []
        },
        
        # Custom data
        "custom_data": {}
    }

# =========================
# Simple Config Manager
# =========================

class SimpleConfigManager:
    """Simple config manager that just saves and loads directly"""
    
    def __init__(self, config_path=CONFIG_PATH):
        self.config_path = config_path
        self.config = None
        self.lock = threading.RLock()
        debug_print(f"SimpleConfigManager initialized with path: {config_path}")
        
    def load_config(self):
        """Load configuration - returns config dict"""
        with self.lock:
            debug_print(f"Loading config from: {self.config_path}")
            
            if not os.path.exists(self.config_path):
                debug_print("Config file not found, creating default")
                self.config = get_default_config()
                self._save_config()
                return self._deepcopy(self.config)
            
            try:
                with open(self.config_path, 'r', encoding='utf-8') as f:
                    loaded_config = json.load(f)
                debug_print(f"Config loaded successfully")
                
                if "version" not in loaded_config:
                    loaded_config["version"] = CONFIG_VERSION
                
                loaded_config["last_loaded"] = datetime.now().isoformat()
                loaded_config["load_count"] = loaded_config.get("load_count", 0) + 1
                
                self.config = loaded_config
                self._save_config()
                
                return self._deepcopy(self.config)
                
            except Exception as e:
                debug_print(f"Error loading config: {e}")
                traceback.print_exc()
                
                if os.path.exists(self.config_path):
                    try:
                        backup_path = self.config_path + ".backup"
                        os.rename(self.config_path, backup_path)
                        debug_print(f"Backed up corrupted file to {backup_path}")
                    except:
                        pass
                
                self.config = get_default_config()
                self._save_config()
                return self._deepcopy(self.config)
    
    def save_config(self):
        """Save current configuration"""
        with self.lock:
            if self.config is None:
                debug_print("Cannot save: config is None")
                return False
            
            debug_print(f"Saving config to: {self.config_path}")
            self.config["last_modified"] = datetime.now().isoformat()
            self.config["version"] = CONFIG_VERSION
            return self._save_config()
    
    def save_external_config(self, config):
        """Save an external config (from the app)"""
        with self.lock:
            self.config = self._deepcopy(config)
            self.config["last_modified"] = datetime.now().isoformat()
            self.config["version"] = CONFIG_VERSION
            return self._save_config()
    
    def _save_config(self):
        """Internal save method - directly overwrites file"""
        try:
            os.makedirs(os.path.dirname(self.config_path), exist_ok=True)
            with open(self.config_path, 'w', encoding='utf-8') as f:
                json.dump(self.config, f, indent=2, sort_keys=True)
            debug_print(f"Config saved successfully to {self.config_path}")
            return True
        except Exception as e:
            debug_print(f"Save failed: {e}")
            traceback.print_exc()
            return False
    
    def update_config(self, updates):
        """Update specific parts of config"""
        with self.lock:
            if self.config is None:
                return False
            for key, value in updates.items():
                self.config[key] = value
            return self.save_config()
    
    def _deepcopy(self, obj):
        """Safe deep copy"""
        try:
            return json.loads(json.dumps(obj))
        except:
            return obj
    
    def export_channel(self, channel_index):
        """Export a single channel configuration"""
        if self.config and 0 <= channel_index < 16:
            channel = self._deepcopy(self.config["channels"][channel_index])
            channel["exported"] = datetime.now().isoformat()
            return channel
        return None
    
    def import_channel(self, channel_data, channel_index):
        """Import a channel configuration"""
        if not self.config or channel_index < 0 or channel_index >= 16:
            return False
        self.config["channels"][channel_index] = channel_data
        self.config["channels"][channel_index]["imported"] = datetime.now().isoformat()
        return self.save_config()
    
    def save_sequence_to_library(self, sequence, name, description=""):
        """Save a sequence to the library"""
        if not self.config:
            return False
        
        if "saved_sequences" not in self.config:
            self.config["saved_sequences"] = {}
        
        sequence_id = f"seq_{int(time.time())}_{len(self.config['saved_sequences'])}"
        
        self.config["saved_sequences"][sequence_id] = {
            "name": name,
            "description": description,
            "sequence": self._deepcopy(sequence),
            "created": datetime.now().isoformat(),
            "modified": datetime.now().isoformat(),
            "use_count": 0
        }
        
        return self.save_config()
    
    def load_sequence_from_library(self, sequence_id):
        """Load a sequence from the library"""
        if self.config and "saved_sequences" in self.config:
            if sequence_id in self.config["saved_sequences"]:
                seq_data = self.config["saved_sequences"][sequence_id]
                seq_data["use_count"] = seq_data.get("use_count", 0) + 1
                seq_data["last_used"] = datetime.now().isoformat()
                self.save_config()
                return self._deepcopy(seq_data["sequence"])
        return None

# =========================
# Theme Helpers (UPDATED)
# =========================

def _valid_hex_color(s: str) -> bool:
    """Validate hex color string"""
    if not isinstance(s, str):
        return False
    s = s.strip()
    if len(s) != 7 or not s.startswith("#"):
        return False
    try:
        int(s[1:], 16)
        return True
    except Exception:
        return False

def resolve_theme(cfg: dict):
    """Resolve theme from config with overrides"""
    theme_cfg = cfg.get("theme", {}) if isinstance(cfg, dict) else {}
    preset_name = theme_cfg.get("preset", "Midnight")
    
    if preset_name not in THEME_PRESETS and preset_name != "Custom":
        preset_name = "Midnight"
    
    base = THEME_PRESETS.get(preset_name, THEME_PRESETS["Midnight"]).copy()

    overrides = theme_cfg.get("colors", {})
    if isinstance(overrides, dict):
        for k, v in overrides.items():
            if isinstance(v, str) and _valid_hex_color(v):
                base[k] = v

    return base, preset_name

def apply_theme(style: ttk.Style, root: tk.Tk, theme: dict):
    """Apply all theme settings to the UI"""
    bg = theme["bg"]
    panel = theme["panel"]
    panel2 = theme["panel2"]
    entry_bg = theme["entry_bg"]
    general_fg = theme["general_fg"]
    entry_fg = theme["entry_fg"]
    button_fg = theme["button_fg"]
    muted = theme["muted"]
    border = theme["border"]
    accent = theme["accent"]
    sel = theme["sel"]
    button_bg = theme.get("button_bg", accent)
    log_bg = theme.get("log_bg", entry_bg)
    log_fg = theme.get("log_fg", general_fg)

    # Root window background
    root.configure(bg=bg)

    # ===== Basic ttk styles =====
    style.configure(".", background=bg, foreground=general_fg)
    
    # Frames
    style.configure("TFrame", background=bg)
    style.configure("Card.TFrame", background=panel, borderwidth=1, relief="solid")
    style.configure("SubCard.TFrame", background=panel2, borderwidth=1, relief="solid")
    
    # Labels
    style.configure("TLabel", background=bg, foreground=general_fg)
    style.configure("Muted.TLabel", background=bg, foreground=muted)
    style.configure("Bold.TLabel", background=bg, foreground=general_fg, font=("Arial", 10, "bold"))
    
    # LabelFrames
    style.configure("TLabelframe", background=bg, foreground=general_fg, bordercolor=border)
    style.configure("TLabelframe.Label", background=bg, foreground=general_fg)
    
    # Separator
    style.configure("TSeparator", background=border)
    
    # Buttons
    style.configure("TButton", 
                   background=button_bg, 
                   foreground=button_fg, 
                   bordercolor=border,
                   focuscolor=sel,
                   padding=(12, 7))
    style.map("TButton",
              background=[("active", sel), ("pressed", sel), ("disabled", panel)],
              foreground=[("disabled", muted)])
    
    # Checkbuttons
    style.configure("TCheckbutton", 
                   background=bg, 
                   foreground=general_fg, 
                   padding=(6, 2))
    style.map("TCheckbutton",
              foreground=[("disabled", muted)],
              background=[("active", bg)])
    
    # Radiobuttons
    style.configure("TRadiobutton", 
                   background=bg, 
                   foreground=general_fg, 
                   padding=(6, 2))
    style.map("TRadiobutton",
              foreground=[("disabled", muted)],
              background=[("active", bg)])
    
    # Entries
    style.configure("TEntry", 
                   fieldbackground=entry_bg, 
                   foreground=entry_fg, 
                   background=bg, 
                   bordercolor=border, 
                   padding=(8, 6))
    style.map("TEntry",
              fieldbackground=[("disabled", panel), ("readonly", panel)],
              foreground=[("disabled", muted)])
    
    # Combobox
    style.configure("TCombobox", 
                   fieldbackground=entry_bg, 
                   foreground=entry_fg, 
                   background=bg, 
                   bordercolor=border,
                   padding=(8, 6))
    style.map("TCombobox",
              fieldbackground=[("disabled", panel), ("readonly", panel)],
              foreground=[("disabled", muted)])
    
    # Treeview
    style.configure("Treeview", 
                   background=panel, 
                   fieldbackground=panel, 
                   foreground=general_fg, 
                   bordercolor=border, 
                   rowheight=26)
    style.configure("Treeview.Heading", 
                   background=accent, 
                   foreground=general_fg, 
                   relief="flat",
                   bordercolor=border)
    style.map("Treeview", 
              background=[("selected", sel)], 
              foreground=[("selected", general_fg)])
    
    # Scrollbar
    style.configure("TScrollbar", 
                   background=panel2, 
                   bordercolor=border,
                   troughcolor=bg,
                   arrowcolor=general_fg)
    style.map("TScrollbar",
              background=[("active", sel), ("pressed", sel)])
    
    # Progressbar
    style.configure("TProgressbar", 
                   background=sel, 
                   troughcolor=panel)
    
    # Panedwindow
    style.configure("TPanedwindow", background=bg)
    
    # Notebook (tabs)
    style.configure("TNotebook", background=bg, bordercolor=border)
    style.configure("TNotebook.Tab", 
                   background=panel, 
                   foreground=general_fg, 
                   bordercolor=border,
                   padding=(12, 4))
    style.map("TNotebook.Tab",
              background=[("selected", panel2), ("active", accent)],
              foreground=[("selected", general_fg)])

def apply_theme_to_widgets(root: tk.Tk, theme: dict):
    """Recursively apply theme colors to all widgets that don't use ttk styles"""
    def apply_to_widget(widget):
        # Get widget class
        w_class = widget.winfo_class()
        
        # Apply based on widget type
        if w_class in ("Frame", "Labelframe", "Toplevel"):
            try:
                widget.configure(bg=theme["bg"])
            except:
                pass
            
        elif w_class == "Canvas":
            try:
                widget.configure(bg=theme["bg"], highlightbackground=theme["border"])
            except:
                pass
            
        elif w_class == "Text":
            try:
                widget.configure(
                    bg=theme.get("log_bg", theme["entry_bg"]),
                    fg=theme.get("log_fg", theme["general_fg"]),
                    insertbackground=theme.get("log_fg", theme["general_fg"]),
                    highlightbackground=theme["border"],
                    highlightcolor=theme["sel"]
                )
            except:
                pass
            
        elif w_class == "Listbox":
            try:
                widget.configure(
                    bg=theme["entry_bg"],
                    fg=theme["entry_fg"],
                    selectbackground=theme["sel"],
                    selectforeground=theme["general_fg"],
                    highlightbackground=theme["border"]
                )
            except:
                pass
            
        elif w_class == "Menu":
            try:
                widget.configure(
                    bg=theme["panel"],
                    fg=theme["general_fg"],
                    activebackground=theme["sel"],
                    activeforeground=theme["general_fg"]
                )
            except:
                pass
        
        # Recursively process children
        try:
            for child in widget.winfo_children():
                apply_to_widget(child)
        except:
            pass
    
    apply_to_widget(root)

def apply_channel_styles(style: ttk.Style, cfg: dict, theme: dict):
    """Apply per-channel styles"""
    pc = (((cfg or {}).get("theme", {}) or {}).get("per_channel", []))
    if not isinstance(pc, list):
        pc = []

    for i in range(16):
        ch = pc[i] if i < len(pc) and isinstance(pc[i], dict) else {}

        general_fg = ch.get("general_fg") if _valid_hex_color(str(ch.get("general_fg"))) else theme["general_fg"]
        entry_bg = ch.get("entry_bg") if _valid_hex_color(str(ch.get("entry_bg"))) else theme["entry_bg"]
        entry_fg = ch.get("entry_fg") if _valid_hex_color(str(ch.get("entry_fg"))) else theme["entry_fg"]
        btn_bg = ch.get("button_bg") if _valid_hex_color(str(ch.get("button_bg"))) else theme.get("button_bg", theme["accent"])
        btn_fg = ch.get("button_fg") if _valid_hex_color(str(ch.get("button_fg"))) else theme["button_fg"]

        # Channel-specific label style
        style.configure(f"Ch{i}.TLabel", 
                       background=theme["bg"], 
                       foreground=general_fg)
        
        # Channel-specific entry style
        style.configure(f"Ch{i}.TEntry", 
                       fieldbackground=entry_bg, 
                       foreground=entry_fg, 
                       background=theme["bg"], 
                       bordercolor=theme["border"], 
                       padding=(8, 6))
        style.map(f"Ch{i}.TEntry",
                 fieldbackground=[("disabled", theme["panel"]), ("readonly", theme["panel"])],
                 foreground=[("disabled", theme["muted"])])
        
        # Channel-specific button style
        style.configure(f"Ch{i}.TButton", 
                       background=btn_bg, 
                       foreground=btn_fg, 
                       bordercolor=theme["border"], 
                       padding=(10, 6))
        style.map(f"Ch{i}.TButton",
                 background=[("active", theme["sel"]), ("pressed", theme["sel"]), ("disabled", theme["panel"])],
                 foreground=[("disabled", theme["muted"])])
        
        # Channel-specific checkbutton style
        style.configure(f"Ch{i}.TCheckbutton", 
                       background=theme["bg"], 
                       foreground=general_fg, 
                       padding=(6, 2))
        style.map(f"Ch{i}.TCheckbutton",
                 foreground=[("disabled", theme["muted"])],
                 background=[("active", theme["bg"])])
        
        # Channel-specific radiobutton style
        style.configure(f"Ch{i}.TRadiobutton", 
                       background=theme["bg"], 
                       foreground=general_fg, 
                       padding=(6, 2))
        style.map(f"Ch{i}.TRadiobutton",
                 foreground=[("disabled", theme["muted"])],
                 background=[("active", theme["bg"])])
        
        # Channel-specific labelframe style
        style.configure(f"Ch{i}.TLabelframe", 
                       background=theme["bg"], 
                       foreground=general_fg, 
                       bordercolor=theme["border"])
        style.configure(f"Ch{i}.TLabelframe.Label", 
                       background=theme["bg"], 
                       foreground=general_fg)

MeetingTranscriber

Meeting intelligence and transcript extraction toolkit
Released-Updates Coming

This project turns long meeting recordings into structured transcripts and searchable summaries for quicker follow-up execution.

Important decisions in meetings are frequently missed or forgotten because notes are incomplete and scattered.

Process audio into transcript blocks, extract speaker-level insights, and support exports for downstream workflows.

Improving speaker segmentation accuracy and speeding up large-file processing.

Teams leave meetings with cleaner records and faster action-item capture.

AI Transcription Workflow

Project Code Snippets

1 file
sample.txt
Text - Audio and Speech Processing - 10.22 KB
import os
import sys
import re
import json
import math
import queue
import shutil
import tempfile
import threading
import subprocess
from datetime import datetime
from difflib import SequenceMatcher


import multiprocessing
if __name__ == "__main__":
    multiprocessing.freeze_support()

import tkinter as tk
from tkinter import filedialog, messagebox, scrolledtext, ttk


def is_frozen():
    return getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS')


if is_frozen():
    os.environ['PYTHONUNBUFFERED'] = '1'
    
    if sys.platform == "win32":
        import subprocess
        subprocess.CREATE_NO_WINDOW = 0x08000000

# ------------------ CONFIG / PATHS ------------------ #

BASE_DIR = os.path.dirname(os.path.abspath(__file__))


if is_frozen():
    APP_DIR = os.path.dirname(sys.executable)
else:
    APP_DIR = BASE_DIR

CACHE_DIR = os.path.join(APP_DIR, "cache")
CONFIG_PATH = os.path.join(APP_DIR, "config.json")

os.makedirs(CACHE_DIR, exist_ok=True)

DEFAULT_CONFIG = {
    # ---- Export/UI ----
    "default_format": "txt",              
    "dir_mode": "dialog",                 
    "custom_dir": "",
    "default_filename": "",
    "theme": "dark",                      
    "wrap_width": 860,
    "text_size": 10,
    "segment_spacing": 2,

    # ---- Live preview ----
    "live_chunk_seconds": 10.0,
    "live_asr_model": "small",            
    "live_language": "en",                

    # ---- File ASR ----
    "asr_model": "large-v2",              
    "asr_language": "auto",               
    "compute_type_cuda": "float16",
    "compute_type_cpu": "float32",

    # ---- Alignment ----
    "align_enabled": True,

    # ---- Segment filtering (feature extraction) ----
    "segment_min_seconds": 0.25,          

    # ---- Feature diarization ----
    "diarization_enabled": True,
    "cluster_min": 2,
    "cluster_max": 8,
    "cluster_divisor": 6,                 
    "yin_fmin": 80.0,
    "yin_fmax": 400.0,

    # ---- Cleanup repetitions ----
    "cleanup_enabled": True,
    "cleanup_overlap_min_words": 3,
    "cleanup_overlap_max_check_words": 24,
    "cleanup_duplicate_ratio": 0.94,
    "cleanup_exact_dup_time_eps": 0.05,
    "cleanup_keep_cross_speaker_overlap": False,

    # ---- Safety / display (optional) ----
    "censor_insights": True,              
}

AUDIO_EXTS = {".wav", ".mp3", ".m4a", ".flac", ".ogg", ".aac", ".wma"}
VIDEO_EXTS = {".mp4", ".mkv", ".mov", ".avi", ".wmv"}

ADDRESS_PATTERN = re.compile(
    r"\b\d{1,6}\s+[A-Za-z0-9.\s]+?"
    r"(Street|St\.?|Road|Rd\.?|Avenue|Ave\.?|Boulevard|Blvd\.?|"
    r"Lane|Ln\.?|Drive|Dr\.?|Court|Ct\.?|Way|Highway|Hwy\.?)\b",
    re.IGNORECASE,
)

_CENSOR_PATTERNS = [
    re.compile(r"\bnigg[aer]s?\b", re.IGNORECASE),
    re.compile(r"\bfag+ot?\b", re.IGNORECASE),
    re.compile(r"\bretard(ed|s)?\b", re.IGNORECASE),
]
def _sanitize_for_insights(s: str) -> str:
    s = s or ""
    for rx in _CENSOR_PATTERNS:
        s = rx.sub("[censored]", s)
    return s

if not os.path.exists(CONFIG_PATH):
    with open(CONFIG_PATH, "w", encoding="utf-8") as f:
        json.dump(DEFAULT_CONFIG, f, indent=2)


def load_config():
    if not os.path.exists(CONFIG_PATH):
        return DEFAULT_CONFIG.copy()
    try:
        with open(CONFIG_PATH, "r", encoding="utf-8") as f:
            data = json.load(f)
        cfg = DEFAULT_CONFIG.copy()
        cfg.update(data or {})
        return cfg
    except Exception:
        return DEFAULT_CONFIG.copy()


def save_config(cfg):
    try:
        with open(CONFIG_PATH, "w", encoding="utf-8") as f:
            json.dump(cfg, f, indent=2)
    except Exception:
        pass


def ensure_app_paths():
    os.makedirs(APP_DIR, exist_ok=True)
    os.makedirs(CACHE_DIR, exist_ok=True)


def bootstrap_config():
    """
    Guarantee CONFIG_PATH exists on disk and contains all DEFAULT_CONFIG keys.
    - If missing: write DEFAULT_CONFIG
    - If present: merge in missing keys and write back
    Returns: merged config dict
    """
    ensure_app_paths()

    if not os.path.exists(CONFIG_PATH):
        cfg = DEFAULT_CONFIG.copy()
        save_config(cfg)
        return cfg

    try:
        with open(CONFIG_PATH, "r", encoding="utf-8") as f:
            data = json.load(f) or {}
    except Exception:
        data = {}

    cfg = DEFAULT_CONFIG.copy()
    if isinstance(data, dict):
        cfg.update(data)

    save_config(cfg)
    return cfg


def preflight_runtime(log_cb=None):
    """
    Ensure all required runtime assets exist:
    - app dirs + config.json
    - python deps
    - spaCy model
    """
    def log(s):
        if log_cb:
            log_cb(s)

    bootstrap_config()
    log(f"App dir: {APP_DIR}")
    log(f"Cache dir: {CACHE_DIR}")
    log(f"Config: {CONFIG_PATH}")

    
    if not is_frozen():
        ensure_python_deps(progress=log)
        ensure_spacy_model(progress=log)
    else:
        log("Running in frozen mode - skipping dependency installation")
        
        try:
            init_nlp()
            log("NLP initialized successfully")
        except Exception as e:
            log(f"Warning: Could not initialize NLP: {e}")


def get_config_fresh():
    return load_config()


def cfg_get(cfg, key, default):
    if not isinstance(cfg, dict):
        return default
    v = cfg.get(key, default)
    return default if v is None else v


# ------------------ RUNTIME CHECKS ------------------ #


def _run(cmd, timeout=120):
    
    if sys.platform == "win32" and is_frozen():
        
        creationflags = 0x08000000
    else:
        creationflags = 0
    
    return subprocess.run(
        cmd, 
        check=False, 
        capture_output=True, 
        text=True, 
        timeout=timeout,
        creationflags=creationflags
    )


def ensure_python_deps(progress=None):
    
    if is_frozen():
        if progress:
            progress("Dependencies: skipped (frozen build)")
        return
    
    def log(s):
        if progress:
            progress(s)

    reqs = [
        ("numpy", "numpy"),
        ("torch", "torch"),
        ("torchaudio", "torchaudio"),
        ("sounddevice", "sounddevice"),
        ("soundfile", "soundfile"),
        ("moviepy", "moviepy"),
        ("librosa", "librosa"),
        ("sklearn", "scikit-learn"),
        ("spacy", "spacy"),
        ("whisperx", "whisperx"),
        ("speechbrain", "speechbrain"),
    ]

    import importlib
    missing = []
    for import_name, pip_name in reqs:
        try:
            importlib.import_module(import_name)
        except Exception:
            missing.append((import_name, pip_name))

    if not missing:
        log("Dependencies: OK")
        return

    log("Dependencies missing: " + ", ".join([m[1] for m in missing]))
    log("Attempting pip install…")

    def in_venv():
        return (
            getattr(sys, "base_prefix", sys.prefix) != sys.prefix
            or bool(os.environ.get("VIRTUAL_ENV"))
            or bool(os.environ.get("CONDA_PREFIX"))
        )

    py = sys.executable
    use_user = not in_venv()

    install_attempts = []
    if use_user:
        install_attempts.append(["-m", "pip", "install", "--upgrade", "--user"])
        install_attempts.append(["-m", "pip", "install", "--upgrade"])
    else:
        install_attempts.append(["-m", "pip", "install", "--upgrade"])
        install_attempts.append(["-m", "pip", "install", "--upgrade", "--user"])

    last_err = None
    for flags in install_attempts:
        try:
            for _, pip_name in missing:
                cmd = [py] + flags + [pip_name]
                r = _run(cmd, timeout=900)
                if r.returncode != 0:
                    raise RuntimeError(r.stderr.strip() or r.stdout.strip() or f"pip failed: {pip_name}")
                log(f"Installed: {pip_name}")
            log("Dependencies install: done")
            return
        except Exception as e:
            last_err = e
            log(f"pip attempt failed ({' '.join(flags)}): {e}")

    raise RuntimeError(f"Failed to install dependencies via pip: {last_err}")


def ensure_spacy_model(progress=None):
    
    if is_frozen():
        if progress:
            progress("spaCy model: using frozen bundle")
        return
        
    def log(s):
        if progress:
            progress(s)

    import spacy
    try:
        spacy.load("en_core_web_sm")
        log("spaCy model: OK (en_core_web_sm)")
        return
    except Exception:
        pass

    log("spaCy model missing: en_core_web_sm. Downloading…")
    
    py = sys.executable
    r = _run([py, "-m", "spacy", "download", "en_core_web_sm"], timeout=900)
    
    if r.returncode != 0:
        raise RuntimeError(r.stderr.strip() or r.stdout.strip() or "spaCy download failed")
    log("spaCy model download: done")


def ensure_ffmpeg(progress=None):
    """
    You need ffmpeg for WMA conversion and some video audio extraction.
    Best-effort install on Windows if winget/choco exists.
    """
    def log(s):
        if progress:
            progress(s)

    if shutil.which("ffmpeg"):
        log("ffmpeg: OK")
        return

    
    if is_frozen():
        log("ffmpeg not found in PATH. Please install ffmpeg manually.")
        return

    log("ffmpeg not found in PATH. Attempting install…")

    if os.name == "nt":
        if shutil.which("winget"):
            r = _run(["winget", "install", "-e", "--id", "Gyan.FFmpeg"], timeout=900)
            if r.returncode == 0 and shutil.which("ffmpeg"):
                log("ffmpeg installed via winget")
                return
        if shutil.which("choco"):
            r = _run(["choco", "install", "-y", "ffmpeg"], timeout=900)
            if r.returncode == 0 and shutil.which("ffmpeg"):
                log("ffmpeg installed via choco")
                return

    
    if is_frozen():
        log("ffmpeg not found. Some features may not work.")
    else:
        raise RuntimeError(
            "ffmpeg not found. Install ffmpeg and ensure it's on PATH.\n"
            "Windows options: winget install -e --id Gyan.FFmpeg  OR  choco install ffmpeg"
        )