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