blind

suckless command-line video editing utility
git clone git://git.suckless.org/blind
Log | Files | Refs | README | 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 }