give me the complete code with updates: heres code: # Third party imports
import os
import json
import time
import tomlkit
import hashlib
import numpy as np
from scipy.io import wavfile
from pydub import AudioSegment
from pydub.silence import detect_nonsilent
import cv2
from moviepy.editor import VideoFileClip, concatenate_videoclips
from utils.console import print_step, print_substep

class VideoCompiler:
    def __init__(self):
        self.config = self.load_config()
        self.config_hash = self.get_config_hash()
        self.results_folder = self.config['paths']['source_folder']
        self.merge_folder = self.config['paths']['merge_folder'] 
        self.target_duration = self.config['merger']['target_duration'] * 60  # convert to seconds
        self.subreddit_durations = {}
        self.processed_videos_file = "processed_videos.json"
        
        os.makedirs(self.merge_folder, exist_ok=True)
        print_step(f"Looking for videos in: {self.results_folder}")
        print_step(f"Current mode: {self.config['merger']['mode']}")
        print_step(f"Current status: {self.config['merger']['status']}")

        self.processed_videos = self.load_processed_videos()

    def get_config_hash(self):
        """Get hash of current config file to detect changes"""
        try:
            with open("config.toml", "rb") as f:
                return hashlib.md5(f.read()).hexdigest()
        except FileNotFoundError:
            return None

    def check_config_changed(self):
        """Check if config file has been modified"""
        try:
            current_hash = self.get_config_hash()
            if current_hash != self.config_hash:
                print_step("Config file changed! Reloading settings...")
                
                # Load new config
                with open("config.toml", "r", encoding="utf-8") as f:
                    new_config = tomlkit.load(f)
                
                # Update config and hash
                self.config = new_config
                self.config_hash = current_hash
                self.results_folder = new_config['paths']['source_folder']
                self.merge_folder = new_config['paths']['merge_folder']
                self.target_duration = new_config['merger']['target_duration'] * 60
                
                # Print all updated settings
                print_step(f"New settings loaded:")
                print_substep(f"Mode: {new_config['merger']['mode']}")
                print_substep(f"Status: {new_config['merger']['status']}")
                print_substep(f"Target duration: {new_config['merger']['target_duration']} minutes")
                print_substep(f"Check interval: {new_config['merger']['check_interval']} minutes")
                
                return True
            return False
        except Exception as e:
            print_substep(f"Error reloading config: {str(e)}")
            return False

    def check_status(self):
        """Check current processing status"""
        self.check_config_changed()
        status = self.config['merger']['status']

        if status == "paused":
            print_step("Processing is paused. Waiting for resume...")
            while self.config['merger']['status'] == "paused":
                time.sleep(10)  # Check every 10 seconds
                self.check_config_changed()
            print_step("Processing resumed!")
            return True
        elif status == "stopping":
            print_step("Stop signal received. Finishing current task...")
            return False
        return True

    def wait_with_status_check(self, minutes):
        """Wait specified minutes while checking for status changes"""
        print_step(f"Waiting {minutes} minutes before next check...")
        for _ in range(minutes):
            if not self.check_status():  # Stop if requested
                return False
            time.sleep(60)  # Sleep for 1 minute
            if self.check_config_changed():
                print_step("Config changed during wait. Starting new cycle...")
                return True
        return True

    def load_processed_videos(self):
        """Load list of already processed videos"""
        try:
            if os.path.exists(self.processed_videos_file):
                with open(self.processed_videos_file, 'r') as f:
                    return json.load(f)
            return []
        except Exception as e:
            print_substep(f"Error loading processed videos: {str(e)}")
            return []

    def save_processed_videos(self):
        """Save list of processed videos"""
        try:
            with open(self.processed_videos_file, 'w') as f:
                json.dump(self.processed_videos, f)
        except Exception as e:
            print_substep(f"Error saving processed videos: {str(e)}")

    def load_config(self):
        """Load configuration from toml file"""
        try:
            with open("config.toml", "r", encoding="utf-8") as f:
                return tomlkit.load(f)
        except FileNotFoundError:
            print_step("Config file not found. Creating default config...")
            self.create_default_config()
            return self.load_config()

    def create_default_config(self):
        """Create default configuration file"""
        doc = tomlkit.document()
        
        merger = tomlkit.table()
        merger.add("status", "running")
        merger.add("mode", "fastest")
        merger.add("specific_subreddit", "AmItheAsshole+relationship_advice+confession+tifu")
        merger.add("check_interval", 3)
        merger.add("target_duration", 10)
        merger.add("auto_process", True)
        doc.add("merger", merger)
        
        paths = tomlkit.table()
        paths.add("source_folder", r"C:\Users\buggy\Desktop\FullyAutomatedRedditVideoMakerBot-main\results")
        paths.add("merge_folder", "MergeVideo")
        doc.add("paths", paths)
        
        processing = tomlkit.table()
        processing.add("delete_source", False)
        processing.add("output_format", "mp4")
        doc.add("processing", processing)

        splicing = tomlkit.table()
        splicing.add("min_silence_len", 500)
        splicing.add("silence_thresh", -40)
        splicing.add("buffer_time", 0.5)
        doc.add("splicing", splicing)
        
        with open("config.toml", "w", encoding="utf-8") as f:
            f.write(tomlkit.dumps(doc))

    def get_video_duration(self, video_path):
        """Get duration of video in seconds"""
        try:
            with VideoFileClip(video_path) as video:
                duration = video.duration
                print_substep(f"Duration of {os.path.basename(video_path)}: {duration:.2f} seconds")
                return duration
        except Exception as e:
            print_substep(f"Error getting duration: {str(e)}")
            return 0

    def detect_title_end(self, video_clip):
        """Detect when title reading ends based on audio analysis"""
        try:
            # Extract audio
            audio = video_clip.audio
            if audio is None:
                print_substep("No audio found in video, using default time")
                return 5.0

            # Get the raw audio data and ensure it's properly formatted
            try:
                audio_array = audio.to_soundarray(fps=22050)  # Specify fps to ensure consistent format
                
                # Handle stereo audio
                if len(audio_array.shape) == 2:
                    audio_array = np.mean(audio_array, axis=1)  # Convert to mono
            except:
                print_substep("Could not process audio, using default time")
                return 5.0

            # Simple volume-based silence detection
            chunk_duration = 0.5  # half second chunks
            chunk_size = int(22050 * chunk_duration)
            
            for i in range(0, len(audio_array), chunk_size):
                chunk = audio_array[i:min(i + chunk_size, len(audio_array))]
                volume = np.abs(chunk).mean()
                
                if volume < 0.01:  # Silence threshold
                    title_end = (i / 22050) + 0.5  # Add buffer
                    if title_end < 2.0:  # Too short, probably false detection
                        continue
                    if title_end > 10.0:  # Too long, use default
                        return 5.0
                    return title_end

            return 5.0  # Default if no clear silence found

        except Exception as e:
            print_substep(f"Error in audio analysis: {str(e)}")
            return 5.0

    def find_video_pairs(self, subreddit_folder):
        """Find matching pairs of videos (_out.mp4 and .mp4) in a subreddit folder"""
        try:
            folder_path = os.path.join(self.results_folder, subreddit_folder)
            print_step(f"Scanning folder: {folder_path}")

            if not os.path.exists(folder_path):
                print_substep(f"Folder not found: {folder_path}")
                return {}, 0

            video_pairs = {}
            total_duration = 0

            # First find all subtitled versions (_out.mp4) and get their durations
            for file in os.listdir(folder_path):
                if file.endswith('_out.mp4'):  # Only look for subtitled versions
                    base_name = file.replace('_out.mp4', '')

                    if base_name not in video_pairs:
                        video_pairs[base_name] = {
                            'normal': None,
                            'subtitled': None,
                            'duration': 0
                        }

                    full_path = os.path.join(folder_path, file)
                    video_pairs[base_name]['subtitled'] = full_path
                    duration = self.get_video_duration(full_path)
                    video_pairs[base_name]['duration'] = duration
                    total_duration += duration

            # Now find matching normal versions
            for file in os.listdir(folder_path):
                if file.endswith('.mp4') and not file.endswith('_out.mp4'):
                    base_name = file.replace('.mp4', '')
                    if base_name in video_pairs:
                        video_pairs[base_name]['normal'] = os.path.join(folder_path, file)

            # Print found pairs and total duration
            print_step(f"Found video pairs in {subreddit_folder}:")
            complete_pairs = 0
            for base_name, pair in video_pairs.items():
                if pair['normal'] and pair['subtitled']:
                    complete_pairs += 1
                    print_substep(f"Pair {complete_pairs}: {base_name}")
                    print_substep(f" Normal: {os.path.basename(pair['normal'])}")
                    print_substep(f" Subtitled: {os.path.basename(pair['subtitled'])}")
                    print_substep(f" Duration: {pair['duration']:.2f} seconds")

            print_substep(f"Total complete pairs: {complete_pairs}")
            print_substep(f"Total duration: {total_duration:.2f} seconds")

            return video_pairs, total_duration

        except Exception as e:
            print_substep(f"Error scanning folder: {str(e)}")
            return {}, 0

    def find_best_combination(self):
        """Find best combination of videos for target duration"""
        available_pairs = []
        total_duration = 0

        # Print target duration clearly at start
        target_minutes = self.target_duration / 60
        print_step(f"\nTarget Duration: {target_minutes:.1f} minutes ({self.target_duration:.0f} seconds)")

        for subreddit in os.listdir(self.results_folder):
            if os.path.isdir(os.path.join(self.results_folder, subreddit)):
                pairs, _ = self.find_video_pairs(subreddit)
                for name, pair in pairs.items():
                    if pair['normal'] and pair['subtitled']:
                        if name not in self.processed_videos:
                            available_pairs.append({
                                'name': name,
                                'subreddit': subreddit,
                                'duration': pair['duration'],
                                'files': pair
                            })

        if not available_pairs:
            print_step("No unprocessed videos found!")
            return None

        # Sort by duration (longest first)
        available_pairs.sort(key=lambda x: x['duration'], reverse=True)
        
        selected_pairs = []
        current_duration = 0

        # Add videos until we exceed target duration
        for pair in available_pairs:
            selected_pairs.append(pair)
            current_duration += pair['duration']
            print_substep(f"\nAdded: {pair['name']} ({pair['duration']:.2f}s) from {pair['subreddit']}")
            print_substep(f"Current total duration: {current_duration:.2f}s")
            
            if current_duration >= self.target_duration:
                print_substep("Reached or exceeded target duration")
                break

        if selected_pairs:
            print_substep(f"\nFinal compilation stats:")
            print_substep(f"Duration: {current_duration:.2f}s ({current_duration/60:.1f} minutes)")
            return selected_pairs

        return None

    def splice_video(self, video_pair):
        """Create spliced version with clean intro and main content"""
        try:
            print_substep(f"Processing video: {video_pair['name']}")
            
            clean_video = VideoFileClip(video_pair['files']['normal'])
            subtitled_video = VideoFileClip(video_pair['files']['subtitled'])
            
            if clean_video.duration == 0 or subtitled_video.duration == 0:
                print_substep("Invalid video duration")
                return None
            
            # Get title end time
            title_end = min(5.0, clean_video.duration - 1)  # Default to 5 seconds or less
            print_substep(f"Using title duration: {title_end:.2f}s")
            
            # Create clips
            intro = clean_video.subclip(0, title_end)
            main_content = subtitled_video.subclip(title_end)
            
            final = concatenate_videoclips([intro, main_content], method="compose")
            
            clean_video.close()
            subtitled_video.close()
            
            return final
        
        except Exception as e:
            print_substep(f"Error in video splicing: {str(e)}")
            return None

    def merge_videos(self, selected_videos):
        """Merge selected videos with proper splicing"""
        processed_clips = []
        try:
            for video in selected_videos:
                clip = self.splice_video(video)
                if clip is not None:
                    processed_clips.append(clip)
                    print_substep(f"Successfully processed: {video['name']}")
            
            if not processed_clips:
                print_step("No valid clips to merge")
                return None
            
            print_step(f"Merging {len(processed_clips)} clips...")
            final = concatenate_videoclips(processed_clips, method="compose")
            
            output_path = os.path.join(
                self.merge_folder,
                f"compilation_{time.strftime('%Y%m%d_%H%M%S')}.mp4"
            )
            
            print_step("Writing final video...")
            final.write_videofile(
                output_path,
                codec='libx264',
                audio_codec='aac',
                temp_audiofile='temp-audio.m4a',
                remove_temp=True,
                threads=4
            )
            
            for video in selected_videos:
                self.processed_videos.append(video['name'])
            self.save_processed_videos()
            
            return output_path
        
        except Exception as e:
            print_step(f"Error in video merging: {str(e)}")
            return None
        
        finally:
            for clip in processed_clips:
                try:
                    clip.close()
                except:
                    pass

    def process_specific_folder(self):
        """Process videos from specific folder"""
        subreddit = self.config['merger']['specific_subreddit']
        print_step(f"Processing specific folder: {subreddit}")

        pairs, duration = self.find_video_pairs(subreddit)
        if not pairs:
            print_step("No videos found in specified folder!")
            return None

        selected_pairs = []
        current_duration = 0

        for name, pair in pairs.items():
            if name not in self.processed_videos and pair['normal'] and pair['subtitled']:
                if current_duration + pair['duration'] <= self.target_duration:
                    selected_pairs.append({
                        'name': name,
                        'subreddit': subreddit,
                        'duration': pair['duration'],
                        'files': pair
                    })
                    current_duration += pair['duration']

        if selected_pairs:
            for pair in selected_pairs:
                self.processed_videos.append(pair['name'])
            self.save_processed_videos()

        return selected_pairs

    def process_videos(self):
        """Process videos based on current mode"""
        if self.check_config_changed():
            if self.config['processing'].get('safe_mode', True):
                print_step("Config changed during operation. Restarting process...")
                return None

        mode = self.config['merger']['mode']
        if mode == "fastest":
            return self.find_best_combination()
        elif mode == "specific":
            return self.process_specific_folder()
        else:
            print_step(f"Unknown mode: {mode}")
            return None

