<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-2910574553699562670</id><updated>2011-04-21T21:37:37.334+01:00</updated><category term='images'/><category term='filosofia'/><category term='plugin'/><category term='hacks'/><category term='dvd'/><category term='rails'/><category term='optimize'/><title type='text'>La letra cero</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://letra0.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2910574553699562670/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://letra0.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>setepo</name><uri>http://www.blogger.com/profile/11694543815191404747</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>6</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-2910574553699562670.post-2865141469878330760</id><published>2007-11-08T00:14:00.000Z</published><updated>2007-11-08T00:46:28.031Z</updated><title type='text'>Enumerable#iterate</title><content type='html'>&lt;p&gt;¿Quién no se ha encontrado alguna vez escribiendo un bucle donde necesita saber cuándo está en el último elemento? ¿O cuál es el próximo elemento?&lt;/p&gt;

&lt;pre&gt;
  coleccion.each {|item|
     if estoy_en_el_ultimo?
       ....
&lt;/pre&gt;

&lt;p&gt;La solución típica en este caso sería usar &lt;code&gt;each_with_index&lt;/code&gt; y dentro del bloque &lt;code&gt;coleccion.size == index + 1&lt;/code&gt;, pero, siendo Ruby como es, molaría algo más &amp;ldquo;objetista&amp;rdquo;&lt;/p&gt;

&lt;p&gt;Seguramente habrán muchas implementaciones de algo así, pero me apetecía experimentar con una, así que antes de buscar le di un poco de caña al &lt;code&gt;Enumerable&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Partiendo de que en Ruby se puede modificar cualquier &lt;i&gt;cosa&lt;/i&gt; (entendiendo por &lt;i&gt;cosa&lt;/i&gt;... lo que sea), bastaría con añadir un método &lt;code&gt;iterate&lt;/code&gt; al módulo &lt;code&gt;Enumerable&lt;/code&gt;, de manera que todos los objetos que actúan como secuencias se beneficiarían del método&lt;/p&gt;

&lt;p&gt;Por ejemplo, podríamos tener&lt;/p&gt;

&lt;pre class="codigo_ruby"&gt;

output = &lt;span class="Special"&gt;''&lt;/span&gt;
(&lt;span class="Constant"&gt;0&lt;/span&gt;..&lt;span class="Constant"&gt;10&lt;/span&gt;).map { (rand * &lt;span class="Constant"&gt;10&lt;/span&gt;).to_i }.iterate {|&lt;span class="Identifier"&gt;iterator&lt;/span&gt;|
    output &amp;lt;&amp;lt; &lt;span class="Special"&gt;&amp;quot;&lt;/span&gt;&lt;span class="Constant"&gt;&amp;lt;b&amp;gt;&lt;/span&gt;&lt;span class="Special"&gt;&amp;quot;&lt;/span&gt; &lt;span class="Statement"&gt;if&lt;/span&gt; iterator.first? || iterator.last?
    output &amp;lt;&amp;lt; iterator.object.to_s

    &lt;span class="Statement"&gt;if&lt;/span&gt; iterator.object % &lt;span class="Constant"&gt;2&lt;/span&gt; == &lt;span class="Constant"&gt;0&lt;/span&gt;
        output &amp;lt;&amp;lt; &lt;span class="Special"&gt;&amp;quot;&lt;/span&gt;&lt;span class="Constant"&gt; &lt;/span&gt;&lt;span class="Special"&gt;&amp;quot;&lt;/span&gt; &amp;lt;&amp;lt; iterator.next.to_s
        iterator.skip_next!
    &lt;span class="Statement"&gt;end&lt;/span&gt;

    output &amp;lt;&amp;lt; &lt;span class="Special"&gt;&amp;quot;&lt;/span&gt;&lt;span class="Constant"&gt;&amp;lt;/b&amp;gt;&lt;/span&gt;&lt;span class="Special"&gt;&amp;quot;&lt;/span&gt; &lt;span class="Statement"&gt;if&lt;/span&gt; iterator.first? || iterator.last?

    output &amp;lt;&amp;lt; &lt;span class="Special"&gt;&amp;quot;&lt;/span&gt;&lt;span class="Constant"&gt; &amp;lt;br /&amp;gt;&lt;/span&gt;&lt;span class="Special"&gt;\n&lt;/span&gt;&lt;span class="Special"&gt;&amp;quot;&lt;/span&gt; &lt;span class="Statement"&gt;unless&lt;/span&gt; iterator.last?
}

puts output


&lt;/pre&gt;

&lt;p&gt;Que nos daría una salida como&lt;/p&gt;

&lt;pre&gt;
&amp;lt;b&amp;gt;6 3&amp;lt;/b&amp;gt; &amp;lt;br /&amp;gt;
6 2 &amp;lt;br /&amp;gt;
7 &amp;lt;br /&amp;gt;
3 &amp;lt;br /&amp;gt;
2 1 &amp;lt;br /&amp;gt;
8 3 &amp;lt;br /&amp;gt;
&amp;lt;b&amp;gt;5&amp;lt;/b&amp;gt;
&lt;/pre&gt;

&lt;p&gt;O también (un &lt;i&gt;poco&lt;/i&gt; más rebuscado)&lt;/p&gt;

&lt;pre class="codigo_ruby"&gt;

[&lt;span class="Constant"&gt;1&lt;/span&gt;, &lt;span class="Special"&gt;&amp;quot;&lt;/span&gt;&lt;span class="Constant"&gt;uno&lt;/span&gt;&lt;span class="Special"&gt;&amp;quot;&lt;/span&gt;, &lt;span class="Constant"&gt;2&lt;/span&gt;, &lt;span class="Special"&gt;&amp;quot;&lt;/span&gt;&lt;span class="Constant"&gt;dos&lt;/span&gt;&lt;span class="Special"&gt;&amp;quot;&lt;/span&gt;, &lt;span class="Special"&gt;&amp;quot;&lt;/span&gt;&lt;span class="Constant"&gt;tres&lt;/span&gt;&lt;span class="Special"&gt;&amp;quot;&lt;/span&gt;, &lt;span class="Constant"&gt;3&lt;/span&gt;, &lt;span class="Special"&gt;&amp;quot;&lt;/span&gt;&lt;span class="Constant"&gt;cuatro&lt;/span&gt;&lt;span class="Special"&gt;&amp;quot;&lt;/span&gt;, &lt;span class="Special"&gt;&amp;quot;&lt;/span&gt;&lt;span class="Constant"&gt;cinco&lt;/span&gt;&lt;span class="Special"&gt;&amp;quot;&lt;/span&gt;, &lt;span class="Special"&gt;&amp;quot;&lt;/span&gt;&lt;span class="Constant"&gt;seis&lt;/span&gt;&lt;span class="Special"&gt;&amp;quot;&lt;/span&gt;, &lt;span class="Constant"&gt;0&lt;/span&gt;].iterate {|&lt;span class="Identifier"&gt;i&lt;/span&gt;|
    puts &lt;span class="Special"&gt;&amp;quot;&lt;/span&gt;&lt;span class="Constant"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;&lt;span class="Special"&gt;&amp;quot;&lt;/span&gt; &lt;span class="Statement"&gt;if&lt;/span&gt; i.first?
    i.skip_next! i.object
    i.object.times {|&lt;span class="Identifier"&gt;n&lt;/span&gt;| print i.next(n + &lt;span class="Constant"&gt;1&lt;/span&gt;) + &lt;span class="Special"&gt;&amp;quot;&lt;/span&gt;&lt;span class="Constant"&gt; &lt;/span&gt;&lt;span class="Special"&gt;&amp;quot;&lt;/span&gt; }
    puts i.last? ? &lt;span class="Special"&gt;&amp;quot;&lt;/span&gt;&lt;span class="Constant"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;span class="Special"&gt;&amp;quot;&lt;/span&gt; : &lt;span class="Special"&gt;&amp;quot;&lt;/span&gt;&lt;span class="Constant"&gt;&amp;lt;br /&amp;gt;&lt;/span&gt;&lt;span class="Special"&gt;&amp;quot;&lt;/span&gt;
}


&lt;/pre&gt;

&lt;p&gt;Que nos daría&lt;/p&gt;

&lt;pre&gt;
&amp;lt;div&amp;gt;
uno &amp;lt;br /&amp;gt;
dos tres &amp;lt;br /&amp;gt;
cuatro cinco seis &amp;lt;br /&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/pre&gt;

&lt;p&gt;¿Y cómo conseguir todo eso?&lt;/p&gt;

&lt;pre class="codigo_ruby"&gt;

&lt;span class="PreProc"&gt;module &lt;/span&gt;&lt;span class="Type"&gt;Enumerable&lt;/span&gt;

    &lt;span class="PreProc"&gt;class &lt;/span&gt;&lt;span class="Type"&gt;Iterator&lt;/span&gt;
        &lt;span class="Statement"&gt;attr_accessor&lt;/span&gt; &lt;span class="Identifier"&gt;:object&lt;/span&gt;
        &lt;span class="Statement"&gt;attr_accessor&lt;/span&gt; &lt;span class="Identifier"&gt;:index&lt;/span&gt;
        &lt;span class="Statement"&gt;attr_reader&lt;/span&gt; &lt;span class="Identifier"&gt;:parent&lt;/span&gt;

        &lt;span class="PreProc"&gt;def &lt;/span&gt;&lt;span class="Identifier"&gt;initialize&lt;/span&gt;(parent)
            &lt;span class="Identifier"&gt;@parent&lt;/span&gt; = parent
            &lt;span class="Identifier"&gt;@skip_items&lt;/span&gt; = &lt;span class="Constant"&gt;0&lt;/span&gt;
        &lt;span class="PreProc"&gt;end&lt;/span&gt;

        &lt;span class="PreProc"&gt;def &lt;/span&gt;&lt;span class="Identifier"&gt;last?&lt;/span&gt;
            index == parent.size - &lt;span class="Constant"&gt;1&lt;/span&gt;
        &lt;span class="PreProc"&gt;end&lt;/span&gt;

        &lt;span class="PreProc"&gt;def &lt;/span&gt;&lt;span class="Identifier"&gt;first?&lt;/span&gt;
            index == &lt;span class="Constant"&gt;0&lt;/span&gt;
        &lt;span class="PreProc"&gt;end&lt;/span&gt;

        &lt;span class="PreProc"&gt;def &lt;/span&gt;&lt;span class="Identifier"&gt;next&lt;/span&gt;(offset = &lt;span class="Constant"&gt;1&lt;/span&gt;)
            (offset + index &amp;gt;= parent.size) ? &lt;span class="Constant"&gt;nil&lt;/span&gt; : parent[index + offset]
        &lt;span class="PreProc"&gt;end&lt;/span&gt;

        &lt;span class="PreProc"&gt;def &lt;/span&gt;&lt;span class="Identifier"&gt;previous&lt;/span&gt;(offset = &lt;span class="Constant"&gt;1&lt;/span&gt;)
            (index - offset &amp;lt; &lt;span class="Constant"&gt;0&lt;/span&gt;) ? &lt;span class="Constant"&gt;nil&lt;/span&gt; : parent[index - offset]
        &lt;span class="PreProc"&gt;end&lt;/span&gt;

        &lt;span class="PreProc"&gt;def &lt;/span&gt;&lt;span class="Identifier"&gt;skip_next!&lt;/span&gt;(items = &lt;span class="Constant"&gt;1&lt;/span&gt;)
            &lt;span class="Identifier"&gt;@skip_items&lt;/span&gt; += items.to_i
        &lt;span class="PreProc"&gt;end&lt;/span&gt;

        &lt;span class="PreProc"&gt;def &lt;/span&gt;&lt;span class="Identifier"&gt;skip?&lt;/span&gt;(keep_value = &lt;span class="Constant"&gt;false&lt;/span&gt;)
            &lt;span class="Statement"&gt;if&lt;/span&gt; &lt;span class="Identifier"&gt;@skip_items&lt;/span&gt; &amp;gt; &lt;span class="Constant"&gt;0&lt;/span&gt;
                &lt;span class="Identifier"&gt;@skip_items&lt;/span&gt; -= &lt;span class="Constant"&gt;1&lt;/span&gt; &lt;span class="Statement"&gt;unless&lt;/span&gt; keep_value
                &lt;span class="Constant"&gt;true&lt;/span&gt;
            &lt;span class="Statement"&gt;end&lt;/span&gt;
        &lt;span class="PreProc"&gt;end&lt;/span&gt;
    &lt;span class="PreProc"&gt;end&lt;/span&gt;

    &lt;span class="PreProc"&gt;def &lt;/span&gt;&lt;span class="Identifier"&gt;iterate&lt;/span&gt;(&amp;amp;block)
        iterator = &lt;span class="Identifier"&gt;Iterator&lt;/span&gt;.new &lt;span class="Constant"&gt;self&lt;/span&gt;
        each_with_index {|&lt;span class="Identifier"&gt;object, index&lt;/span&gt;|
            &lt;span class="Statement"&gt;next&lt;/span&gt; &lt;span class="Statement"&gt;if&lt;/span&gt; iterator.skip?
            iterator.object = object
            iterator.index = index
            block.call iterator
        }
    &lt;span class="PreProc"&gt;end&lt;/span&gt;

&lt;span class="PreProc"&gt;end&lt;/span&gt;



&lt;/pre&gt;

&lt;p&gt;Después de terminar este pequeño experimento hice una &lt;a href="http://www.google.com/search?q=enumerable%20iterate"&gt;búsqueda rápida&lt;/a&gt; a ver qué más había &lt;i&gt;por ahí&lt;/i&gt;. Lo único que vi, sin buscar demasiado, fue un &lt;a href="http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/46337"&gt;mensaje en la ruby-tak&lt;/a&gt; del 2002, donde hay una implementación parecida a ésta.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2910574553699562670-2865141469878330760?l=letra0.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://letra0.blogspot.com/feeds/2865141469878330760/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2910574553699562670&amp;postID=2865141469878330760' title='1 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2910574553699562670/posts/default/2865141469878330760'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2910574553699562670/posts/default/2865141469878330760'/><link rel='alternate' type='text/html' href='http://letra0.blogspot.com/2007/11/enumerableiterate.html' title='Enumerable#iterate'/><author><name>setepo</name><uri>http://www.blogger.com/profile/11694543815191404747</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2910574553699562670.post-6247325214448880041</id><published>2007-10-21T15:45:00.000+01:00</published><updated>2007-10-21T17:15:57.012+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='filosofia'/><title type='text'>12 de octubre: Día del comienzo de la masacre</title><content type='html'>&lt;p&gt;El pasado 12 de octubre (hace algo más de una semana ya) se celebró el &lt;a href="http://es.wikipedia.org/wiki/Fiesta_Nacional_de_Espa%C3%B1a"&gt;Día de la Hispanidad&lt;/a&gt;.
Nada en especial para mí. Sin embargo, después ver varios &lt;i&gt;posts&lt;/i&gt; en la blogosfera me decidí a escribir lo que me parece esa &lt;i&gt;fiesta&lt;/i&gt;.&lt;/p&gt;

&lt;p&gt;El «Día de la Hispanidad» celebra la conquista de América después de ser &lt;i&gt;encontrada por casualidad&lt;/i&gt; por Colón, el 12 de octubre de 1492. En el artículo de la Wikipedia sobre la
&lt;a href="http://es.wikipedia.org/wiki/Conquista_de_Am%C3%A9rica"&gt;conquista de América&lt;/a&gt; hay información muy interesante que, seguramente, no todos conocen. Destacaría,
por ejemplo, esta parte:&lt;/p&gt;

&lt;blockquote&gt;

    &lt;p&gt;La llegada de Cristóbal Colón a América abriría la Conquista del mal llamado Nuevo Mundo por parte de algunos imperios europeos, justificando la dominación en tres
    grandes principios:&lt;/p&gt;

    &lt;ol&gt;
        &lt;li&gt;Era "Tierra de Nadie" (res nullius), principio que supuso de hecho y derecho el desconocimiento de la presencia de sus habitantes como personas con
        derechos.&lt;/li&gt;
        &lt;li&gt;Eran "tierras para la cristianidad", principio que llevó a su vez a la decisión de &lt;b&gt;imponer el cristianismo a los habitantes de América, habilitando su
        exterminio en caso de negarse a aceptar esa religión&lt;/b&gt;. En ciertos casos los europeos sostuvieron que "los indios no tenían alma" negando la condición
        humana de los pueblos originarios.&lt;/li&gt;
        &lt;li&gt;Como América era "Tierra de Nadie", las civilizaciones que allí se habían desarrollado, &lt;b&gt;las riquezas acumuladas y naturales, así como el derecho a utilizar a
        los habitantes como mano de obra forzada, correspondía a quien se impusiera primero&lt;/b&gt;. La obsesión por el oro, simbolizada en la búsqueda de El Dorado, caracterizó
        la Conquista de América así como la migración de miles de tropas mercenarias.&lt;/li&gt;
    &lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;En otro artículo de la Wikipedia, &lt;a href="http://es.wikipedia.org/wiki/Impacto_demogr%C3%A1fico_de_la_llegada_de_los_europeos_a_Am%C3%A9rica"&gt;Catástrofe demográfica
    en América tras la llegada de los europeos&lt;/a&gt;, hablan acerca del extermio de los habitantes del continente &lt;u&gt;invadido&lt;/u&gt;. Destacaría algunos párrafos, como por ejemplo&lt;/p&gt;

&lt;blockquote&gt;
Las causas del colapso demográfico son ampliamente aceptadas entre historiadores y demógrafos: por una parte, las &lt;b&gt;enfermedades infecciosas portadas por los
europeos&lt;/b&gt;, para las cuales la población indígena americana no poseía defensas. Por otra parte, la &lt;b&gt;brutalidad de la conquista y las condiciones del régimen de
explotación&lt;/b&gt; a que fueron sujetos durante la mayor parte de la época colonial. Existen discrepancias acerca de en qué grado contribuyeron unas y otras a la debacle
demográfica, coincidiéndose en la realimentación entre ambas.
&lt;/blockquote&gt;

&lt;p&gt;Y también&lt;/p&gt;

&lt;blockquote&gt;
El investigador norteamericano H. F. Dobyns ha calculado que un 95% de la población total de América murió en los primeros 130 años después de la llegada de
Colón. Por su parte, Cook y Borah, de la Universidad de Berkeley, establecieron luego de décadas de investigación, que la población en México disminuyó de 25,2
millones en 1518 a 700 mil personas en 1623, menos del 3% de la población original
&lt;/blockquote&gt;

&lt;p&gt;El año pasado, el programa &lt;a href="http://es.wikipedia.org/wiki/CQC#Argentina"&gt;Caiga Quien Caiga en Argentina&lt;/a&gt; estuvo la celebración de Madrid de ese día. El
vídeo, sencillamente genial:&lt;/p&gt;

&lt;p style="text-align: center"&gt;
    &lt;object width="425" height="350"&gt;
        &lt;param name="movie" value="http://www.youtube.com/v/f300zp1YhDQ"&gt;&lt;/param&gt;
        &lt;param name="wmode" value="transparent"&gt;&lt;/param&gt;
        &lt;embed src="http://www.youtube.com/v/f300zp1YhDQ" type="application/x-shockwave-flash" wmode="transparent" width="425" height="350"&gt;&lt;/embed&gt;
    &lt;/object&gt;
&lt;/p&gt;

&lt;p&gt;Afortunadamente, la versión española de Caiga Quien Caiga ha vuelto a la carga, y este año tenemos un video tan genial como éste:&lt;/p&gt;

&lt;p style="text-align: center"&gt;
    &lt;object width="425" height="350"&gt;
        &lt;param name="movie" value="http://www.youtube.com/v/nvTOcQOlXLQ"&gt;&lt;/param&gt;
        &lt;param name="wmode" value="transparent"&gt;&lt;/param&gt;
        &lt;embed src="http://www.youtube.com/v/nvTOcQOlXLQ" type="application/x-shockwave-flash" wmode="transparent" width="425" height="350"&gt;&lt;/embed&gt;
    &lt;/object&gt;
&lt;/p&gt;

&lt;p&gt;Este vídeo es más claro aún. Muestra la clase de &lt;i&gt;españoles&lt;/i&gt; que tenemos en ésta, &amp;ldquo;&lt;i&gt;nuestra España&lt;/i&gt;&amp;rdquo;&lt;/p&gt;

&lt;p&gt;Días antes, &lt;a href="http://www.youtube.com/watch?v=zoMWRtECBLk"&gt;Rajoy mostraba su orgullo de ser español&lt;/a&gt;, y nos decía, más o menos, que hiciéramos lo
mismo. A mí, personalmente, me parece lamentable que alguien esté &amp;ldquo;orgulloso&amp;rdquo; de ser de un país, de una religión o lo que sea. Como decía Montesquieu, «&lt;i&gt;soy
necesariamente hombre mientras que no soy francés&lt;/i&gt; [ponga aquí nacionalidad favorita] &lt;i&gt;más que por casualidad&lt;/i&gt;». Me encantaría que esas personas que se
muestran tan orgullosas de ser españoles (empezando por Rajoy) explicaran exáctamente qué les hace sentir &lt;i&gt;tan&lt;/i&gt; orgullosos de haber nacido en España.&lt;/p&gt;
&lt;/p&gt;

&lt;p&gt;Yo, por el momento, secundo a &lt;a href="http://www.humorgraficojr.com/blog/2007/10/12/12-de-julio-no-hay-color/"&gt;JRMora&lt;/a&gt;.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2910574553699562670-6247325214448880041?l=letra0.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://letra0.blogspot.com/feeds/6247325214448880041/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2910574553699562670&amp;postID=6247325214448880041' title='1 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2910574553699562670/posts/default/6247325214448880041'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2910574553699562670/posts/default/6247325214448880041'/><link rel='alternate' type='text/html' href='http://letra0.blogspot.com/2007/10/12-de-octubre-da-del-comienzo-de-la.html' title='12 de octubre: Día del comienzo de la masacre'/><author><name>setepo</name><uri>http://www.blogger.com/profile/11694543815191404747</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2910574553699562670.post-4606424852373912541</id><published>2007-08-19T21:05:00.000+01:00</published><updated>2007-08-20T01:27:36.633+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='images'/><category scheme='http://www.blogger.com/atom/ns#' term='plugin'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='optimize'/><title type='text'>Optimización de la carga de imágenes pequeñas</title><content type='html'>&lt;h4&gt;El problema&lt;/h4&gt;

&lt;p&gt;El tiempo de carga de las páginas es algo muy importante para que los usuarios (nosotros mismos) se sientan cómodos usando las aplicaciones o navegando por la web. En algunas páginas como &lt;a href="http://jimmac.musichall.cz/openoffice-icons.php"&gt;http://jimmac.musichall.cz/openoffice-icons.php&lt;/a&gt; o &lt;a href="http://www.google.com/language_tools"&gt;http://www.google.com/language_tools&lt;/a&gt; hay muchísimos iconos. Cada uno de estos iconos está en un fichero separado, por lo que el navegador los tendrá que descargar uno a uno. Este problema también lo podremos observar en cualquier aplicación que tenga muchos iconos.&lt;/p&gt;

&lt;p&gt;Mirando &lt;a href="http://www.google.com/language_tools"&gt;http://www.google.com/language_tools&lt;/a&gt; con el &lt;a href="http://www.getfirebug.com/"&gt;Firebug&lt;/a&gt;, en la pestaña &lt;i&gt;Net&lt;/i&gt;, podemos ver algo como esto:&lt;/p&gt;

&lt;p align="center"&gt; &lt;a target="_blank" href="http://www.flickr.com/photos/setepo/1172091565/" title="Firebug: Net"&gt;&lt;img src="http://farm2.static.flickr.com/1014/1172091565_8adbb6b486_m.jpg" width="55" height="240" alt="Firebug: Net" /&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Las 158 imágenes de la página se van cargando una detrás de otra. Usando el &lt;a href="http://tools.pingdom.com/fpt/"&gt;&lt;i&gt;Full page test&lt;/i&gt; de Pingdom&lt;/a&gt; &lt;a href="http://tools.pingdom.com/fpt/?url=http://www.google.com/language_tools&amp;amp;treeview=0&amp;amp;column=objectID&amp;amp;order=1&amp;amp;type=0"&gt; vemos un resultado similar de la carga de la página&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Las 157 imágenes que corresponden a las banderas pesan (todas juntas) algo menos de 100&lt;a href="http://en.wikipedia.org/wiki/Kibibyte"&gt;KiB&lt;/a&gt;.  Con una conexión decente no debería llegar a los 2 segundos. Sin embargo está cargando casi 9 segundos. &lt;b&gt;¿Qué pasa?&lt;/b&gt;.&lt;/p&gt;

&lt;p&gt;Para empezar, tenemos las cabeceras HTTP. Para cada imagen se lanza una petición como ésta:&lt;/p&gt;

&lt;pre&gt;
GET /images/flags/XX_flag.gif HTTP/1.1
Host: www.google.com
User-Agent: Mozilla/5.0
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: es-es,es;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Cookie: &lt;b&gt;[Cookie de google. No se puede decir que sea corta...]&lt;/b&gt;
&lt;/pre&gt;

&lt;p&gt;El tamaño de la petición está por encima de 1KiB. Puede ser insignificante para una sola petición, pero no lo es para 157.  La cabecera de la respuesta es bastante más pequeña. Aparte del &lt;a href="http://en.wikipedia.org/wiki/Computational_overhead"&gt;&lt;i&gt;overhead&lt;/i&gt;&lt;/a&gt; que añaden las cabeceras hay que tener el de TCP/IP. El uso de &lt;a href="http://www.io.com/~maus/HttpKeepAlive.html"&gt;&lt;i&gt;keep alive&lt;/i&gt;&lt;/a&gt; mejora los tiempos, ya que no hace falta volver a abrir una nueva conexión, pero sigue siendo necesario enviar una petición para cada archivo.&lt;/p&gt;

&lt;h4&gt;La solución&lt;/h4&gt;

&lt;p&gt;La solución es simplemente combinar todas las imágenes en una sola. Con esto nos ahorramos las cabeceras necesarias para las peticiones HTTP. Además, al comprimir todas las imágenes juntas el fichero resultante tendrá un peso bastante menor.&lt;/p&gt;

&lt;p&gt;Esta técnica se ha comentado varias veces en la blogosfera. En &lt;a href="http://www.alistapart.com/"&gt;A List Apart&lt;/a&gt; hay un artículo titulado &lt;a href="http://www.alistapart.com/articles/sprites"&gt;CSS Sprites: Image Slicing&amp;#8217;s Kiss of Death&lt;/a&gt;. Incluso hay algunas implementaciones, como el &lt;a href="http://code.google.com/p/google-web-toolkit/wiki/ImageBundleDesign"&gt;ImageBundle&lt;/a&gt; de &lt;a href="http://code.google.com/p/google-web-toolkit/"&gt;GWT&lt;/a&gt; o &lt;a href="http://www.djangosnippets.org/snippets/279/"&gt;este &lt;i&gt;snippet&lt;/i&gt; para Django&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;No encontré nada para Rails, así que decidí implementar uno.&lt;/p&gt;

&lt;h4&gt;Funcionamiento&lt;/h4&gt;

&lt;p&gt;El funcionamiento es muy sencillo&lt;/p&gt;

&lt;ol&gt;
    &lt;li&gt;Se empaquetan las imágenes, y se guarda la posición de cada una dentro del paquete.&lt;/li&gt;
    &lt;li&gt;Se cambia el &lt;a href="http://api.rubyonrails.org/classes/ActionView/Helpers/AssetTagHelper.html#M000596"&gt;&lt;code&gt;image_tag&lt;/code&gt;&lt;/a&gt; para que pueda generar imágenes hacia paquete.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Para &lt;b&gt;generar el paquete&lt;/b&gt; hay que tener en cuenta algunos puntos:&lt;/p&gt;

&lt;ul&gt;
    &lt;li&gt;Primero, es preferible agrupar las imágenes según el formato. Es decir, las imágenes PNG en un paquete guardado como PNG. Las JPEG en otro como JPEG, etc. Cada imagen vendrá en un formato según las ventajas de éste (como que PNG no pierde calidad o que JPEG comprime más, por poner algunos ejemplos), así que al convertirlas a otro formato perderemos ventajas.&lt;/li&gt;
    &lt;li&gt;Segundo, sólo deberían empaquetarse imágenes pequeñas. Las que son muy grandes se suelen usar para fondos o algún logotipo, y el &lt;i&gt;overhead&lt;/i&gt; no suele ser un problema. No hay que olvidar que en las pequeñas el peso de las cabeceras puede llegar a ser mayor que la propia imagen.&lt;/li&gt;
    &lt;li&gt;Tercero, las animaciones (generalmente ficheros GIF con más de un fotograma) no pueden ser empaquetadas.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;El &lt;b&gt;&lt;code&gt;image_tag&lt;/code&gt;&lt;/b&gt; tiene que poder detectar si la imagen que se está pidiendo está empaquetada. De ser así, el &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; generado apuntará hacia el paquete.&lt;/p&gt;

&lt;h4&gt;Implementación&lt;/h4&gt;

&lt;p&gt;Para poder disponer de algo así fácilmente en cualquier aplicación lo más cómodo es tenerlo como un &lt;i&gt;plugin&lt;/i&gt;. El código lo subiré a
&lt;a href="http://rubyforge.org/"&gt;RubyForge&lt;/a&gt; desde que me acepten el proyecto (avisaré por aquí).&lt;/p&gt;

&lt;p&gt;El &lt;i&gt;plugin&lt;/i&gt; añade una tarea en el &lt;i&gt;rake&lt;/i&gt; para actualizar los paquetes generados. De esta manera, tan sólo necesitamos poner&lt;/p&gt;

&lt;pre&gt;
$ rake images:bundle:update
&lt;/pre&gt;

&lt;p&gt;Cuando la tarea empieza comprueba si existen los ficheros &lt;code&gt;config/rails-image-bundle.yml&lt;/code&gt; y &lt;code&gt;public/images/fulltransparent.gif&lt;/code&gt;.  Si no existe alguno de ellos los copia del directorio &lt;code&gt;templates&lt;/code&gt; que viene dentro del propio &lt;i&gt;plugin&lt;/i&gt;.  El primer fichero se usa para cambiar algunos parámetros del &lt;i&gt;plugin&lt;/i&gt;. El segundo lo veremos más abajo.&lt;/p&gt;

&lt;p&gt;Los parámetros que se pueden configurar del &lt;i&gt;plugin&lt;/i&gt; son&lt;/p&gt;

&lt;ul&gt;
    &lt;li&gt;&lt;code&gt;file-bundle&lt;/code&gt;, que indica la base del nombre donde se generarán los paquetes. Inicialmente tiene el valor &lt;code&gt;generated-image-bundle&lt;/code&gt;. De esta manera, los paquetes se generarán en &lt;code&gt;public/images/generated-image-bundle.png&lt;/code&gt; (o &lt;code&gt;.jpeg&lt;/code&gt;, &lt;code&gt;.gif&lt;/code&gt;, etc)&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;max-width&lt;/code&gt;, que indica el ancho máximo que puede tener una imagen para ser empaquetada.&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;max-height&lt;/code&gt;, lo mismo que &lt;code&gt;max-width&lt;/code&gt;, pero para el alto.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;En la mayoría de los casos no hará falta tocar el fichero.&lt;/p&gt;

&lt;p&gt;Para &lt;b&gt;generar los paquetes&lt;/b&gt; el &lt;i&gt;plugin&lt;/i&gt; buscará todos los ficheros por debajo de &lt;code&gt;public/images&lt;/code&gt; de la aplicación. Cada uno los cargará con &lt;a href="http://rmagick.rubyforge.org/"&gt;RMagick&lt;/a&gt; y comprobará si:&lt;/p&gt;

&lt;ul&gt;
    &lt;li&gt;Se cargó correctamente&lt;/li&gt;
    &lt;li&gt;Las dimensiones de la imagen no pasan de &lt;code&gt;max-width&lt;/code&gt; y &lt;code&gt;max-height&lt;/code&gt;&lt;/li&gt;
    &lt;li&gt;Sólo tiene un fotograma (no es una animación)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Si se cumplen todas las condiciones añadirá al paquete de su formato (&lt;code&gt;public/images/generated-image-bundle.{png,jpeg,...}&lt;/code&gt;) y guarda la información necesaria para &lt;i&gt;saber&lt;/i&gt; que el fichero está empaquetado. Para esto se necesita guardar:&lt;/p&gt;

&lt;ul&gt;
    &lt;li&gt;El nombre del fichero que se va a empaquetar (por ejemplo, &lt;code&gt;edit_button.png&lt;/code&gt;)&lt;/li&gt;
    &lt;li&gt;La posición (x,y) dentro del paquete&lt;/li&gt;
    &lt;li&gt;El nombre del paquete donde está&lt;/li&gt;
    &lt;li&gt;Las dimensiones de la imagen empaquetada&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Después de empaquetar todas las imágenes se vuelca el fichero con toda la información sobre los paquetes en &lt;code&gt;tmp/rails-image-bundle.marshal&lt;/code&gt;.  Puesto que no es necesario que este fichero sea legible para personas humanas (ni para las personas no humanas) se usa &lt;a href="http://www.ruby-doc.org/core/classes/Marshal.html"&gt;Marshal&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Con esto ya tenemos nuestros paquetes de imágenes generados. Ahora nos queda que sean accesibles al navegador. Cambiaremos el &lt;code&gt;image_tag&lt;/code&gt; para detectar cuándo queremos usar alguna imagen que está empaquetada.&lt;/p&gt;

&lt;p&gt;La técnica para poder utilizar los paquetes se basa en CSS. Consiste en:&lt;/p&gt;

&lt;ul&gt;
    &lt;li&gt;Generamos una etiqueta &lt;code&gt;img&lt;/code&gt; con una imagen completamente transparente (de ahí el &lt;code&gt;fulltransparent.gif&lt;/code&gt;)&lt;/li&gt;
    &lt;li&gt;Ponemos el tamaño exacto de la imagen que queremos mostrar. Con esto además evitamos el efecto (no muy agradable) de que el contenido de la página se mueva mientras se cargan las imágenes&lt;/li&gt;
    &lt;li&gt;Añadimos como fondo de la etiqueta la imagen del paquete, y lo &lt;a href="http://www.w3.org/TR/CSS21/colors.html#propdef-background-position"&gt;desplazamos&lt;/a&gt; para que sólo quede visible la parte que corresponde a la
    imagen que queremos ver,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Para que se hagan una idea, vean cómo quedó implementado el &lt;code&gt;image_tag&lt;/code&gt;&lt;/p&gt;

&lt;pre&gt;
module ImageBundle
    module ViewMethods

        def self.included(base)
            base.alias_method_chain :image_tag, :bundle
        end

        def image_tag_with_bundle(source, options = {})
            @__bundle_information ||= (Marshal.load(File.read(ImageBundle::INFO_FILE)) rescue nil) || {}
            if info = @__bundle_information[source.to_s]
                source = "fulltransparent.gif"
                options[:style] = "background: url(#{image_path info[:bundle]}) -#{info[:x]}px -#{info[:y]}px; #{options[:style]}"
                options[:width] = info[:width]
                options[:height] = info[:height]
            end

            image_tag_without_bundle(source, options)
        end

    end
end
&lt;/pre&gt;

&lt;h4&gt;Posibles mejoras&lt;/h4&gt;

&lt;p&gt;Hay muchas mejoras que se pueden hacer. La implementación actual tiene 142 líneas, escritas en algo más de hora y media. El objetivo principal (optimizar la carga de imágenes pequeñas) está conseguido. ¿Qué podemos hacer ahora?&lt;/p&gt;

&lt;p&gt;Una mejora interesante es poder restringir qué archivos son empaquetados. Ahora mismo se cogen todos los que soporta el &lt;i&gt;backend&lt;/i&gt; que use &lt;i&gt;RMagick&lt;/i&gt; (que será &lt;i&gt;ImageMagick&lt;/i&gt; o &lt;i&gt;GraphicsMagick&lt;/i&gt;) hasta cierto tamaño. En algún caso nos puede interesar que sólo se empaqueten ficheros PNG y GIF, o que no se empaqueten los que estén bajo &lt;code&gt;public/images/donotuse&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Otra posible mejora (aunque de mucha menos utilidad) es distribuir las imágenes para optimizar el espacio. Ahora mismo se ponen todas en vertical. Habría que hacer pruebas, pero en el caso más general no creo que haya diferencias significativas de tamaño.&lt;/p&gt;

&lt;p&gt;Seguro que hay muchas más mejoras, que se irán conociendo con el tiempo.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2910574553699562670-4606424852373912541?l=letra0.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://letra0.blogspot.com/feeds/4606424852373912541/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2910574553699562670&amp;postID=4606424852373912541' title='4 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2910574553699562670/posts/default/4606424852373912541'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2910574553699562670/posts/default/4606424852373912541'/><link rel='alternate' type='text/html' href='http://letra0.blogspot.com/2007/08/optimizacin-de-la-carga-de-imgenes.html' title='Optimización de la carga de imágenes pequeñas'/><author><name>setepo</name><uri>http://www.blogger.com/profile/11694543815191404747</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://farm2.static.flickr.com/1014/1172091565_8adbb6b486_t.jpg' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2910574553699562670.post-1328185999582368613</id><published>2007-07-04T09:21:00.000+01:00</published><updated>2007-07-04T09:26:37.284+01:00</updated><title type='text'>iPhone por aquí, iPhone por allá</title><content type='html'>&lt;p&gt;Estos días no han parado de salir noticias y posts en blogs sobre el iPhone. Como decía &lt;a href="http://mnm.uib.es/gallir/posts/2007/07/02/1119/"&gt;Ricardo Galli&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
Por último, ¿cómo la gente puede sacar tanto para escribir de &lt;b&gt;un teléfono&lt;/b&gt;? ¿cómo se puede estar tan contento de conseguir uno después de horas de largas colas? ¿cómo no se puede sentir un mínimo de pudor de hablar tanto y tanto sobre un cacharrito? Alucino.
&lt;/blockquote&gt;

&lt;p&gt;Pues... he aquí la noticia que más me ha gustado de todas &lt;tt&gt;:-P&lt;/tt&gt;: &lt;a href="http://apple.slashdot.org/article.pl?sid=07/07/03/1622212"&gt; iPhone Root Password Hacked in Three Days&lt;/a&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2910574553699562670-1328185999582368613?l=letra0.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://letra0.blogspot.com/feeds/1328185999582368613/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2910574553699562670&amp;postID=1328185999582368613' title='0 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2910574553699562670/posts/default/1328185999582368613'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2910574553699562670/posts/default/1328185999582368613'/><link rel='alternate' type='text/html' href='http://letra0.blogspot.com/2007/07/iphone-por-aqu-iphone-por-all.html' title='iPhone por aquí, iPhone por allá'/><author><name>setepo</name><uri>http://www.blogger.com/profile/11694543815191404747</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2910574553699562670.post-5165806313538347794</id><published>2007-06-27T16:59:00.000+01:00</published><updated>2007-06-27T17:00:21.540+01:00</updated><title type='text'>Lo veo y SÍ me lo creo...</title><content type='html'>&lt;a href="http://www.alasbarricadas.org/sgae/" alt="sgae"&gt;http://www.alasbarricadas.org/sgae/&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2910574553699562670-5165806313538347794?l=letra0.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://letra0.blogspot.com/feeds/5165806313538347794/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2910574553699562670&amp;postID=5165806313538347794' title='0 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2910574553699562670/posts/default/5165806313538347794'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2910574553699562670/posts/default/5165806313538347794'/><link rel='alternate' type='text/html' href='http://letra0.blogspot.com/2007/06/lo-veo-y-s-me-lo-creo.html' title='Lo veo y SÍ me lo creo...'/><author><name>setepo</name><uri>http://www.blogger.com/profile/11694543815191404747</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2910574553699562670.post-252463621633865789</id><published>2007-06-26T01:14:00.000+01:00</published><updated>2007-06-26T02:13:03.808+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='hacks'/><category scheme='http://www.blogger.com/atom/ns#' term='dvd'/><title type='text'>Viaje al interior de... un DVD</title><content type='html'>&lt;p&gt;Hoy me pasaron un DVD con un video. Al llegar a mis manos me comentaron que el DVD no estaba cerrado («sin finalizar», que supongo que será el término que usen los programas como Nero). En principio no debería haber ningún problema, ya que mi máquina es nueva, ya han pasado por aquí varios CDs sin cerrar, estoy con un 2.6.17 (el de Ubuntu Edgy... y ya... no es que esté &lt;i&gt;recién sacado del horno&lt;/i&gt;, pero tampoco se puede decir que sea viejo), etc etc&lt;/p&gt;

&lt;p&gt;Cuando el DVD entra en la máquina aparece un mensaje como que acabo de meter un disco virgen, y me pregunta si lo quiero desvirgar ¿?¿?. Antes de que cunda el caos, miro en el &lt;code&gt;syslog&lt;/code&gt; a ver qué se cuenta. Cada vez que entra el DVD aparece.&lt;/p&gt;

&lt;pre&gt;
...] cdrom: This disc doesn't have any tracks I recognize!
&lt;/pre&gt;

