Last active
April 11, 2024 04:23
-
-
Save thedeemon/f6b58990c0ae6f45c03debc68c18a8d1 to your computer and use it in GitHub Desktop.
Write small AVI files with just the video stream
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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