Sergio Massa y #LaGente

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 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:

In [1]:
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
Out[1]:
['https://www.youtube.com/watch?v=8pP8G3fSAcY',
 'https://www.youtube.com/watch?v=g6QSwxUo1aw',
 'https://www.youtube.com/watch?v=_9FN6CI8fD4',
 'https://www.youtube.com/watch?v=5wqwNDpkZOo',
 'https://www.youtube.com/watch?v=V865E4mBiHU',
 'https://www.youtube.com/watch?v=TPrGNJnMS9U',
 'https://www.youtube.com/watch?v=SVTl11hG9Gs',
 'https://www.youtube.com/watch?v=Df_dwb5XHQM',
 'https://www.youtube.com/watch?v=sptBkyfq1VU',
 'https://www.youtube.com/watch?v=tzjz1xrNu3k',
 'https://www.youtube.com/watch?v=k-CGbuOo8do',
 'https://www.youtube.com/watch?v=_L-B_wHsEec',
 'https://www.youtube.com/watch?v=iFOABIQdo9Q',
 'https://www.youtube.com/watch?v=WOlRIKGrBWY',
 'https://www.youtube.com/watch?v=a-mCgN6W9ek',
 'https://www.youtube.com/watch?v=x5vhchv3zAY',
 'https://www.youtube.com/watch?v=bi5eK7i59w0',
 'https://www.youtube.com/watch?v=VNHV3D_6o4E',
 'https://www.youtube.com/watch?v=MWVZ6JDU9V8',
 'https://www.youtube.com/watch?v=v-JmdgVZqVc',
 'https://www.youtube.com/watch?v=FBFHpdxsyYU',
 'https://www.youtube.com/watch?v=WXmTc83l1sQ',
 'https://www.youtube.com/watch?v=GfNgds5vS60',
 'https://www.youtube.com/watch?v=UHRa34A6rDg',
 'https://www.youtube.com/watch?v=xVU-EjnuksU',
 'https://www.youtube.com/watch?v=-IXymTZZM6o',
 'https://www.youtube.com/watch?v=tzvwDTPyTHQ',
 'https://www.youtube.com/watch?v=a19z6EVWpQ4',
 'https://www.youtube.com/watch?v=rAOvF8X_nzM',
 'https://www.youtube.com/watch?v=wtvl4esdMGU',
 'https://www.youtube.com/watch?v=1YPHDDH1Az0',
 'https://www.youtube.com/watch?v=w7TnghsrJUo',
 'https://www.youtube.com/watch?v=qBT-6HpSrwc',
 'https://www.youtube.com/watch?v=JM-xblTxLGc',
 'https://www.youtube.com/watch?v=kMymsVsmETY',
 'https://www.youtube.com/watch?v=K1-dfiVfbOI',
 'https://www.youtube.com/watch?v=VnoiHVlR-So',
 'https://www.youtube.com/watch?v=hMTzJyLiXE4',
 'https://www.youtube.com/watch?v=VGQPNQ1Bhkg',
 'https://www.youtube.com/watch?v=0oR4z7SsY14',
 'https://www.youtube.com/watch?v=Cl4r8h_Hlak',
 'https://www.youtube.com/watch?v=gJFmek-YgYo',
 'https://www.youtube.com/watch?v=9VQ7Ov5W_tM',
 'https://www.youtube.com/watch?v=rKwKImVrYu4',
 'https://www.youtube.com/watch?v=LJwj9SHC9EU',
 'https://www.youtube.com/watch?v=-08OEpFThiw',
 'https://www.youtube.com/watch?v=BPJBl5y2P2g',
 'https://www.youtube.com/watch?v=MvkXlg9ZbL4',
 'https://www.youtube.com/watch?v=7KgIa4fX_Ng',
 'https://www.youtube.com/watch?v=upNLrHtzeBI',
 'https://www.youtube.com/watch?v=Y-norf1BKAs',
 'https://www.youtube.com/watch?v=QMvAl_fxQSA',
 'https://www.youtube.com/watch?v=3os_uXUOvcM',
 'https://www.youtube.com/watch?v=ZE_aChIEELo',
 'https://www.youtube.com/watch?v=iKI-8ceuR-A',
 'https://www.youtube.com/watch?v=CASdYLquQII',
 'https://www.youtube.com/watch?v=5cvyi1CcpYs',
 'https://www.youtube.com/watch?v=NVEw-YIAy5A',
 'https://www.youtube.com/watch?v=yMXn04-GQTY',
 'https://www.youtube.com/watch?v=RCCzZGcGg5k',
 'https://www.youtube.com/watch?v=FqMFKGsXLOE',
 'https://www.youtube.com/watch?v=MVOvQb8KBm0',
 'https://www.youtube.com/watch?v=ENvWfMwnJ_0',
 'https://www.youtube.com/watch?v=bs7xGm293Vs',
 'https://www.youtube.com/watch?v=7OvrK-U-axI',
 'https://www.youtube.com/watch?v=VHeWqPqs4vo',
 'https://www.youtube.com/watch?v=nVOEi9FESn8',
 'https://www.youtube.com/watch?v=eikTWAvFwTE',
 'https://www.youtube.com/watch?v=BU2amn3QdWk',
 'https://www.youtube.com/watch?v=GiB1pOuEvqg',
 'https://www.youtube.com/watch?v=GAPN17lTJ9c',
 'https://www.youtube.com/watch?v=4Ja1uZbMM8E',
 'https://www.youtube.com/watch?v=F1dAfCR4rc0',
 'https://www.youtube.com/watch?v=334O9xh-CQY',
 'https://www.youtube.com/watch?v=KgNmw3sJ0g8',
 'https://www.youtube.com/watch?v=-SQSue4-PLk',
 'https://www.youtube.com/watch?v=HPE4PHlYySo']

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.

In [2]:
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)

In [3]:
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.

In [4]:
gente = get_all_subclips_for('la gente')
len(gente)
Out[4]:
77

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.

In [5]:
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)    
In [6]:
make_preview(gente)
[MoviePy] >>>> Building video preview.webm
[MoviePy] Writing audio in previewTEMP_MPY_wvf_snd.ogg
[MoviePy] Done.
[MoviePy] Writing video preview.webm
[MoviePy] Done.
[MoviePy] >>>> Video ready: preview.webm 

El resultado me permitió hacer el tamizado

In [7]:
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

In [8]:
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)
In [9]:
# le damos una mezcladita más
random.shuffle(subclips_cleaned)
subclips_cleaned.append(ending)
make_final(subclips_cleaned, 'massa_lagente_final.webm')
[MoviePy] >>>> Building video massa_lagente_final.webm
[MoviePy] Writing audio in massa_lagente_finalTEMP_MPY_wvf_snd.ogg
[MoviePy] Done.
[MoviePy] Writing video massa_lagente_final.webm
[MoviePy] Done.
[MoviePy] >>>> Video ready: massa_lagente_final.webm 

Y este es el resultado:

In [ ]:
 

El código de este artículo está disponible en github. ¿Encontraste un error? Por favor, enviame un pull request.

Comentarios

Comments powered by Disqus