1+ #ifndef __AVI_WRITER_H__
2+ #define __AVI_WRITER_H__
3+
4+ #include <stdint.h>
5+ #include <stdio.h>
6+ #include <stdlib.h>
7+ #include <string.h>
8+
9+ #include "stable-diffusion.h"
10+
11+ #ifndef INCLUDE_STB_IMAGE_WRITE_H
12+ #include "stb_image_write.h"
13+ #endif
14+
15+ typedef struct {
16+ uint32_t offset ;
17+ uint32_t size ;
18+ } avi_index_entry ;
19+
20+ // Write 32-bit little-endian integer
21+ void write_u32_le (FILE * f , uint32_t val ) {
22+ fwrite (& val , 4 , 1 , f );
23+ }
24+
25+ // Write 16-bit little-endian integer
26+ void write_u16_le (FILE * f , uint16_t val ) {
27+ fwrite (& val , 2 , 1 , f );
28+ }
29+
30+ /**
31+ * Create an MJPG AVI file from an array of sd_image_t images.
32+ * Images are encoded to JPEG using stb_image_write.
33+ *
34+ * @param filename Output AVI file name.
35+ * @param images Array of input images.
36+ * @param num_images Number of images in the array.
37+ * @param fps Frames per second for the video.
38+ * @param quality JPEG quality (0-100).
39+ * @return 0 on success, -1 on failure.
40+ */
41+ int create_mjpg_avi_from_sd_images (const char * filename , sd_image_t * images , int num_images , int fps , int quality = 90 ) {
42+ if (num_images == 0 ) {
43+ fprintf (stderr , "Error: Image array is empty.\n" );
44+ return -1 ;
45+ }
46+
47+ FILE * f = fopen (filename , "wb" );
48+ if (!f ) {
49+ perror ("Error opening file for writing" );
50+ return -1 ;
51+ }
52+
53+ uint32_t width = images [0 ].width ;
54+ uint32_t height = images [0 ].height ;
55+ uint32_t channels = images [0 ].channel ;
56+ if (channels != 3 && channels != 4 ) {
57+ fprintf (stderr , "Error: Unsupported channel count: %u\n" , channels );
58+ fclose (f );
59+ return -1 ;
60+ }
61+
62+ // --- RIFF AVI Header ---
63+ fwrite ("RIFF" , 4 , 1 , f );
64+ long riff_size_pos = ftell (f );
65+ write_u32_le (f , 0 ); // Placeholder for file size
66+ fwrite ("AVI " , 4 , 1 , f );
67+
68+ // 'hdrl' LIST (header list)
69+ fwrite ("LIST" , 4 , 1 , f );
70+ write_u32_le (f , 4 + 8 + 56 + 8 + 4 + 8 + 56 + 8 + 40 );
71+ fwrite ("hdrl" , 4 , 1 , f );
72+
73+ // 'avih' chunk (AVI main header)
74+ fwrite ("avih" , 4 , 1 , f );
75+ write_u32_le (f , 56 );
76+ write_u32_le (f , 1000000 / fps ); // Microseconds per frame
77+ write_u32_le (f , 0 ); // Max bytes per second
78+ write_u32_le (f , 0 ); // Padding granularity
79+ write_u32_le (f , 0x110 ); // Flags (HASINDEX | ISINTERLEAVED)
80+ write_u32_le (f , num_images ); // Total frames
81+ write_u32_le (f , 0 ); // Initial frames
82+ write_u32_le (f , 1 ); // Number of streams
83+ write_u32_le (f , width * height * 3 ); // Suggested buffer size
84+ write_u32_le (f , width );
85+ write_u32_le (f , height );
86+ write_u32_le (f , 0 ); // Reserved
87+ write_u32_le (f , 0 ); // Reserved
88+ write_u32_le (f , 0 ); // Reserved
89+ write_u32_le (f , 0 ); // Reserved
90+
91+ // 'strl' LIST (stream list)
92+ fwrite ("LIST" , 4 , 1 , f );
93+ write_u32_le (f , 4 + 8 + 56 + 8 + 40 );
94+ fwrite ("strl" , 4 , 1 , f );
95+
96+ // 'strh' chunk (stream header)
97+ fwrite ("strh" , 4 , 1 , f );
98+ write_u32_le (f , 56 );
99+ fwrite ("vids" , 4 , 1 , f ); // Stream type: video
100+ fwrite ("MJPG" , 4 , 1 , f ); // Codec: Motion JPEG
101+ write_u32_le (f , 0 ); // Flags
102+ write_u16_le (f , 0 ); // Priority
103+ write_u16_le (f , 0 ); // Language
104+ write_u32_le (f , 0 ); // Initial frames
105+ write_u32_le (f , 1 ); // Scale
106+ write_u32_le (f , fps ); // Rate
107+ write_u32_le (f , 0 ); // Start
108+ write_u32_le (f , num_images ); // Length
109+ write_u32_le (f , width * height * 3 ); // Suggested buffer size
110+ write_u32_le (f , (uint32_t )-1 ); // Quality
111+ write_u32_le (f , 0 ); // Sample size
112+ write_u16_le (f , 0 ); // rcFrame.left
113+ write_u16_le (f , 0 ); // rcFrame.top
114+ write_u16_le (f , 0 ); // rcFrame.right
115+ write_u16_le (f , 0 ); // rcFrame.bottom
116+
117+ // 'strf' chunk (stream format: BITMAPINFOHEADER)
118+ fwrite ("strf" , 4 , 1 , f );
119+ write_u32_le (f , 40 );
120+ write_u32_le (f , 40 ); // biSize
121+ write_u32_le (f , width );
122+ write_u32_le (f , height );
123+ write_u16_le (f , 1 ); // biPlanes
124+ write_u16_le (f , 24 ); // biBitCount
125+ fwrite ("MJPG" , 4 , 1 , f ); // biCompression (FOURCC)
126+ write_u32_le (f , width * height * 3 ); // biSizeImage
127+ write_u32_le (f , 0 ); // XPelsPerMeter
128+ write_u32_le (f , 0 ); // YPelsPerMeter
129+ write_u32_le (f , 0 ); // Colors used
130+ write_u32_le (f , 0 ); // Colors important
131+
132+ // 'movi' LIST (video frames)
133+ long movi_list_pos = ftell (f );
134+ fwrite ("LIST" , 4 , 1 , f );
135+ long movi_size_pos = ftell (f );
136+ write_u32_le (f , 0 ); // Placeholder for movi size
137+ fwrite ("movi" , 4 , 1 , f );
138+
139+ avi_index_entry * index = (avi_index_entry * )malloc (sizeof (avi_index_entry ) * num_images );
140+ if (!index ) {
141+ fclose (f );
142+ return -1 ;
143+ }
144+
145+ // Encode and write each frame as JPEG
146+ struct {
147+ uint8_t * buf ;
148+ size_t size ;
149+ } jpeg_data ;
150+
151+ for (int i = 0 ; i < num_images ; i ++ ) {
152+ jpeg_data .buf = NULL ;
153+ jpeg_data .size = 0 ;
154+
155+ // Callback function to collect JPEG data into memory
156+ auto write_to_buf = [](void * context , void * data , int size ) {
157+ auto jd = (decltype (jpeg_data )* )context ;
158+ jd -> buf = (uint8_t * )realloc (jd -> buf , jd -> size + size );
159+ memcpy (jd -> buf + jd -> size , data , size );
160+ jd -> size += size ;
161+ };
162+
163+ // Encode to JPEG in memory
164+ stbi_write_jpg_to_func (
165+ write_to_buf ,
166+ & jpeg_data ,
167+ images [i ].width ,
168+ images [i ].height ,
169+ channels ,
170+ images [i ].data ,
171+ quality );
172+
173+ // Write '00dc' chunk (video frame)
174+ fwrite ("00dc" , 4 , 1 , f );
175+ write_u32_le (f , jpeg_data .size );
176+ index [i ].offset = ftell (f ) - 8 ;
177+ index [i ].size = jpeg_data .size ;
178+ fwrite (jpeg_data .buf , 1 , jpeg_data .size , f );
179+
180+ // Align to even byte size
181+ if (jpeg_data .size % 2 )
182+ fputc (0 , f );
183+
184+ free (jpeg_data .buf );
185+ }
186+
187+ // Finalize 'movi' size
188+ long cur_pos = ftell (f );
189+ long movi_size = cur_pos - movi_size_pos - 4 ;
190+ fseek (f , movi_size_pos , SEEK_SET );
191+ write_u32_le (f , movi_size );
192+ fseek (f , cur_pos , SEEK_SET );
193+
194+ // Write 'idx1' index
195+ fwrite ("idx1" , 4 , 1 , f );
196+ write_u32_le (f , num_images * 16 );
197+ for (int i = 0 ; i < num_images ; i ++ ) {
198+ fwrite ("00dc" , 4 , 1 , f );
199+ write_u32_le (f , 0x10 );
200+ write_u32_le (f , index [i ].offset );
201+ write_u32_le (f , index [i ].size );
202+ }
203+
204+ // Finalize RIFF size
205+ cur_pos = ftell (f );
206+ long file_size = cur_pos - riff_size_pos - 4 ;
207+ fseek (f , riff_size_pos , SEEK_SET );
208+ write_u32_le (f , file_size );
209+ fseek (f , cur_pos , SEEK_SET );
210+
211+ fclose (f );
212+ free (index );
213+
214+ return 0 ;
215+ }
216+
217+ #endif // __AVI_WRITER_H__
0 commit comments