UNIVERSIDAD DE CONCEPCIÓN Desarrollo de una biblioteca

Anuncio
UNIVERSIDAD DE CONCEPCIÓN
FACULTAD DE INGENIERÍA
DEPARTAMENTO DE INGENIERÍA ELÉCTRICA
Profesor Patrocinante:
Dr. Mario R. Medina C.
Informe de Memoria de Título
para optar al título de:
Ingeniero Civil Electrónico
Desarrollo de una biblioteca para transmisión
distribuida y colaborativa de música
Concepción, Abril de 2012
Felipe Alberto Glaría Grego
UNIVERSIDAD DE CONCEPCIÓN
Facultad de Ingeniería
Departamento de Ingeniería Eléctrica
Profesor Patrocinante:
Mario R. Medina C.
Desarrollo de una biblioteca para transmisión
distribuida y colaborativa de música
Felipe Alberto Glaría Grego
Informe de Memoria de Título
para optar al título de
Ingeniero Civil Electrónico
Abril 2012
iii
Resumen
Ante la gran variedad de música a la que se puede acceder hoy en día, es necesario desarrollar
sistemas que permitan filtrar de alguna manera todas las opciones disponibles, y así poder escuchar
canciones y/o artistas conocidos o desconocidos que sean de nuestro agrado.
En este informe se presenta la realización de una radio online con reproductor web, que permita
evaluar las canciones reproducidas. Luego, mediante la técnica de filtrado colaborativo, se reconocen
usuarios con gustos similares y se predicen nuevas evaluaciones para canciones no ingresadas. Usando
el mismo sistema, se se identifican, además, los usuarios disímiles, y mediante la hipótesis “a las
personas normalmente le gusta y disgustan ciertos estilos definidos”, se predicen evaluaciones inversas
y, para luego, ponderando ambas predicciones, se obtiene una predicción final.
Además, mediante distintos métodos de detección (HTML DOM, AJAX, jQuery), se identifican a
los usuarios que escuchan la radio en línea, para así adaptar efectivamente el contenido a reproducir
según los gustos de los oyentes en ese momento.
Los resultados de este trabajo muestran que es posible identificar a los usuarios en ambos grupos
y generar un listado de canciones adaptativo. Finalmente, se plantean variadas opciones para complementar la solución: mejorar la gestión de usuarios y canciones, usar filtrado colaborativo, optimizar el
algoritmo, entre otras.
Este sistema se implementa en un servidor disponible en el Departamento de Ingeniería Eléctrica
de la Facultad de Ingeniería de la Universidad de Concepción.
iv
A mi tío Carlos...
Por convencerme de volver
v
Agradecimientos
Llegando al final de una larga etapa (más larga de lo pensado), sin duda que hay muchas personas a
las cuales les debo más que un sencillo agradecimiento. Aún así, tengo el honor y gusto de agregarlos
con mucho cariño a este trabajo.
Imposible empezar esta sección por otra persona que no sea mi madre. Su amor, paciencia y apoyo
fueron, son y serán por siempre constantes, invariables, indudables e incuestionables, y por ello estaré
eternamente agradecido. Además, por si el lector no lo deduce aún, es la mujer que me trajo al mundo,
y eso hace que la deuda y gratitud sea aún más enorme. Un gran beso escrito para ti, mamá.
Y de embarazos espontáneos no se llega al mundo (debata el que quiera), el segundo en la lista es
mi padre. Pese a la distancia, siempre se sintió el mismo apoyo antes mencionado, y aunque significó
dejar de vivir juntos en algún momento, con este trabajo se cosecha el fruto de dicha decisión. Otro
beso para ti, papá.
La primera persona externa a mi familia, al menos de sangre, es a mi compañera, polola, pareja,
Ale. Juntos casi el mismo tiempo que duró esta etapa (tampoco los 6 años respectivos), le agradezco
el amor y paciencia que me entrega, ambos igual de enormes y significativos. Debes ser la persona
con la cual compartí más de cerca lo bueno y malo de este viaje, y por eso muchas gracias, un nuevo
beso para ti.
A mi familia le agradezco más apoyo (imposible caerse así). Hermanos, tíos, tías, primos, primas,
padrinos, madrinas, omi, y en especial a mi opa, mi tío Carlos, al cual nunca convencí del todo con mi
aventura en la capital, y por ello he llegado a este momento ahora. Aunque no estás aquí para verlo, te
inmortalizo en esta memoria, como te lo mereces.
A mis amigos de toda la vida, de media vida y de este último cuarto también. Confío en que saben
sin dudar que me refiero a ustedes, cómplices de innumerables anécdotas, pilares fundamentales más
de alguna vez, y piezas esenciales en lo que depare el futuro. Son grandes.
También le agradezco a todos los compañeros de universidad que no se sintieron incluidos en el
párrafo anterior, ya que, si bien no fuimos grandes amigos en estos años, sí compartimos bastantes
momentos gratos e ingratos, aprendimos muchas cosas útiles (y no tanto), sufrimos los mismos cursos
y dormimos las mismas clases. Gracias por hacer más divertido este viaje.
Agradezco haber tenido clases y trabajado con muchos profesores. Al Dr. Mario Medina, por enseñarme a programar con ejemplos simples y problemas a veces demasiado ocurrentes. Al Dr. Miguel
Figueroa, por motivarme a aprender, y siempre estar disponible para conversar. A tantos profesores
más, fue un honor haber tenido la oportunidad de aprender de ustedes.
Al ánimo de muchos funcionarios de la universidad, en especial a Inés y su habilidad de controlar
el espacio-tiempo para poder entregar tantos pre-informes e informes, y su paciencia y alegría siempre
a disposición de resolver dudas académicas existenciales.
Además, a todo aquel que cree haber tenido un efecto insignificante, pasajero e irrelevante en mi
vida, pero mayor aún, a los que saben que tuvieron un efecto negativo en ella, ya que sin ustedes no
sería la persona que soy actualmente, y ya sea para bien o para mal, me gusta el resultado obtenido.
Finalmente, muchas gracias a ti, lector, por interesarte en estas palabras, y confío que de algo
servirá que sigas avanzando...
Tabla de contenidos
vi
Nomeclatura
xii
Abreviaciones
xiii
1. Introducción
1.1. Introducción general . . . .
1.2. Objetivos . . . . . . . . . .
1.2.1. Objetivo general . .
1.2.2. Objetivos específicos
1.3. Alcances y limitaciones . . .
1.4. Temario . . . . . . . . . . .
.
.
.
.
.
.
2. Filtrado colaborativo
2.1. Introducción . . . . . . . . . .
2.2. Bases del filtrado colaborativo
2.3. Ventajas . . . . . . . . . . . .
2.4. Desventajas . . . . . . . . . .
2.5. Discusión . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3. Hardware y software
3.1. Introducción . . . . . . . . . . . .
3.2. El servidor . . . . . . . . . . . . .
3.3. Software de streaming . . . . . . .
3.3.1. Icecast 2 . . . . . . . . . .
3.3.2. Ices . . . . . . . . . . . .
3.4. PHP y reproductor embebido web
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
4. Desarrollo de un algoritmo predictivo y listado adaptativo
4.1. Introducción . . . . . . . . . . . . . . . . . . . . . . .
4.2. Una radio online . . . . . . . . . . . . . . . . . . . . .
4.3. Generar la lista . . . . . . . . . . . . . . . . . . . . .
4.3.1. Adaptar evaluaciones . . . . . . . . . . . . . .
4.3.2. Predicción de evaluaciones . . . . . . . . . . .
4.3.3. Las más populares . . . . . . . . . . . . . . .
4.3.4. La próxima canción . . . . . . . . . . . . . . .
4.4. El algoritmo en Python . . . . . . . . . . . . . . . . .
4.4.1. Función create_matrix . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
1
1
1
2
2
2
.
.
.
.
.
3
3
3
4
4
5
.
.
.
.
.
.
6
6
6
7
7
8
8
.
.
.
.
.
.
.
.
.
10
10
10
10
11
11
15
16
16
16
vii
4.4.2.
4.4.3.
4.4.4.
4.4.5.
4.4.6.
4.4.7.
Función MDS . . . . . .
Función CalculaPesos . .
Función Predict . . . . .
Función GetPredicciones
Función TopTracks . . .
Función PredictTop . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
17
18
18
22
25
26
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
28
28
28
29
29
29
31
31
31
33
42
42
43
43
43
46
47
48
48
49
.
.
.
.
.
.
52
52
52
52
52
53
55
7. Conclusiones
7.1. Resumen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
60
60
5. Implementación de la radio online
5.1. Introducción . . . . . . . . . . . . .
5.2. Preparando el servidor . . . . . . .
5.3. Streaming: Icecast2 . . . . . . . . .
5.3.1. Instalar Icecast2 . . . . . . .
5.3.2. Configurar Icecast2 . . . . .
5.4. Streaming: Ices . . . . . . . . . . .
5.4.1. Instalar Ices . . . . . . . . .
5.4.2. Configurar Ices . . . . . . .
5.4.3. Ices y Python . . . . . . . .
5.5. PHP y Wimpy Button . . . . . . . .
5.5.1. PHP básico . . . . . . . . .
5.5.2. Wimpy Button . . . . . . .
5.6. Usuarios y evaluaciones . . . . . . .
5.6.1. Creando cuentas de usuarios
5.6.2. Sistema de evaluación . . . .
5.6.3. Detectando oyentes . . . . .
5.7. Resumen de directorios y archivos .
5.7.1. Directorios . . . . . . . . .
5.7.2. Archivos . . . . . . . . . . .
6. Pruebas y resultados
6.1. Introducción . . . . . .
6.2. Pruebas al algoritmo . .
6.2.1. Matriz de 2x3 .
6.2.2. Matriz de 4x5 .
6.3. Tiempos del algoritmo
6.4. La radio online . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
viii
7.2. Conclusiones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7.3. Trabajo futuro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
60
61
Bibliografía
63
Anexo
64
ix
Índice de tablas
1.
2.
3.
4.
5.
6.
Hardware de Hopper . . . . . . . . . . . .
Datos de Hopper . . . . . . . . . . . . . . .
Cantidad de usuarios y evaluaciones . . . .
Cantidad de evaluaciones por canción . . .
Evaluaciones de los 6 usuarios . . . . . . .
Cantidad de canciones evaluadas en común
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
6
6
55
56
57
57
x
Índice de ecuaciones
1.
2.
3.
4.
5.
6.
7.
8.
mds(x1 , x2 )
wt(x1 , x2 ) .
S (x) . . . .
D(x) . . . .
w0S (x, y) . .
w0D (x, y) . .
w0 (x, y) . . .
CP(t) . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
12
12
13
14
15
15
15
15
xi
Índice de figuras
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
Diagrama de funcionamiento de Ices y Icecast . . .
Ejemplo: Evaluaciones del usuario 1 . . . . . . . .
Ejemplo: Evaluaciones comunes entre dos usuarios
Ejemplo: Predicciones para usuarios 1 y 2 . . . . .
Radio sólo con reproductor . . . . . . . . . . . . .
Página de inicio . . . . . . . . . . . . . . . . . . .
Creando una nueva cuenta . . . . . . . . . . . . .
Faltan datos para crear una cuenta . . . . . . . . .
Errores creando una cuenta . . . . . . . . . . . . .
Errores al ingresar . . . . . . . . . . . . . . . . . .
Botones de opción y enviar . . . . . . . . . . . . .
Páginas de inicio y últimas 20 . . . . . . . . . . . .
Tiempos del algoritmo . . . . . . . . . . . . . . .
Cuántas evaluaciones tiene cada canción . . . . . .
Evaluaciones de 6 usuarios . . . . . . . . . . . . .
Predicciones vs. evaluaciones . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
7
12
13
14
43
44
44
45
45
45
46
48
54
55
56
58
xii
Nomeclatura
C
ci
E(u)
D(x)
i
L(t)
mds(x1 , x2 )
P(y)
xi
S (x)
T (x)
U
ui
V(x)
W
W0
w0 (x, y)
w0D (x, y)
w0S (x, y)
WT
wt(x1 , x2 )
: Universo de canciones en la radio.
: Canción i.
: Evaluaciones del usuario u.
: Usuarios disímiles a x.
: Índice.
: Grupo de usuarios escuchando radio en tiempo t.
: MDS entre usuario x1 y usuario x2 .
: Grupo de usuarios que evaluaron la canción y.
: Vector de evaluaciones del usuario i.
: Usuarios similares a x.
: Grupo de canciones evaluadas por usuario x.
: Universo de usuarios de la radio.
: Usuario i.
: Vecindario de usuario x (Flycasting).
: Matriz de evaluaciones de usuarios sobre canciones, sin predicción.
: Matriz de evaluaciones de usuarios sobre canciones, con predicción.
: Predicción de evaluación de usuario x sobre canción y.
: Predicción usando grupo D(x).
: Predicción usando grupo S (x).
: Matriz de pesos entre usuarios.
: Peso entre usuario x1 y usuario x2 .
xiii
Abreviaciones
Mayúsculas
AJAX
DOM
DTI
HTML
ID
MD5
MP3
MSD
PC
PHP
S.C.
UDP
URL
XML
: (Asynchronous JavaScript And XML) Técnica desarrollo web para aplicaciones interactivas.
: (Document Object Model) Plataforma e interfaz neutral al lenguaje.
: Dirección de Tecnologías de Información de la Universidad de Concepción.
: (Hyper Text Markup Language) Lenguaje de marcado de hipertexto.
: Identificador.
: (Message-Digest Algorithm 5) Algoritmo de reducción criptográfico de 128 bits.
: (MPEG-1 Audio Layer 3) Formato de compresión de audio digital.
: (Mean Squared Distance) Distancia media cuadrática.
: (Personal Computer) Computadora personal.
: (PHP Hypertext Pre-processor) Lenguaje de programación interpretado.
: (Source Client) Clientes generadores de streaming.
: (User Datagram Protocol) Protocolo no orientado a la conexión.
: (Universal Resource Locator) Dirección de internet.
: (eXtensible Markup Language) Metalenguaje de etiquetas.
1
Capítulo 1. Introducción
1.1.
Introducción general
Con la masificación de internet, el mundo vive una gran revolución cultural, donde todos
pueden acceder por distintas vías a una variada gama de contenidos. Entre estos está la música, arte
donde las grandes casas discográficas han sido quienes dictaron tendencia en cuanto a lo que se oía y
los artistas que triunfaban en el mundo musical. Sin embargo, con el surgimiento de las radios online,
el abanico de opciones se ha ampliado, pues se puede escuchar emisoras de distintas ciudades y países
del mundo, dando a los oyentes una diversidad de alternativas.
Si bien lo anterior implica un beneficio, plantea el problema concreto de cómo encontrar lo
buscado, ya que el contenido existente en internet es tan abundante que puede llegar a ser una tarea
muy compleja. Esto se refleja en los distintos sitios de búsqueda que existen a nivel mundial y sus
cada vez más avanzados algoritmos.
Por esto se considera como una buena opción otro tipo de filtrado, el colaborativo, que se
asemeja a las sugerencias que se hacen y reciben constantemente por las personas en sus relaciones
interpersonales, definidas por los gustos históricos, vivencias y cultura. Además, permite no sólo buscar por contenido escrito, sino también por imágenes, sabores, intereses en general[3].
Así para facilitar el acceso a música que a la gente le gusta y conoce, pero más interesante aún,
le gusta y desconoce [1], se puede usar el filtrado colaborativo para reconocer y agrupar a los usuarios
de distintas maneras [4] [6], e implementar sistemas de recomendación en base a sus preferencias.
Distintas maneras de realizar esto se han desarrollado a través del tiempo [1] [2] [5]. El trabajo
que más se asemeja a lo planteado consiste en la implementación de un sistema que genera un listado
en tiempo real, según el historial de petición de los usuarios, basado en los artistas solicitados y
tratando de mantener cierta coherencia entre una canción y otra. Esto último se refiere a no cambiar
de estilos musicales de manera drástica o aleatoria, mediante la identificación de artistas similares y,
posteriormente, la restricción de los resultados posibles al rango “coherente”, lo que inevitablemente
significa ignorar o posponer las proposiciones para un grupo minoritario.
Mediante la presente memoria, se implementará una radio online que reciba los gustos de los
usuarios conectados, identifique usuarios similares y disímiles, prediga evaluaciones por canción en
base a ambos grupos y adapte la lista de reproducción para obtener una programación dinámica sin
coherencia que cambie en línea de acuerdo a las preferencias de sus oyentes.
1.2.
1.2.1.
Objetivos
Objetivo general
Desarrollar una radio online que adapte su programación al gusto de los usuarios conectados.
2
1.2.2.
Objetivos específicos
Implementar una radio online de reproducción en la web.
Desarrollar un sistema de evaluación en línea para las canciones que escuchan los usuarios.
Procesar las evaluaciones de los usuarios para predecir una programación adaptativa.
1.3.
Alcances y limitaciones
La programación dependerá de los gustos y preferencias de los usuarios que la estén escuchando, de tal manera que la mayoría quede conforme. Los usuarios no podrán cambiar directamente la
lista de reproducción.
No se le exigirá coherencia a la lista, ya que implementar esto significa dejar a algunos usuarios
de lado para evitar cambios drásticos de estilos [2].
1.4.
Temario
El temario se describe a continuación:
El Capítulo 2 entrega la teoría del filtrado colaborativo, sus ventajas y desventajas, y plantea
cómo será utilizado en este trabajo.
El Capítulo 3 detalla tanto el hardware como el software a utilizar.
El Capítulo 4 explica el desarrollo del algoritmo de predicción y cómo generar una lista adaptativa de canciones mejor evaluadas para una radio online.
El Capítulo 5 cubre la implementación de la radio online, incluyendo el sistema de evaluación
por parte de los usuarios y la obtención de una lista adaptativa.
El Capítulo 6 muestra las pruebas y resultados hechos al sistema.
El Capítulo 7 presenta las conclusiones finales y el posible trabajo futuro.
3
Capítulo 2. Filtrado colaborativo
2.1.
Introducción
El filtrado colaborativo es un tipo de recomendación personalizada basada en combinar las distintas opiniones de usuarios sobre diferentes productos de una misma temática, como música, libros,
películas, entre otros, para luego separar a los usuarios similares y encontrar, gracias a sus preferencias, otros elementos conocidos o nuevos.
Este sistema es muy útil cuando se tiene una gran variedad de opciones a elegir, tanto así que
resulta imposible revisarlas todas. Cada usuario conoce y/o descubre una porción del total, y juntando
todas esas preferencias se puede armar un arreglo de evaluaciones, para luego reconocer usuarios con
intereses comunes y en base a ello proponer otras opciones no evaluadas y quizás desconocidas para
cada uno.
2.2.
Bases del filtrado colaborativo
Ante una alta variedad de elementos de cierta índole, es necesario descubrir otro método de
filtrar resultados y obtener de manera efectiva lo que se quiere. Para esto se desarrolla el filtrado
colaborativo, el cual funciona en base a las opiniones y gustos de diferentes individuos, reconoce
usuarios similares y entrega resultados recomendados por cada solicitud.
Para que funcione correctamente, son necesarios tres pilares fundamentales [7]:
1. Deben participar muchas personas (mientras más participen, más probable encontrar usuarios
similares).
2. Debe ser simple para la gente representar sus opiniones.
3. El algoritmo debe ser capaz de agrupar usuarios con intereses similares.
Por lo tanto, la tarea de los usuarios es bastante sencilla, evaluar los elementos que presenta
el sistema según sus preferencias. Sus evaluaciones serán usadas para aproximar el gusto de cada uno
en ese tema, luego compararlas entre todos los demás y encontrar la vecindad cercana de usuarios
similares. Finalmente el sistema recomienda elementos altamente evaluados por la vecindad, que el
usuario no evaluó y quizás hasta no conoce. Además, si la recomendación no es correcta se puede
evaluar negativamente, y así con el tiempo adquiere más exactitud para recomendar.
Un punto clave es cómo combinar las preferencias y darles cierto peso para determinar las
vecindades. Esto define cuántos usuarios las componen y cuán similares o cercanos son al usuario en
cuestión.
4
2.3.
Ventajas
Una de las mayores ventajas que posee este tipo de filtrado, es el hecho de que cada recomendación es personalizada, al tener una vecindad de personas con gustos similares se puede hasta descubrir nuevos elementos que nunca hubieran sido considerados inicialmente.
Además, las recomendaciones son una consecuencia para el usuario, no debe buscar ni similitudes con otros ni nuevos elementos, tan solo debe mantenerse evaluando y con el tiempo la recomendación será más exacta. De hecho, todos los usuarios hacen el mismo trabajo y reciben el mismo
resultado. Esto se conoce como roles uniformes.
Finalmente, tampoco es necesario interacción alguna entre las personas que usan el sistema,
no importa si alguien conoce a miles o a nadie dentro de éste, mientras evalúen todos recibirán recomendaciones.
2.4.
Desventajas
Uno de los grandes problemas de este tipo de filtrado es el denominado inicio frío, mientras
nadie evalúe es imposible comenzar a recomendar. Lo mismo ocurre cuando un item no ha sido evaluado, no se puede incluir en las recomendaciones, por lo tanto la novedad empieza a perder fuerza.
Una manera de mejorar esto último es incluir esporádicamente dentro de las recomendaciones algún
elemento sin evaluar, seleccionado por métodos probabilísticos o al azar, de tal manera que el usuario
decida si es una buena o mala recomendación. Esto puede empeorar levemente los resultados, pero
agrega cierta novedad aleatoria al sistema.
Otro problema es cuando la cantidad de usuarios y evaluaciones es notoriamente menor frente
a la cantidad de elementos a evaluar, lo que resulta en una baja cantidad de evaluaciones en común
y un mal resultado como vecindad. Una manera efectiva de mejorar este problema es combinar el
filtrado colaborativo con recomendaciones basadas en contenido. Por ejemplo, a un usuario le gusta
una canción de John Lennon y a otro usuario una diferente. Usando sólo filtrado colaborativo, como
ambas canciones son distintas, los usuarios no tienen nada en común. Pero si se analiza el contenido
de esas canciones, se podría asociar que son del mismo intérprete, por lo tanto los usuarios pueden ser
similares.
Un efecto negativo a nivel de costo computacional es que las evaluaciones de los usuarios se
almacenan en una matriz, y a grandes cantidades de elementos y usuarios se vuelve cada vez más
dispersa. Distintas soluciones se plantean para este hecho, como descomponer la matriz, técnicas de
reducción de orden, entre otras [5].
5
2.5.
Discusión
En el presente trabajo se desarrollará un algoritmo que utilizará el filtrado colaborativo como
principal herramienta para la predicción de nuevas canciones. Se plantea no sólo utilizar la información
de usuarios similares para crear vecindades, sino que se puede agrupar a los disímiles también, y de
tal manera generar más predicciones y obtener un listado de elementos mucho mayor con el cual
recomendar.
En cuanto a los problemas presentados, se incluirá en los resultados esporádicamente una canción aleatoria para agregar novedad. No se utilizará recomendaciones basadas en contenido, ni tampoco técnicas de descomposición para ahorrar costos de tiempo de cálculo, todo para observar el
comportamiento de la nueva propuesta sin mayores mejoras, las cuales pueden ser estudiadas en un
trabajo futuro.
6
Capítulo 3. Hardware y software
3.1.
Introducción
En este capítulo se describe tanto el hardware como el software a utilizar. Se trabaja con un
sistema de servidor común donde se implementará la radio online. En tanto al software, pese a existir una gama de opciones para la implementación, se eligen estos programas por recomendaciones,
simplicidad y uso masivo.
3.2.
El servidor
El PC usado como servidor consta de un procesador Pentium 4, y fue facilitado por el profesor
guía. De sistema operativo se escoge Ubuntu Server 10.04 LTS por ser código abierto, sencillo y por
contar con buen soporte.
Algunas de las características del servidor se muestran en la tabla 1, y sus datos de configuración en la red de la universidad se muestran en la tabla 2.
Tabla 1: Hardware de Hopper
Procesador
Velocidad Procesador
Memoria RAM
Discos Duros
Intel(R) Pentium(R) 4
3.20 [GHz]
1 [GiB] DDR
33 [GiB] SCSI
33 [GiB] SCSI
149 [GiB] SATA
Tabla 2: Datos de Hopper
Dirección Web
Dirección IP
Máscara
Puerta de Enlace
DNS
hopper.die.udec.cl
152.74.23.87
255.255.0.0
152.74.23.1
152.74.16.14 / 152.74.16.83
En él se almacenan todas las canciones en formato MP3 a utilizar para la radio, en el directorio
/mnt/mp3/Radio/, ordenadas por artista y álbum. La dirección IP del servidor permite un acceso
directo desde Internet a todo el mundo, pero el streaming de audio inicialmente queda limitado por el
puerto 8000, el cual no está abierto fuera de la universidad por el DTI. De todas maneras, se solicita
el desbloqueo del puerto para permitir el uso de la radio online a gente fuera del campus.
7
Figura 1: Diagrama de funcionamiento de Ices y Icecast
3.3.
Software de streaming
El streaming es la transmisión de un flujo continuo en el tiempo de datos de audio o vídeo,
donde el receptor almacena cierta cantidad en una cola o buffer y reproduce este flujo a medida que
lo recibe. Entonces, a diferencia del mecanismo de descarga del archivo, no se necesita tener todo el
archivo para reproducirlo, a medida que se recibe se usa la información de manera fluida.
Por lo mismo, no es necesario asegurar que todos los paquetes de datos llegan al destino, ya
que al ser un flujo continuo, si se pierde un paquete se pueden seguir recibiendo los siguientes y así no
detener la reproducción final. Si el paquete perdido arriba a destino en forma tardía, éste se descarta,
ya que no tiene sentido adjuntarlo al flujo en reproducción.
Por recomendación del profesor guía, se trabaja con el servidor de streaming llamado Icecast
2 y su cliente Ices 0.4. La estructura de trabajo para estos programas se puede observar en la figura 1.
3.3.1.
Icecast 2
Este programa sirve para implementar un servidor de streaming de audio, el cual recibe dicho
streaming de otros clientes (S.C.) y lo transmite a los usuarios.
El sistema es mantenido por la Fundación Xiph.org. Es gratuito, su código es libre y soporta
distintos tipos de formatos, Ogg Vorbis, MP3, AAC, entre otros [8]. Es una alternativa al programa
propietario de servidor de medios SHOUTcast de Nullsoft. Se puede descargar de su página oficial
Icecast.org.
Los prerrequisitos software para instalar Icecast2 son:
Biblioteca libxml2 - http://xmlsoft.org/downloads.html
Biblioteca libxslt - http://xmlsoft.org/XSLT/downloads.html
8
curl - http://curl.haxx.se/download.html (>= versión 7.10).
ogg/vorbis - http://www.vorbis.com/files (>= versión 1.0)
En la sección 5.3 se detalla la instalación y configuración de este programa.
3.3.2.
Ices
Este cliente genera el streaming de audio enviado a Icecast2 para su transmisión. La razón de
separar la generación del streaming del envío a sus usuarios propiamente tal, es para que el cliente
pueda ser implementado en computadores con bajo ancho de banda, mientras el servidor lo sea en
otro PC con mejores condiciones de transmisión. Así, el cliente sólo debe mandar el streaming una
vez, y el servidor se encarga de reenviarlo a los demás.
Existen dos versiones funcionales, la 0.4 (ices0) y la 2.0 (ices2). La principal diferencia es el
soporte de formato de audio, ices0 sólo soporta MP3, mientras ices2 sólo soporta Ogg Vorbis. Por esta
razón se escoge ices0, ya que el formato MP3 aún siendo privativo, es más masivo y permite encontrar
mayor variedad.
Los prerrequisitos software para instalar ices0 son:
GCC y G++ - http://gcc.gnu.org/
LAME (mp3) - http://lame.sourceforge.net/
libshout - http://directory.fsf.org/wiki/Libshout
Python (dev) - http://www.python.org (Opcional)
Perl (dev) - http://www.perl.org (Opcional)
Tanto Ices como Icecast usan archivos XML para su configuración. Una gran ventaja de este
cliente es que permite añadirle funcionalidad mediante módulos en lenguajes Python o Perl para manejar el listado de canciones.
En la sección 5.4 se detalla la instalación y configuración de este programa.
3.4.
PHP y reproductor embebido web
Para no discriminar a los usuarios por sus sistema operativo, programando un cliente de recepción streaming, se prefiere implementar la radio en formato página web.
Primero se debe instalar PHP (http://www.php.net/) para poder montar una página web en el
servidor.
9
Luego es necesario un sistema de reproducción online, para ello se elige trabajar con reproductores basados en flash de Adobe por ser masivos. Se escoge Wimpy Button por ser simple, configurable, bien documentado y compatible con Icecast 2 [9].
En la sección 5.5.2 se detalla la configuración e implementación de este reproductor.
10
Capítulo 4. Desarrollo de un algoritmo predictivo y listado
adaptativo
4.1.
Introducción
En este capítulo se describe el desarrollo del algoritmo predictivo usando filtrado colaborativo,
y cómo generar una lista adaptativa de canciones para una radio online. Los pasos a continuación son
siguiendo el modelo de Flycasting [2], con algunas modificaciones.
Este algoritmo se implementa usando el lenguaje de programación Python, por las razones
expuestas en 3.3.2, y todas las funciones creadas se guardan en el archivo predictop.py.
4.2.
Una radio online
Primero es necesario definir las características de la radio donde se generará la lista. Una radio
online cuenta con 2 entidades básicas: usuarios (U) y canciones (C). Los usuarios son personas que
ingresan a la radio para escuchar música. Las canciones son archivos de audio usados para generar la
lista de reproducción de la radio. Adicionalmente se puede agregar una tercera entidad, los artistas,
pero en este caso no se trabajará con ello.
U: Universo de usuarios.
C: Universo de canciones.
Cada usuario registrado puede evaluar la canción que esté siendo reproducida en el momento, y
esa evaluación debe ser almacenada correctamente. Sólo existirá una evaluación por canción, pudiendo
ésta ser modificada. E(u) son las evaluaciones del usuario u.
E(u) : {c | c ∈ C ∧ u | u ∈ U ∧ u evaluó a c}
Finalmente se debe saber en todo momento quienes son los usuarios conectados escuchando
la radio. L(t) es el grupo de oyentes en un tiempo t.
L(t) : {u | u ∈ U ∧ u está escuchando en el tiempo t}
4.3.
Generar la lista
El objetivo final es generar una lista de reproducción que satisfaga a la mayor cantidad de
usuarios conectados en un momento dado. Al no exigirle coherencia de estilos al cambiar de una
canción a otra, no es necesario determinar artistas similares. Esta lista debe ser capaz de cambiar a
medida que entran y salen los oyentes, para adaptarse a las nuevas preferencias.
Para esto se deben cumplir las siguientes etapas:
11
1. Adaptar las evaluaciones.
2. Predecir la nota de las canciones que los usuarios no han evaluado.
3. Agrupar las canciones que tengan una evaluación promedio aceptable, según los actuales oyentes.
4. Seleccionar la nueva canción a reproducir.
4.3.1.
Adaptar evaluaciones
Como hay distintas maneras de evaluar las canciones (escalas, ponderaciones, etc.), es necesario traducir a valores aptos para seguir trabajando con ellos. Esta etapa es la menos rígida de todas,
ya que depende mucho de cómo se decida implementar el sistema de evaluaciones. En el caso de este
trabajo, las evaluaciones posibles son del 1 al 5, siendo 1 la peor evaluación y 5 la mejor.
El objetivo es ordenar usuarios vs. canciones y sus evaluaciones traducidas al rango [0, 1],
siendo 0 no evaluada y 1 la mejor evaluación, de tal manera que se puedan distinguir cuales han sido
ingresadas por los usuarios y cuáles hay que predecir. Sea W la matriz de orden n × m, donde n es la
cantidad de usuarios evaluadores y m las canciones evaluadas.
W ∈ Mn×m (R)
n: N de usuarios evaluadores.
m: N◦ de canciones evaluadas.
◦
4.3.2.
Predicción de evaluaciones
Con los datos en la matriz W se puede obtener un listado de las canciones mejor evaluadas para
elegir la siguiente a reproducir, pero usando filtrado colaborativo se puede extraer mucha información
adicional para una mejor elección. Esto se logra prediciendo la mayoría de las evaluaciones que fueron
traducidas a 0, las que no han sido ingresadas por los usuarios.
Hay distintas maneras de aplicar el filtrado colaborativo. En este caso se sigue la metodología
de Flycasting [2], usar el algoritmo de la diferencia cuadrática media o Mean Squared Difference
(MSD), junto con el algoritmo de correlación de Pearson.
Antes de detallar los algoritmos, es necesario definir 2 nuevos conjuntos.
T (x) es el grupo de canciones que han sido evaluadas por el usuario x.
T (x) : {c | c ∈ C ∧ W xc > 0}
P(y) es el grupo de usuarios que han evaluado la canción y.
P(y) : {u | u ∈ U ∧ Wuy > 0}
12
Figura 2: Ejemplo: Evaluaciones del usuario 1.
En la figura 2 se muestra un ejemplo de conjunto T (1), donde el usuario 1 ha evaluado 6 de las
8 canciones disponibles. Los discos representan distintas canciones, las flechas azules corresponden a
nota máxima, y las rojas a nota mínima.
Con esto se puede definir el algoritmo MDS. El primer paso es determinar cuán similares
son dos usuarios x1 y x2 . La similitud se mide como la media de las diferencias cuadráticas de las
evaluaciones que ambos usuarios han hecho sobre las mismas canciones. Sea mds(x1 , x2 ) la diferencia
cuadrática media entre los usuarios x1 y x2 .
Si |T (x1 ) ∩ T (x2 )| > 0, se tiene
P
mds(x1 , x2 ) :
(W x1 i − W x2 i )2
|T (x1 ) ∩ T (x2 )|
i∈T (x1 )∩T (x2 )
(1)
Si |T (x1 ) ∩ T (x2 )| = 0, entonces ambos usuarios no han evaluado canciones en común, por
tanto mds(x1 , x2 ) debe ser 1, el valor más alto posible. Se puede apreciar que 0 ≤ mds(x1 , x2 ) ≤ 1.
En la figura 3 se continúa el ejemplo anterior, mostrando las evaluaciones realizadas por el
usuario 2 y las comunes entre ambos usuarios. |T (x1 ) ∩ T (x2 )| valdría 4.
El segundo paso del algoritmo es calcular los pesos, wt(x1 , x2 ), los que representan cuán similar
es el usuario x2 al usuario x1 .
wt(x1 , x2 ) : 1 − mds(x1 , x2 )
(2)
Se puede notar que 0 ≤ wt(x1 , x2 ) ≤ 1. Con wt(x1 , x2 ) = 1 significa que ambos usuarios son
idénticos, y mientras wt(x1 , x2 ) sea alto, significa que los usuarios son más parecidos. Así mismo,
wt(x1 , x2 ) más bajo significa que su similitud disminuye, y si wt(x1 , x2 ) = 0 significa que no tienen
canciones evaluadas en común para comparar. Con estos pesos se arma la matriz de pesos WT .
13
Figura 3: Ejemplo: Evaluaciones comunes entre dos usuarios.
WT ∈ Mn×n ([0, 1]) | WT xy = wt(x, y)
n: Cantidad de usuarios.
Es útil notar que el valor mínimo que se puede calcular como peso para este caso es de 0,36,
ya que la nota mínima es 0,2 y la más alta es 1 normalizada. Suponiendo que no hay más coincidencias, usando MDS resulta 0,82 = 0,64 y el peso será 1 − 0,64 = 0,36. Del ejemplo, como todas las
evaluaciones en común entre los usuarios 1 y 2 son contrarias, el peso entre ellos sería el mínimo,
0,36.
Lo siguiente es determinar cómo usar los pesos y evaluaciones para predecir las canciones
no ingresadas. Flycasting [2] propone definir un vecindario para cada usuario x que sea útil para la
predicción. Este vecindario consiste en todos los otros usuarios u tal que WT xu sea mayor o igual a un
umbral Φ. El vecindario V(x) queda conformado por la cantidad de usuarios suficientemente similares
a x para predecir las evaluaciones que faltan. Entonces, V(x) se define como
V(x) : {u | u ∈ U ∧ WT xu ≥ Φ}
En este trabajo, se propone modificar esta manera de predecir, ya que puede ocurrir el caso que
ningún peso fuese mayor al umbral Φ, por lo tanto V(x) = 0, x no tendría usuarios similares y no se le
podría predecir ninguna evaluación adicional, lo que disminuye notablemente el conjunto de posibles
canciones a reproducir.
Por lo tanto, se plantea que para cada usuario se separen los demás en dos categorías, similares
y disímiles. Los usuarios similares son los mismos definidos anteriormente como V(x), y todos los que
no queden en este grupo están entonces en la categoría de disímiles. Sean S (x) los usuarios similares
y D(x) los disímiles.
S (x) : {u | u ∈ U ∧ WT xu ≥ Φ}
(3)
14
Figura 4: Ejemplo: Predicciones para usuarios 1 y 2
D(x) : {u | u ∈ U ∧ WT xu < Φ ∧ WT xu , 0}
(4)
De esta manera se obtiene más información. Mientras x tenga pesos no nulos, si no hay una
vecindad de usuarios similares a x deberá existir una vecindad de disímiles, por tanto se puede predecir
de manera inversa. Esto garantiza que mientras dos usuarios tengan una canción evaluada en común,
se podrá realizar predicción.
Esta proposición se basa en el supuesto que a las personas normalmente le gusta y disgustan
ciertos estilos definidos. Por ejemplo, a un amante de la música clásica normalmente no le gusta
la música tropical, o alguien que disfruta del pop es poco probable que escuche metal gótico. Se
reconoce que puede ser muy drástica la separación entre usuarios similares y disímiles solamente, y
probablemente sea necesario definir otros niveles entremedio, pero se considera éste un buen punto de
partida.
En la figura 4 se continúa con el ejemplo, ilustrando con flechas entrecortadas las predicciones
para cada usuario. Siguiendo el método de Flycasting, al tener únicamente evaluaciones opuestas, el
peso entre los usuarios sería muy bajo, por lo tanto, no podría predecir absolutamente nada con esta
información. Según el método propuesto, se reconocería a los usuarios como disímiles y se lograría
predecir la nota correspondiente.
Para predecir la evaluación de un usuario x a una canción y se usa el promedio ponderado de
las notas de los demás usuarios que han evaluado y. Claramente si x evaluó y, se usa esa nota en vez
de una predicción.
Para esto, se deben obtener primero las predicciones por separado usando S (x) y D(x).
Sea w0S (x, y) la predicción del usuario x para la canción y usando a los usuarios similares S (x)
P
w0S (x, y)
:
(WT xi · Wiy )
i∈S (x)∩P(y) WT xi
i∈(S (x)∩P(y))
P
(5)
15
Sea w0D (x, y) la predicción del usuario x para la canción y usando a los usuarios distintos D(x).
Rtop es una constante que se ajusta según la escala de evaluación, normalmente la nota siguiente a
la máxima, para que al restarle una nota normal se obtenga la inversa. En este caso sería 6, como la
escala es de 1 a 5.
P
i∈(D(x)∩P(y)) (WT xi · Wiy )
0
P
(6)
wD (x, y) : Rtop −
i∈D(x)∩P(y) WT xi
Rtop : Constante para invertir evaluación.
De esta manera, los usuarios del grupo S (x) predicen una evaluación, y los del grupo D(x)
predicen otra. Para juntar ambas predicciones se vuelve a usar el promedio ponderado, siendo la cantidad de usuarios en ambos grupos la ponderación.
Sea NS el número de usuarios en S (x) y ND el número de usuarios en D(x), y w0 (x, y) la
predicción final de x sobre y. Entonces, se tiene
w0 (x, y) :
(w0S (x, y) · NS ) + (w0D (x, y) · ND )
NS + ND
(7)
Nótese que si NS + ND = 0, ese usuario no tendría ninguna canción evaluada en común con
otro y no se podrían predecir sus evaluaciones que faltan. Con este resultado, se obtiene la matriz de
predicción W 0 tal que
0
W 0 ∈ Mn×m (R) | W xy
= w0 (x, y)
n: Cantidad de usuarios.
m: Cantidad de canciones.
Esta matriz W 0 contiene las evaluaciones de los usuarios y las predicciones. Por lo tanto, se
tiene toda la información posible para el paso siguiente, generar un listado de canciones populares.
4.3.3.
Las más populares
En esta etapa se usa la matriz de predicción W 0 . Esta matriz es calculada para estimar qué
canciones son las que más le gustan a cada usuario. Las canciones populares son consideradas como
las que tienen una evaluación promedio sobre un umbral ω. Recordando que L(t) son los usuarios
conectados, se tiene CP(t) como el conjunto de canciones populares, ambos en el tiempo t.
CP(t) : y | y ∈ C ∧
P
i∈L(t)
Wiy0
|L(t)|
>ω
(8)
16
Nótese que L(t) = 0 significa que no hay usuarios escuchando la radio en ese momento, por
tanto no tiene sentido tratar de elaborar un listado de canciones populares. Importa el hecho que si
un oyente no ha evaluado, pertenece a L(t) pero no aporta con canciones sobre el umbral, por lo que
escuchará lo que le guste a los demás o cualquier canción.
4.3.4.
La próxima canción
Ya teniendo un listado de canciones populares, se puede determinar la siguiente a reproducir.
Una manera sencilla de seleccionar la siguiente es elegir la primera canción, o bien cualquiera
de este listado, e introducirla al final para que sea una reproducción cíclica y sólo cambie de acuerdo
a los usuarios conectados. El principal problema de este modelo es que las populares empezarían a
repetirse, las menos populares nunca sonarían en la radio, y peor aún, no habrían canciones nuevas en
las predicciones, ya que si no fueron evaluadas antes de iniciar el sistema, nunca serán reproducidas.
Para solucionar esto último se pueden agrupar las canciones que nunca han sido evaluadas, y
cada cierto tiempo reproducir una de estas mediante un sistema probabilístico, de tal manera que algún
usuario evalúe dicha canción desconocida y así ésta entre al sistema.
Con respecto a las canciones repetitivas y las menos populares (bajo el umbral ω), se les podría
agregar un peso o cuenta a todas, para así limitar la cantidad excesiva de repeticiones y subir de nivel
a alguna menos popular cada cierto tiempo. También se puede generar un listado de las últimas N
reproducidas, y así evitar muchas repeticiones.
4.4.
El algoritmo en Python
En esta sección se explica cómo se desarrolla el algoritmo predictivo y la generación de la lista
adaptativa en Python, para que se pueda importar directamente desde el módulo mencionado en la
sección 3.3.2.
Las funciones se escriben dentro del archivo preditop.py, y a continuación se describe el
funcionamiento de cada una.
4.4.1.
Función create_matrix
En esta función se crea de manera recursiva una matriz de orden CxF, siendo C el número de
columnas y F el de filas. Recibe los siguientes argumentos:
shape: Tupla de forma (C, F), siendo C la cantidad de columnas y F de filas.
value: Valor inicial de los elementos de la matriz. Si se omite toma valor 0.
17
def create_matrix(shape, value = 0):
if not shape:
return value
return [create_matrix(shape[1:], value) for _ in xrange(shape[0])]
4.4.2.
Función MDS
Antes de definir la función, es necesario explicar lo siguiente: Python posee un tipo de estructuras llamadas diccionarios, similares a una lista, pero que no ordenan secuencialmente los elementos
guardados, y tampoco poseen índices para acceder a un dato, sino más bien se acceden mediante una
clave que puede ser entero, cadena de texto, entre otros.
Hecha la aclaración, en esta función se calcula la diferencia cuadrática media (4.3.2) entre
diccionarios de dos usuarios, donde las claves son el índice de la canción y el resultado es la evaluación
de cada una. Dichas evaluaciones no deben estar normalizadas. Recibe los siguientes argumentos:
x1 : Diccionario de usuario 1.
x2 : Diccionario de usuario 2.
norm: Flotante usado para normalizar valores. Si se omite toma valor 5.0.
Si la canción en x1 tiene una evaluación (distinta a cero) y x2 también, entonces se calcula la
potencia de las diferencias normalizadas y se suma a la diferencia total. Cuando se termina de revisar
el diccionario, si no hubo canciones evaluadas en común se debe retornar 1.0, y si hubo se retorna la
división entre las potencias acumuladas y la cantidad de coincidencias.
def MDS(x1, x2, norm = 5.0):
enambos = 0.0
sdif = 0.0
for track in x1:
if x1[track] != 0 and x2[track] != 0:
sdif += pow(x1[track]/float(norm) - x2[track]/float(norm), 2)
enambos += 1.0
if enambos == 0:
return 1.0
else:
return sdif/enambos
18
4.4.3.
Función CalculaPesos
En esta función se calcula y retorna la matriz cuadrada de pesos de orden N, definida en la
sección 4.3.2 (WT ). N es la cantidad de usuarios que han evaluado alguna canción. Recibe sólo un
argumento:
X: Lista de diccionarios de índice de canción y evaluación de los usuarios.
Primero se crea la matriz cuadrada de orden N, siendo N la cantidad de usuarios con alguna
evaluación, e inicializada en 1. Luego, por cada diccionario en la lista, se revisa cada diccionario en
la lista y, adicionalmente, crea los índices i y j por cada uno. Si son el mismo diccionario (i = j) no
es necesario calcular el peso. En vez de calcular un peso de la triangular inferior ( j > i), se copia de
la triangular superior, y en otro caso (i > j) se calcula el peso. Esto se puede realizar ya que la matriz
de pesos por definición es simétrica, y siempre se calcula primero el peso a copiar de la triangular
superior.
def CalculaPesos(X):
PesosPrediccion = create_matrix((len(X), len(X)), 1)
for i, inlista in enumerate(X):
for j, dicts in enumerate(X):
if i == j:
continue
elif PesosPrediccion[j][i] != 1:
PesosPrediccion[i][j] = PesosPrediccion[j][i]
else:
PesosPrediccion[i][j] = 1 - MDS(inlista, dicts, 5.0)
return PesosPrediccion
4.4.4.
Función Predict
En esta función se predicen las evaluaciones que los usuarios no han ingresado en base a todas
las canciones que han sido evaluadas. Recibe los siguientes argumentos:
X: Lista de diccionarios de índice de canción y evaluación de los usuarios.
P: Matriz de pesos de orden N, donde N es la cantidad de usuarios que han evaluado alguna
canción.
U: Umbral de separación que define usuarios similares y disímiles. Si se omite toma valor 0.5.
19
Es necesario definir algunas variables antes de iniciar la predicción. En POSnum y POSden se
calcula el numerador y denominador de la predicción con todos los usuarios similares, el conjunto
S (x). En NEGnum y NEGden se calcula el numerador y denominador de la predicción con todos los
usuarios disímiles, el conjunto D(x). En nPOS y nNEG se cuenta la cantidad de usuarios similares y
disímiles respectivamente.
Además, para ir guardando las predicciones calculadas y no intervenir en el cálculo de las que
faltan, es necesario crear una copia completa de X, y esta copia se retorna al final de la función. Para
realizar dicha copia se necesita importar el módulo copy.
def Predict(X, P, U = 0.5):
POSnum = 0.0
POSden = 0.0
NEGnum = 0.0
NEGden = 0.0
nPOS = 0
nNEG = 0
Xfinal = copy.deepcopy(X)
...
Luego se crea un índice i y por cada diccionario Xdict en la lista X, por cada evaluación
presente, si es distinta a cero significa que ya fue evaluada, por lo tanto no se cambia.
def Predict(X, P, U = 0.5):
...
for i, Xdict in enumerate(X):
for inXdict in Xdict:
if Xdict[inXdict] != 0:
continue
else:
...
Si la evaluación es cero, significa que no ha sido ingresada y es necesario predecir. Entonces
se crea un índice j y por cada peso del usuario de diccionario Xdict, si i=j significa que es el mismo
usuario, no tiene sentido una predicción auto referente.
Si el peso es menor a 0.2 significa que los usuarios no tienen evaluaciones en común (recordar
que el menor peso posible es 0.36, dos usuarios completamente disímiles, luego pasa a 0 cuando no
hay nada en común).
20
Si el peso es mayor al umbral, y el usuario que sirve de referencia a la predicción evaluó la
canción en cuestión, entonces se le suma a POSnum y POSden su cálculo correspondiente y se cuenta
un usuario similar. Lo mismo ocurre si el peso es menor que el umbral, pero a NEGnum y NEGden
respectivamente.
En cualquier otro caso, como que exista peso pero no evaluación con qué predecir, se sigue
buscando.
def Predict(X, P, U = 0.5):
...
for i, Xdict in enumerate(X):
...
else:
for j,ipesos in enumerate(P[i]):
if i == j:
continue
elif ipesos < 0.2:
continue
elif ipesos >= U and X[j][inXdict] != 0:
POSnum += ipesos*X[j][inXdict]
POSden += ipesos
nPOS += 1
elif ipesos < U and X[j][inXdict] != 0:
NEGnum += ipesos*X[j][inXdict]
NEGden += ipesos
nNEG += 1
else:
continue
...
Después de revisar las posibles combinaciones con los pesos y las evaluaciones de referencia,
es necesario asegurar que la división de numeradores y denominadores sea posible. Luego, si hubo
coincidencia para calcular predicción, se realiza el cálculo descrito en la sección 4.3.2.
21
def Predict(X, P, U = 0.5):
...
else:
...
if POSden < 0.2: # peso menor 0.36
POSdiv = POSnum
else:
POSdiv = POSnum/POSden
if NEGden < 0.2: # peso menor 0.36
NEGdiv = NEGnum
else:
NEGdiv = NEGnum/NEGden
if nPOS == 0 and nNEG == 0:
predicha = 0
else:
predicha = (POSdiv*nPOS+(6-NEGdiv)*nNEG)/(nPOS+nNEG)
...
Finalmente, se actualiza la evaluación en cuestión con su predicción y las variables se igualan
a cero. Al terminar la predicción para todas las evaluaciones no realizadas, se retorna la nueva lista
con diccionario de evaluaciones y predicciones.
def Predict(X, P, U = 0.5):
...
else:
...
Xfinal[i][inXdict] = predicha
POSnum = 0.0
POSden = 0.0
NEGnum = 0.0
NEGden = 0.0
nPOS = 0
nNEG = 0
return Xfinal
22
4.4.5.
Función GetPredicciones
En esta función se importan las evaluaciones de todos los usuarios, se ordenan en una lista de
diccionarios, se llama a las funciones anteriores para obtener las predicciones y se retorna el resultado.
Recibe un solo argumento:
rutausers: Cadena de texto indicando la ruta donde se encuentran los archivos de los usuarios.
Los archivos de usuarios deben tiene la estructura definida en 5.6.1.
Según los pasos explicados en la sección 4.3, el primer objetivo es ordenar los usuarios y todas
sus evaluaciones. En 4.3.1 se recomienda hacerlo en una matriz W de orden n × m, donde n es la
cantidad de usuarios evaluadores y m las canciones evaluadas.
Por comodidad se decide no implementar W como una matriz, sino más bien como un diccionario de usuarios y sus evaluaciones, donde estas quedan agrupadas en otro diccionario de índice
de canción y evaluación.
Para esto se debe obtener todas las evaluaciones de todos los usuarios, se logra recorriendo
el directorio donde se almacenan todos los usuarios (/var/www/.usuarios/), y se crea un listado llamado allusers con todos los nombres de usuarios que existen. Para poder recorrer un directorio es
necesario importar el módulo os.
def GetPredicciones(rutausers):
allusers = []
for root,dirs,files in os.walk(rutausers):
for file in [f for f in files]:
allusers.append(file)
Luego se crean dos listados vacíos, uno llamado listaUsersEval para agregar todos los
usuarios que han evaluado alguna canción, y listadict para ordenar los diccionarios de índice canción
y evaluación que se van creando. Luego por cada usuario listado en allusers se abre su archivo, se
descarta su clave y correo electrónico, y si aún quedan datos por extraer, se agrega dicho usuario a
listaUsersEval.
23
def GetPredicciones(rutausers):
...
listaUsersEval = []
listadict = []
for user in allusers:
fu = open(rutausers+user, ’r’)
evals = fu.read()
fu.close()
evals = evals.split("\n")
evals.pop() # Borra ultimo elemento nulo
evals.pop(0) # Borra Password
evals.pop(0) # Borra Correo
if (len(evals) == 0):
continue
else:
listaUsersEval.append(user)
...
Ahora se sigue trabajando sólo con los usuarios que han evaluado alguna canción. El contenido
leído sigue siendo una lista de cadenas de texto con formato índice evaluación, por lo que es necesario cambiarla a una lista de tupla de dos enteros, para luego transformarla a un diccionario llamado
dictevals, donde las claves sean los índices de las canciones y el contenido sean las evaluaciones.
def GetPredicciones(rutausers):
...
for user in allusers:
...
for index, pareval in enumerate(evals):
evals[index] = tuple(int(i) for i in pareval.split(" "))
dictevals = dict(evals)
...
24
Es necesario que todos los diccionarios tengan la mismas claves (índices de canciones). Si
algún usuario no ha evaluado la canción, entonces ésta queda registrada con un cero para saber que se
necesita predecir dicha evaluación. Esto se realiza de la siguiente manera:
1. Se genera el nuevo diccionario en dictevals.
2. Por cada índice presente en el diccionario, si no se encuentra en algún diccionario listado en
listadict, se agrega con nota cero.
3. Por cada índice presente en cada diccionario listado en listadict, si no se encuentra en
dictevals, se agrega con nota cero.
4. Se agrega dictevals a la lista listadict.
Esto se repite hasta que no hayan usuarios con evaluaciones.
def GetPredicciones(rutausers):
...
for user in allusers:
...
dictevals = dict(evals)
for inlista in listadict:
for indict in dictevals:
if not indict in inlista:
inlista.setdefault(indict,0)
for ininlista in inlista:
if not ininlista in dictevals:
dictevals.setdefault(ininlista,0)
listadict.append(dictevals)
...
Teniendo el listado de diccionarios de índices de canciones y evaluaciones, y con ceros donde
se necesita predecir, se puede llamar a la función CalculaPesos, para luego llamar a Predict con
un umbral medio Φ de 0.7 (el máximo peso es 1 y el mínimo 0.36). Finalmente se crea un diccionario
llamado dictUsersEvals para asociar a los nombres de usuarios con sus diccionarios de índices y
evaluaciones, y luego se retorna como resultado final.
25
def GetPredicciones(rutausers):
...
PesosPrediccion = CalculaPesos(listadict)
listadict = Predict(listadict, PesosPrediccion, 0.7)
dictUsersEvals = {}
for i,dicc in enumerate(listadict):
dictUsersEvals[listaUsersEval[i]] = dicc
return dictUsersEvals
4.4.6.
Función TopTracks
En esta función se genera un listado de todas las canciones evaluadas o predichas para ciertos
usuarios sobre un umbral. Recibe tres argumentos:
Pred: Diccionario de usuarios con diccionarios de índices de canciones y evaluaciones o predicciones (se obtiene de GetPredicciones).
Users: Lista de usuarios.
Tol: Nota mínima que deben tener las canciones. Si se omite toma valor 3.
La lista Users debe contener a los usuarios conectados escuchando la radio. Lo primero es
revisar que si no hay usuarios en la lista, la función retorna falso (False) automáticamente. De lo
contrario genera otra lista llamada listtracks con todas las canciones predichas para todos los
usuarios en Users.
def TopTracks(Pred, Users, Tol = 3):
if (len(Users) == 0):
return False
listtracks = []
for user in Pred:
for track in Pred[user]:
if track not in listtracks:
listtracks.append(track)
...
Luego, se tiene una variable para calcular la media, otra para contar cuántos usuarios aportan a
la media y una lista vacía para añadir las canciones que superen el umbral. Por cada canción presente
26
en listtracks, y por cada usuario listado en Users, si el usuario no se encuentra en Pred, significa
que no ha evaluado canción alguna, por lo tanto no afecta al listado final. De lo contrario, se suma la
nota a la media y se aumenta la cuenta de usuarios. Se divide la acumulación de notas por la cantidad
de usuarios para obtener la media, y si supera el umbral, se agrega al listado final, e independiente de
eso se igualan a cero las variables mencionadas. Finalmente se retorna el listado resultante.
def TopTracks(Pred, Users, Tol = 3):
...
media = 0
uTotal = 0
listTop = []
for track in listtracks:
for uname in Users:
if uname not in Pred:
continue
userdict = Pred[uname]
media += userdict[track]
uTotal += 1
media /= uTotal
if (media >= Tol):
listTop.append(track)
media = 0
uTotal = 0
return listTop
4.4.7.
Función PredictTop
En esta función se usan todas las anteriores. Se une el resultado de GetPredicciones con
el de TopTracks y se retornan en una lista pareada. Además, se puede escribir todo el arreglo de
predicciones a un archivo externo. Recibe cuatro argumentos:
rutaonline: Ruta del archivo que contiene lista de usuarios conectados escuchando la radio.
rutausers: Ruta del directorio que contiene los archivos de usuarios.
tol: Nota mínima usada como tolerancia. Si se omite toma valor 3.
rutaPtoF: Ruta del archivo donde escribir arreglo de predicciones. Si se omite queda como una
cadena de texto vacía.
27
Primero se obtienen las predicciones y se guardan en prediccion. Si se ingresa una ruta
en rutaPtoF, se abre el archivo, se borra su contenido y se escriben las predicciones siguiendo el
formato especificado: Username es el nombre de cada usuario, track el índice de cada canción y
pred la predicción para cada una.
Username1:track11=pred11;track12=pred12;...
Username2:track21=pred21;track22=pred22;...
Luego se abre el archivo de rutaonline, se lee su contenido, se genera el listado de usuarios
conectados escuchando la radio, y finalmente se retorna el resultado de la predicción y el listado de
TopTracks en una lista de dos elementos (diccionario y lista).
def PredictTop(rutaonline, rutausers, tol=3, rutaPtoF = ""):
prediccion = GetPredicciones(rutausers)
if(rutaPtoF != ""):
fPtoF = open(rutaPtoF, ’w’)
for user in prediccion:
fPtoF.write(user+":")
for track in prediccion[user]:
fPtoF.write(str(track)+’=’+str(prediccion[user][track])+’;’)
fPtoF.write("\n")
fPtoF.close()
fonlineusers = open(rutaonline, ’r’)
users = fonlineusers.read()
fonlineusers.close()
users = users.split("\n")
users.pop()
return [prediccion, TopTracks(prediccion, users, tol)]
Con estas funciones se puede predecir y obtener un listado adaptativo de canciones. Las pruebas realizadas al algoritmo se pueden ver en la sección 6.2.
28
Capítulo 5. Implementación de la radio online
5.1.
Introducción
En este capítulo se explica la implementación de la radio online, desde la preparación del
servidor, los programas para generar y transmitir el streaming de audio, el sistema de evaluación de
los usuarios y cómo aplicar el algoritmo predictivo para generar una lista adaptativa.
5.2.
Preparando el servidor
El servidor tiene instalado el sistema operativo Ubuntu Server 10.04 LTS, por lo tanto cuenta
con un gestor de paquetes que permite instalar de manera rápida y eficiente la mayoría de los programas necesarios, hasta se encarga de instalar dependencias y/o prerrequisitos. Se puede usar por línea
de comandos o en su interfaz gráfica. Se prefiere línea de comandos ya que el manejo de paquetes y
dependencias es más trasparente.
Para instalar los programas, se necesita permiso de administrador (root) o súper usuario, y el
gestor se invoca mediante el comando
sudo apt-get install PROGRAMA
PROGRAMA sería el programa a instalar. El comando retorna si existe dentro de los registros para
instalarlo directamente, y de ser así, entonces sólo queda aceptar la instalación. Si no está disponible
para ésta distribución de Ubuntu, entonces es necesario bajar los archivos fuente.
Para la segunda opción es necesario contar con el comando make, el cual se puede descargar e
instalar usando el gestor, de la manera
sudo apt-get install make
Luego, para instalar programas usando make se deben seguir los siguientes pasos:
1. cd RUTA
2. ./configure
3. make
4. make install
RUTA sería la ruta a los archivos fuente descargados. Algunos programas pueden requerir
realizar la llamada a make como súper usuario.
Además, normalmente los archivos fuente vienen comprimidos en un archivo .tar.gz. Para
descomprimirlos se debe usar el siguiente comando
29
tar xvzf archivo.tar.gz
Esto cubre cómo instalar los programas en el servidor. A continuación se describe la instalación
de los programas a usar, su configuración, y la implementación de la página web de la radio online.
5.3.
Streaming: Icecast2
5.3.1.
Instalar Icecast2
Como se detalla en la sección 3.3.1, este programa tiene varios prerrequisitos, pero afortunadamente Icacast2 se encuentra dentro de los repositorios para Ubuntu Server 10.04 LTS, por lo tanto se
puede instalar usando el gestor de paquetes de la siguiente manera
sudo apt-get install icecast2
5.3.2.
Configurar Icecast2
Teniendo Icecast2 instalado, es necesario realizar su configuración. Esto se hace modificando
un archivo XML tipo, el cual se encuentra en la ruta /etc/icecast2/icecast.xml. El archivo
consta de todas las posibles opciones que tiene el programa, siendo las básicas las siguientes:
<icecast>
<limits> ... </limits>
<authentication> ... </authentication>
<hostname>...</hostname>
<listen-socket> ... </listen-socket>
<fileserve>1</fileserve>
<paths> ... </paths>
<logging> ... </logging>
<security> ... </security>
</icecast>
limits: Define la cantidad máxima de conexiones fuentes (S.C.) y clientes, y parámetros de
conexión. No necesita ser modificado.
authentication: Define las claves de conexión para fuente (source, S.C.), relay (para logs, no
usado), y nombre y contraseña para administrador.
hostname: Nombre del servidor de streaming.
listen-socket: El socket (puerto) donde emitir streaming. No necesita ser modificado.
30
fileserve: Define si se puede servir archivos estáticos. No necesita ser modificado.
paths: Rutas a archivos de registro de conexión, solicitud de archivos estáticos, de administración, entre otros. No necesita ser modificado.
logging: Nombre de los archivos de registro de acceso y errores, y qué tipo de eventos registrar.
No necesita ser modificado.
security: Agregar seguridad al servidor usando chroot. No necesita ser modificado.
Como se puede apreciar, la mayoría de los valores por omisión son suficientes para configurar
correctamente el servidor. Lo que sí se recomienda cambiar es la clave de conexión para fuentes y el
usuario y contraseña del administrador.
<authentication>
<!-- Sources log in with username ’source’ -->
<source-password>claveCliente</source-password>
<!-- Relays log in username ’relay’ -->
<relay-password>relay</relay-password>
<!-- Admin logs in with the username given below -->
<admin-user>Administrador</admin-user>
<admin-password>claveAdmin</admin-password>
</authentication>
claveCliente sería la clave para usuarios fuentes (S.C.), Administrador el nombre del
usuario administrador y claveAdmin la clave del administrador. La clave para usuarios relay carece
de importancia, ya que no es usada actualmente.
La ruta donde se crearán los archivos de registro por defecto (/var/log/icecast2) puede causar
problemas. Para esto hay dos posibles soluciones: cambiar la ruta o cambiar los permisos del directorio. Para mantener el orden de los archivos de registro dentro del sistema operativo, se prefiere cambiar
los permisos.
Finalmente, para levantar el servidor se llama a icecast2 por línea de comando, -c para
especificar la ruta del archivo XML de configuración y -b para ejecutarlo separado a la consola.
icecast2 -c /etc/icecast2/icecast.xml -b
31
5.4.
Streaming: Ices
5.4.1.
Instalar Ices
Como se describe en la sección 3.3.2, la versión de Ices que reproduce archivos MP3 es ices0.
Esta no se encuentra disponible en los repositorios, por lo que no se puede usar el gestor de descargas
para instalarlo. Por tanto, es necesario descargar los archivos fuente de http://icecast.org/ices.php.
Antes de instalarlo, se deben tener resueltas todas las dependencias. Casi todas se pueden
obtener usando el gestor de instalación.
sudo apt-get install gcc g++
sudo apt-get install libshout3-dev
sudo apt-get install python-dev
LAME se debe instalar descargando los archivos fuente: http://lame.sourceforge.net/.
Teniendo las dependencias listas, se procede a instalar ices0 pero agregándole dos opciones. La
primera es with-python para poder usar Python para manejar la lista de reproducción, y la segunda
es with-lame para acceder a la metadata de los archivos MP3. Estas opciones se agregan en la etapa
de configuración.
./configure with-python with-lame
5.4.2.
Configurar Ices
Una vez instalado ices0, es necesario configurarlo de la misma manera que Icecast2, editando
un archivo XML de configuración, el cual se encuentra en la ruta /usr/local/etc/ices.conf.dist. En él
se pueden configurar todas las opciones del programa. Las básicas son:
<ices:Configuration xmlns:ices="http://www.icecast.org/projects/ices">
<Playlist> ... </Playlist>
<Execution> ... </Execution>
<Stream> ... </Stream>
</ices:Configuration>
Playlist: Define si la lista de reproducción será manejada por ices0, por módulos de Python o
Perl. Por defecto la maneja ices0.
Execution: Define el comportamiento del programa.
Stream: Datos de conexión y características del stream.
Los valores por defectos no son suficientes para configurar un stream avanzado. A continuación
se detallan las opciones de cada marco XML.
32
<Playlist>
<File>playlist.txt</File>
<Randomize>1</Randomize>
<Type>builtin</Type>
<Module>ices</Module>
</Playlist>
File: Ruta a archivo que contiene lista de rutas de canciones a reproducir (playlist). [builtin].
Randomize: Si se desea reproducir aleatoria la lista; 0: No, 1: Sí. [builtin]
Type: Especifica cómo se maneja la lista de reproducción. Puede ser builtin (el programa la
maneja), python o perl.
Module: Nombre del módulo a pasarle al programa si la lista se maneja con Python o Perl.
Poder configurar si la lista se maneja por defecto [builtin], por Python o Perl es lo que permite
escribir funciones que manejen cuál es la siguiente canción a reproducir. En este caso, se usa Python.
<Execution>
<Background>0</Background>
<Verbose>0</Verbose>
<BaseDirectory>/tmp</BaseDirectory>
</Execution>
Background: El programa se ejecuta como demonio (despegado de consola); 0: No, 1: Sí.
Verbose: El programa emite salidas de texto mas detalladas; 0: No, 1: Sí.
BaseDirectory: Directorio base para crear archivos de comportamiento.
<Stream>
<Server>
<Hostname>localhost</Hostname>
<Port>8000</Port>
<Password>letmein</Password>
<Protocol>http</Protocol>
</Server>
...
</Stream>
Hostname: Dirección o IP del servidor de streaming al cual conectarse.
33
Port: Puerto del streaming
Password: Clave del usuario fuente (S.C.) en el servidor.
Protocol: Encabezado del protocolo a usar, shoutcast: “icy”, icecast 1.x: “xaudiocast”, icecast
2: “http”.
<Stream>
<Server> ... </Server>
<Mountpoint>/ices</Mountpoint>
<Name>Default stream</Name>
<Genre>Default genre</Genre>
<Description>Default description</Description>
<URL>http://localhost/</URL>
<Public>0</Public>
</Stream>
Mountpoint: Punto de montaje en servidor Icecast.
Name: Nombre del streaming.
Genre: Estilo del streaming.
Description: Descripción del streaming.
URL: Dirección a una página externa.
Public: Si el servidor puede publicar el streaming, 0: No, 1: Sí.
5.4.3.
Ices y Python
El sistema builtin de ices0 es suficiente para reproducir aleatoriamente canciones listadas en
un archivo, pero el objetivo final que se busca en este trabajo es adaptar la lista de reproducción a los
gustos de los usuarios. Por lo tanto, es necesario poder elegir la siguiente canción.
El programa viene configurado para que, en caso de especificar python en vez de builtin,
se llame a un módulo Python con cinco funciones básicas. El archivo que viene junto a los fuente de
ices0, ices-0.4/conf/ices.py.dist, trae un ejemplo básico. Para mayor comodidad se crea el directorio
/home/felipe/ices/conf/ para guardar los archivos Python y el XML de configuración.
Las cinco funciones son:
ices_init (): Se llama cuando arranca ices0 para iniciar el ambiente de Python. Retorna 1
no hay errores, o 0 si algo falla.
34
ices_shutdown (): Se llama al cerrar ices0 para detener el ambiente Python. Retorna 1 si no
hay errores, o 0 si algo falla.
ices_get_next (): Se llama para obtener la ruta de la siguiente canción a reproducir. Retorna
un string.
ices_get_metadata (): Retorna el string a usar como metadata. Si retorna null se usa el
comentario del archivo.
ices_get_lineno (): Se usa para escribir el número de la canción dentro del archivo /tmp/ices.cue donde se guardan datos de la canción en reproducción. Retorna un entero.
Otro de los grandes problemas que tiene el método builtin, es que depende de una playlist
externa creada de manera independiente, para generar su lista de reproducción interna. Por lo tanto
cuando se agregan nuevas canciones a la radio, el orden se puede alterar y con ello los índices.
Esto no sirve si se quiere emparejar las evaluaciones con dicho índice. Por lo tanto, en las
funciones a escribir se desarrolla un sistema de revisión de canciones dentro de un directorio, que
luego genera una lista externa en base a las canciones encontradas. Si después se agregan nuevas
canciones al directorio, al volver a ejecutar ices0 se agregan al final de dicha lista, manteniendo el
orden.
Entonces, en la función ices_init() que inicia el ambiente Python, se lee el archivo /home/felipe/ices/playlist.txt y se ordenan las canciones en la lista playlist. Luego se revisa el directorio
donde se encuentran todas las canciones a reproducir, (/mnt/mp3/Radio/), en busca de archivos
MP3, y se busca cada uno de ellos en la lista playlist. Si se encuentra, pasa al siguiente. De lo contrario, se agrega al final de la lista y del archivo. De esta manera, cuando se vuelva a ejecutar ices0, si
hay canciones nuevas, se agregan al final del archivo y se mantiene su numeración. Para poder recorrer
un directorio es necesario importar el módulo os.
35
def ices_init ():
global playlist
global listaTop
global prediccion
global rutaonline
fplaylist = open("/home/felipe/ices/playlist.txt", "a+")
playlist = fplaylist.read()
playlist = playlist.split("\n")
for root,dirs,files in os.walk("/mnt/mp3/Radio"):
for file in [f for f in files if f.lower().endswith(".mp3")]:
if not(os.path.join(root, file) in playlist):
playlist.append(os.path.join(root, file))
fplaylist.write(os.path.join(root, file) + "\n")
fplaylist.close()
...
Nótese que también se declaran como globales listaTop, prediccion y rutaonline. Luego
se borra el contenido del archivo /var/www/.data/last20, que llevará el registro de las últimas 20
canciones reproducidas. Se definen las tres rutas necesarias:
rutaonline: Ruta del archivo que contiene lista de usuarios conectados escuchando la radio
(/var/www/.data/usersonline).
rutausers: Ruta del directorio que contiene los archivos de usuarios (/var/www/.usuarios/).
rutaPtoF: Ruta del archivo donde escribir predicciones (/var/www/.data/predicciones).
Luego, se llama por primera vez a la función PredictTop (4.4.7). Para ello es necesario importar el modulo preditop, por lo que se guarda el archivo predictop.py junto con ices.py en /home/felipe/ices/conf/. El resultado del llamado a la función se guarda por separado en prediccion y
listaTop respectivamente, y al declararlas globales se pueden usar en las demás funciones.
36
def ices_init ():
...
flast20 = open("/var/www/.data/last20", ’w’)
flast20.close()
rutaonline = "/var/www/.data/usersonline"
rutausers = "/var/www/.usuarios/"
rutaPtoF = "/var/www/.data/predicciones"
[prediccion, listaTop] = predictop.PredictTop(rutaonline,
rutausers, 3, rutaPtoF)
return 1
Con esto ya se obtiene una primera predicción y una lista de canciones populares, aunque es
probable que como se está iniciando el sistema, nadie esté escuchando la radio, por lo tanto no exista
la lista y sólo se almacene False.
Lo siguiente que ocurre es el llamado a la función ices_get_next() para reproducir la
primera canción. Esta contiene muchas variables globales, de la función anterior se obtiene playlist,
prediccion, listaTop y rutaonline. songnumber guarda el número o índice de la canción a reproducir, ruta guarda en una cadena de texto la ruta de la canción a reproducir, times lleva la cuenta
de cuántas canciones se han reproducido, y last20 es una lista de las últimas 20 canciones reproducidas.
def ices_get_next ():
global playlist
global prediccion
global listaTop
global rutaonline
global songnumber
global ruta
global times
global last20
...
Lo primero es revisar si hay usuarios conectados escuchando la radio y guardar el contenido
del archivo en la ruta rutaonline en users. Después se lee el contenido del archivo refresh en la
variable refresh, el cuál cambia cuando un usuario evalúa una canción, indicando que es necesario
actualizar las predicciones (ver 5.6.2 para más detalles).
37
def ices_get_next ():
...
fonlineusers = open(rutaonline, ’r’)
users = fonlineusers.read()
fonlineusers.close()
frefresh = open("/var/www/.data/refresh", ’r’)
refresh = frefresh.read()
frefresh.close()
...
Entonces, si refresh contiene algo, se deben actualizar las predicciones. Por comodidad se
vuelven a definir rutausers y rutaPtoF. Luego, se ejecuta la función PredictTop del módulo
predictop para actualizar prediccion y listaTop, y se borra el contenido del archivo refresh
para indicar que las predicciones están actualizadas.
def ices_get_next ():
...
if (len(refresh) != 0):
rutausers = "/var/www/.usuarios/"
rutaPtoF = "/var/www/.data/predicciones"
[prediccion, listaTop] = predictop.PredictTop(rutaonline,
rutausers, 3, rutaPtoF)
frefresh = open("/var/www/.data/refresh", ’w’)
frefresh.close()
...
Si refresh no tiene contenido, significa que nadie ha evaluado alguna canción (o cambiado
alguna evaluación), por lo que no es necesario actualizar las predicciones.
Entonces, se revisa el contenido de users (si hay usuarios escuchando la radio). De ser así, es
necesario adaptar el listado de canciones a reproducir para que se ajuste a los gustos de los oyentes. Como esto no necesita actualizar las predicciones, se puede llamar por separado a la función TopTracks
del módulo predictop, y cambiar solamente el contenido de listaTop. De no haber usuarios escuchando la radio, entonces listaTop valdrá False.
38
def ices_get_next ():
...
elif (len(users) != 0):
users = users.split("\n")
users.pop()
listaTop = predictop.TopTracks(prediccion, users, 3)
else:
listaTop = False
...
Después de revisar si hay usuarios escuchando la radio, se borra el contenido del archivo indicado en rutaonline. Luego, dependiendo si hay listaTop o no, nouserrand toma un valor de 4 si
hay usuarios conectados o de 1 si no los hay.
Luego se entra a un ciclo que cumple dos tareas. La primera, dependiendo del valor actual de
times y nouserrand, decide si el número o índice de la siguiente canción se elige aleatoriamente de
la listaTop o la playlist. Si nouserrand vale 1, significa que no hay usuarios escuchando y el módulo con times siempre será cero, por lo que songnumber saldrá de la playlist. Si nouserrand es
4 significa que hay alguien escuchando, entonces 3 de cada 4 veces songnumber saldrá de listaTop
y el resto de playlist, por lo tanto una de cada 4 canciones es completamente aleatoria. Esto sirve
para agregarle novedad a la lista de reproducción.
La segunda tarea es mantener un listado en last20 de las últimas 20 canciones reproducidas.
Esto se logra revisando el nuevo songnumber; si está dentro de la lista, se vuelve a ejecutar el ciclo.
De lo contrario, se agrega a la lista, se termina el ciclo, se busca la ruta de la canción a reproducir y se
retorna como cadena de texto.
39
def ices_get_next ():
...
if listaTop:
nousersrand = 4
else:
nousersrand = 1
while True:
if (times%nousersrand == 0):
songnumber = random.randrange(0, len(playlist))
else:
songnumber = random.randrange(0, len(listaTop))
songnumber = int(listaTop[songnumber])
times += 1
if (len(last20)>20):
last20.pop(0)
if songnumber in last20:
continue
else:
last20.append(songnumber)
break
ruta = playlist[songnumber]
return ruta
Como las evaluaciones siempre fueron guardadas con el índice respecto a playlist, no hay
problema de obtener la ruta desde ella.
La siguiente función en ser llamada es ices_get_metadata(), la que se encarga de enviar la
cadena de texto a usar como metadata de la canción en reproducción, o null para usar el comentario
del archivo.
Además se aprovecha para ir actualizando el archivo /var/www/.data/last20 con las 20 últimas
canciones reproducidas. Para esto se define como global ruta para obtener la ruta del archivo MP3,
y songnumber para agregar el índice al archivo de las últimas 20. Luego, se abre el archivo MP3
como binario con permiso de lectura y se leen los últimos 128 bytes de información, que contienen
la información según ID3 (v1) [10]. Si los primeros tres bytes corresponden a la sigla TAG, entonces
40
corresponde a la metadata del archivo. En un diccionario trackdata se guarda el título, el artista y el
album de la canción, y en infotrack se arma la cadena de texto que retorna la función.
def ices_get_metadata ():
global ruta
global songnumber
mp3 = open(ruta, "rb")
mp3.seek(-128,2)
metadata = mp3.read(128)
mp3.close()
infotrack = null
if(metadata[:3] == "TAG"):
trackdata = {"title": stripnulls(metadata[3:33]),
"artist": stripnulls(metadata[33:63]),
"album": stripnulls(metadata[63:93])}
infotrack = trackdata["artist"] + " - " + trackdata["title"]
En local20 se lee y guarda el contenido del archivo /var/www/.data/last20, como una lista
de cadenas de texto separadas por un salto de línea, y en caso de tener más de 20 canciones listadas,
se elimina la última. Luego se borra el contenido del archivo y, por cada elemento en local20, si el
índice de la canción no es songnumber (la canción actual), se escribe la misma cadena de texto. De
lo contrario se escribe el índice y la información de la canción según el formato:
índice:artista - título
Esto mantiene cada nuevo índice de la canción en reproducción junto a la información del
archivo. Finalmente se retorna la cadena de texto a servir como metadata, o null si no se pudo leer el
ID3 del archivo.
41
def ices_get_metadata ():
...
local20 = []
flast20 = open("/var/www/.data/last20", ’r’)
local20 = flast20.read()
flast20.close()
local20 = local20.split("\n")
local20.pop()
if (len(local20) == 20):
local20.pop(0)
flast20 = open("/var/www/.data/last20", "w")
for track in local20:
writen = track.split(’:’)
if (writen[0] != str(songnumber)):
flast20.write(track + "\n")
flast20.write(str(songnumber) + ’:’ + infotrack + "\n")
flast20.close()
return infotrack
Finalmente, se llama a la función ices_get_lineno() para escribir el índice de la canción
actual en el archivo /tmp/ices.cue. Entonces, basta con reconocer songnumber como variable global
y retornar su valor.
def ices_get_lineno ():
global songnumber
return songnumber
Esto concluye la implementación de la predicción y adaptabilidad de la lista de reproducción
usando el módulo escrito para ices0. Ambos archivos Python (predictop.py y ices.py) se guardan en
el directorio /home/felipe/ices/conf/, junto con el archivo XML de configuración icespy.xml.
Para ejecutar el programa, se debe ir por consola al directorio donde se encuentra el archivo
XML de configuración de ices0. Luego se llama a ices desde línea de comando, junto con la opción
-c para especificar el archivo de configuración, y -B si se desea una ejecución separada de consola.
En este caso ese directorio es home/felipe/ices/conf/.
42
cd /home/felipe/ices/conf/
ices -c icespy.xml -B
Una vez ejecutado, ices0 genera tres archivos en el directorio /tmp/
ices.cue: Contiene información sobre la canción en reproducción: nombre del archivo, tamaño
(en bytes), bitrate (en kbits/s), minutos:segundos (totales), porcentaje de reproducción, índice
en la playlist, ID3 artista, ID3 título.
ices.log: Registro de cada inicio, mensajes, canciones reproducidas y errores de ices0.
ices.pid: ID del proceso en el sistema operativo de ices0.
5.5.
PHP y Wimpy Button
5.5.1.
PHP básico
Para instalar PHP se puede usar el gestor de paquetes.
sudo apt-get install php5
Luego, se escribe una sencilla página web en /var/www/index.php, la que se muestra a
continuación.
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body>
<h1>RaDIE</h1>
<p>Proyecto en proceso, disfrute por mientras</p>
<!-- Insertar Web Player Aquí -->
</body>
Ahora sólo falta agregar el reproductor web embebido.
43
5.5.2.
Wimpy Button
Para agregar el reproductor embebido Wimpy Button, se descargan los archivos y se procede a
su configuración. Para reproducir el streaming, es necesario que la URL del stream termine en “.mp3”,
de lo contrario no reconocerá el tipo de audio. Por lo tanto, se configura dicha url en 5.4.2 para que
sea como se muestra a continuación.
http://hopper.die.udec.cl:8000/RaDIE.mp3
En los parámetros del reproductor se debe agregar lo siguiente:
bufferaudio=10: Tamaño del buffer [5-10].
icecast=300: Segundos que Wimpy debe reproducir antes de volver a conectarse al stream.
El segundo parámetro indica renovar la conexión. Esto es así ya que los reproductores flash
almacenan los datos del stream en memoria, y si no se establece una nueva conexión, el tamaño de esa
cola crece demasiado y empieza a fallar. Una nueva conexión cada cierto tiempo arregla ese problema.
Aún cuando los parámetros se pueden agregar manualmente, es recomendable hacerlo mediante el Wimpy Button Maker, disponible gratuitamente en la misma página de Wimpy Button, ya que
de otra manera no se garantiza un correcto funcionamiento.
Agregando el reproductor configurado para recibir el stream desde Icecast, la página queda
como se muestra en la figura 5. Esta radio online permite a quien ingrese escuchar las canciones
transmitidas.
Figura 5: Página sólo con reproductor embebido.
5.6.
Usuarios y evaluaciones
Como la radio ya cuenta con una página con reproductor embedido en funcionamiento, el
siguiente paso es poder crear cuentas de usuarios que se conecten a evaluar las canciones.
5.6.1.
Creando cuentas de usuarios
Para realizar esto se crea el directorio /var/www/.usuarios/, donde cada usuario tendrá un
archivo en el cual se guardará su información. En la primera línea se almacena la codificación en MD5
44
de la clave del usuario, en la segunda línea el correo electrónico y de la tercera en adelante se guarda
el par “número de canción” y “evaluación”. Las evaluaciones son enteros del 1 al 5.
Entonces, el usuario al visitar la página por primera vez, deberá crear una cuenta para evaluar
las canciones. La página de inicio se muestra en la figura 6a, y cuando ya ingresó como usuario válido
se muestra en la figura 6b.
(a)
(b)
Figura 6: Página de inicio (a). Sin cuenta de usuario. (b). Con cuenta de usuario.
Para crear una cuenta de usuario, la página lleva a newuser.html. En la figura 7a se muestra la
página sin datos, y en la figura 7b con datos ingresados en los espacios pertinentes.
(a)
(b)
Figura 7: Creando una nueva cuenta (a). Sin datos. (b). Con datos.
En caso de que falte algún dato en el formulario al crear una cuenta, se despliegan distintos
mensajes de error acordes. En la figura 8a faltó indicar el nombre de usuario, en la figura 8b faltó
ingresar la clave, y en la figura 8c faltó ingresar el correo electrónico.
En último caso que haya un error al ingresar los datos para crear una cuenta, también se deben
desplegar mensajes de error acordes. La figura 9a muestra el mensaje en caso de ya existir la cuenta a
crear, y la figura 9b el caso en que las contraseñas no coincidan.
45
(a)
(b)
(c)
Figura 8: Faltan datos para crear una cuenta (a). Sin nombre de usuario. (b). Sin clave. (c). Sin correo.
(a)
(b)
Figura 9: Errores creando una cuenta (a). Usuario ya existe. (b). Claves no coinciden.
Por otra parte, si el error ocurre al tratar de ingresar con la cuenta de usuario, también es
necesario desplegar mensajes de error acordes. La figura 10a muestra el error cuando se trata de
ingresar con una cuenta que no existe, y la figura 10b el error cuando la contraseña es incorrecta.
(a)
(b)
Figura 10: Errores al ingresar (a). Usuario no existe. (b). Clave incorrecta.
Con esto se pueden crear cuentas de usuarios exitosamente. Para reconocer al usuario cuando
entre a la radio se crea una COOKIE llamada uRaDIE, de tal manera que no esté obligado a escribir
todas las veces su nombre de usuario y contraseña, más bien su navegador lo recuerda por él. Lo
siguiente es habilitar el sistema para que puedan evaluar las canciones en reproducción.
46
5.6.2.
Sistema de evaluación
Una vez conectado el usuario a la radio online, debe poder evaluar la canción en reproducción.
Para esto se agrega un pequeño formulario con cinco botones de opción, donde cada uno tiene el valor
las posibles notas, del 1 al 5 ordenadamente. Además, se agrega un botón para enviar la evaluación,
como se muestra en la Figura 11. Este botón puede resolver el formulario hacia otra página PHP
o hacia alguna función en Javascript. En este caso la segunda opción es preferible, ya que permite
utilizar AJAX para enviar de manera asíncrona la información y así evitar la necesidad de refrescar la
página para evaluar otra canción.
Figura 11: Botones de opción y enviar.
Esto se incluye en la página principal, debajo del reproductor embebido. La función Javascript
donde resuelve el botón enviar es sendEval(ID, form, rbname) incluida en el archivo funciones.js,
y sus argumentos son tres.
ID: ID del bloque HTML que recibirá el resultado del llamado a la función.
form: ID del formulario que contiene los botones de opción.
rbname: Nombre del conjunto de botones de opción.
Esta función se encarga de procesar la información enviada en los botones de opción para
obtener la evaluación y luego enviarla a eval.php mediante el método GET de PHP. Una vez procesada
por dicha página, retorna la cadena de texto “Su evaluación fue: X”, donde X es la evaluación
ingresada, y esa cadena es enviada como resultado al bloque HTML indicado. El trabajo que realiza
eval.php es abrir el archivo del usuario y reescribirlo, pero actualizando o añadiendo la evaluación
ingresada. Además, escribe un 1 en /var/www/.data/refresh para avisar que es necesario actualizar
las predicciones.
De esta manera se puede evaluar la canción en reproducción. También es necesario mostrar si
ya fue evaluada antes. Para esto se agregan dos líneas bajo el formulario, una indicando la cuenta de
usuario que se está usando para evaluar canciones, y la segunda para saber el estado de la evaluación.
Este estado debe actualizarse cuando el usuario evalúe o la canción cambie. Para ello se escribe
una función usando jQuery que refresca el mensaje cada cinco segundos, el cual se obtiene llamando
a geteval.php. Esta página revisa si existe la evaluación de una canción determinada, y retorna una
cadena de texto acorde. Los posibles mensajes son los siguientes, siendo X la nota ingresada:
“No has evaluado esta canción.”: Cuando la canción no ha sido evaluada.
47
“Su evaluación fue: X”: Cuando recién se evalúa una canción.
“Evaluaste esta canción con un X”: Cuando la canción fue evaluada.
De la misma manera, se escriben dos funciones más usando jQuery: para refrescar el artista y
título, y el tiempo reproducido y total de la canción.
El primer resultado se ubica entre el formulario y el reproductor Wimpy, y se actualiza cada
cinco segundos llamando a getTrackInfo.php. El segundo resultado se ubica al lado derecho del reproductor, y se actualiza cada un segundo llamando a getTime.php. En ambos casos la información se
obtiene del archivo ices.cue descrito en la sección 5.4.3. Dicho archivo no contiene siempre la duración
total de la canción, por lo tanto, en esos casos no se pueden calcular los tiempos de reproducción.
A petición de algunos usuarios, se desarrolla una página (last20.php) donde se pueden ver las
últimas veinte canciones reproducidas. En ella se puede evaluar alguna canción recién reproducida
pero que no fue evaluada en su momento, y además sirve para aumentar la cantidad de evaluaciones,
ya que al reconocer alguna canción, se evalúa sin tener que esperar su reproducción. Esta página debe
ser refrescada por el usuario.
El resultado de todo lo agregado en esta sección se muestra en la figura 12a.
Además, para poder llevar un registro de cómo predice el sistema, cada vez que alguien evalúa
una canción predicha, ya sea en el momento que es reproducida o mientras pertenece a las últimas 20,
en el archivo /var/www/.data/PvsE se anota el nombre del usuario evaluador, la evaluación predicha
o anterior (dependiendo si se había evaluado antes o no), la nueva evaluación y las siglas PE si se
evaluó una canción predicha o EE si se cambió la evaluación ingresada. Las canciones que no han sido
evaluadas, y por lo tanto no pueden tener predicción, se ingresan como PE pero con nota 0.
5.6.3.
Detectando oyentes
Lo siguiente es desarrollar la detección de usuarios conectados escuchando la radio. Esto es
muy necesario, ya que no basta con que un usuario ingrese con su cuenta, esta puede quedar abierta
indefinidamente, lo que no significa que esté oyendo la radio.
Para esto se hace uso de AJAX y HTML DOM [11], manejando los eventos que se generan al abrir y cerrar una página web, onload y onunload respectivamente. Ambos pueden llamar a
funciones Javascript para ejecutar ciertas acciones cuando ocurran.
Entonces, el evento onload llama a la función openpage(), donde se envía mediante GET
el nombre de usuario ejecutor a listening.php, para actualizar el registro de los usuarios que están
escuchando la radio. De igual manera, el evento onunload llama a la función closepage(), donde
se envía mediante GET el nombre de usuario ejecutor a notlistening.php, para actualizar quién dejó
de escuchar la radio.
48
(a)
(b)
Figura 12: Páginas de inicio y últimas 20, (a). Una canción evaluada. (b). Últimas 4 de las 20.
Por desgracia, el evento onunload tiene un comportamiento irregular, asumible a que hay
muchas maneras de cerrar una página. Además el evento onload es insuficiente para detectar oyentes,
ya que pueden abrirla múltiples veces.
Esto obliga a añadir maneras adicionales para detectar usuarios conectados oyendo la radio.
En la sección 5.6.2, se desarrollan funciones que se conectan de manera asíncrona al servidor para
refrescar pequeñas partes de la página. Por lo tanto, se decide darle la tarea a una de ellas, getTrackInfo.php, de actualizar el listado, incluyendo el nombre del usuario que está solicitando refrescar el
nombre del artista y título de la canción. Por esto, se modifica para que obtenga esos datos mediante
una SESSION de PHP, los cuales serán obtenidos en getTime.php, la solicitud que se genera cada un
segundo. Y para saber cuando dejan de escuchar la radio, cada vez que se cambie la canción, se borra
el contenido de usersonline. De esta manera, si el usuario se mantiene escuchando la radio, vuelve a
ser escrito en el archivo.
5.7.
Resumen de directorios y archivos
Para una mayor claridad, a continuación se escribe un resumen de todos los directorios y
archivos que se crean y/o usan para el correcto funcionamiento de esta radio online.
5.7.1.
Directorios
/etc/icecast2/: Donde se guarda el archivo XML de configuración para Icecast.
49
/home/felipe/ices/: Donde se ubica el directorio conf/ y la playlist externa que maneja el módulo
ices.py.
/home/felipe/ices/conf/: Donde se guardan y manejan los archivos Python ices.py y predictop.py, junto con el archivo XML de configuración de ices0 icespy.xml.
/var/www/: Donde se guardan todos los archivos que se usan para armar la página web de la
radio online. Además contiene los directorios .usuarios/ y .data/.
/var/www/.usuarios/: Donde se crean y manejan todos los archivos de usuarios que contienen
sus evaluaciones. Cada archivo es un usuario.
/var/www/.data/: Donde se guardan los archivos de funcionamiento o registro de la radio online.
/mnt/mp3/Radio/: Donde se guardan todos los archivos MP3 a reproducir en la radio.
5.7.2.
Archivos
En /etc/icecast2/:
icecast.xml: Archivo XML de configuración para Icecast.
En /home/felipe/ices/:
playlist.txt: Archivo de texto que sirve de playlist. Lo genera y maneja ices0 cuando funciona
con Python.
En /home/felipe/ices/conf/:
predictop.py: Contiene el algoritmo de predicción y las funciones para generar la lista adaptativa.
ices.py: Contiene las funciones que llama ices0 cuando se configura para funcionar mediante
Python. Hace uso de predictop.py.
icespy.xml: Archivo de configuración de ices0.
En /var/www/:
index.html: Página principal de la radio online. Contiene el reproductor web con tiempo e
información de canción actual, link a FAQ.html, y si no se ha ingresado con cuenta, link a
newuser.html. Si se ingresó, muestra el formulario para evaluar, nombre de usuario, estado de
evaluación, predicción, link a last20.php y link a singout.php.
50
newuser.html: Página que contiene el formulario para crear un nuevo usuario. Para confirmar
datos, llama a nuevousuario.php .
FAQ.html: Página con preguntas frecuentes.
ingresar.php: Página para ingresar como usuario a la radio. Revisa datos de ingreso y crea la
COOKIE uRaDIE.
singout.php: Página para desconectarse como usuario, borra la COOKIE uRaDIE
eval.php: Ingresa la evaluación de la canción en reproducción, registra el cambio en PvsE y
cambia contenido de refresh.
evalfixed.php: Ingresa la evaluación de la canción indicada mediante GET. Similar a eval.php.
getEval.php: Llamada por la función jQuery para refrescar el estado de la evaluación. Si no a
sido evaluada, carga posible predicción.
getTime.php: Llamada por la función jQuery para refrescar los tiempos de duración y reproducción de la canción. Obtiene información del archivo /tmp/ices.cue, y guarda en SESSION la
información para getTrackInfo.php. Retorna ambos tiempos en una cadena de texto.
getTrackInfo.php: Llamada por la función jQuery para refrescar artista y título de la canción.
Además registra en usersonline el nombre de usuario conectado escuchando la radio.
last20.php: Página que carga las últimas 20 canciones reproducidas con sus respectivos formularios para evaluarlas, usando la función Javascript sendEvalFixed.
listening.php: Llamada por la función Javascript openpage para registrar el ingreso de un
usuario a la página.
notlistening.php: Llamada por la función Javascript closepage para registrar el cierre de un
usuario a la página.
nuevousuario.php: Revisa todos los datos ingresados para crear un nuevo usuario. Si no hay
problemas lo crea, de lo contrario avisa los errores.
funciones.js: Contiene las funciones Javascript getCookie, openpage, closepage, sendEval,
sendEvalFixed y las tres funciones jQuery que permiten refrescar automáticamente los tiempos de la canción cada 1 segundo, y la información y estado de evaluación cada 5.
jquery-1.2.6.min.js: Archivo Javascript que habilita el uso de jQuery.
jquery.timers-1.0.0.js: Plugin de jQuery para usar eventos temporales.
51
En /var/www/.data/:
last20: Registro de últimas 20 canciones reproducidas.
PvsE: Registro de cambio de evaluaciones o evaluar canciones predichas.
refresh: Registro que indica una nueva evaluación, o actualización de alguna ya realizada.
usersonline: Registro de los usuarios conectados escuchando la radio.
predicciones: Contiene todas las predicciones de todos los usuarios evaluadores.
En /tmp/:
ices.cue: Contiene información sobre la canción en reproducción: nombre del archivo, tamaño
(en bytes), bitrate (en kbits/s), minutos:segundos (totales), porcentaje de reproducción, índice
en la playlist, ID3 artista, ID3 título.
ices.pid: ID del proceso en el sistema operativo de ices0.
ices.log: Registro de cada inicio, mensajes, canciones reproducidas y errores de ices0.
52
Capítulo 6. Pruebas y resultados
6.1.
Introducción
En esta sección se realizan pruebas al algoritmo desarrollado en la sección 4 y se presentan los
resultados obtenidos de la radio online por parte de distintos usuarios.
6.2.
Pruebas al algoritmo
Lo primero es estudiar si a baja escala las predicciones son correctas. Para esto se preparan
pruebas con matrices de distintos tamaños.
6.2.1.
Matriz de 2x3
La primera prueba es con una matriz X de 2x3, 2 canciones vs. 3 usuarios, donde el primer
usuario no evaluó la canción 1, y evaluó la canción 2 con un 5. El segundo usuario evaluó ambas con
un 5, y el tercero ambas con un 1. Tanto el método de Flycasting [2] como el propuesto predicen el
mismo resultado. Esto ya que Flycasting sólo utilizará la información otorgada por el segundo usuario,
por ser similar al primero. Con el método propuesto el tercer usuario queda clasificado como disímil,
por lo tanto la predicción según la ecuación (6) también sería un 5, y ponderando ambas se obtiene el
mismo resultado.


 0 5 


X =  5 5 


1 1


 5 5 


X 0 =  5 5 


1 1
Esta prueba, por simple que parezca, muerta que la solución propuesta no entorpece las predicciones a un nivel básico.
6.2.2.
Matriz de 4x5
La siguiente prueba se realiza con una matriz Z de 4x5, 4 canciones y 5 usuarios.
El usuario 1 es similar al 2, disímil al 3 y difiere opuestamente en una evaluación con el usuario
4. El usuario 2 tiene dos evaluaciones idénticas con el usuario 4 pero una muy distinta. El usuario 3
es disímil a los usuarios 1 y 2, y a medias con el usuario 4. Además se introduce un usuario que no
evaluó nada, su predicción será la misma como si hubiera evaluado una canción que nadie más evaluó.
Las predicciones se realizan con un umbral de pesos de 0,7.
Z 0f ly es el resultado del algoritmo Flycasting, y Z 0 el del propuesto. Lo primero que se observa
es que Z 0f ly tiene menos predicciones, resultado esperado al descartar los usuarios disímiles, y por lo
53
mismo no puede reconocer que el usuario 3 hasta el momento es completamente contrario al usuario
1, y no puede obtener predicción para la canción 2.
Z 0 por su parte si lo reconoce y predice sin problemas para la canción 2, con un 5 para el
usuario 2 y un 1 para los usuarios 3 y 4. El usuario 4 no es tan contrario al 1, pero debido a la drástica
clasificación entre similares y disímiles, puede que la predicción no sea del todo indicada.





Z = 



1
1
5
1
0
5
0
0
0
0
1
1
5
5
0
0
2
0
2
0










Z 0f ly





= 



1
1
5
1
0
5
5
0
0
0
1
1
5
5
0
2
2
0
2
0















Z 0 = 



1
1
5
1
0
5
5
1
1
0
1
1
5
5
0
3
2
4
2
0












 1
1 0,36 0,68 0 


 1

1
0,36
0,79
0


Pesos =  0,36 0,36 1 0,68 0 


 0,68 0,79 0,68 1 0 


0
0
0
0 1
Otra diferencia interesante de la propuesta se aprecia en la canción 4, donde pese a que sólo dos
usuarios la han evaluado y con notas bajas, la predicción para el usuario 1 fue un 3 y para el usuario 3
fue un 4. En el caso del usuario 1 se debe a que ambos usuarios que evaluaron quedan clasificados en
grupos contrarios, por tanto uno predice un 2 y el otro un 4, ponderando resulta un 3. Para el usuario
3 ambos usuarios quedan clasificados como disímiles, por tanto se le predice un 4.
Y finalmente el usuario 5 no tiene ninguna evaluación en común, por lo tanto, no se puede
predecir sus evaluaciones. Debido a esto, es recomendable omitir a los usuarios que no han evaluado
canciones del algoritmo de predicción, ya que ahorra tiempo de cálculo.
El análisis del algoritmo con matrices de orden superior resulta muy complejo, por lo que es
preferible observar resultados concretos dentro del sistema completo.
6.3.
Tiempos del algoritmo
Cuando se cambia de una canción a otra, se puede realizar la predicción y adaptar la lista en
ese momento, siempre cuando el tiempo de cálculo que requiere sea muy bajo, de ordenes menores a
un segundo.
Hay dos factores que afectan el rendimiento del algoritmo: Cantidad de usuarios y cantidad de
evaluaciones.
54
Figura 13: Tiempos del algoritmo.
Cantidad de usuarios: Mientras más usuarios, mayor es la cantidad de predicciones que se
deben realizar.
Cantidad de evaluaciones: Mientras más evaluaciones, mayor la cantidad de evaluaciones que
se debe predecir para cada usuario.
De la sección 4.4 se observa que el algoritmo predictivo es de orden O(n2 ), lo que significa
que a mayor número de usuarios y evaluaciones, el tiempo de cálculo aumenta considerablemente. En
la Figura 13 se puede apreciar el rendimiento promedio del algoritmo. Mientras más usuarios y más
evaluaciones tengan, más demora en predecir y lograr adaptar la lista de canciones con nota sobre 3,
suponiendo que están conectados escuchando al mismo tiempo. Con 300 usuarios y 200 evaluaciones
cada uno, demora al rededor de un minuto y medio.
Esto muestra que a un alto uso de la radio online es necesario optimizar el rendimiento del
algoritmo, como también la manera de usarlo. Para ambos casos, una buena opción es paralelizar los
procesos, ya que el cálculo de pesos y predicciones es altamente paralelizable, y mientras suena una
canción se alcanzaría a calcular las predicciones y lista para la siguiente reproducción. Otra opción
para mejorar el rendimiento es trabajar con lenguajes de nivel más bajo, como C o C++. Esto permitiría
optimizar el uso de memoria y tiempos de ejecución.
55
Figura 14: Cuántas evaluaciones tiene cada canción.
6.4.
La radio online
La radio fue implementada con 16128 canciones, una alta cantidad y variedad de estilos musicales. En cuanto a personas, se llegó a 39 usuarios con alguna evaluación, de 51 usuarios en total. Esto
quiere decir que 12 crearon cuentas y nunca evaluaron alguna canción. Ellos no afectan el rendimiento
del sistema, ya que a la hora de predecir son ignorados.
En la tabla 3 se muestran los usuarios clasificados en cuatro grupos, según cuántas evaluaciones
realizaron. Se puede ver que casi la mitad sólo evaluaron una canción, un tercio menos de 10, un 15.4 %
menos de 100 y tan solo 3 usuarios sobre 100 evaluaciones. Por lo tanto, se puede apreciar de la figura
13, que el tiempo necesario para predecir y generar la lista es muy menor a un segundo.
Tabla 3: Cantidad de usuarios y evaluaciones
No Evaluaciones No Usuarios
1
17
< 10
13
< 100
6
≥ 100
3
Total
39
%
43.6
33.3
15.4
7.7
100
En la figura 14 y la tabla 4 se muestra la cantidad de evaluaciones que tiene cada canción. Al
tener una gran variedad de archivos, no sorprende el elevado porcentaje de evaluaciones que no han
sido evaluadas, sólo cerca del 4 % tiene evaluaciones. Además, de ese grupo, más de la mitad solo
56
Tabla 4: Cantidad de evaluaciones por canción
No Evaluaciones No Canciones %
0
15512
96.18
1
363
2.25
2
183
1.13
3
50
0.31
4
17
0.11
5
2
0.01
6
1
0.01
Total
16128
100
Figura 15: Evaluaciones de 6 usuarios.
posee una evaluación, lo que quiere decir que la predicción se realiza con muy poca información, y
clasifica a los usuarios entre similares y disímiles basándose en unas pocas canciones evaluadas en
común.
Para estudiar el efecto de lo anterior, se requiere observar las evaluaciones de algunos usuarios,
y qué tal predijo el sistema para ellos. En la figura 15 y la tabla 5 se muestran las evaluaciones de seis
usuarios distintos. El nombre de cada usuario fue modificado para mantener la privacidad de sus
evaluaciones.
El usuario 1 evaluó pocas canciones, apenas 22. El usuario 2 evaluó 100 canciones y la mayoría
negativas. El usuario 3 evaluó 81 canciones y la mayoría positivas. El usuario 4 evaluó 376 canciones,
es el con más evaluaciones y su tendencia es media. El usuario 5 evaluó 58 canciones con tendencia
57
Tabla 5: Evaluaciones de los 6 usuarios
Usuario No Evaluaciones
1
22
2
100
3
81
4
376
5
58
6
108
1
2
3
1
7
6
45 15 17
2
3
9
32 63 168
25 8 15
18 22 17
4
4
14
22
88
6
29
5
4
9
45
25
4
22
Tabla 6: Cantidad de canciones evaluadas en común
Usuario 1
1
X
2
5
3
3
4
11
5
0
6
4
2 3 4
5 3 11
X 10 36
10 X 48
36 48 X
15 5 24
10 10 64
5 6
0 4
15 10
5 10
24 64
X 6
6 X
negativa. El usuario 6 evaluó 108 canciones y sus notas son muy variadas. Como se puede apreciar,
aún con la baja participación dentro del periodo de prueba del sistema, se logra armar un grupo de
usuarios con comportamiento variado.
La cantidad de canciones evaluadas en común entre ellos se muestra en la tabla 6. Como el
usuario 4 ingresó más evaluaciones, es esperable que sea el que tiene más en común con los demás,
y además sea el que más influencia las predicciones para el resto. Si se omite ese usuario, la mayor
coincidencia es entre el 2 y el 5 con 15 canciones en común.
Un dato importante es que los usuarios 4 y 6 son los más antiguos dentro del sistema. De hecho,
comenzaron a evaluar canciones cuando la radio no predecía aún, y la mayoría de sus evaluaciones
fueron ingresadas en ese periodo, definiendo la base inicial de temas a predecir y reproducir para los
demás usuarios.
La comparación entre predicciones y evaluaciones se muestra en la figura 16. Por ser los más
antiguos, la evolución de los usuarios 4 y 6 está incompleta, ya que no se llevaba registro de predicciones inexistentes al inicio del funcionamiento de la radio online. De todas formas, se puede apreciar
cierta similitud o tendencia entre la predicción y su evaluación para ellos, lo que no es sorpresa si son
los generadores de la base inicial de temas. Los demás usuarios sí tienen su historial de evaluaciones
completo.
Algunas predicciones son bajo 3, el umbral fijado para generar la lista, y por tanto no deberían
sonar mientras se conecten esos usuarios. Si están presentes se debe a dos posibilidades:
58
Figura 16: Predicciones vs. evaluaciones.
La canción con predicción bajo el umbral de reproducción estaba sonando mientras ingresó el
usuario a la radio.
El usuario evaluó la canción estando dentro de la lista de las últimas 20.
Lo importante es que la mayoría de las canciones predichas que evaluaron los usuarios tenían
una predicción igual o superior a 3.
Para el usuario 1, 14 de las 22 evaluaciones realizadas fueron hechas contra predicciones.
Inicialmente hay grandes diferencias entre ambas, y a medida que más evalúa pareciera que empiezan
a tener la misma tendencia.
El usuario 2 tiene casi un 50 % de sus evaluaciones realizadas contra predicciones. De la figura
15 se recuerda que tiene una tendencia muy negativa, comportamiento reflejado nuevamente. Al inicio
se da el caso grave de predecir un 5 y recibir un 1, pero errores de este tipo son esperados en nuevos
usuarios. Además, este usuario reveló que la mayoría de sus evaluaciones fueron realizadas directamente en el listado de las últimas 20 canciones, y que no se quedaba mucho tiempo escuchando la
radio. Por lo tanto, mucha de la información es ingresa al mismo tiempo, lo que no le permite al sistema adaptarse al usuario paulatinamente, sino que trata de predecir abruptamente un nuevo resultado.
Similar es el usuario 5, donde las predicciones no logran ser cercanas a sus evaluaciones. Estos
casos son ejemplos de que el algoritmo es muy drástico en clasificar usuarios o similares o disímiles.
El caso del usuario 3 es todo lo contrario. Un poco más del 50 % de sus evaluaciones fueron
hechas contra predicciones, y no existen canciones predichas que hayan sido evaluadas con un 1, apenas dos canciones con predicción 3 fueron corregidas a un 2. Este usuario se conectaba muy seguido y
59
por largos tiempos a la radio, lo que permite ir ajustando los resultados de manera regular. Se debe tener en cuenta que la tendencia de este usuario es muy positiva, por tanto, los buenos resultados pueden
no deberse necesariamente al algoritmo.
Valiosa es la información otorgada por los usuarios más antiguos, 4 y 6. Opinan que cuando no
existía predicción alguna, se reproducían muchas canciones que no eran de sus gustos, pero desde que
iniciaron las predicciones, la lista mejoró notablemente. En otras palabras, la radio comenzó a adaptar
el listado de acuerdo a la información entregada por ellos hasta entonces.
Si bien la radio adapta el listado a los gustos ingresados por los usuarios, con la información
recopilada se aprecia que el algoritmo no es rápido, necesita de una alta cantidad de evaluaciones y
puede que sea insuficiente.
60
Capítulo 7. Conclusiones
7.1.
Resumen
Se logró implementar una radio online. Se puede acceder a ella desde cualquier locación y sin
necesidad de instalar programas adicionales.
Se desarrolló un sistema de cuentas de usuarios. Cualquier persona puede crear una cuenta,
eligiendo nombre y contraseña. También se detecta a los usuarios conectados escuchando la
radio.
Se desarrolló la propuesta del algoritmo predictivo de manera exitosa.
Se implementó un sistema de evaluación a las canciones por parte de los usuarios. Se pueden
evaluar la canción en reproducción y las últimas 20 de la lista. Se puede ver y cambiar la nota.
Se implementó el algoritmo en la radio online. Se puede conocer la nota predicha para las
canciones del listado.
Se logró generar una lista adaptativa. Cambia de acuerdo a los gustos de los oyentes.
7.2.
Conclusiones
Se puede implementar una biblioteca de música, en formato radio online, que transmita canciones vía streaming, seleccionadas de manera colaborativa por los gustos de los usuarios conectados
escuchando en el momento.
Para generar dicho streaming, se trabajó con los programas Icecast y Ices, ambos creados y
mantenidos por la misma organización. Su configuración es simple, no requiere conocimientos avanzados en transmisión de datos para usarlos, son gratuitos, cuentan con buen soporte y son muy estables.
Fue posible manipular el listado a reproducir, escribiendo módulos en lenguaje Python.
En tanto, la reproducción del streaming en la página web se realizó con la versión demo de
la aplicación flash Wimpy Button. De esta manera, las personas que ingresan no necesitan instalar
ningún tipo de reproductor de audio, basta con que ya tengan la extensión de flash instalada en su
navegador. Wimpy Button es sencillo y se puede configurar para trabajar con un stream proveniente
de Icecast, renovando la conexión cada cierto tiempo para evitar mal uso de memoria.
A falta de un reproductor web que extrajera la información de las canciones de la metadata
directamente del stream, para mostrar el nombre de la canción, el artista, y los tiempos de duración y
reproducción, se utilizó AJAX, creando solicitudes para buscar esos datos en el servidor y los refrescaran de manera asíncrona en la página.
61
La detección de oyentes en la radio, fue realizada mayormente gracias a dichas solicitudes
AJAX, las cuales mantienen un registro de todos los que las emiten. También se detectan los eventos
de abrir y cerrar la página, para ingresar o borrar al usuario de dicho registro.
Usando filtrado colaborativo, fue posible clasificar a los usuarios en los dos grupos propuestos:
similares y disímiles. Para esto se compararon las canciones evaluadas que tienen todos los usuarios en
común, reflejando su grado de similitud en pesos calculados usando MDS y el algoritmo de correlación
de Pearson, basado en el método desarrollado en Flycasting [2].
Sobre el algoritmo propuesto, se logra el objetivo planteado, predecir evaluaciones según los
gustos de otros usuarios, aunque la información presentada muestra que, a corto plazo, es insuficiente
como método único, es necesario complementarlo o mejorarlo de alguna manera. Se necesita más
tiempo para estudiar el comportamiento a largo plazo.
Para elegir la siguiente canción, primero se obtiene el listado de oyentes en línea. Luego, se
buscan todas las canciones evaluadas y predichas de cada usuario, y se crea otra lista con las que
tengan como mínimo una nota promedio media (3). Finalmente, se elije al azar cualquier canción de
ese conjunto, teniendo en claro cuáles han sido las últimas 20, para evitar repetirlas en el corto plazo.
Para agregar cierta novedad, cada 3 canciones se elige una canción aleatoria de todas las disponibles.
Ciertamente, con un orden cuadrático O(n2 ), los tiempos de cálculo serán altos frente a más de
50 usuarios con 100 evaluaciones cada uno, por lo que se requiere buscar mejoras de rendimiento, como cambiar el lenguaje de programación a uno que permita optimizar recursos (C, C++), métodos de
reducción de orden para matrices, explotar el potencial paralelizable, reducción de orden al algoritmo,
o alguna otra opción.
En cuanto a cuán conformes quedan los usuarios, el 50 % de los que más escuchaba la radio
afirma que el sistema sí funciona, escuchan muchos temas de su gusto y hasta conocieron algunos
grupos nuevos. Sin embargo, el otro 50 % difiere de las predicciones y opinan que el universo de
temas es muy poco variado, y por eso no se quedan conectados mucho tiempo. Esto se debe a que si
los mismos usuarios se conectan siempre, pueden sin querer apropiarse de la lista de reproducción, y
cuando entre un nuevo usuario le cueste mucho ver reflejado sus gustos en ella.
En resumen, se considera que se cumplieron los objetivos planteados, aún cuando la predicción
no es del todo efectiva, pero se identifican varias mejoras a estudiar.
7.3.
Trabajo futuro
Una primera alternativa es mejorar la gestión de la página. Las cuentas se manejan creando un
archivo por cada una, y escribiendo en él toda su información. Las canciones se escriben en un archivo
de manera ordenada. Todo esto se puede implementar usando bases de datos SQL como MySQL,
PostgreSQL, entre otros, lo que permitiría gestionar de manera eficiente tanto las cuentas de usuarios,
canciones y evaluaciones.
62
También se puede mejorar la seguridad del sistema, ya que, por ejemplo, las evaluaciones son
ingresadas mediante peticiones tipo GET a las páginas PHP, por tanto, cualquier usuario podría usar
dicha debilidad para alterar la información. No existe un método automático para confirmar que el
índice de la canción evaluada, sea efectivamente válido. Además, se puede desarrollar algún sistema
para informar sobre problemas en alguna canción (incompleta, con ruidos, muy larga, entre otras).
Se puede estudiar modificaciones al algoritmo predictivo, creando más clasificaciones a las
actuales similar y disímil, o acotando su rango. De esta manera sería menos drástica la categorización
y más eficiente la predicción, ya que si bien no es errada la hipótesis “a una persona que le gusta
cierto estilo es probable que no le guste otro”, no se puede asumir cierta estrictamente y eso sucede al
tener sólo las dos categorías.
Buscar alternativas para optimizar el orden del algoritmo mejoraría notablemente su escalabilidad. Además, al desarrollarlo en Python, un lenguaje de alto nivel, no se escatima en recursos ni
se optimiza el tiempo de ejecución. Por tanto, reescribirlo en un lenguaje de bajo nivel, como C o
C++, podría permitir mejoras notables. Paralelizar el algoritmo y la selección de la siguiente canción
es otra posible mejora a estudiar, el cálculo de las matrices W (sec. 4.3.1), WT y W 0 (sec. 4.3.2) se
puede realizar en varios procesos paralelos, y mientras una canción se reproduce, se puede actualizar
la predicción y la lista de reproducción.
También se puede combinar el filtrado colaborativo con otro tipo, por ejemplo, el por contenido. De esta manera, se podría identificar el artista de las canciones, la trayectoria del artista, época
de la canción, idioma, nacionalidad, estilo, entre otras. Esto permitiría reconocer otro tipo de similitudes entre los usuarios, no tan sólo las evaluaciones en común de canciones en particular. Por ejemplo, si a alguien le gusta la música andina, podrían ser considerados similares a otros que les guste
la música en español, de artistas latinoamericanos, de folclore cercano, música tradicional, tipos de
instrumentos, o alguna otra característica.
Otras opciones a mejorar son cómo agregar novedad a la lista, cómo enfrentar el que usuarios
se apoderen del estilo de la radio, cómo evitar el inicio frío, métodos de reducción de orden para las
matrices, implementar un sistema de evaluación a gran escala, y cualquier mejora posible que cause
algún efecto en el rendimiento de la radio es digno de ser estudiado.
63
Bibliografía
[1] Joseph F. McCarthy, Theodore D. Anagnost. “MUSICFX: An Arbiter of Group Preferences for
Computer Supported Collaborative Workouts”, Proceedings of the 1998 ACM Conference on
Computer Supported Cooperative Work (CSCW ’98).
[2] David B. Hauver, James C. French. “Flycasting: Using Collaborative Filtering to Generate a
Playlist for Online Radio”, Proc. Int. Conf. Web Delivering of Music (2001).
[3] Josephine Griffith and Colm O’ Riordan. “Collaborative Filtering”, IT Centre, NUI, Galway
(2000).
[4] Dan Cosley, Joseph A. Konstan, John Riedl. “PolyLens: A recommender system for groups
of users”, Proceedings of the European Conference on Computer-Supported Cooperative Work
(2001).
[5] Òscar Celma. “Music Recomendation And Discovery”, Springer-Verlag Berlin Heidelberg Dordrecht London New York (2010).
[6] Judith Masthoff. “Selecting a sequence of television items to suit a group of viewers. User Modeling and User-Adapted Interaction”, University Of Brighton, UK (2004).
[7] Loren Terveen and Will Hill. “Beyond Recommender Systems: Helping People Help Each Other”,
In HCI In The New Millennium, Jack Carroll, ed., Addison-Wesley (2001).
[8] Página oficial de Icecast, www.icecast.org.
[9] Página oficial de Wimpy, www.wimpyplayer.com.
[10] Página oficial de ID3, www.id3.org.
[11] Escuela w3, www.w3schools.com.
[12] Documentación de Python, www.python.org/doc/.
[13] Proyecto LAME, http://lame.sourceforge.net/.
64
Anexo
Códigos de Python
Listing 1: predictop.py
1
2
import copy
import os
3
4
5
6
7
8
9
10
# Funcion recursiva Crea Matriz
def create_matrix (shape , value = 0):
""" Funcion crea matriz (Tamano , VI):
shape : tupla con dimensiones de matriz ,
value : valor inicial , parametrizado en 0"""
if not shape :
return value
11
12
return [ create_matrix ( shape [1:] , value ) for _ in xrange ( shape [0])]
13
14
15
16
17
18
19
20
21
# Funcion calcula MDS
def MDS(x1 , x2 , norm = 5.0):
""" Calcula el MDS ( Distancia Media Cuadratica ) entre 2 diccionarios (x1 , x2 , norm)
x1: diccionario 1
x2: diccionario 2
norm: para normalizar , depende del valor maximo en diccionarios , parametrizado en 5"""
enambos = 0.0
sdif = 0.0
22
23
24
25
26
27
28
# Cancion por diccionario ( usuario )
for track in x1:
# Si ambos evaluaron el tema
if x1[ track ] != 0 and x2[ track ] != 0:
sdif += pow(x1[ track ]/ float (norm) - x2[ track ]/ float (norm), 2)
enambos += 1.0
29
30
31
32
33
34
35
# Si no hubo coincidencias
if enambos == 0:
return 1.0
# Si las hubo
else:
return sdif/ enambos
36
37
38
39
40
# Funcion Calcula Pesos
def CalculaPesos (X):
""" Obtiene Matriz ( lista de listas ) con los pesos de usuarios parecidos
X: lista de diccionarios con tuplas de evaluaciones ( track : eval)"""
41
42
print " CalculaPesos () ..."
43
44
45
# Matriz cuadrada para calcular pesos (N usuarios X N usuarios )
PesosPrediccion = create_matrix (( len(X), len(X)), 1)
46
47
48
# Calcular Matriz Mds parecido usuarios
for i, inlista in enumerate (X):
65
for j, dicts in enumerate (X):
49
50
# Mismo usuario
if i == j:
continue
# Si ya se calculo la pareja de usuarios
elif PesosPrediccion [j][i] != 1:
PesosPrediccion [i][j] = PesosPrediccion [j][i]
# Si no se ha calculado esta pareja
else:
PesosPrediccion [i][j] = 1 - MDS(inlista , dicts , 5.0)
51
52
53
54
55
56
57
58
59
60
61
return PesosPrediccion
62
63
64
65
66
67
68
# Funcion para predecir
def Predict (X, P, U = 0.5):
""" Predice evaluaciones faltantes en base a Pesos calculados 1-MDS , (X, P, U)
X: Lista de diccionarios c/ evals de usuarios en tuplas
P: Matriz de Pesos 1-MDS
U: Umbral [0 -1[ , parametrizado 0.5 """
69
70
print " Predict () ..."
71
72
73
74
75
76
77
POSnum
POSden
NEGnum
NEGden
nPOS =
nNEG =
=
=
=
=
0
0
0.0
0.0
0.0
0.0
78
79
80
# Copia para ir actualizando final sin modificar original
Xfinal = copy. deepcopy (X)
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# i indice de usuario en lista ( diccionario )
for i, Xdict in enumerate (X):
# tupla en diccionario (del track evaluado )
for inXdict in Xdict :
# Si evaluacion presente , no predice
if Xdict [ inXdict ] != 0:
continue
else:
# j indice de peso de cada usuario con Xdict ( usuario )
for j, ipesos in enumerate (P[i]):
# Si el peso es con si mismo
if i == j:
continue
# Si no hay peso
elif ipesos < 0.2:
continue
# Si es mayor a umbral (POS)
elif ipesos >= U and X[j][ inXdict ] != 0:
POSnum += ipesos *X[j][ inXdict ]
POSden += ipesos
nPOS += 1
# Si es menor a umbral (NEG)
elif ipesos < U and X[j][ inXdict ] != 0:
66
NEGnum += ipesos *X[j][ inXdict ]
NEGden += ipesos
nNEG += 1
# Otro caso , sigue (X[j][ inXdict ] == 0)
else:
continue
105
106
107
108
109
110
111
# POS: Si no hubo mayores a umbral , POSden puede ser cero
if POSden < 0.2: # peso menor 0.36
POSdiv = POSnum
else:
POSdiv = POSnum / POSden
112
113
114
115
116
117
# NEG: Si no hubo menores a umbral , NEGden puede ser cero
if NEGden < 0.2: # peso menor 0.36
NEGdiv = NEGnum
else:
NEGdiv = NEGnum / NEGden
118
119
120
121
122
123
# Cuando pesos son cero , no hay nPOS nNEG (no debiera ocurrir )
if nPOS == 0 and nNEG == 0:
predicha = 0
# Normalmente hay al menos un peso
else:
predicha = ( POSdiv *nPOS + (6 - NEGdiv )*nNEG)/( nPOS + nNEG)
124
125
126
127
128
129
130
# Guarda prediccion
Xfinal [i][ inXdict ] = predicha
POSnum = 0.0
POSden = 0.0
NEGnum = 0.0
NEGden = 0.0
nPOS = 0
nNEG = 0
131
132
133
134
135
136
137
138
139
140
return Xfinal
141
142
143
144
145
## Funcion principal
def Getpredicciones ( rutausers ):
""" Retorna diccionario de username con diccionarios " Ntrack evaluacion "
rutausers : ruta al directorio de archivos de usuarios """
146
147
print " Getpredicciones () ..."
148
149
150
# Crear listado con todos los usuarios
allusers = []
151
152
153
154
155
# Crea lista con todos los usuarios
for root ,dirs , files in os.walk( rutausers ):
for file in [f for f in files ]:
allusers . append (file)
156
157
158
# Lista de usuarios con alguna evaluacion
listaUsersEval = []
159
160
# Lista de diccionarios con evaluaciones en tuplas
67
161
listadict = []
162
163
164
165
166
167
168
# Por cada usuario
for user in allusers :
# Abre archivo de usuario
fu = open( rutausers +user , ’r’)
evals = fu.read ()
fu. close ()
169
170
171
172
173
174
# Transforma lo leido a lista de string , sin pass ni mail ni nulo final
evals = evals . split ("\n")
evals .pop () # Borra ultimo elemento nulo
evals .pop (0) # Borra Password
evals .pop (0) # Borra Correo
175
176
177
178
179
if (len( evals ) == 0):
continue
else:
listaUsersEval . append (user)
180
181
182
183
# Transforma cada string en tupla
for index , pareval in enumerate ( evals ):
evals [ index ] = tuple (int(i) for i in pareval . split (" "))
184
185
186
# Transforma lista de tuplas a diccionario
dictevals = dict( evals )
187
188
189
190
# Revisar diccionarios listados por tracks que falten
# Por cada diccionario en lista
for inlista in listadict :
191
192
193
194
195
196
197
# Por cada tupla en diccionario dictevals
for indict in dictevals :
# Si no esta track
if not indict in inlista :
# Agregar a diccionario inlista , eval = 0
inlista . setdefault (indict ,0)
198
199
200
201
202
203
204
# Por cada tupla en diccionario inlista
for ininlista in inlista :
# Si no esta track
if not ininlista in dictevals :
# Agregar a diccionario dictevals , eval = 0
dictevals . setdefault (ininlista ,0)
205
206
207
# Agrega diccionario al final de la lista
listadict . append ( dictevals )
208
209
PesosPrediccion = CalculaPesos ( listadict )
210
211
212
# Rellena no evaluadas con prediccion
listadict = Predict (listadict , PesosPrediccion , 0.7)
213
214
215
216
# diccionario de username ( listaUsersEval ) con diccionario track :eval ( listadict )
dictUsersEvals = {}
68
217
218
219
# Crea diccionario con username y dict predicciones
for i,dicc in enumerate ( listadict ):
dictUsersEvals [ listaUsersEval [i]] = dicc
220
221
return dictUsersEvals
222
223
224
225
226
227
228
def TopTracks (Pred , Users , Tol = 3):
""" Genera un listado de las canciones mejor evaluadas
Pred: diccionario de usuarios con diccionario de track :eval
Users : Lista de usuarios conectados
Tol: Nota minima que debe tener en todos los usuarios ( defecto = 3) """
229
230
print " TopTracks () ..."
231
232
233
234
# No hay Usuarios conectados
if (len( Users ) == 0):
return False
235
236
237
238
239
240
241
242
243
244
# Crea Lista con todos los tracks evaluados
listtracks = []
# Por cada usuario
for user in Pred:
# Por cada track en diccionario de user
for track in Pred[user ]:
# Si track no esta en lista de tracks , agregar
if track not in listtracks :
listtracks . append ( track )
245
246
247
248
249
250
251
# Variable para media
media = 0
# Variable para usuarios con eval
uTotal = 0
# Crea Lista a guardar Top tracks
listTop = []
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
# Por cada track
for track in listtracks :
# Por cada usuario en Users
for uname in Users :
# Si uname no tiene predicciones , no cuenta
if uname not in Pred:
continue
# Carga diccionario de usuario
userdict = Pred[ uname ]
# Suma evaluacion de track a media
media += userdict [ track ]
# Cuenta de usuarios
uTotal += 1
# Divide por cantidad de usuarios contados
media /= uTotal
if ( media >= Tol):
listTop . append ( track )
media = 0
uTotal = 0
69
273
return listTop
274
275
276
277
278
279
280
281
282
283
def PredictTop ( rutaonline , rutausers , tol =3, rutaPtoF = ""):
""" Funcion Final que hace uso de todas las anteriores ,
guarda listado Toptrack en archivo final
retorna False si no hay usuarios , o True si los hay
rutaonline : ruta del archivo que contiene listado de usuarios conectados
rutausers : ruta al directorio de archivo de usuarios
rutaTop : ruta del archivo a escribir lista Top tracks
tol: nota minima usada de tolerancia ( defecto = 3)
rutaPtoF : ruta del archivo a escribir predicciones """
284
285
print " PredictToptoFile () ..."
286
287
288
# Obtiene arreglo de diccionarios con predicciones
prediccion = Getpredicciones ( rutausers )
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
# Guarda predicciones en archivo , si se desea
if( rutaPtoF != ""):
fPtoF = open(rutaPtoF , ’w’)
# Por cada usuario en prediccion
for user in prediccion :
# Escribir usuario e inicio de track :eval
fPtoF . write (user+":")
# Por cada track por usuario
for track in prediccion [user ]:
# Escribir pareja track :eval
fPtoF . write (str( track )+’=’+str( prediccion [user ][ track ])+’;’)
# Escribir termino de track :eval
fPtoF . write ("\n")
fPtoF . close ()
304
305
306
307
308
309
310
# Carga usuarios conectados
fonlineusers = open( rutaonline , ’r’)
users = fonlineusers .read ()
fonlineusers . close ()
users = users . split ("\n")
users .pop () # Borra ultimo elemento nulo
311
312
return [ prediccion , TopTracks ( prediccion , users , tol)]
70
Listing 2: ices.py
1
2
3
4
5
6
from string import *
import sys
import random
import os
import codecs
import predictop
7
8
9
10
def stripnulls (data):
" strip whitespace and nulls "
return data. replace ("\00", ""). strip ()
11
12
# This is just a skeleton , something for you to start with.
13
14
15
16
17
18
19
20
21
songnumber = -1
playlist = ""
ruta = ""
times = 0
listaTop = []
last20 = []
nousersrand = 1
infotrack = ""
22
23
24
25
26
27
28
29
30
# Function called to initialize your python environment .
# Should return 1 if ok , and 0 if something went wrong .
def ices_init ():
print ’Executing initialize () function .. ’
global playlist
global listaTop
global prediccion
global rutaonline
31
32
33
34
35
# Abrir playlist
fplaylist = open("/home/ felipe /ices/ playlist .txt", "a+")
playlist = fplaylist .read ()
playlist = playlist . split ("\n")
36
37
38
39
40
41
42
# Armar lista escaneando ruta
for root ,dirs , files in os.walk("/mnt/mp3/ Radio "):
for file in [f for f in files if f. lower (). endswith (".mp3")]:
if not(os.path.join(root , file) in playlist ):
playlist . append (os.path.join(root , file))
fplaylist . write (os.path.join(root , file) + "\n")
43
44
fplaylist . close ()
45
46
47
48
# Borra contenido de archivo last20
flast20 = open("/var/www /. data/ last20 ", ’w’)
flast20 . close ()
49
50
51
52
53
54
55
# Ruta archivo de usuarios conectados
rutaonline = "/var/www /. data/ usersonline "
# Ruta carpeta cuentas de usuario
rutausers = "/var/www /. usuarios /"
# Ruta archivo de predicciones
rutaPtoF = "/var/www /. data/ predicciones "
71
56
57
58
# Obtiene prediccion
[ prediccion , listaTop ] = predictop . PredictTop ( rutaonline , rutausers , 3, rutaPtoF )
59
60
return 1
61
62
63
64
65
66
# Function called to shutdown your python enviroment .
# Return 1 if ok , 0 if something went wrong .
def ices_shutdown ():
print ’Executing shutdown () function ... ’
return 1
67
68
69
70
71
72
73
74
75
76
77
78
79
# Function called to get the next filename to stream .
# Should return a string .
def ices_get_next ():
print ’Executing get_next () function ... ’
global songnumber
global playlist
global ruta
global times
global listaTop
global last20
global prediccion
global rutaonline
80
81
82
83
84
# Chequea si hay usuarios online
fonlineusers = open( rutaonline , ’r’)
users = fonlineusers .read ()
fonlineusers . close ()
85
86
87
88
89
90
# Abre archivo para revisar si alguien a evaluado
frefresh = open("/var/www /. data/ refresh ", ’r’)
refresh = frefresh .read ()
frefresh . close ()
print " refresh : " + refresh
91
92
93
94
# Si refresh contiene algo
if (len( refresh ) != 0):
print " Refrescando Predicciones ..."
95
96
97
98
99
# Ruta carpeta cuentas de usuario
rutausers = "/var/www /. usuarios /"
# Ruta archivo de predicciones
rutaPtoF = "/var/www /. data/ predicciones "
100
101
102
# NECESARIO ACTUALIZAR PREDICCIoN Y TOPTRACKS
[ prediccion , listaTop ] = predictop . PredictTop ( rutaonline , rutausers , 3, rutaPtoF )
103
104
105
106
# Borra contenido de archivo refresh
frefresh = open("/var/www /. data/ refresh ", ’w’)
frefresh . close ()
107
108
109
110
111
# No refresh , pero usuarios conectados
elif (len( users ) != 0):
# Arregla string a lista
users = users . split ("\n")
72
112
users .pop ()
113
114
115
# Actualiza TopTracks
listaTop = predictop . TopTracks ( prediccion , users , 3)
116
117
118
119
# Si no refresh y no usuarios online
else:
listaTop = False
120
121
122
123
# Borra listado de archivos conectados
fonlineusers = open( rutaonline , ’w’)
fonlineusers . close ()
124
125
126
127
128
129
130
if listaTop :
nousersrand = 4
print "Hay usuarios conectados "
else:
nousersrand = 1
print "No hay usuarios conectados "
131
132
133
134
135
136
137
while True:
if ( times %nousersrand == 0):
songnumber = random . randrange (0, len( playlist ))
else:
songnumber = random . randrange (0, len( listaTop ))
songnumber = int( listaTop [ songnumber ])
138
139
times += 1
140
141
142
143
# Si hay mas de 20 canciones reproducidas , olvidar la primera
if (len( last20 ) >20):
last20 .pop (0)
144
145
146
147
148
149
150
151
152
# Si la cancion es de las ultimas 20 reproducidas , buscar otra
if songnumber in last20 :
print str( songnumber )+" ya fue tocada ."
continue
# Si no ha sido reproducida ultimamente
else:
last20 . append ( songnumber )
break
153
154
155
156
# Ruta de cancion a reproducir
ruta = playlist [ songnumber ]
return ruta
157
158
159
160
161
162
163
164
165
# This function , if defined , returns the string you ’d like used
# as metadata (ie for title streaming ) for the current song. You may
# return null to indicate that the file comment should be used.
def ices_get_metadata ():
# Abrir mp3 para obtener metadata
global ruta
global songnumber
print ’Executing get_metadata () function ... ’
166
167
mp3 = open(ruta , "rb")
73
168
169
170
mp3.seek ( -128 ,2)
metadata = mp3.read (128)
mp3. close ()
171
172
173
174
175
176
177
infortrack = null
if( metadata [:3] == "TAG"):
trackdata = {" title ": stripnulls ( metadata [3:33]) ,
" artist ": stripnulls ( metadata [33:63]) ,
" album ": stripnulls ( metadata [63:93]) }
infotrack = trackdata [" artist "] + " - " + trackdata [" title "]
178
179
180
# Lista para archivos ya escritos en last20
local20 = []
181
182
183
184
185
186
187
# Escribir ultimas 20 en archivo last20
flast20 = open("/var/www /. data/ last20 ", ’r’)
local20 = flast20 .read ()
flast20 . close ()
local20 = local20 . split ("\n")
local20 .pop ()
188
189
190
if (len( local20 ) == 20):
local20 .pop (0)
191
192
193
194
195
196
197
198
199
200
201
202
flast20 = open("/var/www /. data/ last20 ", "w")
# Escribe lo que tenia antes
for track in local20 :
writen = track . split (’:’)
# Si cancion en archivo no es la actual , escribirla
# Evita escribir nuevamente cancion actual
if ( writen [0] != str( songnumber )):
flast20 . write ( track + "\n")
# Escribe nuevo track e info
flast20 . write (str( songnumber ) + ’:’ + infotrack + "\n")
flast20 . close ()
203
204
return infotrack
205
206
207
208
209
210
211
# Function used to put the current line number of
# the playlist in the cue file. If you don ’t care about this number
# don ’t use it.
def ices_get_lineno ():
global songnumber
print ’Executing get_lineno () function ... ’
212
213
return songnumber
74
Archivos de Configuración XML
Listing 3: icecast.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<icecast >
<limits >
<clients >100 </ clients >
<sources >2</ sources >
<threadpool >5</ threadpool >
<queue -size >524288 </queue -size >
<client - timeout >30 </client - timeout >
<header - timeout >15 </header - timeout >
<source - timeout >10 </source - timeout >
<!-- If enabled , this will provide a burst of data when a client
first connects , thereby significantly reducing the startup
time for listeners that do substantial buffering . However ,
it also significantly increases latency between the source
client and listening client . For low - latency setups , you
might want to disable this. -->
<burst -on - connect >1</burst -on - connect >
<!-- same as burst -on -connect , but this allows for being more
specific on how much to burst . Most people won ’t need to
change from the default 64k. Applies to all mountpoints -->
<burst -size >65535 </ burst -size >
</limits >
22
23
24
25
26
27
<authentication >
<!-- Sources log in with username ’source ’ -->
<source -password >source </ source -password >
<!-- Relays log in username ’relay ’ -->
<relay -password >relay </ relay -password >
28
29
30
31
32
<!-- Admin logs in with the username given below -->
<admin -user >admin </ admin -user >
<admin -password >nimda </ admin -password >
</ authentication >
33
34
35
36
37
38
<!-- set the mountpoint for a shoutcast source to use , the default if not
specified is / stream but you can change it here if an alternative is
wanted or an extension is required
<shoutcast -mount >/ live.nsv </ shoutcast -mount >
-->
39
40
41
42
43
44
45
46
<!-- Uncomment this if you want directory listings -->
<!-<directory >
<yp -url -timeout >15 </yp -url -timeout >
<yp -url > http: // dir.xiph.org/cgi -bin/yp -cgi </yp -url >
</directory >
-->
47
48
49
50
51
<!-- This is the hostname other people will use to connect to your server .
It affects mainly the urls generated by Icecast for playlists and yp
listings . -->
<hostname >localhost </ hostname >
75
52
53
54
55
56
57
58
59
60
61
62
63
<!-- You may have multiple <listener > elements -->
<listen -socket >
<port >8000 </ port >
<!-- <bind -address >127.0.0.1 </ bind -address > -->
<!-- <shoutcast -mount >/ stream </ shoutcast -mount > -->
</listen -socket >
<!-<listen -socket >
<port >8001 </ port >
</listen -socket >
-->
64
65
66
67
68
<!--<master -server >127.0.0.1 </ master -server >-->
<!--<master -server -port >8001 </ master -server -port >-->
<!--<master -update -interval >120 </ master -update -interval >-->
<!--<master -password >hackme </ master -password >-->
69
70
71
72
73
<!-- setting this makes all relays on - demand unless overridden , this is
useful for master relays which do not have <relay > definitions here.
The default is 0 -->
<!--<relays -on -demand >1 </ relays -on -demand >-->
74
75
76
77
78
79
80
81
<!-<relay >
<server >127.0.0.1 </ server >
<port >8001 </ port >
<mount >/ example .ogg </ mount >
<local -mount >/ different .ogg </ local -mount >
<on -demand >0 </on -demand >
82
83
84
85
<relay -shoutcast -metadata >0 </ relay -shoutcast -metadata >
</relay >
-->
86
87
88
89
90
<!-- Only define a <mount > section if you want to use advanced options ,
like alternative usernames or passwords
<mount >
<mount -name >/ example - complex .ogg </ mount -name >
91
92
93
<username > othersource </ username >
<password > hackmemore </ password >
94
95
96
97
98
99
100
101
102
103
104
105
106
107
<max -listeners >1 </max -listeners >
<dump -file >/ tmp/dump - example1 .ogg </ dump -file >
<burst -size >65536 </ burst -size >
<fallback -mount >/ example2 .ogg </ fallback -mount >
<fallback -override >1 </ fallback -override >
<fallback -when -full >1 </ fallback -when -full >
<intro >/ example_intro .ogg </ intro >
<hidden >1 </ hidden >
<no -yp >1 </no -yp >
<authentication type =" htpasswd ">
<option name =" filename " value =" myauth "/>
<option name =" allow_duplicate_users " value ="0"/ >
</ authentication >
76
108
109
110
<on -connect >/ home/ icecast /bin/stream -start </on -connect >
<on - disconnect >/ home/ icecast /bin/stream -stop </on - disconnect >
</mount >
111
112
113
114
115
116
117
118
119
120
<mount >
<mount -name >/ auth_example .ogg </ mount -name >
<authentication type =" url">
<option name =" mount_add "
value =" http: // myauthserver .net/ notify_mount .php "/>
<option name =" mount_remove "
value =" http: // myauthserver .net/ notify_mount .php "/>
<option name =" listener_add "
value =" http: // myauthserver .net/ notify_listener .php "/>
<option name =" listener_remove " value =" http: // myauthserver .net/ notify_listener .php "/>
</ authentication >
</mount >
121
122
-->
123
124
<fileserve >1 </ fileserve >
125
126
127
128
<paths >
<!-- basedir is only used if chroot is enabled -->
<basedir >/ usr/ share /icecast2 </ basedir >
129
130
131
132
133
134
135
<!-- Note that if <chroot > is turned on below , these paths must both
be relative to the new root , not the original root -->
<logdir >/ var/log/icecast2 </ logdir >
<webroot >/ usr/ share / icecast2 /web </ webroot >
<adminroot >/ usr/ share / icecast2 /admin </ adminroot >
<!-- <pidfile >/ usr/ share / icecast2 / icecast .pid </ pidfile > -->
136
137
138
139
140
141
142
143
144
145
146
147
148
149
<!-- Aliases: treat requests for ’source ’ path as being for ’dest ’ path
May be made specific to a port or bound address using the "port"
and "bind - address " attributes .
-->
<!-<alias source ="/ foo" dest ="/ bar "/>
-->
<!-- Aliases: can also be used for simple redirections as well ,
this example will redirect all requests for http: // server:port / to
the status page
-->
<alias source ="/" dest ="/ status .xsl "/>
</paths >
150
151
152
153
154
155
156
157
158
159
160
161
162
163
<logging >
<accesslog > access .log </ accesslog >
<errorlog > error .log </ errorlog >
<!-- <playlistlog > playlist .log </ playlistlog > -->
<loglevel >3 </ loglevel > <!-- 4 Debug , 3 Info , 2 Warn , 1 Error -->
<logsize >10000 </ logsize > <!-- Max size of a logfile -->
<!-- If logarchive is enabled (1) , then when logsize is reached
the logfile will be moved to [ error | access | playlist ]. log.DATESTAMP ,
otherwise it will be moved to [ error | access | playlist ]. log.old.
Default is non - archive mode (i.e. overwrite )
-->
<!-- <logarchive >1 </ logarchive > -->
</logging >
77
164
165
166
167
168
169
170
171
172
173
174
<security >
<chroot >0 </ chroot >
<!-<changeowner >
<user >nobody </ user >
<group >nogroup </ group >
</ changeowner >
-->
</security >
</icecast >
78
Listing 4: icespy.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version ="1.0"?>
<ices:Configuration xmlns:ices =" http: // www. icecast .org/ projects /ices">
<Playlist >
<!-- This is the filename used as a playlist when using the builtin
playlist handler . -->
<File >/home/ felipe /ices/ playlist .txt </File >
<!-- Set this to 0 if you don ’t want to randomize your playlist , and to
1 if you do. -->
<Randomize >0 </ Randomize >
<!-- One of builtin , perl , or python . -->
<Type >python </ Type >
<!-- Module name to pass to the playlist handler if using perl or python .
If you use the builtin playlist handler then this is ignored -->
<Module >ices </ Module >
<!-- Set this to the number of seconds to crossfade between tracks .
Leave out or set to zero to disable crossfading (the default ).
<Crossfade >5 </ Crossfade >
-->
</Playlist >
20
21
22
23
24
25
26
27
28
29
30
31
<Execution >
<!-- Set this to 1 if you want ices to launch in the background as a
daemon -->
<Background >0 </ Background >
<!-- Set this to 1 if you want to see more verbose output from ices -->
<Verbose >0 </ Verbose >
<!-- This directory specifies where ices should put the logfile , cue file
and pid file (if daemonizing ). Don ’t use /tmp if you have l33t h4x0rz
on your server . -->
<BaseDirectory >/tmp </ BaseDirectory >
</ Execution >
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<Stream >
<Server >
<!-- Hostname or ip of the icecast server you want to connect to -->
<Hostname >127.0.0.1 </ Hostname >
<!-- Port of the same -->
<Port >8000 </Port >
<!-- Encoder password on the icecast server -->
<Password >source </ Password >
<!-- Header protocol to use when communicating with the server .
Shoutcast servers need "icy", icecast 1.x needs " xaudiocast ", and
icecast 2.x needs "http". -->
<Protocol >http </ Protocol >
</ Server >
46
47
48
49
50
51
52
53
54
55
<!-- The name of the mountpoint on the icecast server -->
<Mountpoint >/ RaDIE .mp3 </ Mountpoint >
<!-- The name of the dumpfile on the server for your stream . DO NOT set
this unless you know what you ’re doing .
<Dumpfile >ices.dump </ Dumpfile >
-->
<!-- The name of you stream , not the name of the song! -->
<Name >RaDIE </ Name >
<!-- Genre of your stream , be it rock or pop or whatever -->
79
56
57
58
59
60
61
62
63
<Genre >* </ Genre >
<!-- Longer description of your stream -->
<Description > Prueba de Radio </ Description >
<!-- URL to a page describing your stream -->
<URL > http: //152.74.23.87 :8000 / RaDIE .mp3 </URL >
<!-- 0 if you don ’t want the icecast server to publish your stream on
the yp server , 1 if you do -->
<Public >0</ Public >
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
<!-- Stream bitrate , used to specify bitrate if reencoding , otherwise
just used for display on YP and on the server . Try to keep it
accurate -->
<Bitrate >128 </ Bitrate >
<!-- If this is set to 1, and ices is compiled with liblame support ,
ices will reencode the stream on the fly to the stream bitrate . -->
<Reencode >0</ Reencode >
<!-- Number of channels to reencode to , 1 for mono or 2 for stereo -->
<!-- Sampe rate to reencode to in Hz. Leave out for LAME ’s best choice
<Samplerate >44100 </ Samplerate >
-->
<Channels >2 </ Channels >
</Stream >
</ ices:Configuration >
80
Archivos Javascript
Listing 5: funciones.js
1
// Funciones javascript
2
3
4
var shorttime = 1000; // 1 segundo
var longtime = 5000; // 5 segundos
5
6
7
8
9
10
11
12
13
14
15
16
17
// Para obtener una cookie
function getCookie ( c_name ){
var i,x,y, ARRcookies = document . cookie . split (";");
for (i=0;i< ARRcookies . length ;i++)
{
x= ARRcookies [i]. substr (0, ARRcookies [i]. indexOf ("="));
y= ARRcookies [i]. substr ( ARRcookies [i]. indexOf ("=")+1);
x=x. replace (/^\s+|\s+$/g,"");
if (x== c_name )
return unescape (y);
}
}
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
// Al abrir ventana
function openpage (){
var username = getCookie (" uRaDIE ");
if ( username != null && username !=""){
if ( window . XMLHttpRequest )
{// code for IE7+, Firefox , Chrome , Opera , Safari
xmlhttp =new XMLHttpRequest ();
}
else
{// code for IE6 , IE5
xmlhttp =new ActiveXObject (" Microsoft . XMLHTTP ");
}
xmlhttp . onreadystatechange = function ()
{
if ( xmlhttp . readyState ==4 && xmlhttp . status ==200)
{
// Hacer algo con respuesta de servidor
// document . getElementById (" myDiv "). innerHTML = xmlhttp . responseText ;
}
}
xmlhttp .open("GET"," listening .php? uname ="+username ,true);
xmlhttp .send ();
}
}
43
44
45
46
47
48
49
50
51
// Al cerrar ventana
function closepage (){
var username = getCookie (" uRaDIE ");
if ( username != null && username !=""){
if ( window . XMLHttpRequest )
{// code for IE7+, Firefox , Chrome , Opera , Safari
xmlhttp =new XMLHttpRequest ();
}
81
else
{// code for IE6 , IE5
xmlhttp =new ActiveXObject (" Microsoft . XMLHTTP ");
}
xmlhttp . onreadystatechange = function ()
{
if ( xmlhttp . readyState ==4 && xmlhttp . status ==200)
{
// Hacer algo con respuesta de servidor
// document . getElementById (" myDiv "). innerHTML = xmlhttp . responseText ;
}
}
xmlhttp .open("GET"," notlistening .php? uname ="+username ,true);
xmlhttp .send ();
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
function checkForm (id , rb){
var id = document . forms [id ];
var rb = checkRadio (id[rb ]);
return rb;
}
function checkRadio (id){
var x = 0;
var value = null;
while(x<id. length ){
if(id[x]. checked )
value = id[x]. value ;
x++;
}
return value == null ? 0 : value ;
}
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
function sendEval (ID , form , rbname ){
var evaluation = checkForm (form , rbname );
if ( window . XMLHttpRequest )
{// code for IE7+, Firefox , Chrome , Opera , Safari
xmlhttp =new XMLHttpRequest ();
}
else
{// code for IE6 , IE5
xmlhttp =new ActiveXObject (" Microsoft . XMLHTTP ");
}
xmlhttp . onreadystatechange = function ()
{
if ( xmlhttp . readyState ==4 && xmlhttp . status ==200)
{
// Hacer algo con respuesta de servidor
document . getElementById (ID). innerHTML = xmlhttp . responseText ;
}
}
xmlhttp .open("GET","eval.php?eval="+ evaluation ,true);
xmlhttp .send ();
}
106
107
function sendEvalfixed (ID , track , form , rbname ){
82
var evaluation = checkForm (form , rbname );
if ( window . XMLHttpRequest )
{// code for IE7+, Firefox , Chrome , Opera , Safari
xmlhttp =new XMLHttpRequest ();
}
else
{// code for IE6 , IE5
xmlhttp =new ActiveXObject (" Microsoft . XMLHTTP ");
}
xmlhttp . onreadystatechange = function ()
{
if ( xmlhttp . readyState ==4 && xmlhttp . status ==200)
{
// Hacer algo con respuesta de servidor
document . getElementById (ID). innerHTML = xmlhttp . responseText ;
}
}
xmlhttp .open("GET"," evalfixed .php?eval="+ evaluation +"& track ="+track ,true);
xmlhttp .send ();
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
// jQuery y AJAX
// para refrescar artista - canción
$( document ). ready ( function (){
var j = jQuery . noConflict ();
j( document ). ready ( function ()
{
j(". trackInfo "). everyTime (longtime , function (i){
j.ajax ({
url: " getTrackInfo .php",
cache : false ,
success : function (html){
j(". trackInfo ").html(html);
}
})
})
});
});
// para refrescar evaluación
$( document ). ready ( function (){
var j = jQuery . noConflict ();
j( document ). ready ( function ()
{
j(". evalInfo "). everyTime (longtime , function (i){
j.ajax ({
url: " getEval .php",
cache : false ,
success : function (html){
j(". evalInfo ").html(html);
}
})
})
});
});
// para refrescar duración
$( document ). ready ( function (){
83
var j = jQuery . noConflict ();
j( document ). ready ( function ()
{
j(". duration "). everyTime (shorttime , function (i){
j.ajax ({
url: " getTime .php",
cache : false ,
success : function (html){
j(". duration ").html(html);
}
})
})
});
164
165
166
167
168
169
170
171
172
173
174
175
176
177
});
84
Archivos web de Radio Online
Listing 6: index.php
1
2
<?php session_start (); ?>
<html >
3
4
<head >
5
<meta http - equiv ="Content -Type" content ="text/html; charset =UTF -8"/>
<script language =" javascript " src="jquery -1.2.6. min.js" ></script >
<script language =" javascript " src=" jquery .timers -1.0.0. js" ></script >
<script type="text/ javascript " src=" funciones .js" ></script >
</head >
6
7
8
9
10
11
12
13
<body onload =" openpage ()" onunload =" closepage ()">
<h1 >RaDIE </h1 >
<p> Evalúa canciones para una mejor predicción </p>
14
15
16
17
18
19
<table >
<tr >
<td align =" center ">
<!-- Usando Wimpy Player -->
<!-- START WIMPY BUTTON : Standard HTML -->
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<object classid =" clsid :D27CDB6E -AE6D -11cf -96B8 -444553540000 " codebase ="http :// download .
macromedia .com/pub/ shockwave /cabs/ flash / swflash .cab# version =8 ,0 ,0 ,0" width ="35" height =
"35" id=" wimpybutton229 ">
<param name=" movie " value ="http ://152.74.23.87/ Embedded / WimpyButtonv4 .1.7/ wimpy_button .swf"
/>
<param name="loop" value =" false " />
<param name="menu" value ="true" />
<param name=" quality " value ="high" />
<param name=" bgcolor " value ="# FFFFFF " />
<param name=" wmode " value =" transparent " />
<param name=" flashvars " value =" theFile =http %3A %2F %2F152 %2E74 %2E23 %2E87 %3A8000 %2FRaDIE %2Emp3
& autoplay =yes& bufferAudio =10& icecast =300& wimpyReg =& myid= wimpybutton229 " />
<embed src="http ://152.74.23.87/ Embedded / WimpyButtonv4 .1.7/ wimpy_button .swf" flashvars ="
theFile =http %3A %2F %2F152 %2E74 %2E23 %2E87 %3A8000 %2FRaDIE %2Emp3& autoplay =yes& bufferAudio
=10& icecast =300& wimpyReg =& myid= wimpybutton229 " width ="35" height ="35" bgcolor ="# FFFFFF "
loop=" false " menu="true" quality ="high" name=" wimpybutton229 " align =" middle "
allowScriptAccess =" sameDomain " type=" application /x-shockwave - flash " pluginspage ="http
:// www. macromedia .com/go/ getflashplayer " wmode =" transparent " /></object >
<!-- END WIMPY BUTTON : Standard HTML -->
</td >
<td
align =" center ">
<div class =" duration " ></div >
</td >
<tr >
</table >
<!-- AJAX para refrescar la canción -->
<div class =" trackInfo ">Cargando : Artista - Canción </div ></br >
39
40
41
42
<!-- Ingreso Usuario -->
<!-- Evaluar ... -->
<? if (isset ( $_COOKIE [" uRaDIE "])): ?>
85
<form id=" fevals " action ="">
<table >
<tr >
<td >
<table >
<tr >
43
44
45
46
47
48
<td align =" center "> <input type=" radio "
name="eval" value ="1" /> </td >
<td align =" center "> <input type=" radio "
name="eval" value ="2" /> </td >
<td align =" center "> <input type=" radio "
name="eval" value ="3" /> </td >
<td align =" center "> <input type=" radio "
name="eval" value ="4" /> </td >
<td align =" center "> <input type=" radio "
name="eval" value ="5" /> </td >
49
50
51
52
53
</tr >
<tr >
54
55
<td
<td
<td
<td
<td
56
57
58
59
60
align =" center " >1</td >
align =" center " >2</td >
align =" center " >3</td >
align =" center " >4</td >
align =" center " >5</td >
</tr >
</table >
61
62
</td >
<td >
63
64
<input type=" button " value =" Enviar " onclick =" sendEval (’
IDevalInfo ’, ’fevals ’, ’eval ’)"/>
65
</td >
</tr >
</table >
</form >
Evaluando como: <?= $_COOKIE [" uRaDIE "]?></br >
<div id=" IDevalInfo " class =" evalInfo ">Cargando evaluación ... </div ></br >
66
67
68
69
70
71
72
<a href=" last20 .php" target =" _blank ">Últimas 20 </a></br ></br >
73
74
<a href=" signout .php">Cerrar Sesión </a></br >
75
76
77
<? else: ?>
<form action =" ingresar .php" method ="post">
<table >
<tr >
<td > Usuario : </td >
<td > <input type="text" name="user" /> </td >
</tr >
<tr >
<td > Contraseña : </td >
<td > <input type=" password " name="pass" /></td >
</tr >
</table >
<input type=" submit " />
</form >
<a href=" newuser .html">Nuevo Usuario </a></br >
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
<? endif ?>
86
93
94
</br >
<a href="FAQ.html" target =" _blank ">Preguntas Frecuentes </a></br >
95
96
97
</body >
98
99
</html >
87
Listing 7: newuser.html
1
<html >
2
3
<head >
4
<meta http - equiv ="Content -Type" content ="text/html; charset =UTF -8"/>
</head >
5
6
7
8
9
<body >
<h1>RaDIE </h1>
<h2>Nuevo Usuario </h2>
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<form action =" nuevousuario .php" method ="post">
<table >
<tr>
<td >Nombre de Usuario : </td>
<td > <input type="text" name=" username " /> </td >
</tr>
<tr>
<td >Contraseña : </td >
<td > <input type=" password " name=" password " /></td>
</tr>
<tr>
<td >Repita Contraseña : </td>
<td > <input type=" password " name=" passwordcheck " /></td >
</tr>
<tr>
<td >Mail: </td>
<td > <input type="text" name="mail" /></td>
</tr>
</ table >
<input type=" submit " />
</form >
32
33
34
</body >
88
Listing 8: FAQ.html
1
<html >
2
3
4
<head >
<meta http - equiv ="Content -Type" content ="text/html; charset =UTF -8"/>
5
6
7
8
9
</head >
<body >
<h1>RaDIE </h1>
<h2>Preguntas Frecuentes </h2>
10
11
12
<h4>¿Qué es esto?</h4>
<p>Es parte del desarrollo de una Biblioteca de Música Colaborativa Online , como proyecto
memoria UdeC.</p>
13
14
15
16
<h4>¿En qué consiste ?</h4>
<p>Es una Radio Online que adapta la lista de reproducción de acuerdo a los gustos
personales de los usuarios conectados .</p>
<p>Esto se logra comparando todas las evaluaciones de todos los usuarios y prediciendo las
que faltan , por lo que es muy importante que evalúen hartas canciones .</p>
17
18
19
20
<h4>¿Y cómo evalúo ?</h4 >
<p>Para evaluar alguna canción , basta con crearse una cuenta de usuario .</p>
<p>La escala de evaluación es de 1 a 5, siendo 1 "Muy Mala" y 5 "Muy Buena "</p>
21
22
23
24
<h4>Creo que no está adaptando mucho a mis gustos ... </h4>
<p>Si has evaluado pocas canciones , es muy difícil predecir tu gusto , por lo tanto sonarán
muchas canciones al azar.</p>
<p>Recuerda que la predicción se logra comparando muchas evaluaciones , si tu tienes pocas ,
no se puede predecir bien.</p>
25
26
27
<h4>¿El nombre de usuario es sensible a Mayúsculas ?</h4>
<p>Sí , y los otros campos también .</p>
28
29
30
<h4>¿La clave está encriptada ?</h4>
<p>Sí , este sitio no almacena las claves directamente , sólo sus md5.</p>
31
32
33
<h4>¿Me enviarán basura al correo ?</h4>
<p>No , se pide en caso de necesitar contactar a los usuarios .</p>
34
35
36
37
<h4>¿Por qué dice en Predicción un 0? </h4 >
<p>En Predicción aparece la nota que la radio predice que le pondrás a la canción actual .</
p>
<p>Si nadie la ha evaluado , o no has evaluado nada en común con otro usuario , es imposible
predecir .</p>
38
39
40
<h3>CONTACTO </h3>
<p>Para consultas , denuncias de errores , cambio contraseña , correo u otro motivo , escribir
a <a href=" mailto : contactoradie@gmail .com">contactoradie@gmail .com </a>.</p>
41
42
</body >
89
Listing 9: nuevousuario.php
1
<html >
2
3
<head >
4
<meta http - equiv ="Content -Type" content ="text/html; charset =UTF -8"/>
</head >
5
6
7
8
9
10
11
12
13
14
15
16
17
<body >
<h1 >RaDIE </h1 >
<h2 > Creando Usuario </h2 >
<?php
$rutauser = ". usuarios /" . $_POST [" username "];
// Validar Nombre de Usuario
if ( $_POST [" username "] == NULL){
echo "Debe Ingresar Nombre De Usuario ";
echo " </br >";
echo ’<a href =" newuser .html"> Volver a Crear Usuario </a>’;
}
18
19
20
21
22
23
24
// Revisa Existencia y Crear Archivo de Usuario
else if (! $fuser = fopen ($rutauser , "x")){
echo " Usuario Ya Existe ";
echo " </br >";
echo ’<a href =" newuser .html"> Volver a Crear Usuario </a>’;
}
25
26
27
28
29
30
31
32
33
// Validar las Contraseñas
else if ( $_POST [" password "] == NULL){
fclose ( $fuser );
unlink ( $rutauser );
echo "Debe ingresar contraseña ";
echo " </br >";
echo ’<a href =" newuser .html"> Volver a Crear Usuario </a>’;
}
34
35
36
37
38
39
40
41
else if( $_POST [" password "] != $_POST [" passwordcheck "]){
fclose ( $fuser );
unlink ( $rutauser );
echo " Claves No Coinciden ";
echo " </br >";
echo ’<a href =" newuser .html"> Volver a Crear Usuario </a>’;
}
42
43
44
45
46
47
48
49
50
// Validar Correo
else if( $_POST ["mail"] == NULL){
fclose ( $fuser );
unlink ( $rutauser );
echo "Debe Ingresar Correo Electrónico ";
echo " </br >";
echo ’<a href =" newuser .html"> Volver a Crear Usuario </a>’;
}
51
52
53
54
55
else{
// Encriptar Contraseñas con MD5 y Guardarla en Archivo
fwrite ($fuser , md5( $_POST [" password "])."\n");
90
// Guarda Correo en Archivo
fwrite ($fuser , $_POST ["mail"]."\n");
56
57
58
echo " Usuario Creado con Éxito .";
59
}
60
61
62
63
64
65
66
fclose ( $fuser );
?>
</br >
</br >
<a href="http :// hopper .die.udec.cl">Volver a Escuchar RaDIE </a>
67
68
</body >
91
Listing 10: ingresar.php
1
<?php
// Crear cookie
$expire = time () + 60*60*24*30; // Un Mes de expiración
$username = $_POST ["user"];
setcookie (" uRaDIE ", $username , $expire );
2
3
4
5
6
?>
7
8
<html >
9
10
<head >
11
<meta http - equiv ="Content -Type" content ="text/html; charset =UTF -8"/>
</head >
12
13
14
15
16
<body >
<h1 >RaDIE </h1 >
<h2 > Iniciando Sesión </h2 >
17
18
<?php
// $rutauser = "/ home/ felipe / RaDIE / usuarios /".
$rutauser = ". usuarios /". $_POST ["user"];
// Revisa si Usuario Existe
if(! $fuser = fopen($rutauser , r)){
setcookie (" uRaDIE ", "", time () -3600);
exit(" Usuario No Existe ");
}
19
20
21
22
23
24
25
26
// Revisa Contraseña Correcta
$clave = fread ($fuser , "32");
if( $clave != md5( $_POST ["pass"])){
setcookie (" uRaDIE ", "", time () -3600);
exit(" Contraseña Incorrecta ");
}
27
28
29
30
31
32
33
fclose ( $fuser );
34
35
?>
36
37
38
Bienvenido <?php echo $_POST ["user"]; ?><br />
Puedes empezar a evaluar las canciones . <br />
39
40
<a href="http :// hopper .die.udec.cl">Volver a Escuchar RaDIE </a>
41
42
43
</body >
</html >
92
Listing 11: eval.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
session_start ();
$rutauser = ". usuarios /". $_COOKIE [" uRaDIE "];
if( $_GET ["eval"]== NULL)
echo " ERROR : Debe Ingresar Evaluación .";
else if( $_GET ["eval"] < 1 || $_GET ["eval"] > 5)
echo " ERROR : Evaluación Inválida ";
else if (! isset( $_SESSION [" track "]))
echo " ERROR : No hay canción para evaluar ";
else{
$fdatauser = fopen($rutauser , "r") or exit("No se encuentra el archivo del usuario a
leer.");
$datauser = fread( $fdatauser , filesize ( $rutauser )) or exit("No se pudo leer archivo de
usuario .");
$datauser = split("\n", $datauser );
array_pop ( $datauser ); // Borra último elemento después del último salto de línea (
Nada)
fclose ( $fdatauser );
16
17
18
19
$fuser = fopen ($rutauser , "w") or exit("No se encuentra el archivo del usuario a
escribir .");
fwrite ($fuser , array_shift ( $datauser )."\n") or exit("No se pudo escribir Password .");
// Password
fwrite ($fuser , array_shift ( $datauser )."\n") or exit("No se pudo escribir Correo .");
// Correo
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// buscar , cambiar par_eval si existe , o agregar si no , registra cambio en archivo
$notindata = true; // flag "No track evaluado "
foreach ( $datauser as & $par_eval ){
$par_eval_array = explode (" ", $par_eval );
if ( $par_eval_array [0] == $_SESSION [" track "]){
// Abre archivo prediccion vs. evaluacion ( escribe vieja vs. nueva ) "EE"
$fPvsE = fopen(".data/PvsE", ’a’);
fwrite ($fPvsE , $_COOKIE [" uRaDIE "]. ’ ’. $_SESSION [" track "]. ’ ’.
$par_eval_array [1]. ’ ’. $_GET ["eval"]." EE\n");
fclose ( $fPvsE );
$par_eval_array [1] = $_GET ["eval"];
$notindata = false;
$par_eval = $par_eval_array [0]." ". $par_eval_array [1];
break;
}
}
unset ( $par_eval );
37
38
39
40
// Si no estaba , se agrega track evaluado y se escribe prediccion y eval en archivo
if( $notindata ){
array_push ($datauser , $_SESSION [" track "]." ". $_GET ["eval"]);
41
// Abre archivo prediccion vs. evaluacion "PE"
$fPvsE = fopen(".data/PvsE", ’a’);
fwrite ($fPvsE , $_COOKIE [" uRaDIE "]. ’ ’. $_SESSION [" track "]. ’ ’. $_SESSION [" predict
"]. ’ ’. $_GET ["eval"]." PE\n");
fclose ( $fPvsE );
42
43
44
45
46
47
}
93
// Escribe evaluaciones a archivo de usuario
foreach ( $datauser as $par){
fwrite ($fuser , $par."\n") or exit("No se pudo escribir evaluación .");
}
fclose ( $fuser );
48
49
50
51
52
53
// Refrescar con nueva evaluacion
$fneweval = fopen(".data/ refresh ", "w");
fwrite ($fneweval , "1");
fclose ( $fneweval );
54
55
56
57
58
echo "Su evaluación fue: ". $_GET ["eval"]." </br ></br >";
59
}
60
61
?>
94
Listing 12: evalfixed.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
session_start ();
// $rutauser = "/ home/ felipe / RaDIE / usuarios /". $_COOKIE [" uRaDIE "];
$rutauser = ". usuarios /". $_COOKIE [" uRaDIE "];
if( $_GET ["eval"]== NULL)
exit(" ERROR : Debe Ingresar Evaluación .");
else if( $_GET ["eval"] < 1 || $_GET ["eval"] > 5)
exit(" ERROR : Evaluación Inválida ");
else{
$fdatauser = fopen($rutauser , "r") or exit("No se encuentra el archivo del usuario a
leer.");
$datauser = fread( $fdatauser , filesize ( $rutauser )) or exit("No se pudo leer archivo de
usuario .");
$datauser = split("\n", $datauser );
array_pop ( $datauser ); // Borra último elemento después del último salto de línea (
Nada)
fclose ( $fdatauser );
15
16
17
18
$fuser = fopen ($rutauser , "w") or exit("No se encuentra el archivo del usuario a
escribir .");
fwrite ($fuser , array_shift ( $datauser )."\n") or exit("No se pudo escribir Password .");
// Password
fwrite ($fuser , array_shift ( $datauser )."\n") or exit("No se pudo escribir Correo .");
// Correo
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// buscar , cambiar par_eval si existe , o agregar si no , registra cambio en archivo
$notindata = true; // flag "No track evaluado "
foreach ( $datauser as & $par_eval ){
$par_eval_array = explode (" ", $par_eval );
if ( $par_eval_array [0] == $_GET [" track "]){
// Abre archivo prediccion vs. evaluacion ( escribe vieja vs. nueva ) "EE"
$fPvsE = fopen(".data/PvsE", ’a’);
fwrite ($fPvsE , $_COOKIE [" uRaDIE "]. ’ ’. $_GET [" track "]. ’ ’. $par_eval_array
[1]. ’ ’. $_GET ["eval"]." EE\n");
fclose ( $fPvsE );
$par_eval_array [1] = $_GET ["eval"];
$notindata = false;
$par_eval = $par_eval_array [0]." ". $par_eval_array [1];
break;
}
}
unset ( $par_eval );
36
37
38
39
40
41
42
43
44
45
46
47
// Si no estaba , se agrega track evaluado y se escribe prediccion y eval en archivo
if( $notindata ){
// Obtener prediccion de $_GET [" track "]
// Para obtener prediccion
$rutaPred = ".data/ predicciones ";
$fPred = fopen($rutaPred , r) or exit("No se encuentra el archivo de
predicciones ");
do{
$username = fgets ( $fPred ); // Usuario + :
// Username en [0] , Diccionario en [1]
$username = explode (’:’, $username );
}
95
while( $username [0] != $_COOKIE [" uRaDIE "]);
48
49
// cada pareja de track : evaluacion separada
$username [1] = explode (’;’, $username [1]);
50
51
52
// Si no hay prediccion , eval = 0
$eval_predict = 0;
foreach ( $username [1] as $track ){
// separa track de evaluacion
$track = explode (’=’, $track );
if ( $track [0] == $_GET [" track "]){
$eval_predict = $track [1];
break ;
}
}
fclose ( $fPred );
53
54
55
56
57
58
59
60
61
62
63
64
array_push ($datauser , $_GET [" track "]." ". $_GET ["eval"]);
65
66
// Abre archivo prediccion vs. evaluacion "PE"
$fPvsE = fopen(".data/PvsE", ’a’);
fwrite ($fPvsE , $_COOKIE [" uRaDIE "]. ’ ’. $_GET [" track "]. ’ ’. $eval_predict .’ ’.
$_GET ["eval"]." PE\n");
fclose ( $fPvsE );
67
68
69
70
}
71
72
// Escribe evaluaciones a archivo de usuario
foreach ( $datauser as $par){
fwrite ($fuser , $par."\n") or exit("No se pudo escribir evaluación .");
}
fclose ( $fuser );
73
74
75
76
77
78
// Refrescar con nueva evaluacion
$fneweval = fopen(".data/ refresh ", "w");
fwrite ($fneweval , "1");
fclose ( $fneweval );
79
80
81
82
83
echo "Su evaluación fue: ". $_GET ["eval"];
84
}
85
86
?>
96
Listing 13: getEval.php
1
<?php
session_start ();
$_SESSION [" predict "] = 0; // Para usuarios que evalúan por primera vez
$rutauser = ". usuarios /". $_COOKIE [" uRaDIE "];
$fuser = fopen($rutauser , ’r’) or exit("No se encuentra el archivo del usuario ");
$song = fgets ( $fuser ); // Password
$song = fgets ( $fuser ); // eMail
$song = fgets ( $fuser ) or exit("No has evaluado esta canción ."); // primera evaluación , si
hay
2
3
4
5
6
7
8
9
$fueeval = false; // flag para lo anterior
while (! feof( $fuser )){
$song = explode (" ", $song );
if ( $song [0] == $_SESSION [" track "]){
$fueeval = true;
$eval_actual = $song [1];
break;
}
10
11
12
13
14
15
16
17
18
$song = fgets ( $fuser );
}
fclose ( $fuser );
19
20
21
22
// Para obtener predicción
$rutaPred = ".data/ predicciones ";
$fPred = fopen($rutaPred , r) or exit("No se encuentra el archivo de predicciones ");
do{
$username = fgets ( $fPred ); // Usuario + :
// Username en [0] , Diccionario en [1]
$username = explode (’:’, $username );
}
while( $username [0] != $_COOKIE [" uRaDIE "]);
23
24
25
26
27
28
29
30
31
32
// cada pareja de track : evaluación separada
$username [1] = explode (’;’, $username [1]);
33
34
35
// Si no hay predicción , eval = 0
$eval_predict = 0;
foreach ( $username [1] as $track ){
// separa track de evaluación
$track = explode (’=’, $track );
if ( $track [0] == $_SESSION [" track "]){
$eval_predict = $track [1];
break;
}
}
fclose ( $fPred );
$_SESSION [" predict "] = $eval_predict ;
36
37
38
39
40
41
42
43
44
45
46
47
48
if ( $fueeval )
echo " Evaluaste esta canción con un ". $eval_actual ." </br ></br >";
else
echo "No has evaluado esta canción .</br >"." Predicción : ". $eval_predict ;
49
50
51
52
53
?>
97
Listing 14: getTime.php
1
<?php
session_start ();
$file = fopen ("/tmp/ices.cue", "r") or exit("No se pudo leer .cue");
$info = fread ($file , filesize ("/tmp/ices.cue"));
fclose ( $file );
$info = split ("\n", $info );
array_pop ( $info );
// $route = $info [0];// rtrim ( fgets ( $file ), "\n");
// $size = $info [1];// rtrim ( fgets ( $file ), "\n");
// $bitrate = $info [2];// rtrim ( fgets ( $file ), "\n");
$duration = $info [3]; // rtrim ( fgets ( $file ), "\n");
$progress = $info [4]; // rtrim ( fgets ( $file ), "\n");
$idlist = $info [5]; // rtrim ( fgets ( $file ), "\n");
$artist = $info [6]; // rtrim ( fgets ( $file ), "\n");
$title = $info [7]; // rtrim ( fgets ( $file ), "\n");
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$_SESSION [" track "] = $idlist ;
$_SESSION [" artist "] = $artist ;
$_SESSION [" title "] = $title ;
17
18
19
20
$dur = split (":", $duration );
$total = $dur [2]*60 + $dur [3];
$time = $total * $progress /100;
21
22
23
24
$time = round ( $time );
$total = round( $total );
$min = floor ( $time /60);
$sec = $time %60;
25
26
27
28
29
$_SESSION ["time"] = $time ;
$_SESSION [" total "] = $total ;
30
31
32
if ( $total == 0)
echo " --:--/--:--";
else
echo sprintf (" %’02u: %’02u/ %’02u: %’02u", $min , $sec , $dur [2] , $dur [3]);
33
34
35
36
37
?>
98
Listing 15: getTrackInfo.php
1
<?php
session_start ();
2
3
// Forma el string con informacion artista - canción
$trackinfo = $_SESSION [" artist "]." - ". $_SESSION [" title "];
4
5
6
// Si hay cookie , escribir usuario conectado
if ( $_COOKIE [" uRaDIE "]){
$rutaonline = ".data/ usersonline ";
$file = fopen( $rutaonline ,"a+");
$usuarios = fread($file , filesize ( $rutaonline ));
$usuarios = split("\n",$usuarios );
array_pop ( $usuarios );
7
8
9
10
11
12
13
14
// Si usuario no esta en archivo online , escribirlo
if (! in_array ( $_COOKIE [" uRaDIE "], $usuarios ))
fwrite ($file , $_COOKIE [" uRaDIE "]."\n");
15
16
17
18
fclose ( $file );
19
}
20
21
echo $trackinfo ;
22
23
?>
99
Listing 16: last20.php
1
2
3
4
5
<html >
<head >
<meta http - equiv ="Content -Type" content ="text/html; charset =UTF -8"/>
<script type="text/ javascript " src=" funciones .js" ></script >
</head >
6
7
8
9
10
11
12
13
14
15
16
17
<body >
<h1 >RaDIE </h1 >
<h2 > Últimas 20 canciones reproducidas </h2 >
<p> Debes actualizar la página para refrescar la lista <p>
<?php
$flast20 = fopen (".data/ last20 ", ’r’) or exit("No se puede abrir last20 ");
$last20 = fread ($flast20 , filesize (".data/ last20 ")) or exit("No se puede leer last20 "
);
fclose ( $flast20 );
$last20 = split ("\n", $last20 );
array_pop ( $last20 );
$last20 = array_reverse ( $last20 );
18
19
20
21
22
// Abrir archivo de usuario para buscar evaluación
$fuser = fopen (". usuarios /". $_COOKIE [" uRaDIE "], ’r’) or exit("No se encuentra el
archivo del usuario ");
$song = fgets ( $fuser ); // Password
$song = fgets ( $fuser ); // eMail
23
24
25
26
27
28
29
30
31
32
33
// Crea lista con temas y evaluaciones , si hay
$HasSongs = false;
$allsongs = array ();
while (! feof( $fuser )){
$HasSongs = true;
$song = fgets ( $fuser );
$song = explode (" ", $song );
array_push ($allsongs , $song );
}
fclose ( $fuser );
34
35
36
37
38
// N para número de track pasado
$N = 1;
foreach ( $last20 as $track ){
$evaluada = false ;
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
$track = explode (’:’, $track );
echo ’
<b>’.$N.". ". $track [1]." </b>";
echo ’
<form id =" feval ’.$N.’" action ="" >
<table >
<tr >
<td >
<table >
<tr >’;
for ($i =1; $i <6; $i ++)
echo ’
<td align =" center "> <input type =" radio " name =" eval ’.$N.’" value =" ’.$i.’" /> </td >’;
echo ’
100
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
</tr >
<tr >’;
for ($i =1; $i <6; $i ++)
echo ’
<td align =" center ">’.$i.’ </td >’;
echo ’
</tr >
</table >
</td >
<td >
<input type =" button " value =" Enviar " onclick =" sendEvalfixed (\’ID ’.$N.’\’, ’.( int)
$track [0]. ’, \’ feval ’.$N.’\’, \’eval ’.$N.’\’)"/>
</td >
<td >’;
// Buscar la canción , si fue evaluada
if ( $HasSongs )
foreach ( $allsongs as $song ){
if ( $song [0] == $track [0]){
$evaluada = true;
echo ’
<div id =" ID ’.$N.’" style =" vertical - align : middle "> Evaluaste esta canción con un ’.
$song [1]. ’ </div >’;
}
}
// Si no fue evaluada
if (! $evaluada )
echo ’
<div id =" ID ’.$N.’" style =" vertical - align : middle ">No has evaluado esta canción </div >’;
echo ’
</td >
</tr >
</table >
</form >’;
85
$N += 1;
86
}
87
88
89
90
?>
</body >
</html >
101
Listing 17: listening.php
1
<?php
// Open file
$ruta = ".data/ usersonline ";
$file = fopen ($ruta ,"a+");
$usuarios = fread($file , filesize ( $ruta ));
$usuarios = split("\n",$usuarios );
array_pop ( $usuarios );
2
3
4
5
6
7
8
// Si usuario no está en archivo online , escribirlo
if (! in_array ( $_GET [" uname "], $usuarios ))
fwrite ($file , $_GET [" uname "]."\n");
9
10
11
12
fclose ( $file );
13
14
?>
102
Listing 18: notlistening.php
1
<?php
// Open file
$ruta = ".data/ usersonline ";
$file = fopen ($ruta ,"r");
$usuarios = fread($file , filesize ( $ruta ));
fclose ( $file );
$usuarios = split("\n",$usuarios );
array_pop ( $usuarios );
2
3
4
5
6
7
8
9
$file = fopen ($ruta ,"w");
foreach ( $usuarios as $user ){
if ( $user == $_GET [" uname "] )
continue ;
else
fwrite ($file , $user ."\n");
}
10
11
12
13
14
15
16
17
fclose ( $file );
18
19
session_destroy ();
20
21
?>
103
Listing 19: signout.php
1
<?php
session_start ();
2
3
?>
4
5
<html >
6
7
<head >
<meta http - equiv ="Content -Type" content ="text/html; charset =UTF -8"/>
8
9
10
</head >
11
12
13
14
15
16
17
18
19
<body >
<h1 >RaDIE </h1 >
<h2 > Sesión Cerrada </h2 >
<?php
setcookie (" uRaDIE ", "", time () -3600);
session_destroy ();
?>
<a href="http :// hopper .die.udec.cl">Volver a Escuchar RaDIE </a>
20
21
</body >
Descargar