blind-concat.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-concat.c (5171B)
---
1 /* See LICENSE file for copyright and license details. */
2 #include "common.h"
3
4 USAGE("[-o output-file [-j jobs]] first-stream ... last-stream")
5
6 static void
7 concat_to_stdout(int argc, char *argv[], const char *fname)
8 {
9 struct stream *streams;
10 size_t frames = 0;
11 int i;
12
13 streams = emalloc2((size_t)argc, sizeof(*streams));
14
15 for (i = 0; i < argc; i++) {
16 eopen_stream(streams + i, argv[i]);
17 if (i)
18 echeck_compat(streams + i, streams);
19 if (streams[i].frames > SIZE_MAX - frames)
20 eprintf("resulting video is too long\n");
21 frames += streams[i].frames;
22 }
23
24 streams->frames = frames;
25 fprint_stream_head(stdout, streams);
26 efflush(stdout, fname);
27
28 for (i = 0; i < argc; i++) {
29 esend_stream(streams + i, STDOUT_FILENO, fname);
30 close(streams[i].fd);
31 }
32
33 free(streams);
34 }
35
36 static void
37 concat_to_file(int argc, char *argv[], char *output_file)
38 {
39 struct stream stream, refstream;
40 int first = 1;
41 int fd = eopen(output_file, O_RDWR | O_CREAT | O_TRUNC, 0666);
42 char head[STREAM_HEAD_MAX];
43 ssize_t headlen;
44 size_t size;
45 off_t pos;
46 char *data;
47
48 for (; argc--; argv++) {
49 eopen_stream(&stream, *argv);
50
51 if (first) {
52 refstream = stream;
53 first = 1;
54 } else {
55 if (refstream.frames > SIZE_MAX - stream.frames)
56 eprintf("resulting video is too long\n");
57 refstream.frames += stream.frames;
58 echeck_compat(&stream, &refstream);
59 }
60
61 esend_stream(&stream, fd, output_file);
62 close(stream.fd);
63 }
64
65 SPRINTF_HEAD_ZN(head, stream.frames, stream.width, stream.height, stream.pixfmt, &headlen);
66 ewriteall(fd, head, (size_t)headlen, output_file);
67
68 size = (size_t)(pos = elseek(fd, 0, SEEK_CUR, output_file));
69 if ((uintmax_t)pos > SIZE_MAX)
70 eprintf("%s\n", strerror(EFBIG));
71
72 data = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
73 if (data == MAP_FAILED)
74 eprintf("mmap %s:", output_file);
75 memmove(data + headlen, data, size - (size_t)headlen);
76 memcpy(data, head, (size_t)headlen);
77 munmap(data, size);
78
79 close(fd);
80 }
81
82 static void
83 concat_to_file_parallel(int argc, char *argv[], char *output_file, size_t jobs)
84 {
85 #if !defined(HAVE_EPOLL)
86 int fd = eopen(output_file, O_WRONLY | O_CREAT | O_TRUNC, 0666);
87 if (fd != STDOUT_FILENO)
88 edup2(fd, STDOUT_FILENO);
89 concat_to_stdout(argc, argv, output_file);
90 #else
91 struct epoll_event *events;
92 struct stream *streams;
93 off_t *ptrs, ptr;
94 char head[STREAM_HEAD_MAX];
95 size_t frames = 0, next = 0, j;
96 ssize_t headlen;
97 int fd, i, n, pollfd;
98
99 if (jobs > (size_t)argc)
100 jobs = (size_t)argc;
101
102 fd = eopen(output_file, O_RDWR | O_CREAT | O_TRUNC, 0666);
103 events = emalloc2(jobs, sizeof(*events));
104 streams = emalloc2((size_t)argc, sizeof(*streams));
105 ptrs = emalloc2((size_t)argc, sizeof(*ptrs));
106
107 for (i = 0; i < argc; i++) {
108 eopen_stream(streams + i, argv[i]);
109 if (i)
110 echeck_compat(streams + i, streams);
111 if (streams[i].frames > SIZE_MAX - frames)
112 eprintf("resulting video is too long\n");
113 frames += streams[i].frames;
114 }
115
116 SPRINTF_HEAD_ZN(head, frames, streams->width, streams->height, streams->pixfmt, &headlen);
117
118 echeck_dimensions(streams, WIDTH | HEIGHT, NULL);
119 ptr = (off_t)headlen;
120 for (i = 0; i < argc; i++) {
121 ptrs[i] = ptr;
122 ptr += (off_t)streams->frames * (off_t)streams->frame_size;
123 }
124 if (ftruncate(fd, (off_t)ptr))
125 eprintf("ftruncate %s:", output_file);
126 fadvise_random(fd, (off_t)headlen, 0);
127
128 pollfd = epoll_create1(0);
129 if (pollfd == -1)
130 eprintf("epoll_create1:");
131
132 epwriteall(fd, head, (size_t)headlen, 0, output_file);
133 for (i = 0; i < argc; i++) {
134 epwriteall(fd, streams[i].buf, streams[i].ptr, ptrs[i], output_file);
135 ptrs[i] += (off_t)(streams[i].ptr);
136 streams[i].ptr = 0;
137 }
138
139 for (j = 0; j < jobs; j++, next++) {
140 events->events = EPOLLIN;
141 events->data.u64 = next;
142 if (epoll_ctl(pollfd, EPOLL_CTL_ADD, streams[next].fd, events)) {
143 if ((errno == ENOMEM || errno == ENOSPC) && j)
144 break;
145 eprintf("epoll_ctl:");
146 }
147 }
148 jobs = j;
149
150 while (jobs) {
151 n = epoll_wait(pollfd, events, (int)jobs, -1);
152 if (n < 0)
153 eprintf("epoll_wait:");
154 for (i = 0; i < n; i++) {
155 j = events[i].data.u64;
156 if (streams[j].ptr || eread_stream(streams + j, SIZE_MAX)) {
157 epwriteall(fd, streams[j].buf, streams[j].ptr, ptrs[j], output_file);
158 ptrs[j] += (off_t)(streams[j].ptr);
159 streams[j].ptr = 0;
160 continue;
161 }
162
163 close(streams[j].fd);
164 if (next < (size_t)argc) {
165 events->events = EPOLLIN;
166 events->data.u64 = next;
167 if (epoll_ctl(pollfd, EPOLL_CTL_ADD, streams[next].fd, events)) {
168 if ((errno == ENOMEM || errno == ENOSPC) && j)
169 break;
170 eprintf("epoll_ctl:");
171 }
172 next++;
173 } else {
174 jobs--;
175 }
176 }
177 }
178
179 close(pollfd);
180 free(events);
181 free(streams);
182 free(ptrs);
183 #endif
184 }
185
186 int
187 main(int argc, char *argv[])
188 {
189 char *output_file = NULL;
190 size_t jobs = 0;
191
192 ARGBEGIN {
193 case 'o':
194 output_file = UARGF();
195 break;
196 case 'j':
197 jobs = etozu_flag('j', UARGF(), 1, SHRT_MAX);
198 break;
199 default:
200 usage();
201 } ARGEND;
202
203 if (argc < 2 || (jobs && !output_file))
204 usage();
205
206 if (jobs)
207 concat_to_file_parallel(argc, argv, output_file, jobs);
208 else if (output_file)
209 concat_to_file(argc, argv, output_file);
210 else
211 concat_to_stdout(argc, argv, "<stdout>");
212
213 return 0;
214 }