chester's blog

technology, travel, comics, books, math, web, software and random thoughts

SlideMeme

| Comments

Bani and ChesterBR @ Yahoo! Open Hack Day Brasil 2010O SlideMeme foi hack que eu e a Bani apresentamos no Yahoo! Open Hack Day Brasil 2010. O objetivo dele é oferecer uma forma visualmente agradável e conveniente para postar apresentações do SlideShare no Yahoo! Meme, e claro que ficamos muito contentes por ele ter sido premiado como melhor hack na categoria Meme!

UPDATE:Um dos grandes problemas que o hack teve foi o fato de ser baseado num componente ActiveX, o que exigia um servidor Windows – para o qual nós não tínhamos recursos (ou vontade) para manter no ar. Em Jun/2010 eu reescrevi a parte da conversão usando swftools e dei uma condensada na coisa toda, centralizando o back-end em um único script. O post foi mantido, pois os detalhes de funcionamento ainda valem, mas a última versão de tudo está no github para quem quiser.

Mas eu falo do evento em outro post – esse aqui é para documentar o processo e publicar o código do hack (até o final vai ficar claro que ele é curto e grosso demais para merecer um github/sourceforge, e que o maior valor está em detalhar o que foi feito e como). UPDATE: com o fim do experimento, o código fonte está disponível no github, incluindo os arquivos do site.

Para saber mais, veja a apresentação no SlideShare, assista à Bani explicando e demonstrando a coisa toda em dois minutos cravados no relógio – uma proeza digna de nota), e, claro, continue lendo este post.

Tivemos a idéia olhando para os Memes mais populares: ali predominam as fotos e ilustrações “fofinhas” no mesmo estilo daquelas que recebemos por email das tias e mães, invariavelmente no formato do Microsoft PowerPoint (.ppt). Como o SlideShare é o site-referência para a publicação de apresentações neste e em outros formatos (à semelhança do que o YouTube se tornou para arquivos de vídeo), foi natural evoluir o conceito nessa direção.

Claro que as pessoas podem simplesmente copiar e colar o endereço do SlideShare num post, mas o Meme funciona melhor com mídias visuais (a maior prova disso é a quase ausência de MP3). Na nossa cabeça, o leitor teria que ver algo que o estimulasse a clicar, à semelhança do que ocorre com vídeos e fotos maiores. Além disso, podíamos usar a idéia de bookmarklet (que deu certo no MemeThis) para simplificar o processo de copiar e colar.

Após tentativas infrutíferas de embutir o player do SlideShare em um post de texto, decidimos converter as apresentações para algum formato que o Meme suportasse. Ficamos entre o vídeo (que permitiria um controle limitado de navegação, mas ficaria pesado) e o GIF animado (que é muito eficiente para dar uma amostra de alguns slides). Optamos pelo último, e não nos arrependemos.

O próximo passo seria analisar o formato das apresentações do SlideShare. Além de uma tabela YQL, o serviço possui uma API que revela algumas informações sobre as apresentações, mas não permite recuperar slides individuais. Pensamos em colocar um sniffer/proxy no meio do caminho e ver como o player recupera cada slide, mas Hasin Hayder teve a idéia primeiro e já deixou um script PHP prontinho, que gerava a URL de cada slide.

Nossa surpresa foi perceber que os slides não são arquivos de imagem: cada um deles é um arquivinho Flash (.swf), que o player do SlideShare embute. Converter isso para um GIF é bem mais complicado do que pode parecer. A maior parte das soluções para isso depende de um browser e/ou exige uma sessão X rodando – o que tornaria difícil escalar (e até mesmo hospedar de forma barata).

Achamos um software que conversava direto com o Flash para fazer isso, o SWF To Image ActiveX Freeware Library, cujo nome já ilustra as duas coisas que não nos agradam nele: não é open source e exige um servidor Windows. Entretanto, ele funciona muito bem, e entre os amigos e a cloud, não é impossível arrumar um servidor pra fazer o hack funcionar, então mandamos ver. Com um ASP minimalista disponibilizamos sua funcionalidade num serviço web, que recebe a URL de um .swf e devolve o primeiro (único no nosso caso) slide em .gif:

    strFileURL = Request.QueryString("url")
    if Request.ServerVariables("REMOTE_ADDR")<>"ip.of.the.php.server" Then
        Response.Write("Invalid Caller")
        Response.End
    End If
 
    Set SWFToImage = CreateObject("SWFToImage.SWFToImageObject")
    SWFToImage.InitLibrary "demo", "demo"
    SWFToImage.InputSWFFileName = strFileUrl
    SWFToImage.FrameIndex=
    SWFToImage.ImageOutputType = 2 ' (GIF)
    SWFToImage.Execute
 
    Response.ContentType = "image/gif"
    Response.BinaryWrite(SWFToImage.BinaryImage)
    Set SWFToImage = Nothing

Fazendo dessa forma, essa parte fica isolada para o dia em que conseguirmos outra solução (o Gnash é bastante promissor – a versão dev já tem a opção de screenshot). Com isso, restou a tarefa de juntar os slides usando o ImageMagick – optamos por fazer isso no mesmo PHP que identifica a URL de cada um deles e chama o serviço acima para converetr um a um (e manter esse PHP no mesmo host, para otimizar):

    $CONVERT = "
