Upload 93 files
Browse files- app.py +42 -41
- utils.py +141 -0
- videos/curtmetratge_4/MoE/une_ad.srt +11 -11
- videos/curtmetratge_4/Salamandra/une_ad.srt +4 -4
app.py
CHANGED
|
@@ -12,10 +12,15 @@ except ModuleNotFoundError: # Py<3.11
|
|
| 12 |
import streamlit as st
|
| 13 |
from moviepy.editor import VideoFileClip
|
| 14 |
|
|
|
|
| 15 |
from database import set_db_path, init_schema, get_user, create_video, update_video_status, list_videos, get_video, get_all_users, upsert_result, get_results, add_feedback, get_feedback_for_video, get_feedback_stats
|
| 16 |
from api_client import APIClient
|
| 17 |
from utils import ensure_dirs, save_bytes, save_text, human_size
|
| 18 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
# -- Move DB ---
|
| 21 |
os.environ["STREAMLIT_DATA_DIRECTORY"] = "/tmp/.streamlit"
|
|
@@ -98,7 +103,7 @@ with st.sidebar:
|
|
| 98 |
st.session_state.user = None
|
| 99 |
st.rerun()
|
| 100 |
if st.session_state.user:
|
| 101 |
-
page = st.radio("Navegació", ["Analitzar
|
| 102 |
else:
|
| 103 |
page = None
|
| 104 |
|
|
@@ -245,9 +250,9 @@ if page == "Processar vídeo nou":
|
|
| 245 |
if st.session_state.characters_saved:
|
| 246 |
st.button("Generar Audiodescripció")
|
| 247 |
|
| 248 |
-
elif page == "Analitzar
|
| 249 |
require_login()
|
| 250 |
-
st.header("Analitzar
|
| 251 |
base_dir = Path("/tmp/data/videos")
|
| 252 |
|
| 253 |
if not base_dir.exists():
|
|
@@ -321,39 +326,28 @@ elif page == "Analitzar video-transcripcions":
|
|
| 321 |
c1, c2 = st.columns(2)
|
| 322 |
with c1:
|
| 323 |
if st.button("Reconstruir àudio amb narració lliure", use_container_width=True, key="rebuild_free_ad"):
|
| 324 |
-
if subcarpeta_seleccio:
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
save_bytes(output_path, response["mp3_bytes"])
|
| 334 |
-
st.success(f"Àudio generat i desat a: {output_path}")
|
| 335 |
-
else:
|
| 336 |
-
st.error(f"Error en la generació de l'àudio: {response.get('error', 'Desconegut')}")
|
| 337 |
-
else:
|
| 338 |
-
st.warning("No s'ha trobat el fitxer 'free_ad.txt' en aquesta versió.")
|
| 339 |
|
| 340 |
with c2:
|
| 341 |
if st.button("Reconstruir vídeo amb audiodescripció", use_container_width=True, key="rebuild_video_ad"):
|
| 342 |
-
if
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
st.info("Pots visualitzar-lo activant la casella 'Afegir audiodescripció' i seleccionant el nou fitxer si cal.")
|
| 353 |
-
else:
|
| 354 |
-
st.error(f"Error en la reconstrucció del vídeo: {response.get('error', 'Desconegut')}")
|
| 355 |
-
else:
|
| 356 |
-
st.warning("No s'ha trobat el fitxer 'une_ad.srt' en aquesta versió.")
|
| 357 |
|
| 358 |
|
| 359 |
# --- Columna Derecha (Editor de texto y guardado) ---
|
|
@@ -376,24 +370,31 @@ elif page == "Analitzar video-transcripcions":
|
|
| 376 |
else:
|
| 377 |
st.info(f"No s'ha trobat el fitxer **{ad_filename}**.")
|
| 378 |
else:
|
| 379 |
-
|
|
|
|
| 380 |
|
| 381 |
# Área de texto para edición
|
| 382 |
new_text = st.text_area(f"Contingut de {tipus_ad_seleccio}", value=text_content, height=500, key=f"editor_{seleccio}_{subcarpeta_seleccio}_{ad_filename}")
|
| 383 |
|
| 384 |
-
# Controles de reproducción de narración
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 389 |
|
| 390 |
# Botón de guardado
|
| 391 |
if st.button("Desar canvis", use_container_width=True, type="primary"):
|
| 392 |
if ad_path:
|
| 393 |
try:
|
| 394 |
-
|
| 395 |
st.success(f"Fitxer **{ad_filename}** desat correctament.")
|
| 396 |
-
|
|
|
|
|
|
|
| 397 |
except Exception as e:
|
| 398 |
st.error(f"No s'ha pogut desar el fitxer: {e}")
|
| 399 |
else:
|
|
|
|
| 12 |
import streamlit as st
|
| 13 |
from moviepy.editor import VideoFileClip
|
| 14 |
|
| 15 |
+
import sys
|
| 16 |
from database import set_db_path, init_schema, get_user, create_video, update_video_status, list_videos, get_video, get_all_users, upsert_result, get_results, add_feedback, get_feedback_for_video, get_feedback_stats
|
| 17 |
from api_client import APIClient
|
| 18 |
from utils import ensure_dirs, save_bytes, save_text, human_size
|
| 19 |
|
| 20 |
+
# Añadir la carpeta de scripts al path para poder importar el cliente
|
| 21 |
+
sys.path.append(str(Path(__file__).parent / "scripts"))
|
| 22 |
+
from client_generate_av import generate_free_ad_mp3, generate_une_ad_video
|
| 23 |
+
|
| 24 |
|
| 25 |
# -- Move DB ---
|
| 26 |
os.environ["STREAMLIT_DATA_DIRECTORY"] = "/tmp/.streamlit"
|
|
|
|
| 103 |
st.session_state.user = None
|
| 104 |
st.rerun()
|
| 105 |
if st.session_state.user:
|
| 106 |
+
page = st.radio("Navegació", ["Analitzar audio-descripcions","Processar vídeo nou","Estadístiques"], index=0)
|
| 107 |
else:
|
| 108 |
page = None
|
| 109 |
|
|
|
|
| 250 |
if st.session_state.characters_saved:
|
| 251 |
st.button("Generar Audiodescripció")
|
| 252 |
|
| 253 |
+
elif page == "Analitzar audio-descripcions":
|
| 254 |
require_login()
|
| 255 |
+
st.header("Analitzar audio-descripcions")
|
| 256 |
base_dir = Path("/tmp/data/videos")
|
| 257 |
|
| 258 |
if not base_dir.exists():
|
|
|
|
| 326 |
c1, c2 = st.columns(2)
|
| 327 |
with c1:
|
| 328 |
if st.button("Reconstruir àudio amb narració lliure", use_container_width=True, key="rebuild_free_ad"):
|
| 329 |
+
if seleccio and subcarpeta_seleccio:
|
| 330 |
+
with st.spinner("Generant àudio de la narració lliure..."):
|
| 331 |
+
result = generate_free_ad_mp3(seleccio, subcarpeta_seleccio, api)
|
| 332 |
+
if result.get("status") == "success":
|
| 333 |
+
st.success(f"Àudio generat amb èxit: {result.get('path')}")
|
| 334 |
+
else:
|
| 335 |
+
st.error(f"Error: {result.get('reason', 'Desconegut')}")
|
| 336 |
+
else:
|
| 337 |
+
st.warning("Selecciona un vídeo i una versió.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 338 |
|
| 339 |
with c2:
|
| 340 |
if st.button("Reconstruir vídeo amb audiodescripció", use_container_width=True, key="rebuild_video_ad"):
|
| 341 |
+
if seleccio and subcarpeta_seleccio:
|
| 342 |
+
with st.spinner("Reconstruint el vídeo... Aquesta operació pot trigar."):
|
| 343 |
+
result = generate_une_ad_video(seleccio, subcarpeta_seleccio, api)
|
| 344 |
+
if result.get("status") == "success":
|
| 345 |
+
st.success(f"Vídeo generat amb èxit: {result.get('path')}")
|
| 346 |
+
st.info("Pots visualitzar-lo activant la casella 'Afegir audiodescripció'.")
|
| 347 |
+
else:
|
| 348 |
+
st.error(f"Error: {result.get('reason', 'Desconegut')}")
|
| 349 |
+
else:
|
| 350 |
+
st.warning("Selecciona un vídeo i una versió.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 351 |
|
| 352 |
|
| 353 |
# --- Columna Derecha (Editor de texto y guardado) ---
|
|
|
|
| 370 |
else:
|
| 371 |
st.info(f"No s'ha trobat el fitxer **{ad_filename}**.")
|
| 372 |
else:
|
| 373 |
+
# Eliminada la nota de advertencia
|
| 374 |
+
pass
|
| 375 |
|
| 376 |
# Área de texto para edición
|
| 377 |
new_text = st.text_area(f"Contingut de {tipus_ad_seleccio}", value=text_content, height=500, key=f"editor_{seleccio}_{subcarpeta_seleccio}_{ad_filename}")
|
| 378 |
|
| 379 |
+
# Controles de reproducción de narración
|
| 380 |
+
free_ad_mp3_path = vid_dir / subcarpeta_seleccio / "free_ad.mp3" if seleccio and subcarpeta_seleccio else None
|
| 381 |
+
can_play_free_ad = free_ad_mp3_path is not None and free_ad_mp3_path.exists()
|
| 382 |
+
|
| 383 |
+
if st.button("▶️ Reproduir narració lliure", use_container_width=True, disabled=not can_play_free_ad, key="play_button_editor"):
|
| 384 |
+
if can_play_free_ad:
|
| 385 |
+
st.audio(str(free_ad_mp3_path), format="audio/mp3")
|
| 386 |
+
else:
|
| 387 |
+
st.warning("No s'ha trobat el fitxer 'free_ad.mp3'. Reconstrueix l'àudio primer.")
|
| 388 |
|
| 389 |
# Botón de guardado
|
| 390 |
if st.button("Desar canvis", use_container_width=True, type="primary"):
|
| 391 |
if ad_path:
|
| 392 |
try:
|
| 393 |
+
save_text(ad_path, new_text)
|
| 394 |
st.success(f"Fitxer **{ad_filename}** desat correctament.")
|
| 395 |
+
# Forzar la recarga del contenido en el text_area
|
| 396 |
+
st.session_state[f"editor_{seleccio}_{subcarpeta_seleccio}_{ad_filename}"] = new_text
|
| 397 |
+
st.rerun() # Opcional, si quieres recargar toda la UI
|
| 398 |
except Exception as e:
|
| 399 |
st.error(f"No s'ha pogut desar el fitxer: {e}")
|
| 400 |
else:
|
utils.py
CHANGED
|
@@ -5,6 +5,11 @@ import subprocess
|
|
| 5 |
from pathlib import Path
|
| 6 |
from dataclasses import dataclass
|
| 7 |
import shlex # Para manejar argumentos de línea de comandos de forma segura
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
|
| 10 |
def incrustar_subtitulos_ffmpeg(
|
|
@@ -152,6 +157,142 @@ def recortar_video(input_path: str, output_path: str, duracion_segundos: int = 2
|
|
| 152 |
subprocess.run(cmd, check=True)
|
| 153 |
|
| 154 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
#----------------------------
|
| 156 |
|
| 157 |
if __name__ == "__main__":
|
|
|
|
| 5 |
from pathlib import Path
|
| 6 |
from dataclasses import dataclass
|
| 7 |
import shlex # Para manejar argumentos de línea de comandos de forma segura
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
from dataclasses import dataclass
|
| 10 |
+
from typing import List, Optional, Callable
|
| 11 |
+
import re
|
| 12 |
+
import xml.etree.ElementTree as ET
|
| 13 |
|
| 14 |
|
| 15 |
def incrustar_subtitulos_ffmpeg(
|
|
|
|
| 157 |
subprocess.run(cmd, check=True)
|
| 158 |
|
| 159 |
|
| 160 |
+
# ---- Núcleo: SRT -> ESF (XML string) ----
|
| 161 |
+
|
| 162 |
+
TIME_RE = re.compile(
|
| 163 |
+
r"(?P<start>\d{2}:\d{2}:\d{2}[,\.]\d{3})\s*-->\s*(?P<end>\d{2}:\d{2}:\d{2}[,\.]\d{3})"
|
| 164 |
+
)
|
| 165 |
+
|
| 166 |
+
@dataclass
|
| 167 |
+
class Cue:
|
| 168 |
+
index: int
|
| 169 |
+
start: str # "HH:MM:SS.mmm"
|
| 170 |
+
end: str # "HH:MM:SS.mmm"
|
| 171 |
+
text: str
|
| 172 |
+
|
| 173 |
+
def _norm_ts(ts: str) -> str:
|
| 174 |
+
"""Convierte '01:02:03,456' -> '01:02:03.456'."""
|
| 175 |
+
return ts.replace(",", ".")
|
| 176 |
+
|
| 177 |
+
def _parse_srt(srt_text: str) -> List[Cue]:
|
| 178 |
+
"""Parsea SRT a una lista de cues normalizados."""
|
| 179 |
+
srt_text = srt_text.replace("\r\n", "\n").replace("\r", "\n")
|
| 180 |
+
blocks = [b.strip() for b in re.split(r"\n\s*\n", srt_text) if b.strip()]
|
| 181 |
+
cues: List[Cue] = []
|
| 182 |
+
|
| 183 |
+
for block in blocks:
|
| 184 |
+
lines = block.split("\n")
|
| 185 |
+
# Detectar si la primera línea es índice
|
| 186 |
+
idx = None
|
| 187 |
+
if lines and lines[0].strip().isdigit():
|
| 188 |
+
idx = int(lines[0].strip())
|
| 189 |
+
time_candidates = lines[1:]
|
| 190 |
+
else:
|
| 191 |
+
idx = len(cues) + 1
|
| 192 |
+
time_candidates = lines
|
| 193 |
+
|
| 194 |
+
m = None
|
| 195 |
+
time_line_idx = None
|
| 196 |
+
for i, ln in enumerate(time_candidates[:3]): # robustez
|
| 197 |
+
mm = TIME_RE.search(ln)
|
| 198 |
+
if mm:
|
| 199 |
+
m = mm
|
| 200 |
+
time_line_idx = i
|
| 201 |
+
break
|
| 202 |
+
if not m:
|
| 203 |
+
raise ValueError(f"Bloque SRT sin tiempos válidos (índice {idx}):\n{block}")
|
| 204 |
+
|
| 205 |
+
start = _norm_ts(m.group("start"))
|
| 206 |
+
end = _norm_ts(m.group("end"))
|
| 207 |
+
text_lines = time_candidates[time_line_idx + 1 :]
|
| 208 |
+
text = "\n".join(text_lines).strip()
|
| 209 |
+
|
| 210 |
+
cues.append(Cue(index=idx, start=start, end=end, text=text))
|
| 211 |
+
|
| 212 |
+
# Re-indexar por si venía desordenado
|
| 213 |
+
for i, c in enumerate(cues, 1):
|
| 214 |
+
c.index = i
|
| 215 |
+
return cues
|
| 216 |
+
|
| 217 |
+
def _build_esf_tree(
|
| 218 |
+
cues: List[Cue],
|
| 219 |
+
language: str = "es",
|
| 220 |
+
voice_db: float = -6.0,
|
| 221 |
+
original_db: float = -3.0,
|
| 222 |
+
audio_lookup: Optional[Callable[[int], Optional[str]]] = None,
|
| 223 |
+
) -> ET.ElementTree:
|
| 224 |
+
"""
|
| 225 |
+
Construye el árbol XML ESF.
|
| 226 |
+
audio_lookup: función opcional index->filename (p. ej., lambda i: f\"{i:03d}.wav\" si existe).
|
| 227 |
+
"""
|
| 228 |
+
root = ET.Element("esef", attrib={"version": "1.0"})
|
| 229 |
+
header = ET.SubElement(root, "header")
|
| 230 |
+
ET.SubElement(header, "language").text = language
|
| 231 |
+
mix = ET.SubElement(header, "mix")
|
| 232 |
+
ET.SubElement(mix, "voice", attrib={"level": f"{voice_db}dB"})
|
| 233 |
+
ET.SubElement(mix, "original", attrib={"level": f"{original_db}dB"})
|
| 234 |
+
|
| 235 |
+
ad = ET.SubElement(root, "ad")
|
| 236 |
+
for c in cues:
|
| 237 |
+
attrs = {"in": c.start, "out": c.end}
|
| 238 |
+
if audio_lookup:
|
| 239 |
+
fname = audio_lookup(c.index)
|
| 240 |
+
if fname:
|
| 241 |
+
attrs["file"] = fname
|
| 242 |
+
cue_el = ET.SubElement(ad, "cue", attrib=attrs)
|
| 243 |
+
cue_el.text = c.text
|
| 244 |
+
return ET.ElementTree(root)
|
| 245 |
+
|
| 246 |
+
def _xml_pretty_string(tree: ET.ElementTree) -> str:
|
| 247 |
+
"""Devuelve XML con sangría (sin depender de minidom)."""
|
| 248 |
+
def _indent(elem, level=0):
|
| 249 |
+
i = "\n" + level * " "
|
| 250 |
+
if len(elem):
|
| 251 |
+
if not elem.text or not elem.text.strip():
|
| 252 |
+
elem.text = i + " "
|
| 253 |
+
for e in elem:
|
| 254 |
+
_indent(e, level + 1)
|
| 255 |
+
if not e.tail or not e.tail.strip():
|
| 256 |
+
e.tail = i
|
| 257 |
+
if level and (not elem.tail or not elem.tail.strip()):
|
| 258 |
+
elem.tail = i
|
| 259 |
+
root = tree.getroot()
|
| 260 |
+
_indent(root)
|
| 261 |
+
return ET.tostring(root, encoding="utf-8", xml_declaration=True).decode("utf-8")
|
| 262 |
+
|
| 263 |
+
def srt_to_esf(
|
| 264 |
+
srt_text: str,
|
| 265 |
+
*,
|
| 266 |
+
language: str = "es",
|
| 267 |
+
voice_db: float = -6.0,
|
| 268 |
+
original_db: float = -3.0,
|
| 269 |
+
audio_lookup: Optional[Callable[[int], Optional[str]]] = None,
|
| 270 |
+
) -> str:
|
| 271 |
+
"""
|
| 272 |
+
Convierte un SRT (texto) en un ESF (XML) y lo devuelve como string.
|
| 273 |
+
|
| 274 |
+
Parámetros:
|
| 275 |
+
srt_text: Contenido del .srt.
|
| 276 |
+
language: Código ISO del idioma (por defecto 'es').
|
| 277 |
+
voice_db: Nivel de la voz AD (dB).
|
| 278 |
+
original_db: Nivel del audio original (dB).
|
| 279 |
+
audio_lookup: Función opcional index->filename para asociar locuciones por cue.
|
| 280 |
+
|
| 281 |
+
Retorna:
|
| 282 |
+
Cadena XML ESF.
|
| 283 |
+
"""
|
| 284 |
+
cues = _parse_srt(srt_text)
|
| 285 |
+
tree = _build_esf_tree(
|
| 286 |
+
cues,
|
| 287 |
+
language=language,
|
| 288 |
+
voice_db=voice_db,
|
| 289 |
+
original_db=original_db,
|
| 290 |
+
audio_lookup=audio_lookup,
|
| 291 |
+
)
|
| 292 |
+
return _xml_pretty_string(tree)
|
| 293 |
+
|
| 294 |
+
|
| 295 |
+
|
| 296 |
#----------------------------
|
| 297 |
|
| 298 |
if __name__ == "__main__":
|
videos/curtmetratge_4/MoE/une_ad.srt
CHANGED
|
@@ -1,26 +1,26 @@
|
|
| 1 |
1
|
| 2 |
-
00:00:00,000 --> 00:00:
|
| 3 |
-
(AD): "
|
| 4 |
|
| 5 |
2
|
| 6 |
-
00:00:
|
| 7 |
-
(AD): ""
|
| 8 |
|
| 9 |
3
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
00:00:11,134 --> 00:00:12,044
|
| 11 |
[Sento]: bon dia neus
|
| 12 |
|
| 13 |
-
|
| 14 |
00:00:12,044 --> 00:00:12,653
|
| 15 |
[Neus]: bon dia gràcies
|
| 16 |
|
| 17 |
-
5
|
| 18 |
-
00:00:12,653 --> 00:00:15,919
|
| 19 |
-
(AD): "Neus, amb una samarreta, llegeix un diari."
|
| 20 |
-
|
| 21 |
6
|
| 22 |
-
00:00:
|
| 23 |
-
(AD): ""
|
| 24 |
|
| 25 |
7
|
| 26 |
00:00:16,568 --> 00:00:18,777
|
|
|
|
| 1 |
1
|
| 2 |
+
00:00:00,000 --> 00:00:01,020
|
| 3 |
+
(AD): "Títol: No puc."
|
| 4 |
|
| 5 |
2
|
| 6 |
+
00:00:01,020 --> 00:00:04,121
|
| 7 |
+
(AD): "Títol: No puc."
|
| 8 |
|
| 9 |
3
|
| 10 |
+
00:00:04,121 --> 00:00:11,134
|
| 11 |
+
(AD): "Neus es troba en un sofà, amb la llum apagada, concentrada en la pantalla del seu telèfon mòbil, reflexionant."
|
| 12 |
+
|
| 13 |
+
4
|
| 14 |
00:00:11,134 --> 00:00:12,044
|
| 15 |
[Sento]: bon dia neus
|
| 16 |
|
| 17 |
+
5
|
| 18 |
00:00:12,044 --> 00:00:12,653
|
| 19 |
[Neus]: bon dia gràcies
|
| 20 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
6
|
| 22 |
+
00:00:12,653 --> 00:00:16,568
|
| 23 |
+
(AD): "Neus, amb una samarreta, llegeix un diari."
|
| 24 |
|
| 25 |
7
|
| 26 |
00:00:16,568 --> 00:00:18,777
|
videos/curtmetratge_4/Salamandra/une_ad.srt
CHANGED
|
@@ -1,10 +1,10 @@
|
|
| 1 |
1
|
| 2 |
-
00:00:
|
| 3 |
-
(AD):
|
| 4 |
|
| 5 |
2
|
| 6 |
-
00:00:
|
| 7 |
-
(AD): Una
|
| 8 |
|
| 9 |
3
|
| 10 |
00:00:11,134 --> 00:00:12,044
|
|
|
|
| 1 |
1
|
| 2 |
+
00:00:01,000 --> 00:00:03,000
|
| 3 |
+
(AD): "No puc."
|
| 4 |
|
| 5 |
2
|
| 6 |
+
00:00:03,000 --> 00:00:11,134
|
| 7 |
+
(AD): "Una dona amb el llum apagat, asseguda en un sofà, mirant el seu telèfon."
|
| 8 |
|
| 9 |
3
|
| 10 |
00:00:11,134 --> 00:00:12,044
|