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
import librosa
import numpy as np
temporary_audio_files = []
# Define video file extensions and the output folder
video_extensions = ['.mp4', '.mkv', '.wmv', '.avi']
output_folder = 'Output'
neutralization_factor = 0.5
def find_vocal_segments(audio_filename, num_moments, segment_duration, video_duration, starting_offset, ending_offset):
global neutralization_factor
y, sr = librosa.load(audio_filename, sr=None) # Charger le fichier audio avec LibROSA
# Convertisseur en mono si stéréo
if y.ndim > 1:
y = librosa.to_mono(y)
# Trouver les intensités (RMS) pour identifier les parties plus “fortes”
rms = librosa.feature.rms(y=y)[0]
# Une approche très simplifiée pour trouver des segments basée sur RMS
# Remarque : Ceci ne prend pas directement en compte les formants mais cherche des segments plus audibles qui pourraient être raffinés pour la reconnaissance de la parole
high_energy = np.argwhere(rms > np.median(rms) * neutralization_factor).flatten()
segments = []
volumes = []
for start_frame in high_energy[::int(segment_durationsr)]:
start_time = max(start_frame / sr, starting_offset)
end_time = start_time + segment_duration
# Filtrer les segments qui dépassent les limites de temps vidéo
if end_time <= (video_duration - ending_offset):
segments.append(start_time)
# Ce calcul de volume est simplifié; considérez d’affiner cette métrique pour la parole
volume = np.mean(rms[int(start_frame):int(start_frame+segment_durationsr)])
volumes.append(volume)
# Ici, on pourrait ajouter une étape supplémentaire pour analyser les segments détectés pour y reconnaître plus spécifiquement les formants
return segments[:num_moments], volumes[:num_moments]
def update_max_segments_for_selected_video():
global selected_video_path, max_segments, segment_duration, starting_offset_seconds, ending_offset_seconds, num_moments
if selected_video_path in video_durations:
video_duration = video_durations[selected_video_path]
max_segments = calculate_max_segments(video_duration, segment_duration, starting_offset_seconds, ending_offset_seconds)
num_moments = min(num_moments, max_segments) # Ajustez num_moments si nécessaire
else:
max_segments = 0
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 choose_video(videos):
# Separate root files from subdirectory files
root_files = [video for video in videos if os.path.dirname(os.path.relpath(video)) == '']
subdirectory_files = [video for video in videos if os.path.dirname(os.path.relpath(video)) != '']
# Sort the files in subdirectories
subdirectory_files_sorted = sorted(subdirectory_files, key=lambda x: (os.path.dirname(x).lower(), os.path.basename(x).lower()))
# Combine lists: root files first, then sorted subdirectory files
combined_videos = root_files + subdirectory_files_sorted
print("Liste des vidéos disponibles :")
for i, video in enumerate(combined_videos):
# Get the relative path for printing
rel_path = os.path.relpath(video)
print(f"{i + 1}- {rel_path}")
while True:
choice = input("Veuillez choisir la vidéo à extraire (entrez le numéro) : ")
try:
choice_idx = int(choice) - 1
if 0 <= choice_idx < len(combined_videos):
return combined_videos[choice_idx]
else:
print("Le numéro doit être valide. Veuillez réessayer.")
except ValueError:
print("Veuillez entrer un nombre valide.")
def print_menu(selected_video_path):
global max_segments
global include_subfolders, starting_offset_seconds, ending_offset_seconds
global segment_duration, num_moments, sorting_preference, peak_position
video_name = os.path.basename(selected_video_path) if selected_video_path else "Aucune vidéo sélectionnée"
print("\nMenu des options :")
print(f"1. Traiter les sous-dossiers ou non ({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 ({str(segment_duration)}s | max segments extractibles: {str(max_segments)})")
print(f"4. Changer le nombre de segments à extraire ({str(num_moments)})")
print(f"5. Changer l'ordre de tri ({sorting_preference_to_string(sorting_preference)})")
print(f"6. Changer l'emplacement du pic sonore ({peak_position_to_string(peak_position)})")
print(f"7. Changer le facteur de neutralisation (actuellement: {neutralization_factor})")
print(f"8- ({video_name}) - Modifier")
print(f"9. Lancer l'extraction")
print(f"0. Quitter")
def ask_neutralization_factor():
global neutralization_factor
while True:
factor_input = input("Veuillez entrer le nouveau facteur de neutralisation (valeur entre 0 et 1) : ").strip()
try:
factor_value = float(factor_input)
if 0 <= factor_value <= 1:
neutralization_factor = factor_value
return
else:
print("Le facteur doit être compris entre 0 et 1. Veuillez réessayer.")
except ValueError:
print("Entrée non valide, veuillez réessayer avec un nombre flottant.")
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):
# Cette fonction a été modifiée pour répondre plus précisément aux contraintes de durée.
while True:
duration = input("Quelle est la durée (en secondes) de chaque segment vidéo à extraire (Entrez un nombre positif et inférieur ou égal à {allowable_duration}) ? ")
try:
segment_duration = float(duration)
if 0 < 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 moins 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_duration, segment_duration, starting_offset, ending_offset):
allowable_duration = video_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):
# global neutralization_factor
# 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
# neutralization_factor = neutralization_factor
# # Calculer le rayon de neutralisation en appliquant le facteur à la durée du segment
# neutralization_radius = segment_duration * neutralization_factor
# # Déterminer les indices de début et de fin de la plage de neutralisation
# clear_range_start = max(0, global_index - int(rate * neutralization_radius))
# clear_range_end = min(len(volume_dB), global_index + int(rate * neutralization_radius))
# # Mettre la plage de neutralisation à -inf pour éviter de sélectionner à nouveau des moments proches
# 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():
global starting_offset_seconds, ending_offset_seconds
global segment_duration, num_moments, sorting_preference, peak_position, processed_videos, selected_video_path, max_segments
# Vérifiez si une vidéo a été sélectionnée.
if not selected_video_path:
print("Aucune vidéo sélectionnée pour l'extraction.")
return
# Vérifiez si la vidéo sélectionnée est contenue dans video_durations.
if selected_video_path not in video_durations:
print(f"La vidéo sélectionnée '{selected_video_path}' n'est pas disponible.")
return
# Obtenez la durée de la vidéo sélectionnée.
duration = video_durations[selected_video_path]
available_duration = duration - (starting_offset_seconds + ending_offset_seconds)
if available_duration <= segment_duration:
print(f"La vidéo {selected_video_path} est trop courte après les décalages. Ignorer.")
return
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(selected_video_path) as video_clip:
video_clip.audio.write_audiofile(audio_path, codec='pcm_s16le', fps=44100, nbytes=2, buffersize=2000)
moments, volumes = find_vocal_segments(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)
# Juste avant l'appel à extract_segments, mettez à jour num_moments pour être sûr qu'il ne dépasse pas max_segments
num_moments = min(num_moments, max_segments)
extract_segments(selected_video_path, selected_moments, segment_duration, duration, peak_position)
processed_videos += 1
print(f"Extraction terminée pour la vidéo '{selected_video_path}'")
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
global processed_videos, video_durations, selected_video_path, max_segments
# Initialize the variables with default values
include_subfolders = False
starting_offset_seconds = 0
ending_offset_seconds = 0
segment_duration = 5.0 # Default value, e.g., 5 seconds
num_moments = 20 # Default value, e.g., 20 moments
sorting_preference = 4 # Default value, e.g., sorting by descending volume
peak_position = '2' # Default value, e.g., peak at the middle of the segment
selected_video_path = "" # Initialement vide
processed_videos = 0
# Load video durations at the start of the script
video_durations = get_video_durations(include_subfolders)
if not video_durations:
print("Aucune vidéo trouvée pour l'analyse.")
exit()
video_list = sorted(list(video_durations.keys()), key=lambda x: os.path.basename(x))
selected_video_path = video_list[0] if video_list else "" # Select the first video by default, if available.
# Calculate max_segments with the newly obtained video_durations
video_duration = video_durations[selected_video_path] # Obtenez la durée de la vidéo sélectionnée
max_segments = calculate_max_segments(video_durations[selected_video_path], segment_duration, starting_offset_seconds, ending_offset_seconds)
while True:
# Recalculate the max segments using the selected video before displaying the menu
update_max_segments_for_selected_video()
# Display the menu with the updated max segments
print_menu(selected_video_path)
choice = input("Veuillez entrer le numéro de votre choix : ").strip()
# Handle user choices
if choice == '1':
# Update include_subfolders and recalculate video durations
include_subfolders = ask_directory_preference()
video_durations = get_video_durations(include_subfolders)
# Optionally, reset selected_video_path if include_subfolders changed
video_list = sorted(list(video_durations.keys()), key=lambda x: os.path.basename(x))
selected_video_path = video_list[0] if video_list else ""
update_max_segments_for_selected_video()
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':
# L'utilisateur définit la durée des segments et potentiellement le nombre de moments
video_duration = min(video_durations.values()) # Min. duration among all videos
allowable_duration = video_duration - (starting_offset_seconds + ending_offset_seconds)
segment_duration, max_segments = ask_for_segment_duration(
allowable_duration,
video_duration,
starting_offset_seconds,
ending_offset_seconds
)
if num_moments > max_segments:
num_moments = max_segments
# Ce bloc assure que num_moments n'excède jamais max_segments après un changement de durée des segments
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':
ask_neutralization_factor()
elif choice == '8':
video_list = sorted(list(video_durations.keys()), key=lambda x: os.path.basename(x))
prev_video_path = selected_video_path
selected_video_path = choose_video(video_list)
if selected_video_path != prev_video_path:
print(f"Vidéo sélectionnée pour l'extraction : {os.path.basename(selected_video_path)}")
update_max_segments_for_selected_video() # Mise à jour des segments max après le choix.
elif choice == '9':
# Exécutez vos sous-fonctions pour effectuer l'extraction des segments.
# Vous souhaiterez probablement encapsuler cela dans une autre fonction.
perform_extraction() # Call perform_extraction instead of extract_segment
elif choice == '0':
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()
MoviePy - Writing audio in temp_audio_fe72d714fb46432da119dfd32b2fb780.wav
MoviePy - Done.
Traceback (most recent call last):
File "C:\Users\jp\Desktop\Nouveau dossier\Script.py", line 555, in <module>
main()
File "C:\Users\jp\Desktop\Nouveau dossier\Script.py", line 544, in main
perform_extraction() # Call perform_extraction instead of extract_segment
File "C:\Users\jp\Desktop\Nouveau dossier\Script.py", line 311, in perform_extraction
moments, volumes = find_vocal_segments(audio_path, num_moments, segment_duration, duration, starting_offset_seconds, ending_offset_seconds)
File "C:\Users\jp\Desktop\Nouveau dossier\Script.py", line 35, in find_vocal_segments
for start_frame in high_energy[::int(segment_durationsr)]:
NameError: name 'segment_durationsr' is not defined. Did you mean: 'segment_duration'?