Friday, January 4, 2013

A python .ap and .sc generator for enigma2 recordings

In my quest to make a transcoder for recordings on my enigma2 satellite receiver I've made an .ap and .sc file generator in python. The .ap and .sc files are used for more accurate skipping in the recording when viewing on the receiver. As input and inspiration I used ProcessApSc plugin by Michel Hartman and also the OpenPli source for pvrparse.cpp.
from struct import pack, unpack
import io
import os.path
import sys

LEN = 24064
PACKET_SIZE = 188
    
def WriteBufInternal(f, sc, tm):
    f.write(pack('>QQ', sc, tm))

def FrameGenerator(ts, packetoffset=0, maxlen=-1):
    s = io.open(ts, 'rb', buffering=LEN)
    buf = bytearray(PACKET_SIZE)
    streamtype = -1
    pid = -1
    while len(s.peek())>=PACKET_SIZE: #Skip if not a whole packet left
        while ord(s.peek()[0]) != 0x47: #Sync stream
            s.read(1)
            print("Skipped 1 byte")
 
        filepos = s.tell()
        if maxlen >= 0 and filepos > maxlen: #Limit reading for debugging purposes
            break
        
        if s.readinto(buf) != PACKET_SIZE: #If not enough data quit
            s.close()
            break

        if not buf[3]&0x10: #Skip if there is no payload
            continue

        pos = buf[4] + 5 if buf[3]&0x20 else 4 #Skip adaption field
        if pos > PACKET_SIZE: #Skip large adaption field
            continue

        tpid = ((buf[1]&0x1F) << 8) | buf[2] #Read current packet pid
        if (not (buf[pos] or buf[pos+1] or not buf[pos+2]&0x01)
            and buf[pos+3]&0xf0 == 0xe0
            and buf[1]&0x040): #Find video pid
            pid = tpid
        elif pid>=0 and pid != tpid: #Wrong pid
            continue

        pts = -1
        if buf[1]&0x40: #Pusi
            if buf[pos] or buf[pos+1] or not buf[pos+2]&0x01:
                print("Broken startcode")
                continue
            if buf[pos+7]&0x80: #PTS present?
                pts = ((buf[pos+9]&0xE) << 29   | (buf[pos+10]&0xFF) << 22 |
                       (buf[pos+11]&0xFE) << 14 | (buf[pos+12]&0xFF) << 7  |
                       (buf[pos+13]&0xFE) >> 1)
            pos = buf[pos+8] + 9
        
        while pos < PACKET_SIZE - 4:
            if not (buf[pos] or buf[pos+1] or not buf[pos+2]&0x01):
                sc = buf[pos+3]
                if streamtype < 0: #Stream type is unknown
                    if sc in [0x00, 0xb3, 0xb8]:
                        streamtype = 0
                        print("Detected MPEG2 stream type")
                    elif sc in [0x09]:
                        streamtype = 1
                        print("Detected H264 stream type")
                    else:
                        pos += 1
                        continue
                
                if streamtype == 0: #MPEG2
                    if sc in [0x00, 0xb3, 0xb8]: #Picture, sequence, group start code
                        retpos = retpts = retdat = retpos2 = -1
                        if sc == 0xb3 and pts >=0 : #Sequence header
                            retpos = filepos
                            retpts = pts
                        if pos < PACKET_SIZE - 6:
                            retdat = sc | buf[pos+4] << 8 | buf[pos+5] << 16
                            if pts >= 0:
                                retdat |= (pts << 31) | 0x1000000;
                            retpos2 = filepos + pos
                        yield (retpts, retpos, retdat, retpos2)

                elif streamtype == 1:
                    if sc == 0x09:
                        retpos = retpts = retdat = retpos2 = -1
                        retdat = sc | (buf[pos+4] << 8)
                        if pts >= 0:
                            retdat |= (pts << 31) | 0x1000000
                        retpos2 = filepos + pos
                        if (buf[pos+4]&0x60) == 0:
                            if pts >= 0:
                                retpos = filepos
                                retpts = pts
                        yield (retpts, retpos, retdat, retpos2)
                                
            pos += 1

def ProcessScAp(ts, maxlen=-1):
    scf = open(ts+'.sc', 'wb')
    apf = open(ts+'.ap', 'wb')
    filesize = os.path.getsize(ts)

    lastprogress = -1
    for (pts, pos, dat, pos2) in FrameGenerator(ts, maxlen=maxlen):
        curpos = max(pos, pos2)
        if curpos >= 0:
            progress = curpos*100/filesize
        if progress > lastprogress:
            print("{0}%".format(progress))
            lastprogress = progress
        if pts >= 0 and pos >= 0:
            WriteBufInternal(apf, pos,  pts)
        if dat >= 0 and pos2 >= 0:
            WriteBufInternal(scf, pos2, dat)

    scf.close()
    apf.close()

1 comment:

  1. Hello,
    I have a vuduo also so i would like to use your script :) but how?

    ReplyDelete