&lt;p&gt;Buscando por Google con ese mensaje no veo nada de utilidad, así que intento atacar el disco directamente. Aunque no tenía muchas esperanzas probé con un &lt;code&gt;dd&lt;/code&gt;, pero salía nada más empezar,  y en el &lt;code&gt;syslog&lt;/code&gt; podía ver&lt;/p&gt;

&lt;pre&gt;
...] end_request: I/O error, dev sr0, sector 0
...] printk: 17 messages suppressed.
...] Buffer I/O error on device sr0, logical block 0
...] end_request: I/O error, dev sr0, sector 0
&lt;/pre&gt;

&lt;p&gt;Ouch!&lt;/p&gt;

&lt;p&gt;Buscando herramientas relacionadas con los DVD (aún no he perdido la esperanza) veo un &lt;b&gt;dvd+rw-mediainfo&lt;/b&gt;, que forma parte del paquete &lt;code&gt;dvd+rw-tools&lt;/code&gt;. Al ejecutarlo sobre el DVD veo &lt;i&gt;cosas&lt;/i&gt; bastante prometedoras:&lt;/p&gt;

&lt;pre&gt;
&lt;b&gt;# dvd+rw-mediainfo /dev/dvd&lt;/b&gt;
INQUIRY:                [PHILIPS ][DVD+-RW SDVD8820][AD15]
GET [CURRENT] CONFIGURATION:
 Mounted Media:         11h, DVD-R Sequential
 Media ID:              CMC MAG. AE1
 Current Write Speed:   8.0x1385=11080KB/s
 Write Speed #0:        8.0x1385=11080KB/s
 Write Speed #1:        6.0x1385=8310KB/s
 Write Speed #2:        4.0x1385=5540KB/s
 Write Speed #3:        2.0x1385=2770KB/s
 Speed Descriptor#0:    00/2297887 R@8.0x1385=11080KB/s W@8.0x1385=11080KB/s
 Speed Descriptor#1:    00/2297887 R@8.0x1385=11080KB/s W@6.0x1385=8310KB/s
 Speed Descriptor#2:    00/2297887 R@8.0x1385=11080KB/s W@4.0x1385=5540KB/s
 Speed Descriptor#3:    00/2297887 R@8.0x1385=11080KB/s W@2.0x1385=2770KB/s
