Anoche me reí mucho con el hashtag #LaGente, que se viralizó mientras Alejandro Fantino entrevistaba, una vez más, al inefable candidato presidencial Sergio Massa.
"Me comprometo no ante vos, ante #LaGente". Lo harta que me tiene este tipo no tiene nombre.
— Fenicia (@zeinicienta) marzo 12, 2015
Es insoportable la cantidad de frases hechas y lugares comunes que tira @SergioMassa. Demagogo berreta. #LaGente
— Javier Smaldone (@mis2centavos) marzo 12, 2015
Quiero contar la cantidad de veces que +a dice #LaGente
— memorex (@memorex) marzo 12, 2015
Me acordé entonces de un post de Zulko, cuyo blog es un compilado de gemas ñoñamente divertidas. Allí muestra cómo recortar automáticamente los pedacitos de un video que mencionen una palabra o frase, basándose en las marcas de tiempo del archivo de subtítulos, utilizando su maravillosa biblioteca Moviepy y un poco de Python. Más o menos lo que hace videogrep, pero más prolijo.
La herramienta youtube-dl (que también es genial y hecha en Python), permite no sólo bajar videos de youtube y los subtitulos existentes, sino que también puede bajar el "subtitulo automático". En general son bastante malos pero es suficientemente efectivo para encontrar pequeñas frases.
Todo sea por "la gente": manos a la obra¶
Lo primero que necesitamos es una lista de videos donde Sergio Massa hable. Hice una búsqueda, decidí ignorar algunos (parodias, por ejemplo) y generé una lista. Hay varias maneras de obtener este listado de las primeras paginas de resultados, yo utilicé el rústico y efectivo webscrapping:
from pyquery import PyQuery
links = []
skip = ('M0yuFHbhYLY','TLmMh9Qvmic', 'rY4Hwvn6GlA')
for page in range(1, 5):
pq = PyQuery('https://www.youtube.com/results?search_query=entrevista+sergio+massa&page=%s' % page)
pq.make_links_absolute()
links.extend([pq(a).attr('href') for a in pq('a.yt-uix-tile-link') if pq(a).attr('href').split('v=')[1] not in skip])
links
Luego, el paso lento: bajar los videos. Al parecer, Youtube no genera un subtitulo automático para videos demasiado largo, así que limité hasta 30 minutos.
for link in links:
!youtube-dl --write-auto-sub --sub-lang es --max-filesize 30.00m {link}
Con el material crudo disponible (aunque puede ser que no se hayan encontrado subtitulos para todos los videos), podemos copiar descaradamente partes del código de Zulko (levemente adaptado)
import re
import os
import glob
import random
from moviepy.editor import VideoFileClip, concatenate, TextClip, CompositeVideoClip
def convert_time(timestring):
""" Converts a string into seconds """
nums = [float(t) for t in re.findall(r'\d+', timestring)]
return 3600 * nums[0] + 60*nums[1] + nums[2] + nums[3]/1000
def get_time_texts(file):
with open(file) as f:
lines = f.readlines()
times_texts = []
current_times , current_text = None, ""
for line in lines:
times = re.findall("[0-9]*:[0-9]*:[0-9]*,[0-9]*", line)
if times != []:
current_times = [convert_time(t) for t in times]
elif line == '\n':
times_texts.append((current_times, current_text))
current_times, current_text = None, ""
elif current_times is not None:
current_text = current_text + line.replace("\n"," ")
return times_texts
def find_word(word, times_texts, padding=.4):
""" Finds all 'exact' (t_start, t_end) for a word """
matches = [re.search(word, text)
for (t,text) in times_texts]
return [(t1 + m.start()*(t2-t1)/len(text) - padding,
t1 + m.end()*(t2-t1)/len(text) + padding)
for m,((t1,t2),text) in zip(matches, times_texts)
if (m is not None)]
def get_subclips(video_path, cuts):
video = VideoFileClip(video_path)
return [video.subclip(start, end) for (start,end) in cuts]
def get_all_subclips_for(word, pattern='*.mp4', sub_ext='.es.srt', shuffle=True):
subclips = []
for mp4 in glob.glob(pattern):
sub = os.path.splitext(mp4)[0] + sub_ext
try:
times = find_word(word, get_time_texts(sub))
except IOError:
# ignore video if it hasn't subtitle
continue
cuts = get_subclips(mp4, times)
subclips.extend(cuts)
if shuffle:
random.shuffle(subclips)
return subclips
La función get_all_subclip
recibe la frase a buscar y devuelve un listado de segmentos donde, muy probablemente, se pronuncia.
gente = get_all_subclips_for('la gente')
len(gente)
El problema es que aunque es muy probable que sea Sergio Massa el que diga "la gente" en sus entrevistas, a veces es el entrevistador, a veces youtube entendió mal al desgrabar y a veces el código recortador la pifia. Por este motivo hay que descartar los segmentos que no sirven.
Se me ocurrió hacerlo visualmente: los pegué todos, superponiendo el índice al que corresponde cada segmento, para luego anotar los que no sirven y filtrarlos en otra pasada.
def make_preview(subclips):
subclips_ = []
for (i, clip) in enumerate(subclips):
txt_clip = TextClip(str(i),fontsize=70, color='white')
txt_clip = txt_clip.set_pos('center').set_duration(clip.duration)
clip = CompositeVideoClip([clip, txt_clip])
subclips_.append(clip)
final = concatenate(subclips_, method='compose')
final.write_videofile('preview.webm', codec='libvpx', fps=24)
make_preview(gente)
El resultado me permitió hacer el tamizado
ignore = [2, 3, 8, 12, 17, 19, 25, 28, 32, 36, 38, 40, 41, 44, 49, 55, 56, 61, 62, 66, 73, 74]
subclips_cleaned = [i for j, i in enumerate(gente) if j not in ignore]
Aunque no tengo idea de edición de videos, y porque de verdad creo que es un demamogo impresentable que no debería presidir ni una junta vecinal, quería darle un toque final, con una pequeña frase
import numpy as np
from moviepy.video.tools.segmenting import findObjects
def arrive(screenpos,i,nletters):
v = np.array([-1,0])
d = lambda t : max(0, 3-3*t)
return lambda t: screenpos-400*v*d(t-0.2*i)
screensize = (640,360)
txtClip = TextClip('Yn tragr ab rf obyhqn'.decode('rot13'), color='white', font="Amiri-Bold", kerning=5, fontsize=50)
cvc = CompositeVideoClip( [txtClip.set_pos('center')],
size=screensize)
letters = findObjects(cvc) # a list of ImageClips
def moveLetters(letters, funcpos):
return [ letter.set_pos(funcpos(letter.screenpos,i,len(letters)))
for i,letter in enumerate(letters)]
ending = CompositeVideoClip(moveLetters(letters, arrive), size=screensize).subclip(0, 10)
# le damos una mezcladita más
random.shuffle(subclips_cleaned)
subclips_cleaned.append(ending)
make_final(subclips_cleaned, 'massa_lagente_final.webm')
Y este es el resultado:
Comentarios
Comments powered by Disqus