字符动画

前两天 @明月照大江 发的一个帖子 : http://www.oschina.net/question/568779_82653

今天起来没啥事情,决定自己也写一个html版本的字符动画生成器。

思路:
1、首先将视频按帧分解为图片,并提取音频
2、对于每个图片
    a. 处理为灰度图
    b. 算出每块像素块的灰度平均值
    c. 根据平均值查找对应的字符
    d. 重复a-c,并把所有的字符拼接为html代码
3、写一个javascript脚本,循环显示div,形成动画

整个程序用到了ffmpegPIL,安装过程就略过了,都很简单。

代码如下:

from PIL import Image
import os
import sys

# the color from black to white
CHARS = ('M', 'N', 'H', 'Q',
         '$', 'O', 'C', '?',
         '7', '>', '!', ':',
         '-', ';', '.', ' ')
CHARS_COUNT = 16
# every 4 x 6 pixel matrix will be converted into a character
CHAR_WIDTH  = 4
CHAR_HEIGHT = 6
# the rate of char anime
RATE = 20
FONT_SIZE = 9
# output html and mp3 name
OUT_HTML_NAME = 'out.html'
OUT_MP3_NAME = 'out.mp3'
# output dir and temp dir
OUT_DIR = 'out'
TMP_DIR = 'tmp'


class CharacterBMP:
    '''
    Character bit map, load an image from path and convert it into html
    '''
    def __init__(self, path):
        '''
        path : path of image
        '''
        # load image and convert into grey scale image
        img = Image.open(path).convert('L')

        self.width = img.size[0]
        self.height = img.size[1]
        # get the data of pixels
        self.data = list(img.getdata())

    def toHTML(self):
        '''
        convert to html
        '''
        str = ''
        for y in xrange(0, self.height, CHAR_HEIGHT):
            for x in xrange(0, self.width, CHAR_WIDTH):
                average = self._average(x, y)
                # map the grey scale value into chars
                charIndex = average / (256 / CHARS_COUNT)
                str = str + CHARS[charIndex]
            str = str + '<br/>'

        return str

    def _average(self, x, y):
        '''
        calculate the average grey scale of certain matrix
        and the size of matrix is CHAR_WIDTH x CHAR_HEIGHT

        x, y : start location of matrix
        '''
        sum = 0
        for j in xrange(CHAR_HEIGHT):
            for i in xrange(CHAR_WIDTH):
                sum = sum + self._getValue(x + i, y + j)
        return sum / (CHAR_HEIGHT * CHAR_WIDTH)

    def _getValue(self, x, y):
        '''
        get the grey scale value of certain point
        x, y : the location of point
        '''
        if x > self.width:
            return CHARS_COUNT - 1
        if y > self.height:
            return CHARS_COUNT - 1
        return self.data[x + y * self.width]


def constructHTML():
    '''
    construct html:
    1. write head info (including style)
    2. write every frame image into html
    3. write javascript at the last which is used to animate the frames
    '''
    bmpFiles = os.listdir(TMP_DIR)

    f = open('%s/%s' % (OUT_DIR, OUT_HTML_NAME), 'w+')
    f.write(''' <!DOCTYPE>
                <html>
                    <head>
                        <meta charset="utf-8">
                        <style type="text/css">
                            div{
                                font-family: monospace; 
                                font-size: %dpx; 
                                margin: 0 auto;
                            }
                        </style>
                    </head>
                    <body>''' % FONT_SIZE)

    print 'convert images (total %d)' % len(bmpFiles)

    for i in xrange(len(bmpFiles)):
        bmpPath = '%s/%s' % (TMP_DIR, bmpFiles[i])
        cbmp = CharacterBMP(bmpPath)

        print 'converting : %s' % bmpPath
        if i is 0:
            f.write('<div id="%d" style="display:block">' % i)
        else:
            f.write('<div id="%d" style="display:none">' % i)
        f.write(cbmp.toHTML())
        f.write('</div>')

    f.write('<audio src="%s" autoplay="autoplay">' % OUT_MP3_NAME)
    f.write(''' <script type="text/javascript">(function() {
                    var FRAME_PER_SECOND = %d;

                    var timer = setInterval(anime, 1000 / FRAME_PER_SECOND);
                    var index = 0;

                    function anime() {
                        document.getElementById('' + index).style.display = 'none';
                        index++;
                        var next = document.getElementById('' + index);
                        if(!next) {
                            clearInterval(timer);
                            return;
                        } else {
                            next.style.display = 'block';
                            next.innerHTML = next.innerHTML.replace(/ /g, '&nbsp;');
                        }
                    }
            })();</script>''' % RATE)
    f.write('</body></html>')
    f.close()


if __name__ == '__main__':
    if len(sys.argv) is not 2:
        print 'Usage : python anime.py INPUT_FILE_NAME'
    else:
        filename = sys.argv[1]

        # make dirs
        os.mkdir(TMP_DIR)
        os.mkdir(OUT_DIR)
        # convert input to mp3
        os.system('ffmpeg -i %s out/%s' % (filename, OUT_MP3_NAME))
        # convert input to html
        os.system('ffmpeg -i %s -r %d -f image2 tmp/%%05d.bmp' % (filename, RATE))

        constructHTML()

        # delete temp file and temp dir
        os.system('rm -rf tmp')

使用方法为:

python anime.py INPUT_FILE_NAME

目前还需要改进一下,首先是生成的HTML太大,另外是转换过程有点慢。考虑引进压缩和多线程来进行改进。

转载于:https://my.oschina.net/Jeky/blog/95317

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值