USER
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
temporary_audio_files = []
# Define video file extensions and the output folder
video_extensions = ['.mp4', '.mkv', '.wmv', '.avi']
output_folder = 'Output'
def sorting_preference_to_string(pref):
sorting_options = {
1: "Par ordre de lecture de la vidéo",
2: "Par ordre inverse de lecture de la vidéo",
3: "Par volume croissant",
4: "Par volume décroissant",
5: "Aléatoire"
}
return sorting_options.get(pref, "Non défini")
def peak_position_to_string(pos):
peak_options = {
'1': "À 1/4 du temps de lecture de la vidéo",
'2': "À 1/2 du temps de lecture de la vidéo",
'3': "À 3/4 du temps de lecture de la vidéo"
}
return peak_options.get(pos, "Non défini")
def print_menu(video_durations):
global include_subfolders, starting_offset_seconds, ending_offset_seconds
global segment_duration, num_moments, sorting_preference, peak_position
max_segments = calculate_max_segments(video_durations, segment_duration, starting_offset_seconds, ending_offset_seconds)
print("\nMenu des options :")
print(f"1. Traiter les sous-dossiers ou non (actuel: {str(include_subfolders)})")
print(f"2. Effectuer un retrait temporel (début: {str(starting_offset_seconds)}s, fin: {str(ending_offset_seconds)}s)")
print(f"3. Changer la durée des segments (actuel: {str(segment_duration)}s | max segments extractibles: {str(max_segments)})")
print(f"4. Changer le nombre de segments à extraire (actuel: {str(num_moments)})")
print(f"5. Changer l'ordre de tri (actuel: {sorting_preference_to_string(sorting_preference)})")
print(f"6. Changer l'emplacement du pic sonore (actuel: {peak_position_to_string(peak_position)})")
print(f"7. Relancer l'extraction")
print(f"8. Quitter")
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 calculate_max_segments(video_durations, segment_duration, starting_offset, ending_offset):
min_duration = min(video_durations.values())
allowable_duration = min_duration - (starting_offset + ending_offset)
if allowable_duration > 0:
return int(allowable_duration // segment_duration)
else:
return 0
def find_loudest_moments(audio_filename, num_moments, segment_duration, video_duration, starting_offset, ending_offset):
rate, audio_data = wavfile.read(audio_filename)
# Convert stereo to mono if necessary
if audio_data.ndim == 2:
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 len(moments) < num_moments and (end_index - start_index) > 0:
index = np.argmax(volume_dB[start_index:end_index])
print(f"Current index: {index}, start_index: {start_index}, end_index: {end_index}") # Ajouté pour le débogage
moment = (start_index + index) / rate
print(f"Added moment at {moment} seconds") # Ajouté pour le débogage
moment_volume = volume_dB[start_index + index]
if moment - segment_half_duration < starting_offset or moment + segment_half_duration > video_duration - ending_offset:
volume_dB[start_index + index] = -np.inf
continue
moments.append(moment)
print(f"Added moment at {moment} seconds") # Ajouté pour le débogage
volumes.append(moment_volume)
# Clear the volume around the found moment to prevent picking up nearby moments
# Increment the global index by adding the start_index
global_index = start_index + index
# Clear the volume around the found moment to prevent picking up nearby moments
clear_range_start = max(0, global_index - int(rate * segment_half_duration))
clear_range_end = min(len(volume_dB), global_index + int(rate * segment_half_duration))
# Debug print statements to check if the index and the volume are cleared
print(f"Clearing from {clear_range_start} to {clear_range_end}")
print(f"Volume before clearing at index {global_index}: {volume_dB[global_index]}")
volume_dB[clear_range_start:clear_range_end] = -np.inf
print(f"Volume after clearing at index {global_index}: {volume_dB[global_index]}")
return moments, volumes
def perform_extraction(video_durations):
global starting_offset_seconds, ending_offset_seconds
global segment_duration, num_moments, sorting_preference, peak_position, processed_videos
for video_path, duration in video_durations.items():
available_duration = duration - (starting_offset_seconds + ending_offset_seconds)
if available_duration <= segment_duration:
print(f"La vidéo {video_path} est trop courte après les décalages. Ignorer.")
continue
if sorting_preference == 5: # Aléatoire
all_possible_moments = np.arange(starting_offset_seconds, duration - ending_offset_seconds - segment_duration, segment_duration) + (segment_duration / 2)
np.random.shuffle(all_possible_moments)
selected_moments = all_possible_moments[:num_moments]
volumes = [0] * len(selected_moments) # Les volumes sont justes pour la compatibilité avec extract_segments.
else:
audio_path = f'temp_audio_{uuid.uuid4().hex}.wav'
try:
with VideoFileClip(video_path) as video_clip:
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)
sorted_moments, sorted_volumes = sort_moments(moments, volumes, sorting_preference)
selected_moments = sorted_moments
volumes = sorted_volumes
finally:
if os.path.exists(audio_path):
os.remove(audio_path)
extract_segments(video_path, selected_moments, segment_duration, duration, peak_position)
processed_videos += 1
print(f"Extraction terminée pour la vidéo '{video_path}'")
print(f"L'extraction de toutes les vidéos est terminée. Nombre de vidéos traitées : {processed_videos}.")
def extract_segments(video_path, moments, segment_duration, video_duration, peak_position):
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 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
]
try:
subprocess.run(command, check=True, stderr=subprocess.PIPE)
print(f"Extracted and re-encoded {output_filename}")
except subprocess.CalledProcessError as e:
# Catch the exception for this process, print an error message,
# but don't break from the for loop.
err_msg = e.stderr.decode('utf-8') if e.stderr else 'Unknown error'
print(f"Failed to extract segment from {video_path}: {err_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 get_video_durations(include_subfolders):
video_durations = {}
for root, dirs, files in os.walk('.', topdown=True):
# Si l'utilisateur ne souhaite pas inclure les sous-dossiers, nous modifions dirs sur place
if not include_subfolders:
dirs[:] = [] # Cela empêchera os.walk de descendre dans tous les sous-dossiers
# La suite du traitement des fichiers reste inchangée
for file in files:
if file.lower().endswith(tuple(video_extensions)):
video_path = os.path.join(root, file)
try:
# Essayez d'ouvrir et d'extraire les métadonnées du fichier vidéo.
video_clip = VideoFileClip(video_path)
video_duration = video_clip.duration
video_durations[video_path] = video_duration
except Exception as e:
# Si une erreur survient, affichez un message et ignorez le fichier vidéo.
print(f"Error processing video {video_path}: {e}")
finally:
# Assurez-vous de fermer le clip pour libérer les ressources.
video_clip.close()
return video_durations
def confirm_segment_number_or_ask_again(video_duration, starting_offset_seconds, ending_offset_seconds):
allowable_duration = video_duration - (starting_offset_seconds + ending_offset_seconds)
while True:
segment_duration, max_segments = ask_for_segment_duration(allowable_duration, 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 ce nombre de segments ?")
if confirmation:
return segment_duration, max_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 main():
global include_subfolders, starting_offset_seconds, ending_offset_seconds
global segment_duration, num_moments, sorting_preference, peak_position, processed_videos, video_durations
# Initialisation des variables avec des valeurs par défaut
include_subfolders = False
starting_offset_seconds = 0
ending_offset_seconds = 0
segment_duration = 5.0 # Valeur par défaut, par exemple 5 secondes
max_segments = calculate_max_segments(video_durations, segment_duration, starting_offset_seconds, ending_offset_seconds)
num_moments = 20 # Valeur par défaut, par exemple 5 moments
sorting_preference = 4 # Valeur par défaut, par exemple tri par ordre de lecture de la vidéo
peak_position = '2' # Valeur par défaut, par exemple à 1/2 du temps de lecture de la vidéo
processed_videos = 0
# Chargement des durées des vidéos au démarrage du script
video_durations = get_video_durations(include_subfolders)
if not video_durations:
print("Aucune vidéo trouvée pour l'analyse.")
exit()
while True:
print_menu(video_durations)
choice = input("Veuillez entrer le numéro de votre choix : ").strip()
if choice == '1':
include_subfolders = ask_directory_preference()
video_durations = get_video_durations(include_subfolders)
elif choice == '2':
# Récupérez les nouvelles valeurs.
offset_type = ask_offset_type()
# Utilisez une valeur minimale mise à jour si nécessaire.
min_duration = min(video_durations.values())
print("Configuration du retrait temporel pour le début de la vidéo:")
starting_offset_seconds = get_offset_value(min_duration, offset_type)
print("Configuration du retrait temporel pour la fin de la vidéo:")
ending_offset_seconds = get_offset_value(min_duration, offset_type)
elif choice == '3':
segment_duration, max_segments = confirm_segment_number_or_ask_again(min(video_durations.values()), starting_offset_seconds, ending_offset_seconds)
elif choice == '4':
num_moments = ask_for_number_of_moments(max_segments)
elif choice == '5':
sorting_preference = ask_sorting_preference()
elif choice == '6':
peak_position = ask_peak_position()
elif choice == '7':
# Exécutez vos sous-fonctions pour effectuer l'extraction des segments.
# Vous souhaiterez probablement encapsuler cela dans une autre fonction.
perform_extraction(video_durations) # Call perform_extraction instead of extract_segments
elif choice == '8':
print("Fin du programme.")
break
else:
print("Choix non valide. Veuillez réessayer.")
print(f"Le traitement de toutes les vidéos est terminé. {processed_videos} vidéos ont été traitées.")
if __name__ == "__main__":
main()