READ DVD STRUCTURE[#10h]:
 Media Book Type:       25h, DVD-R book [revision 5]
 Legacy lead-out at:    2298496*2KB=4707319808
READ DVD STRUCTURE[#0h]:
 Media Book Type:       25h, DVD-R book [revision 5]
 Last border-out at:    0*2KB=0
READ DISC INFORMATION:
 Disc status:           appendable
 &lt;b&gt;Number of Sessions:    1&lt;/b&gt;
 State of Last Session: incomplete
 "Next" Track:          1
 Number of Tracks:      4
READ TRACK INFORMATION[#1]:
 &lt;b&gt;Track State:           reserved incremental&lt;/b&gt;
 Track Start Address:   0*2KB
 Next Writable Address: 0*2KB
 Free Blocks:           1520*2KB
 Track Size:            1520*2KB
READ TRACK INFORMATION[#2]:
 &lt;b&gt;Track State:           complete incremental&lt;/b&gt;
 Track Start Address:   1536*2KB
 Free Blocks:           0*2KB
 Track Size:            176*2KB
 Last Recorded Address: 1551*2KB
READ TRACK INFORMATION[#3]:
 &lt;b&gt;Track State:           complete incremental&lt;/b&gt;
 Track Start Address:   1728*2KB
 Free Blocks:           0*2KB
 Track Size:            668544*2KB
 Last Recorded Address: 670271*2KB
READ TRACK INFORMATION[#4]:
 Track State:           invisible incremental
 Track Start Address:   670288*2KB
 Next Writable Address: 670288*2KB
 Free Blocks:           1627600*2KB
 Track Size:            1627600*2KB
READ CAPACITY:          0*2048=0
&lt;/pre&gt;

&lt;p&gt;Apenas he trabajado con DVDs multisesión, pero supongo que esa salida será más que normal. Lo que importa es que el DVD está ahí, y, de algún modo, &lt;b&gt;dvd+rw-mediainfo&lt;/b&gt; puede acceder a él.&lt;p&gt;

&lt;p&gt;Me pongo a buscar entre las herramientas sobre DVDs y CDs y veo que &lt;code&gt;cdrecord&lt;/code&gt; tiene un &lt;b&gt;readcd&lt;/b&gt;, que parece ser lo que necesito. Los primeros resultados no son muy positivos&lt;/p&gt;

&lt;pre&gt;
&lt;b&gt;# readcd dev=/dev/scd0 f=cdimage.raw&lt;/b&gt;
Read  speed: 11080 kB/s (CD  62x, DVD  8x).
Write speed: 11080 kB/s (CD  62x, DVD  8x).
Capacity: 1 Blocks = 2 kBytes = 0 MBytes = 0 prMB
Sectorsize: 2048 Bytes
Copy from SCSI (1,0,0) disk to file 'cdimage.raw'
end:         1
readcd: Input/output error. read_g1: scsi sendcmd: no error
CDB:  28 00 00 00 00 00 00 00 01 00
status: 0x2 (CHECK CONDITION)
Sense Bytes: 70 00 05 00 00 00 00 0A 00 00 00 00 21 00 00 00
Sense Key: 0x5 Illegal Request, Segment 0
Sense Code: 0x21 Qual 0x00 (logical block address out of range) Fru 0x0
Sense flags: Blk 0 (not valid) 
resid: 2048
cmd finished after 0.009s timeout 40s
readcd: Input/output error. Cannot read source disk
readcd: Retrying from sector 0.
.~~-~~~+~~~-~~~+~~~-~~~+~~~-~~~+~~~-~~~+~~~-~~~+~~~-~~~+~~~-~~~+~~~-~~~+~~~-~~~+~~~-~~~+~~~-~~~+~~~-~~~+~~~-~~~+~~~-~~~
readcd: Input/output error. Error on sector 0 not corrected. Total of 1 errors.

Time total: 1.982sec
Read 0.00 kB at 0.0 kB/sec.
Max corected retry count was 0 (limited to 128).
The following 1 sector(s) could not be read correctly:
0
&lt;/pre&gt;

&lt;p&gt;Tiene el mismo problema que el &lt;code&gt;dd&lt;/code&gt;. Mirando en el &lt;code&gt;man&lt;/code&gt; para ver si hay manera de que ignore esos errores y siga leyendo veo que hay opciones como &lt;code&gt;-noerror&lt;/code&gt; o &lt;code&gt;-nocorr&lt;/code&gt;. Con estas opciones lee un poco más, pero no sale de la primera pista&lt;/p&gt;

&lt;p&gt;Mirando un poco más veo que hay una opción para indicar qué sectores sacar del DVD. Fíjandome en la salida del &lt;b&gt;dvd+rw-mediainfo&lt;/b&gt; veo que la segunda y la tercera pista están desde el sector 1536 al 670448 (el final se saca sumando el tamaño de las dos)... Así que añado &lt;b&gt;sectors=1536-670448&lt;/b&gt; a las opciones del &lt;b&gt;readcd&lt;/b&gt;... y ¡&lt;i&gt;Voilà&lt;/i&gt;!&lt;/p&gt;

&lt;p&gt;Los datos que empiezan a leer corresponden al video que quería ver. Después de unos minutos tengo un fichero de 1,3Gib con un video de unos 20 minutos&lt;/p&gt;

&lt;p&gt;Según &lt;code&gt;file&lt;/code&gt; el fichero es un &lt;code&gt;Video title set, v11&lt;/code&gt;, aunque el &lt;code&gt;mplayer&lt;/code&gt; lo abre usando el &lt;i&gt;codec&lt;/i&gt; &lt;code&gt;mpegpes&lt;/code&gt;, que (según su propia descripción) corresponde a &lt;i&gt;MPEG-PES output (.mpg or DXR3/DVB card)&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;&lt;i&gt;Quizás&lt;/i&gt; con algún programa de esos llenos de dibujos y colores por todas partes no hubiera tenido que &lt;i&gt;pensar&lt;/i&gt; en cómo solucionar este tema, pero &lt;b&gt;seguro&lt;/b&gt; que no me hubiera sentido tan bien con la solución.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2910574553699562670-252463621633865789?l=letra0.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://letra0.blogspot.com/feeds/252463621633865789/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2910574553699562670&amp;postID=252463621633865789' title='1 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2910574553699562670/posts/default/252463621633865789'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2910574553699562670/posts/default/252463621633865789'/><link rel='alternate' type='text/html' href='http://letra0.blogspot.com/2007/06/vueltas-con-un-dvd.html' title='Viaje al interior de... un DVD'/><author><name>setepo</name><uri>http://www.blogger.com/profile/11694543815191404747</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry></feed>
