Source code for pytomo.kaa_metadata.video.flv

# -*- coding: iso-8859-1 -*-
# -----------------------------------------------------------------------------
# flv.py - parser for flash video files
# -----------------------------------------------------------------------------
# $Id: flv.py 4041 2009-05-22 14:59:59Z tack $
#
# -----------------------------------------------------------------------------
# kaa-Metadata - Media Metadata for Python
# Copyright (C) 2003-2006 Thomas Schueppel, Dirk Meyer
#
# First Edition: Dirk Meyer <dischi@freevo.org>
# Maintainer:    Dirk Meyer <dischi@freevo.org>
#
# Please see the file AUTHORS for a complete list of authors.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MER-
# CHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# -----------------------------------------------------------------------------

from __future__ import absolute_import

__all__ = ['Parser']

# python imports
#import sys
import struct
import logging

# import kaa_metadata.video core
from . import core

# get logging object
log = logging.getLogger('metadata')

FLV_TAG_TYPE_AUDIO = 0x08
FLV_TAG_TYPE_VIDEO = 0x09
FLV_TAG_TYPE_META  = 0x12

# audio flags
FLV_AUDIO_CHANNEL_MASK = 0x01
FLV_AUDIO_SAMPLERATE_MASK = 0x0c
FLV_AUDIO_CODECID_MASK = 0xf0

FLV_AUDIO_SAMPLERATE_OFFSET = 2
FLV_AUDIO_CODECID_OFFSET = 4
FLV_AUDIO_CODECID = ( 0x0001, 0x0002, 0x0055, 0x0001 )

# video flags
FLV_VIDEO_CODECID_MASK = 0x0f
FLV_VIDEO_CODECID = ( 'FLV1', 'MSS1', 'VP60') # wild guess

FLV_DATA_TYPE_NUMBER =     0x00
FLV_DATA_TYPE_BOOL =       0x01
FLV_DATA_TYPE_STRING =     0x02
FLV_DATA_TYPE_OBJECT =     0x03
FLC_DATA_TYPE_CLIP =       0x04
FLV_DATA_TYPE_REFERENCE =  0x07
FLV_DATA_TYPE_ECMARRAY =   0x08
FLV_DATA_TYPE_ENDOBJECT =  0x09
FLV_DATA_TYPE_ARRAY =      0x0a
FLV_DATA_TYPE_DATE =       0x0b
FLV_DATA_TYPE_LONGSTRING = 0x0c

FLVINFO = {
    'creator': 'copyright',
}

class FlashVideo(core.AVContainer):
    """
    Experimental parser for Flash videos. It requires certain flags to
    be set to report video resolutions and in most cases it does not
    provide that information.
    """
    table_mapping = { 'FLVINFO' : FLVINFO }

    def __init__(self, in_file):
        core.AVContainer.__init__(self)
        self.mime = 'video/flv'
        self.type = 'Flash Video'
        self.copyright = None
        self.width = None
        self.height = None
        self.length = None
        data = in_file.read(13)
        if len(data) < 13 or struct.unpack('>3sBBII', data)[0] != 'FLV':
            raise core.ParseError()

        for _ in range(10):
            if self.audio and self.video:
                break
            data = in_file.read(11)
            if len(data) < 11:
                break
            chunk = struct.unpack('>BH4BI', data)
            size = (chunk[1] << 8) + chunk[2]

            if chunk[0] == FLV_TAG_TYPE_AUDIO:
                flags = ord(in_file.read(1))
                if not self.audio:
                    self.audio.append(self.get_audio_type(flags))
                in_file.seek(size - 1, 1)

            elif chunk[0] == FLV_TAG_TYPE_VIDEO:
                flags = ord(in_file.read(1))
                if not self.video:
                    self.video.append(self.get_video_type(flags))
                in_file.seek(size - 1, 1)

            elif chunk[0] == FLV_TAG_TYPE_META:
                log.info('metadata %s', str(chunk))
                metadata = in_file.read(size)
                self.add_metadata(metadata)
            else:
                log.info('unkown %s', str(chunk))
                in_file.seek(size, 1)
            in_file.seek(4, 1)

    @staticmethod
    def get_audio_type(flags):
        "Create an audio stream in the media"
        a = core.AudioStream()
        a.channels = (flags & FLV_AUDIO_CHANNEL_MASK) + 1
        srate = (flags & FLV_AUDIO_SAMPLERATE_MASK)
        a.samplerate = (44100 <<
                        (srate >> FLV_AUDIO_SAMPLERATE_OFFSET) >> 3)
        codec = ((flags & FLV_AUDIO_CODECID_MASK)
                 >> FLV_AUDIO_CODECID_OFFSET)
        if codec < len(FLV_AUDIO_CODECID):
            a.codec = FLV_AUDIO_CODECID[codec]
        return a

    @staticmethod
    def get_video_type(flags):
        "Create a video stream in the media"
        v = core.VideoStream()
        codec = (flags & FLV_VIDEO_CODECID_MASK) - 2
        if codec < len(FLV_VIDEO_CODECID):
            v.codec = FLV_VIDEO_CODECID[codec]
        # width and height are in the meta packet, but I have
        # no in_file with such a packet inside. So maybe we have
        # to decode some parts of the video.
        return v

    def add_metadata(self, metadata):
        'Tries to put metadata in the stream'
        try:
            while metadata:
                length, value = self._parse_value(metadata)
                if isinstance(value, dict):
                    log.info('metadata: %s', value)
                    if value.get('creator'):
                        self.copyright = value.get('creator')
                    if value.get('width'):
                        self.width = value.get('width')
                    if value.get('height'):
                        self.height = value.get('height')
                    if value.get('duration'):
                        self.length = value.get('duration')
                    self._appendtable('FLVINFO', value)
                if not length:
                    # parse error
                    break
                metadata = metadata[length:]
        except (IndexError, struct.error, TypeError):
            #log.exception('Error after parsing flv header')
            pass

    def _parse_value(self, data):
        """
        Parse the next metadata value.
        """
        if ord(data[0]) == FLV_DATA_TYPE_NUMBER:
            value = struct.unpack('>d', data[1:9])[0]
            return 9, value
        if ord(data[0]) == FLV_DATA_TYPE_BOOL:
            return 2, bool(data[1])
        if ord(data[0]) == FLV_DATA_TYPE_STRING:
            length = (ord(data[1]) << 8) + ord(data[2])
            return (length + 3), data[3:(length + 3)]
        if ord(data[0]) == FLV_DATA_TYPE_ECMARRAY:
            init_length = len(data)
            num = struct.unpack('>I', data[1:5])[0]
            data = data[5:]
            result = {}
            for _ in range(num):
                length = (ord(data[0]) << 8) + ord(data[1])
                key = data[2:(length + 2)]
                data = data[(length + 2):]
                length, value = self._parse_value(data)
                if not length:
                    return 0, result
                result[key] = value
                data = data[length:]
            return (init_length - len(data)), result
        log.info('unknown code: %x. Stop metadata parser', ord(data[0]))
        return 0, None

Parser = FlashVideo