def main():
    compiler = VideoCompiler()
    print_step("Monitoring for config changes...")
    print_substep("You can modify config.toml at any time to change settings")
    print_substep("Set status to 'paused' to pause, 'running' to resume, or 'stopping' to stop")

    first_run = True

    while True:
        try:
            # Check status first
            if not compiler.check_status():
                print_step("Stopping process...")
                break

            # Check if auto-processing is still enabled
            compiler.check_config_changed()
            if not compiler.config['merger']['auto_process']:
                print_step("Auto-processing disabled in config. Exiting.")
                break

            selected_videos = compiler.process_videos()

            if selected_videos:
                print_step("\nVideos Selected for Compilation:")
                total_duration = 0
                for video in selected_videos:
                    print_substep(f"Video: {video['name']}")
                    print_substep(f"From: {video['subreddit']}")
                    print_substep(f"Duration: {video['duration']:.2f} seconds")
                    print_substep("-" * 50)
                    total_duration += video['duration']

                if total_duration < compiler.target_duration:
                    print_step(f"Still need {(compiler.target_duration - total_duration):.2f}s to reach target")
                
                print_step(f"Total Compilation Duration: {total_duration:.2f} seconds")

                # Process the selected videos
                output_path = compiler.merge_videos(selected_videos)
                if output_path:
                    print_step(f"Successfully created compilation: {output_path}")
                    # Save processed videos only after successful compilation
                    for video in selected_videos:
                        compiler.processed_videos.append(video['name'])
                    compiler.save_processed_videos()

                # Only wait if not first run
                if not first_run:
                    check_interval = compiler.config['merger']['check_interval']
                    if not compiler.wait_with_status_check(check_interval):
                        break
                else:
                    first_run = False
            else:
                print_step("Not enough unprocessed videos for compilation.")
                check_interval = compiler.config['merger']['check_interval']
                if not compiler.wait_with_status_check(check_interval):
                    break

        except Exception as e:
            print_step(f"Error occurred: {str(e)}")
            print_step("Waiting 5 minutes before retrying...")
            if not compiler.wait_with_status_check(5):
                break

if __name__ == "__main__":
    main(), heres update, import cv2
import numpy as np

def detect_template_change(video_path):
    """Detect when the title template disappears"""
    cap = cv2.VideoCapture(video_path)
    fps = cap.get(cv2.CAP_PROP_FPS)
    
    # Get first frame (which has title)
    ret, first_frame = cap.read()
    if not ret:
        return 5.0  # Default fallback
    
    # Convert to grayscale
    prev_frame = cv2.cvtColor(first_frame, cv2.COLOR_BGR2GRAY)
    frame_count = 0
    
    while True:
        ret, frame = cap.read()
        if not ret:
            break
            
        # Convert current frame to grayscale
        curr_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        
        # Calculate difference between frames
        diff = cv2.absdiff(curr_frame, prev_frame)
        
        # If we detect a significant change
        if np.mean(diff) > 50:  # Threshold value - might need adjustment
            # Convert frame count to seconds
            time_point = frame_count / fps
            print_substep(f"Detected template change at: {time_point:.2f}s")
            cap.release()
            return time_point
            
        prev_frame = curr_frame
        frame_count += 1
    
    cap.release()
    return 5.0  # Default fallback