import os
import subprocess
import numpy as np
import uuid  # Import uuid to generate unique IDs
from moviepy.editor import VideoFileClip
from scipy.io import wavfile
import random
import tempfile
import shutil

temporary_audio_files = []

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

def get_video_durations(include_subfolders):
    video_durations = {}
    for root, dirs, files in os.walk('.', topdown=True):
        if not include_subfolders:
            dirs[:] = []
        
        for file in files:
            if file.lower().endswith(tuple(video_extensions)):
                video_path = os.path.join(root, file)
                try:
                    # Open the video file and extract metadata
                    video_clip = VideoFileClip(video_path)
                    if not video_clip.audio:  # Vérifier si la vidéo a une piste audio
                        print(f"La vidéo {video_path} n'a pas de piste audio.")
                        continue  # Passer à la vidéo suivante
                    video_duration = video_clip.duration
                    video_durations[video_path] = video_duration
                except Exception as e:
                    print(f"Error processing video {video_path}: {e}")
                finally:
                    video_clip.close()  # Make sure to close the clip to release resources
    return video_durations

def ask_min_or_max_duration_preference():
    print("Souhaitez-vous vous baser sur la durée de la vidéo la plus courte ou la plus longue pour le calcul du nombre de segments ?")
    print("1- Sur la vidéo la plus courte")
    print("2- Sur la vidéo la plus longue")
    while True:
        choice = input("Veuillez entrer votre choix (1 ou 2) : ")
        if choice in ('1', '2'):
            return choice
        else:
            print("Entrée non valide, veuillez réessayer.")

def ask_batch_or_individual_processing():
    print("Comment souhaitez-vous traiter les fichiers vidéo ?")
    print("1- Par lot (traiter tous les fichiers avec les mêmes paramètres)")
    print("2- Individuellement (définir des paramètres pour chaque fichier)")
    while True:
        choice = input("Veuillez entrer le numéro de votre choix (1 ou 2) : ").strip()
        if choice in ('1', '2'):
            return choice
        else:
            print("Entrée non valide, veuillez réessayer.")

def ask_for_number_of_moments(max_segments):
    while True:
        num_input = input(f"Veuillez entrer le nombre de moments forts à extraire (maximum {max_segments}): ")
        try:
            num = int(num_input)
            if num > 0 and num <= max_segments:
                return num
            else:
                print(f"Le nombre doit être supérieur à 0 et inférieur ou égal à {max_segments}. Veuillez réessayer.")
        except ValueError:
            print("Entrée non valide, veuillez réessayer avec un nombre entier.")

def ask_yes_no_question(question):
    answer = None
    while answer not in ('1', '2'):
        print(question)
        print("1- Oui")
        print("2- Non")
        answer = input("Veuillez entrer le numéro de votre choix (1 ou 2) : ").strip()
        if answer not in ('1', '2'):
            print("Entrée non valide, veuillez réessayer.")
    return answer == '1'
    
def ask_offset_type():
    print("Souhaitez-vous un décalage temporel relatif ou absolu ?")
    print("1- Relatif (pourcentage)")
    print("2- Absolu (secondes)")
    while True:
        choice = input("Veuillez entrer le numéro de votre choix (1 ou 2) : ").strip()
        if choice in ('1', '2'):
            return choice
        else:
            print("Entrée non valide, veuillez réessayer.")
            
def get_offset_value(video_duration, offset_type):
    if offset_type == '1':  # Relative offset
        while True:
            percent = input("Entrez le pourcentage du temps vidéo à ignorer : ")
            try:
                percent_value = float(percent)
                return percent_value * video_duration / 100
            except ValueError:
                print("Veuillez entrer un nombre valide.")
    else:  # Absolute offset
        while True:
            seconds = input("Entrez le nombre de secondes à ignorer : ")
            try:
                return float(seconds)
            except ValueError:
                print("Veuillez entrer un nombre valide.")
                
