1 // utils.c
2 // LiVES
3 // (c) G. Finch 2003 - 2020 <salsaman+lives@gmail.com>
4 // released under the GNU GPL 3 or later
5 // see file ../COPYING or www.gnu.org for licensing details
6
7 #include <fcntl.h>
8 #include <dirent.h>
9 #include <sys/statvfs.h>
10 #ifdef HAVE_LIBEXPLAIN
11 #include <libexplain/system.h>
12 #include <libexplain/read.h>
13 #endif
14 #include "main.h"
15 #include "interface.h"
16 #include "audio.h"
17 #include "resample.h"
18 #include "callbacks.h"
19 #include "cvirtual.h"
20
21 #define ASPECT_ALLOWANCE 0.005
22
23 typedef struct {
24 uint32_t hash;
25 char *key;
26 char *data;
27 } lives_speed_cache_t;
28
29 static boolean omute, osepwin, ofs, ofaded, odouble;
30
31 static int get_hex_digit(const char c) GNU_CONST;
32
33
34 /**
35 @brief: return filename from an open fd, freeing val first
36
37 in case of error function returns val
38
39 if fd is a buffered file then the function just returns the known name,
40 else the name is procured from /proc
41
42 call like: foo = filename_from_fd(foo,fd); lives_free(foo);
43 input param foo can be NULL or some (non-const) string buffer
44 if non-NULL the old value will be freed, so e.g
45
46 char *badfile = NULL;
47 while (condition) {
48 ....
49 if (failed) badfile = filename_from_fd(badfile, fd);
50 }
51 if (badfile != NULL) lives_free(badfile);
52
53 or:
54
55 char *badfile = NULL;
56 badfile = filename_from_fd(badfile, fd);
57 if (badfile == NULL) // error getting filename
58
59 **/
filename_from_fd(char * val,int fd)60 char *filename_from_fd(char *val, int fd) {
61 lives_file_buffer_t *fbuff = find_in_file_buffers(fd);
62 if (fbuff) {
63 return lives_strdup(fbuff->pathname);
64 } else {
65 char *fdpath;
66 char *fidi;
67 char rfdpath[PATH_MAX];
68 struct stat stb0, stb1;
69
70 ssize_t slen;
71
72 if (fstat(fd, &stb0)) return val;
73
74 fidi = lives_strdup_printf("%d", fd);
75 fdpath = lives_build_filename("/proc", "self", "fd", fidi, NULL);
76 lives_free(fidi);
77
78 if ((slen = lives_readlink(fdpath, rfdpath, PATH_MAX)) == -1) return val;
79 lives_free(fdpath);
80
81 lives_memset(rfdpath + slen, 0, 1);
82
83 if (stat(rfdpath, &stb1)) return val;
84 if (stb0.st_dev != stb1.st_dev) return val;
85 if (stb0.st_ino != stb1.st_ino) return val;
86 if (val) lives_free(val);
87 return lives_strdup(rfdpath);
88 }
89 }
90
91
92 // system calls
93
lives_open3(const char * pathname,int flags,mode_t mode)94 LIVES_GLOBAL_INLINE int lives_open3(const char *pathname, int flags, mode_t mode) {
95 return open(pathname, flags, mode);
96 }
97
98
lives_open2(const char * pathname,int flags)99 LIVES_GLOBAL_INLINE int lives_open2(const char *pathname, int flags) {
100 return open(pathname, flags);
101 }
102
103
lives_readlink(const char * path,char * buf,size_t bufsiz)104 LIVES_GLOBAL_INLINE ssize_t lives_readlink(const char *path, char *buf, size_t bufsiz) {
105 return readlink(path, buf, bufsiz);
106 }
107
108
lives_fsync(int fd)109 LIVES_GLOBAL_INLINE boolean lives_fsync(int fd) {
110 // ret TRUE on success
111 return !fsync(fd);
112 }
113
114
lives_sync(int times)115 LIVES_GLOBAL_INLINE void lives_sync(int times) {
116 for (int i = 0; i < times; i++) sync();
117 }
118
119
lives_setenv(const char * name,const char * value)120 LIVES_GLOBAL_INLINE boolean lives_setenv(const char *name, const char *value) {
121 // ret TRUE on success
122 #if IS_IRIX
123 char *env = lives_strdup_printf("%s=%s", name, val);
124 boolean ret = !putenv(env);
125 lives_free(env);
126 return ret;
127 #else
128 return !setenv(name, value, 1);
129 #endif
130 }
131
lives_unsetenv(const char * name)132 LIVES_GLOBAL_INLINE boolean lives_unsetenv(const char *name) {
133 // ret TRUE on success
134 #if IS_IRIX
135 char *env = lives_strdup_printf("%s=", name);
136 boolean ret = !putenv(env);
137 lives_free(env);
138 return ret;
139 #else
140 return !unsetenv(name);
141 #endif
142 }
143
144
lives_system(const char * com,boolean allow_error)145 int lives_system(const char *com, boolean allow_error) {
146 LiVESResponseType response;
147 int retval;
148 boolean cnorm = FALSE;
149
150 //g_print("doing: %s\n",com);
151
152 if (mainw && mainw->is_ready && !mainw->is_exiting &&
153 ((!mainw->multitrack && mainw->cursor_style == LIVES_CURSOR_NORMAL) ||
154 (mainw->multitrack && mainw->multitrack->cursor_style == LIVES_CURSOR_NORMAL))) {
155 cnorm = TRUE;
156 lives_set_cursor_style(LIVES_CURSOR_BUSY, NULL);
157 /* lives_widget_process_updates(LIVES_MAIN_WINDOW_WIDGET); */
158 }
159
160 do {
161 THREADVAR(com_failed) = FALSE;
162 response = LIVES_RESPONSE_NONE;
163 retval = system(com);
164 if (retval) {
165 char *msg = NULL;
166 THREADVAR(com_failed) = TRUE;
167 if (!allow_error) {
168 msg = lives_strdup_printf("lives_system failed with code %d: %s\n%s", retval, com,
169 #ifdef HAVE_LIBEXPLAIN
170 explain_system(com)
171 #else
172 ""
173 #endif
174 );
175 LIVES_ERROR(msg);
176 response = do_system_failed_error(com, retval, NULL, TRUE, FALSE);
177 }
178 #ifndef LIVES_NO_DEBUG
179 else {
180 msg = lives_strdup_printf("lives_system failed with code %d: %s (not an error)", retval, com);
181 LIVES_DEBUG(msg);
182 }
183 #endif
184 if (msg) lives_free(msg);
185 }
186 } while (response == LIVES_RESPONSE_RETRY);
187
188 if (cnorm) lives_set_cursor_style(LIVES_CURSOR_NORMAL, NULL);
189
190 return retval;
191 }
192
193
lives_popen(const char * com,boolean allow_error,char * buff,ssize_t buflen)194 ssize_t lives_popen(const char *com, boolean allow_error, char *buff, ssize_t buflen) {
195 // runs com, fills buff with a NUL terminated string (total length <= buflen)
196 // returns number of bytes read. If an error occurs during popen or fread
197 // then THREADVAR(com_failed) is set, and if allow_error is FALSE then an an error dialog is displayed to the user
198
199 // on error we return err as a -ve number
200
201 // id buflen is 0, then buff os cast from a textbuff, and the output will be appended to it
202
203 FILE *fp;
204 char *xbuff;
205 LiVESResponseType response;
206 ssize_t totlen = 0, xtotlen = 0;
207 size_t slen;
208 LiVESTextBuffer *tbuff = NULL;
209 LiVESTextIter end_iter;
210 boolean cnorm = FALSE;
211 int err = 0;
212
213 if (buflen <= 0) {
214 tbuff = (LiVESTextBuffer *)buff;
215 buflen = get_read_buff_size(BUFF_SIZE_READ_LARGE);
216 xbuff = (char *)lives_calloc(1, buflen);
217 } else {
218 xbuff = buff;
219 lives_memset(xbuff, 0, 1);
220 }
221 //g_print("doing: %s\n",com);
222
223 if (mainw && mainw->is_ready && !mainw->is_exiting &&
224 ((!mainw->multitrack && mainw->cursor_style == LIVES_CURSOR_NORMAL) ||
225 (mainw->multitrack && mainw->multitrack->cursor_style == LIVES_CURSOR_NORMAL))) {
226 cnorm = TRUE;
227 lives_set_cursor_style(LIVES_CURSOR_BUSY, NULL);
228 //lives_widget_process_updates(LIVES_MAIN_WINDOW_WIDGET);
229 }
230
231 do {
232 char *strg = NULL;
233 response = LIVES_RESPONSE_NONE;
234 THREADVAR(com_failed) = FALSE;
235 fflush(NULL);
236 fp = popen(com, "r");
237 if (!fp) {
238 err = errno;
239 } else {
240 while (1) {
241 strg = fgets(xbuff + totlen, tbuff ? buflen : buflen - totlen, fp);
242 err = ferror(fp);
243 if (err != 0 || !strg || !(*strg)) break;
244 slen = lives_strlen(xbuff);
245 if (tbuff) {
246 lives_text_buffer_get_end_iter(LIVES_TEXT_BUFFER(tbuff), &end_iter);
247 lives_text_buffer_insert(LIVES_TEXT_BUFFER(tbuff), &end_iter, xbuff, slen);
248 xtotlen += slen;
249 } else {
250 //lives_snprintf(buff + totlen, buflen - totlen, "%s", xbuff);
251 totlen = slen;
252 if (slen >= buflen - 1) break;
253 }
254 }
255 pclose(fp);
256 }
257
258 if (tbuff) {
259 lives_free(xbuff);
260 totlen = xtotlen;
261 }
262
263 if (err != 0) {
264 char *msg = NULL;
265 THREADVAR(com_failed) = TRUE;
266 if (!allow_error) {
267 msg = lives_strdup_printf("lives_popen failed p after %ld bytes with code %d: %s",
268 !strg ? 0 : lives_strlen(strg), err, com);
269 LIVES_ERROR(msg);
270 response = do_system_failed_error(com, err, NULL, TRUE, FALSE);
271 }
272 #ifndef LIVES_NO_DEBUG
273 else {
274 msg = lives_strdup_printf("lives_popen failed with code %d: %s (not an error)", err, com);
275 LIVES_DEBUG(msg);
276 }
277 #endif
278 if (msg) lives_free(msg);
279 }
280 } while (response == LIVES_RESPONSE_RETRY);
281
282 if (cnorm) lives_set_cursor_style(LIVES_CURSOR_NORMAL, NULL);
283 if (err != 0) return -ABS(err);
284 return totlen;
285 }
286
287
lives_fork(const char * com)288 lives_pgid_t lives_fork(const char *com) {
289 // returns a number which is the pgid to use for lives_killpg
290
291 // mingw - return PROCESS_INFORMATION * to use in GenerateConsoleCtrlEvent (?)
292
293 // to signal to sub process and all children
294 // TODO *** - error check
295
296 pid_t ret;
297
298 if (!(ret = fork())) {
299 setsid(); // create new session id
300 setpgid(capable->mainpid, 0); // create new pgid
301 IGN_RET(system(com));
302 _exit(0);
303 }
304
305 return ret;
306 }
307
308
lives_write(int fd,const void * buf,ssize_t count,boolean allow_fail)309 ssize_t lives_write(int fd, const void *buf, ssize_t count, boolean allow_fail) {
310 ssize_t retval;
311 if (count <= 0) return 0;
312
313 retval = write(fd, buf, count);
314
315 if (retval < count) {
316 char *msg = NULL;
317 /// TODO ****: this needs to be threadsafe
318 THREADVAR(write_failed) = fd + 1;
319 THREADVAR(write_failed_file) = filename_from_fd(THREADVAR(write_failed_file), fd);
320 if (retval >= 0)
321 msg = lives_strdup_printf("Write failed %"PRId64" of %"PRId64" in: %s", retval,
322 count, THREADVAR(write_failed_file));
323 else
324 msg = lives_strdup_printf("Write failed with error %"PRId64" in: %s", retval,
325 THREADVAR(write_failed_file));
326
327 if (!allow_fail) {
328 LIVES_ERROR(msg);
329 close(fd);
330 }
331 #ifndef LIVES_NO_DEBUG
332 else {
333 char *ffile = filename_from_fd(NULL, fd);
334 if (retval >= 0)
335 msg = lives_strdup_printf("Write failed %"PRIu64" of %"PRIu64" in: %s (not an error)", (uint64_t)retval,
336 (uint64_t)count, ffile);
337 else
338 msg = lives_strdup_printf("Write failed with error %"PRIu64" in: %s (allowed)", (uint64_t)retval,
339 THREADVAR(write_failed_file));
340 LIVES_DEBUG(msg);
341 lives_free(ffile);
342 }
343 #endif
344 if (msg) lives_free(msg);
345 }
346 return retval;
347 }
348
349
lives_write_le(int fd,const void * buf,ssize_t count,boolean allow_fail)350 ssize_t lives_write_le(int fd, const void *buf, ssize_t count, boolean allow_fail) {
351 if (count <= 0) return 0;
352 if (capable->byte_order == LIVES_BIG_ENDIAN && (prefs->bigendbug != 1)) {
353 reverse_bytes((char *)buf, count, count);
354 }
355 return lives_write(fd, buf, count, allow_fail);
356 }
357
358
lives_fputs(const char * s,FILE * stream)359 int lives_fputs(const char *s, FILE *stream) {
360 int retval = fputs(s, stream);
361 if (retval == EOF) {
362 THREADVAR(write_failed) = fileno(stream) + 1;
363 }
364 return retval;
365 }
366
367
lives_fgets(char * s,int size,FILE * stream)368 char *lives_fgets(char *s, int size, FILE *stream) {
369 char *retval;
370 if (!size) return NULL;
371 retval = fgets(s, size, stream);
372 if (!retval && ferror(stream)) {
373 THREADVAR(read_failed) = fileno(stream) + 1;
374 }
375 return retval;
376 }
377
378
lives_fread(void * ptr,size_t size,size_t nmemb,FILE * stream)379 size_t lives_fread(void *ptr, size_t size, size_t nmemb, FILE *stream) {
380 size_t bytes_read = fread(ptr, size, nmemb, stream);
381 if (ferror(stream)) {
382 THREADVAR(read_failed) = fileno(stream) + 1;
383 }
384 return bytes_read;
385 }
386
387
lives_fread_string(char * buff,size_t stlen,const char * fname)388 size_t lives_fread_string(char *buff, size_t stlen, const char *fname) {
389 size_t bread = 0;
390 FILE *infofile;
391 if (!stlen) return 0;
392 infofile = fopen(fname, "r");
393 if (!infofile) return 0;
394 bread = lives_fread(buff, 1, stlen - 1, infofile);
395 fclose(infofile);
396 lives_memset(buff + bread, 0, 1);
397 return bread;
398 }
399
400
find_in_file_buffers(int fd)401 lives_file_buffer_t *find_in_file_buffers(int fd) {
402 lives_file_buffer_t *fbuff = NULL;
403 LiVESList *fblist;
404
405 pthread_mutex_lock(&mainw->fbuffer_mutex);
406
407 for (fblist = mainw->file_buffers; fblist; fblist = fblist->next) {
408 fbuff = (lives_file_buffer_t *)fblist->data;
409 if (fbuff->fd == fd) break;
410 fbuff = NULL;
411 }
412
413 pthread_mutex_unlock(&mainw->fbuffer_mutex);
414
415 return fbuff;
416 }
417
418
find_in_file_buffers_by_pathname(const char * pathname)419 lives_file_buffer_t *find_in_file_buffers_by_pathname(const char *pathname) {
420 lives_file_buffer_t *fbuff = NULL;
421 LiVESList *fblist;
422
423 pthread_mutex_lock(&mainw->fbuffer_mutex);
424
425 for (fblist = mainw->file_buffers; fblist; fblist = fblist->next) {
426 fbuff = (lives_file_buffer_t *)fblist->data;
427 if (!lives_strcmp(fbuff->pathname, pathname)) break;
428 fbuff = NULL;
429 }
430
431 pthread_mutex_unlock(&mainw->fbuffer_mutex);
432
433 return fbuff;
434 }
435
436
do_file_read_error(int fd,ssize_t errval,void * buff,ssize_t count)437 static void do_file_read_error(int fd, ssize_t errval, void *buff, ssize_t count) {
438 char *msg = NULL;
439 THREADVAR(read_failed) = fd + 1;
440 THREADVAR(read_failed_file) = filename_from_fd(THREADVAR(read_failed_file), fd);
441
442 if (errval >= 0)
443 msg = lives_strdup_printf("Read failed %"PRId64" of %"PRId64" in: %s", (int64_t)errval,
444 count, THREADVAR(read_failed_file));
445 else {
446 msg = lives_strdup_printf("Read failed with error %"PRId64" in: %s (%s)", (int64_t)errval,
447 THREADVAR(read_failed_file),
448 #ifdef HAVE_LIBEXPLAIN
449 buff ? explain_read(fd, buff, count) : ""
450 #else
451 ""
452 #endif
453 );
454 }
455 LIVES_ERROR(msg);
456 lives_free(msg);
457 }
458
459
lives_read(int fd,void * buf,ssize_t count,boolean allow_less)460 ssize_t lives_read(int fd, void *buf, ssize_t count, boolean allow_less) {
461 ssize_t retval = read(fd, buf, count);
462 if (count <= 0) return 0;
463
464 if (retval < count) {
465 if (!allow_less || retval < 0) {
466 do_file_read_error(fd, retval, buf, count);
467 close(fd);
468 }
469 #ifndef LIVES_NO_DEBUG
470 else {
471 char *msg = NULL;
472 char *ffile = filename_from_fd(NULL, fd);
473 msg = lives_strdup_printf("Read got %"PRIu64" of %"PRIu64" in: %s (not an error)",
474 (uint64_t)retval,
475 (uint64_t)count, ffile);
476 LIVES_DEBUG(msg);
477 lives_free(ffile);
478 lives_free(msg);
479 }
480 #endif
481 }
482 return retval;
483 }
484
485
lives_read_le(int fd,void * buf,ssize_t count,boolean allow_less)486 ssize_t lives_read_le(int fd, void *buf, ssize_t count, boolean allow_less) {
487 ssize_t retval;
488 if (count <= 0) return 0;
489 retval = lives_read(fd, buf, count, allow_less);
490 if (retval < count) return retval;
491 if (capable->byte_order == LIVES_BIG_ENDIAN && !prefs->bigendbug) {
492 reverse_bytes((char *)buf, count, count);
493 }
494 return retval;
495 }
496
497 //// buffered io ////
498
499 // explanation of values
500
501 // read:
502 // fbuff->buffer holds (fbuff->ptr - fbuff->buffer + fbuff->bytes) bytes
503 // fbuff->offset is the next real read position
504
505 // read x bytes : fbuff->ptr increases by x, fbuff->bytes decreases by x
506 // if fbuff->bytes is < x, then we concat fbuff->bytes, refill buffer from file, concat remaining bytes
507 // on read: fbuff->ptr = fbuff->buffer. fbuff->offset += bytes read, fbuff->bytes = bytes read
508 // if fbuff->reversed is set then we seek to a position offset - 3 / 4 buffsize, fbuff->ptr = fbuff->buffer + 3 / 4 buffsz, bytes = 1 / 4 buffsz
509
510
511 // on seek (read only):
512 // forward: seek by +z: if z < fbuff->bytes : fbuff->ptr += z, fbuff->bytes -= z
513 // if z > fbuff->bytes: subtract fbuff->bytes from z. Increase fbuff->offset by remainder. Fill buffer.
514
515 // backward: if fbuff->ptr - z >= fbuff->buffer : fbuff->ptr -= z, fbuff->bytes += z
516 // fbuff->ptr - z < fbuff->buffer: z -= (fbuff->ptr - fbuff->buffer) : fbuff->offset -= (fbuff->bytes + z) : Fill buffer
517
518 // seek absolute: current viritual posn is fbuff->offset - fbuff->bytes : subtract this from absolute posn
519
520 // return value is: fbuff->offset - fbuff->bytes ?
521
522 // when writing we simply fill up the buffer until full, then flush the buffer to file io
523 // buffer is finally flushed when we close the file (or we call file_buffer_flush)
524
525 // in this case fbuff->bytes holds the number of bytes written to fbuff->buffer, fbuff->offset contains the offset in the underlying fil
526
527 // in append mode, seek is first tthe end of the file. In creat mode any existing file is truncated and overwritten.
528
529 // in write mode, if we have fallocate, then we preallocate the buffer size on disk.
530 // When the file is closed we truncate any remaining bytes. Thus CAUTION because the file size as read directly will include the
531 // padding bytes, and thus appending directly to the file will write after the padding.bytes, and either be overwritten or truncated.
532 // in this case the correct size can be obtained from
533
file_buffer_flush(lives_file_buffer_t * fbuff)534 static ssize_t file_buffer_flush(lives_file_buffer_t *fbuff) {
535 // returns number of bytes written to file io, or error code
536 ssize_t res = 0;
537
538 if (fbuff->buffer) res = lives_write(fbuff->fd, fbuff->buffer, fbuff->bytes, fbuff->allow_fail);
539 //g_print("writing %ld bytes to %d\n", fbuff->bytes, fbuff->fd);
540
541 if (!fbuff->allow_fail && res < fbuff->bytes) {
542 lives_close_buffered(-fbuff->fd); // use -fd as lives_write will have closed
543 return res;
544 }
545
546 if (res > 0) {
547 fbuff->offset += res;
548 fbuff->bytes = 0;
549 fbuff->ptr = fbuff->buffer;
550 }
551 //g_print("writer offs at %ld bytes to %d\n", fbuff->offset, fbuff->fd);
552
553 return res;
554 }
555
556
lives_invalidate_all_file_buffers(void)557 void lives_invalidate_all_file_buffers(void) {
558 lives_file_buffer_t *fbuff = NULL;
559 LiVESList *fblist;
560
561 pthread_mutex_lock(&mainw->fbuffer_mutex);
562
563 for (fblist = mainw->file_buffers; fblist; fblist = fblist->next) {
564 fbuff = (lives_file_buffer_t *)fblist->data;
565 // if a writer, flush
566 if (!fbuff->read && mainw->memok) {
567 file_buffer_flush(fbuff);
568 fbuff->buffer = NULL;
569 } else {
570 fbuff->invalid = TRUE;
571 }
572 }
573
574 pthread_mutex_unlock(&mainw->fbuffer_mutex);
575 }
576
577
lives_open_real_buffered(const char * pathname,int flags,int mode,boolean isread)578 static int lives_open_real_buffered(const char *pathname, int flags, int mode, boolean isread) {
579 lives_file_buffer_t *fbuff, *xbuff;
580 boolean is_append = FALSE;
581 int fd;
582
583 if (flags & O_APPEND) {
584 is_append = TRUE;
585 flags &= ~O_APPEND;
586 }
587
588 fd = lives_open3(pathname, flags, mode);
589 if (fd >= 0) {
590 fbuff = (lives_file_buffer_t *)lives_calloc(sizeof(lives_file_buffer_t) >> 2, 4);
591 fbuff->fd = fd;
592 fbuff->read = isread;
593 fbuff->pathname = lives_strdup(pathname);
594 fbuff->bufsztype = isread ? BUFF_SIZE_READ_SMALL : BUFF_SIZE_WRITE_SMALL;
595
596 if ((xbuff = find_in_file_buffers(fd)) != NULL) {
597 char *msg = lives_strdup_printf("Duplicate fd (%d) in file buffers !\n%s was not removed, and\n%s will be added.", fd,
598 xbuff->pathname,
599 fbuff->pathname);
600 break_me("dupe fd in fbuffs");
601 LIVES_ERROR(msg);
602 lives_free(msg);
603 lives_close_buffered(fd);
604 } else {
605 if (!isread && !(flags & O_TRUNC)) {
606 if (is_append) fbuff->offset = fbuff->orig_size = lseek(fd, 0, SEEK_END);
607 else fbuff->orig_size = (size_t)get_file_size(fd);
608 /// TODO - handle fsize < 0
609 }
610 }
611 pthread_mutex_lock(&mainw->fbuffer_mutex);
612 mainw->file_buffers = lives_list_prepend(mainw->file_buffers, (livespointer)fbuff);
613 pthread_mutex_unlock(&mainw->fbuffer_mutex);
614 }
615
616 return fd;
617 }
618
619 static size_t bigbytes = BUFFER_FILL_BYTES_LARGE;
620 static size_t medbytes = BUFFER_FILL_BYTES_MED;
621 static size_t smedbytes = BUFFER_FILL_BYTES_SMALLMED;
622 static size_t smbytes = BUFFER_FILL_BYTES_SMALL;
623 #define AUTOTUNE
624 #ifdef AUTOTUNE
625 static weed_plant_t *tunerl = NULL;
626 static boolean tunedl = FALSE;
627 static weed_plant_t *tunerm = NULL;
628 static boolean tunedm = FALSE;
629 static weed_plant_t *tunersm = NULL;
630 static boolean tunedsm = FALSE;
631 static weed_plant_t *tuners = NULL;
632 static boolean tuneds = FALSE;
633 #endif
634
635
lives_open_buffered_rdonly(const char * pathname)636 LIVES_GLOBAL_INLINE int lives_open_buffered_rdonly(const char *pathname) {
637 return lives_open_real_buffered(pathname, O_RDONLY, 0, TRUE);
638 }
639
640
_lives_buffered_rdonly_slurp(int fd,off_t skip)641 boolean _lives_buffered_rdonly_slurp(int fd, off_t skip) {
642 lives_file_buffer_t *fbuff = find_in_file_buffers(fd);
643 off_t fsize = get_file_size(fd) - skip, bufsize = smbytes, res;
644 if (fsize > 0) {
645 lseek(fd, skip, SEEK_SET);
646 fbuff->orig_size = fsize + skip;
647 fbuff->buffer = fbuff->ptr = lives_calloc(1, fsize);
648 //g_printerr("slurp for %d, %s with size %ld\n", fd, fbuff->pathname, fsize);
649 while (fsize > 0) {
650 if (bufsize > fsize) bufsize = fsize;
651 res = lives_read(fbuff->fd, fbuff->buffer + fbuff->offset, bufsize, TRUE);
652 //g_printerr("slurp for %d, %s with size %ld, read %lu bytes, remain\n", fd, fbuff->pathname, res, fsize);
653 if (res < 0) {
654 fbuff->eof = TRUE;
655 return FALSE;
656 }
657 if (res > fsize) res = fsize;
658 fbuff->offset += res;
659 fsize -= res;
660 if (fsize >= bigbytes && bufsize >= medbytes) bufsize = bigbytes;
661 else if (fsize >= medbytes && bufsize >= smedbytes) bufsize = medbytes;
662 else if (fsize >= smedbytes) bufsize = smedbytes;
663 //g_printerr("slurp %d oof %ld %ld remain %lu \n", fd, fbuff->offset, fsize, ofsize);
664 }
665 }
666 fbuff->eof = TRUE;
667 return TRUE;
668 }
669
670
lives_buffered_rdonly_slurp(int fd,off_t skip)671 void lives_buffered_rdonly_slurp(int fd, off_t skip) {
672 lives_file_buffer_t *fbuff = find_in_file_buffers(fd);
673 if (!fbuff || fbuff->slurping) return;
674 fbuff->slurping = TRUE;
675 fbuff->bytes = fbuff->offset = 0;
676 lives_proc_thread_create(LIVES_THRDATTR_NONE, (lives_funcptr_t)_lives_buffered_rdonly_slurp, 0, "iI", fd, skip);
677 lives_nanosleep_until_nonzero(fbuff->offset | fbuff->eof);
678 }
679
680
lives_buffered_rdonly_set_reversed(int fd,boolean val)681 LIVES_GLOBAL_INLINE boolean lives_buffered_rdonly_set_reversed(int fd, boolean val) {
682 lives_file_buffer_t *fbuff = find_in_file_buffers(fd);
683 if (!fbuff) {
684 // normal non-buffered file
685 LIVES_DEBUG("lives_buffered_readonly_set_reversed: no file buffer found");
686 return FALSE;
687 }
688 fbuff->reversed = val;
689 return TRUE;
690 }
691
692
693 #ifndef O_DSYNC
694 #define O_DSYNC O_SYNC
695 #define NO_O_DSYNC
696 #endif
697
lives_create_buffered(const char * pathname,int mode)698 LIVES_GLOBAL_INLINE int lives_create_buffered(const char *pathname, int mode) {
699 return lives_open_real_buffered(pathname, O_CREAT | O_WRONLY | O_TRUNC | O_DSYNC, mode, FALSE);
700 }
701
lives_create_buffered_nosync(const char * pathname,int mode)702 LIVES_GLOBAL_INLINE int lives_create_buffered_nosync(const char *pathname, int mode) {
703 return lives_open_real_buffered(pathname, O_CREAT | O_WRONLY | O_TRUNC, mode, FALSE);
704 }
705
lives_open_buffered_writer(const char * pathname,int mode,boolean append)706 int lives_open_buffered_writer(const char *pathname, int mode, boolean append) {
707 return lives_open_real_buffered(pathname, O_CREAT | O_WRONLY | O_DSYNC | (append ? O_APPEND : 0), mode, FALSE);
708 }
709
710 #ifdef NO_O_DSYNC
711 #undef O_DSYNC
712 #undef NO_O_DSYNC
713 #endif
714
715
lives_close_buffered(int fd)716 int lives_close_buffered(int fd) {
717 lives_file_buffer_t *fbuff;
718 boolean should_close = TRUE;
719 int ret = 0;
720
721 if (IS_VALID_CLIP(mainw->scrap_file) && mainw->files[mainw->scrap_file]->ext_src &&
722 fd == LIVES_POINTER_TO_INT(mainw->files[mainw->scrap_file]->ext_src))
723
724 if (fd < 0) {
725 should_close = FALSE;
726 fd = -fd;
727 }
728
729 fbuff = find_in_file_buffers(fd);
730
731 if (!fbuff) {
732 // normal non-buffered file
733 LIVES_DEBUG("lives_close_buffered: no file buffer found");
734 if (should_close) ret = close(fd);
735 return ret;
736 }
737
738 if (!fbuff->read && should_close) {
739 boolean allow_fail = fbuff->allow_fail;
740 ssize_t bytes = fbuff->bytes;
741
742 if (bytes > 0) {
743 ret = file_buffer_flush(fbuff);
744 // this is correct, as flush will have called close again with should_close=FALSE;
745 if (!allow_fail && ret < bytes) return ret;
746 }
747 #ifdef HAVE_POSIX_FALLOCATE
748 IGN_RET(ftruncate(fbuff->fd, MAX(fbuff->offset, fbuff->orig_size)));
749 /* //g_print("truncated at %ld bytes in %d\n", MAX(fbuff->offset, fbuff->orig_size), fbuff->fd); */
750 #endif
751 }
752
753 if (fbuff->slurping) lives_nanosleep_until_nonzero(fbuff->eof);
754 if (should_close && fbuff->fd >= 0) ret = close(fbuff->fd);
755
756 lives_free(fbuff->pathname);
757
758 pthread_mutex_lock(&mainw->fbuffer_mutex);
759 mainw->file_buffers = lives_list_remove(mainw->file_buffers, (livesconstpointer)fbuff);
760 pthread_mutex_unlock(&mainw->fbuffer_mutex);
761
762 if (fbuff->buffer && !fbuff->invalid) {
763 lives_free(fbuff->buffer);
764 }
765
766 lives_free(fbuff);
767 return ret;
768 }
769
770
get_read_buff_size(int sztype)771 size_t get_read_buff_size(int sztype) {
772 switch (sztype) {
773 case BUFF_SIZE_READ_SMALLMED: return smedbytes;
774 case BUFF_SIZE_READ_MED: return medbytes;
775 case BUFF_SIZE_READ_LARGE: return bigbytes;
776 default: break;
777 }
778 return smbytes;
779 }
780
781
file_buffer_fill(lives_file_buffer_t * fbuff,ssize_t min)782 static ssize_t file_buffer_fill(lives_file_buffer_t *fbuff, ssize_t min) {
783 ssize_t res;
784 ssize_t delta = 0;
785 size_t bufsize;
786
787 if (min < 0) min = 0;
788
789 if (fbuff->bufsztype == BUFF_SIZE_READ_CUSTOM) {
790 if (fbuff->buffer) bufsize = fbuff->ptr - fbuff->buffer + fbuff->bytes;
791 else {
792 bufsize = fbuff->bytes;
793 fbuff->bytes = 0;
794 }
795 } else bufsize = get_read_buff_size(fbuff->bufsztype);
796
797 if (fbuff->reversed) delta = (bufsize >> 2) * 3;
798 if (delta > fbuff->offset) delta = fbuff->offset;
799 if (bufsize - delta < min) bufsize = min + delta;
800 if (fbuff->buffer && bufsize > fbuff->ptr - fbuff->buffer + fbuff->bytes) {
801 lives_freep((void **)&fbuff->buffer);
802 }
803 if (!fbuff->buffer || !fbuff->ptr) {
804 fbuff->buffer = (uint8_t *)lives_calloc_safety(bufsize >> 1, 2);
805 }
806 fbuff->offset -= delta;
807 fbuff->offset = lseek(fbuff->fd, fbuff->offset, SEEK_SET);
808
809 res = lives_read(fbuff->fd, fbuff->buffer, bufsize, TRUE);
810 if (res < 0) {
811 lives_close_buffered(-fbuff->fd); // use -fd as lives_read will have closed
812 return res;
813 }
814
815 fbuff->bytes = res - delta;
816 fbuff->ptr = fbuff->buffer + delta;
817 fbuff->offset += res;
818 if (res < bufsize) fbuff->eof = TRUE;
819 else fbuff->eof = FALSE;
820
821 #if defined HAVE_POSIX_FADVISE || (defined _GNU_SOURCE && defined __linux__)
822 if (fbuff->reversed) {
823 #if defined HAVE_POSIX_FADVISE
824 posix_fadvise(fbuff->fd, 0, fbuff->offset - (bufsize >> 2) * 3, POSIX_FADV_RANDOM);
825 posix_fadvise(fbuff->fd, fbuff->offset - (bufsize >> 2) * 3, bufsize, POSIX_FADV_WILLNEED);
826 #endif
827 #ifdef __linux__
828 readahead(fbuff->fd, fbuff->offset - (bufsize >> 2) * 3, bufsize);
829 #endif
830 } else {
831 #if defined HAVE_POSIX_FADVISE
832 posix_fadvise(fbuff->fd, fbuff->offset, 0, POSIX_FADV_SEQUENTIAL);
833 posix_fadvise(fbuff->fd, fbuff->offset, bufsize, POSIX_FADV_WILLNEED);
834 #endif
835 #ifdef __linux__
836 readahead(fbuff->fd, fbuff->offset, bufsize);
837 #endif
838 }
839 #endif
840
841 return res - delta;
842 }
843
844
_lives_lseek_buffered_rdonly_relative(lives_file_buffer_t * fbuff,off_t offset)845 static off_t _lives_lseek_buffered_rdonly_relative(lives_file_buffer_t *fbuff, off_t offset) {
846 off_t newoffs;
847 if (offset == 0) return fbuff->offset - fbuff->bytes;
848 fbuff->nseqreads = 0;
849
850 if (offset > 0) {
851 // seek forwards
852 if (offset < fbuff->bytes) {
853 fbuff->ptr += offset;
854 fbuff->bytes -= offset;
855 newoffs = fbuff->offset - fbuff->bytes;
856 } else {
857 offset -= fbuff->bytes;
858 fbuff->offset += offset;
859 fbuff->bytes = 0;
860 newoffs = fbuff->offset;
861 }
862 } else {
863 // seek backwards
864 offset = -offset;
865 if (offset <= fbuff->ptr - fbuff->buffer) {
866 fbuff->ptr -= offset;
867 fbuff->bytes += offset;
868 newoffs = fbuff->offset - fbuff->bytes;
869 } else {
870 offset -= fbuff->ptr - fbuff->buffer;
871
872 fbuff->offset = fbuff->offset - (fbuff->ptr - fbuff->buffer + fbuff->bytes) - offset;
873 if (fbuff->offset < 0) fbuff->offset = 0;
874
875 fbuff->bytes = 0;
876
877 fbuff->eof = FALSE;
878 newoffs = fbuff->offset;
879 }
880 }
881
882 #ifdef HAVE_POSIX_FADVISE
883 if (fbuff->reversed)
884 posix_fadvise(fbuff->fd, 0, fbuff->offset - fbuff->bytes, POSIX_FADV_RANDOM);
885 else
886 posix_fadvise(fbuff->fd, fbuff->offset, 0, POSIX_FADV_SEQUENTIAL);
887 #endif
888
889 lseek(fbuff->fd, fbuff->offset, SEEK_SET);
890
891 return newoffs;
892 }
893
894
lives_lseek_buffered_rdonly(int fd,off_t offset)895 off_t lives_lseek_buffered_rdonly(int fd, off_t offset) {
896 // seek relative
897 lives_file_buffer_t *fbuff;
898 if (!(fbuff = find_in_file_buffers(fd))) {
899 LIVES_DEBUG("lives_lseek_buffered_rdonly: no file buffer found");
900 return lseek(fd, offset, SEEK_CUR);
901 }
902
903 return _lives_lseek_buffered_rdonly_relative(fbuff, offset);
904 }
905
906
lives_lseek_buffered_rdonly_absolute(int fd,off_t offset)907 off_t lives_lseek_buffered_rdonly_absolute(int fd, off_t offset) {
908 lives_file_buffer_t *fbuff;
909
910 if (!(fbuff = find_in_file_buffers(fd))) {
911 LIVES_DEBUG("lives_lseek_buffered_rdonly_absolute: no file buffer found");
912 return lseek(fd, offset, SEEK_SET);
913 }
914
915 if (!fbuff->ptr || !fbuff->buffer) {
916 fbuff->offset = offset;
917 return fbuff->offset;
918 }
919 offset -= fbuff->offset - fbuff->bytes;
920 return _lives_lseek_buffered_rdonly_relative(fbuff, offset);
921 }
922
923
lives_read_buffered(int fd,void * buf,ssize_t count,boolean allow_less)924 ssize_t lives_read_buffered(int fd, void *buf, ssize_t count, boolean allow_less) {
925 lives_file_buffer_t *fbuff;
926 ssize_t retval = 0, res = 0;
927 size_t ocount = count;
928 uint8_t *ptr = (uint8_t *)buf;
929 int bufsztype;
930 #ifdef AUTOTUNE
931 double cost;
932 #endif
933
934 if (count <= 0) return retval;
935
936 if ((fbuff = find_in_file_buffers(fd)) == NULL) {
937 LIVES_DEBUG("lives_read_buffered: no file buffer found");
938 return lives_read(fd, buf, count, allow_less);
939 }
940
941 if (!fbuff->read) {
942 LIVES_ERROR("lives_read_buffered: wrong buffer type");
943 return 0;
944 }
945
946 bufsztype = fbuff->bufsztype;
947
948 #ifdef AUTOTUNE
949 if (!fbuff->slurping && fbuff->bufsztype != BUFF_SIZE_READ_CUSTOM) {
950 cost = 1. / (1. + (double)fbuff->nseqreads);
951 if (fbuff->bufsztype == BUFF_SIZE_READ_SMALL) {
952 if (!tuneds && (!tuners || (tunedsm && !tunedm))) tuners = lives_plant_new_with_index(LIVES_WEED_SUBTYPE_TUNABLE, 3);
953 autotune_u64(tuners, 8, smedbytes / 4, 16, cost);
954 } else if (fbuff->bufsztype == BUFF_SIZE_READ_SMALLMED) {
955 if (tuneds && !tunedsm && !tunersm) tunersm = lives_plant_new_with_index(LIVES_WEED_SUBTYPE_TUNABLE, 4);
956 autotune_u64(tunersm, smbytes * 4, 32768, 16, cost);
957 } else if (fbuff->bufsztype == BUFF_SIZE_READ_MED) {
958 if (tunedsm && !tunedm && !tunerm) tunerm = lives_plant_new_with_index(LIVES_WEED_SUBTYPE_TUNABLE, 5);
959 autotune_u64(tunerm, smedbytes * 4, 65536 * 2, 32, cost);
960 } else if (fbuff->bufsztype == BUFF_SIZE_READ_LARGE) {
961 if (tunedm && !tunedl && !tunerl) tunerl = lives_plant_new_with_index(LIVES_WEED_SUBTYPE_TUNABLE, 6);
962 autotune_u64(tunerl, medbytes * 4, 8192 * 1024, 256, cost);
963 }
964 }
965 #endif
966 if (!buf) {
967 /// function can be called with buf == NULL to preload a buffer with at least (count) bytes
968 lives_freep((void **)&fbuff->buffer);
969 if (allow_less) {
970 bufsztype = BUFF_SIZE_READ_SMALL;
971 if (ocount >= (smbytes >> 2) || count > smbytes) bufsztype = BUFF_SIZE_READ_SMALLMED;
972 if (ocount >= (smedbytes >> 2) || count > smedbytes) bufsztype = BUFF_SIZE_READ_MED;
973 if (ocount >= (medbytes >> 1) || count > medbytes) bufsztype = BUFF_SIZE_READ_LARGE;
974 fbuff->bufsztype = bufsztype;
975 } else {
976 fbuff->bufsztype = BUFF_SIZE_READ_CUSTOM;
977 fbuff->bytes = count;
978 }
979 return file_buffer_fill(fbuff, count);
980 }
981
982 fbuff->totops++;
983
984 // read bytes from fbuff
985 if (fbuff->bytes > 0 || fbuff->slurping) {
986 size_t nbytes;
987 if (fbuff->slurping) {
988 if (fbuff->orig_size - fbuff->bytes < count) {
989 nbytes = fbuff->orig_size - fbuff->bytes;
990 if (nbytes == 0) goto rd_exit;
991 } else {
992 while ((nbytes = fbuff->offset - fbuff->bytes) < count) {
993 lives_nanosleep(1000);
994 }
995 }
996 } else nbytes = fbuff->bytes;
997 if (nbytes > count) nbytes = count;
998
999 // use up buffer
1000
1001 if (fbuff->invalid) {
1002 if (mainw->is_exiting) {
1003 return retval;
1004 }
1005 fbuff->offset -= (fbuff->ptr - fbuff->buffer + fbuff->bytes);
1006 if (fbuff->bufsztype == BUFF_SIZE_READ_CUSTOM) fbuff->bytes = (fbuff->ptr - fbuff->buffer + fbuff->bytes);
1007 fbuff->buffer = NULL;
1008 file_buffer_fill(fbuff, fbuff->bytes);
1009 fbuff->invalid = FALSE;
1010 }
1011
1012 lives_memcpy(ptr, fbuff->ptr, nbytes);
1013
1014 retval += nbytes;
1015 count -= nbytes;
1016 fbuff->ptr += nbytes;
1017 ptr += nbytes;
1018 fbuff->totbytes += nbytes;
1019
1020 if (fbuff->slurping) fbuff->bytes += nbytes;
1021 else fbuff->bytes -= nbytes;
1022
1023 fbuff->nseqreads++;
1024 if (fbuff->slurping) goto rd_exit;
1025 if (count == 0) goto rd_done;
1026 if (fbuff->eof && !fbuff->reversed) goto rd_done;
1027 fbuff->nseqreads--;
1028 if (fbuff->reversed) {
1029 fbuff->offset -= (fbuff->ptr - fbuff->buffer) + count;
1030 }
1031 }
1032
1033 /// buffer used up
1034
1035 if (count <= bigbytes || fbuff->bufsztype == BUFF_SIZE_READ_CUSTOM) {
1036 if (fbuff->bufsztype != BUFF_SIZE_READ_CUSTOM) {
1037 bufsztype = BUFF_SIZE_READ_SMALL;
1038 if (ocount >= (smbytes >> 2) || count > smbytes) bufsztype = BUFF_SIZE_READ_SMALLMED;
1039 if (ocount >= (smedbytes >> 2) || count > smedbytes) bufsztype = BUFF_SIZE_READ_MED;
1040 if (ocount >= (medbytes >> 1) || count > medbytes) bufsztype = BUFF_SIZE_READ_LARGE;
1041 if (fbuff->bufsztype < bufsztype) fbuff->bufsztype = bufsztype;
1042 } else bufsztype = BUFF_SIZE_READ_CUSTOM;
1043 if (fbuff->invalid) {
1044 if (mainw->is_exiting) {
1045 return retval;
1046 }
1047 fbuff->offset -= (fbuff->ptr - fbuff->buffer + fbuff->bytes);
1048 if (fbuff->bufsztype == BUFF_SIZE_READ_CUSTOM) fbuff->bytes = (fbuff->ptr - fbuff->buffer + fbuff->bytes);
1049 fbuff->buffer = NULL;
1050 file_buffer_fill(fbuff, fbuff->bytes);
1051 fbuff->invalid = FALSE;
1052 } else {
1053 if (fbuff->bufsztype != bufsztype) {
1054 lives_freep((void **)&fbuff->buffer);
1055 }
1056 }
1057
1058 if (fbuff->bufsztype != bufsztype) fbuff->nseqreads = 0;
1059 res = file_buffer_fill(fbuff, count);
1060 if (res < 0) {
1061 retval = res;
1062 goto rd_done;
1063 }
1064
1065 // buffer is sufficient (or eof hit)
1066 if (res > count) res = count;
1067 lives_memcpy(ptr, fbuff->ptr, res);
1068 retval += res;
1069 fbuff->ptr += res;
1070 fbuff->bytes -= res;
1071 count -= res;
1072 fbuff->totbytes += res;
1073 } else {
1074 // larger size -> direct read
1075 if (fbuff->bufsztype != bufsztype) {
1076 if (fbuff->invalid) {
1077 fbuff->buffer = NULL;
1078 fbuff->invalid = FALSE;
1079 } else {
1080 lives_freep((void **)&fbuff->buffer);
1081 }
1082 }
1083
1084 fbuff->offset = lseek(fbuff->fd, fbuff->offset, SEEK_SET);
1085
1086 res = lives_read(fbuff->fd, ptr, count, TRUE);
1087 if (res < 0) {
1088 retval = res;
1089 goto rd_done;
1090 }
1091 if (res < count) fbuff->eof = TRUE;
1092 fbuff->offset += res;
1093 count -= res;
1094 retval += res;
1095 fbuff->totbytes += res;
1096 }
1097
1098 rd_done:
1099 #ifdef AUTOTUNE
1100 if (fbuff->bufsztype == BUFF_SIZE_READ_SMALL) {
1101 if (tuners) {
1102 smbytes = autotune_u64_end(&tuners, smbytes);
1103 if (!tuners) {
1104 tuneds = TRUE;
1105 }
1106 }
1107 } else if (fbuff->bufsztype == BUFF_SIZE_READ_SMALLMED) {
1108 if (tunersm) {
1109 smedbytes = autotune_u64_end(&tunersm, smedbytes);
1110 if (!tunersm) {
1111 tunedsm = TRUE;
1112 smedbytes = get_near2pow(smedbytes);
1113 if (prefs->show_dev_opts) {
1114 char *tmp;
1115 g_printerr("value rounded to %s\n", (tmp = lives_format_storage_space_string(smedbytes)));
1116 lives_free(tmp);
1117 // *INDENT-OFF*
1118 }}}}
1119 // *INDENT-ON*
1120 else if (fbuff->bufsztype == BUFF_SIZE_READ_MED) {
1121 if (tunerm) {
1122 medbytes = autotune_u64_end(&tunerm, medbytes);
1123 if (!tunerm) {
1124 tunedm = TRUE;
1125 medbytes = get_near2pow(medbytes);
1126 if (prefs->show_dev_opts) {
1127 char *tmp;
1128 g_printerr("value rounded to %s\n", (tmp = lives_format_storage_space_string(medbytes)));
1129 lives_free(tmp);
1130 // *INDENT-OFF*
1131 }}}}
1132 // *INDENT-ON*
1133 else {
1134 if (tunerl) {
1135 bigbytes = autotune_u64_end(&tunerl, bigbytes);
1136 if (!tunerl) {
1137 tunedl = TRUE;
1138 bigbytes = get_near2pow(bigbytes);
1139 if (prefs->show_dev_opts) {
1140 char *tmp;
1141 g_printerr("value rounded to %s\n", (tmp = lives_format_storage_space_string(bigbytes)));
1142 lives_free(tmp);
1143 // *INDENT-OFF*
1144 }}}}
1145 // *INDENT-ON*
1146
1147 #endif
1148 rd_exit:
1149 if (!allow_less && count > 0) {
1150 do_file_read_error(fd, retval, NULL, ocount);
1151 lives_close_buffered(fd);
1152 }
1153
1154 return retval;
1155 }
1156
1157
lives_read_le_buffered(int fd,void * buf,ssize_t count,boolean allow_less)1158 ssize_t lives_read_le_buffered(int fd, void *buf, ssize_t count, boolean allow_less) {
1159 ssize_t retval;
1160 if (count <= 0) return 0;
1161 retval = lives_read_buffered(fd, buf, count, allow_less);
1162 if (retval < count) return retval;
1163 if (capable->byte_order == LIVES_BIG_ENDIAN && !prefs->bigendbug) {
1164 reverse_bytes((char *)buf, count, count);
1165 }
1166 return retval;
1167 }
1168
1169
lives_read_buffered_eof(int fd)1170 boolean lives_read_buffered_eof(int fd) {
1171 lives_file_buffer_t *fbuff;
1172 if ((fbuff = find_in_file_buffers(fd)) == NULL) {
1173 LIVES_DEBUG("lives_read_buffered: no file buffer found");
1174 return TRUE;
1175 }
1176
1177 if (!fbuff->read) {
1178 LIVES_ERROR("lives_read_buffered_eof: wrong buffer type");
1179 return FALSE;
1180 }
1181 return (fbuff->eof && ((!fbuff->reversed && !fbuff->bytes)
1182 || (fbuff->reversed && fbuff->ptr == fbuff->buffer)));
1183 }
1184
1185
lives_write_buffered_direct(lives_file_buffer_t * fbuff,const char * buf,ssize_t count,boolean allow_fail)1186 static ssize_t lives_write_buffered_direct(lives_file_buffer_t *fbuff, const char *buf, ssize_t count, boolean allow_fail) {
1187 ssize_t res = 0;
1188 ssize_t bytes = fbuff->bytes;
1189
1190 if (count <= 0) return 0;
1191
1192 if (bytes > 0) {
1193 res = file_buffer_flush(fbuff);
1194 // this is correct, as flush will have called close again with should_close=FALSE;
1195 if (!allow_fail && res < bytes) return 0;
1196 }
1197 res = 0;
1198
1199 while (count > 0) {
1200 size_t bytes;
1201 #define WRITE_ALL
1202 #ifdef WRITE_ALL
1203 size_t bigbsize = count;
1204 #else
1205 size_t bigbsize = BUFFER_FILL_BYTES_LARGE;
1206 #endif
1207 if (bigbsize > count) bigbsize = count;
1208 bytes = lives_write(fbuff->fd, buf + res, bigbsize, allow_fail);
1209 if (bytes == bigbsize) {
1210 fbuff->offset += bytes;
1211 count -= bytes;
1212 res += bytes;
1213 } else {
1214 LIVES_ERROR("lives_write_buffered: error in bigblock writer");
1215 if (!fbuff->allow_fail) {
1216 lives_close_buffered(-fbuff->fd); // use -fd as lives_write will have closed
1217 return res;
1218 }
1219 break;
1220 }
1221 }
1222 return res;
1223 }
1224
1225
lives_write_buffered(int fd,const char * buf,ssize_t count,boolean allow_fail)1226 ssize_t lives_write_buffered(int fd, const char *buf, ssize_t count, boolean allow_fail) {
1227 lives_file_buffer_t *fbuff;
1228 ssize_t retval = 0, res;
1229 size_t space_left;
1230 int bufsztype = BUFF_SIZE_WRITE_SMALL;
1231 ssize_t buffsize;
1232
1233 if (!(fbuff = find_in_file_buffers(fd))) {
1234 LIVES_DEBUG("lives_write_buffered: no file buffer found");
1235 return lives_write(fd, buf, count, allow_fail);
1236 }
1237
1238 if (fbuff->read) {
1239 LIVES_ERROR("lives_write_buffered: wrong buffer type");
1240 return 0;
1241 }
1242
1243 if (count <= 0) return 0;
1244
1245 if (count > BUFFER_FILL_BYTES_LARGE) return lives_write_buffered_direct(fbuff, buf, count, allow_fail);
1246
1247 fbuff->totops++;
1248 fbuff->totbytes += count;
1249
1250 if (count >= BUFFER_FILL_BYTES_BIGMED >> 1)
1251 bufsztype = BUFF_SIZE_WRITE_LARGE;
1252 else if (count >= BUFFER_FILL_BYTES_MED >> 1)
1253 bufsztype = BUFF_SIZE_WRITE_BIGMED;
1254 else if (fbuff->totbytes >= BUFFER_FILL_BYTES_SMALLMED)
1255 bufsztype = BUFF_SIZE_WRITE_MED;
1256 else if (fbuff->totbytes >= BUFFER_FILL_BYTES_SMALL)
1257 bufsztype = BUFF_SIZE_WRITE_SMALLMED;
1258
1259 if (bufsztype < fbuff->bufsztype) bufsztype = fbuff->bufsztype;
1260
1261 fbuff->allow_fail = allow_fail;
1262
1263 // write bytes to fbuff
1264 while (count) {
1265 if (!fbuff->buffer) fbuff->bufsztype = bufsztype;
1266
1267 if (fbuff->bufsztype == BUFF_SIZE_WRITE_SMALL)
1268 buffsize = BUFFER_FILL_BYTES_SMALL;
1269 else if (fbuff->bufsztype == BUFF_SIZE_WRITE_SMALLMED)
1270 buffsize = BUFFER_FILL_BYTES_SMALLMED;
1271 else if (fbuff->bufsztype == BUFF_SIZE_WRITE_MED)
1272 buffsize = BUFFER_FILL_BYTES_MED;
1273 else if (fbuff->bufsztype == BUFF_SIZE_WRITE_BIGMED)
1274 buffsize = BUFFER_FILL_BYTES_BIGMED;
1275 else
1276 buffsize = BUFFER_FILL_BYTES_LARGE;
1277
1278 if (!fbuff->buffer) {
1279 fbuff->buffer = (uint8_t *)lives_calloc(buffsize >> 4, 16);
1280 fbuff->ptr = fbuff->buffer;
1281 fbuff->bytes = 0;
1282
1283 #ifdef HAVE_POSIX_FALLOCATE
1284 // pre-allocate space for next buffer, we need to ftruncate this when closing the file
1285 //g_print("alloc space in %d from %ld to %ld\n", fbuff->fd, fbuff->offset, fbuff->offset + buffsize);
1286 posix_fallocate(fbuff->fd, fbuff->offset, buffsize);
1287 /* lseek(fbuff->fd, fbuff->offset, SEEK_SET); */
1288 #endif
1289 }
1290
1291 space_left = buffsize - fbuff->bytes;
1292 if (space_left > count) {
1293 lives_memcpy(fbuff->ptr, buf, count);
1294 retval += count;
1295 fbuff->ptr += count;
1296 fbuff->bytes += count;
1297 count = 0;
1298 } else {
1299 lives_memcpy(fbuff->ptr, buf, space_left);
1300 fbuff->bytes = buffsize;
1301 res = file_buffer_flush(fbuff);
1302 retval += space_left;
1303 if (res < buffsize) return (res < 0 ? res : retval);
1304 count -= space_left;
1305 buf += space_left;
1306 if (fbuff->bufsztype != bufsztype) {
1307 lives_free(fbuff->buffer);
1308 fbuff->buffer = NULL;
1309 }
1310 }
1311 }
1312 return retval;
1313 }
1314
1315
lives_buffered_write_printf(int fd,boolean allow_fail,const char * fmt,...)1316 ssize_t lives_buffered_write_printf(int fd, boolean allow_fail, const char *fmt, ...) {
1317 ssize_t ret;
1318 va_list xargs;
1319 char *text;
1320 va_start(xargs, fmt);
1321 text = lives_strdup_vprintf(fmt, xargs);
1322 va_end(xargs);
1323 ret = lives_write_buffered(fd, text, lives_strlen(text), allow_fail);
1324 lives_free(text);
1325 return ret;
1326 }
1327
1328
lives_write_le_buffered(int fd,const void * buf,ssize_t count,boolean allow_fail)1329 ssize_t lives_write_le_buffered(int fd, const void *buf, ssize_t count, boolean allow_fail) {
1330 if (count <= 0) return 0;
1331 if (capable->byte_order == LIVES_BIG_ENDIAN && (prefs->bigendbug != 1)) {
1332 reverse_bytes((char *)buf, count, count);
1333 }
1334 return lives_write_buffered(fd, (char *)buf, count, allow_fail);
1335 }
1336
1337
lives_lseek_buffered_writer(int fd,off_t offset)1338 off_t lives_lseek_buffered_writer(int fd, off_t offset) {
1339 lives_file_buffer_t *fbuff;
1340
1341 if ((fbuff = find_in_file_buffers(fd)) == NULL) {
1342 LIVES_DEBUG("lives_lseek_buffered_writer: no file buffer found");
1343 return lseek(fd, offset, SEEK_SET);
1344 }
1345
1346 if (fbuff->read) {
1347 LIVES_ERROR("lives_lseek_buffered_writer: wrong buffer type");
1348 return 0;
1349 }
1350
1351 if (fbuff->bytes > 0) {
1352 ssize_t res = file_buffer_flush(fbuff);
1353 if (res < 0) return res;
1354 if (res < fbuff->bytes && !fbuff->allow_fail) {
1355 fbuff->eof = TRUE;
1356 return fbuff->offset;
1357 }
1358 }
1359 fbuff->offset = lseek(fbuff->fd, offset, SEEK_SET);
1360 return fbuff->offset;
1361 }
1362
1363
lives_buffered_offset(int fd)1364 off_t lives_buffered_offset(int fd) {
1365 lives_file_buffer_t *fbuff;
1366
1367 if ((fbuff = find_in_file_buffers(fd)) == NULL) {
1368 LIVES_DEBUG("lives_buffered_offset: no file buffer found");
1369 return lseek(fd, 0, SEEK_CUR);
1370 }
1371
1372 if (fbuff->read) return fbuff->offset - fbuff->bytes;
1373 return fbuff->offset + fbuff->bytes;
1374 }
1375
1376
lives_buffered_orig_size(int fd)1377 size_t lives_buffered_orig_size(int fd) {
1378 lives_file_buffer_t *fbuff;
1379
1380 if ((fbuff = find_in_file_buffers(fd)) == NULL) {
1381 LIVES_DEBUG("lives_buffered_offset: no file buffer found");
1382 return lseek(fd, 0, SEEK_CUR);
1383 }
1384
1385 if (!fbuff->read) return fbuff->orig_size;
1386 if (fbuff->orig_size == 0) fbuff->orig_size = (size_t)get_file_size(fd);
1387 return fbuff->orig_size;
1388 }
1389
1390
1391 /////////////////////////////////////////////
1392
lives_chdir(const char * path,boolean no_error_dlg)1393 int lives_chdir(const char *path, boolean no_error_dlg) {
1394 /// returns 0 on success
1395 /// on failure pops up an error dialog, unless no_error_dlg is TRUE
1396 int retval = chdir(path);
1397
1398 if (retval) {
1399 char *msg = lives_strdup_printf("Chdir failed to: %s", path);
1400 THREADVAR(chdir_failed) = TRUE;
1401 if (!no_error_dlg) {
1402 LIVES_ERROR(msg);
1403 do_chdir_failed_error(path);
1404 } else LIVES_DEBUG(msg);
1405 lives_free(msg);
1406 }
1407 return retval;
1408 }
1409
1410
lives_freep(void ** ptr)1411 LIVES_GLOBAL_INLINE boolean lives_freep(void **ptr) {
1412 // free a pointer and nullify it, only if it is non-null to start with
1413 // pass the address of the pointer in
1414 if (ptr && *ptr) {
1415 lives_free(*ptr);
1416 *ptr = NULL;
1417 return TRUE;
1418 }
1419 return FALSE;
1420 }
1421
1422
lives_kill(lives_pid_t pid,int sig)1423 LIVES_GLOBAL_INLINE int lives_kill(lives_pid_t pid, int sig) {
1424 if (pid == 0) {
1425 LIVES_ERROR("Tried to kill pid 0");
1426 return -1;
1427 }
1428 return kill(pid, sig);
1429 }
1430
1431
lives_killpg(lives_pgid_t pgrp,int sig)1432 LIVES_GLOBAL_INLINE int lives_killpg(lives_pgid_t pgrp, int sig) {return killpg(pgrp, sig);}
1433
1434
clear_mainw_msg(void)1435 LIVES_GLOBAL_INLINE void clear_mainw_msg(void) {lives_memset(mainw->msg, 0, MAINW_MSG_SIZE);}
1436
1437
lives_10pow(int pow)1438 LIVES_GLOBAL_INLINE uint64_t lives_10pow(int pow) {
1439 register int i;
1440 uint64_t res = 1;
1441 for (i = 0; i < pow; i++) res *= 10;
1442 return res;
1443 }
1444
1445
lives_fix(double val,int decimals)1446 LIVES_GLOBAL_INLINE double lives_fix(double val, int decimals) {
1447 double factor = (double)lives_10pow(decimals);
1448 if (val >= 0.) return (double)((int)(val * factor + 0.5)) / factor;
1449 return (double)((int)(val * factor - 0.5)) / factor;
1450 }
1451
1452
get_approx_ln(uint32_t x)1453 LIVES_GLOBAL_INLINE uint32_t get_approx_ln(uint32_t x) {
1454 x |= (x >> 1); x |= (x >> 2); x |= (x >> 4); x |= (x >> 8); x |= (x >> 16);
1455 return (++x) >> 1;
1456 }
1457
get_approx_ln64(uint64_t x)1458 LIVES_GLOBAL_INLINE uint64_t get_approx_ln64(uint64_t x) {
1459 x |= (x >> 1); x |= (x >> 2); x |= (x >> 4); x |= (x >> 8); x |= (x >> 16); x |= (x >> 32);
1460 return (++x) >> 1;
1461 }
1462
get_near2pow(uint64_t val)1463 LIVES_GLOBAL_INLINE uint64_t get_near2pow(uint64_t val) {
1464 uint64_t low = get_approx_ln64(val), high = low * 2;
1465 if (high < low || (val - low < high - val)) return low;
1466 return high;
1467 }
1468
1469 ///////////////////////////////////////////////////////////
1470
1471 static lives_time_source_t lastt = LIVES_TIME_SOURCE_NONE;
1472 static ticks_t delta = 0;
1473
reset_playback_clock(void)1474 void reset_playback_clock(void) {
1475 mainw->cadjticks = mainw->adjticks = mainw->syncticks = 0;
1476 lastt = LIVES_TIME_SOURCE_NONE;
1477 delta = 0;
1478 }
1479
1480
lives_get_current_playback_ticks(int64_t origsecs,int64_t orignsecs,lives_time_source_t * time_source)1481 ticks_t lives_get_current_playback_ticks(int64_t origsecs, int64_t orignsecs, lives_time_source_t *time_source) {
1482 // get the time using a variety of methods
1483 // time_source may be NULL or LIVES_TIME_SOURCE_NONE to set auto
1484 // or another value to force it (EXTERNAL cannot be forced)
1485 lives_time_source_t *tsource, xtsource = LIVES_TIME_SOURCE_NONE;
1486 ticks_t clock_ticks, current = -1;
1487 static ticks_t lclock_ticks, interticks;
1488
1489 if (time_source) tsource = time_source;
1490 else tsource = &xtsource;
1491
1492 clock_ticks = lives_get_relative_ticks(origsecs, orignsecs);
1493 mainw->clock_ticks = clock_ticks;
1494
1495 if (*tsource == LIVES_TIME_SOURCE_EXTERNAL) *tsource = LIVES_TIME_SOURCE_NONE;
1496
1497 if (mainw->foreign || prefs->force_system_clock || (prefs->vj_mode && (prefs->audio_src == AUDIO_SRC_EXT))) {
1498 *tsource = LIVES_TIME_SOURCE_SYSTEM;
1499 current = clock_ticks;
1500 }
1501
1502 if (*tsource == LIVES_TIME_SOURCE_NONE) {
1503 #ifdef ENABLE_JACK_TRANSPORT
1504 if (mainw->jack_can_stop && (prefs->jack_opts & JACK_OPTS_TIMEBASE_CLIENT) &&
1505 (prefs->jack_opts & JACK_OPTS_TRANSPORT_CLIENT) && !(mainw->record && !(prefs->rec_opts & REC_FRAMES))) {
1506 // calculate the time from jack transport
1507 *tsource = LIVES_TIME_SOURCE_EXTERNAL;
1508 current = jack_transport_get_current_ticks();
1509 }
1510 #endif
1511 }
1512
1513 if (is_realtime_aplayer(prefs->audio_player) && (*tsource == LIVES_TIME_SOURCE_NONE ||
1514 *tsource == LIVES_TIME_SOURCE_SOUNDCARD)) {
1515 if ((!mainw->is_rendering || (mainw->multitrack && !cfile->opening && !mainw->multitrack->is_rendering)) &&
1516 (!(mainw->fixed_fpsd > 0. || (mainw->vpp && mainw->vpp->fixed_fpsd > 0. && mainw->ext_playback)))) {
1517 // get time from soundcard
1518 // this is done so as to synch video stream with the audio
1519 // we do this in two cases:
1520 // - for internal audio, playing back a clip with audio (writing)
1521 // - or when audio source is set to external (reading) and we are recording, no internal audio generator is running
1522
1523 // we ignore this if we are running with a playback plugin which requires a fixed framerate (e.g a streaming plugin)
1524 // in that case we will adjust the audio rate to fit the system clock
1525
1526 // or if we are rendering
1527
1528 #ifdef ENABLE_JACK
1529 if (prefs->audio_player == AUD_PLAYER_JACK &&
1530 ((prefs->audio_src == AUDIO_SRC_INT && mainw->jackd && mainw->jackd->in_use &&
1531 IS_VALID_CLIP(mainw->jackd->playing_file) && mainw->files[mainw->jackd->playing_file]->achans > 0) ||
1532 (prefs->audio_src == AUDIO_SRC_EXT && mainw->jackd_read && mainw->jackd_read->in_use))) {
1533 *tsource = LIVES_TIME_SOURCE_SOUNDCARD;
1534 if (prefs->audio_src == AUDIO_SRC_EXT && mainw->agen_key == 0 && !mainw->agen_needs_reinit)
1535 current = lives_jack_get_time(mainw->jackd_read);
1536 else
1537 current = lives_jack_get_time(mainw->jackd);
1538 }
1539 #endif
1540
1541 #ifdef HAVE_PULSE_AUDIO
1542 if (prefs->audio_player == AUD_PLAYER_PULSE &&
1543 ((prefs->audio_src == AUDIO_SRC_INT && mainw->pulsed && mainw->pulsed->in_use &&
1544 IS_VALID_CLIP(mainw->pulsed->playing_file) && mainw->files[mainw->pulsed->playing_file]->achans > 0) ||
1545 (prefs->audio_src == AUDIO_SRC_EXT && mainw->pulsed_read && mainw->pulsed_read->in_use))) {
1546 *tsource = LIVES_TIME_SOURCE_SOUNDCARD;
1547 if (prefs->audio_src == AUDIO_SRC_EXT && mainw->agen_key == 0 && !mainw->agen_needs_reinit)
1548 current = lives_pulse_get_time(mainw->pulsed_read);
1549 else current = lives_pulse_get_time(mainw->pulsed);
1550 }
1551 #endif
1552 }
1553 }
1554
1555 if (*tsource == LIVES_TIME_SOURCE_NONE || current == -1) {
1556 *tsource = LIVES_TIME_SOURCE_SYSTEM;
1557 current = clock_ticks;
1558 }
1559
1560 //if (lastt != *tsource) {
1561 /* g_print("t1 = %ld, t2 = %ld cadj =%ld, adj = %ld del =%ld %ld %ld\n", clock_ticks, current, mainw->cadjticks, mainw->adjticks, */
1562 /* delta, clock_ticks + mainw->cadjticks, current + mainw->adjticks); */
1563 //}
1564
1565 /// synchronised timing
1566 /// it can be helpful to imagine a virtual clock which is at currrent time:
1567 /// clock time - cadjticks = virtual time = other time + adjticks
1568 /// cadjticks and adjticks are only set when we switch from one source to another, i.e the virtual clock will run @ different rates
1569 /// depending on the source. This is fine as it enables sync with the clock source, provided the time doesn't jump when moving
1570 /// from one source to another.
1571 /// when the source changes we then alter either cadjticks or adjticks so that the initial timing matches
1572 /// e.g when switching to clock source, cadjticks and adjticks will have diverged. So we want to set new cadjtick s.t:
1573 /// clock ticks - cadjticks == source ticks + adjticks. i.e cadjticks = clock ticks - (source ticks + adjticks).
1574 /// we use the delta calculated the last time, since the other source may longer be available.
1575 /// this should not be a concern since this function is called very frequently
1576 /// recalling cadjticks_new = clock_ticks - (source_ticks + adjticks), and substituting for delta we get:
1577 // cadjticks_new = clock_ticks - (source_ticks + adjticks) = delta + cadjticks_old
1578 /// conversely, when switching from clock to source, adjticks_new = clock_ticks - cadjticks - source_ticks
1579 /// again, this just delta + adjticks; in this case we can use current delta since it is assumed that the system clock is always available
1580
1581 /// this scheme does, however introduce a small problem, which is that when the sources are switched, we assume that the
1582 /// time on both clocks is equivalent. This can lead to a problem when switching clips, since temporarily we switch to system
1583 /// time and then back to soundcard. However, this can cause some updates to the timer to be missed, i.e the audio is playing but the
1584 /// samples are not counted, however we cannot simply add these to the soundcard timer, as they will be lost due to the resync.
1585 /// hence we need mainw->syncticks --> a global adjustment which is independant of the clock source. This is similar to
1586 /// mainw->deltaticks for the player, however, deltaticks is a temporary impulse, whereas syncticks is a permanent adjustment.
1587
1588 if (*tsource == LIVES_TIME_SOURCE_SYSTEM) {
1589 if (lastt != LIVES_TIME_SOURCE_SYSTEM && lastt != LIVES_TIME_SOURCE_NONE) {
1590 // current + adjt == clock_ticks - cadj /// interticks == lcurrent + adj
1591 // current - ds + adjt == clock_ticks - dc - cadj /// interticks == lcurrent + adj
1592
1593 // cadj = clock_ticks - interticks + (current - lcurrent) - since we may not have current
1594 // we have to approximate with clock_ticks - lclock_ticks
1595 mainw->cadjticks = clock_ticks - interticks - (clock_ticks - lclock_ticks);
1596 }
1597 interticks = clock_ticks - mainw->cadjticks;
1598 } else {
1599 if (lastt == LIVES_TIME_SOURCE_SYSTEM) {
1600 // current - ds + adjt == clock_ticks - dc - cadj /// iinterticks == lclock_ticks - cadj ///
1601 mainw->adjticks = interticks - current + (clock_ticks - lclock_ticks);
1602 }
1603 interticks = current + mainw->adjticks;
1604 }
1605
1606 /* if (lastt != *tsource) { */
1607 /* g_print("aft t1 = %ld, t2 = %ld cadj =%ld, adj = %ld del =%ld %ld %ld\n", clock_ticks, current, mainw->cadjticks, */
1608 /* mainw->adjticks, delta, clock_ticks + mainw->cadjticks, current + mainw->adjticks); */
1609 /* } */
1610 lclock_ticks = clock_ticks;
1611 lastt = *tsource;
1612 return interticks + mainw->syncticks;
1613 }
1614
1615
1616 ///////////////// alarms /////
1617
lives_alarm_reset(lives_alarm_t alarm_handle,ticks_t ticks)1618 LIVES_GLOBAL_INLINE lives_alarm_t lives_alarm_reset(lives_alarm_t alarm_handle, ticks_t ticks) {
1619 // set to now + offset
1620 // invalid alarm number
1621 lives_timeout_t *alarm;
1622 if (alarm_handle <= 0 || alarm_handle > LIVES_MAX_ALARMS) {
1623 LIVES_WARN("Invalid alarm handle");
1624 break_me("inv alarm handle in lives_alarm_reset");
1625 return -1;
1626 }
1627
1628 // offset of 1 was added for caller
1629 alarm = &mainw->alarms[--alarm_handle];
1630
1631 alarm->lastcheck = lives_get_current_ticks();
1632 alarm->tleft = ticks;
1633 return ++alarm_handle;
1634 }
1635
1636
1637 /** set alarm for now + delta ticks (10 nanosec)
1638 param ticks (10 nanoseconds) is the offset when we want our alarm to trigger
1639 returns int handle or -1
1640 call lives_get_alarm(handle) to test if time arrived
1641 */
1642
lives_alarm_set(ticks_t ticks)1643 lives_alarm_t lives_alarm_set(ticks_t ticks) {
1644 int i;
1645
1646 // we will assign [this] next
1647 lives_alarm_t ret;
1648
1649 pthread_mutex_lock(&mainw->alarmlist_mutex);
1650
1651 ret = mainw->next_free_alarm;
1652
1653 if (ret > LIVES_MAX_USER_ALARMS) ret--;
1654 else {
1655 // no alarm slots left
1656 if (mainw->next_free_alarm == ALL_USED) {
1657 pthread_mutex_unlock(&mainw->alarmlist_mutex);
1658 LIVES_WARN("No alarms left");
1659 return ALL_USED;
1660 }
1661 }
1662
1663 // system alarms
1664 if (ret >= LIVES_MAX_USER_ALARMS) {
1665 lives_alarm_reset(++ret, ticks);
1666 pthread_mutex_unlock(&mainw->alarmlist_mutex);
1667 return ret;
1668 }
1669
1670 i = ++mainw->next_free_alarm;
1671
1672 // find free slot for next time
1673 while (mainw->alarms[i].lastcheck != 0 && i < LIVES_MAX_USER_ALARMS) i++;
1674
1675 if (i == LIVES_MAX_USER_ALARMS) mainw->next_free_alarm = ALL_USED; // no more alarm slots
1676 else mainw->next_free_alarm = i; // OK
1677 lives_alarm_reset(++ret, ticks);
1678 pthread_mutex_unlock(&mainw->alarmlist_mutex);
1679
1680 return ret;
1681 }
1682
1683
1684 /*** check if alarm time passed yet, if so clear that alarm and return TRUE
1685 else return FALSE
1686 */
lives_alarm_check(lives_alarm_t alarm_handle)1687 ticks_t lives_alarm_check(lives_alarm_t alarm_handle) {
1688 ticks_t curticks;
1689 lives_timeout_t *alarm;
1690
1691 // invalid alarm number
1692 if (alarm_handle <= 0 || alarm_handle > LIVES_MAX_ALARMS) {
1693 LIVES_WARN("Invalid alarm handle");
1694 break_me("inv alarm handle in lives_alarm_check");
1695 return -1;
1696 }
1697
1698 // offset of 1 was added for caller
1699 alarm = &mainw->alarms[--alarm_handle];
1700
1701 // alarm time was never set !
1702 if (alarm->lastcheck == 0) {
1703 LIVES_WARN("Alarm time not set");
1704 return 0;
1705 }
1706
1707 curticks = lives_get_current_ticks();
1708
1709 if (prefs->show_dev_opts) {
1710 /// guard against long interrupts (when debugging for example)
1711 // if the last check was > 5 seconds ago, we ignore the time jump, updating the check time but not reducing the time left
1712 if (curticks - alarm->lastcheck > 5 * TICKS_PER_SECOND) {
1713 alarm->lastcheck = curticks;
1714 return alarm->tleft;
1715 }
1716 }
1717
1718 alarm->tleft -= curticks - alarm->lastcheck;
1719
1720 if (alarm->tleft <= 0) {
1721 // reached alarm time, free up this timer and return TRUE
1722 alarm->lastcheck = 0;
1723 LIVES_DEBUG("Alarm reached");
1724 return 0;
1725 }
1726 alarm->lastcheck = curticks;
1727 // alarm time not reached yet
1728 return alarm->tleft;
1729 }
1730
1731
lives_alarm_clear(lives_alarm_t alarm_handle)1732 boolean lives_alarm_clear(lives_alarm_t alarm_handle) {
1733 if (alarm_handle <= 0 || alarm_handle > LIVES_MAX_ALARMS) {
1734 LIVES_WARN("Invalid clear alarm handle");
1735 return FALSE;
1736 }
1737
1738 mainw->alarms[--alarm_handle].lastcheck = 0;
1739
1740 if (alarm_handle < LIVES_MAX_USER_ALARMS
1741 && (mainw->next_free_alarm == ALL_USED || alarm_handle < mainw->next_free_alarm)) {
1742 mainw->next_free_alarm = alarm_handle;
1743 }
1744 return TRUE;
1745 }
1746
1747
1748
1749 /* convert to/from a big endian 32 bit float for internal use */
LEFloat_to_BEFloat(float f)1750 LIVES_GLOBAL_INLINE float LEFloat_to_BEFloat(float f) {
1751 if (capable->byte_order == LIVES_LITTLE_ENDIAN) swab4(&f, &f, 1);
1752 return f;
1753 }
1754
1755
calc_time_from_frame(int clip,int frame)1756 LIVES_GLOBAL_INLINE double calc_time_from_frame(int clip, int frame) {return (frame - 1.) / mainw->files[clip]->fps;}
1757
1758
calc_frame_from_time(int filenum,double time)1759 LIVES_GLOBAL_INLINE int calc_frame_from_time(int filenum, double time) {
1760 // return the nearest frame (rounded) for a given time, max is cfile->frames
1761 int frame = 0;
1762 if (time < 0.) return mainw->files[filenum]->frames ? 1 : 0;
1763 frame = (int)(time * mainw->files[filenum]->fps + 1.49999);
1764 return (frame < mainw->files[filenum]->frames) ? frame : mainw->files[filenum]->frames;
1765 }
1766
1767
calc_frame_from_time2(int filenum,double time)1768 LIVES_GLOBAL_INLINE int calc_frame_from_time2(int filenum, double time) {
1769 // return the nearest frame (rounded) for a given time
1770 // allow max (frames+1)
1771 int frame = 0;
1772 if (time < 0.) return mainw->files[filenum]->frames ? 1 : 0;
1773 frame = (int)(time * mainw->files[filenum]->fps + 1.49999);
1774 return (frame < mainw->files[filenum]->frames + 1) ? frame : mainw->files[filenum]->frames + 1;
1775 }
1776
1777
calc_frame_from_time3(int filenum,double time)1778 LIVES_GLOBAL_INLINE int calc_frame_from_time3(int filenum, double time) {
1779 // return the nearest frame (floor) for a given time
1780 // allow max (frames+1)
1781 int frame = 0;
1782 if (time < 0.) return mainw->files[filenum]->frames ? 1 : 0;
1783 frame = (int)(time * mainw->files[filenum]->fps + 1.);
1784 return (frame < mainw->files[filenum]->frames + 1) ? frame : mainw->files[filenum]->frames + 1;
1785 }
1786
1787
calc_frame_from_time4(int filenum,double time)1788 LIVES_GLOBAL_INLINE int calc_frame_from_time4(int filenum, double time) {
1789 // return the nearest frame (rounded) for a given time, no maximum
1790 int frame = 0;
1791 if (time < 0.) return mainw->files[filenum]->frames ? 1 : 0;
1792 frame = (int)(time * mainw->files[filenum]->fps + 1.49999);
1793 return frame;
1794 }
1795
1796
check_for_audio_stop(int fileno,frames_t first_frame,frames_t last_frame)1797 static boolean check_for_audio_stop(int fileno, frames_t first_frame, frames_t last_frame) {
1798 // this is only used for older versions with non-realtime players
1799 // return FALSE if audio stops playback
1800 lives_clip_t *sfile = mainw->files[fileno];
1801 #ifdef ENABLE_JACK
1802 if (prefs->audio_player == AUD_PLAYER_JACK && mainw->jackd && mainw->jackd->playing_file == fileno) {
1803 if (!mainw->loop || mainw->playing_sel) {
1804 if (!mainw->loop_cont) {
1805 if ((sfile->adirection == LIVES_DIRECTION_REVERSE && mainw->aframeno - 0.0001 < (double)first_frame + 0.0001)
1806 || (sfile->adirection == LIVES_DIRECTION_FORWARD && mainw->aframeno + 0.0001 >= (double)last_frame - 0.0001)) {
1807 return FALSE;
1808 }
1809 }
1810 } else {
1811 if (!mainw->loop_cont) {
1812 if ((sfile->adirection == LIVES_DIRECTION_REVERSE && mainw->aframeno < 0.9999) ||
1813 (sfile->adirection == LIVES_DIRECTION_FORWARD && calc_time_from_frame(mainw->current_file, mainw->aframeno + 1.0001)
1814 >= cfile->laudio_time - 0.0001)) {
1815 return FALSE;
1816 // *INDENT-OFF*
1817 }}}}
1818 // *INDENT-ON*
1819
1820 #endif
1821 #ifdef HAVE_PULSE_AUDIO
1822 if (prefs->audio_player == AUD_PLAYER_PULSE && mainw->pulsed && mainw->pulsed->playing_file == fileno) {
1823 if (!mainw->loop || mainw->playing_sel) {
1824 if (!mainw->loop_cont) {
1825 if ((sfile->adirection == LIVES_DIRECTION_REVERSE && mainw->aframeno - 0.0001 < (double)first_frame + 0.0001)
1826 || (sfile->adirection == LIVES_DIRECTION_FORWARD && mainw->aframeno + 1.0001 >= (double)last_frame - 0.0001)) {
1827 return FALSE;
1828 }
1829 }
1830 } else {
1831 if (!mainw->loop_cont) {
1832 if ((sfile->adirection == LIVES_DIRECTION_REVERSE && mainw->aframeno < 0.9999) ||
1833 (sfile->adirection == LIVES_DIRECTION_FORWARD && calc_time_from_frame(mainw->current_file, mainw->aframeno + 1.0001)
1834 >= cfile->laudio_time - 0.0001)) {
1835 return FALSE;
1836 // *INDENT-OFF*
1837 }}}}
1838 // *INDENT-ON*
1839
1840 #endif
1841 return TRUE;
1842 }
1843
1844
calc_aframeno(int fileno)1845 void calc_aframeno(int fileno) {
1846 #ifdef ENABLE_JACK
1847 if (prefs->audio_player == AUD_PLAYER_JACK && ((mainw->jackd && mainw->jackd->playing_file == fileno) ||
1848 (mainw->jackd_read && mainw->jackd_read->playing_file == fileno))) {
1849 // get seek_pos from jack
1850 if (mainw->jackd_read) mainw->aframeno = lives_jack_get_pos(mainw->jackd_read) * cfile->fps + 1.;
1851 else mainw->aframeno = lives_jack_get_pos(mainw->jackd) * cfile->fps + 1.;
1852 }
1853 #endif
1854 #ifdef HAVE_PULSE_AUDIO
1855 if (prefs->audio_player == AUD_PLAYER_PULSE && ((mainw->pulsed && mainw->pulsed->playing_file == fileno) ||
1856 (mainw->pulsed_read && mainw->pulsed_read->playing_file == fileno))) {
1857 // get seek_pos from pulse
1858 if (mainw->pulsed_read) mainw->aframeno = lives_pulse_get_pos(mainw->pulsed_read) * cfile->fps + 1.;
1859 else mainw->aframeno = lives_pulse_get_pos(mainw->pulsed) * cfile->fps + 1.;
1860 }
1861 #endif
1862 }
1863
1864
calc_new_playback_position(int fileno,ticks_t otc,ticks_t * ntc)1865 frames_t calc_new_playback_position(int fileno, ticks_t otc, ticks_t *ntc) {
1866 // returns a frame number (floor) using sfile->last_frameno and ntc-otc
1867 // takes into account looping modes
1868
1869 // the range is first_frame -> last_frame
1870
1871 // which is generally 1 -> sfile->frames, unless we are playing a selection
1872
1873 // in case the frame is out of range and playing, returns 0 and sets mainw->cancelled
1874
1875 // ntc is adjusted backwards to timecode of the new frame
1876
1877 // the basic operation is quite simple, given the time difference between the last frame and
1878 // now, we calculate the new frame from the current fps and then ensure it is in the range
1879 // first_frame -> last_frame
1880
1881 // Complications arise because we have ping-pong loop mode where the play direction
1882 // alternates - here we need to determine how many times we have reached the start or end
1883 // play point. This is similar to the winding number in topological calculations.
1884
1885 // caller should check return value of ntc, and if it differs from otc, show the frame
1886
1887 // note we also calculate the audio "frame" and position for realtime audio players
1888 // this is done so we can check here if audio limits stopped playback
1889
1890 static boolean norecurse = FALSE;
1891 static ticks_t last_ntc = 0;
1892
1893 ticks_t ddtc = *ntc - last_ntc;
1894 ticks_t dtc = *ntc - otc;
1895 int64_t first_frame, last_frame, selrange;
1896 lives_clip_t *sfile = mainw->files[fileno];
1897 double fps;
1898 lives_direction_t dir;
1899 frames_t cframe, nframe, fdelta;
1900 int nloops;
1901 int aplay_file = fileno;
1902
1903 if (!sfile) return 0;
1904
1905 cframe = sfile->last_frameno;
1906 if (norecurse) return cframe;
1907
1908 if (sfile->achans > 0) {
1909 #ifdef HAVE_PULSE_AUDIO
1910 if (prefs->audio_player == AUD_PLAYER_PULSE) {
1911 if (mainw->pulsed) aplay_file = mainw->pulsed->playing_file;
1912 }
1913 #endif
1914 #ifdef ENABLE_JACK
1915 if (prefs->audio_player == AUD_PLAYER_JACK) {
1916 if (mainw->jackd) aplay_file = mainw->jackd->playing_file;
1917 }
1918 #endif
1919 if (!IS_VALID_CLIP(aplay_file)) aplay_file = -1;
1920 else {
1921 if (fileno != aplay_file) {
1922 off64_t aseek_pos_delta = (off64_t)((double)dtc / TICKS_PER_SECOND_DBL * (double)(sfile->arate))
1923 * sfile->achans * sfile->asampsize / 8;
1924 if (sfile->adirection == LIVES_DIRECTION_FORWARD) sfile->aseek_pos += aseek_pos_delta;
1925 else sfile->aseek_pos -= aseek_pos_delta;
1926 if (sfile->aseek_pos < 0 || sfile->aseek_pos > sfile->afilesize) {
1927 nloops = sfile->aseek_pos / sfile->afilesize;
1928 if (mainw->ping_pong && (sfile->adirection == LIVES_DIRECTION_REVERSE || clip_can_reverse(fileno))) {
1929 sfile->adirection += nloops;
1930 sfile->adirection &= 1;
1931 if (sfile->adirection == LIVES_DIRECTION_BACKWARD && !clip_can_reverse(fileno))
1932 sfile->adirection = LIVES_DIRECTION_REVERSE;
1933 }
1934 sfile->aseek_pos -= nloops * sfile->afilesize;
1935 if (sfile->adirection == LIVES_DIRECTION_REVERSE) sfile->aseek_pos = sfile->afilesize - sfile->aseek_pos;
1936 // *INDENT-OFF*
1937 }}}}
1938 // *INDENT-ON*
1939
1940 if (sfile->frames == 0 && !mainw->foreign) {
1941 if (fileno == mainw->playing_file) mainw->scratch = SCRATCH_NONE;
1942 return 0;
1943 }
1944
1945 fps = sfile->pb_fps;
1946 if (!LIVES_IS_PLAYING || (fps < 0.001 && fps > -0.001 && mainw->scratch != SCRATCH_NONE)) fps = sfile->fps;
1947
1948 if (fps < 0.001 && fps > -0.001) {
1949 *ntc = otc;
1950 if (fileno == mainw->playing_file) {
1951 mainw->scratch = SCRATCH_NONE;
1952 if (prefs->audio_src == AUDIO_SRC_INT) calc_aframeno(fileno);
1953 }
1954 return cframe;
1955 }
1956
1957 // dtc is delta ticks (last frame time - current time), quantise this to the frame rate and round down
1958 dtc = q_gint64_floor(dtc, fps);
1959
1960 // ntc is the time when the next frame should be / have been played, or if dtc is zero we just set it to otc - the last frame time
1961 *ntc = otc + dtc;
1962
1963 // nframe is our new frame; convert dtc to sencods, and multiply by the frame rate, then add or subtract from current frame number
1964 // the small constant is just to account for rounding errors
1965 if (fps >= 0.)
1966 nframe = cframe + (frames_t)((double)dtc / TICKS_PER_SECOND_DBL * fps + .00001);
1967 else
1968 nframe = cframe + (frames_t)((double)dtc / TICKS_PER_SECOND_DBL * fps - .00001);
1969
1970 if (nframe != cframe) {
1971 if (!IS_NORMAL_CLIP(fileno)) {
1972 return 1;
1973 }
1974 if (mainw->foreign) return sfile->frameno + 1;
1975 }
1976
1977 if (fileno == mainw->playing_file) {
1978 /// if we are scratching we do the following:
1979 /// the time since the last call is considered to have happened at an increased fps (fwd or back)
1980 /// we recalculate the frame at ntc as if we were at the faster framerate.
1981
1982 if (mainw->scratch == SCRATCH_FWD || mainw->scratch == SCRATCH_BACK
1983 || mainw->scratch == SCRATCH_FWD_EXTRA || mainw->scratch == SCRATCH_BACK_EXTRA) {
1984 if (mainw->scratch == SCRATCH_FWD_EXTRA || mainw->scratch == SCRATCH_BACK_EXTRA) ddtc *= 4;
1985 if (mainw->scratch == SCRATCH_BACK || mainw->scratch == SCRATCH_BACK_EXTRA) {
1986 mainw->deltaticks -= ddtc * KEY_RPT_INTERVAL * prefs->scratchback_amount
1987 * USEC_TO_TICKS / TICKS_PER_SECOND_DBL;
1988 } else mainw->deltaticks += ddtc * KEY_RPT_INTERVAL * prefs->scratchback_amount
1989 * USEC_TO_TICKS / TICKS_PER_SECOND_DBL;
1990 // dtc is delta ticks, quantise this to the frame rate and round down
1991 mainw->deltaticks = q_gint64_floor(mainw->deltaticks, fps * 4);
1992 }
1993
1994 if (nframe != cframe) {
1995 int delval = (ticks_t)((double)mainw->deltaticks / TICKS_PER_SECOND_DBL * fps + .5);
1996 if (delval <= -1 || delval >= 1) {
1997 /// the frame number changed, but we will recalulate the value using mainw->deltaticks
1998 frames64_t xnframe = cframe + (int64_t)delval;
1999 frames64_t dframes = xnframe - nframe;
2000
2001 if (xnframe != nframe) {
2002 nframe = xnframe;
2003 /// retain the fractional part for next time
2004 mainw->deltaticks -= (ticks_t)((double)delval / fps * TICKS_PER_SECOND_DBL);
2005 if (nframe != cframe) {
2006 sfile->last_frameno += dframes;
2007 if (fps < 0. && mainw->scratch == SCRATCH_FWD) sfile->last_frameno--;
2008 if (fps > 0. && mainw->scratch == SCRATCH_BACK) sfile->last_frameno++;
2009 mainw->scratch = SCRATCH_JUMP_NORESYNC;
2010 } else mainw->scratch = SCRATCH_NONE;
2011 }
2012 }
2013 }
2014 last_ntc = *ntc;
2015 }
2016
2017 last_frame = sfile->frames;
2018 first_frame = 1;
2019
2020 if (fileno == mainw->playing_file) {
2021 // calculate audio "frame" from the number of samples played
2022 if (prefs->audio_src == AUDIO_SRC_INT) calc_aframeno(fileno);
2023
2024 if (nframe == cframe || mainw->foreign) {
2025 if (!mainw->foreign && fileno == mainw->playing_file &&
2026 mainw->scratch == SCRATCH_JUMP && (!mainw->event_list || mainw->record || mainw->record_paused) &&
2027 (prefs->audio_opts & AUDIO_OPTS_FOLLOW_FPS)) {
2028 resync_audio(nframe);
2029 mainw->scratch = SCRATCH_JUMP_NORESYNC;
2030 }
2031 return nframe;
2032 }
2033
2034 if (!mainw->clip_switched && (mainw->scratch == SCRATCH_NONE || mainw->scratch == SCRATCH_REV)) {
2035 last_frame = mainw->playing_sel ? sfile->end : mainw->play_end;
2036 if (last_frame > sfile->frames) last_frame = sfile->frames;
2037 first_frame = mainw->playing_sel ? sfile->start : mainw->loop_video ? mainw->play_start : 1;
2038 if (first_frame > sfile->frames) first_frame = sfile->frames;
2039 }
2040
2041 if (sfile->frames > 1 && prefs->noframedrop && (mainw->scratch == SCRATCH_NONE || mainw->scratch == SCRATCH_REV)) {
2042 // if noframedrop is set, we may not skip any frames
2043 // - the usual situation is that we are allowed to drop late frames
2044 // in this mode we may be forced to play at a reduced framerate
2045 if (nframe > cframe + 1) {
2046 // update this so the player can calculate 'dropped' frames correctly
2047 cfile->last_frameno -= (nframe - cframe - 1);
2048 nframe = cframe + 1;
2049 } else if (nframe < cframe - 1) {
2050 cfile->last_frameno += (cframe - 1 - nframe);
2051 nframe = cframe - 1;
2052 }
2053 }
2054 }
2055
2056 while (IS_NORMAL_CLIP(fileno) && (nframe < first_frame || nframe > last_frame)) {
2057 // get our frame back to within bounds:
2058 // define even parity (0) as backwards, odd parity (1) as forwards
2059 // we subtract the lower bound from the new frame, and divide the result by the selection length,
2060 // rounding towards zero. (nloops)
2061 // in ping mode this is then added to the original direction, and the resulting parity gives the new direction
2062 // the remainder after doing the division is then either added to the selection start (forwards)
2063 /// or subtracted from the selection end (backwards) [if we started backwards then the boundary crossing will be with the
2064 // lower bound and nloops and the remainder will be negative, so we subtract and add the negatvie value instead]
2065 // we must also set
2066
2067 if (fileno == mainw->playing_file) {
2068 // check if video stopped playback
2069 if (mainw->whentostop == STOP_ON_VID_END) {
2070 mainw->cancelled = CANCEL_VID_END;
2071 mainw->scratch = SCRATCH_NONE;
2072 return 0;
2073 }
2074 // we need to set this for later in the function
2075 mainw->scratch = SCRATCH_JUMP;
2076 }
2077
2078 if (first_frame == last_frame) {
2079 nframe = first_frame;
2080 break;
2081 }
2082
2083 if (fps < 0.) dir = LIVES_DIRECTION_BACKWARD; // 0, and even parity
2084 else dir = LIVES_DIRECTION_FORWARD; // 1, and odd parity
2085
2086 if (dir == LIVES_DIRECTION_FORWARD && nframe < first_frame) {
2087 // if FWD and before lower bound, just jump to lower bound
2088 nframe = first_frame;
2089 sfile->last_frameno = first_frame;
2090 break;
2091 }
2092
2093 if (dir == LIVES_DIRECTION_BACKWARD && nframe > last_frame) {
2094 // if BACK and after upper bound, just jump to upper bound
2095 nframe = last_frame;
2096 sfile->last_frameno = last_frame;
2097 break;
2098 }
2099
2100 fdelta = ABS(sfile->frameno - sfile->last_frameno);
2101 nframe -= first_frame;
2102 selrange = (1 + last_frame - first_frame);
2103 if (nframe < 0) nframe = -nframe;
2104 nloops = nframe / selrange;
2105 if (mainw->ping_pong && (dir == LIVES_DIRECTION_BACKWARD || (clip_can_reverse(fileno)))) {
2106 dir += nloops + dir + 1;
2107 dir = LIVES_DIRECTION_PAR(dir);
2108 if (dir == LIVES_DIRECTION_BACKWARD && !clip_can_reverse(fileno))
2109 dir = LIVES_DIRECTION_FORWARD;
2110 }
2111
2112 nframe -= nloops * selrange;
2113
2114 if (dir == LIVES_DIRECTION_FORWARD) {
2115 nframe += first_frame;
2116 sfile->last_frameno = nframe - fdelta;
2117 if (fps < 0.) {
2118 // backwards -> forwards
2119 if (fileno == mainw->playing_file) {
2120 /// must set norecurse, otherwise we can end up in an infinite loop since dirchange_callback calls this function
2121 norecurse = TRUE;
2122 dirchange_callback(NULL, NULL, 0, (LiVESXModifierType)0,
2123 LIVES_INT_TO_POINTER(SCREEN_AREA_FOREGROUND));
2124 norecurse = FALSE;
2125 } else sfile->pb_fps = -sfile->pb_fps;
2126 }
2127 }
2128
2129 else {
2130 nframe = last_frame - nframe;
2131 sfile->last_frameno = nframe + fdelta;
2132 if (fps > 0.) {
2133 // forwards -> backwards
2134 if (fileno == mainw->playing_file) {
2135 norecurse = TRUE;
2136 dirchange_callback(NULL, NULL, 0, (LiVESXModifierType)0,
2137 LIVES_INT_TO_POINTER(SCREEN_AREA_FOREGROUND));
2138 norecurse = FALSE;
2139 } else sfile->pb_fps = -sfile->pb_fps;
2140 }
2141 }
2142 break;
2143 }
2144
2145 if (fileno == mainw->playing_file && prefs->audio_src == AUDIO_SRC_INT && fileno == aplay_file && sfile->achans > 0
2146 && (prefs->audio_opts & AUDIO_OPTS_FOLLOW_FPS)
2147 && (mainw->scratch != SCRATCH_NONE && mainw->scratch != SCRATCH_JUMP_NORESYNC)) {
2148 if (mainw->whentostop == STOP_ON_AUD_END) {
2149 // check if audio stopped playback. The audio player will also be checking this, BUT: we have to check here too
2150 // before doing any resync, otherwise the video can loop and if the audio is then resynced it may never reach the end
2151 calc_aframeno(fileno);
2152 if (!check_for_audio_stop(fileno, first_frame + 1, last_frame - 1)) {
2153 mainw->cancelled = CANCEL_AUD_END;
2154 mainw->scratch = SCRATCH_NONE;
2155 return 0;
2156 }
2157 resync_audio(nframe);
2158 if (mainw->scratch == SCRATCH_JUMP) {
2159 mainw->video_seek_ready = TRUE; /// ????
2160 mainw->scratch = SCRATCH_JUMP_NORESYNC;
2161 }
2162 }
2163 }
2164 if (fileno == mainw->playing_file) {
2165 if (mainw->scratch != SCRATCH_NONE) {
2166 sfile->last_frameno = nframe;
2167 mainw->scratch = SCRATCH_JUMP_NORESYNC;
2168 }
2169 }
2170 return nframe;
2171 }
2172
2173
calc_maxspect(int rwidth,int rheight,int * cwidth,int * cheight)2174 void calc_maxspect(int rwidth, int rheight, int *cwidth, int *cheight) {
2175 // calculate maxspect (maximum size which maintains aspect ratio)
2176 // of cwidth, cheight - given restrictions rwidth * rheight
2177
2178 double aspect;
2179 if (*cwidth <= 0 || *cheight <= 0 || rwidth <= 0 || rheight <= 0) return;
2180
2181 aspect = (double)(*cwidth) / (double)(*cheight);
2182
2183 *cwidth = rwidth;
2184 *cheight = (double)(*cwidth) / aspect;
2185 if (*cheight > rheight) {
2186 // image too tall shrink it
2187 *cheight = rheight;
2188 *cwidth = (double)(*cheight) * aspect;
2189 }
2190 *cwidth = ((*cwidth + 1) >> 1) << 1;
2191 *cheight = ((*cheight + 1) >> 1) << 1;
2192 }
2193
2194
calc_minspect(int rwidth,int rheight,int * cwidth,int * cheight)2195 void calc_minspect(int rwidth, int rheight, int *cwidth, int *cheight) {
2196 // calculate minspect (maximum size which conforms to aspect ratio of
2197 // of rwidth, rheight) - given restrictions cwidth * cheight
2198
2199 double aspect, dheight;
2200
2201 if (*cwidth <= 0 || *cheight <= 0 || rwidth <= 0 || rheight <= 0) return;
2202
2203 aspect = (double)(rwidth) / (double)(rheight);
2204 dheight = (double)(*cwidth) / aspect;
2205
2206 if (dheight <= ((double)(*cheight) * (1. + ASPECT_ALLOWANCE)))
2207 *cheight = (int)dheight;
2208 else
2209 *cwidth = (int)((double)(*cheight * aspect));
2210
2211 *cwidth = ((*cwidth + 1) >> 1) << 1;
2212 *cheight = ((*cheight + 1) >> 1) << 1;
2213 }
2214
2215
calc_midspect(int rwidth,int rheight,int * cwidth,int * cheight)2216 void calc_midspect(int rwidth, int rheight, int *cwidth, int *cheight) {
2217 // calculate midspect (minimum size which conforms to aspect ratio of
2218 // of rwidth, rheight) - which contains cwidth, cheight
2219
2220 double aspect, dheight;
2221
2222 if (*cwidth <= 0 || *cheight <= 0 || rwidth <= 0 || rheight <= 0) return;
2223
2224 aspect = (double)(rwidth) / (double)(rheight);
2225 dheight = (double)(*cwidth) / aspect;
2226
2227 if (dheight >= ((double)(*cheight) * (1. - ASPECT_ALLOWANCE)))
2228 *cheight = (int)dheight;
2229 else
2230 *cwidth = (int)((double)(*cheight * aspect));
2231
2232 *cwidth = ((*cwidth + 1) >> 1) << 1;
2233 *cheight = ((*cheight + 1) >> 1) << 1;
2234 }
2235
2236 /////////////////////////////////////////////////////////////////////////////
2237
init_clipboard(void)2238 void init_clipboard(void) {
2239 int current_file = mainw->current_file;
2240 char *com;
2241
2242 if (!clipboard) {
2243 // here is where we create the clipboard
2244 // use get_new_handle(clipnumber,name);
2245 if (!get_new_handle(CLIPBOARD_FILE, "clipboard")) {
2246 mainw->error = TRUE;
2247 return;
2248 }
2249 } else {
2250 // experimental feature - we can have duplicate copies of the clipboard with different palettes / gamma
2251 for (int i = 0; i < mainw->ncbstores; i++) {
2252 if (mainw->cbstores[i] != clipboard) {
2253 char *clipd = lives_build_path(prefs->workdir, mainw->cbstores[i]->handle, NULL);
2254 if (lives_file_test(clipd, LIVES_FILE_TEST_EXISTS)) {
2255 char *com = lives_strdup_printf("%s close \"%s\"", prefs->backend, mainw->cbstores[i]->handle);
2256 char *permitname = lives_build_path(clipd, TEMPFILE_MARKER "." LIVES_FILE_EXT_TMP, NULL);
2257 lives_touch(permitname);
2258 lives_free(permitname);
2259 lives_system(com, TRUE);
2260 lives_free(com);
2261 }
2262 lives_free(clipd);
2263 }
2264 }
2265 mainw->ncbstores = 0;
2266
2267 if (clipboard->frames > 0) {
2268 // clear old clipboard
2269 // need to set current file to 0 before monitoring progress
2270 mainw->current_file = CLIPBOARD_FILE;
2271 cfile->cb_src = current_file;
2272
2273 if (cfile->clip_type == CLIP_TYPE_FILE) {
2274 lives_freep((void **)&cfile->frame_index);
2275 if (cfile->ext_src && cfile->ext_src_type == LIVES_EXT_SRC_DECODER) {
2276 close_clip_decoder(CLIPBOARD_FILE);
2277 }
2278 cfile->clip_type = CLIP_TYPE_DISK;
2279 }
2280
2281 com = lives_strdup_printf("%s clear_clipboard \"%s\"", prefs->backend, clipboard->handle);
2282 lives_rm(clipboard->info_file);
2283 lives_system(com, FALSE);
2284 lives_free(com);
2285
2286 if (THREADVAR(com_failed)) {
2287 mainw->current_file = current_file;
2288 return;
2289 }
2290
2291 cfile->progress_start = cfile->start;
2292 cfile->progress_end = cfile->end;
2293 // show a progress dialog, not cancellable
2294 do_progress_dialog(TRUE, FALSE, _("Clearing the clipboard"));
2295 }
2296 }
2297 mainw->current_file = current_file;
2298 *clipboard->file_name = 0;
2299 clipboard->img_type = IMG_TYPE_BEST; // override the pref
2300 clipboard->cb_src = current_file;
2301 }
2302
2303
get_nth_info_message(int n)2304 weed_plant_t *get_nth_info_message(int n) {
2305 weed_plant_t *msg = mainw->msg_list;
2306 const char *leaf;
2307 weed_error_t error;
2308 int m = 0;
2309
2310 if (n < 0) return NULL;
2311
2312 if (n >= mainw->n_messages) n = mainw->n_messages - 1;
2313
2314 if (n >= (mainw->n_messages >> 1)) {
2315 m = mainw->n_messages - 1;
2316 msg = weed_get_plantptr_value(msg, WEED_LEAF_PREVIOUS, &error);
2317 }
2318 if (mainw->ref_message && ABS(mainw->ref_message_n - n) < ABS(m - n)) {
2319 m = mainw->ref_message_n;
2320 msg = mainw->ref_message;
2321 }
2322
2323 if (m > n) leaf = WEED_LEAF_PREVIOUS;
2324 else leaf = WEED_LEAF_NEXT;
2325
2326 while (m != n) {
2327 msg = weed_get_plantptr_value(msg, leaf, &error);
2328 if (error != WEED_SUCCESS) return NULL;
2329 if (m > n) m--;
2330 else m++;
2331 }
2332 mainw->ref_message = msg;
2333 mainw->ref_message_n = n;
2334 return msg;
2335 }
2336
2337
dump_messages(int start,int end)2338 char *dump_messages(int start, int end) {
2339 weed_plant_t *msg = mainw->msg_list;
2340 char *text = lives_strdup(""), *tmp, *msgtext;
2341 boolean needs_newline = FALSE;
2342 int msgno = 0;
2343 int error;
2344
2345 while (msg) {
2346 msgtext = weed_get_string_value(msg, WEED_LEAF_LIVES_MESSAGE_STRING, &error);
2347 if (error != WEED_SUCCESS) break;
2348 if (msgno >= start) {
2349 #ifdef SHOW_MSG_LINENOS
2350 tmp = lives_strdup_printf("%s%s(%d)%s", text, needs_newline ? "\n" : "", msgno, msgtext);
2351 #else
2352 tmp = lives_strdup_printf("%s%s%s", text, needs_newline ? "\n" : "", msgtext);
2353 #endif
2354 lives_free(text);
2355 text = tmp;
2356 needs_newline = TRUE;
2357 }
2358 lives_free(msgtext);
2359 if (++msgno > end) if (end > -1) break;
2360 msg = weed_get_plantptr_value(msg, WEED_LEAF_NEXT, &error);
2361 if (error != WEED_SUCCESS) break;
2362 }
2363 return text;
2364 }
2365
2366
make_msg(const char * text)2367 static weed_plant_t *make_msg(const char *text) {
2368 // make single msg. text should have no newlines in it, except possibly as the last character.
2369 weed_plant_t *msg = weed_plant_new(WEED_PLANT_LIVES);
2370 if (!msg) return NULL;
2371
2372 weed_set_int_value(msg, WEED_LEAF_LIVES_SUBTYPE, LIVES_WEED_SUBTYPE_MESSAGE);
2373 weed_set_string_value(msg, WEED_LEAF_LIVES_MESSAGE_STRING, text);
2374
2375 weed_set_plantptr_value(msg, WEED_LEAF_NEXT, NULL);
2376 weed_set_plantptr_value(msg, WEED_LEAF_PREVIOUS, NULL);
2377 return msg;
2378 }
2379
2380
free_n_msgs(int frval)2381 int free_n_msgs(int frval) {
2382 int error;
2383 weed_plant_t *next, *end;
2384
2385 if (frval <= 0) return WEED_SUCCESS;
2386 if (frval > mainw->n_messages || !mainw->msg_list) frval = mainw->n_messages;
2387
2388 end = weed_get_plantptr_value(mainw->msg_list, WEED_LEAF_PREVIOUS, &error); // list end
2389 if (error != WEED_SUCCESS) {
2390 return error;
2391 }
2392
2393 while (frval-- && mainw->msg_list) {
2394 next = weed_get_plantptr_value(mainw->msg_list, WEED_LEAF_NEXT, &error); // becomes new head
2395 if (error != WEED_SUCCESS) {
2396 return error;
2397 }
2398 weed_plant_free(mainw->msg_list);
2399 mainw->msg_list = next;
2400 if (mainw->msg_list) {
2401 if (mainw->msg_list == end) weed_set_plantptr_value(mainw->msg_list, WEED_LEAF_PREVIOUS, NULL);
2402 else weed_set_plantptr_value(mainw->msg_list, WEED_LEAF_PREVIOUS, end);
2403 }
2404 mainw->n_messages--;
2405 if (mainw->ref_message) {
2406 if (--mainw->ref_message_n < 0) mainw->ref_message = NULL;
2407 }
2408 }
2409
2410 if (mainw->msg_adj)
2411 lives_adjustment_set_value(mainw->msg_adj, lives_adjustment_get_value(mainw->msg_adj) - 1.);
2412 return WEED_SUCCESS;
2413 }
2414
2415
add_messages_to_list(const char * text)2416 int add_messages_to_list(const char *text) {
2417 // append text to our message list, splitting it into lines
2418 // if we hit the max message limit then free the oldest one
2419 // returns a weed error
2420 weed_plant_t *msg, *end;;
2421 char **lines;
2422 int error, i, numlines;
2423
2424 if (prefs->max_messages == 0) return WEED_SUCCESS;
2425 if (!text || !*text) return WEED_SUCCESS;
2426
2427 // split text into lines
2428 numlines = get_token_count(text, '\n');
2429 lines = lives_strsplit(text, "\n", numlines);
2430
2431 for (i = 0; i < numlines; i++) {
2432 if (!mainw->msg_list) {
2433 mainw->msg_list = make_msg(lines[i]);
2434 if (!mainw->msg_list) {
2435 mainw->n_messages = 0;
2436 lives_strfreev(lines);
2437 return WEED_ERROR_MEMORY_ALLOCATION;
2438 }
2439 mainw->n_messages = 1;
2440 continue;
2441 }
2442
2443 end = weed_get_plantptr_value(mainw->msg_list, WEED_LEAF_PREVIOUS, &error);
2444 if (error != WEED_SUCCESS) {
2445 lives_strfreev(lines);
2446 return error;
2447 }
2448 if (!end) end = mainw->msg_list;
2449
2450 if (i == 0) {
2451 // append first line to text of last msg
2452 char *strg2, *strg = weed_get_string_value(end, WEED_LEAF_LIVES_MESSAGE_STRING, &error);
2453 if (error != WEED_SUCCESS) {
2454 lives_strfreev(lines);
2455 return error;
2456 }
2457 strg2 = lives_strdup_printf("%s%s", strg, lines[0]);
2458 weed_set_string_value(end, WEED_LEAF_LIVES_MESSAGE_STRING, strg2);
2459 lives_free(strg);
2460 lives_free(strg2);
2461 continue;
2462 }
2463
2464 if (prefs->max_messages > 0 && mainw->n_messages + 1 > prefs->max_messages) {
2465 // retire the oldest if we reached the limit
2466 error = free_n_msgs(1);
2467 if (error != WEED_SUCCESS) {
2468 lives_strfreev(lines);
2469 return error;
2470 }
2471 if (!mainw->msg_list) {
2472 i = numlines - 2;
2473 continue;
2474 }
2475 }
2476
2477 msg = make_msg(lines[i]);
2478 if (!msg) {
2479 lives_strfreev(lines);
2480 return WEED_ERROR_MEMORY_ALLOCATION;
2481 }
2482
2483 mainw->n_messages++;
2484
2485 // head will get new previous (us)
2486 weed_set_plantptr_value(mainw->msg_list, WEED_LEAF_PREVIOUS, msg);
2487 // we will get new previous (end)
2488 weed_set_plantptr_value(msg, WEED_LEAF_PREVIOUS, end);
2489 // end will get new next (us)
2490 weed_set_plantptr_value(end, WEED_LEAF_NEXT, msg);
2491 }
2492 lives_strfreev(lines);
2493 return WEED_SUCCESS;
2494 }
2495
2496
d_print_urgency(double timeout,const char * fmt,...)2497 boolean d_print_urgency(double timeout, const char *fmt, ...) {
2498 // overlay emergency message on playback frame
2499 va_list xargs;
2500 char *text;
2501
2502 va_start(xargs, fmt);
2503 text = lives_strdup_vprintf(fmt, xargs);
2504 va_end(xargs);
2505
2506 d_print(text);
2507
2508 if (LIVES_IS_PLAYING && prefs->show_urgency_msgs) {
2509 int nfa = mainw->next_free_alarm;
2510 mainw->next_free_alarm = LIVES_URGENCY_ALARM;
2511 lives_freep((void **)&mainw->urgency_msg);
2512 lives_alarm_set(timeout * TICKS_PER_SECOND_DBL);
2513 mainw->next_free_alarm = nfa;
2514 mainw->urgency_msg = lives_strdup(text);
2515 lives_free(text);
2516 return TRUE;
2517 }
2518 lives_free(text);
2519 return FALSE;
2520 }
2521
2522
d_print_overlay(double timeout,const char * fmt,...)2523 boolean d_print_overlay(double timeout, const char *fmt, ...) {
2524 // overlay a message on playback frame
2525 va_list xargs;
2526 char *text;
2527 va_start(xargs, fmt);
2528 text = lives_strdup_vprintf(fmt, xargs);
2529 va_end(xargs);
2530 if (LIVES_IS_PLAYING && prefs->show_overlay_msgs && !(mainw->urgency_msg && prefs->show_urgency_msgs)) {
2531 lives_freep((void **)&mainw->overlay_msg);
2532 mainw->overlay_msg = lives_strdup(text);
2533 lives_free(text);
2534 lives_alarm_reset(mainw->overlay_alarm, timeout * TICKS_PER_SECOND_DBL);
2535 return TRUE;
2536 }
2537 lives_free(text);
2538 return FALSE;
2539 }
2540
2541
d_print(const char * fmt,...)2542 void d_print(const char *fmt, ...) {
2543 // collect output for the main message area (and info log)
2544
2545 // there are several small tweaks for this:
2546
2547 // mainw->suppress_dprint :: TRUE - dont print anything, return (for silencing noisy message blocks)
2548 // mainw->no_switch_dprint :: TRUE - disable printing of switch message when maine->current_file changes
2549
2550 // mainw->last_dprint_file :: clip number of last mainw->current_file;
2551 va_list xargs;
2552
2553 char *tmp, *text;
2554
2555 if (!prefs->show_gui) return;
2556 if (mainw->suppress_dprint) return;
2557
2558 va_start(xargs, fmt);
2559 text = lives_strdup_vprintf(fmt, xargs);
2560 va_end(xargs);
2561
2562 if (mainw->current_file != mainw->last_dprint_file && mainw->current_file != 0 && !mainw->multitrack &&
2563 (mainw->current_file == -1 || (cfile && cfile->clip_type != CLIP_TYPE_GENERATOR)) && !mainw->no_switch_dprint) {
2564 if (mainw->current_file > 0) {
2565 char *swtext = lives_strdup_printf(_("\n==============================\nSwitched to clip %s\n"),
2566 tmp = get_menu_name(cfile,
2567 TRUE));
2568 lives_free(tmp);
2569 add_messages_to_list(swtext);
2570 lives_free(swtext);
2571 } else {
2572 add_messages_to_list(_("\n==============================\nSwitched to empty clip\n"));
2573 }
2574 }
2575
2576 add_messages_to_list(text);
2577 lives_free(text);
2578
2579 if (!mainw->go_away && prefs->show_gui && prefs->show_msg_area
2580 && ((!mainw->multitrack && mainw->msg_area
2581 && mainw->msg_adj)
2582 || (!mainw->multitrack && mainw->multitrack->msg_area
2583 && mainw->multitrack->msg_adj))) {
2584 if (mainw->multitrack) {
2585 msg_area_scroll_to_end(mainw->multitrack->msg_area, mainw->multitrack->msg_adj);
2586 lives_widget_queue_draw_if_visible(mainw->multitrack->msg_area);
2587 } else {
2588 msg_area_scroll_to_end(mainw->msg_area, mainw->msg_adj);
2589 lives_widget_queue_draw_if_visible(mainw->msg_area);
2590 }
2591 }
2592
2593 if ((mainw->current_file == -1 || (cfile && cfile->clip_type != CLIP_TYPE_GENERATOR)) &&
2594 (!mainw->no_switch_dprint || mainw->current_file != 0)) mainw->last_dprint_file = mainw->current_file;
2595 }
2596
2597
d_print_utility(const char * text,int osc_note,const char * osc_detail)2598 static void d_print_utility(const char *text, int osc_note, const char *osc_detail) {
2599 boolean nsdp = mainw->no_switch_dprint;
2600 mainw->no_switch_dprint = TRUE;
2601 d_print(text);
2602 if (osc_note != LIVES_OSC_NOTIFY_NONE) lives_notify(osc_note, osc_detail);
2603 if (!nsdp) {
2604 mainw->no_switch_dprint = FALSE;
2605 d_print("");
2606 }
2607 }
2608
2609
d_print_cancelled(void)2610 LIVES_GLOBAL_INLINE void d_print_cancelled(void) {
2611 d_print_utility(_("cancelled.\n"), LIVES_OSC_NOTIFY_CANCELLED, "");
2612 }
2613
2614
d_print_failed(void)2615 LIVES_GLOBAL_INLINE void d_print_failed(void) {
2616 d_print_utility(_("failed.\n"), LIVES_OSC_NOTIFY_FAILED, "");
2617 }
2618
2619
d_print_done(void)2620 LIVES_GLOBAL_INLINE void d_print_done(void) {
2621 d_print_utility(_("done.\n"), 0, NULL);
2622 }
2623
2624
d_print_file_error_failed(void)2625 LIVES_GLOBAL_INLINE void d_print_file_error_failed(void) {
2626 d_print_utility(_("error in file. Failed.\n"), 0, NULL);
2627 }
2628
2629
d_print_enough(int frames)2630 LIVES_GLOBAL_INLINE void d_print_enough(int frames) {
2631 if (frames == 0) d_print_cancelled();
2632 else {
2633 char *msg = lives_strdup_printf(P_("%d frame is enough !\n", "%d frames are enough !\n", frames), frames);
2634 d_print_utility(msg, 0, NULL);
2635 lives_free(msg);
2636 }
2637 }
2638
2639
buffer_lmap_error(lives_lmap_error_t lerror,const char * name,livespointer user_data,int clipno,int frameno,double atime,boolean affects_current)2640 void buffer_lmap_error(lives_lmap_error_t lerror, const char *name, livespointer user_data, int clipno,
2641 int frameno, double atime, boolean affects_current) {
2642 lmap_error *err = (lmap_error *)lives_malloc(sizeof(lmap_error));
2643 if (!err) return;
2644 err->type = lerror;
2645 if (name) err->name = lives_strdup(name);
2646 else err->name = NULL;
2647 err->data = user_data;
2648 err->clipno = clipno;
2649 err->frameno = frameno;
2650 err->atime = atime;
2651 err->current = affects_current;
2652 mainw->new_lmap_errors = lives_list_prepend(mainw->new_lmap_errors, err);
2653 }
2654
2655
unbuffer_lmap_errors(boolean add)2656 void unbuffer_lmap_errors(boolean add) {
2657 LiVESList *list = mainw->new_lmap_errors;
2658 while (list) {
2659 lmap_error *err = (lmap_error *)list->data;
2660 if (add) add_lmap_error(err->type, err->name, err->data, err->clipno, err->frameno, err->atime, err->current);
2661 else mainw->files[err->clipno]->tcache_dubious_from = 0;
2662 if (err->name) lives_free(err->name);
2663 lives_free(err);
2664 list = list->next;
2665 }
2666 if (mainw->new_lmap_errors) {
2667 lives_list_free(mainw->new_lmap_errors);
2668 mainw->new_lmap_errors = NULL;
2669 }
2670 }
2671
2672
add_lmap_error(lives_lmap_error_t lerror,const char * name,livespointer user_data,int clipno,int frameno,double atime,boolean affects_current)2673 boolean add_lmap_error(lives_lmap_error_t lerror, const char *name, livespointer user_data, int clipno,
2674 int frameno, double atime, boolean affects_current) {
2675 // potentially add a layout map error to the layout textbuffer
2676 LiVESTextIter end_iter;
2677 LiVESList *lmap;
2678
2679 char *text, *name2;
2680 char **array;
2681
2682 double orig_fps;
2683 double max_time;
2684
2685 int resampled_frame;
2686
2687 lives_text_buffer_get_end_iter(LIVES_TEXT_BUFFER(mainw->layout_textbuffer), &end_iter);
2688
2689 if (affects_current && !user_data) {
2690 mainw->affected_layout_marks = lives_list_append(mainw->affected_layout_marks,
2691 (livespointer)lives_text_buffer_create_mark
2692 (LIVES_TEXT_BUFFER(mainw->layout_textbuffer), NULL, &end_iter, TRUE));
2693 }
2694
2695 switch (lerror) {
2696 case LMAP_INFO_SETNAME_CHANGED:
2697 if (!(*name)) name2 = (_("(blank)"));
2698 else name2 = lives_strdup(name);
2699 text = lives_strdup_printf
2700 (_("The set name has been changed from %s to %s. Affected layouts have been updated accordingly\n"),
2701 name2, (char *)user_data);
2702 lives_text_buffer_insert(LIVES_TEXT_BUFFER(mainw->layout_textbuffer), &end_iter, text, -1);
2703 lives_free(name2);
2704 lives_free(text);
2705 break;
2706 case LMAP_ERROR_MISSING_CLIP:
2707 if (prefs->warning_mask & WARN_MASK_LAYOUT_MISSING_CLIPS) return FALSE;
2708 text = lives_strdup_printf(_("The clip %s is missing from this set.\nIt is required by the following layouts:\n"), name);
2709 lives_text_buffer_insert(LIVES_TEXT_BUFFER(mainw->layout_textbuffer), &end_iter, text, -1);
2710 lives_free(text);
2711 case LMAP_ERROR_CLOSE_FILE:
2712 text = lives_strdup_printf(_("The clip %s has been closed.\nIt is required by the following layouts:\n"), name);
2713 lives_text_buffer_insert(LIVES_TEXT_BUFFER(mainw->layout_textbuffer), &end_iter, text, -1);
2714 lives_free(text);
2715 break;
2716 case LMAP_ERROR_SHIFT_FRAMES:
2717 text = lives_strdup_printf(_("Frames have been shifted in the clip %s.\nThe following layouts are affected:\n"), name);
2718 lives_text_buffer_insert(LIVES_TEXT_BUFFER(mainw->layout_textbuffer), &end_iter, text, -1);
2719 lives_free(text);
2720 break;
2721 case LMAP_ERROR_DELETE_FRAMES:
2722 text = lives_strdup_printf(_("Frames have been deleted from the clip %s.\nThe following layouts are affected:\n"), name);
2723 lives_text_buffer_insert(LIVES_TEXT_BUFFER(mainw->layout_textbuffer), &end_iter, text, -1);
2724 lives_free(text);
2725 break;
2726 case LMAP_ERROR_DELETE_AUDIO:
2727 text = lives_strdup_printf(_("Audio has been deleted from the clip %s.\nThe following layouts are affected:\n"), name);
2728 lives_text_buffer_insert(LIVES_TEXT_BUFFER(mainw->layout_textbuffer), &end_iter, text, -1);
2729 lives_free(text);
2730 break;
2731 case LMAP_ERROR_SHIFT_AUDIO:
2732 text = lives_strdup_printf(_("Audio has been shifted in clip %s.\nThe following layouts are affected:\n"), name);
2733 lives_text_buffer_insert(LIVES_TEXT_BUFFER(mainw->layout_textbuffer), &end_iter, text, -1);
2734 lives_free(text);
2735 break;
2736 case LMAP_ERROR_ALTER_AUDIO:
2737 text = lives_strdup_printf(_("Audio has been altered in the clip %s.\nThe following layouts are affected:\n"), name);
2738 lives_text_buffer_insert(LIVES_TEXT_BUFFER(mainw->layout_textbuffer), &end_iter, text, -1);
2739 lives_free(text);
2740 break;
2741 case LMAP_ERROR_ALTER_FRAMES:
2742 text = lives_strdup_printf(_("Frames have been altered in the clip %s.\nThe following layouts are affected:\n"), name);
2743 lives_text_buffer_insert(LIVES_TEXT_BUFFER(mainw->layout_textbuffer), &end_iter, text, -1);
2744 lives_free(text);
2745 break;
2746 }
2747
2748 if (affects_current && user_data) {
2749 mainw->affected_layout_marks = lives_list_append(mainw->affected_layout_marks,
2750 (livespointer)lives_text_buffer_create_mark
2751 (LIVES_TEXT_BUFFER(mainw->layout_textbuffer), NULL, &end_iter, TRUE));
2752 }
2753
2754 switch (lerror) {
2755 case LMAP_INFO_SETNAME_CHANGED:
2756 lmap = mainw->current_layouts_map;
2757 while (lmap) {
2758 array = lives_strsplit((char *)lmap->data, "|", -1);
2759 text = lives_strdup_printf("%s\n", array[0]);
2760 lives_text_buffer_insert(LIVES_TEXT_BUFFER(mainw->layout_textbuffer), &end_iter, text, -1);
2761 lives_free(text);
2762 // we could list all affected layouts, which could potentially be a lot !
2763 //mainw->affected_layouts_map=lives_list_append_unique(mainw->affected_layouts_map,array[0]);
2764 lives_strfreev(array);
2765 lmap = lmap->next;
2766 }
2767 break;
2768 case LMAP_ERROR_MISSING_CLIP:
2769 case LMAP_ERROR_CLOSE_FILE:
2770 if (affects_current) {
2771 text = lives_strdup_printf("%s\n", mainw->string_constants[LIVES_STRING_CONSTANT_CL]);
2772 lives_text_buffer_insert(LIVES_TEXT_BUFFER(mainw->layout_textbuffer), &end_iter, text, -1);
2773 lives_free(text);
2774 mainw->affected_layouts_map = lives_list_append_unique(mainw->affected_layouts_map,
2775 mainw->string_constants[LIVES_STRING_CONSTANT_CL]);
2776
2777 mainw->affected_layout_marks = lives_list_append(mainw->affected_layout_marks,
2778 (livespointer)lives_text_buffer_create_mark(LIVES_TEXT_BUFFER(mainw->layout_textbuffer),
2779 NULL, &end_iter, TRUE));
2780
2781 }
2782 lmap = (LiVESList *)user_data;
2783 while (lmap) {
2784 array = lives_strsplit((char *)lmap->data, "|", -1);
2785 text = lives_strdup_printf("%s\n", array[0]);
2786 lives_text_buffer_insert(LIVES_TEXT_BUFFER(mainw->layout_textbuffer), &end_iter, text, -1);
2787 lives_free(text);
2788 mainw->affected_layouts_map = lives_list_append_unique(mainw->affected_layouts_map, array[0]);
2789 lives_strfreev(array);
2790 lmap = lmap->next;
2791 }
2792 break;
2793 case LMAP_ERROR_SHIFT_FRAMES:
2794 case LMAP_ERROR_DELETE_FRAMES:
2795 case LMAP_ERROR_ALTER_FRAMES:
2796 if (affects_current) {
2797 text = lives_strdup_printf("%s\n", mainw->string_constants[LIVES_STRING_CONSTANT_CL]);
2798 lives_text_buffer_insert(LIVES_TEXT_BUFFER(mainw->layout_textbuffer), &end_iter, text, -1);
2799 lives_free(text);
2800 mainw->affected_layouts_map = lives_list_append_unique(mainw->affected_layouts_map,
2801 mainw->string_constants[LIVES_STRING_CONSTANT_CL]);
2802
2803 mainw->affected_layout_marks = lives_list_append(mainw->affected_layout_marks,
2804 (livespointer)lives_text_buffer_create_mark(LIVES_TEXT_BUFFER(mainw->layout_textbuffer),
2805 NULL, &end_iter, TRUE));
2806 }
2807 lmap = (LiVESList *)user_data;
2808 while (lmap) {
2809 array = lives_strsplit((char *)lmap->data, "|", -1);
2810 orig_fps = strtod(array[3], NULL);
2811 resampled_frame = count_resampled_frames(frameno, orig_fps, mainw->files[clipno]->fps);
2812 if (resampled_frame <= atoi(array[2])) {
2813 text = lives_strdup_printf("%s\n", array[0]);
2814 lives_text_buffer_insert(LIVES_TEXT_BUFFER(mainw->layout_textbuffer), &end_iter, text, -1);
2815 lives_free(text);
2816 mainw->affected_layouts_map = lives_list_append_unique(mainw->affected_layouts_map, array[0]);
2817 }
2818 lives_strfreev(array);
2819 lmap = lmap->next;
2820 }
2821 break;
2822 case LMAP_ERROR_SHIFT_AUDIO:
2823 case LMAP_ERROR_DELETE_AUDIO:
2824 case LMAP_ERROR_ALTER_AUDIO:
2825 if (affects_current) {
2826 text = lives_strdup_printf("%s\n", mainw->string_constants[LIVES_STRING_CONSTANT_CL]);
2827 lives_text_buffer_insert(LIVES_TEXT_BUFFER(mainw->layout_textbuffer), &end_iter, text, -1);
2828 lives_free(text);
2829 mainw->affected_layouts_map = lives_list_append_unique(mainw->affected_layouts_map,
2830 mainw->string_constants[LIVES_STRING_CONSTANT_CL]);
2831
2832 mainw->affected_layout_marks = lives_list_append(mainw->affected_layout_marks,
2833 (livespointer)lives_text_buffer_create_mark(LIVES_TEXT_BUFFER(mainw->layout_textbuffer),
2834 NULL, &end_iter, TRUE));
2835 }
2836 lmap = (LiVESList *)user_data;
2837 while (lmap) {
2838 array = lives_strsplit((char *)lmap->data, "|", -1);
2839 max_time = strtod(array[4], NULL);
2840 if (max_time > 0. && atime <= max_time) {
2841 text = lives_strdup_printf("%s\n", array[0]);
2842 lives_text_buffer_insert(LIVES_TEXT_BUFFER(mainw->layout_textbuffer), &end_iter, text, -1);
2843 lives_free(text);
2844 mainw->affected_layouts_map = lives_list_append_unique(mainw->affected_layouts_map, array[0]);
2845 }
2846 lives_strfreev(array);
2847 lmap = lmap->next;
2848 }
2849 break;
2850 }
2851
2852 lives_widget_set_sensitive(mainw->show_layout_errors, TRUE);
2853 if (mainw->multitrack) lives_widget_set_sensitive(mainw->multitrack->show_layout_errors, TRUE);
2854 return TRUE;
2855 }
2856
2857
clear_lmap_errors(void)2858 void clear_lmap_errors(void) {
2859 LiVESTextIter start_iter, end_iter;
2860 LiVESList *lmap;
2861
2862 lives_text_buffer_get_start_iter(LIVES_TEXT_BUFFER(mainw->layout_textbuffer), &start_iter);
2863 lives_text_buffer_get_end_iter(LIVES_TEXT_BUFFER(mainw->layout_textbuffer), &end_iter);
2864 lives_text_buffer_delete(LIVES_TEXT_BUFFER(mainw->layout_textbuffer), &start_iter, &end_iter);
2865
2866 lmap = mainw->affected_layouts_map;
2867
2868 while (lmap) {
2869 lives_free((livespointer)lmap->data);
2870 lmap = lmap->next;
2871 }
2872 lives_list_free(lmap);
2873
2874 mainw->affected_layouts_map = NULL;
2875 lives_widget_set_sensitive(mainw->show_layout_errors, FALSE);
2876 if (mainw->multitrack) lives_widget_set_sensitive(mainw->multitrack->show_layout_errors, FALSE);
2877
2878 if (mainw->affected_layout_marks) {
2879 remove_current_from_affected_layouts(mainw->multitrack);
2880 }
2881 }
2882
2883 /**
2884 @brief check for set lock file
2885 do this via the back-end (smogrify)
2886 this allows for the locking scheme to be more flexible
2887
2888 smogrify indicates a lock very simply by by writing > 0 bytes to stdout
2889 we read this via popen
2890
2891 type == 0 for load, type == 1 for save
2892
2893 */
check_for_lock_file(const char * set_name,int type)2894 boolean check_for_lock_file(const char *set_name, int type) {
2895 char *com;
2896
2897 if (type == 1 && !lives_strcmp(set_name, mainw->set_name)) return TRUE;
2898
2899 com = lives_strdup_printf("%s check_for_lock \"%s\" \"%s\" %d", prefs->backend_sync, set_name, capable->myname,
2900 capable->mainpid);
2901
2902 clear_mainw_msg();
2903
2904 threaded_dialog_spin(0.);
2905 lives_popen(com, TRUE, mainw->msg, MAINW_MSG_SIZE);
2906 threaded_dialog_spin(0.);
2907 lives_free(com);
2908
2909 if (THREADVAR(com_failed)) return FALSE;
2910
2911 if (*(mainw->msg)) {
2912 if (type == 0) {
2913 if (mainw->recovering_files) return do_set_locked_warning(set_name);
2914 threaded_dialog_spin(0.);
2915 widget_opts.non_modal = TRUE;
2916 do_error_dialogf(_("Set %s\ncannot be opened, as it is in use\nby another copy of LiVES.\n"), set_name);
2917 widget_opts.non_modal = FALSE;
2918 threaded_dialog_spin(0.);
2919 } else if (type == 1) {
2920 if (!mainw->osc_auto) do_error_dialogf(_("\nThe set %s is currently in use by another copy of LiVES.\n"
2921 "Please choose another set name.\n"), set_name);
2922 }
2923 return FALSE;
2924 }
2925 return TRUE;
2926 }
2927
2928
do_std_checks(const char * type_name,const char * type,size_t maxlen,const char * nreject)2929 boolean do_std_checks(const char *type_name, const char *type, size_t maxlen, const char *nreject) {
2930 char *xtype = lives_strdup(type), *msg;
2931 const char *reject = " /\\*\"";
2932 size_t slen = strlen(type_name);
2933
2934 if (nreject) reject = nreject;
2935
2936 if (slen == 0) {
2937 msg = lives_strdup_printf(_("\n%s names may not be blank.\n"), xtype);
2938 if (!mainw->osc_auto) do_error_dialog(msg);
2939 lives_free(msg);
2940 lives_free(xtype);
2941 return FALSE;
2942 }
2943
2944 if (slen > MAX_SET_NAME_LEN) {
2945 msg = lives_strdup_printf(_("\n%s names may not be longer than %d characters.\n"), xtype, (int)maxlen);
2946 if (!mainw->osc_auto) do_error_dialog(msg);
2947 lives_free(msg);
2948 lives_free(xtype);
2949 return FALSE;
2950 }
2951
2952 if (strcspn(type_name, reject) != slen) {
2953 msg = lives_strdup_printf(_("\n%s names may not contain spaces or the characters%s.\n"), xtype, reject);
2954 if (!mainw->osc_auto) do_error_dialog(msg);
2955 lives_free(msg);
2956 lives_free(xtype);
2957 return FALSE;
2958 }
2959
2960 for (int i = 0; i < slen; i++) {
2961 if (type_name[i] == '.' && (i == 0 || type_name[i - 1] == '.')) {
2962 msg = lives_strdup_printf(_("\n%s names may not start with a '.' or contain '..'\n"), xtype);
2963 if (!mainw->osc_auto) do_error_dialog(msg);
2964 lives_free(msg);
2965 lives_free(xtype);
2966 return FALSE;
2967 }
2968 }
2969
2970 lives_free(xtype);
2971 return TRUE;
2972 }
2973
2974
is_legal_set_name(const char * set_name,boolean allow_dupes,boolean leeway)2975 boolean is_legal_set_name(const char *set_name, boolean allow_dupes, boolean leeway) {
2976 // check (clip) set names for validity
2977 // - may not be of zero length
2978 // - may not contain spaces or characters / \ * "
2979 // - must NEVER be name of a set in use by another copy of LiVES (i.e. with a lock file)
2980
2981 // - as of 1.6.0:
2982 // - may not start with a .
2983 // - may not contain ..
2984
2985 // - as of 3.2.0
2986 // - must start with a letter [a - z] or [A - Z]
2987
2988 // should be in FILESYSTEM encoding
2989
2990 // may not be longer than MAX_SET_NAME_LEN chars
2991
2992 // iff allow_dupes is FALSE then we disallow the name of any existing set (has a subdirectory in the working directory)
2993
2994 if (!do_std_checks(set_name, _("Set"), MAX_SET_NAME_LEN, NULL)) return FALSE;
2995
2996 // check if this is a set in use by another copy of LiVES
2997 if (mainw && mainw->is_ready && !check_for_lock_file(set_name, 1)) return FALSE;
2998
2999 if ((set_name[0] < 'a' || set_name[0] > 'z') && (set_name[0] < 'A' || set_name[0] > 'Z')) {
3000 if (leeway) {
3001 if (mainw->is_ready)
3002 do_warning_dialog(_("As of LiVES 3.2.0 all set names must begin with alphabetical character\n"
3003 "(A - Z or a - z)\nYou will need to give a new name for the set when saving it.\n"));
3004 } else {
3005 do_error_dialog(_("All set names must begin with an alphabetical character\n(A - Z or a - z)\n"));
3006 return FALSE;
3007 }
3008 }
3009
3010 if (!allow_dupes) {
3011 // check for duplicate set names
3012 char *set_dir = lives_build_filename(prefs->workdir, set_name, NULL);
3013 if (lives_file_test(set_dir, LIVES_FILE_TEST_IS_DIR)) {
3014 lives_free(set_dir);
3015 return do_yesno_dialogf(_("\nThe set %s already exists.\n"
3016 "Do you want to add the current clips to the existing set ?.\n"), set_name);
3017 }
3018 lives_free(set_dir);
3019 }
3020
3021 return TRUE;
3022 }
3023
3024
get_image_ext_for_type(lives_img_type_t imgtype)3025 LIVES_GLOBAL_INLINE const char *get_image_ext_for_type(lives_img_type_t imgtype) {
3026 switch (imgtype) {
3027 case IMG_TYPE_JPEG: return LIVES_FILE_EXT_JPG; // "jpg"
3028 case IMG_TYPE_PNG: return LIVES_FILE_EXT_PNG; // "png"
3029 default: return "";
3030 }
3031 }
3032
3033
lives_image_ext_to_img_type(const char * img_ext)3034 LIVES_GLOBAL_INLINE lives_img_type_t lives_image_ext_to_img_type(const char *img_ext) {
3035 return lives_image_type_to_img_type(image_ext_to_lives_image_type(img_ext));
3036 }
3037
3038
image_ext_to_lives_image_type(const char * img_ext)3039 LIVES_GLOBAL_INLINE const char *image_ext_to_lives_image_type(const char *img_ext) {
3040 if (!strcmp(img_ext, LIVES_FILE_EXT_PNG)) return LIVES_IMAGE_TYPE_PNG;
3041 if (!strcmp(img_ext, LIVES_FILE_EXT_JPG)) return LIVES_IMAGE_TYPE_JPEG;
3042 return LIVES_IMAGE_TYPE_UNKNOWN;
3043 }
3044
3045
lives_image_type_to_img_type(const char * lives_img_type)3046 LIVES_GLOBAL_INLINE lives_img_type_t lives_image_type_to_img_type(const char *lives_img_type) {
3047 if (!strcmp(lives_img_type, LIVES_IMAGE_TYPE_PNG)) return IMG_TYPE_PNG;
3048 if (!strcmp(lives_img_type, LIVES_IMAGE_TYPE_JPEG)) return IMG_TYPE_JPEG;
3049 return IMG_TYPE_UNKNOWN;
3050 }
3051
3052
make_image_file_name(lives_clip_t * sfile,frames_t frame,const char * img_ext)3053 LIVES_GLOBAL_INLINE char *make_image_file_name(lives_clip_t *sfile, frames_t frame,
3054 const char *img_ext) {
3055 char *fname, *ret;
3056 if (!*img_ext) {
3057 sfile->img_type = resolve_img_type(sfile);
3058 img_ext = get_image_ext_for_type(sfile->img_type);
3059 }
3060 fname = lives_strdup_printf("%08d.%s", frame, img_ext);
3061 ret = lives_build_filename(prefs->workdir, sfile->handle, fname, NULL);
3062 lives_free(fname);
3063 return ret;
3064 }
3065
3066
3067 /** @brief check number of frames is correct
3068 for files of type CLIP_TYPE_DISK
3069 - check the image files (e.g. jpeg or png)
3070
3071 use a "goldilocks" algorithm (just the right frames, not too few and not too many)
3072
3073 ignores gaps */
check_frame_count(int idx,boolean last_checked)3074 boolean check_frame_count(int idx, boolean last_checked) {
3075 /// make sure nth frame is there...
3076 char *frame;
3077 if (mainw->files[idx]->frames > 0) {
3078 frame = make_image_file_name(mainw->files[idx], mainw->files[idx]->frames,
3079 get_image_ext_for_type(mainw->files[idx]->img_type));
3080 if (!lives_file_test(frame, LIVES_FILE_TEST_EXISTS)) {
3081 // not enough frames
3082 lives_free(frame);
3083 return FALSE;
3084 }
3085 lives_free(frame);
3086 }
3087
3088 /// ...make sure n + 1 th frame is not
3089 frame = make_image_file_name(mainw->files[idx], mainw->files[idx]->frames + 1,
3090 get_image_ext_for_type(mainw->files[idx]->img_type));
3091
3092 if (lives_file_test(frame, LIVES_FILE_TEST_EXISTS)) {
3093 /// too many frames
3094 lives_free(frame);
3095 return FALSE;
3096 }
3097 lives_free(frame);
3098
3099 /// just right
3100 return TRUE;
3101 }
3102
3103
3104 /** @brief sets mainw->files[idx]->frames with current framecount
3105
3106 calls smogrify which physically finds the last frame using a (fast) O(log n) binary search method
3107 for CLIP_TYPE_DISK only
3108 (CLIP_TYPE_FILE should use the decoder plugin frame count) */
get_frame_count(int idx,int start)3109 int get_frame_count(int idx, int start) {
3110 ssize_t bytes;
3111 char *com = lives_strdup_printf("%s count_frames \"%s\" %s %d", prefs->backend_sync, mainw->files[idx]->handle,
3112 get_image_ext_for_type(mainw->files[idx]->img_type), start);
3113
3114 bytes = lives_popen(com, FALSE, mainw->msg, MAINW_MSG_SIZE);
3115 lives_free(com);
3116
3117 if (THREADVAR(com_failed)) return 0;
3118
3119 if (bytes > 0) return atoi(mainw->msg);
3120 return 0;
3121 }
3122
3123
get_frames_sizes(int fileno,int frame,int * hsize,int * vsize)3124 boolean get_frames_sizes(int fileno, int frame, int *hsize, int *vsize) {
3125 // get the actual physical frame size
3126 lives_clip_t *sfile = mainw->files[fileno];
3127 weed_layer_t *layer = weed_layer_new(WEED_LAYER_TYPE_VIDEO);
3128 char *fname = make_image_file_name(sfile, frame, get_image_ext_for_type(sfile->img_type));
3129 weed_set_int_value(layer, WEED_LEAF_HOST_FLAGS, LIVES_LAYER_GET_SIZE_ONLY);
3130 if (!weed_layer_create_from_file_progressive(layer, fname, 0, 0, WEED_PALETTE_END,
3131 get_image_ext_for_type(sfile->img_type))) {
3132 lives_free(fname);
3133 return FALSE;
3134 }
3135 lives_free(fname);
3136 *hsize = weed_layer_get_width(layer);
3137 *vsize = weed_layer_get_height(layer);
3138 weed_layer_free(layer);
3139 return FALSE;
3140 }
3141
3142
lives_string_ends_with(const char * string,const char * fmt,...)3143 boolean lives_string_ends_with(const char *string, const char *fmt, ...) {
3144 char *textx;
3145 va_list xargs;
3146 size_t slen, cklen;
3147 boolean ret = FALSE;
3148
3149 if (!string) return FALSE;
3150
3151 va_start(xargs, fmt);
3152 textx = lives_strdup_vprintf(fmt, xargs);
3153 va_end(xargs);
3154 if (!textx) return FALSE;
3155 slen = lives_strlen(string);
3156 cklen = lives_strlen(textx);
3157 if (cklen == 0 || cklen > slen) {
3158 lives_free(textx);
3159 return FALSE;
3160 }
3161 if (!lives_strncmp(string + slen - cklen, textx, cklen)) ret = TRUE;
3162 lives_free(textx);
3163 return ret;
3164 }
3165
3166
get_dirname(char * filename)3167 void get_dirname(char *filename) {
3168 char *tmp;
3169 // get directory name from a file
3170 // filename should point to char[PATH_MAX]
3171 // WARNING: will change contents of filename
3172
3173 lives_snprintf(filename, PATH_MAX, "%s%s", (tmp = lives_path_get_dirname(filename)), LIVES_DIR_SEP);
3174 if (!strcmp(tmp, ".")) {
3175 char *tmp1 = lives_get_current_dir(), *tmp2 = lives_build_filename(tmp1, filename + 2, NULL);
3176 lives_free(tmp1);
3177 lives_snprintf(filename, PATH_MAX, "%s", tmp2);
3178 lives_free(tmp2);
3179 }
3180
3181 lives_free(tmp);
3182 }
3183
3184
get_dir(const char * filename)3185 char *get_dir(const char *filename) {
3186 // get directory as string, should free after use
3187 char tmp[PATH_MAX];
3188 lives_snprintf(tmp, PATH_MAX, "%s", filename);
3189 get_dirname(tmp);
3190 return lives_strdup(tmp);
3191 }
3192
3193
get_basename(char * filename)3194 LIVES_GLOBAL_INLINE void get_basename(char *filename) {
3195 // get basename from a file
3196 // (filename without directory)
3197 // filename should point to char[PATH_MAX]
3198 // WARNING: will change contents of filename
3199 char *tmp = lives_path_get_basename(filename);
3200 lives_snprintf(filename, PATH_MAX, "%s", tmp);
3201 lives_free(tmp);
3202 }
3203
3204
get_filename(char * filename,boolean strip_dir)3205 LIVES_GLOBAL_INLINE void get_filename(char *filename, boolean strip_dir) {
3206 // get filename (part without extension) of a file
3207 //filename should point to char[PATH_MAX]
3208 // WARNING: will change contents of filename
3209 if (strip_dir) get_basename(filename);
3210 lives_strstop(filename, '.');
3211 }
3212
3213 /// return filename (no dir, no .ext)
lives_get_filename(char * uri)3214 LIVES_GLOBAL_INLINE char *lives_get_filename(char *uri) {return lives_strstop(lives_path_get_basename(uri), '.');}
3215
3216
get_extension(const char * filename)3217 char *get_extension(const char *filename) {
3218 // return file extension without the "."
3219 char *tmp = lives_path_get_basename(filename);
3220 char *ptr = strrchr(tmp, '.');
3221 if (!ptr) {
3222 lives_free(tmp);
3223 return lives_strdup("");
3224 } else {
3225 char *ret = lives_strdup(ptr + 1);
3226 lives_free(tmp);
3227 return ret;
3228 }
3229 }
3230
3231
ensure_extension(const char * fname,const char * ext)3232 char *ensure_extension(const char *fname, const char *ext) {
3233 // make sure filename fname has file extension ext
3234 // if ext does not begin with a "." we prepend one to the start of ext
3235 // we then check if fname ends with ext. If not we append ext to fname.
3236 // we return a copy of fname, possibly modified. The string returned should be freed after use.
3237 // NOTE: the original ext is not changed.
3238
3239 size_t se = strlen(ext), sf;
3240 char *eptr = (char *)ext;
3241
3242 if (!fname) return NULL;
3243
3244 if (se == 0) return lives_strdup(fname);
3245
3246 if (eptr[0] == '.') {
3247 eptr++;
3248 se--;
3249 }
3250
3251 sf = lives_strlen(fname);
3252 if (sf < se + 1 || strcmp(fname + sf - se, eptr) || fname[sf - se - 1] != '.') {
3253 return lives_strconcat(fname, ".", eptr, NULL);
3254 }
3255
3256 return lives_strdup(fname);
3257 }
3258
3259
3260 // input length includes terminating NUL
3261
lives_ellipsize(char * txt,size_t maxlen,LiVESEllipsizeMode mode)3262 LIVES_GLOBAL_INLINE char *lives_ellipsize(char *txt, size_t maxlen, LiVESEllipsizeMode mode) {
3263 /// eg. txt = "abcdefgh", maxlen = 6, LIVES_ELLIPSIZE_END -> txt == "...gh" + NUL
3264 /// txt = "abcdefgh", maxlen = 6, LIVES_ELLIPSIZE_START -> txt == "ab..." + NUL
3265 /// txt = "abcdefgh", maxlen = 6, LIVES_ELLIPSIZE_MIDDLE -> txt == "a...h" + NUL
3266 // LIVES_ELLIPSIZE_NONE - do not ellipsise
3267 // return value should be freed, unless txt is returned
3268 const char ellipsis[4] = "...\0";
3269 size_t slen = lives_strlen(txt);
3270 off_t stlen, enlen;
3271 char *retval = txt;
3272 if (!maxlen) return NULL;
3273 if (slen >= maxlen) {
3274 if (maxlen == 1) return lives_strdup("");
3275 retval = (char *)lives_malloc(maxlen);
3276 if (maxlen == 2) return lives_strdup(".");
3277 if (maxlen == 3) return lives_strdup("..");
3278 if (maxlen == 4) return lives_strdup("...");
3279 maxlen -= 4;
3280 switch (mode) {
3281 case LIVES_ELLIPSIZE_END:
3282 lives_memcpy(retval, ellipsis, 3);
3283 lives_memcpy(retval + 3, txt + slen - maxlen, maxlen + 1);
3284 break;
3285 case LIVES_ELLIPSIZE_START:
3286 lives_memcpy(retval, txt, maxlen);
3287 lives_memcpy(retval + maxlen, ellipsis, 4);
3288 break;
3289 case LIVES_ELLIPSIZE_MIDDLE:
3290 enlen = maxlen >> 1;
3291 stlen = maxlen - enlen;
3292 lives_memcpy(retval, txt, stlen);
3293 lives_memcpy(retval + stlen, ellipsis, 3);
3294 lives_memcpy(retval + stlen + 3, txt + slen - enlen, enlen + 1);
3295 break;
3296 default: break;
3297 }
3298 }
3299 return retval;
3300 }
3301
3302
lives_pad(char * txt,size_t minlen,int align)3303 LIVES_GLOBAL_INLINE char *lives_pad(char *txt, size_t minlen, int align) {
3304 // pad with spaces at start and end respectively
3305 // ealign gives ellipsis pos, palign can be LIVES_ALIGN_START, LIVES_ALIGN_END
3306 // LIVES_ALIGN_START -> pad end, LIVES_ALIGN_END -> pad start
3307 // LIVES_ALIGN_CENTER -> pad on both sides
3308 // LIVES_ALIGN_FILL - do not pad
3309 size_t slen = lives_strlen(txt);
3310 char *retval = txt;
3311 off_t ipos = 0;
3312 if (align == LIVES_ALIGN_FILL) return txt;
3313 if (slen < minlen - 1) {
3314 retval = (char *)lives_malloc(minlen);
3315 lives_memset(retval, ' ', --minlen);
3316 retval[minlen] = 0;
3317 switch (align) {
3318 case LIVES_ALIGN_END:
3319 ipos = minlen - slen;
3320 break;
3321 case LIVES_ALIGN_CENTER:
3322 ipos = minlen - slen;
3323 break;
3324 default:
3325 break;
3326 }
3327 lives_memcpy(retval + ipos, txt, slen);
3328 }
3329 return retval;
3330 }
3331
3332
lives_pad_ellipsize(char * txt,size_t fixlen,int palign,LiVESEllipsizeMode emode)3333 LIVES_GLOBAL_INLINE char *lives_pad_ellipsize(char *txt, size_t fixlen, int palign, LiVESEllipsizeMode emode) {
3334 // if len of txt < fixlen it will be padded, if longer, ellipsised
3335 // ealign gives ellipsis pos, palign can be LIVES_ALIGN_START, LIVES_ALIGN_END
3336 // pad with spaces at start and end respectively
3337 // LIVES_ALIGN_CENTER -> pad on both sides
3338 // LIVES_ALIGN_FILL - do not pad
3339 size_t slen = lives_strlen(txt);
3340 if (slen == fixlen - 1) return txt;
3341 if (slen >= fixlen) return lives_ellipsize(txt, fixlen, emode);
3342 return lives_pad(txt, fixlen, palign);
3343 }
3344
3345
ensure_isdir(char * fname)3346 boolean ensure_isdir(char *fname) {
3347 // ensure dirname ends in a single dir separator
3348 // fname should be char[PATH_MAX]
3349
3350 // returns TRUE if fname was altered
3351
3352 size_t tlen = lives_strlen(fname), slen, tlen2;
3353 size_t dslen = strlen(LIVES_DIR_SEP);
3354 ssize_t offs;
3355 boolean ret = FALSE;
3356 char *tmp = lives_strdup(fname), *tmp2;
3357
3358 while (1) {
3359 // recursively remove double DIR_SEP
3360 tmp2 = subst(tmp, LIVES_DIR_SEP LIVES_DIR_SEP, LIVES_DIR_SEP);
3361 if ((tlen2 = lives_strlen(tmp2)) < tlen) {
3362 ret = TRUE;
3363 lives_free(tmp);
3364 tmp = tmp2;
3365 tlen = tlen2;
3366 } else {
3367 lives_free(tmp2);
3368 break;
3369 }
3370 }
3371
3372 if (ret) lives_snprintf(fname, PATH_MAX, "%s", tmp);
3373 lives_free(tmp);
3374
3375 slen = tlen - 1;
3376 offs = slen;
3377
3378 // we should now only have one or zero DIR_SEP at the end, but just in case we remove all but the last one
3379 while (offs >= 0 && !strncmp(fname + offs, LIVES_DIR_SEP, dslen)) offs -= dslen;
3380 if (offs == slen - dslen) return ret; // format is OK as-is
3381
3382 // strip off all terminating DIR_SEP and then append one
3383 if (++offs < 0) offs = 0;
3384 if (offs < slen) fname[offs] = 0;
3385 fname = strncat(fname, LIVES_DIR_SEP, PATH_MAX - offs - 1);
3386 return TRUE;
3387 }
3388
3389
dirs_equal(const char * dira,const char * dirb)3390 boolean dirs_equal(const char *dira, const char *dirb) {
3391 // filenames in locale encoding
3392 char *tmp;
3393 char dir1[PATH_MAX];
3394 char dir2[PATH_MAX];
3395 lives_snprintf(dir1, PATH_MAX, "%s", (tmp = F2U8(dira)));
3396 lives_free(tmp);
3397 lives_snprintf(dir2, PATH_MAX, "%s", (tmp = F2U8(dirb)));
3398 lives_free(tmp);
3399 ensure_isdir(dir1);
3400 ensure_isdir(dir2);
3401 // TODO: for some (Linux) fstypes we should use strcasecmp
3402 // can get this using "df -T"
3403 return (!lives_strcmp(dir1, dir2));
3404 }
3405
3406
get_location(const char * exe,char * val,int maxlen)3407 void get_location(const char *exe, char *val, int maxlen) {
3408 // find location of "exe" in path
3409 // sets it in val which is a char array of maxlen bytes
3410
3411 char *loc;
3412 if ((loc = lives_find_program_in_path(exe)) != NULL) {
3413 lives_snprintf(val, maxlen, "%s", loc);
3414 lives_free(loc);
3415 } else {
3416 lives_memset(val, 0, 1);
3417 }
3418 }
3419
3420
has_executable(const char * exe)3421 LIVES_LOCAL_INLINE lives_presence_t has_executable(const char *exe) {
3422 char *loc;
3423 if ((loc = lives_find_program_in_path(exe)) != NULL) {
3424 lives_free(loc);
3425 return PRESENT;
3426 }
3427 // for now we don't return MISSING (requires code update to differentiate MISSING / UNCHECKED / PRESENT)
3428 return FALSE;
3429 }
3430
3431
3432 // check if executable is present, missing or unchecked
3433 // if unchecked, check for it, and if not found ask the user politely to install it
check_for_executable(lives_checkstatus_t * cap,const char * exec)3434 boolean check_for_executable(lives_checkstatus_t *cap, const char *exec) {
3435 #ifdef NEW_CHECKSTATUS
3436 if (!cap || (*cap)->present == UNCHECKED) {
3437 if (!cap || ((*cap)->flags & INSTALL_CANLOCAL)) {
3438 /// TODO (next version)
3439 #else
3440 if (!cap || *cap == UNCHECKED) {
3441 if (!lives_strcmp(exec, EXEC_YOUTUBE_DL)) {
3442 #endif
3443 char *localv = lives_build_filename(capable->home_dir, LOCAL_HOME_DIR, "bin", exec, NULL);
3444 if (lives_file_test(localv, LIVES_FILE_TEST_IS_EXECUTABLE)) {
3445 lives_free(localv);
3446 if (cap) *cap = LOCAL;
3447 return TRUE;
3448 }
3449 lives_free(localv);
3450 }
3451 if (has_executable(exec)) {
3452 if (cap) *cap = PRESENT;
3453 return TRUE;
3454 } else {
3455 if (!lives_strcmp(exec, EXEC_XDOTOOL) || !lives_strcmp(exec, EXEC_WMCTRL)) {
3456 if (cap) *cap = MISSING;
3457 }
3458 //if (importance == necessary)
3459 //do_please_install(exec);
3460 #ifdef HAS_MISSING_PRESENCE
3461 if (cap) *cap = MISSING;
3462 #endif
3463 //do_program_not_found_error(exec);
3464 return FALSE;
3465 }
3466 }
3467 #if 0
3468 }
3469 }
3470 #endif
3471 return (*cap == PRESENT || *cap == LOCAL);
3472 }
3473
3474
get_version_hash(const char * exe,const char * sep,int piece)3475 uint64_t get_version_hash(const char *exe, const char *sep, int piece) {
3476 /// get version hash output for an executable from the backend
3477 uint64_t val;
3478 char buff[128];
3479 char **array;
3480 int ntok;
3481
3482 lives_popen(exe, TRUE, buff, 128);
3483 if (THREADVAR(com_failed)) {
3484 THREADVAR(com_failed) = FALSE;
3485 return -2;
3486 }
3487 ntok = get_token_count(buff, sep[0]);
3488 if (ntok < piece) return -1;
3489 array = lives_strsplit(buff, sep, ntok);
3490 val = make_version_hash(array[piece]);
3491 lives_strfreev(array);
3492 return val;
3493 }
3494
3495
3496 #define VER_MAJOR_MULT 1000000
3497 #define VER_MINOR_MULT 1000
3498 #define VER_MICRO_MULT 1
3499
make_version_hash(const char * ver)3500 uint64_t make_version_hash(const char *ver) {
3501 /// convert a version to uint64_t hash, for comparing
3502 char **array;
3503 uint64_t hash;
3504 int ntok;
3505
3506 if (!ver) return 0;
3507
3508 ntok = get_token_count((char *)ver, '.');
3509 array = lives_strsplit(ver, ".", ntok);
3510
3511 hash = atoi(array[0]) * VER_MAJOR_MULT;
3512 if (ntok > 1) {
3513 hash += atoi(array[1]) * VER_MINOR_MULT;
3514 if (ntok > 2) hash += atoi(array[2]) * VER_MICRO_MULT;
3515 }
3516
3517 lives_strfreev(array);
3518 return hash;
3519 }
3520
3521
unhash_version(uint64_t version)3522 char *unhash_version(uint64_t version) {
3523 if (!version) return lives_strdup(_("'Unknown'"));
3524 else {
3525 uint64_t maj = version / VER_MAJOR_MULT, min;
3526 version -= maj * VER_MAJOR_MULT;
3527 min = version / VER_MINOR_MULT;
3528 version -= min * VER_MINOR_MULT;
3529 return lives_strdup_printf("%lu.%lu.%lu", maj, min, version);
3530 }
3531 }
3532
3533
repl_workdir(const char * entry,boolean fwd)3534 char *repl_workdir(const char *entry, boolean fwd) {
3535 // replace prefs->workdir with string workdir or vice-versa. This allows us to relocate workdir if necessary.
3536 // used for layout.map file
3537 // return value should be freed
3538
3539 // fwd TRUE replaces "/tmp/foo" with "workdir"
3540 // fwd FALSE replaces "workdir" with "/tmp/foo"
3541 size_t wdl;
3542 char *string = lives_strdup(entry);
3543
3544 if (fwd) {
3545 if (!lives_strncmp(entry, prefs->workdir, (wdl = lives_strlen(prefs->workdir)))) {
3546 lives_free(string);
3547 string = lives_strdup_printf("%s%s", WORKDIR_LITERAL, entry + wdl);
3548 }
3549 } else {
3550 if (!lives_strncmp(entry, WORKDIR_LITERAL, WORKDIR_LITERAL_LEN)) {
3551 lives_free(string);
3552 string = lives_build_filename(prefs->workdir, entry + WORKDIR_LITERAL_LEN, NULL);
3553 }
3554 }
3555 return string;
3556 }
3557
3558
remove_layout_files(LiVESList * map)3559 void remove_layout_files(LiVESList * map) {
3560 // removes a LiVESList of layouts from the set layout map
3561
3562 // removes from: - global layouts map
3563 // - disk
3564 // - clip layout maps
3565
3566 // called after, for example: a clip is removed or altered and the user opts to remove all associated layouts
3567
3568 LiVESList *lmap, *lmap_next, *cmap, *cmap_next, *map_next;
3569 size_t maplen;
3570 char **array;
3571 char *fname, *fdir;
3572 boolean is_current;
3573
3574 while (map) {
3575 map_next = map->next;
3576 if (map->data) {
3577 if (!lives_utf8_strcasecmp((char *)map->data, mainw->string_constants[LIVES_STRING_CONSTANT_CL])) {
3578 is_current = TRUE;
3579 fname = lives_strdup(mainw->string_constants[LIVES_STRING_CONSTANT_CL]);
3580 } else {
3581 is_current = FALSE;
3582 maplen = lives_strlen((char *)map->data);
3583
3584 // remove from mainw->current_layouts_map
3585 cmap = mainw->current_layouts_map;
3586 while (cmap) {
3587 cmap_next = cmap->next;
3588 if (!lives_utf8_strcasecmp((char *)cmap->data, (char *)map->data)) {
3589 lives_free((livespointer)cmap->data);
3590 mainw->current_layouts_map = lives_list_delete_link(mainw->current_layouts_map, cmap);
3591 break;
3592 }
3593 cmap = cmap_next;
3594 }
3595
3596 array = lives_strsplit((char *)map->data, "|", -1);
3597 fname = repl_workdir(array[0], FALSE);
3598 lives_strfreev(array);
3599 }
3600
3601 // fname should now hold the layout name on disk
3602 d_print(_("Removing layout %s\n"), fname);
3603
3604 if (!is_current) {
3605 lives_rm(fname);
3606
3607 // if no more layouts in parent dir, we can delete dir
3608
3609 // ensure that parent dir is below our own working dir
3610 if (!lives_strncmp(fname, prefs->workdir, lives_strlen(prefs->workdir))) {
3611 // is in workdir, safe to remove parents
3612
3613 char *protect_file = lives_build_filename(prefs->workdir, "noremove", NULL);
3614
3615 // touch a file in tpmdir, so we cannot remove workdir itself
3616 lives_touch(protect_file);
3617
3618 if (!THREADVAR(com_failed)) {
3619 // ok, the "touch" worked
3620 // now we call rmdir -p : remove directory + any empty parents
3621 fdir = lives_path_get_dirname(fname);
3622 lives_rmdir_with_parents(fdir);
3623 lives_free(fdir);
3624 }
3625
3626 // remove the file we touched to clean up
3627 lives_rm(protect_file);
3628 lives_free(protect_file);
3629 }
3630
3631 // remove from mainw->files[]->layout_map
3632 for (int i = 1; i <= MAX_FILES; i++) {
3633 if (mainw->files[i]) {
3634 if (mainw->files[i]->layout_map) {
3635 lmap = mainw->files[i]->layout_map;
3636 while (lmap) {
3637 lmap_next = lmap->next;
3638 if (!lives_strncmp((char *)lmap->data, (char *)map->data, maplen)) {
3639 lives_free((livespointer)lmap->data);
3640 mainw->files[i]->layout_map = lives_list_delete_link(mainw->files[i]->layout_map, lmap);
3641 }
3642 lmap = lmap_next;
3643 // *INDENT-OFF*
3644 }}}}
3645 // *INDENT-ON*
3646
3647 } else {
3648 // asked to remove the currently loaded layout
3649
3650 if (mainw->stored_event_list || mainw->sl_undo_mem) {
3651 // we are in CE mode, so event_list is in storage
3652 stored_event_list_free_all(TRUE);
3653 }
3654 // in mt mode we need to do more
3655 else remove_current_from_affected_layouts(mainw->multitrack);
3656
3657 // and we dont want to try reloading this next time
3658 prefs->ar_layout = FALSE;
3659 set_string_pref(PREF_AR_LAYOUT, "");
3660 lives_memset(prefs->ar_layout_name, 0, 1);
3661 }
3662 lives_free(fname);
3663 }
3664 map = map_next;
3665 }
3666
3667 // save updated layout.map
3668 save_layout_map(NULL, NULL, NULL, NULL);
3669 }
3670
3671
get_play_times(void)3672 LIVES_GLOBAL_INLINE void get_play_times(void) {
3673 update_timer_bars(0, 0, 0, 0, 0);
3674 }
3675
3676
update_play_times(void)3677 void update_play_times(void) {
3678 // force a redraw, reread audio
3679 if (!CURRENT_CLIP_IS_VALID) return;
3680 if (cfile->audio_waveform) {
3681 int i;
3682 for (i = 0; i < cfile->achans; lives_freep((void **)&cfile->audio_waveform[i++]));
3683 lives_freep((void **)&cfile->audio_waveform);
3684 lives_freep((void **)&cfile->aw_sizes);
3685 }
3686 get_play_times();
3687 }
3688
3689
get_total_time(lives_clip_t * file)3690 void get_total_time(lives_clip_t *file) {
3691 // get times (video, left and right audio)
3692
3693 file->laudio_time = file->raudio_time = file->video_time = 0.;
3694
3695 if (file->opening) {
3696 int frames;
3697 if (file->frames != 123456789) frames = file->frames;
3698 else frames = file->opening_frames;
3699 if (frames * file->fps > 0) {
3700 file->video_time = file->frames / file->fps;
3701 }
3702 return;
3703 }
3704
3705 if (file->fps > 0.) {
3706 file->video_time = file->frames / file->fps;
3707 }
3708
3709 if (file->asampsize >= 8 && file->arate > 0 && file->achans > 0) {
3710 file->laudio_time = (double)(file->afilesize / (file->asampsize >> 3) / file->achans) / (double)file->arate;
3711 if (file->achans > 1) {
3712 file->raudio_time = file->laudio_time;
3713 }
3714 }
3715
3716 if (file->laudio_time + file->raudio_time == 0. && !file->opening) {
3717 file->achans = file->afilesize = file->asampsize = file->arate = file->arps = 0;
3718 }
3719 }
3720
3721
find_when_to_stop(void)3722 void find_when_to_stop(void) {
3723 // work out when to stop playing
3724 //
3725 // ---------------
3726 // no loop loop to fit loop cont
3727 // ------- ----------- ---------
3728 // a>v stop on video end stop on audio end no stop
3729 // v>a stop on video end stop on video end no stop
3730 // generator start - not playing : stop on vid_end, unless pure audio;
3731 if (mainw->alives_pgid > 0) mainw->whentostop = NEVER_STOP;
3732 else if (mainw->aud_rec_fd != -1 &&
3733 mainw->ascrap_file == -1) mainw->whentostop = STOP_ON_VID_END;
3734 else if (mainw->multitrack && CURRENT_CLIP_HAS_VIDEO) mainw->whentostop = STOP_ON_VID_END;
3735 else if (!CURRENT_CLIP_IS_NORMAL) {
3736 if (mainw->loop_cont) mainw->whentostop = NEVER_STOP;
3737 else mainw->whentostop = STOP_ON_VID_END;
3738 } else if (cfile->opening_only_audio) mainw->whentostop = STOP_ON_AUD_END;
3739 else if (cfile->opening_audio) mainw->whentostop = STOP_ON_VID_END;
3740 else if (!mainw->preview && (mainw->loop_cont)) mainw->whentostop = NEVER_STOP;
3741 else if (!CURRENT_CLIP_HAS_VIDEO || (mainw->loop && cfile->achans > 0 && !mainw->is_rendering
3742 && (mainw->audio_end / cfile->fps)
3743 < MAX(cfile->laudio_time, cfile->raudio_time) &&
3744 calc_time_from_frame(mainw->current_file, mainw->play_start) < cfile->laudio_time))
3745 mainw->whentostop = STOP_ON_AUD_END;
3746 else mainw->whentostop = STOP_ON_VID_END; // tada...
3747 }
3748
3749
minimise_aspect_delta(double aspect,int hblock,int vblock,int hsize,int vsize,int * width,int * height)3750 void minimise_aspect_delta(double aspect, int hblock, int vblock, int hsize, int vsize, int *width, int *height) {
3751 // we will use trigonometry to calculate the smallest difference between a given
3752 // aspect ratio and the actual frame size. If the delta is smaller than current
3753 // we set the height and width
3754 int cw = width[0];
3755 int ch = height[0];
3756
3757 int real_width, real_height;
3758 uint64_t delta, current_delta;
3759
3760 // minimise d[(x-x1)^2 + (y-y1)^2]/d[x1], to get approximate values
3761 int calc_width = (int)((vsize + aspect * hsize) * aspect / (aspect * aspect + 1.));
3762
3763 int i;
3764
3765 current_delta = (hsize - cw) * (hsize - cw) + (vsize - ch) * (vsize - ch);
3766
3767 #ifdef DEBUG_ASPECT
3768 lives_printerr("aspect %.8f : width %d height %d is best fit\n", aspect, calc_width, (int)(calc_width / aspect));
3769 #endif
3770 // use the block size to find the nearest allowed size
3771 for (i = -1; i < 2; i++) {
3772 real_width = (int)(calc_width / hblock + i) * hblock;
3773 real_height = (int)(real_width / aspect / vblock + .5) * vblock;
3774 delta = (hsize - real_width) * (hsize - real_width) + (vsize - real_height) * (vsize - real_height);
3775
3776 if (real_width % hblock != 0 || real_height % vblock != 0 ||
3777 ABS((double)real_width / (double)real_height - aspect) > ASPECT_ALLOWANCE) {
3778 // encoders can be fussy, so we need to fit both aspect ratio and blocksize
3779 while (1) {
3780 real_width = ((int)(real_width / hblock) + 1) * hblock;
3781 real_height = (int)((double)real_width / aspect + .5);
3782
3783 if (real_height % vblock == 0) break;
3784
3785 real_height = ((int)(real_height / vblock) + 1) * vblock;
3786 real_width = (int)((double)real_height * aspect + .5);
3787
3788 if (real_width % hblock == 0) break;
3789 }
3790 }
3791
3792 #ifdef DEBUG_ASPECT
3793 lives_printerr("block quantise to %d x %d\n", real_width, real_height);
3794 #endif
3795 if (delta < current_delta) {
3796 #ifdef DEBUG_ASPECT
3797 lives_printerr("is better fit\n");
3798 #endif
3799 current_delta = delta;
3800 width[0] = real_width;
3801 height[0] = real_height;
3802 }
3803 }
3804 }
3805
3806
zero_spinbuttons(void)3807 void zero_spinbuttons(void) {
3808 lives_signal_handler_block(mainw->spinbutton_start, mainw->spin_start_func);
3809 lives_spin_button_set_range(LIVES_SPIN_BUTTON(mainw->spinbutton_start), 0., 0.);
3810 lives_spin_button_set_value(LIVES_SPIN_BUTTON(mainw->spinbutton_start), 0.);
3811 lives_signal_handler_unblock(mainw->spinbutton_start, mainw->spin_start_func);
3812 lives_signal_handler_block(mainw->spinbutton_end, mainw->spin_end_func);
3813 lives_spin_button_set_range(LIVES_SPIN_BUTTON(mainw->spinbutton_end), 0., 0.);
3814 lives_spin_button_set_value(LIVES_SPIN_BUTTON(mainw->spinbutton_end), 0.);
3815 lives_signal_handler_unblock(mainw->spinbutton_end, mainw->spin_end_func);
3816 }
3817
3818
switch_aud_to_jack(boolean set_in_prefs)3819 boolean switch_aud_to_jack(boolean set_in_prefs) {
3820 #ifdef ENABLE_JACK
3821 if (mainw->is_ready) {
3822 if (!mainw->jack_inited) lives_jack_init();
3823 if (!mainw->jackd) {
3824 jack_audio_init();
3825 jack_audio_read_init();
3826 mainw->jackd = jack_get_driver(0, TRUE);
3827 if (!jack_create_client_writer(mainw->jackd)) {
3828 mainw->jackd = NULL;
3829 return FALSE;
3830 }
3831 mainw->jackd->whentostop = &mainw->whentostop;
3832 mainw->jackd->cancelled = &mainw->cancelled;
3833 mainw->jackd->in_use = FALSE;
3834 mainw->jackd->play_when_stopped = (prefs->jack_opts & JACK_OPTS_NOPLAY_WHEN_PAUSED) ? FALSE : TRUE;
3835 jack_write_driver_activate(mainw->jackd);
3836 }
3837
3838 mainw->aplayer_broken = FALSE;
3839 lives_widget_show(mainw->vol_toolitem);
3840 if (mainw->vol_label) lives_widget_show(mainw->vol_label);
3841 lives_widget_show(mainw->recaudio_submenu);
3842 lives_widget_set_sensitive(mainw->vol_toolitem, TRUE);
3843
3844 if (mainw->vpp && mainw->vpp->get_audio_fmts)
3845 mainw->vpp->audio_codec = get_best_audio(mainw->vpp);
3846
3847 #ifdef HAVE_PULSE_AUDIO
3848 if (mainw->pulsed_read) {
3849 pulse_close_client(mainw->pulsed_read);
3850 mainw->pulsed_read = NULL;
3851 }
3852
3853 if (mainw->pulsed) {
3854 pulse_close_client(mainw->pulsed);
3855 mainw->pulsed = NULL;
3856 pulse_shutdown();
3857 }
3858 #endif
3859 }
3860 prefs->audio_player = AUD_PLAYER_JACK;
3861 if (set_in_prefs) set_string_pref(PREF_AUDIO_PLAYER, AUDIO_PLAYER_JACK);
3862 lives_snprintf(prefs->aplayer, 512, "%s", AUDIO_PLAYER_JACK);
3863
3864 if (mainw->is_ready && mainw->vpp && mainw->vpp->get_audio_fmts)
3865 mainw->vpp->audio_codec = get_best_audio(mainw->vpp);
3866
3867 if (prefs->perm_audio_reader && prefs->audio_src == AUDIO_SRC_EXT) {
3868 jack_rec_audio_to_clip(-1, -1, RECA_MONITOR);
3869 mainw->jackd_read->in_use = FALSE;
3870 }
3871
3872 lives_widget_set_sensitive(mainw->int_audio_checkbutton, TRUE);
3873 lives_widget_set_sensitive(mainw->ext_audio_checkbutton, TRUE);
3874 lives_widget_set_sensitive(mainw->mute_audio, TRUE);
3875 lives_widget_set_sensitive(mainw->m_mutebutton, TRUE);
3876 lives_widget_set_sensitive(mainw->p_mutebutton, TRUE);
3877
3878 return TRUE;
3879 #endif
3880 return FALSE;
3881 }
3882
3883
switch_aud_to_pulse(boolean set_in_prefs)3884 boolean switch_aud_to_pulse(boolean set_in_prefs) {
3885 #ifdef HAVE_PULSE_AUDIO
3886 boolean retval;
3887
3888 if (mainw->is_ready) {
3889 if ((retval = lives_pulse_init(-1))) {
3890 if (!mainw->pulsed) {
3891 pulse_audio_init();
3892 pulse_audio_read_init();
3893 mainw->pulsed = pulse_get_driver(TRUE);
3894 mainw->pulsed->whentostop = &mainw->whentostop;
3895 mainw->pulsed->cancelled = &mainw->cancelled;
3896 mainw->pulsed->in_use = FALSE;
3897 pulse_driver_activate(mainw->pulsed);
3898 }
3899 mainw->aplayer_broken = FALSE;
3900 lives_widget_show(mainw->vol_toolitem);
3901 if (mainw->vol_label) lives_widget_show(mainw->vol_label);
3902 lives_widget_show(mainw->recaudio_submenu);
3903 lives_widget_set_sensitive(mainw->vol_toolitem, TRUE);
3904
3905 prefs->audio_player = AUD_PLAYER_PULSE;
3906 if (set_in_prefs) set_string_pref(PREF_AUDIO_PLAYER, AUDIO_PLAYER_PULSE);
3907 lives_snprintf(prefs->aplayer, 512, "%s", AUDIO_PLAYER_PULSE);
3908
3909 if (mainw->vpp && mainw->vpp->get_audio_fmts)
3910 mainw->vpp->audio_codec = get_best_audio(mainw->vpp);
3911 }
3912
3913 #ifdef ENABLE_JACK
3914 if (mainw->jackd_read) {
3915 jack_close_device(mainw->jackd_read);
3916 mainw->jackd_read = NULL;
3917 }
3918
3919 if (mainw->jackd) {
3920 jack_close_device(mainw->jackd);
3921 mainw->jackd = NULL;
3922 }
3923 #endif
3924
3925 if (prefs->perm_audio_reader && prefs->audio_src == AUDIO_SRC_EXT) {
3926 pulse_rec_audio_to_clip(-1, -1, RECA_MONITOR);
3927 mainw->pulsed_read->in_use = FALSE;
3928 }
3929
3930 lives_widget_set_sensitive(mainw->int_audio_checkbutton, TRUE);
3931 lives_widget_set_sensitive(mainw->ext_audio_checkbutton, TRUE);
3932 lives_widget_set_sensitive(mainw->mute_audio, TRUE);
3933 lives_widget_set_sensitive(mainw->m_mutebutton, TRUE);
3934 if (mainw->play_window)
3935 lives_widget_set_sensitive(mainw->p_mutebutton, TRUE);
3936
3937 return retval;
3938 }
3939 #endif
3940 return FALSE;
3941 }
3942
3943
switch_aud_to_sox(boolean set_in_prefs)3944 boolean switch_aud_to_sox(boolean set_in_prefs) {
3945 if (!capable->has_sox_play) return FALSE; // TODO - show error
3946
3947 prefs->audio_player = AUD_PLAYER_SOX;
3948 lives_snprintf(prefs->audio_play_command, 256, "%s", EXEC_PLAY);
3949 if (set_in_prefs) set_string_pref(PREF_AUDIO_PLAYER, AUDIO_PLAYER_SOX);
3950 lives_snprintf(prefs->aplayer, 512, "%s", AUDIO_PLAYER_SOX);
3951 //set_string_pref(PREF_AUDIO_PLAY_COMMAND, prefs->audio_play_command);
3952
3953 if (mainw->is_ready) {
3954 /* //ubuntu / Unity has a hissy fit if you hide things in the menu !
3955 lives_widget_hide(mainw->vol_toolitem);
3956 if (mainw->vol_label) lives_widget_hide(mainw->vol_label);
3957 */
3958 lives_widget_set_sensitive(mainw->vol_toolitem, FALSE);
3959 lives_widget_hide(mainw->recaudio_submenu);
3960
3961 if (mainw->vpp && mainw->vpp->get_audio_fmts)
3962 mainw->vpp->audio_codec = get_best_audio(mainw->vpp);
3963
3964 pref_factory_bool(PREF_REC_EXT_AUDIO, FALSE, TRUE);
3965
3966 lives_widget_set_sensitive(mainw->int_audio_checkbutton, FALSE);
3967 lives_widget_set_sensitive(mainw->ext_audio_checkbutton, FALSE);
3968 lives_widget_set_sensitive(mainw->mute_audio, TRUE);
3969 lives_widget_set_sensitive(mainw->m_mutebutton, TRUE);
3970 lives_widget_set_sensitive(mainw->p_mutebutton, TRUE);
3971 }
3972
3973 #ifdef ENABLE_JACK
3974 if (mainw->jackd_read) {
3975 jack_close_device(mainw->jackd_read);
3976 mainw->jackd_read = NULL;
3977 }
3978
3979 if (mainw->jackd) {
3980 jack_close_device(mainw->jackd);
3981 mainw->jackd = NULL;
3982 }
3983 #endif
3984
3985 #ifdef HAVE_PULSE_AUDIO
3986 if (mainw->pulsed_read) {
3987 pulse_close_client(mainw->pulsed_read);
3988 mainw->pulsed_read = NULL;
3989 }
3990
3991 if (mainw->pulsed) {
3992 pulse_close_client(mainw->pulsed);
3993 mainw->pulsed = NULL;
3994 pulse_shutdown();
3995 }
3996 #endif
3997 return TRUE;
3998 }
3999
4000
switch_aud_to_none(boolean set_in_prefs)4001 void switch_aud_to_none(boolean set_in_prefs) {
4002 prefs->audio_player = AUD_PLAYER_NONE;
4003 if (set_in_prefs) set_string_pref(PREF_AUDIO_PLAYER, AUDIO_PLAYER_NONE);
4004 lives_snprintf(prefs->aplayer, 512, "%s", AUDIO_PLAYER_NONE);
4005
4006 if (mainw->is_ready) {
4007 /* //ubuntu has a hissy fit if you hide things in the menu
4008 lives_widget_hide(mainw->vol_toolitem);
4009 if (mainw->vol_label) lives_widget_hide(mainw->vol_label);
4010 */
4011 lives_widget_set_sensitive(mainw->vol_toolitem, FALSE);
4012 // lives_widget_hide(mainw->recaudio_submenu);
4013
4014 if (mainw->vpp && mainw->vpp->get_audio_fmts)
4015 mainw->vpp->audio_codec = get_best_audio(mainw->vpp);
4016
4017 pref_factory_bool(PREF_REC_EXT_AUDIO, FALSE, TRUE);
4018
4019 lives_widget_set_sensitive(mainw->int_audio_checkbutton, FALSE);
4020 lives_widget_set_sensitive(mainw->ext_audio_checkbutton, FALSE);
4021 lives_widget_set_sensitive(mainw->mute_audio, FALSE);
4022 lives_widget_set_sensitive(mainw->m_mutebutton, FALSE);
4023 if (mainw->preview_box) {
4024 lives_widget_set_sensitive(mainw->p_mutebutton, FALSE);
4025 }
4026 }
4027
4028 #ifdef ENABLE_JACK
4029 if (mainw->jackd_read) {
4030 jack_close_device(mainw->jackd_read);
4031 mainw->jackd_read = NULL;
4032 }
4033
4034 if (mainw->jackd) {
4035 jack_close_device(mainw->jackd);
4036 mainw->jackd = NULL;
4037 }
4038 #endif
4039
4040 #ifdef HAVE_PULSE_AUDIO
4041 if (mainw->pulsed_read) {
4042 pulse_close_client(mainw->pulsed_read);
4043 mainw->pulsed_read = NULL;
4044 }
4045
4046 if (mainw->pulsed) {
4047 pulse_close_client(mainw->pulsed);
4048 mainw->pulsed = NULL;
4049 pulse_shutdown();
4050 }
4051 #endif
4052 }
4053
4054
prepare_to_play_foreign(void)4055 boolean prepare_to_play_foreign(void) {
4056 // here we are going to 'play' a captured external window
4057
4058 #ifdef GUI_GTK
4059
4060 #if !GTK_CHECK_VERSION(3, 0, 0)
4061 #ifdef GDK_WINDOWING_X11
4062 GdkVisual *vissi = NULL;
4063 register int i;
4064 #endif
4065 #endif
4066 #endif
4067
4068 int new_file = mainw->first_free_file;
4069
4070 mainw->foreign_window = NULL;
4071
4072 // create a new 'file' to play into
4073 if (!get_new_handle(new_file, NULL)) {
4074 return FALSE;
4075 }
4076
4077 mainw->current_file = new_file;
4078
4079 if (mainw->rec_achans > 0) {
4080 cfile->arate = cfile->arps = mainw->rec_arate;
4081 cfile->achans = mainw->rec_achans;
4082 cfile->asampsize = mainw->rec_asamps;
4083 cfile->signed_endian = mainw->rec_signed_endian;
4084 #ifdef HAVE_PULSE_AUDIO
4085 if (mainw->rec_achans > 0 && prefs->audio_player == AUD_PLAYER_PULSE) {
4086 pulse_rec_audio_to_clip(mainw->current_file, -1, RECA_WINDOW_GRAB);
4087 mainw->pulsed_read->in_use = TRUE;
4088 }
4089 #endif
4090 #ifdef ENABLE_JACK
4091 if (mainw->rec_achans > 0 && prefs->audio_player == AUD_PLAYER_JACK) {
4092 jack_rec_audio_to_clip(mainw->current_file, -1, RECA_WINDOW_GRAB);
4093 mainw->jackd_read->in_use = TRUE;
4094 }
4095 #endif
4096 }
4097
4098 cfile->hsize = mainw->foreign_width / 2 + 1;
4099 cfile->vsize = mainw->foreign_height / 2 + 3;
4100
4101 cfile->fps = cfile->pb_fps = mainw->rec_fps;
4102
4103 resize(-2);
4104
4105 lives_widget_show(mainw->playframe);
4106 lives_widget_show(mainw->playarea);
4107 lives_widget_process_updates(LIVES_MAIN_WINDOW_WIDGET);
4108 lives_widget_set_opacity(mainw->playframe, 1.);
4109
4110 // size must be exact, must not be larger than play window or we end up with nothing
4111 mainw->pwidth = lives_widget_get_allocation_width(mainw->playframe);// - H_RESIZE_ADJUST + 2;
4112 mainw->pheight = lives_widget_get_allocation_height(mainw->playframe);// - V_RESIZE_ADJUST + 2;
4113
4114 cfile->hsize = mainw->pwidth;
4115 cfile->vsize = mainw->pheight;
4116
4117 cfile->img_type = IMG_TYPE_BEST; // override the pref
4118
4119 #ifdef GUI_GTK
4120 #if GTK_CHECK_VERSION(3, 0, 0)
4121
4122 #ifdef GDK_WINDOWING_X11
4123 mainw->foreign_window = gdk_x11_window_foreign_new_for_display
4124 (mainw->mgeom[widget_opts.monitor].disp,
4125 mainw->foreign_id);
4126 #else
4127 #ifdef GDK_WINDOWING_WIN32
4128 if (!mainw->foreign_window)
4129 mainw->foreign_window = gdk_win32_window_foreign_new_for_display
4130 (mainw->mgeom[widget_opts.monitor].disp,
4131 mainw->foreign_id);
4132 #endif
4133
4134 #endif // GDK_WINDOWING
4135
4136 if (mainw->foreign_window) lives_xwindow_set_keep_above(mainw->foreign_window, TRUE);
4137
4138 #else // 3, 0, 0
4139 mainw->foreign_window = gdk_window_foreign_new(mainw->foreign_id);
4140 #endif
4141 #endif // GUI_GTK
4142
4143 #ifdef GUI_GTK
4144 #ifdef GDK_WINDOWING_X11
4145 #if !GTK_CHECK_VERSION(3, 0, 0)
4146
4147 if (mainw->foreign_visual) {
4148 for (i = 0; i < capable->nmonitors; i++) {
4149 vissi = gdk_x11_screen_lookup_visual(mainw->mgeom[i].screen, hextodec(mainw->foreign_visual));
4150 if (vissi) break;
4151 }
4152 }
4153
4154 if (!vissi) vissi = gdk_visual_get_best_with_depth(mainw->foreign_bpp);
4155 if (!vissi) return FALSE;
4156
4157 mainw->foreign_cmap = gdk_x11_colormap_foreign_new(vissi,
4158 gdk_x11_colormap_get_xcolormap(gdk_colormap_new(vissi, TRUE)));
4159
4160 if (!mainw->foreign_cmap) return FALSE;
4161
4162 #endif
4163 #endif
4164 #endif
4165
4166 if (!mainw->foreign_window) return FALSE;
4167
4168 mainw->play_start = 1;
4169 if (mainw->rec_vid_frames == -1) mainw->play_end = INT_MAX;
4170 else mainw->play_end = mainw->rec_vid_frames;
4171
4172 mainw->rec_samples = -1;
4173
4174 omute = mainw->mute;
4175 osepwin = mainw->sep_win;
4176 ofs = mainw->fs;
4177 ofaded = mainw->faded;
4178 odouble = mainw->double_size;
4179
4180 mainw->mute = TRUE;
4181 mainw->sep_win = FALSE;
4182 mainw->fs = FALSE;
4183 mainw->faded = TRUE;
4184 mainw->double_size = FALSE;
4185
4186 lives_widget_hide(mainw->t_sepwin);
4187 lives_widget_hide(mainw->t_infobutton);
4188
4189 return TRUE;
4190 }
4191
4192
after_foreign_play(void)4193 boolean after_foreign_play(void) {
4194 // read details from capture file
4195 int capture_fd = -1;
4196 char *capfile = lives_strdup_printf("%s/.capture.%d", prefs->workdir, capable->mainpid);
4197 char capbuf[256];
4198 ssize_t length;
4199 int new_frames = 0;
4200 int old_file = mainw->current_file;
4201
4202 char **array;
4203
4204 // assume for now we only get one clip passed back
4205 if ((capture_fd = lives_open2(capfile, O_RDONLY)) > -1) {
4206 lives_memset(capbuf, 0, 256);
4207 if ((length = read(capture_fd, capbuf, 256))) {
4208 if (get_token_count(capbuf, '|') > 2) {
4209 array = lives_strsplit(capbuf, "|", 3);
4210 new_frames = atoi(array[1]);
4211 if (new_frames > 0) {
4212 create_cfile(-1, array[0], FALSE);
4213 lives_strfreev(array);
4214 lives_snprintf(cfile->file_name, 256, "Capture %d", mainw->cap_number);
4215 lives_snprintf(cfile->name, CLIP_NAME_MAXLEN, "Capture %d", mainw->cap_number++);
4216 lives_snprintf(cfile->type, 40, "Frames");
4217
4218 cfile->progress_start = cfile->start = 1;
4219 cfile->progress_end = cfile->frames = cfile->end = new_frames;
4220 cfile->pb_fps = cfile->fps = mainw->rec_fps;
4221
4222 cfile->hsize = CEIL(mainw->foreign_width, 4);
4223 cfile->vsize = CEIL(mainw->foreign_height, 4);
4224
4225 cfile->img_type = IMG_TYPE_BEST;
4226 cfile->changed = TRUE;
4227
4228 if (mainw->rec_achans > 0) {
4229 cfile->arate = cfile->arps = mainw->rec_arate;
4230 cfile->achans = mainw->rec_achans;
4231 cfile->asampsize = mainw->rec_asamps;
4232 cfile->signed_endian = mainw->rec_signed_endian;
4233 }
4234
4235 save_clip_values(mainw->current_file);
4236 if (prefs->crash_recovery) add_to_recovery_file(cfile->handle);
4237
4238 close(capture_fd);
4239 lives_rm(capfile);
4240 capture_fd = -1;
4241 do_threaded_dialog(_("Cleaning up clip"), FALSE);
4242 lives_widget_show_all(mainw->proc_ptr->processing);
4243 resize_all(mainw->current_file, cfile->hsize, cfile->vsize, cfile->img_type, FALSE, NULL, NULL);
4244 end_threaded_dialog();
4245 if (cfile->afilesize > 0 && cfile->achans > 0
4246 && CLIP_TOTAL_TIME(mainw->current_file) > cfile->laudio_time + AV_TRACK_MIN_DIFF) {
4247 pad_init_silence();
4248 }
4249 // *INDENT-OFF*
4250 }}}}
4251 // *INDENT-ON*
4252
4253 if (capture_fd > -1) {
4254 close(capture_fd);
4255 lives_rm(capfile);
4256 }
4257
4258 if (new_frames == 0) {
4259 // nothing captured; or cancelled
4260 lives_free(capfile);
4261 return FALSE;
4262 }
4263
4264 cfile->nopreview = FALSE;
4265 lives_free(capfile);
4266
4267 add_to_clipmenu();
4268 if (!mainw->multitrack) switch_to_file(old_file, mainw->current_file);
4269
4270 else {
4271 int new_file = mainw->current_file;
4272 mainw->current_file = mainw->multitrack->render_file;
4273 mt_init_clips(mainw->multitrack, new_file, TRUE);
4274 mt_clip_select(mainw->multitrack, TRUE);
4275 }
4276
4277 cfile->is_loaded = TRUE;
4278 cfile->changed = TRUE;
4279 lives_notify(LIVES_OSC_NOTIFY_CLIP_OPENED, "");
4280 return TRUE;
4281 }
4282
4283
int_array_contains_value(int * array,int num_elems,int value)4284 LIVES_GLOBAL_INLINE boolean int_array_contains_value(int *array, int num_elems, int value) {
4285 for (int i = 0; i < num_elems; i++) if (array[i] == value) return TRUE;
4286 return FALSE;
4287 }
4288
4289
reset_clipmenu(void)4290 void reset_clipmenu(void) {
4291 // sometimes the clip menu gets messed up, e.g. after reloading a set.
4292 // This function will clean up the 'x's and so on.
4293
4294 if (mainw->current_file > 0 && cfile && cfile->menuentry) {
4295 #ifdef GTK_RADIO_MENU_BUG
4296 register int i;
4297 for (i = 1; i < MAX_FILES; i++) {
4298 if (i != mainw->current_file && mainw->files[i] && mainw->files[i]->menuentry) {
4299 lives_signal_handler_block(mainw->files[i]->menuentry, mainw->files[i]->menuentry_func);
4300 lives_check_menu_item_set_active(LIVES_CHECK_MENU_ITEM(mainw->files[i]->menuentry), FALSE);
4301 lives_signal_handler_unblock(mainw->files[i]->menuentry, mainw->files[i]->menuentry_func);
4302 }
4303 }
4304 #endif
4305 lives_signal_handler_block(cfile->menuentry, cfile->menuentry_func);
4306 lives_check_menu_item_set_active(LIVES_CHECK_MENU_ITEM(cfile->menuentry), TRUE);
4307 lives_signal_handler_unblock(cfile->menuentry, cfile->menuentry_func);
4308 }
4309 }
4310
4311
check_file(const char * file_name,boolean check_existing)4312 boolean check_file(const char *file_name, boolean check_existing) {
4313 int check;
4314 boolean exists = FALSE;
4315 char *msg;
4316 // file_name should be in utf8
4317 char *lfile_name = U82F(file_name);
4318
4319 mainw->error = FALSE;
4320
4321 while (1) {
4322 // check if file exists
4323 if (lives_file_test(lfile_name, LIVES_FILE_TEST_EXISTS)) {
4324 if (check_existing) {
4325 msg = lives_strdup_printf(_("\n%s\nalready exists.\n\nOverwrite ?\n"), file_name);
4326 if (!do_warning_dialog(msg)) {
4327 lives_free(msg);
4328 lives_free(lfile_name);
4329 return FALSE;
4330 }
4331 lives_free(msg);
4332 }
4333 check = open(lfile_name, O_WRONLY);
4334 exists = TRUE;
4335 }
4336 // if not, check if we can write to it
4337 else {
4338 check = open(lfile_name, O_CREAT | O_EXCL | O_WRONLY, DEF_FILE_PERMS);
4339 }
4340
4341 if (check < 0) {
4342 LiVESResponseType resp = LIVES_RESPONSE_NONE;
4343 mainw->error = TRUE;
4344 if (mainw && mainw->is_ready) {
4345 if (errno == EACCES)
4346 resp = do_file_perm_error(lfile_name, TRUE);
4347 else
4348 resp = do_write_failed_error_s_with_retry(lfile_name, NULL);
4349 if (resp == LIVES_RESPONSE_RETRY) {
4350 continue;
4351 }
4352 }
4353 lives_free(lfile_name);
4354 return FALSE;
4355 }
4356
4357 close(check);
4358 break;
4359 }
4360 if (!exists) lives_rm(lfile_name);
4361 lives_free(lfile_name);
4362 return TRUE;
4363 }
4364
4365
lives_rmdir(const char * dir,boolean force)4366 int lives_rmdir(const char *dir, boolean force) {
4367 // if force is TRUE, removes non-empty dirs, otherwise leaves them
4368 // may fail
4369 char *com, *cmd;
4370 int retval;
4371
4372 if (force) {
4373 cmd = lives_strdup_printf("%s -rf", capable->rm_cmd);
4374 } else {
4375 cmd = lives_strdup(capable->rmdir_cmd);
4376 }
4377
4378 com = lives_strdup_printf("%s \"%s/\" >\"%s\" 2>&1", cmd, dir, prefs->cmd_log);
4379 retval = lives_system(com, TRUE);
4380 lives_free(com);
4381 lives_free(cmd);
4382 return retval;
4383 }
4384
4385
lives_rmdir_with_parents(const char * dir)4386 int lives_rmdir_with_parents(const char *dir) {
4387 // may fail, will not remove empty dirs
4388 char *com = lives_strdup_printf("%s -p \"%s\" >\"%s\" 2>&1", capable->rmdir_cmd, dir, prefs->cmd_log);
4389 int retval = lives_system(com, TRUE);
4390 lives_free(com);
4391 return retval;
4392 }
4393
4394
lives_rm(const char * file)4395 int lives_rm(const char *file) {
4396 // may fail
4397 char *com = lives_strdup_printf("%s -f \"%s\" >\"%s\" 2>&1", capable->rm_cmd, file, prefs->cmd_log);
4398 int retval = lives_system(com, TRUE);
4399 lives_free(com);
4400 return retval;
4401 }
4402
4403
lives_rmglob(const char * files)4404 int lives_rmglob(const char *files) {
4405 // delete files with name "files"*
4406 // may fail
4407 char *com = lives_strdup_printf("%s \"%s\"* >\"%s\" 2>&1", capable->rm_cmd, files, prefs->cmd_log);
4408 int retval = lives_system(com, TRUE);
4409 lives_free(com);
4410 return retval;
4411 }
4412
4413
lives_cp(const char * from,const char * to)4414 int lives_cp(const char *from, const char *to) {
4415 // may not fail - BUT seems to return -1 sometimes
4416 char *com = lives_strdup_printf("%s \"%s\" \"%s\" >\"%s\" 2>&1", capable->cp_cmd, from, to, prefs->cmd_log);
4417 int retval = lives_system(com, FALSE);
4418 lives_free(com);
4419 return retval;
4420 }
4421
4422
lives_cp_recursive(const char * from,const char * to,boolean incl_dir)4423 int lives_cp_recursive(const char *from, const char *to, boolean incl_dir) {
4424 // may not fail
4425 int retval;
4426 char *com;
4427 if (incl_dir) com = lives_strdup_printf("%s -r \"%s\" \"%s\" >\"%s\" 2>&1", capable->cp_cmd, from, to, prefs->cmd_log);
4428 else com = lives_strdup_printf("%s -rf \"%s\"/* \"%s\" >\"%s\" 2>&1", capable->cp_cmd, from, to, prefs->cmd_log);
4429 if (!lives_file_test(to, LIVES_FILE_TEST_EXISTS))
4430 lives_mkdir_with_parents(to, capable->umask);
4431 retval = lives_system(com, FALSE);
4432 lives_free(com);
4433 return retval;
4434 }
4435
4436
lives_cp_keep_perms(const char * from,const char * to)4437 int lives_cp_keep_perms(const char *from, const char *to) {
4438 // may not fail
4439 char *com = lives_strdup_printf("%s -a \"%s\" \"%s/\" >\"%s\" 2>&1", capable->cp_cmd, from, to, prefs->cmd_log);
4440 int retval = lives_system(com, FALSE);
4441 lives_free(com);
4442 return retval;
4443 }
4444
4445
lives_mv(const char * from,const char * to)4446 int lives_mv(const char *from, const char *to) {
4447 // may not fail
4448 char *com = lives_strdup_printf("%s \"%s\" \"%s\"", capable->mv_cmd, from, to);
4449 int retval = lives_system(com, FALSE);
4450 lives_free(com);
4451 return retval;
4452 }
4453
4454
lives_touch(const char * tfile)4455 int lives_touch(const char *tfile) {
4456 // may not fail
4457 char *com = lives_strdup_printf("%s \"%s\" >\"%s\" 2>&1", capable->touch_cmd, tfile, prefs->cmd_log);
4458 int retval = lives_system(com, FALSE);
4459 lives_free(com);
4460 return retval;
4461 }
4462
4463
lives_ln(const char * from,const char * to)4464 int lives_ln(const char *from, const char *to) {
4465 // may not fail
4466 char *com;
4467 int retval;
4468 com = lives_strdup_printf("%s -s \"%s\" \"%s\" >\"%s\" 2>&1", capable->ln_cmd, from, to, prefs->cmd_log);
4469 retval = lives_system(com, FALSE);
4470 lives_free(com);
4471 return retval;
4472 }
4473
4474
lives_chmod(const char * target,const char * mode)4475 int lives_chmod(const char *target, const char *mode) {
4476 // may not fail
4477 char *com = lives_strdup_printf("%s %s \"%s\" >\"%s\" 2>&1", capable->chmod_cmd, mode, target, prefs->cmd_log);
4478 int retval = lives_system(com, FALSE);
4479 lives_free(com);
4480 return retval;
4481 }
4482
4483
lives_cat(const char * from,const char * to,boolean append)4484 int lives_cat(const char *from, const char *to, boolean append) {
4485 // may not fail
4486 char *com;
4487 char *op;
4488 int retval;
4489
4490 if (append) op = ">>";
4491 else op = ">";
4492
4493 com = lives_strdup_printf("%s \"%s\" %s \"%s\" >\"%s\" 2>&1", capable->cat_cmd, from, op, to, prefs->cmd_log);
4494 retval = lives_system(com, FALSE);
4495 lives_free(com);
4496 return retval;
4497 }
4498
4499
lives_echo(const char * text,const char * to,boolean append)4500 int lives_echo(const char *text, const char *to, boolean append) {
4501 // may not fail
4502 char *com;
4503 char *op;
4504 int retval;
4505
4506 if (append) op = ">>";
4507 else op = ">";
4508
4509 com = lives_strdup_printf("%s \"%s\" %s \"%s\" 2>\"%s\"", capable->echo_cmd, text, op, to, prefs->cmd_log);
4510 retval = lives_system(com, FALSE);
4511 lives_free(com);
4512 return retval;
4513 }
4514
4515
lives_kill_subprocesses(const char * dirname,boolean kill_parent)4516 void lives_kill_subprocesses(const char *dirname, boolean kill_parent) {
4517 char *com;
4518 if (kill_parent)
4519 com = lives_strdup_printf("%s stopsubsub \"%s\"", prefs->backend_sync, dirname);
4520 else
4521 com = lives_strdup_printf("%s stopsubsubs \"%s\"", prefs->backend_sync, dirname);
4522 lives_system(com, TRUE);
4523 lives_free(com);
4524 }
4525
4526
lives_suspend_resume_process(const char * dirname,boolean suspend)4527 void lives_suspend_resume_process(const char *dirname, boolean suspend) {
4528 char *com;
4529 if (!suspend)
4530 com = lives_strdup_printf("%s stopsubsub \"%s\" SIGCONT 2>/dev/null", prefs->backend_sync, dirname);
4531 else
4532 com = lives_strdup_printf("%s stopsubsub \"%s\" SIGTSTP 2>/dev/null", prefs->backend_sync, dirname);
4533 lives_system(com, TRUE);
4534 lives_free(com);
4535
4536 com = lives_strdup_printf("%s resume \"%s\"", prefs->backend_sync, dirname);
4537 lives_system(com, FALSE);
4538 lives_free(com);
4539 }
4540
4541
check_dir_access(const char * dir,boolean leaveit)4542 boolean check_dir_access(const char *dir, boolean leaveit) {
4543 // if a directory exists, make sure it is readable and writable
4544 // otherwise create it and then check
4545 // we test here by actually creating a (mkstemp) file and writing to it
4546 // dir is in locale encoding
4547
4548 // see also is_writeable_dir() which uses statvfs
4549
4550 // WARNING: may leave some parents around
4551 char test[5] = "1234";
4552 char *testfile;
4553 boolean exists = lives_file_test(dir, LIVES_FILE_TEST_EXISTS);
4554 int fp;
4555
4556 if (!exists) lives_mkdir_with_parents(dir, capable->umask);
4557
4558 if (!lives_file_test(dir, LIVES_FILE_TEST_IS_DIR)) return FALSE;
4559
4560 testfile = lives_build_filename(dir, "livestst-XXXXXX", NULL);
4561 fp = g_mkstemp(testfile);
4562 if (fp == -1) {
4563 lives_free(testfile);
4564 if (!exists) {
4565 lives_rmdir(dir, FALSE);
4566 }
4567 return FALSE;
4568 }
4569 if (lives_write(fp, test, 4, TRUE) != 4) {
4570 close(fp);
4571 lives_rm(testfile);
4572 if (!exists) {
4573 lives_rmdir(dir, FALSE);
4574 }
4575 lives_free(testfile);
4576 return FALSE;
4577 }
4578 close(fp);
4579 fp = lives_open2(testfile, O_RDONLY);
4580 if (fp < 0) {
4581 lives_rm(testfile);
4582 if (!exists) {
4583 lives_rmdir(dir, FALSE);
4584 }
4585 lives_free(testfile);
4586 return FALSE;
4587 }
4588 if (lives_read(fp, test, 4, TRUE) != 4) {
4589 close(fp);
4590 lives_rm(testfile);
4591 if (!exists) {
4592 lives_rmdir(dir, FALSE);
4593 }
4594 lives_free(testfile);
4595 return FALSE;
4596 }
4597 close(fp);
4598 lives_rm(testfile);
4599 if (!exists && !leaveit) {
4600 lives_rmdir(dir, FALSE);
4601 }
4602 lives_free(testfile);
4603 return TRUE;
4604 }
4605
4606
activate_url_inner(const char * link)4607 void activate_url_inner(const char *link) {
4608 #if GTK_CHECK_VERSION(2, 14, 0)
4609 LiVESError *err = NULL;
4610 #if GTK_CHECK_VERSION(3, 22, 0)
4611 gtk_show_uri_on_window(NULL, link, GDK_CURRENT_TIME, &err);
4612 #else
4613 gtk_show_uri(NULL, link, GDK_CURRENT_TIME, &err);
4614 #endif
4615 #else
4616 char *com = getenv("BROWSER");
4617 com = lives_strdup_printf("\"%s\" '%s' &", com ? com : "gnome-open", link);
4618 lives_system(com, FALSE);
4619 lives_free(com);
4620 #endif
4621 }
4622
4623
activate_url(LiVESAboutDialog * about,const char * link,livespointer data)4624 void activate_url(LiVESAboutDialog * about, const char *link, livespointer data) {
4625 activate_url_inner(link);
4626 }
4627
4628
show_manual_section(const char * lang,const char * section)4629 void show_manual_section(const char *lang, const char *section) {
4630 char *tmp = NULL, *tmp2 = NULL;
4631 const char *link;
4632
4633 link = lives_strdup_printf("%s%s%s%s", LIVES_MANUAL_URL, (lang == NULL ? "" : (tmp2 = lives_strdup_printf("//%s//", lang))),
4634 LIVES_MANUAL_FILENAME, (section == NULL ? "" : (tmp = lives_strdup_printf("#%s", section))));
4635
4636 activate_url_inner(link);
4637
4638 if (tmp) lives_free(tmp);
4639 if (tmp2) lives_free(tmp2);
4640 }
4641
4642
4643
wait_for_bg_audio_sync(int fileno)4644 void wait_for_bg_audio_sync(int fileno) {
4645 char *afile = lives_get_audio_file_name(fileno);
4646 lives_alarm_t alarm_handle = lives_alarm_set(LIVES_SHORTEST_TIMEOUT);
4647 int fd;
4648
4649 while ((fd = open(afile, O_RDONLY)) < 0 && lives_alarm_check(alarm_handle) > 0) {
4650 lives_sync(1);
4651 lives_usleep(prefs->sleep_time);
4652 }
4653 lives_alarm_clear(alarm_handle);
4654
4655 if (fd >= 0) close(fd);
4656 lives_free(afile);
4657 }
4658
4659
create_event_space(int length)4660 boolean create_event_space(int length) {
4661 // try to create desired events
4662 // if we run out of memory, all events requested are freed, and we return FALSE
4663 // otherwise we return TRUE
4664
4665 // NOTE: this is the OLD event system, it's only used for reordering in the clip editor
4666
4667 if (cfile->resample_events) {
4668 lives_free(cfile->resample_events);
4669 }
4670 if ((cfile->resample_events = (resample_event *)(lives_calloc(length, sizeof(resample_event)))) == NULL) {
4671 // memory overflow
4672 return FALSE;
4673 }
4674 return TRUE;
4675 }
4676
4677
lives_list_strcmp_index(LiVESList * list,livesconstpointer data,boolean case_sensitive)4678 int lives_list_strcmp_index(LiVESList * list, livesconstpointer data, boolean case_sensitive) {
4679 // find data in list, using strcmp
4680 int i;
4681 int len;
4682 if (!list) return -1;
4683
4684 len = lives_list_length(list);
4685
4686 if (case_sensitive) {
4687 for (i = 0; i < len; i++) {
4688 if (!lives_strcmp((const char *)lives_list_nth_data(list, i), (const char *)data)) return i;
4689 if (!lives_strcmp((const char *)lives_list_nth_data(list, i), (const char *)data)) return i;
4690 }
4691 } else {
4692 for (i = 0; i < len; i++) {
4693 if (!lives_utf8_strcasecmp((const char *)lives_list_nth_data(list, i), (const char *)data)) return i;
4694 if (!lives_utf8_strcasecmp((const char *)lives_list_nth_data(list, i), (const char *)data)) return i;
4695 }
4696 }
4697 return -1;
4698 }
4699
4700
add_to_recent(const char * filename,double start,frames_t frames,const char * extra_params)4701 void add_to_recent(const char *filename, double start, frames_t frames, const char *extra_params) {
4702 const char *mtext;
4703 char buff[PATH_MAX * 2];
4704 char *file, *mfile, *prefname;
4705 register int i;
4706
4707 if (frames > 0) {
4708 mfile = lives_strdup_printf("%s|%.2f|%d", filename, start, frames);
4709 if (!extra_params || (!(*extra_params))) file = lives_strdup(mfile);
4710 else file = lives_strdup_printf("%s\n%s", mfile, extra_params);
4711 } else {
4712 mfile = lives_strdup(filename);
4713 if (!extra_params || (!(*extra_params))) file = lives_strdup(mfile);
4714 else file = lives_strdup_printf("%s\n%s", mfile, extra_params);
4715 }
4716
4717 for (i = 0; i < N_RECENT_FILES; i++) {
4718 mtext = lives_menu_item_get_text(mainw->recent[i]);
4719 if (!lives_strcmp(mfile, mtext)) break;
4720 }
4721
4722 if (i == 0) return;
4723
4724 if (i == N_RECENT_FILES) --i;
4725
4726 for (; i > 0; i--) {
4727 mtext = lives_menu_item_get_text(mainw->recent[i - 1]);
4728 lives_menu_item_set_text(mainw->recent[i], mtext, FALSE);
4729 if (mainw->multitrack) lives_menu_item_set_text(mainw->multitrack->recent[i], mtext, FALSE);
4730
4731 prefname = lives_strdup_printf("%s%d", PREF_RECENT, i);
4732 get_utf8_pref(prefname, buff, PATH_MAX * 2);
4733 lives_free(prefname);
4734
4735 prefname = lives_strdup_printf("%s%d", PREF_RECENT, i + 1);
4736 set_utf8_pref(prefname, buff);
4737 lives_free(prefname);
4738 }
4739
4740 lives_menu_item_set_text(mainw->recent[0], mfile, FALSE);
4741 if (mainw->multitrack) lives_menu_item_set_text(mainw->multitrack->recent[0], mfile, FALSE);
4742 prefname = lives_strdup_printf("%s%d", PREF_RECENT, 1);
4743 set_utf8_pref(prefname, file);
4744 lives_free(prefname);
4745
4746 for (; i < N_RECENT_FILES; i++) {
4747 mtext = lives_menu_item_get_text(mainw->recent[i]);
4748 if (*mtext) lives_widget_show(mainw->recent[i]);
4749 }
4750
4751 lives_free(mfile); lives_free(file);
4752 }
4753
4754
verhash(char * xv)4755 int verhash(char *xv) {
4756 char *version, *s;
4757 int major = 0, minor = 0, micro = 0;
4758
4759 if (!xv) return 0;
4760
4761 version = lives_strdup(xv);
4762
4763 if (!(*version)) {
4764 lives_free(version);
4765 return 0;
4766 }
4767
4768 s = strtok(version, ".");
4769 if (s) {
4770 major = atoi(s);
4771 s = strtok(NULL, ".");
4772 if (s) {
4773 minor = atoi(s);
4774 s = strtok(NULL, ".");
4775 if (s) micro = atoi(s);
4776 }
4777 }
4778 lives_free(version);
4779 return major * 1000000 + minor * 1000 + micro;
4780 }
4781
4782
4783 // TODO - move into undo.c
set_undoable(const char * what,boolean sensitive)4784 void set_undoable(const char *what, boolean sensitive) {
4785 if (mainw->current_file > -1) {
4786 cfile->redoable = FALSE;
4787 cfile->undoable = sensitive;
4788 if (what) {
4789 char *what_safe = lives_strdelimit(lives_strdup(what), "_", ' ');
4790 lives_snprintf(cfile->undo_text, 32, _("_Undo %s"), what_safe);
4791 lives_snprintf(cfile->redo_text, 32, _("_Redo %s"), what_safe);
4792 lives_free(what_safe);
4793 } else {
4794 cfile->undoable = FALSE;
4795 cfile->undo_action = UNDO_NONE;
4796 lives_snprintf(cfile->undo_text, 32, "%s", _("_Undo"));
4797 lives_snprintf(cfile->redo_text, 32, "%s", _("_Redo"));
4798 }
4799 lives_menu_item_set_text(mainw->undo, cfile->undo_text, TRUE);
4800 lives_menu_item_set_text(mainw->redo, cfile->redo_text, TRUE);
4801 }
4802
4803 lives_widget_hide(mainw->redo);
4804 lives_widget_show(mainw->undo);
4805 lives_widget_set_sensitive(mainw->undo, sensitive);
4806
4807 #ifdef PRODUCE_LOG
4808 lives_log(what);
4809 #endif
4810 }
4811
4812
set_redoable(const char * what,boolean sensitive)4813 void set_redoable(const char *what, boolean sensitive) {
4814 if (mainw->current_file > -1) {
4815 cfile->undoable = FALSE;
4816 cfile->redoable = sensitive;
4817 if (what) {
4818 char *what_safe = lives_strdelimit(lives_strdup(what), "_", ' ');
4819 lives_snprintf(cfile->undo_text, 32, _("_Undo %s"), what_safe);
4820 lives_snprintf(cfile->redo_text, 32, _("_Redo %s"), what_safe);
4821 lives_free(what_safe);
4822 } else {
4823 cfile->redoable = FALSE;
4824 cfile->undo_action = UNDO_NONE;
4825 lives_snprintf(cfile->undo_text, 32, "%s", _("_Undo"));
4826 lives_snprintf(cfile->redo_text, 32, "%s", _("_Redo"));
4827 }
4828 lives_menu_item_set_text(mainw->undo, cfile->undo_text, TRUE);
4829 lives_menu_item_set_text(mainw->redo, cfile->redo_text, TRUE);
4830 }
4831
4832 lives_widget_hide(mainw->undo);
4833 lives_widget_show(mainw->redo);
4834 lives_widget_set_sensitive(mainw->redo, sensitive);
4835 }
4836
4837
set_sel_label(LiVESWidget * sel_label)4838 void set_sel_label(LiVESWidget * sel_label) {
4839 char *tstr, *frstr, *tmp;
4840 char *sy, *sz;
4841
4842 if (mainw->current_file == -1 || !cfile->frames || mainw->multitrack) {
4843 lives_label_set_text(LIVES_LABEL(sel_label), _("-------------Selection------------"));
4844 } else {
4845 tstr = lives_strdup_printf("%.2f", calc_time_from_frame(mainw->current_file, cfile->end + 1) -
4846 calc_time_from_frame(mainw->current_file, cfile->start));
4847 frstr = lives_strdup_printf("%d", cfile->end - cfile->start + 1);
4848
4849 // TRANSLATORS: - try to keep the text of the middle part the same length, by deleting "-" if necessary
4850 lives_label_set_text(LIVES_LABEL(sel_label),
4851 (tmp = lives_strconcat("---------- [ ", tstr, (sy = ((_(" sec ] ----------Selection---------- [ ")))),
4852 frstr, (sz = (_(" frames ] ----------"))), NULL)));
4853 lives_free(sy); lives_free(sz);
4854 lives_free(tmp); lives_free(frstr); lives_free(tstr);
4855 }
4856 lives_widget_queue_draw(sel_label);
4857 }
4858
4859
lives_list_free_strings(LiVESList * list)4860 LIVES_GLOBAL_INLINE void lives_list_free_strings(LiVESList * list) {
4861 for (; list; list = list->next) lives_freep((void **)&list->data);
4862 }
4863
4864
lives_slist_free_all(LiVESSList ** list)4865 LIVES_GLOBAL_INLINE void lives_slist_free_all(LiVESSList **list) {
4866 if (!list || !*list) return;
4867 lives_list_free_strings((LiVESList *)*list);
4868 lives_slist_free(*list);
4869 *list = NULL;
4870 }
4871
4872
lives_list_free_all(LiVESList ** list)4873 LIVES_GLOBAL_INLINE void lives_list_free_all(LiVESList **list) {
4874 if (!list || !*list) return;
4875 lives_list_free_strings(*list);
4876 lives_list_free(*list);
4877 *list = NULL;
4878 }
4879
4880
cached_list_free(LiVESList ** list)4881 LIVES_GLOBAL_INLINE void cached_list_free(LiVESList **list) {
4882 lives_speed_cache_t *speedy;
4883 for (LiVESList *xlist = *list; xlist; xlist = xlist->next) {
4884 speedy = (lives_speed_cache_t *)(*list)->data;
4885 if (speedy) {
4886 if (speedy->key) lives_free(speedy->key);
4887 if (speedy->data) lives_free(speedy->data);
4888 lives_free(speedy);
4889 }
4890 xlist->data = NULL;
4891 }
4892 lives_list_free(*list);
4893 *list = NULL;
4894 }
4895
4896
print_cache(LiVESList * cache)4897 void print_cache(LiVESList * cache) {
4898 /// for debugging
4899 lives_speed_cache_t *speedy;
4900 LiVESList *ll = cache;
4901 g_print("dumping cache %p\n", cache);
4902 for (; ll; ll = ll->next) {
4903 speedy = (lives_speed_cache_t *)ll->data;
4904 g_print("cach dets: %s = %s\n", speedy->key, speedy->data);
4905 }
4906 }
4907
4908
cache_file_contents(const char * filename)4909 LiVESList *cache_file_contents(const char *filename) {
4910 lives_speed_cache_t *speedy;
4911 LiVESList *list = NULL;
4912 FILE *hfile;
4913 size_t kelen;
4914 char buff[65536];
4915 char *key = NULL, *keystr_end = NULL, *cptr, *tmp, *data = NULL;
4916
4917 if (!(hfile = fopen(filename, "r"))) return NULL;
4918 while (fgets(buff, 65536, hfile)) {
4919 if (!*buff) continue;
4920 if (*buff == '#') continue;
4921 if (key) {
4922 if (!lives_strncmp(buff, keystr_end, kelen)) {
4923 speedy = (lives_speed_cache_t *)lives_calloc(1, sizeof(lives_speed_cache_t));
4924 speedy->hash = fast_hash(key);
4925 speedy->key = key;
4926 speedy->data = data;
4927 key = data = NULL;
4928 lives_free(keystr_end);
4929 keystr_end = NULL;
4930 list = lives_list_prepend(list, speedy);
4931 continue;
4932 }
4933 cptr = buff;
4934 if (data) {
4935 if (*buff != '|') continue;
4936 cptr++;
4937 }
4938 lives_chomp(cptr);
4939 tmp = lives_strdup_printf("%s%s", data ? data : "", cptr);
4940 if (data) lives_free(data);
4941 data = tmp;
4942 continue;
4943 }
4944 if (*buff != '<') continue;
4945 kelen = 0;
4946 for (cptr = buff; cptr; cptr++) {
4947 if (*cptr == '>') {
4948 kelen = cptr - buff;
4949 if (kelen > 2) {
4950 *cptr = 0;
4951 key = lives_strdup(buff + 1);
4952 keystr_end = lives_strdup_printf("</%s>", key);
4953 kelen++;
4954 }
4955 break;
4956 }
4957 }
4958 }
4959 fclose(hfile);
4960 if (key) lives_free(key);
4961 if (keystr_end) lives_free(keystr_end);
4962 return lives_list_reverse(list);
4963 }
4964
4965
get_val_from_cached_list(const char * key,size_t maxlen,LiVESList * cache)4966 char *get_val_from_cached_list(const char *key, size_t maxlen, LiVESList * cache) {
4967 // WARNING - contents may be invalid if the underlying file is updated (e.g with set_*_pref())
4968 LiVESList *list = cache;
4969 uint32_t khash = fast_hash(key);
4970 lives_speed_cache_t *speedy;
4971 for (; list; list = list->next) {
4972 speedy = (lives_speed_cache_t *)list->data;
4973 if (khash == speedy->hash && !lives_strcmp(key, speedy->key))
4974 return lives_strndup(speedy->data, maxlen);
4975 }
4976 return NULL;
4977 }
4978
4979
clip_detail_to_string(lives_clip_details_t what,size_t * maxlenp)4980 char *clip_detail_to_string(lives_clip_details_t what, size_t *maxlenp) {
4981 char *key = NULL;
4982
4983 switch (what) {
4984 case CLIP_DETAILS_HEADER_VERSION:
4985 key = lives_strdup("header_version"); break;
4986 case CLIP_DETAILS_BPP:
4987 key = lives_strdup("bpp"); break;
4988 case CLIP_DETAILS_FPS:
4989 key = lives_strdup("fps"); break;
4990 case CLIP_DETAILS_PB_FPS:
4991 key = lives_strdup("pb_fps"); break;
4992 case CLIP_DETAILS_WIDTH:
4993 key = lives_strdup("width"); break;
4994 case CLIP_DETAILS_HEIGHT:
4995 key = lives_strdup("height"); break;
4996 case CLIP_DETAILS_UNIQUE_ID:
4997 key = lives_strdup("unique_id"); break;
4998 case CLIP_DETAILS_ARATE:
4999 key = lives_strdup("audio_rate"); break;
5000 case CLIP_DETAILS_PB_ARATE:
5001 key = lives_strdup("pb_audio_rate"); break;
5002 case CLIP_DETAILS_ACHANS:
5003 key = lives_strdup("audio_channels"); break;
5004 case CLIP_DETAILS_ASIGNED:
5005 key = lives_strdup("audio_signed"); break;
5006 case CLIP_DETAILS_AENDIAN:
5007 key = lives_strdup("audio_endian"); break;
5008 case CLIP_DETAILS_ASAMPS:
5009 key = lives_strdup("audio_sample_size"); break;
5010 case CLIP_DETAILS_FRAMES:
5011 key = lives_strdup("frames"); break;
5012 case CLIP_DETAILS_TITLE:
5013 key = lives_strdup("title"); break;
5014 case CLIP_DETAILS_AUTHOR:
5015 key = lives_strdup("author"); break;
5016 case CLIP_DETAILS_COMMENT:
5017 key = lives_strdup("comment"); break;
5018 case CLIP_DETAILS_KEYWORDS:
5019 key = lives_strdup("keywords"); break;
5020 case CLIP_DETAILS_PB_FRAMENO:
5021 key = lives_strdup("pb_frameno"); break;
5022 case CLIP_DETAILS_CLIPNAME:
5023 key = lives_strdup("clipname"); break;
5024 case CLIP_DETAILS_FILENAME:
5025 key = lives_strdup("filename"); break;
5026 case CLIP_DETAILS_INTERLACE:
5027 key = lives_strdup("interlace"); break;
5028 case CLIP_DETAILS_DECODER_NAME:
5029 key = lives_strdup("decoder"); break;
5030 case CLIP_DETAILS_GAMMA_TYPE:
5031 key = lives_strdup("gamma_type"); break;
5032 default: break;
5033 }
5034 if (maxlenp && *maxlenp == 0) *maxlenp = 256;
5035 return key;
5036 }
5037
5038
get_clip_value(int which,lives_clip_details_t what,void * retval,size_t maxlen)5039 boolean get_clip_value(int which, lives_clip_details_t what, void *retval, size_t maxlen) {
5040 lives_clip_t *sfile = mainw->files[which];
5041 char *lives_header = NULL;
5042 char *val, *key, *tmp;
5043
5044 int retval2 = LIVES_RESPONSE_NONE;
5045
5046 if (!IS_VALID_CLIP(which)) return FALSE;
5047
5048 if (!mainw->hdrs_cache) {
5049 /// ascrap_file now uses a different header name; this is to facilitate diskspace cleanup
5050 /// otherwise it may be wrongly classified as a recoverable clip
5051 /// (here this is largely academic, since the values are only read during crash recovery,
5052 /// and the header should have been cached)
5053 if (which == mainw->ascrap_file) {
5054 lives_header = lives_build_filename(prefs->workdir, mainw->files[which]->handle,
5055 LIVES_ACLIP_HEADER, NULL);
5056 if (!lives_file_test(lives_header, LIVES_FILE_TEST_EXISTS)) {
5057 lives_free(lives_header);
5058 lives_header = NULL;
5059 }
5060 }
5061 if (!lives_header)
5062 lives_header = lives_build_filename(prefs->workdir, mainw->files[which]->handle,
5063 LIVES_CLIP_HEADER, NULL);
5064 if (!sfile->checked_for_old_header) {
5065 struct stat mystat;
5066 time_t old_time = 0, new_time = 0;
5067 char *old_header = lives_build_filename(prefs->workdir, sfile->handle, LIVES_CLIP_HEADER_OLD, NULL);
5068 sfile->checked_for_old_header = TRUE;
5069 if (!lives_file_test(old_header, LIVES_FILE_TEST_EXISTS)) {
5070 if (!stat(old_header, &mystat)) old_time = mystat.st_mtime;
5071 if (!stat(lives_header, &mystat)) new_time = mystat.st_mtime;
5072 if (old_time > new_time) {
5073 sfile->has_old_header = TRUE;
5074 lives_free(lives_header);
5075 return FALSE; // clip has been edited by an older version of LiVES
5076 }
5077 }
5078 lives_free(old_header);
5079 }
5080 }
5081
5082 //////////////////////////////////////////////////
5083 key = clip_detail_to_string(what, &maxlen);
5084
5085 if (!key) {
5086 tmp = lives_strdup_printf("Invalid detail %d requested from file %s", which, lives_header);
5087 LIVES_ERROR(tmp);
5088 lives_free(tmp);
5089 lives_free(lives_header);
5090 return FALSE;
5091 }
5092
5093 if (mainw->hdrs_cache) {
5094 val = get_val_from_cached_list(key, maxlen, mainw->hdrs_cache);
5095 lives_free(key);
5096 if (!val) return FALSE;
5097 } else {
5098 val = (char *)lives_malloc(maxlen);
5099 if (!val) return FALSE;
5100 retval2 = get_pref_from_file(lives_header, key, val, maxlen);
5101 lives_free(lives_header);
5102 lives_free(key);
5103 }
5104
5105 if (retval2 == LIVES_RESPONSE_CANCEL) {
5106 lives_free(val);
5107 return FALSE;
5108 }
5109
5110 switch (what) {
5111 case CLIP_DETAILS_BPP:
5112 case CLIP_DETAILS_WIDTH:
5113 case CLIP_DETAILS_HEIGHT:
5114 case CLIP_DETAILS_ARATE:
5115 case CLIP_DETAILS_ACHANS:
5116 case CLIP_DETAILS_ASAMPS:
5117 case CLIP_DETAILS_FRAMES:
5118 case CLIP_DETAILS_GAMMA_TYPE:
5119 case CLIP_DETAILS_HEADER_VERSION:
5120 *(int *)retval = atoi(val); break;
5121 case CLIP_DETAILS_ASIGNED:
5122 *(int *)retval = 0;
5123 if (sfile->header_version == 0) *(int *)retval = atoi(val);
5124 if (*(int *)retval == 0 && (!strcasecmp(val, "false"))) *(int *)retval = 1; // unsigned
5125 break;
5126 case CLIP_DETAILS_PB_FRAMENO:
5127 *(int *)retval = atoi(val);
5128 if (retval == 0) *(int *)retval = 1;
5129 break;
5130 case CLIP_DETAILS_PB_ARATE:
5131 *(int *)retval = atoi(val);
5132 if (retval == 0) *(int *)retval = sfile->arps;
5133 break;
5134 case CLIP_DETAILS_INTERLACE:
5135 *(int *)retval = atoi(val);
5136 break;
5137 case CLIP_DETAILS_FPS:
5138 *(double *)retval = strtod(val, NULL);
5139 if (*(double *)retval == 0.) *(double *)retval = prefs->default_fps;
5140 break;
5141 case CLIP_DETAILS_PB_FPS:
5142 *(double *)retval = strtod(val, NULL);
5143 if (*(double *)retval == 0.) *(double *)retval = sfile->fps;
5144 break;
5145 case CLIP_DETAILS_UNIQUE_ID:
5146 if (capable->cpu_bits == 32) {
5147 *(uint64_t *)retval = (uint64_t)atoll(val);
5148 } else {
5149 *(uint64_t *)retval = (uint64_t)atol(val);
5150 }
5151 break;
5152 case CLIP_DETAILS_AENDIAN:
5153 *(int *)retval = atoi(val) * 2; break;
5154 case CLIP_DETAILS_TITLE:
5155 case CLIP_DETAILS_AUTHOR:
5156 case CLIP_DETAILS_COMMENT:
5157 case CLIP_DETAILS_CLIPNAME:
5158 case CLIP_DETAILS_KEYWORDS:
5159 lives_snprintf((char *)retval, maxlen, "%s", val);
5160 break;
5161 case CLIP_DETAILS_FILENAME:
5162 case CLIP_DETAILS_DECODER_NAME:
5163 lives_snprintf((char *)retval, maxlen, "%s", (tmp = F2U8(val)));
5164 lives_free(tmp);
5165 break;
5166 default:
5167 lives_free(val);
5168 return FALSE;
5169 }
5170 lives_free(val);
5171 return TRUE;
5172 }
5173
5174
save_clip_value(int which,lives_clip_details_t what,void * val)5175 boolean save_clip_value(int which, lives_clip_details_t what, void *val) {
5176 lives_clip_t *sfile;
5177 char *lives_header;
5178 char *com, *tmp;
5179 char *myval;
5180 char *key;
5181
5182 boolean needs_sigs = FALSE;
5183
5184 THREADVAR(write_failed) = 0;
5185 THREADVAR(com_failed) = FALSE;
5186
5187 if (which == 0 || which == mainw->scrap_file) return FALSE;
5188
5189 if (!IS_VALID_CLIP(which)) return FALSE;
5190
5191 sfile = mainw->files[which];
5192
5193 /// ascrap_file now uses a different header name; this is to facilitate diskspace cleanup
5194 /// otherwise it may be wrongly classified as a recoverable clip
5195 if (which == mainw->ascrap_file)
5196 lives_header = lives_build_filename(prefs->workdir, sfile->handle, LIVES_ACLIP_HEADER, NULL);
5197 else
5198 lives_header = lives_build_filename(prefs->workdir, sfile->handle, LIVES_CLIP_HEADER, NULL);
5199
5200 key = clip_detail_to_string(what, NULL);
5201
5202 if (!key) {
5203 tmp = lives_strdup_printf("Invalid detail %d added for file %s", which, lives_header);
5204 LIVES_ERROR(tmp);
5205 lives_free(tmp);
5206 lives_free(lives_header);
5207 return FALSE;
5208 }
5209
5210 switch (what) {
5211 case CLIP_DETAILS_BPP:
5212 myval = lives_strdup_printf("%d", *(int *)val);
5213 break;
5214 case CLIP_DETAILS_FPS:
5215 if (!sfile->ratio_fps) myval = lives_strdup_printf("%.3f", *(double *)val);
5216 else myval = lives_strdup_printf("%.8f", *(double *)val);
5217 break;
5218 case CLIP_DETAILS_PB_FPS:
5219 if (sfile->ratio_fps && (sfile->pb_fps == sfile->fps))
5220 myval = lives_strdup_printf("%.8f", *(double *)val);
5221 else myval = lives_strdup_printf("%.3f", *(double *)val);
5222 break;
5223 case CLIP_DETAILS_WIDTH:
5224 myval = lives_strdup_printf("%d", *(int *)val); break;
5225 case CLIP_DETAILS_HEIGHT:
5226 myval = lives_strdup_printf("%d", *(int *)val); break;
5227 case CLIP_DETAILS_UNIQUE_ID:
5228 myval = lives_strdup_printf("%"PRIu64, *(uint64_t *)val); break;
5229 case CLIP_DETAILS_ARATE:
5230 myval = lives_strdup_printf("%d", *(int *)val); break;
5231 case CLIP_DETAILS_PB_ARATE:
5232 myval = lives_strdup_printf("%d", *(int *)val); break;
5233 case CLIP_DETAILS_ACHANS:
5234 myval = lives_strdup_printf("%d", *(int *)val); break;
5235 case CLIP_DETAILS_ASIGNED:
5236 if ((*(int *)val) == 1) myval = lives_strdup("true");
5237 else myval = lives_strdup("false");
5238 break;
5239 case CLIP_DETAILS_AENDIAN:
5240 myval = lives_strdup_printf("%d", (*(int *)val) / 2);
5241 break;
5242 case CLIP_DETAILS_ASAMPS:
5243 myval = lives_strdup_printf("%d", *(int *)val); break;
5244 case CLIP_DETAILS_FRAMES:
5245 myval = lives_strdup_printf("%d", *(int *)val); break;
5246 case CLIP_DETAILS_GAMMA_TYPE:
5247 myval = lives_strdup_printf("%d", *(int *)val); break;
5248 case CLIP_DETAILS_INTERLACE:
5249 myval = lives_strdup_printf("%d", *(int *)val); break;
5250 case CLIP_DETAILS_TITLE:
5251 myval = lives_strdup((char *)val); break;
5252 case CLIP_DETAILS_AUTHOR:
5253 myval = lives_strdup((char *)val); break;
5254 case CLIP_DETAILS_COMMENT:
5255 myval = lives_strdup((const char *)val); break;
5256 case CLIP_DETAILS_KEYWORDS:
5257 myval = lives_strdup((const char *)val); break;
5258 case CLIP_DETAILS_PB_FRAMENO:
5259 myval = lives_strdup_printf("%d", *(int *)val); break;
5260 case CLIP_DETAILS_CLIPNAME:
5261 myval = lives_strdup((char *)val); break;
5262 case CLIP_DETAILS_FILENAME:
5263 myval = U82F((const char *)val); break;
5264 case CLIP_DETAILS_DECODER_NAME:
5265 myval = U82F((const char *)val); break;
5266 case CLIP_DETAILS_HEADER_VERSION:
5267 myval = lives_strdup_printf("%d", *(int *)val); break;
5268 default:
5269 return FALSE;
5270 }
5271
5272 if (mainw->clip_header) {
5273 char *keystr_start = lives_strdup_printf("<%s>\n", key);
5274 char *keystr_end = lives_strdup_printf("\n</%s>\n\n", key);
5275 lives_fputs(keystr_start, mainw->clip_header);
5276 lives_fputs(myval, mainw->clip_header);
5277 lives_fputs(keystr_end, mainw->clip_header);
5278 lives_free(keystr_start);
5279 lives_free(keystr_end);
5280 } else {
5281 if (!mainw->signals_deferred) {
5282 set_signal_handlers((SignalHandlerPointer)defer_sigint);
5283 needs_sigs = TRUE;
5284 }
5285 com = lives_strdup_printf("%s set_clip_value \"%s\" \"%s\" \"%s\"", prefs->backend_sync, lives_header, key, myval);
5286 lives_system(com, FALSE);
5287 if (mainw->signal_caught) catch_sigint(mainw->signal_caught);
5288 if (needs_sigs) set_signal_handlers((SignalHandlerPointer)catch_sigint);
5289 lives_free(com);
5290 }
5291
5292 lives_free(lives_header);
5293 lives_free(myval);
5294 lives_free(key);
5295
5296 if (mainw->clip_header && THREADVAR(write_failed) == fileno(mainw->clip_header) + 1) {
5297 THREADVAR(write_failed) = 0;
5298 return FALSE;
5299 }
5300 if (THREADVAR(com_failed)) return FALSE;
5301 return TRUE;
5302 }
5303
5304
get_set_list(const char * dir,boolean utf8)5305 LiVESList *get_set_list(const char *dir, boolean utf8) {
5306 // get list of sets in top level dir
5307 // values will be in filename encoding
5308
5309 LiVESList *setlist = NULL;
5310 DIR *tldir, *subdir;
5311 struct dirent *tdirent, *subdirent;
5312 char *subdirname;
5313
5314 if (!dir) return NULL;
5315
5316 tldir = opendir(dir);
5317
5318 if (!tldir) return NULL;
5319
5320 lives_set_cursor_style(LIVES_CURSOR_BUSY, NULL);
5321 lives_widget_process_updates(LIVES_MAIN_WINDOW_WIDGET);
5322
5323 while (1) {
5324 tdirent = readdir(tldir);
5325
5326 if (!tdirent) {
5327 closedir(tldir);
5328 lives_set_cursor_style(LIVES_CURSOR_NORMAL, NULL);
5329 return setlist;
5330 }
5331
5332 if (tdirent->d_name[0] == '.'
5333 && (!tdirent->d_name[1] || tdirent->d_name[1] == '.')) continue;
5334
5335 subdirname = lives_build_filename(dir, tdirent->d_name, NULL);
5336 subdir = opendir(subdirname);
5337
5338 if (!subdir) {
5339 lives_free(subdirname);
5340 continue;
5341 }
5342
5343 while (1) {
5344 subdirent = readdir(subdir);
5345 if (!subdirent) break;
5346
5347 if (!strcmp(subdirent->d_name, "order")) {
5348 if (!utf8)
5349 setlist = lives_list_append(setlist, lives_strdup(tdirent->d_name));
5350 else
5351 setlist = lives_list_append(setlist, F2U8(tdirent->d_name));
5352 break;
5353 }
5354 }
5355 lives_free(subdirname);
5356 closedir(subdir);
5357 }
5358 }
5359
5360
check_for_ratio_fps(double fps)5361 boolean check_for_ratio_fps(double fps) {
5362 boolean ratio_fps;
5363 char *test_fps_string1 = lives_strdup_printf("%.3f00000", fps);
5364 char *test_fps_string2 = lives_strdup_printf("%.8f", fps);
5365
5366 if (strcmp(test_fps_string1, test_fps_string2)) {
5367 // got a ratio
5368 ratio_fps = TRUE;
5369 } else {
5370 ratio_fps = FALSE;
5371 }
5372 lives_free(test_fps_string1);
5373 lives_free(test_fps_string2);
5374
5375 return ratio_fps;
5376 }
5377
5378
get_ratio_fps(const char * string)5379 double get_ratio_fps(const char *string) {
5380 // return a ratio (8dp) fps from a string with format num:denom
5381 double fps;
5382 char *fps_string;
5383 char **array = lives_strsplit(string, ":", 2);
5384 int num = atoi(array[0]);
5385 int denom = atoi(array[1]);
5386 lives_strfreev(array);
5387 fps = (double)num / (double)denom;
5388 fps_string = lives_strdup_printf("%.8f", fps);
5389 fps = lives_strtod(fps_string, NULL);
5390 lives_free(fps_string);
5391 return fps;
5392 }
5393
5394
remove_trailing_zeroes(double val)5395 char *remove_trailing_zeroes(double val) {
5396 int i;
5397 double xval = val;
5398
5399 if (val == (int)val) return lives_strdup_printf("%d", (int)val);
5400 for (i = 0; i <= 16; i++) {
5401 xval *= 10.;
5402 if (xval == (int)xval) return lives_strdup_printf("%.*f", i, val);
5403 }
5404 return lives_strdup_printf("%.*f", i, val);
5405 }
5406
5407
get_signed_endian(boolean is_signed,boolean little_endian)5408 uint32_t get_signed_endian(boolean is_signed, boolean little_endian) {
5409 // asigned TRUE == signed, FALSE == unsigned
5410
5411 if (is_signed) {
5412 if (little_endian) {
5413 return 0;
5414 } else {
5415 return AFORM_BIG_ENDIAN;
5416 }
5417 } else {
5418 if (!is_signed) {
5419 if (little_endian) {
5420 return AFORM_UNSIGNED;
5421 } else {
5422 return AFORM_UNSIGNED | AFORM_BIG_ENDIAN;
5423 }
5424 }
5425 }
5426 return AFORM_UNKNOWN;
5427 }
5428
5429
get_token_count(const char * string,int delim)5430 size_t get_token_count(const char *string, int delim) {
5431 size_t pieces = 1;
5432 if (!string) return 0;
5433 if (delim <= 0 || delim > 255) return 1;
5434
5435 while ((string = strchr(string, delim)) != NULL) {
5436 pieces++;
5437 string++;
5438 }
5439 return pieces;
5440 }
5441
5442
get_nth_token(const char * string,const char * delim,int pnumber)5443 char *get_nth_token(const char *string, const char *delim, int pnumber) {
5444 char **array;
5445 char *ret = NULL;
5446 register int i;
5447 if (pnumber < 0 || pnumber >= get_token_count(string, (int)delim[0])) return NULL;
5448 array = lives_strsplit(string, delim, pnumber + 1);
5449 for (i = 0; i < pnumber; i++) {
5450 if (i == pnumber) ret = array[i];
5451 else lives_free(array[i]);
5452 }
5453 lives_free(array);
5454 return ret;
5455 }
5456
5457
lives_utf8_strcasecmp(const char * s1,const char * s2)5458 int lives_utf8_strcasecmp(const char *s1, const char *s2) {
5459 // ignore case
5460 char *s1u = lives_utf8_casefold(s1, -1);
5461 char *s2u = lives_utf8_casefold(s2, -1);
5462 int ret = lives_strcmp(s1u, s2u);
5463 lives_free(s1u);
5464 lives_free(s2u);
5465 return ret;
5466 }
5467
5468
lives_utf8_strcmp(const char * s1,const char * s2)5469 LIVES_GLOBAL_INLINE int lives_utf8_strcmp(const char *s1, const char *s2) {
5470 return lives_utf8_collate(s1, s2);
5471 }
5472
5473
lives_list_sort_alpha(LiVESList * list,boolean fwd)5474 LIVES_GLOBAL_INLINE LiVESList *lives_list_sort_alpha(LiVESList * list, boolean fwd) {
5475 /// stable sort, so input list should NOT be freed
5476 /// handles utf-8 strings
5477 return lives_list_sort_with_data(list, lives_utf8_strcmpfunc, LIVES_INT_TO_POINTER(fwd));
5478 }
5479
5480
5481 #define BSIZE (8)
5482 #define INITSIZE 32
5483
subst(const char * xstring,const char * from,const char * to)5484 char *subst(const char *xstring, const char *from, const char *to) {
5485 // return a string with all occurrences of from replaced with to
5486 // return value should be freed after use
5487 char *ret = lives_calloc(INITSIZE, BSIZE);
5488 uint64_t ubuff = 0;
5489 char *buff;
5490
5491 const size_t fromlen = strlen(from);
5492 const size_t tolen = strlen(to);
5493 const size_t tolim = BSIZE - tolen;
5494
5495 size_t match = 0;
5496 size_t xtolen = tolen;
5497 size_t bufil = 0;
5498 size_t retfil = 0;
5499 size_t retsize = INITSIZE;
5500 size_t retlimit = retsize - BSIZE;
5501
5502 buff = (char *)&ubuff;
5503
5504 for (char *cptr = (char *)xstring; *cptr; cptr++) {
5505 if (*cptr == from[match++]) {
5506 if (match == fromlen) {
5507 match = 0;
5508 if (bufil > tolim) xtolen = BSIZE - bufil;
5509 lives_memcpy(buff + bufil, to, xtolen);
5510 if ((bufil += xtolen) == BSIZE) {
5511 if (retfil > retlimit) {
5512 ret = lives_recalloc(ret, retsize * 2, retsize, BSIZE);
5513 retsize *= 2;
5514 retlimit = (retsize - 1) * BSIZE;
5515 }
5516 lives_memcpy(ret + retfil, buff, BSIZE);
5517 retfil += BSIZE;
5518 bufil = 0;
5519 if (xtolen < tolen) {
5520 lives_memcpy(buff, to + xtolen, tolen - xtolen);
5521 bufil += tolen - xtolen;
5522 xtolen = tolen;
5523 }
5524 }
5525 }
5526 continue;
5527 }
5528 if (--match > 0) {
5529 xtolen = match;
5530 if (bufil > BSIZE - match) xtolen = BSIZE - bufil;
5531 lives_memcpy(buff + bufil, from, xtolen);
5532 if ((bufil += xtolen) == BSIZE) {
5533 if (retfil > retlimit) {
5534 ret = lives_recalloc(ret, retsize * 2, retsize, BSIZE);
5535 retsize *= 2;
5536 retlimit = (retsize - 1) * BSIZE;
5537 }
5538 lives_memcpy(ret + retfil, buff, BSIZE);
5539 retfil += BSIZE;
5540 bufil = 0;
5541 if (xtolen < fromlen) {
5542 lives_memcpy(buff, from + xtolen, fromlen - xtolen);
5543 bufil += fromlen - xtolen;
5544 xtolen = tolen;
5545 }
5546 }
5547 match = 0;
5548 }
5549 buff[bufil] = *cptr;
5550 if (++bufil == BSIZE) {
5551 if (retfil > retlimit) {
5552 ret = lives_recalloc(ret, retsize * 2, retsize, BSIZE);
5553 retsize *= 2;
5554 retlimit = (retsize - 1) * BSIZE;
5555 }
5556 lives_memcpy(ret + retfil, buff, BSIZE);
5557 retfil += BSIZE;
5558 bufil = 0;
5559 }
5560 }
5561
5562 if (bufil) {
5563 if (retsize > retlimit) {
5564 ret = lives_recalloc(ret, retsize + 1, retsize, BSIZE);
5565 retsize++;
5566 }
5567 lives_memcpy(ret + retfil, buff, bufil);
5568 retfil += bufil;
5569 }
5570 if (match) {
5571 if (retsize > retlimit) {
5572 ret = lives_recalloc(ret, retsize + 1, retsize, BSIZE);
5573 retsize++;
5574 }
5575 lives_memcpy(ret + retsize, from, match);
5576 retfil += match;
5577 }
5578 ret[retfil++] = 0;
5579 retsize *= BSIZE;
5580
5581 if (retsize - retfil > (retsize >> 2)) {
5582 char *tmp = lives_malloc(retfil);
5583 lives_memcpy(tmp, ret, retfil);
5584 lives_free(ret);
5585 return tmp;
5586 }
5587 return ret;
5588 }
5589
5590
insert_newlines(const char * text,int maxwidth)5591 char *insert_newlines(const char *text, int maxwidth) {
5592 // crude formating of strings, ensure a newline after every run of maxwidth chars
5593 // does not take into account for example utf8 multi byte chars
5594
5595 wchar_t utfsym;
5596 char *retstr;
5597
5598 size_t runlen = 0;
5599 size_t req_size = 1; // for the terminating \0
5600 size_t tlen, align = 1;
5601
5602 int xtoffs;
5603
5604 boolean needsnl = FALSE;
5605
5606 register int i;
5607
5608 if (!text) return NULL;
5609
5610 if (maxwidth < 1) return lives_strdup("Bad maxwidth, dummy");
5611
5612 tlen = lives_strlen(text);
5613
5614 xtoffs = mbtowc(NULL, NULL, 0); // reset read state
5615
5616 //pass 1, get the required size
5617 for (i = 0; i < tlen; i += xtoffs) {
5618 xtoffs = mbtowc(&utfsym, &text[i], 4); // get next utf8 wchar
5619 if (!xtoffs) break;
5620 if (xtoffs == -1) {
5621 LIVES_WARN("mbtowc returned -1");
5622 return lives_strdup(text);
5623 }
5624
5625 if (*(text + i) == '\n') runlen = 0; // is a newline (in any encoding)
5626 else {
5627 runlen++;
5628 if (needsnl) req_size++; ///< we will insert a nl here
5629 }
5630
5631 if (runlen == maxwidth) {
5632 if (i < tlen - 1 && (*(text + i + 1) != '\n')) {
5633 // needs a newline
5634 needsnl = TRUE;
5635 runlen = 0;
5636 }
5637 } else needsnl = FALSE;
5638 req_size += xtoffs;
5639 }
5640
5641 xtoffs = mbtowc(NULL, NULL, 0); // reset read state
5642
5643 align = get_max_align(req_size, DEF_ALIGN);
5644
5645 retstr = (char *)lives_calloc(req_size / align, align);
5646 req_size = 0; // reuse as a ptr to offset in retstr
5647 runlen = 0;
5648 needsnl = FALSE;
5649
5650 //pass 2, copy and insert newlines
5651
5652 for (i = 0; i < tlen; i += xtoffs) {
5653 xtoffs = mbtowc(&utfsym, &text[i], 4); // get next utf8 wchar
5654 if (!xtoffs) break;
5655 if (*(text + i) == '\n') runlen = 0; // is a newline (in any encoding)
5656 else {
5657 runlen++;
5658 if (needsnl) {
5659 *(retstr + req_size) = '\n';
5660 req_size++;
5661 }
5662 }
5663
5664 if (runlen == maxwidth) {
5665 if (i < tlen - 1 && (*(text + i + 1) != '\n')) {
5666 // needs a newline
5667 needsnl = TRUE;
5668 runlen = 0;
5669 }
5670 } else needsnl = FALSE;
5671 lives_memcpy(retstr + req_size, &utfsym, xtoffs);
5672 req_size += xtoffs;
5673 }
5674
5675 *(retstr + req_size) = 0;
5676
5677 return retstr;
5678 }
5679
5680
get_hex_digit(const char c)5681 static int get_hex_digit(const char c) {
5682 switch (c) {
5683 case 'a': case 'A': return 10;
5684 case 'b': case 'B': return 11;
5685 case 'c': case 'C': return 12;
5686 case 'd': case 'D': return 13;
5687 case 'e': case 'E': return 14;
5688 case 'f': case 'F': return 15;
5689 default: return c - 48;
5690 }
5691 }
5692
5693
hextodec(const char * string)5694 LIVES_GLOBAL_INLINE int hextodec(const char *string) {
5695 int tot = 0;
5696 for (char c = *string; c; c = *(++string)) tot = (tot << 4) + get_hex_digit(c);
5697 return tot;
5698 }
5699
5700
is_writeable_dir(const char * dir)5701 boolean is_writeable_dir(const char *dir) {
5702 // return FALSE if we cannot create / write to dir
5703 // dir should be in locale encoding
5704 // WARNING: this will actually create the directory (since we dont know if its parents are needed)
5705
5706 struct statvfs sbuf;
5707 if (!lives_file_test(dir, LIVES_FILE_TEST_IS_DIR)) {
5708 lives_mkdir_with_parents(dir, capable->umask);
5709 if (!lives_file_test(dir, LIVES_FILE_TEST_IS_DIR)) {
5710 return FALSE;
5711 }
5712 }
5713
5714 // use statvfs to get fs details
5715 if (statvfs(dir, &sbuf) == -1) return FALSE;
5716 if (sbuf.f_flag & ST_RDONLY) return FALSE;
5717 return TRUE;
5718 }
5719
5720
lives_make_writeable_dir(const char * newdir)5721 boolean lives_make_writeable_dir(const char *newdir) {
5722 /// create a directory (including parents)
5723 /// and ensure we can actually write to it
5724 int ret = lives_mkdir_with_parents(newdir, capable->umask);
5725 int myerrno = errno;
5726 if (!check_dir_access(newdir, TRUE)) {
5727 // abort if we cannot create the new subdir
5728 if (myerrno == EINVAL) {
5729 LIVES_ERROR("Could not write to directory");
5730 } else LIVES_ERROR("Could not create directory");
5731 LIVES_ERROR(newdir);
5732 THREADVAR(com_failed) = FALSE;
5733 return FALSE;
5734 } else {
5735 if (ret != -1) {
5736 LIVES_DEBUG("Created directory");
5737 LIVES_DEBUG(newdir);
5738 }
5739 }
5740 return TRUE;
5741 }
5742
5743
get_interp_value(short quality,boolean low_for_mt)5744 LIVES_GLOBAL_INLINE LiVESInterpType get_interp_value(short quality, boolean low_for_mt) {
5745 if ((mainw->is_rendering || (mainw->multitrack && mainw->multitrack->is_rendering)) && !mainw->preview_rendering)
5746 return LIVES_INTERP_BEST;
5747 if (low_for_mt && mainw->multitrack) return LIVES_INTERP_FAST;
5748 if (quality <= PB_QUALITY_LOW) return LIVES_INTERP_FAST;
5749 else if (quality == PB_QUALITY_MED) return LIVES_INTERP_NORMAL;
5750 return LIVES_INTERP_BEST;
5751 }
5752
5753
5754 #define BL_LIM 128
buff_to_list(const char * buffer,const char * delim,boolean allow_blanks,boolean strip)5755 LIVES_GLOBAL_INLINE LiVESList *buff_to_list(const char *buffer, const char *delim, boolean allow_blanks, boolean strip) {
5756 LiVESList *list = NULL;
5757 int pieces = get_token_count(buffer, delim[0]);
5758 char *buf, **array = lives_strsplit(buffer, delim, pieces);
5759 boolean biglist = pieces >= BL_LIM;
5760 for (int i = 0; i < pieces; i++) {
5761 if (array[i]) {
5762 if (strip) buf = lives_strstrip(array[i]);
5763 else buf = array[i];
5764 if (*buf || allow_blanks) {
5765 if (biglist) list = lives_list_prepend(list, lives_strdup(buf));
5766 else list = lives_list_append(list, lives_strdup(buf));
5767 }
5768 }
5769 }
5770 lives_strfreev(array);
5771 if (biglist && list) return lives_list_reverse(list);
5772 return list;
5773 }
5774
5775
lives_list_append_unique(LiVESList * xlist,const char * add)5776 LIVES_GLOBAL_INLINE LiVESList *lives_list_append_unique(LiVESList * xlist, const char *add) {
5777 LiVESList *list = xlist, *listlast = NULL;
5778 while (list) {
5779 listlast = list;
5780 if (!lives_utf8_strcasecmp((const char *)list->data, add)) return xlist;
5781 list = list->next;
5782 }
5783 list = lives_list_append(listlast, lives_strdup(add));
5784 if (!xlist) return list;
5785 return xlist;
5786 }
5787
5788
lives_list_move_to_first(LiVESList * list,LiVESList * item)5789 LIVES_GLOBAL_INLINE LiVESList *lives_list_move_to_first(LiVESList * list, LiVESList * item) {
5790 // move item to first in list
5791 LiVESList *xlist = item;
5792 if (xlist == list || !xlist) return list;
5793 if (xlist->prev) xlist->prev->next = xlist->next;
5794 if (xlist->next) xlist->next->prev = xlist->prev;
5795 xlist->prev = NULL;
5796 if ((xlist->next = list) != NULL) list->prev = xlist;
5797 return xlist;
5798 }
5799
5800
lives_list_delete_string(LiVESList * list,const char * string)5801 LiVESList *lives_list_delete_string(LiVESList * list, const char *string) {
5802 // remove string from list, using strcmp
5803
5804 LiVESList *xlist = list;
5805 for (; xlist; xlist = xlist->next) {
5806 if (!lives_utf8_strcasecmp((char *)xlist->data, string)) {
5807 lives_free((livespointer)xlist->data);
5808 if (xlist->prev) xlist->prev->next = xlist->next;
5809 else list = xlist;
5810 if (xlist->next) xlist->next->prev = xlist->prev;
5811 xlist->next = xlist->prev = NULL;
5812 lives_list_free(xlist);
5813 return list;
5814 }
5815 }
5816 return list;
5817 }
5818
5819
lives_list_copy_strings(LiVESList * list)5820 LIVES_GLOBAL_INLINE LiVESList *lives_list_copy_strings(LiVESList * list) {
5821 // copy a list, copying the strings too
5822 LiVESList *xlist = NULL, *olist = list;
5823 while (olist) {
5824 xlist = lives_list_prepend(xlist, lives_strdup((char *)olist->data));
5825 olist = olist->next;
5826 }
5827 return lives_list_reverse(xlist);
5828 }
5829
5830
string_lists_differ(LiVESList * alist,LiVESList * blist)5831 boolean string_lists_differ(LiVESList * alist, LiVESList * blist) {
5832 // compare 2 lists of strings and see if they are different (ignoring ordering)
5833 // for long lists this would be quicker if we sorted the lists first; however this function
5834 // is designed to deal with short lists only
5835
5836 LiVESList *plist, *rlist = blist;
5837
5838 if (lives_list_length(alist) != lives_list_length(blist)) return TRUE; // check the simple case first
5839
5840 // run through alist and see if we have a mismatch
5841
5842 plist = alist;
5843 while (plist) {
5844 LiVESList *qlist = rlist;
5845 boolean matched = TRUE;
5846 while (qlist) {
5847 if (!(lives_utf8_strcasecmp((char *)plist->data, (char *)qlist->data))) {
5848 if (matched) rlist = qlist->next;
5849 break;
5850 }
5851 matched = FALSE;
5852 qlist = qlist->next;
5853 }
5854 if (!qlist) return TRUE;
5855 plist = plist->next;
5856 }
5857
5858 // since both lists were of the same length, there is no need to check blist
5859
5860 return FALSE;
5861 }
5862
5863