EITB nahieran: askatu bideoak API honen bidez
by Mikel Larreategi
Mikel Larreategi naiz, ezagutzen ez nauzuenontzat
CodeSyntaxen egiten dut lan webguneak, intranetak eta
web aplikazioak egiten, hasiera hasieratik (nik 2004tik
eta enpresak 2001etik) python erabiliz horretarako.
Gehien Plone izeneko edukiak kudeatzeko sistema
erabiltzen dut, Zopen oinarritutako sistema ahaltsua
baina Django ere gero eta gehiago egiten dut.
Baina egia esateko Pythonen dabilen edozer ikutzeko
gogoa eukitzen dugu, dela pyramid, dela ansible,
dela edozer.
EITB APIa? Zergatik? (I)
Zergatik ez?
Gurea ere bada, ezta?
XXI. mendean gaude jada...
Hitzaldi hau ikustean burura etorri daitekeen lehenengo
galdera da: "eta zergatik API bat EITBrentzat?"; eta
lehenengo erantzuna: "Eta, zergatik ez?".
Egia esateko ez da beste munduko galdera, eta erantzun
bat izan daiteke: EITBk berak ez duelako egin, eta pixka
bat gurea ere badelako EITB... eta jada Interneten garaian
horrealko gauzak egin egin behar direlako...
Baina hori ez da arrazoi bakarra
Zergatik? (II)
Webgunea infumablea iruditzen zaidalako
Ez dudalako lortzen ganoraz ezer ikustea
Nabigatzailea kolgatzen didalako
Flash erabiltzen duelako eta askotan ez zaidalako kargatzeko
EITB Nahieran webgunea hasiera-hasieratik nahiko txarra
iruditu zaidalako. Ez dut sekula ezer ganoraz ikustea:
agian nire ordenagailuen errua izango da, baina beti
eduki dut sentsazioa webgunea nahiko geldoa dela,
zerbait ikusten hasten naizenean weba kolgatuta bezala
gelditzen zaidala, eta flash player hori erdi-blokeatuta.
Gainera azkenaldian askotan flash playerra ondo konfiguratu
edo instalatu gabe izaten dut nabigatzailean (bai, bizi
daiteke flash gabe), eta orduan ezin naiz ezer ikustera
sartu.
Eta jakina, ezin dut sinistu gauden garaian flash behar
izatea horretarako...
Beno azken finean, aurreko guztiaren ondorio, orrialde
hau ikusteaz nazkatu naizelako.
Hori da eternamente "kargatzen..." agertzen zaidan irudia,
agian nire erruagatik flash ondo ez dudalako...
Beno, badago beste arrazoi bat...
"Baina beste arrazoiren bat ere egongo da ezta?"
pentsatu dezakezue, zeren arrazoiak ahul xamarrak
izan daitezke...
Raspberry PI
Raspberry PI bat erosi nuen
Arrazoia nere Raspberryari erabilpena ematea zen
Badakizue, Raspberry bat erosi nuen, hasiera mila
ideia nituen (backupak, VPN bat, kanpotik etxera
sartzea, ...), baina ondo jakingo duzuen bezala, gero
Raspberrya kajoi baten gelditzen da guztiz ahaztuta
Aitzakiekin jarraituz, etxez aldatzea tokatu zitzaidan
bitartean, eta han zegoen geldirik kutxa baten...
Eta bueno, jada etxe berrian ezin nuenez ordenagailua
zuzenean telebistara lotu, orduan aprobetxatu nuen
Raspberryari hautsa kendu eta Media Center bihurtzeko
Kodi (lehen XBMC)
Eta horretarako Kodi ezagutu nuen eta (OSMC
erabiliz) instalatu egin nuen.
Kostatu zitzaidain pixkat aklaratzea eta gauzak ezagutzea
baina era erraz baten ulertzeko, Kodi Media Center bat
egiteko aplikazio bat da, OSMC aldiz, Raspberryrako
optimizatutako linux banaketa bat Kodi zuzenean instalatu
eta abiarazten duena.
Hor konturatu nintzen ze ahaltsua zen Kodi, batez ere
pluginei esker. Milaka plugin ezberdin daude, hainbat
errepositoriotatik deskargatu daitezkeenak, edo zuzenean
ZIP fitxategi bat kargatuz instalatu daitezkeenak.
Lotura horretatik deskargatu dezakezue zuen sistemarako
eta probatu
Kodi (lehen XBMC)
Baina, gehienetan bezala, horrelako programen ahalmena
pluginekin etortzen da. Defektuzko plugin-sortarekin
zure disko gogor bat gehitu eta automatikoki azpitituluak,
MP3en karatulak, pelikulen kartelak eta abar ekarriko ditu,
baina Youtuberekin guztiz integratzea plugin batek ematen du
(horrela hortik jarraitu ditzaket harpidetuta nagoen kanalak),
edo podcastak entzun, edo beste hainbat gauza.
Nere kasuan ordenagailuan Plex instalatuta neukan lehendik,
eta horrentzat plugin bat ere badu. Etxeko sarean dagoenez
automatikoki deskubritzen du Plex Kodik (ordenagailua
itzalita badago piztu ere egiten du), eta Plexen ditudan
gauzak automatikoki erreproduzitzeko bezala jartzen dizkit,
beste disko batekin aurrera eta atzera ibili beharrean.
OpenSubtitles defektuz erabiltzen du, beraz azpitituluak
ere automatikoki ekartzen ditu.
Baina Kodirako aurkitu nuen altxorretako bat, eta hitzaldi
honetarako inspirazioa eman zidana tvalacarta zen.
tvalacarta, izenak dioen bezala, Internet bidez euren programen
bildumak eskaintzen dituzten telebista kateen zerbitzuetara
sartzeko plugina da. Ez Espainiakoak bakarrik, hegoameriketako
hainbat telebista ere badaude bere "katalogoan": adibidez
TVEren "A la Carta" aukera oso ondo dabil TVEren ia edozein
programa nahi duzunean ikusteko.
Eta EITB? Ba...
EITB eta tvalacarta?
Ba bai, tvalacartak bazeukan EITB ikusteko aukera, baina ez zebilen
Webgune zaharrerako prestatuta zegoen
Screenscraping egiten zuen
Ba bai, EITB Nahieranekin ere funtzionatzen zuen tvalacarta
plugin honek, baina programen zerrenda bakarrik ateratzen zuen.
Hortik aurrera konexio erroreak besterik ez zituen ematen.
Pixkat inbestigatzen hasita pluginak screen-scraping egiten
zuela konturatu nintzen: hau da, orrialdearen iturburu kodea
deskargatu eta han javascriptean ezkutatuta zeuden helbideak lortu
bideoen URLak asmatzeko.
def mainlist(item):
logger.info("[eitb.py] mainlist")
itemlist=[]
url = 'http://www.eitb.tv/es/'
# Descarga la página
data = scrapertools.cachePage(url)
patron = "#CENSORED REGEX#"
matches = re.compile(patron,re.DOTALL).findall(data)
if DEBUG: scrapertools.printMatches(matches)
for id,titulo,titulo2 in matches:
scrapedtitle = titulo
if titulo!=titulo2:
scrapedtitle = scrapedtitle + " - " + titulo2
scrapedurl = "http://www.eitb.tv/es/get/playlist/"+id
scrapedthumbnail = ""
scrapedplot = ""
if (DEBUG): logger.info("title=["+scrapedtitle+"], url=["+scrapedurl+"], thumbnail=["+scrapedthumbnail+"]")
itemlist.append( Item(channel=CHANNELNAME, title=scrapedtitle , action="episodios" , url=scrapedurl, thumbnail=scrapedthumbnail, plot=scrapedplot , folder=True) )
return itemlist
Programen zerrenda lortzeko kodea
Hori egiten du programa zerrenda lortzeko
HTMLa irakurri eta handik programen helbideak atera
Gainera kodea nahiko zikina da, javascript bidez aktibatzen
diren loturekin.
def episodios(item):
logger.info("[eitb.py] episodios")
itemlist=[]
# Descarga la página
data = scrapertools.cachePage(item.url)
logger.info(data)
episodios_json = load_json(data)
if episodios_json == None : episodios_json = []
itemlist = []
for video in episodios_json['videos']:
scrapedthumbnail = video['thumbnailURL']
if scrapedthumbnail is None:
scrapedthumbnail = ""
logger.info("scrapedthumbnail="+scrapedthumbnail)
scrapedtitle = video['name']#.encode("utf-8",errors="ignore")
#scrapedtitle = unicode( scrapedtitle , "iso-8859-1" , errors="ignore").encode("utf-8")
scrapedplot = video['shortDescription']#.encode("utf8","ignore")
try:
scrapedtitle = video['customFields']['name_c']#.encode("utf-8","ignore")
scrapedplot = video['customFields']['shortdescription_c']#.encode("utf-8","ignore")
except:
pass
scrapedurl = "http://www.eitb.tv/es/#/video/"+str(video['id'])
if (DEBUG): logger.info("title=["+scrapedtitle+"], url=["+scrapedurl+"], thumbnail=["+scrapedthumbnail+"]")
itemlist.append( Item(channel=CHANNELNAME, title=scrapedtitle , action="play" , server="eitb", url=scrapedurl, thumbnail=scrapedthumbnail, plot=scrapedplot , folder=False) )
return itemlist
Hor, programa jakin baten bideo zerrenda nola lortzen zen ikusten duzue.
Honetarako JSON bat zuten (seguraski menuak osatzeko javascript-en baten
erabiltzeko), eta hortik zetozen datuak
def get_video_url( page_url , premium = False , user="" , password="", video_password="", page_data="" ):
logger.info("[eitb.py] get_video_url(page_url='%s')" % page_url)
data = scrapertools.cache_page(page_url)
player_id = scrapertools.get_match(data,'<param name="playerID" value="(\d+)')
logger.info("player_id="+player_id)
player_key = scrapertools.get_match(data,'<param name="playerKey" value="([^"]+)"')
logger.info("player_key="+player_key)
# id del contenido, viene en la URL
# http://www.eitb.tv/es/#/video/1628880837001
video_player = scrapertools.get_match(page_url,'http://www.eitb.tv/es/#/video/(\d+)')
logger.info("video_player="+video_player)
response = get_rtmp( player_key , video_player , page_url , player_id)
Bideo baten helbidea lortzeko "marabilak"
Eta programa jakin baten bideo baten helbidea lortzea
ere, tela marinera.
Ikusten duzue nola flash playerraren etiketak parseatu behar ziren
bideoen idak lortu eta horiekin URLak sortzeko.
2016 urtean horrela ibili behar izatea ere TB publiko batekin...
Bueno, hori ikusita alde batetik pena ere sentitu nuen,
ETBren bideoteka era programatikoan lortzeko horrelako "hack"ak
egin behar izateaz...
Ba nik egingo dut
Baina bueno, total, screenscrapping egin behar bada,
*neuk egingo dut* pentsatu nuen eta han ireki nuen Firebug
eta network pestaña nabigatzaileak egiten zituen eskaerak ikusteko,
HTMLa nola aldatu den ikusteko eta horren arabera parserrak
egokitzeko.
Eta ireki nuen Firebug-en "Network" pestaña egiten ziren eskaerak
ikusteko, eta deskargatzen ziren Javascript fitxategi kopuruarekin
bakarrik flipatuta gelditu nintzen.
Pixkat begiratzen hasita, hasierako programen zerrenda eta programa
baten informazio orokorra (izena, eta beste pare bat datu), lortzeko
kodeak antzerako eran funtzionatzen zuen.
Baina hori ez zen guztia, ze bideo bat ikusteko orduan
egiten zituen eskaerak ikusita, ikusi nuen beste zerbitzari
batera eskaerak egiten zituela
Eskaerak errepasatzen hasi nintzen eta bideo bat eskatzen diozunean,
sesio bat irekitzen du hirugarren
zerbitzari baten bideoa eskatu duzula ikusteko eta cookie bat
pasatzen dio eta horrekin bideoa erreproduzitzen hasten da.
Buff!!! Javascripta erreproduzitzen saiatu nintzen baina ez nuen ezer
lortu.
Gero, Googlen bilatuta, Brightcove izeneko zerbait erabiltzen
zutela ikusi nuen. Bideoteka gordetzeko zerbitzari bat
zirudien, eta horregatik egiten zituen beste zerbitzari baterako eskaerak
Googleatzen hasi nintzen hori nola egin nezakeen eta lan handia
zela zirudien eta orduan...
Alde batera utzi nuen proiektua, eta beste gauza bat
egitera pasatu nintzen.
Raspberry eta Kodirekin jarraituko nuen, baina EITB gabe.
Zer egingo diogu bada...
Utzi baina....
youtube-dl ezagutu nuen arte
Egia esan nahiz eta utzi, hortxe izan nuen "hau egin egin
behar dut" zioen zera hori... Eta youtube-dl ezagututakoan
atera nuen ezten hori.
youtube-dl
Youtubetik bideoak deskargatzeko aplikazioa
Python pakete bat da
Python paketeetatik erabili daiteke
Eta ez da youtuberako bakarrik, EITBrekin funtzionatzen du
Ezagutzen duzue youtube-dl?
Youtube-dl, izenak dioen bezala, youtubeko bideoak deskargatzeko
programa bat zen. python-en idatzita dago, gainera, eta orain
jada ez du youtuberako bakarrik balio. Ia edozein plataformatarako
balio du.
Eta euren artean EITBrentzat!!!
Ba hori horrela, probatzea pentsatu nuen, lehenengo EITB
Nahieraneko bideo baten URLa hartu eta zuzenean deskargatuz.
Eta lehenengoan funtzionatu zuen, kexatu gabe
Kodea youtube-dl-rekin
def get_video_url( page_url , premium = False , user="" , password="", video_password="", page_data="" ):
logger.info("[eitb.py] get_video_url(page_url='%s')" % page_url)
ydl = youtube_dl.YoutubeDL({'outtmpl': u'%(id)s%(ext)s'})
result = ydl.extract_info(page_url, download=False)
video_urls = []
if 'formats' in result:
for entry in result['formats']:
if 'http' in entry['format']:
video_urls.append([safe_unicode(entry['format']).encode('utf-8'), safe_unicode(entry['url']).encode('utf-8')])
logger.info('Append: {}'.format(entry['url']))
return video_urls
joer, hori eta gainera ganorazko python pakete
bat zela ikusita, tvalacartaren plugina aldatu nuen,
ez zebilen kode zatia hartu eta ordezkatzeko.
Lehengo screen-scrapping guztiak, youtube-dl
erabiltzera mugatu ziren
Aldaketak egin, komitak egin eta tvalacartaren egileari
bidali nizkion githubetik, eta berehala atera zuen bertsio
berri bat.
Jada telebista aurrean jarri eta nahi nuena ikustea
besterik ez zitzaidan gelditzen.
eta API bat egiten badut?
Hala ere, ez zen gauza horretan gelditu, pentsatzen
jarrita nik egindakoa hack konkretu bat zen tvalacarta
plugin horrentzat.
Baina egian soluzioa orokortu nezakeela okurritu
zitzaidan eta API bat egin.
Ze total EITBk ez du egingo...
EITB APIa
Pyramid-en eginda
Framework arin-arina
Zuk aukeratu guztia (DB, template, ...)
Horrela, Pyramid erabiltzea erabaki nuen: framework arin-arina da,
ez du erabakirik hartzen ezeren inguruan: zure esku dago
nola funtzionatuko duen: url-ak ruta bezala edo traversal bezala
(django vs. zope), datubasea (sql, ZODB, no-sql, datu-baserik ez, ...)
plantillak (zpt, django, jinja, stringak...); gaineko maila ematen dizu
guztia pegatzeko pegamentua.
Zeren ez nuen datubaserik eta ezer behar datuak gordetzeko
(asko jota katxea, gero ikusiko den bezala), era arin baten
EITBren webgunea irakurri eta horrekin APIa osatzea behar nuen.
Eta horretarako Pyramid egokiena zela iruditu zitzaidan.
APIa pyramid-en
Misterio handirik ez
3 URL:
programa guztiak
programa baten saioak
saioa
Plone-ren API lanean inspiratuta
Bai, Plone berriz :)
Gutxi gora-behera sasoi berean Ploneren REST APIa prestatzen zebiltzan
eta horretarako egindako lanean inspiratzen saiatu nintzen, nola edo hala APIa
nabegablea izateko, hau da, bertan agertzeko programa, saio eta guztien helbideak,
era erraz baten erabili ahal izateko.
Esan eta egin
Nabigatu APIan
Kodea ikusi
Bide batez Heroku erabiltzen ikasteko aukera izan nuen,
ez baduzue ezagutzen probatu, era oso erraz baten python
aplikazioak sarean jartzeko aukera ematen du (django eta
pyramid edo Plone ere bai esaterako), eta zerbitzarien
kudeaketaz arduratu beharrik gabe eta eskalabilitatea
era errazean kudeatuz (komando lerrotik dena)
@view_config(route_name='programs', renderer='prettyjson')
def programs(request):
"""get all information about all the programs.
How: scrap the website and look for the javascript links.
"""
data = requests.get(EITB_FRONT_PAGE_URL)
matches = re.compile(EITB_EPISODE_LIST_REGEX, re.DOTALL).findall(data.text)
result = {
'@context': 'http://www.w3.org/ns/hydra/context.jsonld',
'@id': request.route_url('programs'),
'@type': 'TV',
'parent': {},
}
results = []
for id, title1, title2 in matches:
scrapedtitle = title1
if title1 != title2:
scrapedtitle = scrapedtitle + " - " + title2
results.append({
'@id': request.route_url('playlist', playlist_id=id),
'@type': 'Playlist',
'title': scrapedtitle,
'description': '',
})
result['member'] = results
return result
@view_config(route_name='playlist', renderer='prettyjson')
def playlist(request):
""" get all the information about the given program.
How: get the information from a pseudo-api
"""
playlist_id = request.matchdict['playlist_id']
result = {
'@context': 'http://www.w3.org/ns/hydra/context.jsonld',
'@id': request.route_url('playlist', playlist_id=playlist_id),
'@type': 'Playlist',
'parent': request.route_url('programs'),
}
playlist_url = EITB_PLAYLIST_BASE_URL.format(playlist_id)
data = requests.get(playlist_url)
playlist_data = data.json()
web_medias = playlist_data.get('web_media')
del playlist_data['web_media']
playlist_data['member'] = []
for web_media in web_medias:
item = {
'@id': create_internal_video_url(
playlist_data.get('name_playlist'),
playlist_data.get('id_web_playlist'),
web_media.get('NAME_ES'),
web_media.get('ID_WEB_MEDIA'),
request=request,
),
'@type': 'Episode',
'title': web_media.get('NAME_ES'),
'description': web_media.get('SHORT_DESC_ES', ''),
}
playlist_data['member'].append(item)
del playlist_data['id']
result.update(playlist_data)
return result
@view_config(route_name='episode', renderer='prettyjson')
def episode(request):
""" Get all the information and the video links from a given episode.
How: use youtube-dl to get the information
"""
episode_url = request.matchdict['episode_url']
url = EITB_VIDEO_BASE_URL + episode_url
playlist_title, playlist_id, video_title, video_id = episode_url.split('/')
result = {
'@context': 'http://www.w3.org/ns/hydra/context.jsonld',
'@id': request.route_url('episode', episode_url=episode_url),
'@type': 'Episode',
'parent': request.route_url('playlist', playlist_id=playlist_id),
}
ydl = youtube_dl.YoutubeDL({'outtmpl': '%(id)s%(ext)s'})
video_data = ydl.extract_info(url, download=False)
result.update(video_data)
return result
def clean_title(title):
"""slugify the titles using the method that EITB uses in
the website:
- url: http://www.eitb.tv/resources/js/comun/comun.js
- method: string2url
"""
translation_map = {
'À': 'A', 'Á': 'A', 'Â': 'A', 'Ã': 'A', 'Ä': 'A', 'Å': 'A', 'Æ': 'E',
'È': 'E', 'É': 'E', 'Ê': 'E', 'Ë': 'E',
'Ì': 'I', 'Í': 'I', 'Î': 'I', 'Ï': 'I',
'Ò': 'O', 'Ó': 'O', 'Ô': 'O', 'Ö': 'O',
'Ù': 'U', 'Ú': 'U', 'Û': 'U', 'Ü': 'U',
'Ñ': 'N', '?': '', '¿': '', '!': '',
'¡': '', ': ': '', '_': '-', 'º': '',
'ª': 'a', ',': '', '.': '', '(': '',
')': '', '@': '', ' ': '-', '&': ''
}
val = title.upper()
for k, v in translation_map.items():
val = val.replace(k, v)
return val.lower()
EITBren webgunetik JavaScript kodea "lapurtu" behar izan nuen,
URLak era egokian sortzeko konbertsioak JavaScript bidez egiten
dituztelako programen izenburuetatik abiatuz eta karaktere bereziak
kenduz
eta honekin zer?
Egin nahi duzuna
Telebista ikusteko beste modu batzuk daude orain
Kodi bat da, baina beste mila daude (chromecast, SmartTV, ...)
Eman sarrera bideotekara era garbi baten
EITBk ez badu egiten, besteok egingo dugu
ingurukoek badute APIa
Eta garatzaileok gauzak egiteko aukera dugu
Beste batzuk badute, edo APIak daude, edo RSSak edo zerbait
era programatikoan programa eta bideoetara sarrera izateko
EITBri ere esan genion jada tvalacarta-rena egin genuenean
eta erantzun zuzen horietako bat eman ziguten "bai, aztertu
behar dugu, negozio eredua, eta publizitatea? eta klikak kontatu
ditzakegu, eta...?"
Interes handirik ez zuten agertu
Garbi ikusten da hemendik kanpora "publikotasunaren"
kontu hori beste era batera ulertzen dela. Hemendik kanpo
publikoa denona dela ondo ulertzen da, hemen ez nago ziur
hori ulertzen den edo zer....
Jendeak eskatzen badu egin beharko dute.
Denok joan beharko gara EITBra eskatzera
API batek irismena emango dio EITBri, bere bideoak ikusteko
Chromecast-ak, Kodi, ... telebista ikusteko moduak aldatu
egin dira... guztia kontuan izan behar da, ez publizitatea
eta klikak bakarrik.
Guztiok eskatu beharko diogu horrelako zerbait EITBri, eurek
egitea da-eta onena.
Galderarik?
Eta ez ahaztu...