def ask_for_segment_duration(allowable_duration, video_duration, starting_offset_seconds, ending_offset_seconds):
    while True:
        duration = input("Quelle est la durée (en secondes) de chaque segment vidéo à extraire ? ")
        try:
            segment_duration = float(duration)
            if segment_duration > 0 and segment_duration <= allowable_duration:
                # Calculez le nombre maximal de segments pour une vidéo
                available_duration = video_duration - (starting_offset_seconds + ending_offset_seconds)
                max_segments = int(available_duration // segment_duration)
                return segment_duration, max_segments
            else:
                print(f"La durée doit être un nombre positif et inférieure ou égale à {allowable_duration} secondes.")
        except ValueError:
            print("Veuillez entrer un nombre valide.")

def ask_directory_preference():
    print("Souhaitez-vous inclure les sous-dossiers dans la recherche des vidéos ?")
    print("1- Oui")
    print("2- Non")
    choice = input("Veuillez entrer le numéro de votre choix (1 ou 2) : ")
    return choice.strip() == '1'  # Retourne True si l'utilisateur choisit '1' (Oui), False sinon

def calculate_loudness(audio_data):
    if audio_data.ndim == 1:
        volume = audio_data.astype('float32') ** 2
    else:
        volume = np.mean(audio_data.astype('float32') ** 2, axis=1)
    volume_dB = 10 * np.log10(volume + 1e-9)  # +1e-9 to avoid log(0) and convert to dB
    return volume_dB

def find_loudest_moments(audio_filename, num_moments, segment_duration, video_duration, starting_offset, ending_offset):
    try:
        # Read the audio data from the file
        rate, audio_data = wavfile.read(audio_filename)

        if audio_data.ndim == 2:  # if stereo, average the channels to mono
            audio_data = np.mean(audio_data, axis=1)

        volume_dB = calculate_loudness(audio_data)
        segment_half_duration = segment_duration / 2.0
        start_index = int(starting_offset * rate)
        end_index = int((video_duration - ending_offset) * rate)

        moments = []
        volumes = []

        # While there are fewer moments found than requested and there is still data to analyze
        while len(moments) < num_moments and (end_index - start_index) > int(segment_duration * rate):
            index = np.argmax(volume_dB[start_index:end_index])
            moment = (start_index + index) / rate
            moment_volume = volume_dB[start_index + index]
            
                # Check if the moment is within allowed offsets and add it to the list if so
            if moment - segment_half_duration >= starting_offset and moment + segment_half_duration <= video_duration - ending_offset:
                moments.append(moment)
                volumes.append(moment_volume)

                # Clear the volume around the found moment to prevent picking up nearby moments
                clear_range_start = max(start_index, start_index + index - int(segment_half_duration * rate))
                clear_range_end = min(end_index, start_index + index + int(segment_half_duration * rate))
                volume_dB[clear_range_start:clear_range_end] = -np.inf
            else:
                # If the moment is not within the offsets, just clear its volume and continue
                volume_dB[start_index + index] = -np.inf

            moments.append(moment)
            volumes.append(moment_volume)

            # Suppress loudness of the recently found moment
            suppression_start = max(index - int(segment_half_duration * rate), 0)
            suppression_end = min(index + int(segment_half_duration * rate), len(volume_dB))
            volume_dB[suppression_start:suppression_end] = -np.inf

            # Move the start_index past the recently found moment by at least the duration
            start_index = max(start_index + int(segment_duration * rate), suppression_end)

        print(f"Found {len(moments)} moments in {audio_filename}")
        return moments, volumes
    except Exception as e:
        print(f"An error occurred while processing the audio file: {e}")
        return [], []

def extract_segments(video_path, moments, segment_duration, video_duration, peak_position):
    # Vérifiez si FFmpeg est installé
    if shutil.which("ffmpeg") is None:
        raise FileNotFoundError("FFmpeg n'est pas installé ou n'est pas dans le chemin du système.")
    if not os.path.exists(output_folder):
        os.makedirs(output_folder)
    base_name = os.path.splitext(os.path.basename(video_path))[0]
    half_segment_duration = segment_duration / 2
    
    for i, moment in enumerate(moments):
        # If the volume peak is selected to be in the middle of the segment (-t 0.5), start time should be adjusted.
        if peak_position == '1':  # 1/4
            start_time = max(moment - segment_duration * 0.25, 0)
        elif peak_position == '2':  # 1/2
            start_time = max(moment - segment_duration * 0.5, 0)
        elif peak_position == '3':  # 3/4
            start_time = max(moment - segment_duration * 0.75, 0)
        end_time = min(start_time + segment_duration, video_duration)

        output_filename = f"{base_name}_moment{i + 1}.mp4"
        output_path = os.path.join(output_folder, output_filename)

        command = [
            "ffmpeg",
            "-y",                     # Overwrite output files without asking
            "-ss", str(start_time),   # Start time
            "-i", video_path,         # Input file
            "-t", str(min(segment_duration, video_duration - start_time)),  # Duration or remaining video
            "-c:v", "libx264",        # Specify video codec for output
            "-preset", "medium",      # Specify the encoding preset (trade-off between encoding speed and quality)
            "-crf", "23",             # Specify the Constant Rate Factor for quality (lower means better quality)
            "-c:a", "aac",            # Specify audio codec for output
            "-strict", "-2",          # Necessary for some versions of ffmpeg to use experimental aac encoder
            "-b:a", "192k",           # Specify the audio bitrate
            output_path               # Output path
        ]
        print(f"Extracting {len(moments)} segments from {video_path}")
        try:
            result = subprocess.run(command, check=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
            print(f"Extracted and re-encoded {output_filename}")
        except subprocess.CalledProcessError as e:
            err_msg = e.stderr.decode('utf-8') if e.stderr else 'Unknown error'
            out_msg = e.stdout.decode('utf-8') if e.stdout else 'No output'
            print(f"Failed to extract segment from {video_path}: {err_msg}")
            print(f"FFmpeg Output: {out_msg}")
        
def store_segment_info(video_path, moment, volume, order):
    base_name = os.path.splitext(os.path.basename(video_path))[0]
    output_filename = f"{base_name}_moment{order}.mp4"
    output_path = os.path.join(output_folder, output_filename)
    extracted_segments.append({
        'path': output_path,
        'timestamp': moment,
        'volume': volume
    })
        
def ask_sorting_preference():
    print("Comment souhaitez-vous trier les vidéos extraites ?")
    print("1- Par ordre de lecture de la vidéo")
    print("2- Par ordre inverse de lecture de la vidéo")
    print("3- Par volume croissant")
    print("4- Par volume décroissant")
    print("5- Aléatoire")
    choice = int(input("Veuillez entrer le numéro de votre choix : "))
    return choice
    
def sort_moments(moments, volumes, choice):
    if choice == 1:  # Par ordre de lecture de la vidéo
        zipped = sorted(zip(moments, volumes), key=lambda x: x[0])
    elif choice == 2:  # Par ordre inverse de lecture de la vidéo
        zipped = sorted(zip(moments, volumes), key=lambda x: x[0], reverse=True)
    elif choice == 3:  # Par volume croissant
        zipped = sorted(zip(moments, volumes), key=lambda x: x[1])
    elif choice == 4:  # Par volume décroissant
        zipped = sorted(zip(moments, volumes), key=lambda x: x[1], reverse=True)
    elif choice == 5:  # Pas de tri, sélection aléatoire
        zipped = list(zip(moments, volumes))
        random.shuffle(zipped)
    else:
        zipped = zip(moments, volumes)

    # Unzip the list of tuples to two lists
    sorted_moments, sorted_volumes = zip(*zipped) if zipped else ([], [])
    return list(sorted_moments), list(sorted_volumes)
    
def confirm_segment_number_or_ask_again(video_duration, starting_offset_seconds, ending_offset_seconds):
    while True:
        segment_duration, max_segments = ask_for_segment_duration(video_duration - (starting_offset_seconds + ending_offset_seconds), video_duration, starting_offset_seconds, ending_offset_seconds)
        print(f"Avec cette durée, vous pouvez extraire jusqu'à {max_segments} segments.")
        confirmation = ask_yes_no_question("Voulez-vous continuer avec cette durée de segments ?")
        if confirmation:
            num_moments = ask_for_number_of_moments(max_segments)
            return segment_duration, num_moments  # Retournons maintenant le nombre de moments choisis
        else:
            print("Veuillez entrer une nouvelle durée pour les segments.")
            
def ask_peak_position():
    print("Où doit être situé le pic sonore dans la vidéo extraite ?")
    print("1- A 1/4 du temps de lecture de la vidéo")
    print("2- A 1/2 du temps de lecture de la vidéo")
    print("3- A 3/4 du temps de lecture de la vidéo")
    while True:
        choice = input("Veuillez entrer le numéro de votre choix (1, 2, ou 3) : ").strip()
        if choice in ('1', '2', '3'):
            return choice
        else:
            print("Entrée non valide, veuillez réessayer.")
            
def extract_audio(video_path):
    try:
        # Use tempfile to create a temporary audio file
        with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as temp_audio:
            audio_file_path = temp_audio.name
        # Extract audio using moviepy
        with VideoFileClip(video_path) as video_clip:
            if video_clip.audio:
                video_clip.audio.write_audiofile(audio_file_path, codec='pcm_s16le', fps=44100, nbytes=2, buffersize=2000)
        return audio_file_path
    except Exception as e:
        print(f"An error occurred while extracting audio from video {video_path}: {e}")
        return None
         
def main():
    if not os.path.exists(output_folder):
        os.makedirs(output_folder)
    batch_or_individual = ask_batch_or_individual_processing()
    include_subfolders = ask_yes_no_question("Souhaitez-vous inclure les sous-dossiers dans la recherche des vidéos ?")
    video_durations = get_video_durations(include_subfolders)
    
    if not video_durations:
        print("Aucune vidéo trouvée pour l'analyse.")
        return

    processed_videos = 0
    
    if batch_or_individual == '1':  # Traitement par lot
        duration_choice = ask_min_or_max_duration_preference()
        reference_video_duration = min(video_durations.values()) if duration_choice == '1' else max(video_durations.values())
        perform_temporal_offset = ask_yes_no_question("Souhaitez-vous effectuer un retrait temporel (début et fin) pour l'analyse des vidéos ?")
        
        if perform_temporal_offset:
            offset_type = ask_offset_type()
            print("Configuration du retrait temporel pour le début de la vidéo:")
            starting_offset_seconds = get_offset_value(reference_video_duration, offset_type)
            print("Configuration du retrait temporel pour la fin de la vidéo:")
            ending_offset_seconds = get_offset_value(reference_video_duration, offset_type)
        else:
            starting_offset_seconds = 0
            ending_offset_seconds = 0
        
        segment_duration, num_moments = confirm_segment_number_or_ask_again(reference_video_duration, starting_offset_seconds, ending_offset_seconds)
        sorting_preference = ask_sorting_preference()
        peak_position = ask_peak_position()

    for video_path, duration in video_durations.items():
        print(f"Traitement de la vidéo : {video_path}")
        
        # Utilisez le module tempfile pour créer un fichier audio temporaire
        with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as temp_audio:
            audio_path = temp_audio.name
        
        # Traitement individuel pour chaque vidéo
        if batch_or_individual == '2':
            perform_temporal_offset = ask_yes_no_question("Souhaitez-vous effectuer un retrait temporel (début et fin) pour l'analyse de cette vidéo ?")
            
            if perform_temporal_offset:
                offset_type = ask_offset_type()
                print("Configuration du retrait temporel pour le début de la vidéo:")
                starting_offset_seconds = get_offset_value(duration, offset_type)
                print("Configuration du retrait temporel pour la fin de la vidéo:")
                ending_offset_seconds = get_offset_value(duration, offset_type)
            else:
                starting_offset_seconds = 0
                ending_offset_seconds = 0
            
            segment_duration, num_moments = confirm_segment_number_or_ask_again(duration, starting_offset_seconds, ending_offset_seconds)
            sorting_preference = ask_sorting_preference()
            peak_position = ask_peak_position()

        audio_path = f'temp_audio_{uuid.uuid4().hex}.wav'
        temporary_audio_files.append(audio_path)
        
        try:
            with VideoFileClip(video_path) as video_clip:
                if video_clip.audio:
                    video_clip.audio.write_audiofile(audio_path, codec='pcm_s16le', fps=44100, nbytes=2, buffersize=2000)
                    moments, volumes = find_loudest_moments(audio_path, num_moments, segment_duration, duration, starting_offset_seconds, ending_offset_seconds)
                    selected_moments, associated_volumes = sort_moments(moments, volumes, sorting_preference)
                    extract_segments(video_path, selected_moments, segment_duration, duration, peak_position)
                    processed_videos += 1
                    if len(moments) < num_moments:
                        print(f"Insufficient moments found for {video_path}, found {len(moments)} of {num_moments}")

                    print(f"Traitement terminé pour la vidéo '{video_path}'")
                else:
                    print(f"La vidéo {video_path} n'a pas de piste audio. Aucun moment ne peut être extrait.")
                    continue  # Passer à la vidéo suivante
        except Exception as e:
                print(f"Error processing video {video_path}: {e}")
        finally:
                if os.path.exists(audio_path):
                    os.remove(audio_path)
                
        # Pour les traitements par lot, pas de demande de satisfaction après chaque vidéo
        if batch_or_individual == '2':
            satisfied = ask_yes_no_question("Êtes-vous satisfait de la position du pic sonore dans les vidéos extraites ?")
            if not satisfied:
                print("Les paramètres peuvent être ajustés pour la vidéo suivante.")

    print(f"Le traitement de toutes les vidéos est terminé. Nombre total de vidéos traitées : {processed_videos}")

if __name__ == "__main__":
    main()

Le script extrait chaque segment deux fois. 1 et 2 sont identiques, 3 et 4 sont identiques, 5 et 6 sont identiques...