Skip to content

Instantly share code, notes, and snippets.

@thedeemon
Last active April 11, 2024 04:23
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save thedeemon/f6b58990c0ae6f45c03debc68c18a8d1 to your computer and use it in GitHub Desktop.
Save thedeemon/f6b58990c0ae6f45c03debc68c18a8d1 to your computer and use it in GitHub Desktop.
Write small AVI files with just the video stream
# To write a small AVI file with just a video stream, follow these steps:
# bmi = BITMAPINFOHEADER(40, X, Y, 1, 24, codecFourCC, X*Y*3, 0,0,0,0)
# avi = AviWriter(bmi, b'scpr') # use your codec FourCC instead of "scpr" here
# for ...: avi.addFrame(ftype, frameBytes) # ftype=0 for key frame, 1 for delta frame.
# avi.saveFile('myfile.avi', 10) # put desired FPS instead of 10
from ctypes import *
import ctypes.wintypes
class BITMAPINFOHEADER(Structure):
_fields_ = [('biSize', ctypes.wintypes.DWORD),
('biWidth', ctypes.wintypes.LONG),
('biHeight', ctypes.wintypes.LONG),
('biPlanes', ctypes.wintypes.WORD),
('biBitCount', ctypes.wintypes.WORD),
('biCompression', ctypes.wintypes.DWORD),
('biSizeImage', ctypes.wintypes.DWORD),
('biXPelsPerMeter', ctypes.wintypes.LONG),
('biYPelsPerMeter', ctypes.wintypes.LONG),
('biClrUsed', ctypes.wintypes.DWORD),
('biClrImportant', ctypes.wintypes.DWORD)]
dwords = lambda names: list(map(lambda s: (s, ctypes.wintypes.DWORD), names))
chars = lambda s: (s, c_char * 4)
class AviHeader(Structure):
names = ['dwMicroSecPerFrame', 'dwMaxBytesPerSec', 'dwPaddingGranularity','dwFlags',
'dwTotalFrames','dwInitialFrames', 'dwStreams','dwSuggestedBufferSize',
'dwWidth','dwHeight', 'r1','r2','r3','r4']
_fields_ = dwords(names)
class StreamHeader(Structure):
names = ['dwFlags', 'prioLang', 'dwInitialFrames', 'dwScale','dwRate',
'dwStart', 'dwLength', 'dwSuggestedBufferSize', 'dwQuality', 'dwSampleSize' ]
_fields_ = [chars('fccType'), chars('fccHandler')] + dwords(names) + [('rcFrame', ctypes.wintypes.SMALL_RECT)]
class ChunkHeader(Structure):
_fields_ = [chars('fourCC'), ('size', ctypes.wintypes.DWORD)]
class ListHeader(Structure):
_fields_ = [chars('list'), ('size', ctypes.wintypes.DWORD), chars('fourCC')]
class IdxEntry(Structure):
names = ['dwFlags','dwChunkOffset','dwChunkLength']
_fields_ = [chars('ckId')] + dwords(names)
def sumChunks(arr):
return sum(map(lambda x: x.chunkSize(), arr))
class Frm:
zero = bytes([0])
flags = [16, 0]
def __init__(self, ftype, frameBytes):
self.ftype = ftype
self.data = frameBytes
def chunkSize(self):
n = len(self.data)
return 8 + n + n % 2
def write(self, f):
n = len(self.data)
f.write(bytes(ChunkHeader(b'00dc', n)))
f.write(self.data)
if n % 2 == 1:
f.write(self.zero)
def indexEntry(self):
return bytes( IdxEntry(b'00dc', self.flags[self.ftype], self.offset, len(self.data)) )
class List:
def __init__(self, fourcc, arr, kind = b'LIST'):
self.contents = arr
self.kind = kind
self.fourcc = fourcc
def write(self, f):
f.write(bytes(ListHeader(self.kind, sumChunks(self.contents) + 4, self.fourcc)))
for x in self.contents:
x.write(f)
def chunkSize(self):
return sumChunks(self.contents) + 12
class Chunk:
def __init__(self, fourcc, data):
self.fourcc = fourcc
self.data = data
def chunkSize(self):
return len(self.data) + 8
def write(self, f):
f.write(bytes(ChunkHeader(self.fourcc, len(self.data))))
f.write(self.data)
class AviWriter:
def __init__(self, bmi, codecFourCC):
self.frames = []
self.bmi = bmi # bitmapinfoheader
self.fourcc = codecFourCC # like b'scpr'
def addFrame(self, ftype, frameBytes): # ftype: 0-I, 1-P
self.frames.append( Frm(ftype, frameBytes) )
def saveFile(self, fname, fps):
totalFrameChunksSize = sum(map(lambda f: f.chunkSize(), self.frames))
off = 4
maxsize = 1000
for fm in self.frames:
fm.offset = off
sz = fm.chunkSize()
off += sz
if sz > maxsize: maxsize = sz
movi = List(b'movi', self.frames)
idx1 = Chunk(b'idx1', b''.join(map(lambda x: x.indexEntry(), self.frames)))
X,Y = self.bmi.biWidth, self.bmi.biHeight
nframes = len(self.frames)
ah = AviHeader(1000000 // fps, maxsize*fps, 0, 16, nframes, 0, 1, maxsize + 256, X,Y, 0,0,0,0)
sh = StreamHeader(b'vids', self.fourcc, 0, 0, 0, 1, fps,
0, nframes, maxsize + 256, 10000, 0, ctypes.wintypes.SMALL_RECT(0,0,X,Y))
strl = List(b'strl', [Chunk(b'strh', bytes(sh)), Chunk(b'strf', bytes(self.bmi))])
hdrl = List(b'hdrl', [Chunk(b'avih', bytes(ah)), strl])
riff = List(b'AVI ', [hdrl, movi, idx1], b'RIFF')
with open(fname, 'wb') as f:
riff.write(f)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment