Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
AVI file format
#4
(03-22-2025, 08:00 PM)Petr Wrote: I've been wanting this for a really long time. A program that creates a valid AVI format with sound. The following program only creates (does not play) an uncompressed AVI file, which also contains uncompressed stereo audio. For this reason, prepare about 180 megabytes of free disk space for the test. I think it's already quite complicated, so if I add compression to it, it would be much more complicated. I will deal with compression, but I don't know how far I will get, because I encounter many obstacles.
I must warn you that Windows Media Player blurs the image due to the player's internal filters, VLC player has the best outputs. MPC-HC plays it too, but it rotates the image. I can rotate the image in AVI, but then it is rotated again in Windows Media Player and in VLC. It's kind of a beginning... and also an extreme. First I deal with compression in GIF and save every byte and now tens of MegaBytes are no big deal...
It's uncompressed, so expect a data rate somewhere around 60 megabytes per second. QB64PE can handle that speed just fine.
Code: (Select All)

' Program creates an uncompressed AVI file with uncompressed animation and uncompressed audio.
' The uncompressed format is easier to understand, though I discovered that the AVI file itself
' is a topic suitable for psychiatry... Smile
' -------------------------
' Structure Definitions
' -------------------------
Type AVIMainHeader
    microSecPerFrame As Long '    Microseconds per frame
    maxBytesPerSec As Long '      Maximum data rate
    paddingGranularity As Long '  Padding granularity
    flags As Long '              AVI flags (e.g., HASINDEX)
    totalFrames As Long '        Total number of frames in the video
    initialFrames As Long '      Initial frames (typically 0)
    streams As Long '            Number of streams (video, audio, etc.)
    suggestedBufferSize As Long ' Suggested buffer size for playback
    width As Long '              Video width in pixels
    height As Long '              Video height in pixels
    reserved As String * 16 '    Reserved bytes (set to 0)
End Type
Type AVIStreamHeader
    fccType As String * 4 '      Stream type (e.g., "vids" or "auds")
    fccHandler As String * 4 '    Codec handler (e.g., "DIB " for uncompressed video)
    flags As Long '              Stream flags
    priority As Integer '        Priority (unused)
    language As Integer '        Language code (unused)
    initialFrames As Long '      Initial frames (unused)
    scale As Long '              Time scale for the stream
    rate As Long '                Data rate (scale/rate = FPS for video)
    start As Long '              Start time (usually 0)
    length As Long '              Length of the stream (in time units)
    suggestedBufferSize As Long ' Suggested buffer size for the stream
    quality As Long '            Quality indicator (-1 for default)
    sampleSize As Long '          Sample size (0 for video, nonzero for audio)
    frameLeft As Long '          For video: left coordinate of the source rectangle
    frameTop As Long '            For video: top coordinate of the source rectangle
    frameRight As Long '          For video: right coordinate of the source rectangle
    frameBottom As Long '        For video: bottom coordinate of the source rectangle
End Type
Type BITMAPINFOHEADER
    size As Long '                Size of this header (40 bytes)
    width As Long '              Width of the image in pixels
    height As Long '              Height of the image (negative for top–down)
    planes As Integer '          Number of color planes (must be 1)
    bitCount As Integer '        Bits per pixel (24 for 24-bit, 32 for 32-bit)
    compression As Long '        Compression method (0 = BI_RGB for uncompressed)
    sizeImage As Long '          Size of the image data in bytes
    xPelsPerMeter As Long '      Horizontal resolution (pixels per meter)
    yPelsPerMeter As Long '      Vertical resolution (pixels per meter)
    clrUsed As Long '            Number of colors used (0 = all)
    clrImportant As Long '        Number of important colors (0 = all)
End Type
Type WAVEFORMATEX
    wFormatTag As Integer '      Audio format code (1 = PCM)
    nChannels As Integer '        Number of audio channels (2 for stereo)
    nSamplesPerSec As Long '      Sample rate in Hz
    nAvgBytesPerSec As Long '    Average bytes per second
    nBlockAlign As Integer '      Block alignment in bytes
    wBitsPerSample As Integer '  Bits per sample (16 for 16-bit PCM)
    cbSize As Integer '          Extra size (0 for PCM)
End Type
' -------------------------
' Video Constants
' -------------------------
Const vidFPS = 30 '              Frames per second for the video
Const bytesPerPixel = 4 '        Each pixel in 32-bit image (BGRA) takes 4 bytes
' -------------------------
' Video Parameters
' -------------------------
Dim numFrames As Long: numFrames = 90 '                                      90 frames = 3 seconds of video
Dim frameWidth As Long: frameWidth = 800 '                                    Video width in pixels
Dim frameHeight As Long: frameHeight = 600 '                                  Video height in pixels
Dim frameSize As Long: frameSize = frameWidth * frameHeight * bytesPerPixel ' Total size of one 32-bit frame
' -------------------------
' Create Test Frame Array
' -------------------------
Dim Frames(1 To numFrames) As String
Dim i As Long, s As String, sm As _MEM
s = Space$(frameSize)
$If WIN Then
    pad$ = "\"
$Else
    pad$ = "/"
$End If
fn$ = _CWD$ + pad$ + "VideoTest.avi"
Print "Generating file "; fn$
preDest = _Dest '                                                Save current destination settings
For i = 1 To numFrames
    Dim img As Long, img2 As Long
    img2 = _NewImage(frameWidth, frameHeight, 32)
    _Dest img2 '                                                  Set current destination to img2
    Cls '                                                        Set not transparent background in frame
    _PrintMode _KeepBackground
    ' Draw 100 random colored lines
    For x = 1 To 100
        Line (Rnd * 50, Rnd * 50)-(Rnd * 750, Rnd * 550), _RGB32(Rnd * 255, Rnd * 255, Rnd * 255), B
    Next
    '                                                            Print frame number text at a fixed position
    _PrintString (350, 250), "Video Frame " + Str$(i), img2
    img = _NewImage(frameWidth, frameHeight, 32)
    ' For VLC Player use this line:
    ' _PutImage (0, 0), img2, img, (0, frameHeight)-(frameWidth, 0)
    ' For WMP, MPC-HC Player use:
    _PutImage (0, 0), img2, img
    _FreeImage img2
    sm = _MemImage(img)
    _MemGet sm, sm.OFFSET, s
    _MemFree sm
    _FreeImage img
    Frames(i) = s ' Store frame data as string
Next i
_Dest preDest '    Restore previous destination
' -------------------------
' Simulate Audio
' -------------------------
Dim sampleRate As Long: sampleRate = 44100 '                                          Audio sample rate in Hz
Dim totalSeconds As Long: totalSeconds = numFrames \ vidFPS '                        Duration of video in seconds
Dim totalSamplesPerChannel As Long: totalSamplesPerChannel = totalSeconds * sampleRate
Dim totalSamples As Long: totalSamples = totalSamplesPerChannel * 2 '                Stereo: two channels
ReDim audioSamples(1 To totalSamples) As Integer
'                                                                            Generate a tone with panning effect (from left to right)
Dim freq As Single: freq = 500 '                                            Starting frequency in Hz
Dim As Integer SampleL, SampleR '                                            Variables for left and right channel samples
Dim t As Single, normAmp As Single: normAmp = 0.9
For i = 1 To totalSamplesPerChannel - 1
    t = i / sampleRate
    '                                                                        Calculate pan value from 0 (left) to 1 (right)
    pan = i / (totalSamplesPerChannel - 1)
    '                                                                        Left channel amplitude = 0.9*(1 - pan), right channel amplitude = 0.9*pan
    If Rnd * 10 > 5 Then f = 5 Else f = -5
    freq = freq + f
    SampleL = 32767 * 0.9 * (1 - pan) * Sin(2 * 3.14159 * freq * t)
    SampleR = 32767 * 0.9 * pan * Sin(2 * 3.14159 * freq * t)
    audioSamples(2 * i + 1) = SampleL
    audioSamples(2 * i + 2) = SampleR
Next i
' ---------------------------
' Create AVI file with audio
' ---------------------------
If _FileExists("VideoTest.avi") Then Kill "VideoTest.avi"
CreateAVI Frames(), frameWidth, frameHeight, numFrames, audioSamples(), sampleRate
Print "File created. Open it with your media player program. Recommended is VLC Player."
' -------------------------------------------------------------
' Function to write an AVI file with video + audio (16-bit PCM)
' -------------------------------------------------------------
Sub CreateAVI (Frames() As String, frameWidth As Long, frameHeight As Long, _
              numFrames As Long, audioSamples() As Integer, sampleRate As Long)
    Dim frameSize As Long: frameSize = frameWidth * frameHeight * bytesPerPixel
    Dim fileNum As Integer: fileNum = FreeFile
    Open "VideoTest.avi" For Binary As #fileNum
    Dim videoDuration As Long
    videoDuration = numFrames \ vidFPS
    Dim totalAudioSamples As Long
    totalAudioSamples = UBound(audioSamples)
    Dim stereoFrames As Long
    stereoFrames = totalAudioSamples \ 2
    ' -------------------------------------------
    ' Define chunk index structure for AVI index
    ' -------------------------------------------
    Type ChunkIndex
        chunkID As String * 4 ' Chunk identifier (e.g., "00db" for video, "01wb" for audio)
        flags As Long '        Flags (e.g., keyframe flag)
        offset As Long '        Offset of chunk data from start of 'movi' section
        size As Long '          Size of chunk data in bytes
    End Type
    Dim maxChunks As Long
    maxChunks = numFrames + videoDuration + 10
    Dim idxArr(1 To maxChunks) As ChunkIndex
    Dim idxCount As Long: idxCount = 0
    ' --------------------------------------------------------
    ' RIFF and AVI chunks
    ' --------------------------------------------------------
    Dim riffHeader As String: riffHeader = "RIFF"
    Dim tmpStr As String: tmpStr = riffHeader
    Put #fileNum, , tmpStr
    Dim riffSizePos As Long: riffSizePos = LOF(fileNum) + 1
    Dim dummyLong As Long: dummyLong = 0
    Put #fileNum, , dummyLong '  placeholder for RIFF size
    Dim aviSig As String: aviSig = "AVI "
    tmpStr = aviSig
    Put #fileNum, , tmpStr
    ' --------------------------------------------------------
    ' LIST hdrl chunk (header list)
    ' --------------------------------------------------------
    Dim listHdrl As String: listHdrl = "LIST"
    tmpStr = listHdrl
    Put #fileNum, , tmpStr
    Dim hdrlSizePos As Long: hdrlSizePos = LOF(fileNum) + 1
    Put #fileNum, , dummyLong '  placeholder for hdrl size
    Dim hdrlSig As String: hdrlSig = "hdrl"
    tmpStr = hdrlSig
    Put #fileNum, , tmpStr
    ' --------------------------------------------------------
    ' avih chunk (Main AVI header)
    ' --------------------------------------------------------
    Dim avihChunkID As String: avihChunkID = "avih"
    tmpStr = avihChunkID
    Put #fileNum, , tmpStr
    Dim avihChunkSize As Long: avihChunkSize = 56
    Put #fileNum, , avihChunkSize
    Dim mainHdr As AVIMainHeader
    mainHdr.microSecPerFrame = 1000000 \ vidFPS
    mainHdr.maxBytesPerSec = frameSize * vidFPS
    mainHdr.paddingGranularity = 0
    mainHdr.flags = &H110 '            AVIF_HASINDEX flag
    mainHdr.totalFrames = numFrames
    mainHdr.initialFrames = 0
    mainHdr.streams = 2 '              2 streams: video + audio (stereo is 1 stream and mono also)
    mainHdr.suggestedBufferSize = frameSize
    mainHdr.width = frameWidth
    mainHdr.height = frameHeight
    mainHdr.reserved = String$(16, Chr$(0))
    Put #fileNum, , mainHdr
    ' --------------------------------------------------------
    ' LIST strl chunk for video stream
    ' --------------------------------------------------------
    Dim listStrlVid As String: listStrlVid = "LIST"
    tmpStr = listStrlVid
    Put #fileNum, , tmpStr
    Dim strlVidSizePos As Long: strlVidSizePos = LOF(fileNum) + 1
    Put #fileNum, , dummyLong
    Dim strlVidSig As String: strlVidSig = "strl"
    tmpStr = strlVidSig
    Put #fileNum, , tmpStr
    ' -- strh (video stream header)
    Dim strhVidID As String: strhVidID = "strh"
    tmpStr = strhVidID
    Put #fileNum, , tmpStr
    Dim strhVidSize As Long: strhVidSize = 64
    Put #fileNum, , strhVidSize
    Dim streamHdrVid As AVIStreamHeader
    streamHdrVid.fccType = "vids"
    streamHdrVid.fccHandler = "DIB " ' Uncompressed video (DIB)
    streamHdrVid.flags = 0
    streamHdrVid.priority = 0
    streamHdrVid.language = 0
    streamHdrVid.initialFrames = 0
    streamHdrVid.scale = 1
    streamHdrVid.rate = vidFPS
    streamHdrVid.start = 0
    streamHdrVid.length = numFrames
    streamHdrVid.suggestedBufferSize = frameSize
    streamHdrVid.quality = -1
    streamHdrVid.sampleSize = 0
    streamHdrVid.frameLeft = 0
    streamHdrVid.frameTop = 0
    streamHdrVid.frameRight = frameWidth
    streamHdrVid.frameBottom = frameHeight
    Put #fileNum, , streamHdrVid
    ' --------------------------------------------------------
    ' -- strf (video format chunk) -> BITMAPINFOHEADER
    ' --------------------------------------------------------
    Dim strfVidID As String: strfVidID = "strf"
    tmpStr = strfVidID
    Put #fileNum, , tmpStr
    Dim strfVidSize As Long: strfVidSize = 40
    Put #fileNum, , strfVidSize
    Dim bmpInfo As BITMAPINFOHEADER
    bmpInfo.size = 40
    bmpInfo.width = frameWidth
    bmpInfo.height = -frameHeight ' Negative height indicates top-down format
    bmpInfo.planes = 1
    bmpInfo.bitCount = 32
    bmpInfo.compression = 0 '      BI_RGB = uncompressed
    bmpInfo.sizeImage = frameSize
    bmpInfo.xPelsPerMeter = 0
    bmpInfo.yPelsPerMeter = 0
    bmpInfo.clrUsed = 0
    bmpInfo.clrImportant = 0
    Put #fileNum, , bmpInfo
    Dim currPos As Long
    currPos = LOF(fileNum) + 1
    Dim calcStrlVidSize As Long: calcStrlVidSize = currPos - strlVidSizePos - 4
    Seek #fileNum, strlVidSizePos
    Put #fileNum, , calcStrlVidSize
    Seek #fileNum, currPos
    ' --------------------------------------------------------
    ' LIST strl chunk for audio stream
    ' --------------------------------------------------------
    Dim listStrlAud As String: listStrlAud = "LIST"
    tmpStr = listStrlAud
    Put #fileNum, , tmpStr
    Dim strlAudSizePos As Long: strlAudSizePos = LOF(fileNum) + 1
    Put #fileNum, , dummyLong
    Dim strlAudSig As String: strlAudSig = "strl"
    tmpStr = strlAudSig
    Put #fileNum, , tmpStr
    ' --------------------------------------------------------
    ' -- strh (audio stream header)
    ' --------------------------------------------------------
    Dim strhAudID As String: strhAudID = "strh"
    tmpStr = strhAudID
    Put #fileNum, , tmpStr
    Dim strhAudSize As Long: strhAudSize = 64
    Put #fileNum, , strhAudSize
    Dim streamHdrAud As AVIStreamHeader
    streamHdrAud.fccType = "auds"
    streamHdrAud.fccHandler = String$(4, 0) ' For PCM audio, this is typically empty
    streamHdrAud.flags = 0
    streamHdrAud.priority = 0
    streamHdrAud.language = 0
    streamHdrAud.initialFrames = 0
    streamHdrAud.scale = 1
    streamHdrAud.rate = sampleRate
    streamHdrAud.start = 0
    streamHdrAud.length = stereoFrames
    streamHdrAud.suggestedBufferSize = sampleRate * 4 ' 2 channels x 2 bytes per sample (is used 16bit stereo waveform sound, 1 integer to 1 sample)
    streamHdrAud.quality = -1
    streamHdrAud.sampleSize = 4 ' 4 bytes per stereo sample (2 bytes left + 2 bytes right)
    streamHdrAud.frameLeft = 0
    streamHdrAud.frameTop = 0
    streamHdrAud.frameRight = 0
    streamHdrAud.frameBottom = 0
    Put #fileNum, , streamHdrAud
    ' --------------------------------------------------------
    ' -- strf (audio format chunk) -> WAVEFORMATEX (16 bytes)
    ' --------------------------------------------------------
    Dim strfAudID As String: strfAudID = "strf"
    tmpStr = strfAudID
    Put #fileNum, , tmpStr
    Dim strfAudSize As Long: strfAudSize = 16
    Put #fileNum, , strfAudSize
    Dim wf As WAVEFORMATEX
    wf.wFormatTag = 1
    wf.nChannels = 2
    wf.nSamplesPerSec = sampleRate
    wf.wBitsPerSample = 16
    wf.nBlockAlign = wf.nChannels * (wf.wBitsPerSample \ 8)
    wf.nAvgBytesPerSec = wf.nSamplesPerSec * wf.nBlockAlign
    wf.cbSize = 0
    Put #fileNum, , wf
    currPos = LOF(fileNum) + 1
    Dim calcStrlAudSize As Long: calcStrlAudSize = currPos - strlAudSizePos - 4
    Seek #fileNum, strlAudSizePos
    Put #fileNum, , calcStrlAudSize
    Seek #fileNum, currPos
    currPos = LOF(fileNum) + 1
    Dim hdrlSize As Long: hdrlSize = currPos - hdrlSizePos - 4
    Seek #fileNum, hdrlSizePos
    Put #fileNum, , hdrlSize
    Seek #fileNum, currPos
    ' ----------------------------------------------------------
    ' LIST movi chunk (contains the actual video and audio data)
    ' ----------------------------------------------------------
    Dim listMovi As String: listMovi = "LIST"
    tmpStr = listMovi
    Put #fileNum, , tmpStr
    Dim moviSizePos As Long: moviSizePos = LOF(fileNum) + 1
    Put #fileNum, , dummyLong
    Dim moviSig As String: moviSig = "movi"
    tmpStr = moviSig
    Put #fileNum, , tmpStr
    Dim moviDataStart As Long: moviDataStart = LOF(fileNum) + 1
    ' --------------------------------------------------------------
    ' Interleaved writing of video and audio chunks:
    ' For each second: 30 video frames and 1 audio chunk (1 second)  - this solution MUST NOT BE ALWAYS OPTIMAL! is possible writing sound data after every 5 th frame...
    ' --------------------------------------------------------------
    Dim framesPerSec As Long: framesPerSec = vidFPS
    Dim videoIndex As Long: videoIndex = 1
    Dim sec As Long
    For sec = 1 To videoDuration
        ' Write video chunks
        Dim f As Long
        For f = 1 To framesPerSec
            If videoIndex > numFrames Then Exit For
            Dim chunkOffset As Long
            chunkOffset = LOF(fileNum) - moviDataStart + 1
            Dim frameChunkID As String: frameChunkID = "00db"
            tmpStr = frameChunkID
            Put #fileNum, , tmpStr
            Dim currFrameSize As Long: currFrameSize = frameSize
            Put #fileNum, , currFrameSize
            Put #fileNum, , Frames(videoIndex)
            If (currFrameSize Mod 2) <> 0 Then
                Dim padByte As String: padByte = Chr$(0)
                Put #fileNum, , padByte
            End If
            idxCount = idxCount + 1
            idxArr(idxCount).chunkID = frameChunkID
            idxArr(idxCount).flags = &H10 ' Keyframe flag
            idxArr(idxCount).offset = chunkOffset
            idxArr(idxCount).size = currFrameSize
            videoIndex = videoIndex + 1
        Next f
        ' Write audio chunk for the current second
        Dim chunkOffsetAud As Long
        chunkOffsetAud = LOF(fileNum) - moviDataStart + 1
        Dim audioChunkID As String: audioChunkID = "01wb"
        tmpStr = audioChunkID
        Put #fileNum, , tmpStr
        Dim audioChunkSize As Long
        audioChunkSize = sampleRate * 2 * 2 ' 1 second = sampleRate * (2 channels x 2 bytes)
        Put #fileNum, , audioChunkSize
        Dim startAud As Long: startAud = (sec - 1) * (sampleRate * 2) + 1
        Dim endAud As Long: endAud = startAud + (sampleRate * 2) - 1
        If endAud > UBound(audioSamples) Then endAud = UBound(audioSamples)
        Dim k As Long
        Dim audioBuffer As String
        audioBuffer = ""
        Dim sampleValue As Integer
        Dim uVal As Long
        For k = startAud To endAud
            sampleValue = audioSamples(k)
            uVal = sampleValue And &HFFFF ' Convert to 16-bit two's complement representation
            audioBuffer = audioBuffer + Chr$(uVal And &HFF) + Chr$((uVal \ 256) And &HFF)
        Next k
        Put #fileNum, , audioBuffer
        If (audioChunkSize Mod 2) <> 0 Then
            Dim padByteAud As String: padByteAud = Chr$(0)
            Put #fileNum, , padByteAud
        End If
        idxCount = idxCount + 1
        idxArr(idxCount).chunkID = audioChunkID
        idxArr(idxCount).flags = 0
        idxArr(idxCount).offset = chunkOffsetAud
        idxArr(idxCount).size = audioChunkSize
    Next sec
    currPos = LOF(fileNum) + 1
    Dim moviSize As Long
    moviSize = currPos - moviSizePos - 4
    Seek #fileNum, moviSizePos
    Put #fileNum, , moviSize
    Seek #fileNum, currPos
    Dim riffSize As Long
    riffSize = LOF(fileNum) - 8 + 1
    Seek #fileNum, riffSizePos
    Put #fileNum, , riffSize
    Seek #fileNum, LOF(fileNum) + 1
    Dim idxHeader As String: idxHeader = "idx1"
    tmpStr = idxHeader
    Put #fileNum, , tmpStr
    Dim idxSize As Long: idxSize = idxCount * 16
    Put #fileNum, , idxSize
    Dim n As Long
    For n = 1 To idxCount
        Put #fileNum, , idxArr(n).chunkID
        Put #fileNum, , idxArr(n).flags
        Put #fileNum, , idxArr(n).offset
        Put #fileNum, , idxArr(n).size
    Next n
    Close #fileNum
End Sub
This is pretty awesome code.    I remember recently struggling with just the WAV & BMP file formats.    I bet this one took some hair pulling.      I'm really trying to figure out how I might use this.    My current QB64PE project is a Video convertor to turn MP4, AVI, MOV, etc...   Into a format that's playable on the Neo retro Commander X16 platform.    I didn't do any Video parsing on the source side in my own code, I let ffmpeg do that for me.    The output format I invented myself.     https://cx16forum.com/forum/viewtopic.php?t=8226
Reply


Messages In This Thread
AVI file format - by Petr - 03-22-2025, 08:00 PM
RE: AVI file format - by madscijr - 03-22-2025, 11:01 PM
RE: AVI file format - by mrbcx - 03-23-2025, 01:12 AM
RE: AVI file format - by ahenry3068 - 03-23-2025, 01:24 AM
RE: AVI file format - by madscijr - 03-23-2025, 02:26 AM
RE: AVI file format - by Petr - 03-23-2025, 08:58 AM
RE: AVI file format - by madscijr - 03-23-2025, 03:09 PM
RE: AVI file format - by mdijkens - 03-24-2025, 04:49 PM
RE: AVI file format - by madscijr - 03-24-2025, 06:13 PM
RE: AVI file format - by madscijr - 03-24-2025, 07:59 PM
RE: AVI file format - by Petr - 03-24-2025, 09:35 PM
RE: AVI file format - by madscijr - 03-24-2025, 09:55 PM
RE: AVI file format - by mdijkens - 03-25-2025, 08:05 AM
RE: AVI file format - by Petr - 03-25-2025, 09:20 AM
RE: AVI file format - by Steffan-68 - 03-25-2025, 05:25 PM
RE: AVI file format - by Petr - 03-25-2025, 06:14 PM
RE: AVI file format - by madscijr - 05-28-2025, 10:50 PM
RE: AVI file format - by Petr - 08-02-2025, 04:30 PM
RE: AVI file format - by madscijr - 08-03-2025, 01:03 AM

Possibly Related Threads…
Thread Author Replies Views Last Post
  APNG File Format Petr 5 567 11-20-2025, 02:32 PM
Last Post: ahenry3068
  GIF89a File Format Petr 6 1,370 03-04-2025, 01:20 AM
Last Post: a740g
  PCX file format Petr 13 3,451 03-01-2025, 10:52 PM
Last Post: Petr
  BMP File format Petr 8 1,786 02-23-2025, 07:54 PM
Last Post: Petr

Forum Jump:


Users browsing this thread: 1 Guest(s)