import os
import subprocess
import numpy as np
import uuid
from moviepy.editor import VideoFileClip
from scipy.io import wavfile

# Define video file extensions and the output folder
video_extensions = ['.mp4', '.mkv', '.wmv', '.avi']
output_folder = 'Output'

def calculate_loudness(audio_data, rate):
    """Calculate loudness using ITU-R BS.1770-4 method."""
    if audio_data.ndim == 2:
        audio_data = np.mean(audio_data, axis=1)  # convert to mono by averaging channels
    # Normalization constants for the ITU-R BS.1770-4 method
    pre_filter = np.array([1.53512485958697, -2.69169618940638, 1.19839281085285])
    pre_gain = 1.0
    power = 10.0
    # Pre-filter
    audio_data = np.convolve(audio_data, pre_filter) * pre_gain
    # Calculate power and then LUFS
    mean_square = np.mean(audio_data ** 2)
    mean_power = mean_square ** (0.5)
    loudness = (1 / rate) * np.sum(mean_power ** power)
    lufs = -0.691 + 10 * np.log10(loudness)
    return lufs

def find_loudest_segments(audio_data, rate, num_segments, segment_duration, video_duration, starting_offset, ending_offset):
    """Find the segments with the highest loudness in the audio data."""
    # Calculate the loudness of each segment in the audio file
    segment_loudness = [
        calculate_loudness(
            audio_data[max(0, i * int(rate * segment_duration)) : min(len(audio_data), (i + 1) * int(rate * segment_duration))], rate)
        for i in range(0, int(video_duration / segment_duration))
    ]
    
    # Combine segment start times with their corresponding loudness
    loudness_and_segments = [
        (segment_loudness[i], i * segment_duration)
        for i in range(len(segment_loudness))
        if i * segment_duration >= starting_offset and (i + 1) * segment_duration <= video_duration - ending_offset
    ]
    
    # Sort the loudness and segments based on loudness
    loudness_and_segments.sort(key=lambda x: x[0], reverse=True)
    
    # Return the top num_segments segments sorted by loudness
    return loudness_and_segments[:num_segments]
    
def sort_segments(segments_with_loudness, preference):
    if preference == 1:
        return sorted(segments_with_loudness, key=lambda x: x[1])
    elif preference == 2:
        return sorted(segments_with_loudness, key=lambda x: x[1], reverse=True)
    elif preference == 3:
        return sorted(segments_with_loudness, key=lambda x: x[0])
    elif preference == 4:
        return sorted(segments_with_loudness, key=lambda x: x[0], reverse=True)

def extract_segment(video_path, start_time, duration, output_path):
    """Use ffmpeg to extract a segment from a video with x264 encoding."""
    command = [
        "ffmpeg",
        "-y",                       # Overwrite output files without asking
        "-ss", str(start_time),     # Start time
        "-i", video_path,           # Input file
        "-t", str(duration),        # Duration
        "-c:v", "libx264",          # Use x264 video codec
        "-c:a", "aac",              # AAC audio codec
        "-strict", "experimental",  # Allow experimental codecs (if necessary for AAC)
        output_path                 # Output path
    ]
    try:
        subprocess.run(command, check=True, stderr=subprocess.PIPE)
    except subprocess.CalledProcessError as e:
        print(f"ffmpeg command failed: {e.stderr.decode()}")

def process_video_file(video_path, starting_offset, ending_offset, num_segments, segment_duration, preference):
    """Process a single video file to find and extract the loudest segments."""
    unique_id = str(uuid.uuid4())
    audio_filename = f'temp_audio_{unique_id}.wav'
    try:
        video_clip = VideoFileClip(video_path)
        video_duration = video_clip.duration

        video_clip.audio.write_audiofile(audio_filename)
        rate, audio_data = wavfile.read(audio_filename)

        segments_with_loudness = find_loudest_segments(audio_data, rate, num_segments, segment_duration, video_duration, starting_offset, ending_offset)

        # Trier les segments en fonction de la préférence de l'utilisateur
        sorted_segments = sort_segments(segments_with_loudness, preference)
        
        # Anticiper si les noms de fichiers doivent correspondre à l'ordre initial
        if preference in [1, 2]:
            initial_order_index = {start_time: i for i, (loudness, start_time) in enumerate(segments_with_loudness)}

        if not os.path.exists(output_folder):
            os.makedirs(output_folder)

        for segment in sorted_segments:
            loudness, start_time = segment
            segment_index = initial_order_index[start_time] if preference in [1, 2] else sorted_segments.index(segment)
            output_filename = f"{os.path.splitext(os.path.basename(video_path))[0]}_segment{segment_index+1}.mp4"
            output_path = os.path.join(output_folder, output_filename)
            extract_segment(video_path, start_time, segment_duration, output_path)
            print(f"Extracted segment to {output_path}")

    finally:
        if os.path.exists(audio_filename):
            os.unlink(audio_filename)  # Clean up the temporary file

# Main
if __name__ == "__main__":
    starting_offset = float(input("Start offset (sec)? "))
    ending_offset = float(input("End offset (sec)? "))
    num_segments = int(input("How many segments to extract? "))
    segment_duration = float(input("Duration of each segment (sec)? "))
    print("Choose your sorting preference for the extracted files:")
    print("1 - In order of appearance in the video")
    print("2 - In reverse order of appearance")
    print("3 - By increasing loudness")
    print("4 - By decreasing loudness")
    preference = int(input("Enter the number of your preference: "))
    # Validate the user's preference
    if preference not in [1, 2, 3, 4]:
        print("Invalid preference. Please enter a valid number.")
        exit(1)

    for root, dirs, files in os.walk('.'):
        for file in files:
            if any(file.lower().endswith(ext) for ext in video_extensions):
                video_path = os.path.join(root, file)
                print(f"Processing video {video_path}…")
                process_video_file(video_path, starting_offset, ending_offset, num_segments, segment_duration, preference)

    print("All videos have been processed.")