<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>AlferSoft Blog &#187; Programming FAQ</title>
	<atom:link href="http://www.alfersoft.com.ar/blog/category/dev/programming-faq/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.alfersoft.com.ar/blog</link>
	<description>Explaining this blog in a few words since 1999</description>
	<lastBuildDate>Wed, 01 Feb 2012 22:36:48 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>Migrating GAE Blobstore to an HRD application</title>
		<link>http://www.alfersoft.com.ar/blog/2011/11/14/migrating-gae-blobstore-to-an-hrd-application/</link>
		<comments>http://www.alfersoft.com.ar/blog/2011/11/14/migrating-gae-blobstore-to-an-hrd-application/#comments</comments>
		<pubDate>Mon, 14 Nov 2011 03:09:40 +0000</pubDate>
		<dc:creator>fvicente</dc:creator>
				<category><![CDATA[Programming FAQ]]></category>
		<category><![CDATA[Software Development]]></category>
		<category><![CDATA[app]]></category>
		<category><![CDATA[blobinfo]]></category>
		<category><![CDATA[blobstore]]></category>
		<category><![CDATA[engine]]></category>
		<category><![CDATA[GAE]]></category>
		<category><![CDATA[google]]></category>
		<category><![CDATA[high replication database]]></category>
		<category><![CDATA[HRD]]></category>
		<category><![CDATA[migrate]]></category>
		<category><![CDATA[migration]]></category>

		<guid isPermaLink="false">http://www.alfersoft.com.ar/blog/?p=296</guid>
		<description><![CDATA[If you are planning to migrate your Google App Engine application to the &#8220;new&#8221; High Replication Database scheme, then you probably know that the Google migration process won&#8217;t handle Blobstore files. Here you will find some python scripts that will help you to move the Blobstore files from the old application to the new one, [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.alfersoft.com.ar/blog/wp-content/uploads/2011/11/google-app-engine_logo_0111.png"><img src="http://www.alfersoft.com.ar/blog/wp-content/uploads/2011/11/google-app-engine_logo_0111.png" alt="Google App Engine" title="google app engine" width="150" height="150" class="alignleft size-full wp-image-301" /></a>If you are planning to migrate your Google App Engine application to the &#8220;new&#8221; High Replication Database scheme, then you probably know that the Google migration process won&#8217;t handle Blobstore files.</p>
<p>Here you will find some python scripts that will help you to move the Blobstore files from the old application to the new one, and correct all the references in the new database. As always, use at your own risk, don&#8217;t try to do anything without reading and understanding the scripts, otherwise you may loose your data permanently.</p>
<p><span id="more-296"></span></p>
<p>Some considerations:</p>
<ul>
<li>We did used these scripts in our very own application, so I can say it works. However, I had to edit the original scripts to make them more &#8220;generic&#8221;, remove my database references and names, etc.</li>
<li>I will try to describe the steps of the migration, but please don&#8217;t try to do anything before reading the whole article first.</li>
<li>This is not by any means intended to be a fully automatic migration, in fact, the steps needs to be manually executed one by one.</li>
<li><b>The scripts won&#8217;t migrate big files.</b> We have migrated files up to 40Mb without problems, but if you have files bigger than that, it will probably fail. For example we tried a 300Mb file unsuccessfully, the reason is that the file needs to be downloaded from the old app to the new, and there is a 1 minute timeout for the app requests, so if the download takes more than that it will fail.</li>
</ul>
<h5>How does the script works</h5>
<p>You will need to put the script in your applications, both the new (HRD) and the old one. It basically exposes four URLs and you will need to access two of them from the HRD application.<br />
By doing this you will download all the blob files from the old application to the new one, the script will create a model that keeps a map between the old and new references.<br />
The final step is to migrate the old references to the new one in the HRD databases.</p>
<h5>Prerequisites</h5>
<ul>
<li>All the references to blob files in your models must be of type <code>blobstore.BlobReferenceProperty</code>, if not you will need to adjust them first.</li>
<li>Migrate the databases from the old app to the new HRD app using the <a href="http://code.google.com/appengine/docs/adminconsole/applicationsettings.html#Migrate_from_Master/Slave_to_High_Replication_Datastore" title="Google App Engine migrate datastore">standard Google migration method</a>.</li>
</ul>
<h5>Steps</h5>
<ul>
<li>Add the URLs to your application</li>
</ul>
<pre>
<pre class="brush: python; title: ; notranslate">
def main():
    application = webapp.WSGIApplication(
          [
           # ... Your app URLs here ...
           # TODO - REMOVE THIS AFTER MIGRATION
           ('/mig/__getblob/(.+)', GetBlob),
           ('/mig/__getblobkeys/?', GetBlobKeys),
           ('/mig/__migrateblobs/?', MigrateBlobs),
           ('/mig/__migratereferences/?', MigrateBlobReferences),
          ], debug=True)
    run_wsgi_app(application)

if __name__ == '__main__':
  main()
</pre>
</pre>
<ul>
<li>Then add the migration code</li>
</ul>
<pre>
<pre class="brush: python; title: ; notranslate">
####
# TEMPORARY BLOBSTORE MIGRATION CODE
####

MODELS = (
    # add your (model, blobinfo_field_name) tuples here
    (&quot;myModel1&quot;, &quot;blobinfofield&quot;),
    (&quot;myModel2&quot;, &quot;BlobInfo&quot;),
)

class mig_BlobMig(db.Model):
    fname = db.StringProperty()
    blobinfo = blobstore.BlobReferenceProperty()
    origkey = db.StringProperty()

class MigrateBlobReferences(blobstore_handlers.BlobstoreDownloadHandler):
    '''Migrate blob info references'''
    def get(self):
        import models
        tb = dict([(str(blob.origkey), blob.blobinfo) for blob in mig_BlobMig.all()])
        for modelname, field in MODELS:
            to_put = []
            skipped = 0
            for obj in db.GqlQuery('SELECT * FROM %s'%modelname):
                oldkey = getattr(obj, field)
                if oldkey:
                    oldkey = str(oldkey.key())
                if oldkey in tb:
                    setattr(obj, field, tb[oldkey])
                    to_put.append(obj)
                else:
                    skipped += 1
            db.put(to_put)
            self.response.out.write(&quot;[%s] - Migrated %d references!&lt;br/&gt;Skipped: %d&lt;br/&gt;\n&quot;%(modelname, len(to_put), skipped))

class MigrateBlobs(blobstore_handlers.BlobstoreDownloadHandler):
    '''Migrate blobs from myoldapp to mynewapp'''
    def get(self):
        import time
        from google.appengine.api import files, urlfetch
        start_time = time.time()
        timed_out = False
        migrated_keys = set([str(blob.origkey) for blob in mig_BlobMig.all()])
        all_blobs = eval(urlfetch.fetch(&quot;https://myoldapp.appspot.com/mig/__getblobkeys&quot;, deadline=15.0).content)
        all_keys = set(all_blobs.keys())
        missing_keys = list(all_keys-migrated_keys)
        if not missing_keys:
            self.response.out.write(&quot;Nothing to migrate!&quot;)
            return
        # Download 20 blobs per round
        download_keys = missing_keys[:20]
        MAXSIZE = (10*1024*1024) # 10MB
        for origkey in download_keys:
            blob = all_blobs[origkey]
            url = &quot;https://myoldapp.appspot.com/mig/__getblob/%s&quot;%urllib.quote_plus(origkey)
            blob_path = files.blobstore.create(mime_type=blob[&quot;content_type&quot;], _blobinfo_uploaded_filename=blob[&quot;filename&quot;])
            fsize = int(blob[&quot;size&quot;])
            with files.open(blob_path, 'a') as f:
                for first_byte in range(0, fsize, MAXSIZE):
                    last_byte = (first_byte+MAXSIZE-1)
                    if last_byte&gt;=fsize: last_byte=(fsize-1)
                    bytes_range = &quot;bytes=%d-%d&quot;%(first_byte,last_byte)
                    logging.info(&quot;Downloading [%s] range [%s] key=[%s]&quot;%(blob[&quot;filename&quot;], bytes_range, origkey))
                    res = urlfetch.fetch(url, deadline=35.0, headers={&quot;Range&quot;: bytes_range})
                    f.write(res.content)
                    del res.content
                    del res
                    if (time.time()-start_time &gt; 40.0):
                        timed_out = True
                        break
            if timed_out:
                self.response.out.write(&quot;Stopped due to time limit!&lt;br/&gt;&quot;)
                break
            files.finalize(blob_path)
            blob_key = files.blobstore.get_blob_key(blob_path)
            blob_info = blobstore.BlobInfo.get(blob_key)
            mig_BlobMig(fname=blob[&quot;filename&quot;], blobinfo=blob_info, origkey=origkey).put()
            logging.info(&quot;Successfully downloaded [%s]!!!&quot;%(blob[&quot;filename&quot;]))
            self.response.out.write(&quot;Migrated %s&lt;br/&gt;&quot;%blob[&quot;filename&quot;])
        self.response.out.write(&quot;&lt;br/&gt;SUCCESS!&quot;)

class GetBlobKeys(blobstore_handlers.BlobstoreDownloadHandler):
    '''Retrieve all Blob Keys, as a JSON string'''
    def get(self):
        from simplejson.encoder import JSONEncoder
        blobs = {}
        for blob_info in blobstore.BlobInfo.all():
            blobs[str(blob_info.key())] = {
             &quot;filename&quot;:str(blob_info.filename),
             &quot;content_type&quot;:str(blob_info.content_type),
             &quot;size&quot;:int(blob_info.size),
             &quot;key&quot;:str(blob_info.key()),
            }
        self.response.headers[&quot;Content-type&quot;] = &quot;application/json&quot;
        self.response.out.write(JSONEncoder().encode(blobs))

class GetBlob(blobstore_handlers.BlobstoreDownloadHandler):
    '''Download a blob given its key'''
    def get(self, blobkey):
        blobkey = str(urllib.unquote(blobkey))
        blob_info = blobstore.BlobInfo.get(blobkey)
        if not blob_info:
            self.error(404)
            return
        self.response.headers[&quot;Content-type&quot;] = str(blob_info.content_type)
        self.send_blob(blob_info, save_as=True)

####
# END OF TEMPORARY BLOBSTORE MIGRATION CODE
####
</pre>
</pre>
<ul>
<li>Modify the <code>MODELS</code> variable adding the models and blob info fields that you need to update</li>
<li>Replace <code>myoldapp</code> URLs with the actual URL of your old application</li>
<li>Synchronize the code on both apps</li>
<li>Open a browser and point it to <code>http://mynewapp.appspot.com/mig/__migrateblobs</code>. Of course, <code>mynewapp</code> must be replaced with your actual HRD application name. This will download the blobs from the old app to the new one. Because there is a timeout limitation you will need to call it many times. <b>WARNING: You must make only one request at time, never call it from two browsers / tabs at the same time or you will mess the migration database!!!</b>. So this step must be carefully executed, one request at time and wait for it to finish before calling the next one. Repeat this until you see the message <code>Nothing to migrate!</code>. This step can be very slow depending on how many files you have in the blobstore and the size of them, it might take a long time to get all the files downloaded. Be patient!</li>
<li>Once all the files are copied to the new app, point the browser to <code>http://mynewapp.appspot.com/mig/__migratereferences</code>. Should be quick, and it needs to be executed only one time. However doesn&#8217;t harm to call it more than once.</li>
<li>The last step, remove the migration code and URLs and synchronize the code again. You can also remove the <code>mig_BlobMig</code> database.</li>
</ul>
<p>Good migration.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.alfersoft.com.ar/blog/2011/11/14/migrating-gae-blobstore-to-an-hrd-application/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>How to create two wired virtual serial ports on Linux?</title>
		<link>http://www.alfersoft.com.ar/blog/2010/02/19/how-to-create-two-wired-virtual-serial-ports-on-linux/</link>
		<comments>http://www.alfersoft.com.ar/blog/2010/02/19/how-to-create-two-wired-virtual-serial-ports-on-linux/#comments</comments>
		<pubDate>Fri, 19 Feb 2010 18:18:32 +0000</pubDate>
		<dc:creator>fvicente</dc:creator>
				<category><![CDATA[Linux FAQ]]></category>
		<category><![CDATA[Programming FAQ]]></category>
		<category><![CDATA[bridged]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[null-modem]]></category>
		<category><![CDATA[port]]></category>
		<category><![CDATA[rs-232]]></category>
		<category><![CDATA[serial]]></category>
		<category><![CDATA[ubuntu]]></category>
		<category><![CDATA[virtual]]></category>
		<category><![CDATA[wired]]></category>

		<guid isPermaLink="false">http://www.alfersoft.com.ar/blog/?p=134</guid>
		<description><![CDATA[To create two bridged virtual serial ports use the following command: socat -d -d pty,raw,echo=0 pty,raw,echo=0 The output will show you which are the virtual ports (or pseudo terminals) created, e.g.: 2010/02/19 16:16:33 socat[9662] N PTY is /dev/pts/3 2010/02/19 16:16:33 socat[9662] N PTY is /dev/pts/4 2010/02/19 16:16:33 socat[9662] N starting data transfer loop with FDs [...]]]></description>
			<content:encoded><![CDATA[<p><img class="alignnone" title="FAQ" src="http://www.alfersoft.com.ar/files/question.png" alt="" width="48" height="48" /></p>
<p>To create two bridged virtual serial ports use the following command:<br />
<code><br />
socat -d -d pty,raw,echo=0 pty,raw,echo=0<br />
</code></p>
<p>The output will show you which are the virtual ports (or pseudo terminals) created, e.g.:<br />
<code><br />
2010/02/19 16:16:33 socat[9662] N PTY is /dev/pts/3<br />
2010/02/19 16:16:33 socat[9662] N PTY is /dev/pts/4<br />
2010/02/19 16:16:33 socat[9662] N starting data transfer loop with FDs [3,3] and [5,5]<br />
</code></p>
<p>Note: if you are using Ubuntu and you do not have this command, try:<br />
<code><br />
sudo apt-get install socat<br />
</code></p>
<h6><a title="Lock" href="http://commons.wikimedia.org/wiki/File:Gnome-dialog-question.svg" target="_blank">Image source</a></h6>
]]></content:encoded>
			<wfw:commentRss>http://www.alfersoft.com.ar/blog/2010/02/19/how-to-create-two-wired-virtual-serial-ports-on-linux/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>How do I compare two binary files on Linux?</title>
		<link>http://www.alfersoft.com.ar/blog/2010/02/19/how-do-i-compare-two-binary-files-on-linux/</link>
		<comments>http://www.alfersoft.com.ar/blog/2010/02/19/how-do-i-compare-two-binary-files-on-linux/#comments</comments>
		<pubDate>Fri, 19 Feb 2010 17:51:00 +0000</pubDate>
		<dc:creator>fvicente</dc:creator>
				<category><![CDATA[Linux FAQ]]></category>
		<category><![CDATA[Programming FAQ]]></category>
		<category><![CDATA[binaries]]></category>
		<category><![CDATA[binary]]></category>
		<category><![CDATA[compare]]></category>
		<category><![CDATA[diff]]></category>
		<category><![CDATA[hexdump]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[meld]]></category>

		<guid isPermaLink="false">http://www.alfersoft.com.ar/blog/?p=128</guid>
		<description><![CDATA[The easiest way I found is dumping the binaries into text files using hexdump and then comparing them with your favourite program (diff, Meld, etc.). E.g.: hexdump -C a.bin >a.txt hexdump -C b.bin >b.txt diff a.txt b.txt Image source]]></description>
			<content:encoded><![CDATA[<p><img class="alignnone" title="FAQ" src="http://www.alfersoft.com.ar/files/question.png" alt="" width="48" height="48" /></p>
<p>The easiest way I found is dumping the binaries into text files using hexdump and then comparing them with your favourite program (diff, Meld, etc.). E.g.:<br />
<code><br />
hexdump -C a.bin >a.txt<br />
hexdump -C b.bin >b.txt<br />
diff a.txt b.txt<br />
</code></p>
<h6><a title="Lock" href="http://commons.wikimedia.org/wiki/File:Gnome-dialog-question.svg" target="_blank">Image source</a></h6>
]]></content:encoded>
			<wfw:commentRss>http://www.alfersoft.com.ar/blog/2010/02/19/how-do-i-compare-two-binary-files-on-linux/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Extracting FLV meta tags with Python</title>
		<link>http://www.alfersoft.com.ar/blog/2010/02/05/extracting-flv-meta-tags-with-python/</link>
		<comments>http://www.alfersoft.com.ar/blog/2010/02/05/extracting-flv-meta-tags-with-python/#comments</comments>
		<pubDate>Sat, 06 Feb 2010 00:07:36 +0000</pubDate>
		<dc:creator>fvicente</dc:creator>
				<category><![CDATA[Programming FAQ]]></category>
		<category><![CDATA[flash]]></category>
		<category><![CDATA[flv]]></category>
		<category><![CDATA[metadata]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[video]]></category>

		<guid isPermaLink="false">http://www.alfersoft.com.ar/blog/?p=112</guid>
		<description><![CDATA[I&#8217;ve stolen this code from http://code.activestate.com/recipes/457406/ which in turn was stolen / ported from http://inlet-media.de/flvtool2 , made some small fixes in bugs related to the date conversion function, object and array unmarshaling, file name hardcoded, etc&#8230; If you want to learn more about the FLV format and metatags read Acrobat&#8217;s Video File Format Specification Version [...]]]></description>
			<content:encoded><![CDATA[<p>I&#8217;ve stolen this code from <a href="http://code.activestate.com/recipes/457406/" target="_blank">http://code.activestate.com/recipes/457406/</a> which in turn was stolen / ported from <a rel="nofollow" href="http://inlet-media.de/flvtool2">http://inlet-media.de/flvtool2 </a>, made some small fixes in bugs related to the date conversion function, object and array unmarshaling, file name hardcoded, etc&#8230; If you want to learn more about the FLV format and metatags read Acrobat&#8217;s <a title="Video File Format Specification Version 10" href="http://www.adobe.com/devnet/flv/pdf/video_file_format_spec_v10.pdf" target="_blank">Video File Format Specification Version 10</a><br />
<span id="more-112"></span></p>
<pre class="brush: python; title: ; notranslate">
from struct import unpack
from datetime import datetime

class FLVReader(dict):
    &quot;&quot;&quot;
    Reads metadata from FLV files
    &quot;&quot;&quot;

    # Tag types
    AUDIO = 8
    VIDEO = 9
    META = 18
    UNDEFINED = 0

    def __init__(self, filename):
        &quot;&quot;&quot;
        Pass the filename of an flv file and it will return a dictionary of meta
        data.
        &quot;&quot;&quot;
        # Lock on to the file
        self.file = open(filename, 'rb')
        self.signature = self.file.read(3)
        assert self.signature == 'FLV', 'Not an flv file'
        self.version = self.readbyte()
        self.typeFlags = self.readbyte()
        self.dataOffset = self.readint()
        extraDataLen = self.dataOffset - self.file.tell()
        self.extraData = self.file.read(extraDataLen)
        self.readtag()

    def readtag(self):
        unknown = self.readint()
        tagType = self.readbyte()
        dataSize = self.read24bit()
        timeStamp = self.read24bit()
        unknown = self.readint()
        if tagType == self.AUDIO:
            print &quot;Can't handle audio tags yet&quot;
        elif tagType == self.VIDEO:
            print &quot;Can't handle video tags yet&quot;
        elif tagType == self.META:
            endpos = self.file.tell() + dataSize
            self.event = self.readAMFData()
            metaData = self.readAMFData()
            # We got the meta data.
            # Our job is done.
            # We are complete
            self.update(metaData)
        elif tagType == self.UNDEFINED:
            print &quot;Can't handle undefined tags yet&quot;

    def readint(self):
      data = self.file.read(4)
      return unpack('&gt;I', data)[0]

    def readshort(self):
      data = self.file.read(2)
      return unpack('&gt;H', data)[0]

    def readbyte(self):
      data = self.file.read(1)
      return unpack('B', data)[0]

    def read24bit(self):
      b1, b2, b3 = unpack('3B', self.file.read(3))
      return (b1 &lt;&lt; 16) + (b2 &lt;&lt; 8 ) + b3

    def readAMFData(self, dataType=None):
        if dataType is None:
            dataType = self.readbyte()
        funcs = {
            0: self.readAMFDouble,
            1: self.readAMFBoolean,
            2: self.readAMFString,
            3: self.readAMFObject,
            8: self.readAMFMixedArray,
           10: self.readAMFArray,
           11: self.readAMFDate
        }
        func = funcs[dataType]
        if callable(func):
            return func()

    def readAMFDouble(self):
        return unpack('&gt;d', self.file.read(8))[0]

    def readAMFBoolean(self):
        return self.readbyte() == 1

    def readAMFString(self):
        size = self.readshort()
        return self.file.read(size)

    def readAMFObject(self):
        result = {}
        data = True
        while data:
            size = self.readshort()
            key = self.file.read(size)
            dataType = self.readbyte()
            if not key and dataType == 9:
                break
            data = self.readAMFData(dataType)
            result[key] = data
        return result

    def readAMFMixedArray(self):
        size = self.readint()
        result = {}
        i = 0
        while i &lt; size:
            key = self.readAMFString()
            dataType = self.readbyte()
            if not key and dataType == 9:
                break
            result[key] = self.readAMFData(dataType)
            i += 1
        return result

    def readAMFArray(self):
        size = self.readint()
        result = []
        i = 0
        while i &lt; size:
            result.append(self.readAMFData())
            i += 1
        return result

    def readAMFDate(self):
        date = self.readAMFDouble()/1000
        localoffset = self.readshort()
        return datetime.fromtimestamp(date)

if __name__ == '__main__':
    import sys
    from pprint import pprint
    if len(sys.argv) == 1:
        print 'Usage: %s filename [filename]...' % sys.argv[0]
        print 'Where filename is a .flv file'
        print 'eg. %s myfile.flv' % sys.argv[0]
    for fn in sys.argv[1:]:
        x = FLVReader(fn)
        pprint(x)
</pre>
<p>Download this code snippet from <a title="flv.py" href="http://www.alfersoft.com.ar/files/flv.py" target="_blank">here</a></p>
<div id="_mcePaste" style="overflow: hidden; position: absolute; left: -10000px; top: 55px; width: 1px; height: 1px;">from&amp;nbsp;struct&amp;nbsp;import&amp;nbsp;unpack<br />
from&amp;nbsp;datetime&amp;nbsp;import&amp;nbsp;datetime</p>
<p>class&amp;nbsp;FLVReader(dict):<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&#8221;"&#8221;<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Reads&amp;nbsp;metadata&amp;nbsp;from&amp;nbsp;FLV&amp;nbsp;files<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&#8221;"&#8221;</p>
<p>&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;Tag&amp;nbsp;types<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AUDIO&amp;nbsp;=&amp;nbsp;8<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;VIDEO&amp;nbsp;=&amp;nbsp;9<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;META&amp;nbsp;=&amp;nbsp;18<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;UNDEFINED&amp;nbsp;=&amp;nbsp;0</p>
<p>&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;__init__(self,&amp;nbsp;filename):<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&#8221;"&#8221;<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Pass&amp;nbsp;the&amp;nbsp;filename&amp;nbsp;of&amp;nbsp;an&amp;nbsp;flv&amp;nbsp;file&amp;nbsp;and&amp;nbsp;it&amp;nbsp;will&amp;nbsp;return&amp;nbsp;a&amp;nbsp;dictionary&amp;nbsp;of&amp;nbsp;meta<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data.<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&#8221;"&#8221;<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;Lock&amp;nbsp;on&amp;nbsp;to&amp;nbsp;the&amp;nbsp;file<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.file&amp;nbsp;=&amp;nbsp;open(filename,&amp;nbsp;&#8217;rb&#8217;)<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.signature&amp;nbsp;=&amp;nbsp;self.file.read(3)<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;assert&amp;nbsp;self.signature&amp;nbsp;==&amp;nbsp;&#8217;FLV&#8217;,&amp;nbsp;&#8217;Not&amp;nbsp;an&amp;nbsp;flv&amp;nbsp;file&#8217;<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.version&amp;nbsp;=&amp;nbsp;self.readbyte()<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.typeFlags&amp;nbsp;=&amp;nbsp;self.readbyte()<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.dataOffset&amp;nbsp;=&amp;nbsp;self.readint()<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;extraDataLen&amp;nbsp;=&amp;nbsp;self.dataOffset&amp;nbsp;-&amp;nbsp;self.file.tell()<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.extraData&amp;nbsp;=&amp;nbsp;self.file.read(extraDataLen)<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.readtag()</p>
<p>&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;readtag(self):<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;unknown&amp;nbsp;=&amp;nbsp;self.readint()<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;tagType&amp;nbsp;=&amp;nbsp;self.readbyte()<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;dataSize&amp;nbsp;=&amp;nbsp;self.read24bit()<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;timeStamp&amp;nbsp;=&amp;nbsp;self.read24bit()<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;unknown&amp;nbsp;=&amp;nbsp;self.readint()<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;tagType&amp;nbsp;==&amp;nbsp;self.AUDIO:<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print&amp;nbsp;&#8221;Can&#8217;t&amp;nbsp;handle&amp;nbsp;audio&amp;nbsp;tags&amp;nbsp;yet&#8221;<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;elif&amp;nbsp;tagType&amp;nbsp;==&amp;nbsp;self.VIDEO:<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print&amp;nbsp;&#8221;Can&#8217;t&amp;nbsp;handle&amp;nbsp;video&amp;nbsp;tags&amp;nbsp;yet&#8221;<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;elif&amp;nbsp;tagType&amp;nbsp;==&amp;nbsp;self.META:<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;endpos&amp;nbsp;=&amp;nbsp;self.file.tell()&amp;nbsp;+&amp;nbsp;dataSize<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.event&amp;nbsp;=&amp;nbsp;self.readAMFData()<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;metaData&amp;nbsp;=&amp;nbsp;self.readAMFData()<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;We&amp;nbsp;got&amp;nbsp;the&amp;nbsp;meta&amp;nbsp;data.<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;Our&amp;nbsp;job&amp;nbsp;is&amp;nbsp;done.<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;We&amp;nbsp;are&amp;nbsp;complete<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.update(metaData)<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;elif&amp;nbsp;tagType&amp;nbsp;==&amp;nbsp;self.UNDEFINED:<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print&amp;nbsp;&#8221;Can&#8217;t&amp;nbsp;handle&amp;nbsp;undefined&amp;nbsp;tags&amp;nbsp;yet&#8221;</p>
<p>&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;readint(self):<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data&amp;nbsp;=&amp;nbsp;self.file.read(4)<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;unpack(&#8216;&gt;I&#8217;,&amp;nbsp;data)[0]</p>
<p>&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;readshort(self):<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data&amp;nbsp;=&amp;nbsp;self.file.read(2)<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;unpack(&#8216;&gt;H&#8217;,&amp;nbsp;data)[0]</p>
<p>&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;readbyte(self):<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data&amp;nbsp;=&amp;nbsp;self.file.read(1)<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;unpack(&#8216;B&#8217;,&amp;nbsp;data)[0]</p>
<p>&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;read24bit(self):<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;b1,&amp;nbsp;b2,&amp;nbsp;b3&amp;nbsp;=&amp;nbsp;unpack(&#8217;3B&#8217;,&amp;nbsp;self.file.read(3))<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;(b1&amp;nbsp;&lt;&lt;&amp;nbsp;16)&amp;nbsp;+&amp;nbsp;(b2&amp;nbsp;&lt;&lt;&amp;nbsp;8)&amp;nbsp;+&amp;nbsp;b3</p>
<p>&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;readAMFData(self,&amp;nbsp;dataType=None):<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;dataType&amp;nbsp;is&amp;nbsp;None:<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;dataType&amp;nbsp;=&amp;nbsp;self.readbyte()<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;funcs&amp;nbsp;=&amp;nbsp;{<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;0:&amp;nbsp;self.readAMFDouble,<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;1:&amp;nbsp;self.readAMFBoolean,<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2:&amp;nbsp;self.readAMFString,<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;3:&amp;nbsp;self.readAMFObject,<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;8:&amp;nbsp;self.readAMFMixedArray,<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;10:&amp;nbsp;self.readAMFArray,<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;11:&amp;nbsp;self.readAMFDate<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;func&amp;nbsp;=&amp;nbsp;funcs[dataType]<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;callable(func):<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;func()</p>
<p>&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;readAMFDouble(self):<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;unpack(&#8216;&gt;d&#8217;,&amp;nbsp;self.file.read(8))[0]</p>
<p>&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;readAMFBoolean(self):<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;self.readbyte()&amp;nbsp;==&amp;nbsp;1</p>
<p>&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;readAMFString(self):<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;size&amp;nbsp;=&amp;nbsp;self.readshort()<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;self.file.read(size)</p>
<p>&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;readAMFObject(self):<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result&amp;nbsp;=&amp;nbsp;{}<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data&amp;nbsp;=&amp;nbsp;True<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;while&amp;nbsp;data:<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;size&amp;nbsp;=&amp;nbsp;self.readshort()<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;key&amp;nbsp;=&amp;nbsp;self.file.read(size)<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;dataType&amp;nbsp;=&amp;nbsp;self.readbyte()<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;key&amp;nbsp;and&amp;nbsp;dataType&amp;nbsp;==&amp;nbsp;9:<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;break<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data&amp;nbsp;=&amp;nbsp;self.readAMFData(dataType)<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result[key]&amp;nbsp;=&amp;nbsp;data<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;result</p>
<p>&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;readAMFMixedArray(self):<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;size&amp;nbsp;=&amp;nbsp;self.readint()<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result&amp;nbsp;=&amp;nbsp;{}<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;i&amp;nbsp;=&amp;nbsp;0<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;while&amp;nbsp;i&amp;nbsp;&lt;&amp;nbsp;size:<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;key&amp;nbsp;=&amp;nbsp;self.readAMFString()<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;dataType&amp;nbsp;=&amp;nbsp;self.readbyte()<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;key&amp;nbsp;and&amp;nbsp;dataType&amp;nbsp;==&amp;nbsp;9:<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;break<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result[key]&amp;nbsp;=&amp;nbsp;self.readAMFData(dataType)<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;i&amp;nbsp;+=&amp;nbsp;1<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;result</p>
<p>&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;readAMFArray(self):<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;size&amp;nbsp;=&amp;nbsp;self.readint()<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result&amp;nbsp;=&amp;nbsp;[]<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;i&amp;nbsp;=&amp;nbsp;0<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;while&amp;nbsp;i&amp;nbsp;&lt;&amp;nbsp;size:<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result.append(self.readAMFData())<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;i&amp;nbsp;+=&amp;nbsp;1<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;result</p>
<p>&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;readAMFDate(self):<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;date&amp;nbsp;=&amp;nbsp;self.readAMFDouble()/1000<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;localoffset&amp;nbsp;=&amp;nbsp;self.readshort()<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;datetime.fromtimestamp(date)</p>
<p>if&amp;nbsp;__name__&amp;nbsp;==&amp;nbsp;&#8217;__main__&#8217;:<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;import&amp;nbsp;sys<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;from&amp;nbsp;pprint&amp;nbsp;import&amp;nbsp;pprint<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;len(sys.argv)&amp;nbsp;==&amp;nbsp;1:<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print&amp;nbsp;&#8217;Usage:&amp;nbsp;%s&amp;nbsp;filename&amp;nbsp;[filename]&#8230;&#8217;&amp;nbsp;%&amp;nbsp;sys.argv[0]<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print&amp;nbsp;&#8217;Where&amp;nbsp;filename&amp;nbsp;is&amp;nbsp;a&amp;nbsp;.flv&amp;nbsp;file&#8217;<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print&amp;nbsp;&#8217;eg.&amp;nbsp;%s&amp;nbsp;myfile.flv&#8217;&amp;nbsp;%&amp;nbsp;sys.argv[0]<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;fn&amp;nbsp;in&amp;nbsp;sys.argv[1:]:<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;x&amp;nbsp;=&amp;nbsp;FLVReader(fn)<br />
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;pprint(x)</p></div>
]]></content:encoded>
			<wfw:commentRss>http://www.alfersoft.com.ar/blog/2010/02/05/extracting-flv-meta-tags-with-python/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>(HTML) Table cell not inheriting style</title>
		<link>http://www.alfersoft.com.ar/blog/2009/06/04/html-table-cell-not-inheriting-style/</link>
		<comments>http://www.alfersoft.com.ar/blog/2009/06/04/html-table-cell-not-inheriting-style/#comments</comments>
		<pubDate>Fri, 05 Jun 2009 00:24:27 +0000</pubDate>
		<dc:creator>fvicente</dc:creator>
				<category><![CDATA[Programming FAQ]]></category>

		<guid isPermaLink="false">http://www.alfersoft.com.ar/blog/?p=108</guid>
		<description><![CDATA[The goal of this new blog category called &#8220;Programming FAQ&#8221; is to publish short posts with day to day programming problems and their solutions, or at least provide a tip to find the proper solution to your problem. And today we start with this simple and stupid problem that can happen to anyone with poor [...]]]></description>
			<content:encoded><![CDATA[<p>The goal of this new blog category called &#8220;Programming FAQ&#8221; is to publish short posts with day to day programming problems and their solutions, or at least provide a tip to find the proper solution to your problem. And today we start with this simple and stupid problem that can happen to anyone with poor HTML experience like me. &#8220;My HTML table does not seems to inherit the style from a parent element&#8221;, here is the example:</p>
<pre>&lt;html&gt;
&lt;body&gt;
&lt;div style="color: blue;"&gt;
	&lt;table&gt;
	&lt;tbody&gt;
		&lt;tr&gt;
			&lt;td&gt;BLUE TEXT&lt;/td&gt;
		&lt;/tr&gt;
	&lt;/tbody&gt;
	&lt;/table&gt;
&lt;div&gt;
&lt;/body&gt;
&lt;/html&gt;</pre>
<p>In the below example I would expect to see the &#8220;BLUE TEXT&#8221; in blue, but is not (at least in FireFox). So what the hell is wrong with my code? probably many things but because I&#8217;m not interested in following/reading the W3C recommendations for my uncle&#8217;s home page, here is one possible solution: Add the transitional DOCTYPE to your document, or the DOCTYPE that adequate better to your page (<a title="W3C DOCTYPE" href="http://www.w3.org/QA/2002/04/valid-dtd-list.html" target="_blank">read more here</a>). E.g.:</p>
<pre>&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;</pre>
<p>So, now you know. Never forget the DOCTYPE it is very important&#8230; Why? <a title="Don't forget to add a doctype" href="http://www.w3.org/QA/Tips/Doctype" target="_blank">read this</a>&#8230; It has to do with poor standards, browsers and all that crap.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.alfersoft.com.ar/blog/2009/06/04/html-table-cell-not-inheriting-style/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