";
    $URL_PREFIX = "http://img.slide.memethis.com/";
    $ASP_HOST = "";
 
    $slideshowUrl = $_REQUEST["url"];
    if (strpos($slideshowUrl, "http://www.slideshare.net/")!==) {
        die("Invalid parameter");
    }
    $slideshowPageContent = file_get_contents($slideshowUrl);
    $pattern = "~doc=([\w-]+)~";
    preg_match($pattern,$slideshowPageContent,$matches);
    $xmlurl = "http://s3.amazonaws.com/slideshare/{$matches[1]}.xml";
    preg_match('/\(.*)\<\/title\>/',$slideshowPageContent,$matches);
    $title = $matches[1];
 
    $sxml = simplexml_load_file($xmlurl);
 
    $tempprefix = "img/img".uniqid();
    $i = 1;
    foreach ($sxml-&gt;Slide as $slide) {
        $filename = $tempprefix . $i . ".gif";
        $ch = curl_init($ASP_HOST . "swf2gif.asp?url=" . $slide['Src']);
        $fp = fopen($filename, "w");
        curl_setopt($ch, CURLOPT_FILE, $fp);
        curl_setopt($ch, CURLOPT_HEADER, );
        curl_exec($ch);
        curl_close($ch);
        fclose($fp);
        $i++;
        if ($i&gt;5) break;
    }
 
    $cmd = $CONVERT." -delay 200 -loop 0 ".$tempprefix."*.gif img/end.gif ".$tempprefix.".gif";
    $WshShell = new COM("WScript.Shell");
    $output = $WshShell-&gt;Exec($cmd)-&gt;StdOut-&gt;ReadAll;
 
    for($i=1; $i&lt;=5; $i++) {
        unlink($tempprefix . $i . ".gif");
    }
 
    echo $_REQUEST["callback"].'({"gif":"'.$URL_PREFIX.$tempprefix.'.gif","title":"'.$title.'","url":"'.$slideshowUrl.'"})';

Dessa vez (e ao contrário de como fizemos o MemeThis) não usamos oAuth ou YQL para postar – queríamos algo mais dinâmico, e a API semi-oficial de pré-preenchimento de formulários do Meme veio a calhar. A bookmarklet simplesmente injeta na página o código abaixo, que:

  • Escurece a tela;
  • Aciona o PHP (note que ele retorna JSON[P], justamente pra isso);
  • Posta o resultado no Meme.

(tudo isso é apoiado em outra malandragem: aproveitamos o JQuery do próprio SlideShare, tomando o cuidado de só referenciar quando temos certeza que estamos no site certo)

    var SLIDEMEME_PATH = "http://slide.memethis.com/";
 
    function slideMeme() {
        if (location.href.indexOf("http://www.slideshare.net/")!=) {
            alert("This only works with SlideShare");
            return false;
        }
        if (!$("#svPlayerId").length) {
            alert("You need to be on a presentation to use this");
            return false;
        }
 
        $('
 
 
<div id="darkBackgroundLayer" class="darkenBackground"><img src="'+SLIDEMEME_PATH+'js/ajax-loader.gif" alt="" /></div>
 
 
').appendTo('body');
 
        $.ajax({
            url: 'http://img.slide.memethis.com/slidememe.php?callback=?',
            data: {
                url: location.href
            },
            dataType: "jsonp",
            success: function (data, status) {
                location.href = 'http://meme.yahoo.com/dashboard/?photo='+data.gif+'&caption=<a href="'+data.url+'">'+data.title+'</a>';
            },
            error: function (xOptions, textStatus) {
                $("#darkBackgroundLayer").hide();
                alert("Error! :-(");
            }
        });
 
    }
    slideMeme();

Sim, o código acima pode e deve ser bem melhorado. Por exmeplo, o PHP 5 suporta objetos COM de forma muito natural, dispensando o ASP só para isso. E também poderíamos ter experimentado as bibliotecas do ImageMagick para PHP (ao invés de chamar o convert “na veia”).

Acima de tudo, todos os fontes deveriam ter um tratamento de erros e edge cases mais apropriado, e provavelmente menos redundância – mas nada disso cabe na realidade de um hack de 24h (que nós começamos um ou dois dias antes – dentro das regras – mas na soma final levou mais ou menos esse tempo mesmo).

Ah, teve um sub-hack extra: depois da correria (mas ainda no evento) eu olhei com mais carinho o PHP, percebendo que o processo de separar os slides era baixar um arquivo, aplicar uma expressão regular nele e iterar. Aí resolvi criar uma tabela YQL que faz isso. Ainda precisa ser melhorada (ex.: colocando os slides em itens-raiz para facilitar cruzamentos) mas já dá pra brincar com ela no YQL Console.

Ufa, foi um fim-de-semana movimentado… :–)

UPDATE: O Gleicon Moraes me passou a dica de que o swfrender (componente do swftools) consegue fazer a conversão. Eu cheguei a considerar ele na época, mas a documentação dá a entender que ele só funcionaria com arquivos previamente criados pelo swftools (o que pode ou não ser o caso dos slides do SlideShare). O fato é: funcionou em um ou dois testes preliminares, e pode ser a chave para migrar o hack para usar ferramentas livres e hospedagem mais acessível. Assim que eu tiver tempo vou olhar isso com carinho.

Comments