datasetTTS / app.py
Woziii's picture
Create app.py
a74c3ba verified
raw
history blame
7.69 kB
import os
import shutil
import zipfile
from pathlib import Path
import gradio as gr
import torch
from pydub import AudioSegment
from transformers import pipeline
# ------------------------
# CONFIG
# ------------------------
MODEL_NAME = "openai/whisper-large-v3"
device = 0 if torch.cuda.is_available() else "cpu"
pipe = pipeline(
task="automatic-speech-recognition",
model=MODEL_NAME,
device=device,
model_kwargs={"low_cpu_mem_usage": True},
)
TEMP_DIR = "./temp_audio"
os.makedirs(TEMP_DIR, exist_ok=True)
# On stocke la liste des métadonnées (segments) dans un State
# pour la conserver entre les étapes (transcription, découpe, zip).
def init_metadata_state():
return []
# ------------------------
# FONCTIONS
# ------------------------
def transcribe_audio(audio_path):
"""
Étape 2 : Transcription du fichier audio via Whisper
+ récupération de la transcription brute.
"""
if not audio_path:
return "Aucun fichier audio fourni.", [], None
# Transcription Whisper
result = pipe(audio_path, return_timestamps="word")
text = result["text"]
chunks = result["chunks"] # liste de { 'timestamp': (start, end), 'text': ... }
raw_transcription = " ".join([c["text"] for c in chunks])
# Le 3e retour = chemin du fichier audio, qu'on renverra tel quel pour la découpe
return raw_transcription, [], audio_path
def validate_segments(audio_path, table_data, metadata_state):
"""
Étape 5 : Découpe de l'audio en fonction des segments
et mise à jour du State `metadata_state`.
- `table_data` doit contenir : [ [Texte, Début(s), Fin(s), ID], ... ]
- Retourne :
1) Une liste de chemins (extraits audio) pour les players
2) La liste des nouvelles métadonnées (mise à jour).
"""
if not audio_path:
return ["Aucun fichier audio..."], metadata_state
# Nettoyage du dossier temporaire avant recréation des extraits
if os.path.exists(TEMP_DIR):
shutil.rmtree(TEMP_DIR)
os.makedirs(TEMP_DIR, exist_ok=True)
original_audio = AudioSegment.from_file(audio_path)
segment_paths = []
updated_metadata = []
for i, row in enumerate(table_data):
# row = [ segment_text, start_time, end_time, seg_id ]
if len(row) < 4:
continue
seg_text, start_time, end_time, seg_id = row
if not seg_text or start_time is None or end_time is None:
continue
# Si l'utilisateur n'a pas mis d'ID, en créer un
if not seg_id:
seg_id = f"seg_{i+1:02d}"
# Découpe
start_ms = int(float(start_time) * 1000)
end_ms = int(float(end_time) * 1000)
extract = original_audio[start_ms:end_ms]
# Nom de fichier
stem_name = Path(audio_path).stem
segment_filename = f"{stem_name}_{seg_id}.wav"
segment_filepath = os.path.join(TEMP_DIR, segment_filename)
extract.export(segment_filepath, format="wav")
segment_paths.append(segment_filepath)
# Stocker la métadonnée
updated_metadata.append({
"audio_file": segment_filename,
"text": seg_text,
"start_time": start_time,
"end_time": end_time,
"id": seg_id,
})
return segment_paths, updated_metadata
def generate_zip(metadata_state):
"""
Étape 8 : Générer un ZIP contenant tous les extraits + un metadata.csv
Retourne le chemin vers le ZIP final.
"""
if not metadata_state:
return None
# Supprimer un ancien zip si présent
zip_path = os.path.join(TEMP_DIR, "dataset.zip")
if os.path.exists(zip_path):
os.remove(zip_path)
# Créer metadata.csv
metadata_csv_path = os.path.join(TEMP_DIR, "metadata.csv")
with open(metadata_csv_path, "w", encoding="utf-8") as f:
f.write("audio_file|text|speaker_name|API\n")
for seg in metadata_state:
# Ajuste speaker_name ou API selon ton besoin
line = f"{seg['audio_file']}|{seg['text']}|projectname|/API_PHONETIC/\n"
f.write(line)
# Créer le ZIP
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
# Ajouter chaque extrait
for seg in metadata_state:
seg_file = os.path.join(TEMP_DIR, seg["audio_file"])
if os.path.exists(seg_file):
zf.write(seg_file, seg["audio_file"])
# Ajouter le metadata.csv
zf.write(metadata_csv_path, "metadata.csv")
return zip_path
def distribute_segments_to_players(segments):
"""
Transforme la liste de segments en un tuple de 20 valeurs max
(pour 20 players).
Si moins de 20 segments, on complète avec None.
"""
max_players = 20
result = []
for i in range(max_players):
if i < len(segments):
result.append(segments[i])
else:
result.append(None)
return tuple(result)
# ------------------------
# CONSTRUCTION UI GRADIO
# ------------------------
with gr.Blocks(css="style.css") as demo:
gr.Markdown("# Application de Découpage Audio + Transcription (jusqu'à 20 extraits)")
metadata_state = gr.State(init_metadata_state())
# Étape 1 : Chargement de l'audio
with gr.Box():
gr.Markdown("### 1. Téléversez votre fichier audio")
audio_input = gr.Audio(source="upload", type="filepath", label="Fichier audio")
# Étape 3 : Transcription brute
raw_transcription = gr.Textbox(
label="Transcription brute (Whisper)",
placeholder="Le texte s'affichera ici après la transcription...",
interactive=False
)
# Étape 4 : Tableau pour 20 segments max
gr.Markdown("### 2. Définissez jusqu'à 20 segments")
gr.Markdown("""**Colonnes :**
1) Texte (phrase ou portion copiée depuis la transcription)
2) Début (en secondes)
3) Fin (en secondes)
4) ID segment (optionnel)""")
table = gr.Dataframe(
headers=["Texte", "Début (s)", "Fin (s)", "ID"],
datatype=["str", "number", "number", "str"],
row_count=20, # <-- 20 lignes
col_count=4
)
validate_button = gr.Button("Valider et générer les extraits")
# Étape 6 : 20 players audio pour l'écoute
# On les organise en 5 rangées de 4 players
players = []
for i in range(20):
players.append(gr.Audio(label=f"Extrait {i+1}", interactive=False))
# Groupons-les en blocs de 4
for i in range(0, 20, 4):
with gr.Row():
for j in range(i, i+4):
players[j]
# Étape 8 : Génération ZIP
generate_button = gr.Button("Générer le fichier ZIP")
zip_file = gr.File(label="Télécharger le ZIP (audios + metadata.csv)")
# 1) Callback quand on charge l'audio => Transcription
audio_input.change(
fn=transcribe_audio,
inputs=audio_input,
outputs=[raw_transcription, table, audio_input]
)
# 2) Callback quand on valide => Découpe audio + maj metadata
validate_button.click(
fn=validate_segments,
inputs=[audio_input, table, metadata_state],
outputs=[ # 1) chemins extraits (list) 2) metadata (list)
players, # Les 20 players
metadata_state
],
# On va mapper la liste de segments sur 20 players
).then(
fn=distribute_segments_to_players,
inputs=None, # la sortie "players" (chemins) est déjà captée
outputs=players
)
# 3) Génération ZIP
generate_button.click(
fn=generate_zip,
inputs=metadata_state,
outputs=zip_file
)
demo.queue().launch()