blind-from-video.c - blind - suckless command-line video editing utility
(HTM) git clone git://git.suckless.org/blind
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) README
(DIR) LICENSE
---
blind-from-video.c (7228B)
---
1 /* See LICENSE file for copyright and license details. */
2 #include "common.h"
3
4 USAGE("[-F pixel-format] [-r frame-rate] [-w width -h height] [-dL] input-file [output-file]")
5
6 static int draft = 0;
7 static void (*convert_segment)(char *buf, size_t n, int fd, const char *file);
8
9 static void
10 read_metadata(FILE *fp, char *fname, size_t *width, size_t *height)
11 {
12 char *line = NULL;
13 size_t size = 0;
14 ssize_t len;
15 char *p;
16
17 while ((len = getline(&line, &size, fp)) != -1) {
18 if (len && line[len - 1])
19 line[--len] = '\0';
20 p = strchr(line, '=') + 1;
21 if (strstr(line, "width=") == line) {
22 if (tozu(p, 1, SIZE_MAX, width))
23 eprintf("invalid width: %s\n", p);
24 } else if (strstr(line, "height=") == line) {
25 if (tozu(p, 1, SIZE_MAX, height))
26 eprintf("invalid height: %s\n", p);
27 }
28 }
29
30 if (ferror(fp))
31 eprintf("getline %s:", fname);
32 free(line);
33
34 if (!*width || !*height)
35 eprintf("could not get all required metadata\n");
36 }
37
38 static void
39 get_metadata(char *file, size_t *width, size_t *height)
40 {
41 FILE *fp;
42 int fd, pipe_rw[2];
43 pid_t pid;
44 int status;
45
46 epipe(pipe_rw);
47 pid = efork();
48
49 if (!pid) {
50 pdeath(SIGKILL);
51 fd = eopen(file, O_RDONLY);
52 edup2(fd, STDIN_FILENO);
53 close(fd);
54 close(pipe_rw[0]);
55 edup2(pipe_rw[1], STDOUT_FILENO);
56 close(pipe_rw[1]);
57 eexeclp("ffprobe", "ffprobe", "-v", "quiet", "-show_streams",
58 "-select_streams", "v", "-", NULL);
59 }
60
61 close(pipe_rw[1]);
62 fp = fdopen(pipe_rw[0], "rb");
63 if (!fp)
64 eprintf("fdopen <subprocess>:");
65 read_metadata(fp, file, width, height);
66 fclose(fp);
67 close(pipe_rw[0]);
68
69 ewaitpid(pid, &status, 0);
70 if (status)
71 exit(1);
72 }
73
74 #define CONVERT_SEGMENT(TYPE)\
75 do {\
76 typedef TYPE pixel_t[4];\
77 size_t i, ptr;\
78 TYPE y, u, v, max = (TYPE)0xFF00L, ymax = (TYPE)0xDAF4L;\
79 TYPE r, g, b;\
80 pixel_t pixels[1024];\
81 uint16_t *pix;\
82 if (draft) {\
83 for (ptr = i = 0; ptr < n; ptr += 8) {\
84 pix = (uint16_t *)(buf + ptr);\
85 pixels[i][3] = 1;\
86 y = (TYPE)((long int)(le16toh(pix[1])) - 0x1001L);\
87 u = (TYPE)((long int)(le16toh(pix[2])) - 0x8000L);\
88 v = (TYPE)((long int)(le16toh(pix[3])) - 0x8000L);\
89 scaled_yuv_to_ciexyz(y, u, v, pixels[i] + 0,\
90 pixels[i] + 1, pixels[i] + 2);\
91 if (++i == 1024) {\
92 i = 0;\
93 ewriteall(fd, pixels, sizeof(pixels), file);\
94 }\
95 }\
96 } else {\
97 for (ptr = i = 0; ptr < n; ptr += 8) {\
98 pix = (uint16_t *)(buf + ptr);\
99 pixels[i][3] = le16toh(pix[0]) / max;\
100 pixels[i][3] = CLIP(0, pixels[i][3], 1);\
101 y = (TYPE)((long int)le16toh(pix[1]) - 0x1001L) / ymax;\
102 u = (TYPE)((long int)le16toh(pix[2]) - 0x8000L) / max;\
103 v = (TYPE)((long int)le16toh(pix[3]) - 0x8000L) / max;\
104 yuv_to_srgb(y, u, v, &r, &g, &b);\
105 r = srgb_decode(r);\
106 g = srgb_decode(g);\
107 b = srgb_decode(b);\
108 srgb_to_ciexyz(r, g, b, pixels[i] + 0, pixels[i] + 1, pixels[i] + 2);\
109 if (++i == 1024) {\
110 i = 0;\
111 ewriteall(fd, pixels, sizeof(pixels), file);\
112 }\
113 }\
114 }\
115 if (i)\
116 ewriteall(fd, pixels, i * sizeof(*pixels), file);\
117 } while (0)
118
119 static void convert_segment_xyza (char *buf, size_t n, int fd, const char *file) {CONVERT_SEGMENT(double);}
120 static void convert_segment_xyzaf(char *buf, size_t n, int fd, const char *file) {CONVERT_SEGMENT(float);}
121
122 static void
123 convert(const char *infile, int outfd, const char *outfile, size_t width, size_t height, const char *frame_rate)
124 {
125 char geometry[2 * INTSTRLEN(size_t) + 2], buf[BUFSIZ];
126 const char *cmd[13];
127 int status, pipe_rw[2];
128 size_t i = 0, n, ptr;
129 pid_t pid;
130
131 cmd[i++] = "ffmpeg";
132 cmd[i++] = "-i", cmd[i++] = infile;
133 cmd[i++] = "-f", cmd[i++] = "rawvideo";
134 cmd[i++] = "-pix_fmt", cmd[i++] = "ayuv64le";
135 if (width && height) {
136 sprintf(geometry, "%zux%zu", width, height);
137 cmd[i++] = "-s:v", cmd[i++] = geometry;
138 }
139 if (frame_rate)
140 cmd[i++] = "-r", cmd[i++] = frame_rate;
141 cmd[i++] = "-";
142 cmd[i++] = NULL;
143
144 epipe(pipe_rw);
145 pid = efork();
146
147 if (!pid) {
148 pdeath(SIGKILL);
149 close(pipe_rw[0]);
150 edup2(pipe_rw[1], STDOUT_FILENO);
151 close(pipe_rw[1]);
152 eexecvp("ffmpeg", (char **)(void *)cmd);
153 }
154
155 close(pipe_rw[1]);
156
157 if (convert_segment) {
158 for (ptr = 0;;) {
159 if (!(n = eread(pipe_rw[0], buf + ptr, sizeof(buf) - ptr, "<subprocess>")))
160 break;
161 ptr += n;
162 n = ptr - (ptr % 8);
163 convert_segment(buf, n, outfd, outfile);
164 memmove(buf, buf + n, ptr -= n);
165 }
166 if (ptr)
167 eprintf("<subprocess>: incomplete frame\n");
168 } else {
169 while ((n = eread(pipe_rw[0], buf, sizeof(buf), "<subprocess>")))
170 ewriteall(outfd, buf, (size_t)n, outfile);
171 }
172
173 close(pipe_rw[0]);
174 ewaitpid(pid, &status, 0);
175 if (status)
176 exit(1);
177 }
178
179 int
180 main(int argc, char *argv[])
181 {
182 size_t width = 0, height = 0, frames;
183 char head[STREAM_HEAD_MAX];
184 char *frame_rate = NULL;
185 char *infile;
186 const char *outfile;
187 char *data;
188 const char *pixfmt = "xyza";
189 ssize_t headlen;
190 size_t length, frame_size, pixel_size;
191 int outfd, skip_length = 0;
192 struct stat st;
193
194 ARGBEGIN {
195 case 'd':
196 draft = 1;
197 break;
198 case 'L':
199 skip_length = 1;
200 break;
201 case 'F':
202 pixfmt = UARGF();
203 break;
204 case 'r':
205 frame_rate = UARGF();
206 break;
207 case 'w':
208 width = etozu_flag('w', UARGF(), 1, SIZE_MAX);
209 break;
210 case 'h':
211 height = etozu_flag('h', UARGF(), 1, SIZE_MAX);
212 break;
213 default:
214 usage();
215 } ARGEND;
216
217 if (argc < 1 || argc > 2 || !width != !height)
218 usage();
219
220 infile = argv[0];
221 outfile = argv[1] ? argv[1] : "-";
222
223 pixfmt = get_pixel_format(pixfmt, "xyza");
224 if (!strcmp(pixfmt, "xyza")) {
225 convert_segment = convert_segment_xyza;
226 pixel_size = 4 * sizeof(double);
227 } else if (!strcmp(pixfmt, "xyza f")) {
228 convert_segment = convert_segment_xyzaf;
229 pixel_size = 4 * sizeof(float);
230 } else if (!strcmp(pixfmt, "raw0")) {
231 convert_segment = NULL;
232 pixel_size = 4 * sizeof(uint16_t);
233 } else {
234 eprintf("pixel format %s is not supported, try xyza or raw0 and blind-convert\n", pixfmt);
235 }
236
237 if (!width)
238 get_metadata(infile, &width, &height);
239 if (width > SIZE_MAX / height)
240 eprintf("video frame too large\n");
241 frame_size = width * height;
242 if (pixel_size > SIZE_MAX / frame_size)
243 eprintf("video frame too large\n");
244 frame_size *= pixel_size;
245
246 if (!strcmp(outfile, "-")) {
247 outfile = "<stdout>";
248 outfd = STDOUT_FILENO;
249 if (!skip_length)
250 eprintf("standard out as output file is only allowed with -L\n");
251 } else {
252 outfd = eopen(outfile, O_RDWR | O_CREAT | O_TRUNC, 0666);
253 }
254
255 if (skip_length) {
256 SPRINTF_HEAD_ZN(head, 0, width, height, pixfmt, &headlen);
257 ewriteall(outfd, head, (size_t)headlen, outfile);
258 }
259
260 convert(infile, outfd, outfile, width, height, frame_rate);
261
262 if (outfd == STDOUT_FILENO)
263 return 0;
264
265 if (fstat(outfd, &st))
266 eprintf("fstat %s:", outfile);
267 length = (size_t)(st.st_size);
268
269 if (skip_length)
270 length -= (size_t)headlen;
271 if (length % frame_size)
272 eprintf("<subprocess>: incomplete frame\n");
273 frames = length / frame_size;
274
275 if (!skip_length) {
276 SPRINTF_HEAD_ZN(head, frames, width, height, pixfmt, &headlen);
277 ewriteall(outfd, head, (size_t)headlen, outfile);
278 data = mmap(0, length + (size_t)headlen, PROT_READ | PROT_WRITE, MAP_SHARED, outfd, 0);
279 memmove(data + headlen, data, length);
280 memcpy(data, head, (size_t)headlen);
281 munmap(data, length + (size_t)headlen);
282 }
283
284 close(outfd);
285 return 0;
286 }