Spaces:
Running
on
Zero
Running
on
Zero
File size: 6,983 Bytes
8b05224 cbb52ea 8b05224 454765e 8b05224 4098c08 8b05224 0940c19 8b05224 0940c19 c130b3e b87e99d 7f0f936 0940c19 8b05224 0940c19 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 |
#generate_subtitles.py
import random
import os
import torch
from moviepy import (
VideoFileClip,
TextClip,
CompositeVideoClip,
ImageClip,
vfx
)
from moviepy.video.fx import FadeIn, Resize
import spaces
FONT_PATH = "DejaVuSans-Bold"
# Palette de couleurs « flashy »
SUBTITLE_COLORS = [
"white", "yellow", "cyan", "deeppink", "gold", "lightgreen", "magenta", "orange"
]
def color_for_word(word: str) -> str:
return random.choice(SUBTITLE_COLORS)
def chunk_text_by_words(segments, max_words=1):
"""
Découpe chaque segment Whisper en mini sous-titres de max_words mots
pour un affichage plus dynamique.
"""
print("✂️ Découpage en sous-titres dynamiques (4 mots max)...")
subs = []
for seg in segments:
words = seg['text'].strip().split()
seg_duration = seg['end'] - seg['start']
if not words or seg_duration <= 0:
continue
word_duration = seg_duration / len(words)
for i in range(0, len(words), max_words):
chunk_words = words[i:i + max_words]
chunk_text = " ".join(chunk_words)
start_time = seg['start'] + i * word_duration
end_time = start_time + len(chunk_words) * word_duration
subs.append({
"start": start_time,
"end": end_time,
"text": chunk_text
})
print(f"🧩 {len(subs)} sous-titres créés (dynamiques).")
return subs
def save_subtitles_to_srt(subtitles, output_path):
"""
Sauvegarde les sous-titres au format .srt
"""
def format_timestamp(seconds):
h = int(seconds // 3600)
m = int((seconds % 3600) // 60)
s = int(seconds % 60)
ms = int((seconds - int(seconds)) * 1000)
return f"{h:02}:{m:02}:{s:02},{ms:03}"
with open(output_path, "w", encoding="utf-8") as f:
for i, sub in enumerate(subtitles, 1):
f.write(f"{i}\n")
f.write(f"{format_timestamp(sub['start'])} --> {format_timestamp(sub['end'])}\n")
f.write(f"{sub['text'].strip()}\n\n")
def transcribe_audio_to_subs(audio_path):
"""
Transcrit le fichier audio en texte (via Whisper), retourne la liste
des segments start/end/text, et sauvegarde en .srt.
"""
print("🎙️ Transcription avec Whisper...")
# Empêche Torch de détecter CUDA
import os
os.environ["CUDA_VISIBLE_DEVICES"] = ""
import whisper
model = whisper.load_model("medium", device="cpu")
result = model.transcribe(audio_path)
subtitles = [{
"start": seg['start'],
"end": seg['end'],
"text": seg['text']
} for seg in result['segments']]
print(f"📝 {len(subtitles)} sous-titres générés.")
# Sauvegarde .srt
base_name = os.path.splitext(audio_path)[0]
srt_path = f"{base_name}.srt"
save_subtitles_to_srt(subtitles, srt_path)
print(f"💾 Sous-titres enregistrés dans : {srt_path}")
return subtitles
def format_subtitle_text(text, max_chars=50):
"""
Coupe le texte en 2 lignes max (~50 caractères max par ligne)
pour mieux remplir la vidéo verticale sans déborder.
"""
words = text.strip().split()
lines = []
current_line = ""
for word in words:
if len(current_line + " " + word) <= max_chars:
current_line += (" " + word if current_line else word)
else:
lines.append(current_line.strip())
current_line = word
# Ajout de la dernière ligne
lines.append(current_line.strip())
# Retourne uniquement 2 lignes max
return "\n".join(lines[:2])
def create_animated_subtitle_clip(text, start, end, video_w, video_h):
"""
Crée un TextClip avec :
- Couleur aléatoire
- Fade-in / pop (resize progressif)
- Position verticale fixe (ajustable) ou légèrement aléatoire
"""
word = text.strip()
color = color_for_word(word)
# Mise en forme du texte
# Création du clip texte de base
txt_clip = TextClip(
text=text,
font=FONT_PATH,
font_size=100,
color=color,
stroke_color="black",
stroke_width=6,
method="caption",
size=(int(video_w * 0.8), None), # 80% de la largeur, hauteur auto
text_align="center", # alignement dans la box
horizontal_align="center", # box centrée horizontalement
vertical_align="center", # box centrée verticalement
interline=4,
transparent=True
)
y_choices = [int(video_h * 0.45), int(video_h * 0.55), int(video_h * 0.6)]
base_y = random.choice(y_choices)
txt_clip = txt_clip.with_position(("center", base_y))
txt_clip = txt_clip.with_start(start).with_end(end)
# On applique un fadein + un petit effet "pop" qui grandit de 5% sur la durée du chunk
# 1) fadein de 0.2s
clip_fadein = FadeIn(duration=0.2).apply(txt_clip)
# 2) agrandissement progressif (ex: 1.0 → 1.05 sur la durée)
duration_subtitle = end - start
def pop_effect(t):
if duration_subtitle > 0:
progress = t / duration_subtitle
scale = 1.0 + 0.07 * (1 - (1 - progress) ** 3) # easing out cubic
else:
scale = 1.0
return scale
resize_effect = Resize(pop_effect)
clip_pop = resize_effect.apply(clip_fadein) # ✅ Utilisation correcte
return clip_pop
def add_subtitles_to_video(video_path, subtitles, output_file="./assets/output/video_with_subs.mp4"):
"""
Insère les sous-titres animés/couleur dans la vidéo,
recadre en 1080x1920 si besoin et exporte le résultat.
"""
print("🎬 Insertion des sous-titres optimisés SHORTS...")
video = VideoFileClip(video_path)
# Force le format vertical 1080×1920 si non conforme
if (video.w, video.h) != (1080, 1920):
print("📐 Recadrage vidéo en 1080×1920...")
video = video.resize((1080, 1920))
clips = [video]
for sub in subtitles:
start_time = sub['start']
end_time = sub['end']
text_chunk = sub['text']
animated_sub_clip = create_animated_subtitle_clip(
text_chunk, start_time, end_time, video_w=video.w, video_h=video.h
)
clips.append(animated_sub_clip)
final = CompositeVideoClip(clips, size=(1080, 1920)).with_duration(video.duration)
# Export en MP4 H.264 + AAC, 30 fps
final.write_videofile(
output_file,
codec="libx264",
audio_codec="aac",
fps=30,
threads=4,
preset="medium",
ffmpeg_params=["-pix_fmt", "yuv420p"]
)
print(f"✅ Vidéo Shorts/TikTok prête : {output_file}")
|