Tag Archives: PHP

Obtener video webm de Youtube (antes FLV) en PHP

Hace tiempo había publicado el código para obtener el código de FLV de Youtube (http://vida.danguer.com/2009/01/09/obtener-video-flv-de-youtube/), esto ya no funciona debido a que el archivo cambió y que Youtube ya no proporciona ahora un archivo FLV, ahora proporciona un archivo webm que es una extensión de un archivo Matroska, esto es debido a su iniciativa de soportar HTML5 para reproducir sus video.

El código para descargar el archivo webm es igual de fácil, pero no se puede reproducir con los reproductores “tradicionales” de flash, después de descargar el archivo pueden utilizar VLC o para web pueden utilizar el jwplayer (para reproducirlo deben cambiar la extensión a .webm)

Más información de webm: http://www.webmproject.org/

<?php
//video id by default
$video_id = 'v6xU96KLBL4';

if (isset($_REQUEST['video_id']))
        $video_id = trim($_REQUEST['video_id']);

//get vars from video
$url_info = 'http://www.youtube.com/get_video_info?video_id='.$video_id;
$info = file_get_contents($url_info);

$vars = array();
parse_str($info, $vars);

if ($vars['status'] == 'ok') {
	$vars_fmt = array();
	parse_str($vars['url_encoded_fmt_stream_map'], $vars_fmt);

	print "WEBM: {$vars_fmt['url']}";
} else
	print "Error: {$vars['reason']}";

Puedes descargar el código en github: https://github.com/danguer/blog-examples/blob/master/php/youtube/get-flv.php

Iniciando sesión con Google+ y OAuth

Un escenario común es iniciar una sesión mediante una cuenta externa como Google+, Twitter o Facebook.
En este código mostraré como hacerlo con Google+ API

Primero necesitas registrar el API en https://code.google.com/apis/console/, registrar el servicio de Google+ API y crear una nueva aplicación, en el campo de “Redirect URI” necesitas escribir la ruta del script y lo siguiente: “googleplus.php?op=redirect“, comentaré el porqué después; después que veas el código podrás separarlo en rutas o scripts más elegantes.

En la aplicación generada, necesitarás los campos de Redirection URI y Client ID

Google OAuth funciona de la siguiente manera:

  1. Generar una URL usando los parámetros de los servicios que quieres acceder (scope) y tu Client ID y Redirection URI
  2. Necesitas redireccionar al usuario a esta URL
  3. Después que el usuario ha ingresado, el sistema te redireccionará a la URL que proporcionaste con parametros hash (googleplus.php?op=redirect#access_token=....), así que no verás esto en el servidor; puedes pasarlo a tu servidor mediante AJAX o en este caso mediante una petición GET

En el código el parámetro “?op=redirect” mostrará una página blanca con un código en javascript para convertir el hash en una petición GET; esto pemitirá obtener en el servidor el parámetro access_token que usarás para verificar mediante Google+ si es válido.

Esta parte del código lo hace:

<?php
        $access_token = $_GET['access_token'];

	//verificar si es real
	$data = @file_get_contents("https://www.googleapis.com/plus/v1/people/me?access_token={$access_token}");
	if ($data) {
		print $data;
	} else {
		print "Token no valido!";
	}

El código no está preparado para manejar errores (lo mostrará como un parámetro $_GET['error']) así que necesitas manejar eso en tu caso.

Ligas:

Usando php para analizar logs de apache

Apache tiene una característica especial que es enviar la salida de sus logs a través de una tubería (pipe). Esto evita configurar syslog para reenviar los registros a un servidor en php.

Los cambios en apache son realmente simples, solo necesitas colocar algo así en la configuración:

LogFormat "%v %A %D \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" milog
CustomLog "|/usr/bin/php5 [PATH_TO_SCRIPT]/apache-stdin.php log" milog
ErrorLog "|/usr/bin/php5 [PATH_TO_SCRIPT]/apache-stdin.php error"

En apache-stdin.php el código es realmente simple, solo necesitas hacer:

<?php
$fp = fopen('php://stdin', 'r');
do {
	//leer una línea de apache, si no tiene datos se bloqueará hasta obtenerla
	$data = fgets($fp);
	$data = trim($data); //quitar espacios y "enters" del mensaje

	if (empty($data)) {
		break; //no envia más datos, así que terminar el script
	}

	//procesar el mensaje
} while(true);

fclose($fp);

Como puedes ver es simplemente leer una línea y procesarla.

He escrito un script de ayuda para esto, que puedes descargar aqui:
https://github.com/danguer/blog-examples/blob/master/php/syslog/apache-stdin.php

A este script le puedes pasar un parámetro adicional para especificar si es un registro normal o un registro de error; será igual que en la configuración de apache que puse anteriormente.

También puedes configurar el script para especificar el format de registro que estás usando en apache para obtener una descripción simple; el formato debe ser literal como:

<?php
$format = '%v %A %D \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"';

Con esto, el script generará un arreglo como:

array(
'hostname' => 'danguer.com',
'local_ip' => 127.0.0.1,
'time_ms' => 0.0002,
'first_line_request' => 'GET / HTTP/1.1',
'status_last' => 200,
'bytes_sent' => 2048
);

Con esto puedes usarlo para almacenarlo en un archivo (es lo que hace sin cambios el script), insertarlo a una base de datos, usar simpledb, etc.

Instalando php-fpm y apache2

He leído muchos tutoriales sobre como instalar nginx y php-fpm.
Sigo usando apache2 más que nada debido por la facilidad del archivo .htaccess que permite hacer uso de mod_rewrite y proteger las rutas con un password (así como usar otros módulos como mod_svn, etc)

No he encontrado una manera 100% transparente como ngninx, pero detallo lo que uso para trabajar con php-fpm y apache2. Pasos en debian:

  • echo "deb http://packages.dotdeb.org stable all" >> /etc/apt/sources.list
  • apt-get update
  • apt-get install libapache2-mod-fastcgi apache2-mpm-worker php5-fpm php5-common php5-cli
  • hacer todo los apt-get de todos los módulos php5-* que quieras
  • Agregar lo siguiente a un nuevo archivo: /etc/apache2/mods-enabled/fpm.load
    AddType application/x-httpd-fastphp5 .php .phtml
    Action application/x-httpd-fastphp5 /fast-cgi-fake-handler
  • en /etc/php5/fpm/pool.d/www.conf cambiar:
    listen = 127.0.0.1:9000

    por:

    ; listen = 127.0.0.1:9000
     listen = /var/run/php-fpm.socket

    Esto habilitará el socket de unix que debe ser más eficiente

  • /etc/init.d/php-fpm restart
  • /etc/init.d/apache2 restart

 

Con esto podrás usar php-fpm a través de una url “ficticia”: /fast-cgi-fake-handler si quieres cambiar tu archivo /etc/apache2/sites-enabled/000-default se vería de esta manera:

FastCGIExternalServer /var/www/fast-cgi-fake-handler -socket /var/run/php-fpm.socket
DocumentRoot /var/www

El sistema cambiará la url /index.php por: /fast-cgi-fake-handler/index.php que será enviada a php-fpm mediante fastcgi

Esto tiene dos problemas:

  1. Necesitas especificar FastCGIExternalServer de la forma ${DOCUMENT_ROOT}/fast-cgi-fake-handler en todos tus hosts virtuales para que funcione
  2. Zend framework y otros que utilicen el patron de consumir cualquier url y pasársela a un script index.php no funcionarán porque generarán un ciclo infinito; pero hay una solución sencilla en tu código .htaccess escribe después de RewriteEngine On:
    RewriteRule ^fast-cgi-fake-handler/ - [L,NC]

    Esto evitará procesar todas las ligas que contengan “fast-cgi-fake-handler” como su inicio; o claro puedes usar RewriteCond para evitar esto.

Cuidado al usar Zend_Date

En un proyecto, estaba utilizando Zend_Date para manejo de horas en diferentes zonas horarias, cuando lo estaba desarrollando empecé a tener un error porque olvidé que la fecha se actualiza cuando cambias un parámetro y el problema surge cuando cambias el mes o día y se encuentra con una fecha inexistente aunque en el código parezca correcta, por ejemplo, supongamos que estamos en el mes de febrero y queremos crear una fecha de marzo (asignaré la fecha para reproducir el error, aunque el error es cuando se utiliza la fecha actual)

include_once("Zend/Date.php");
$fecha_febrero = mktime(0, 0, 0, 2, 1, 2010);
$fecha = new Zend_Date($fecha_febrero);
$fecha->setDay(31);
$fecha->setMonth(3);
print "Fecha Marzo: ".date("d/m/Y", $fecha->getTimestamp());

Uno esperaría que el resultado fuera: 31/03/2010

Pero en su lugar mostrará 03/03/2010

Esto es porque al hacer $fecha->setDay(31) el sistema intenta crear la fecha: 31/02/2010 que no existe, por lo que “recorre” los días restantes para quedar en 03/03/2010, al asignar el mes, no tiene efecto.

La solución es simplemente hacerlo en orden inverso:

include_once("Zend/Date.php");
$fecha_febrero = mktime(0, 0, 0, 2, 1, 2010);
$fecha = new Zend_Date($fecha_febrero);
$fecha->setMonth(3);
$fecha->setDay(31);
print "Fecha Marzo: ".date("d/m/Y", $fecha->getTimestamp());

Eso parecería la solución ideal (y es la recomendada por la documentación), pero no es totalmente cierta, si por ejemplo estamos en enero 31 del 2010 e intentamos asignar una fecha de febrero, siempre obtendremos una fecha de marzo:

include_once 'Zend/Date.php';
$fecha_enero = mktime(0, 0, 0, 1, 31, 2010);
$fecha = new Zend_Date($fecha_enero);
$fecha->setMonth(2);
$fecha->setDay(10);
print "Fecha Febrero: ".date("d/m/Y", $fecha->getTimestamp());

Uno esperaría que mostrara la fecha:
10/02/2010

Pero en realidad muestra
10/03/2010

Esto porque cuando asignamos el mes, el sistema intenta crear la fecha 31/03/2010 que una vez más es incorrecta, por lo que crea la fecha 03/03/2010, pero al asignar la fecha, el resultado es un error en el mes.

Aunque esto no es un error, es muy peligroso porque uno puede suponer que la fecha es correcta porque en el código lo parece, la solución sin embargo es trivial, asignar el primer día de un mes que tenga 31 días y asignar los valores en orden inverso (primero año, luego mes y por último día), así el código sin error sería:

include_once 'Zend/Date.php';
$fecha = new Zend_Date(0, 0, 0, 1, 1, date('Y'));
$fecha->setMonth(2);
$fecha->setDay(10);
print "Fecha Febrero: ".date("d/m/Y", $fecha->getTimestamp());

Resultado (el esperado)
10/02/2010

Esto funciona en la mayoría de los casos; aunque si van a manejar diferentes zonas horarias mediante setTimezone() el código anterior tendrá el mismo error en los casos que la zona horaria sea menor a la zona horaria del servidor, por lo que hay que hacer algo más elaborado:

include_once 'Zend/Date.php';
$fecha = new Zend_Date();
//eliminar "errores"
$fecha->setTimezone('mi_codigo_zona_horaria');
$fecha->setDay(1);
$fecha->setMonth(1);

//nuestro código
$fecha->setMonth(2);
$fecha->setDay(10);
print "Fecha Febrero: ".date("d/m/Y", $fecha->getTimestamp());

Lo peligroso del comportamiento de Zend_Date es que funcionará con errores durante algunos días/meses dependiendo de la manera en como se programe y es un poco difícil de diagnosticar por su misma naturaleza, con esto se elimina cualquier tipo de errores.

Una hora perdida

Mientras estaba haciendo un pequeño calendario que mostrara fechas con ligas a otras parte del sitio, pensé que la manera más fácil era evitar llamar a strototime:

strtotime("+1 days", $time)

y en su lugar utilizar directamente la suma de 86,400 segundos que es un día:

60 * 60 segundos = 3600 segundos (1 hora) * 24 = 86400

El calendario no tenía ningún problema hasta que noté que tenía repetido dos veces el día 1º de noviembre, los tiempos eran los siguientes (tiempo Unix):

1257051600: 2009-11-01 00:00:00
1257138000: 2009-11-01 23:00:00

Si hacen la suma, observarán que se sumó 86,400 segundos a una fecha y otra; pero el tiempo regresado por PHP (de hecho por el sistema Linux) es de 23 horas y no 24 horas.

Hice algunas pruebas en otros servidores y solo uno imprimió la fecha que esperaba (2009-11-02 00:00:00), la primera idea (por el cansancio) fue que era un error de PHP/Linux. Buscando un posible error, encontré algunas preguntas que como yo habían tenido un problema similar, pero mencionaban algo de la zona horaria, eso me recordó que el 1º de Noviembre en México atrasamos el reloj una hora por lo que en realidad el cálculo era correcto desde la perspectiva de Linux ya que estaba olvidando esa “hora perdida” en mis cálculos, el servidor que regresó bien la fecha estaba en otra zona horaria por lo que no compartían nuestro cambio de horario.

El tiempo Unix correcto es: 1257141600 no es otra cosa que es el día (86400) más la hora “perdida” (3600), este tiempo lo obtuve llamando a strtotime en PHP en lugar de calcularlo por mi cuenta.

Moraleja: Manipular el tiempo Unix de forma directa agregando valores puede ser muy rápido y simplifica la implementación, pero si quieres hacer algo que no tenga errores utiliza siempre las funciones de tiempo de PHP/Linux.

Tip Rápido: Completación de código en Zend Studio

Uso mucho el Zend Studio, aunque la primera versión fue desastrosa a partir de la 6.0.1 se han esmerado muchísimo; lo único malo que me he topado es que al hacer un proyecto Zend Framework se vuelve terriblemente lento y que tiene un poco de código extra poco necesario en su plantilla; aunque la ventaja es que ya trae ejemplos de Unit Test Case para los modelos (falta probarlos a detalle =P)

Usualmente al construir mis clases no pongo mucha documentación por función, por tanto Zend Studio no puede “determinar” qué tipo de variable regresa esa función. 

Para facilitar esto, lo que hago es simplemente agregar este pedazo de código:

    /**
     * @return Zend_Db_Adapter_Abstract
     */
	public static function getDB() {
             /* ... aqui va el código */
         }
 

Básicamente es el snippet de código para documentación pero usando solamente el tipo de objeto que regresa la función.
Con esto cuando utilices la función, Zend Studio podrá fácilmente mostrarte las propiedades y métodos del objeto.

TinyURL API – Snippet

Aqui hay un snippet (pedazo de código) para utilizar el API de TinyURL (TinyAPI ;) ), básicamente es llamar a la siguiente dirección:


http://tinyurl.com/api-create.php?url= 

y agregar al final la URL que queremos “achicar” con TinyURL, la página regresará como único contenido la URL nueva, por ejemplo:
http://tinyurl.com/api-create.php?url=http://vida.danguer.com

Da como resultado:
http://tinyurl.com/dfz9c3

El snippet para PHP es el siguiente: 

function get_tinyurl($url) {
     $url = "http://tinyurl.com/api-create.php?url=".urlencode($url);
     return trim(file_get_contents($url));
}

 

Demasiado facil, no? solamente hay que invocarlo como:

get_tinyurl('http://vida.danguer.com')

y obtendremos la nueva URL.

Obtener video FLV de YouTube

Actualización

Debido a que este código no funciona, el nuevo código (aunque el formato del video ya no es FLV, sino WEBM) puede consultarse aquí: http://vida.danguer.com/2011/10/20/obtener-video-webm-de-youtube-antes-flv-en-php/

——————————————————————–

Esta es la nueva manera de obtener el video de YouTube, los parámetros para get_video.php quedan iguales (video_id y t), pero t cambia y ahora no se obtiene al cargar el video, se tiene que llamar a otra URL para obtener esa información.

El método es muy fácil, los pasos son:

  1. Llamar a: 
    http://www.youtube.com/get_video_info.php?video_id=VIDEOID
    Donde VIDEOID es el id del video que vamos a obtener el archivo FLV (las ligas de YouTube para ver el video son como: http://www.youtube.com/v/VIDEOID)
    Tomaremos por ejemplo este:
    http://www.youtube.com/get_video_info.php?video_id=HDi9OeJqwG4 
  2. El resultado será en texto y tendrá muchas variables como:
    status=ok&title=Rob+Dougan+-+Furious+Angels&muted=0&avg_rating=4.8496592845&creator=eitch&length_seconds=237&vq=None&fmt_map=&token=OEgsToPDskKxBcum6P4BEStM-z3qkYZW&thumbnail_url=http%3A%2F%2Fi1.ytimg.com%2Fvi%2FHDi9OeJqwG4%2Fdefault.jpg&allow_ratings=True&plid=AARgErHUJPLX2RPWAAAAoABoIAE&track_embed=1
  3. De aqui solamente necesitamos obtener el parámetro token y pasarlo al ya conocido get_video.php:
    http://www.youtube.com/get_video.php?video_id=HDi9OeJqwG4&t=OEgsToPDskKxBcum6P4BEStM-z3qkYZW

Con esto ya obtendremos el FLV deseado, en PHP la rutina sería la siguiente:

<?php
//video id por defecto
$video_id = 'HDi9OeJqwG4';

if (isset($_REQUEST['video_id']))
        $video_id = trim($_REQUEST['video_id']);

//obtener informacion
$url_info = 'http://www.youtube.com/get_video_info.php?video_id='.$video_id;
$info = file_get_contents($url_info);

$vars = array();
parse_str($info, $vars);

$url_flv = 'http://www.youtube.com/get_video.php?video_id='.$video_id.'&t='.$var
s['token'];

print 'Archivo FLV: '.$url_flv;
?>

Con esto obtenemos la URL del archivo FLV.

Es importante notar que en Flex/Flash no se podrá cargar diréctamente get_video_info.php por la política del crossdomain de YouTube así que hay que utilizar un proxy.

El demo lo pueden probar desde:
http://demo.livesourcing.com/blog_vida/articulos/DanguerArticle_Youtube/?video_id=VIDEOID
Donde VIDEOID es el id del video de YouTube de su preferencia =)

El código fuente lo pueden descargar de:
https://github.com/danguer/blog-examples/blob/master/php/youtube/get-flv.php
 

 

 

 

Tips de PHP: Funciones para el manejo de Nombres de Archivo

Hay tres funciones en PHP que ayudan mucho para programar, las tres tienen que ver con el manejo de nombres de archivo (no con el archivo en si), estas son:

basename($cadena[, $sufijo])
Regresa el nombre del archivo o directorio, por ejemplo:

<?php
basename('/var/www/index.html'); //regresa 'index.html'
basename('/var/www'); //regresa 'www'
?>

Adicionalmente si se le pasa un segundo parámetro se removerá el sufijo si es que existe por ejemplo:

<?php
basename('/var/www/index.html', '.html'); //regresa 'index'
?>

dirname($cadena)
Regresa el nombre del directorio padre del archivo o directorio, es decir regresa lo opuesto a basename

<?php
dirname('/var/www/index.html'); //regresa '/var/www'
dirname('/var/www'); //regresa '/var'
?>

Debe notarse que no regresará el último separador (/)

pathinfo($cadena[, $opciones])
Regresa información del archivo, si no se le pasan opciones regresa un arreglo con las siguientes llaves:
dirname: nombre del directorio, es la misma salida de dirname($cadena)
basename: nombre del archivo, es la misma salida de basename($cadena)
extension: Si la extensión existe estará presente y será la extensión del archivo (cadena a partir del último .)
filename: nombre del archivo sin extension

Por ejemplo:

<?php

pathinfo('/var/www/index.html');
/*
regresa:
Array
(
    [dirname] => /var/www
    [basename] => index.html
    [extension] => html
    [filename] => index
)
*/

pathinfo('/var/www/index');
/*
regresa:
Array
(
    [dirname] => /var/www
    [basename] => index
    [filename] => index
)
*/

pathinfo('/var/www/index.inc.php');
/*
regresa:
Array
(
    [dirname] => /var/www
    [basename] => index.inc.php
    [extension] => php
    [filename] => index.inc
)
*/

pathinfo('/var/www');
/*
regresa:
Array
(
    [dirname] => /var
    [basename] => www
    [filename] => www
)
*/

?>

Adicionalmente se puede pasar una constante como parámetro para especificar unicamente un valor, en este caso la función regresa una cadena solamente; las constantes posibles son:

  • PATHINFO_DIRNAME
  • PATHINFO_BASENAME
  • PATHINFO_EXTENSION
  • PATHINFO_FILENAME

De los cuales, solamente son útiles: PATHINFO_EXTENSION y PATHINFO_FILENAME ya que PATHINFO_DIRNAME es equivalente a dirname y PATHINFO_BASENAME es equivalente a basename

PATHINFO_FILENAME evitar hacer dos llamadas (una a filepath para saber la extensión y la siguiente a basename con la extensión como sufijo para saber el nombre del archivo)

Ejemplo:

<?php

pathinfo('/var/www/index.html', PATHINFO_EXTENSION); //regresa 'html'
pathinfo('/var/www/index.html', PATHINFO_FILENAME); //regresa 'index'
?>