-
Notifications
You must be signed in to change notification settings - Fork 340
/
movie.c
2857 lines (2713 loc) · 156 KB
/
movie.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*--------------------------------------------------------------------
*
* Copyright (c) 1991-2024 by the GMT Team (https://www.generic-mapping-tools.org/team.html)
* See LICENSE.TXT file for copying and redistribution conditions.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; version 3 or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* Contact info: www.generic-mapping-tools.org
*--------------------------------------------------------------------*/
/*
* Author: Paul Wessel
* Date: 1-Jan-2018
* Version: 6 API
*
* Brief synopsis: gmt movie automates the generation of animations
*
* movie automates the main frame loop and much of the machinery needed
* to script a movie sequence. It allows for an optional background
* and foreground layer to be specified via separate modern mode scripts and
* a single frame-script that uses special variables to make slightly
* different plots for each frame. The user only needs to compose these
* simple one-plot scripts and then movie takes care of the automation,
* processing to PNG, assembly of a movie or animated GIF, and cleanup.
* Frame scripts are run in parallel without need for OpenMP, etc.
* Optionally, you can supply a title page script and request fading of
* the title page and/or the main animation. The fore-, back-ground, and
* title page scripts can also just be ready-to-use PostScripts plot of
* correct canvas size.
*
* Note 1: movie has options -L and -P that let each frame plot have an
* overlay of one or more movie labels and one or more sets of movie progress
* indicators. Like tags in subplots (e.g., "a)"), these must be plotted
* ON TOP OF everything else plotted in each frame. When movie is run it
* does not have the brains to figure that out, but it knows that these items,
* if specified, must be plotted at the end, yet the options are given to
* movie. Here is how I implemented this: This black magic is similar to
* what happens in subplot (see the Note in subplot.c first) but there are
* differences. movie creates a bunch of parameter files and a master
* script, and then we launch that master script with an frame number that lets
* it read the corresponding parameters set. Many of the items in the parameter
* file are things that change from frame to frame and may have originated in
* the time file (-T). For instance, we may have a shell assignment of a variable
* like MOVIE_COL0=-77.2085847092. This may be a quantity that the master
* script can use to plot a string or use to get a color via a CPT, for instance.
* That is nice, but how about labels? Well, this is the problem: We want those
* labels to just happen, not make the user write pstext calls using something
* like a MOVIE_WORD0="Time is 64.5". If it worked that way, why would there be
* a -L (or -P) option if you have to do all the work anyway? So, we want movie
* to automatically set these labels, just like it works for subplot tags.
* The solution chosen for this is write this content as comments.
* As an example, the parameter file for frame 29 may have this comment:
*
* # MOVIE_L: L|0.1|4.7|0.5|0|9|0.0416667|0.0416667|-|-|-|-|20p,Helvetica,black|29
*
* or in DOS
*
* REM MOVIE_L: L|0.1|4.7|0.5|0|9|0.0416667|0.0416667|-|-|-|-|20p,Helvetica,black|29
*
* Here, we only have one movie label [-L is repeatable so we could have more].
* In this case, we want to place the frame string "29". The rest is information gmt needs
* to know what to do: placement, offsets, font, etc. OK, then the rest of the frame script
* composed by the user (which knows nothing of the above) happens. Unbeknownst to
* the user, her script is actually embellished by movie in various ways. For instance,
* we call gmt figure to define the plot format before the user's commands are appended.
* When gmt figure runs we end up calling gmt_add_figure (gmt_init.c) and it actually
* is passed a special option -I<parameterfile>. If gmt figure is given this special option
* we get its value and learn (1) that gmt figure is called from a movie script and (2)
* that we have labels to place. Now, we extract all such labels (here just 1) from
* that parameter file. These labels are then written to a file under the session directory
* called gmt.movie_labels. Only the current frame process will find that file since each
* frame is run in separate session directories.
* If you used -P then the exact same thing happens for movie progress bars and there will
* be a file called gmt.movie_prog_indicators created. So -L -P lead to one or two files
* being created in the session directory before the first gmt command after gmt figure
* starts. From here on it is very similar to the subplot story: In gmt_plotinit, we
* check if these files exist, and if they do we build a PostScript function called
* PSL_movie_label_completion [for labels] or PSL_movie_prog_indicator_completion [for
* progress indicators]. These are used a bit differently from the PSL_plot_completion
* function for subplots since there are many panels and finishing one panel is not
* necessarily the end of the plot. However, for the movie embellishments, we know
* that when gmt end comes and calls PSL_endplot, it is time to execute these two
* PostScript functions, should they exist. Once completed, they are redefined as
* NULL functions. Unlike PSL_plot_completion, the movie functions are more complicated
* and may plot more than one label and more than one progress indicator.
*/
#include "gmt_dev.h"
#include "longopt/movie_inc.h"
#ifdef WIN32
#include <windows.h>
#endif
#include "gmt_gsformats.h"
#define THIS_MODULE_CLASSIC_NAME "movie"
#define THIS_MODULE_MODERN_NAME "movie"
#define THIS_MODULE_LIB "core"
#define THIS_MODULE_PURPOSE "Create animation sequences and movies"
#define THIS_MODULE_KEYS "<D("
#define THIS_MODULE_NEEDS ""
#define THIS_MODULE_OPTIONS "-Vf"
#define MOVIE_PREFLIGHT 0
#define MOVIE_POSTFLIGHT 1
#define MOVIE_WAIT_TO_CHECK 10000 /* In microseconds, so 0.01 seconds */
#define MOVIE_PAUSE_A_SEC 1000000 /* In microseconds, so 1 seconds */
#define MOVIE_RASTER_EXTENSION "png" /* Fixed raster format extension */
#define MOVIE_DEBUG_FORMAT ",ps" /* Comma is intentional since we append to a list of formats */
enum enum_video {
MOVIE_PNG, /* Create PNGs */
MOVIE_GIF, /* Create an animated GIF*/
MOVIE_MP4, /* Create a H.264 MP4 video */
MOVIE_WEBM, /* Create a WebM video */
MOVIE_N_FORMATS}; /* Number of video formats above */
enum enum_label {MOVIE_LABEL_IS_FRAME = 1,
MOVIE_LABEL_IS_ELAPSED,
MOVIE_LABEL_IS_PERCENT,
MOVIE_LABEL_IS_COL_C,
MOVIE_LABEL_IS_COL_T,
MOVIE_LABEL_IS_STRING};
struct MOVIE_ITEM {
struct GMT_FONT font;
char format[GMT_LEN128];
char fill[GMT_LEN64], fill2[GMT_LEN64], sfill[GMT_LEN64];
char pen[GMT_LEN64], pen2[GMT_LEN64];
char kind; /* Either a-f|A-F for progress indicators or L for label */
unsigned int box; /* 1 if rounded text box, 0 if rectangular */
unsigned int mode; /* What type of "time" selected for label */
unsigned int justify; /* Placement location of label or indicator */
unsigned int col; /* Which data column to use for labels (if active) */
unsigned int ne; /* Number of elements in elapsed format */
unsigned int n_labels; /* Number of labels for progress indicator */
double scale; /* Scaling from frame number to elapsed time [1/framerate] */
double width; /* Width of progress indicator */
double x, y; /* Placement of progress indicator or label in inches */
double off[2]; /* Offset from justification point for progress indicators */
double clearance[2]; /* Space between label and text box (if selected) for -L labels */
double soff[2]; /* Space between text box and shaded box background (if selected) for -L labels */
};
/* Control structure for movie */
struct MOVIE_CTRL {
bool animate; /* True if we are making any animated product (GIF, movies) */
struct MOVIE_ITEM item[2][GMT_LEN32]; /* 0 for labels, 1 for progress indicators */
unsigned int n_items[2]; /* 0 for labels, 1 for progress indicators */
bool item_active[2]; /* 0 for labels, 1 for progress indicators */
struct MOVIE_In { /* mainscript (Bourne, Bourne Again, csh, or DOS (bat) script) */
bool active;
enum GMT_enum_script mode;
char *file; /* Name of main script */
FILE *fp; /* Open file pointer to main script */
} In;
struct MOVIE_A { /* -A<audiofile>[+e] */
bool active;
bool exact;
double duration; /* Original length of audio track */
char *file;
} A;
struct MOVIE_C { /* -C<namedcanvas>|<canvas_and_dpu>[+c|i] */
bool active;
double dim[3];
char unit;
char *string;
} C;
struct MOVIE_D { /* -D<displayrate> */
bool active;
double framerate;
} D;
struct MOVIE_E { /* -E<title>[+d<duration>[s]][+f[i|o][<fade>[s]]][+g<fill>] */
bool active;
bool PS; /* true if we got a plot instead of a script */
char *file; /* Name of title script */
char *fill; /* Fade color [black] */
unsigned int duration; /* Total number of frames of title/fade sequence */
unsigned int fade[2]; /* Duration of fade title in, fade title out [none]*/
FILE *fp; /* Open file pointer to title script */
} E;
struct MOVIE_F { /* -F<videoformat>[+l<n>][+o<options>][+s<stride>][+t][+v] - repeatable */
bool active[MOVIE_N_FORMATS];
bool view[MOVIE_N_FORMATS];
bool transparent;
bool loop;
bool skip;
unsigned int loops;
unsigned int stride;
char *format[MOVIE_N_FORMATS];
char *options[MOVIE_N_FORMATS];
char *i_options[MOVIE_N_FORMATS];
} F;
struct MOVIE_G { /* -G<canvasfill>[+p<pen>] */
bool active;
unsigned int mode;
char *fill; /* Canvas constant fill */
char pen[GMT_LEN64]; /* Canvas outline pen */
} G;
struct MOVIE_H { /* -H<scale> */
bool active;
int factor; /* Amount of subpixel rendering */
} H;
struct MOVIE_I { /* -I<includefile> */
bool active;
char *file; /* Name of include script */
FILE *fp; /* Open file pointer to include script */
} I;
struct MOVIE_K { /* -K[+f[i|o][<fade>[s]]][+g<fill>][+p[i|o]] */
bool active;
bool preserve[2]; /* Preserve first and last frames */
char *fill; /* Fade color [black] */
unsigned int fade[2]; /* Duration of movie in and movie out fades [none]*/
} K;
struct MOVIE_L { /* Repeatable: -L[e|f|c#|t#|s<string>][+c<clearance>][+f<font>][+g<fill>][+h[<dx>/<dy>/][<shade>]][+j<justify>][+o<offset>][+p<pen>][+r][+t<fmt>][+s<scl>] */
bool active;
} L;
struct MOVIE_M { /* -M[<frame>|f|l|m][,format][+r<dpu>][+v] */
bool active;
bool exit;
bool dpu_set;
bool view;
unsigned int update; /* 1 = set middle, 2 = set last frame */
unsigned int frame; /* Frame selected as master frame */
double dpu;
char *format; /* Plot format for master frame */
} M;
struct MOVIE_N { /* -N<movieprefix> */
bool active;
char *prefix; /* Movie prefix and also name of working directory (but see -W) */
} N;
struct MOVIE_P { /* Repeatable: -P[<kind>][+w<width>][+p<pen1>][+P<pen2>][+g<fill1>][+G<fill2>][+o<offset>][+j<justify>][+a[e|f|c#|t#|s<string>][+t<fmt>][+s<scl>] */
bool active;
} P;
struct MOVIE_Q { /* -Q[s] */
bool active;
bool scripts;
} Q;
struct MOVIE_S { /* -Sb|f<script>|<psfile> */
bool active;
bool PS; /* true if we got a plot instead of a script */
char *file; /* Name of script or PostScript file */
FILE *fp; /* Open file pointer to script */
} S[2];
struct MOVIE_T { /* -T<n_frames>|<min>/<max/<inc>[+n]|<timefile>[+p<precision>][+s<frame>][+w[<str>]] */
bool active;
bool split; /* true means we must split any trailing text in to words, using separators in <str> [" \t"] */
unsigned int n_frames; /* Total number of frames */
unsigned int start_frame; /* First frame [0] */
unsigned int precision; /* Decimals used in making unique frame tags */
char sep[GMT_LEN8]; /* word separator(s) */
char *file; /* timefile name */
} T;
struct MOVIE_W { /* -W<workingdirectory> */
bool active;
char *dir; /* Alternative working directory than implied by -N */
} W;
struct MOVIE_Z { /* -Z[s] */
bool active; /* Delete temporary files when completed */
bool delete; /* Also delete all files including the mainscript and anything passed via -E, -I, -S */
} Z;
struct MOVIE_x { /* -x[[-]<ncores>] */
bool active;
int n_threads;
} x;
};
struct MOVIE_STATUS {
/* Used to monitor the start, running, and completion of frame jobs running in parallel */
bool started; /* true if frame job has started */
bool completed; /* true if PNG has been successfully produced */
};
static void *New_Ctrl (struct GMT_CTRL *GMT) { /* Allocate and initialize a new control structure */
struct MOVIE_CTRL *C;
C = gmt_M_memory (GMT, NULL, 1, struct MOVIE_CTRL);
C->C.unit = 'c'; /* c for SI units */
C->D.framerate = 24.0; /* 24 frames/sec */
C->F.loops = 1; /* No loop, just play once */
C->F.options[MOVIE_WEBM] = strdup ("-crf 10 -b:v 1.2M"); /* Default WebM options for now */
strcpy (C->T.sep, "\t "); /* Any white space */
C->x.n_threads = GMT->parent->n_cores; /* Use all cores available unless -x is set */
return (C);
}
static void Free_Ctrl (struct GMT_CTRL *GMT, struct MOVIE_CTRL *C) { /* Deallocate control structure */
gmt_M_unused (GMT);
if (!C) return;
gmt_M_str_free (C->In.file);
gmt_M_str_free (C->A.file);
gmt_M_str_free (C->C.string);
gmt_M_str_free (C->E.fill);
gmt_M_str_free (C->M.format);
for (unsigned int k = 0; k < MOVIE_N_FORMATS; k++) {
gmt_M_str_free (C->F.format[k]);
gmt_M_str_free (C->F.options[k]);
}
gmt_M_str_free (C->G.fill);
gmt_M_str_free (C->I.file);
gmt_M_str_free (C->K.fill);
gmt_M_str_free (C->N.prefix);
gmt_M_str_free (C->S[MOVIE_PREFLIGHT].file);
gmt_M_str_free (C->S[MOVIE_POSTFLIGHT].file);
gmt_M_str_free (C->T.file);
gmt_M_str_free (C->W.dir);
gmt_M_free (GMT, C);
}
/*! -x[[-]<ncores>] parsing needed but here not related to OpenMP etc - it is just a local option */
GMT_LOCAL int movie_parse_x_option (struct GMT_CTRL *GMT, struct MOVIE_CTRL *Ctrl, char *arg) {
if (!arg) return (GMT_PARSE_ERROR); /* -x requires a non-NULL argument */
if (arg[0])
Ctrl->x.n_threads = atoi (arg);
if (Ctrl->x.n_threads == 0) /* Not having any of that. At least one */
Ctrl->x.n_threads = 1;
else if (Ctrl->x.n_threads < 0) /* Meant to reduce the number of threads */
Ctrl->x.n_threads = MAX(GMT->parent->n_cores + Ctrl->x.n_threads, 1); /* Max-n but at least one */
return (GMT_NOERROR);
}
static int usage (struct GMTAPI_CTRL *API, int level) {
const char *name = gmt_show_name_and_purpose (API, THIS_MODULE_LIB, THIS_MODULE_CLASSIC_NAME, THIS_MODULE_PURPOSE);
if (level == GMT_MODULE_PURPOSE) return (GMT_NOERROR);
GMT_Usage (API, 0, "usage: %s <mainscript> -C<canvas>|<width>x<height>x<dpu>[+c|i] -N<prefix> -T<nframes>|<min>/<max>/<inc>[+n]|<timefile>[+p<width>][+s<first>][+w[<str>]|W] "
"-A<audiofile>[+e]] [-D<rate>] [-E<titlepage>[+d[<duration>[s]]][+f[i|o][<fade>[s]]][+g<fill>]] [-Fgif|mp4|webm|png[+l[<n>]][+i<opts>][+o<opts>][+s<stride>][+t][+v]] "
"[-G[<fill>][+p<pen>]] [-H<scale>] [-I<includefile>] [-K[+f[i|o][<fade>[s]]][+g<fill>][+p[i|o]]] [-L<labelinfo>] [-M[<frame>|f|m|l,][<format>][+r<dpu>][+v]] "
"[-P<progressinfo>] [-Q[s]] [-Sb<background>] [-Sf<foreground>] [%s] [-W[<dir>]] [-Z[s]] [%s] [-x[[-]<n>]] [%s]\n", name, GMT_V_OPT, GMT_f_OPT, GMT_PAR_OPT);
if (level == GMT_SYNOPSIS) return (GMT_MODULE_SYNOPSIS);
GMT_Message (API, GMT_TIME_NONE, " REQUIRED ARGUMENTS:\n");
GMT_Usage (API, 1, "\n<mainscript>");
GMT_Usage (API, -2, "<mainscript> is the main GMT modern script that builds a single frame image.");
GMT_Usage (API, 1, "\n-C<canvas>|<width>x<height>x<dpu>[+c|i]");
GMT_Usage (API, -2, "Specify canvas. Choose either a known named canvas or set custom dimensions.");
GMT_Usage (API, -2, "%s Recognized 16:9-ratio names and associated dimensions:", GMT_LINE_BULLET);
/* Using GMT_Message here for verbatim text and alignment since GMT_Usage will eat repeated spaces */
GMT_Message (API, GMT_TIME_NONE, " name: pixel size: canvas (SI): dpc: canvas (US): dpi:\n");
GMT_Message (API, GMT_TIME_NONE, " ---------------------------------------------------------------------------\n");
GMT_Message (API, GMT_TIME_NONE, " 4320p: 7680 x 4320 24 x 13.5 cm 320.0000 9.6 x 5.4 inch 800.0000\n");
GMT_Message (API, GMT_TIME_NONE, " 2160p: 3840 x 2160 24 x 13.5 cm 160.0000 9.6 x 5.4 inch 400.0000\n");
GMT_Message (API, GMT_TIME_NONE, " 1080p: 1920 x 1080 24 x 13.5 cm 80.0000 9.6 x 5.4 inch 200.0000\n");
GMT_Message (API, GMT_TIME_NONE, " 720p: 1280 x 720 24 x 13.5 cm 53.3333 9.6 x 5.4 inch 133.3333\n");
GMT_Message (API, GMT_TIME_NONE, " 540p: 960 x 540 24 x 13.5 cm 40.0000 9.6 x 5.4 inch 100.0000\n");
GMT_Message (API, GMT_TIME_NONE, " 480p: 854 x 480 24 x 13.5 cm 35.5833 9.6 x 5.4 inch 88.9583\n");
GMT_Message (API, GMT_TIME_NONE, " 360p: 640 x 360 24 x 13.5 cm 26.6667 9.6 x 5.4 inch 66.6667\n");
GMT_Message (API, GMT_TIME_NONE, " 240p: 426 x 240 24 x 13.5 cm 17.7500 9.6 x 5.4 inch 44.3750\n");
GMT_Usage (API, -2, "%s Recognized 4:3-ratio names and associated dimensions:", GMT_LINE_BULLET);
GMT_Message (API, GMT_TIME_NONE, " name: pixel size: canvas (SI): dpc: canvas (US): dpi:\n");
GMT_Message (API, GMT_TIME_NONE, " ---------------------------------------------------------------------------\n");
GMT_Message (API, GMT_TIME_NONE, " uxga: 1600 x 1200 24 x 18 cm 66.6667 9.6 x 7.2 inch 166.6667\n");
GMT_Message (API, GMT_TIME_NONE, " sxga+: 1400 x 1050 24 x 18 cm 58.3333 9.6 x 7.2 inch 145.8333\n");
GMT_Message (API, GMT_TIME_NONE, " xga: 1024 x 768 24 x 18 cm 42.6667 9.6 x 7.2 inch 106.6667\n");
GMT_Message (API, GMT_TIME_NONE, " svga: 800 x 600 24 x 18 cm 33.3333 9.6 x 7.2 inch 83.3333\n");
GMT_Message (API, GMT_TIME_NONE, " dvd: 640 x 480 24 x 18 cm 26.6667 9.6 x 7.2 inch 66.6667\n");
GMT_Usage (API, -2, "Note: uhd-2 and 8k can be used for 4320p, uhd and 4k for 2160p, and fhd or hd for 1080p. "
"Current PROJ_LENGTH_UNIT determines if you get SI or US canvas dimensions and dpu. "
"Alternatively, set a custom canvas with dimensions and dots-per-unit manually by "
"providing <width>x<height>x<dpu> (e.g., 15cx10cx50, 6ix6ix100, etc. Alternatively, give pixel dimensions and a modifier:");
GMT_Usage (API, 3, "+c Dimensions are pixels and dpu is pixels per cm.");
GMT_Usage (API, 3, "+i Dimensions are pixels and dpu is pixels per inch.");
GMT_Usage (API, 1, "\n-N<prefix>");
GMT_Usage (API, -2, "Set the <prefix> used for movie files and directory names. "
"The directory cannot already exist; see -Z to remove such directories at the end.");
GMT_Usage (API, 1, "\n-T<nframes>|<min>/<max>/<inc>[+n]|<timefile>[+p<width>][+s<first>][+w[<str>]|W]");
GMT_Usage (API, -2, "Set number of frames, create times from <min>/<max>/<inc>[+n] or give file with frame-specific information. "
"If <min>/<max>/<inc> is used then +n is used to indicate that <inc> is in fact number of frames instead. "
"If <timefile> does not exist it must be created by the background script given via -Sb.");
GMT_Usage (API, 3, "+p Append number of digits used in creating the frame tags [automatic].");
GMT_Usage (API, 3, "+s Append <first> to change the value of the first frame [0].");
GMT_Usage (API, 3, "+w Let trailing text in <timefile> be split into individual word variables. "
"We use space or TAB as separators; append <str> to set custom characters as separators instead.");
GMT_Usage (API, 3, "+W Same as +w but only use TAB as separator.");
GMT_Message (API, GMT_TIME_NONE, "\n OPTIONAL ARGUMENTS:\n");
GMT_Usage (API, 1, "\n-A<audiofile>[+e]");
GMT_Usage (API, -2, "Merge in an audio track, starting at first frame.");
GMT_Usage (API, 3, "+e Adjust length of audio track to fit the length of the movie exactly.");
GMT_Usage (API, 1, "\n-D<rate>");
GMT_Usage (API, -2, "Set movie display frame rate in frames/second [24].");
GMT_Usage (API, 1, "\n-E<titlepage>[+d[<duration>[s]]][+f[i|o][<fade>[s]]][+g<fill>]");
GMT_Usage (API, -2, "Give name of optional <titlepage> script to build title page displayed before the animation [no title page]. "
"Alternatively, give PostScript file of correct canvas size that will be the title page.");
GMT_Usage (API, 3, "+d Set duration of the title frames (append s for seconds) [4s].");
GMT_Usage (API, 3, "+f Fade in and out over the title via black [1s]. "
"Use +fi and/or +fo to set unequal fade lengths or to just select one of them.");
GMT_Usage (API, 3, "+g Select another terminal fade fill than black.");
GMT_Usage (API, 1, "\n-Fgif|mp4|webm|png[+l[<n>]][+o<opts>][+s<stride>][+t]");
GMT_Usage (API, -2, "Select the desired video format(s) from available choices. Repeatable:");
GMT_Usage (API, 3, "%s gif: Make and convert PNG frames into an animated GIF.", GMT_LINE_BULLET);
GMT_Usage (API, 3, "%s mp4: Make and convert PNG frames into an MP4 movie.", GMT_LINE_BULLET);
GMT_Usage (API, 3, "%s webm: Make and convert PNG frames into an WebM movie.", GMT_LINE_BULLET);
GMT_Usage (API, 3, "%s png: Just make the PNG frames.", GMT_LINE_BULLET);
GMT_Usage (API, -2, "Note: gif|mp4|webm all imply png. Two modifiers are available for mp4 or webm:");
GMT_Usage (API, 3, "+i Prepend custom FFmpeg encoding options (in quotes) to be applyied only to input the file (e.g. -thread_queue_size) [none].");
GMT_Usage (API, 3, "+o Append custom FFmpeg encoding options (in quotes) [none].");
GMT_Usage (API, 3, "+t Build transparent images [opaque].");
GMT_Usage (API, 3, "+v Open the movie in the default viewer when completed.");
GMT_Usage (API, -2, "Two modifiers are available for gif:");
GMT_Usage (API, 3, "+l Enable looping [no loop]; optionally append number of loops [infinite loop].");
GMT_Usage (API, 3, "+s Set stride: If -Fmp4|webm is also used you may restrict the GIF animation to use every <stride> frame only [all]. "
"<stride> must be taken from the list 2, 5, 10, 20, 50, 100, 200, or 500.");
GMT_Usage (API, -2, "Default is no video products; this requires -M.");
GMT_Usage (API, 1, "\n-G[<fill>][+p<pen>]");
GMT_Usage (API, -2, "Set the canvas background color [none]. Append +p<pen> to draw canvas outline [none].");
GMT_Usage (API, 1, "\n-H<scale>");
GMT_Usage (API, -2, "Temporarily increase <dpu> by <scale>, rasterize, then downsample [no downsampling]. "
"Stabilizes sub-pixel changes between frames, such as moving text and lines.");
GMT_Usage (API, 1, "\n-I<includefile>");
GMT_Usage (API, -2, "Include a script file to be inserted into the movie_init.sh script [none]. "
"Used to add constant variables needed by all movie scripts.");
GMT_Usage (API, 1, "\n-K[+f[i|o][<fade>[s]]][+g<fill>][+p[i|o]]");
GMT_Usage (API, -2, "Fade in and out over the main animation via black [no fading]:");
GMT_Usage (API, 3, "+f Set duration of fading in frames (append s for seconds) [1s]. "
"Use +fi and/or +fo to set unequal fade times or to just select one of them.");
GMT_Usage (API, 3, "+p Preserve all frames by repeated use of first and last frames during fade sequence [Fade all frames at start and end of movie]. "
"Append i or o to only have the in- or out-fade repeat a single frame [both ends].");
GMT_Usage (API, 3, "+g Select terminal fade fill [black].");
GMT_Usage (API, 1, "\n-L<labelinfo>");
GMT_Usage (API, -2, "Automatic labeling of frames; repeatable (max 32). Places chosen label at the frame perimeter:");
GMT_Usage (API, 3, "e: Select elapsed time as label. Append +s<scl> to set time in sec per frame [1/<framerate>].");
GMT_Usage (API, 3, "f: Select running frame numbers as label.");
GMT_Usage (API, 3, "p: Select percent of completion as label.");
GMT_Usage (API, 3, "s: Select fixed text <string> as label.");
GMT_Usage (API, 3, "c: Use the value in appended column <col> of <timefile> (first column is 0).");
GMT_Usage (API, 3, "t: Use word number <col> from the trailing text in <timefile> (first word is 0).");
GMT_Usage (API, -2, "Several modifiers control label appearance:");
GMT_Usage (API, 3, "+c Set clearance <dx>[/<dy>] between label and surrounding box. Only "
"used if +g or +p are set. Append units {%s} or %% of fontsize [%d%%].", GMT_DIM_UNITS_DISPLAY, GMT_TEXT_CLEARANCE);
GMT_Usage (API, 3, "+f Set the font used for the label [%s].", gmt_putfont (API->GMT, &API->GMT->current.setting.font_tag));
GMT_Usage (API, 3, "+g Fill the label textbox with specified <fill> [no fill].");
GMT_Usage (API, 3, "+h Set offset label textbox shade color fill (requires +g also). "
"Append <dx>/<dy> to change offset [%gp/%gp] and/or <shade> to change the shade [gray50].", GMT_FRAME_CLEARANCE, -GMT_FRAME_CLEARANCE);
GMT_Usage (API, 3, "+j Specify where the label should be plotted [TL].");
GMT_Usage (API, 3, "+o Offset label by <dx>[/<dy>] in direction implied by <justify> [%d%% of font size].", GMT_TEXT_OFFSET);
GMT_Usage (API, 3, "+p Draw the outline of the textbox using selected pen [no outline].");
GMT_Usage (API, 3, "+r Select a rounded rectangular textbox (requires +g or +p) [rectangular].");
GMT_Usage (API, 3, "+t Provide a C-format statement to be used with the item selected [none].");
GMT_Usage (API, 1, "\n-M[<frame>|f|m|l,][<format>][+r<dpu>][+v]");
GMT_Usage (API, -2, "Create a master frame plot as well; append comma-separated frame number [0] and format [pdf]. "
"Master plot will be named <prefix>.<format> and placed in the current directory. "
"Instead of frame number you can specify f(irst), m(iddle), or l(last) frame.");
GMT_Usage (API, 3, "+r For a raster master frame you may optionally select another <dpu> [same as movie].");
GMT_Usage (API, 3, "+v Open the master frame in the default viewer.");
GMT_Usage (API, 1, "\n-P<progressinfo>");
GMT_Usage (API, -2, "Automatic plotting of progress indicator(s); repeatable (max 32). Places chosen indicator at frame perimeter. "
"Append desired indicator (a-f) [a] and consult the movie documentation for which attributes are needed:");
GMT_Usage (API, 3, "+a Add annotations via e|f|p|c<col> (see -L for details).");
GMT_Usage (API, 3, "+f Set font attributes for the label [%s].", gmt_putfont (API->GMT, &API->GMT->current.setting.font_annot[GMT_SECONDARY]));
GMT_Usage (API, 3, "+G Set background (static) color fill for indicator [Depends in indicator selected].");
GMT_Usage (API, 3, "+g Set foreground (moving) color fill for indicator [Depends in indicator selected].");
GMT_Usage (API, 3, "+j Specify where the indicator should be plotted [TR for circles, BC for axes].");
GMT_Usage (API, 3, "+o Offset indicator by <dx>[/<dy>] in direction implied by <justify> [%d%% of font size].", GMT_TEXT_OFFSET);
GMT_Usage (API, 3, "+P Set background (static) pen for indicator [Depends in indicator selected].");
GMT_Usage (API, 3, "+p Set foreground (moving) pen for indicator [Depends in indicator selected].");
GMT_Usage (API, 3, "+s Compute elapsed time as frame counter times appended <scale> [no scaling].");
GMT_Usage (API, 3, "+t Provide a C-format statement to be used with the item selected [none].");
GMT_Usage (API, 3, "+w Specify indicator size [5%% of max canvas dimension for circles, 60%% for axes].");
GMT_Usage (API, 1, "\n-Q[s]");
GMT_Usage (API, -2, "Debugging: Leave all intermediate files and directories behind for inspection. "
"Append s to only create the work scripts but none will be executed (except for background script).");
GMT_Usage (API, 1, "\n-Sb<background>");
GMT_Usage (API, -2, "Append name of background GMT modern script that may compute "
"files needed by <mainscript> and/or build a static background plot layer. "
"If a plot is generated then the script must be in GMT modern mode. "
"Alternatively, give PostScript file of correct canvas size that will be the background.");
GMT_Usage (API, 1, "\n-Sf<foreground>");
GMT_Usage (API, -2, "Append name of foreground GMT modern mode script which will "
"build a static foreground plot overlay appended to all frames. "
"Alternatively, give PostScript file of correct canvas size that will be the foreground.");
GMT_Option (API, "V");
GMT_Usage (API, 1, "\n-W[<dir>]");
GMT_Usage (API, -2, "Give <dir> where temporary files will be built [Default <dir> = <prefix> set by -N]. "
"If <dir> is not given we create one in the system temp directory named <prefix> (from -N).");
GMT_Usage (API, 1, "\n-Z[s]");
GMT_Usage (API, -2, "Erase directory <prefix> after converting to movie [leave directory with PNGs alone]. "
"Append s to also delete all input scripts (mainscript and any files via -E, -I, -S).");
GMT_Option (API, "f");
/* Number of threads (re-purposed from -x in GMT_Option since this local option is always available and we are not using OpenMP) */
GMT_Usage (API, 1, "\n-x[[-]<n>]");
GMT_Usage (API, -2, "Limit the number of cores used in frame generation [Default uses all %d cores]. "
"If <n> is negative then we select (%d - <n>) cores (or at least 1). Note: 1 core is used by movie itself.", API->n_cores, API->n_cores);
GMT_Option (API, ".");
return (GMT_MODULE_USAGE);
}
GMT_LOCAL void movie_set_default_width (struct GMT_CTRL *GMT, struct MOVIE_CTRL *Ctrl, struct MOVIE_ITEM *I) {
double def_width = (strchr ("defDEF", I->kind) && (I->justify == PSL_ML || I->justify == PSL_MR)) ? Ctrl->C.dim[GMT_Y] : Ctrl->C.dim[GMT_X];
if (I->width > 0.0) return;
/* Assign default widths */
I->width = (strchr ("abcABC", I->kind)) ? 0.05 * def_width : 0.6 * def_width;
GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "No width given for progress indicator %c. Setting width to %g%c.\n", I->kind, I->width, Ctrl->C.unit);
if (Ctrl->C.unit == 'c') I->width /= 2.54; else if (Ctrl->C.unit == 'p') I->width /= 72.0; /* Now in inches */
}
GMT_LOCAL unsigned int movie_get_item_default (struct GMT_CTRL *GMT, struct MOVIE_CTRL *Ctrl, char *arg, struct MOVIE_ITEM *I) {
unsigned int n_errors = 0;
/* Default progress indicator: Pie-wedge with different colors */
movie_set_default_width (GMT, Ctrl, I); /* Initialize progress indicator width if not set */
if (I->fill[0] == '-') /* Give default color for foreground wedge */
strcpy (I->fill, "lightred");
if (gmt_get_modifier (arg, 'G', I->fill2) && I->fill2[0]) { /* Found +G<fill> */
struct GMT_FILL fill; /* Only used to make sure fill is given with correct syntax */
if (gmt_getfill (GMT, I->fill2, &fill)) n_errors++;
}
if (I->fill2[0] == '-') /* Give default fixed circle color */
strcpy (I->fill2, "lightgreen");
if (I->kind == 'A') {
GMT_Report (GMT->parent, GMT_MSG_ERROR, "Progress indicator a does not place any labels. modifier +a ignored\n");
I->kind = 'a';
}
return (n_errors);
}
GMT_LOCAL unsigned int movie_get_item_two_pens (struct GMT_CTRL *GMT, struct MOVIE_CTRL *Ctrl, char *arg, struct MOVIE_ITEM *I) {
unsigned int n_errors = 0;
struct GMT_PEN pen; /* Only used to make sure any pen is given with correct syntax */
char kind = tolower (I->kind);
gmt_M_memset (&pen, 1, struct GMT_PEN);
movie_set_default_width (GMT, Ctrl, I); /* Initialize progress indicator width if not set */
if (I->pen[0] == '-') { /* Set pen for foreground (changing) feature */
switch (kind) {
case 'b': sprintf (I->pen, "%gp,blue", 0.1 * rint (I->width * 1.5 * 72.0)); break; /* Give default moving ring pen width (15% of width) and blue color */
case 'c': sprintf (I->pen, "%gp,red", 0.1 * rint (I->width * 0.5 * 72.0)); break; /* Give default moving math angle pen width (5% of width) and red color */
case 'd': sprintf (I->pen, "%gp,yellow", 0.1 * MIN (irint (I->width * 0.05 * 72.0), 80)); break; /* Give default crossbar pen width (0.5% of length) and color yellow */
case 'e': sprintf (I->pen, "%gp,red", 0.1 * MIN (rint (I->width * 0.25 * 72.0), 80)); break; /* Give a variable pen thickness >= 8p in red */
}
}
if (gmt_get_modifier (arg, 'P', I->pen2) && I->pen2[0]) { /* Found +P<pen> */
if (gmt_getpen (GMT, I->pen2, &pen)) n_errors++;
}
if (I->pen2[0] == '-') { /* Set pen for background (static) feature */
switch (kind) {
case 'b': sprintf (I->pen2, "%gp,lightblue", 0.1 * rint (I->width * 1.5 * 72.0)); break; /* Give default static ring pen width (15% of width) and color lightblue */
case 'c': sprintf (I->pen2, "%gp,darkred,-", 0.1 * rint (I->width * 0.1 * 72.0)); break; /* Give default static ring dashed pen width (1% of width) and color darkred */
case 'd': sprintf (I->pen2, "%gp,black", 0.1 * MIN (irint (I->width * 0.25 * 72.0), 80)); break; /* Give a variable pen thickness <= 8p in black */
case 'e': sprintf (I->pen2, "%gp,lightgreen", 0.1 * MIN (irint (I->width * 0.25 * 72.0), 80)); break;/* Give a variable pen thickness <= 8p in lightgreen */
}
}
return (n_errors);
}
GMT_LOCAL unsigned int movie_get_item_pen_fill (struct GMT_CTRL *GMT, struct MOVIE_CTRL *Ctrl, char *arg, struct MOVIE_ITEM *I) {
unsigned int n_errors = 0;
gmt_M_unused (GMT);
gmt_M_unused (arg);
/* Default progress indicator: line and filled symbol */
movie_set_default_width (GMT, Ctrl, I); /* Initialize progress indicator width if not set */
if (I->pen2[0] == '-') /* Give default static line pen thickness <= 3p in black */
sprintf (I->pen2, "%gp,black", 0.1 * MIN (irint (I->width * 0.15 * 72.0), 30));
if (I->fill[0] == '-') /* Give default moving triangle the red color */
strcpy (I->fill, "red"); /* Give default moving color */
return (n_errors);
}
GMT_LOCAL unsigned int movie_parse_common_item_attributes (struct GMT_CTRL *GMT, char option, char *arg, struct MOVIE_ITEM *I) {
/* Initialize and parse the modifiers for item attributes for both labels and progress indicators */
unsigned int n_errors = 0;
char *c = NULL, *t = NULL, string[GMT_LEN128] = {""}, placement[4] = {""};
char *allowed_mods = (option == 'L') ? "cfghjoprst" : "acfgGjopPstw"; /* Tolerate +G+P for progress bars even if not processed in this function */
struct GMT_FILL fill; /* Only used to make sure any fill is given with correct syntax */
struct GMT_PEN pen; /* Only used to make sure any pen is given with correct syntax */
I->fill[0] = I->fill2[0] = I->sfill[0] = I->pen[0] = I->pen2[0] = '-'; /* No fills or pens set yet */
I->off[GMT_X] = I->off[GMT_Y] = 0.01 * GMT_TEXT_OFFSET * GMT->current.setting.font_tag.size / PSL_POINTS_PER_INCH; /* 20% offset of TAG font size */
I->clearance[GMT_X] = I->clearance[GMT_Y] = 0.01 * GMT_TEXT_CLEARANCE * GMT->current.setting.font_tag.size / PSL_POINTS_PER_INCH; /* 15% of TAG font size */
if (I->kind == 'L') /* Default label placement is top left of canvas */
I->justify = PSL_TL;
else if (strchr ("abc", I->kind)) /* Default circular progress indicator placement is top right of canvas */
I->justify = PSL_TR;
else /* Default line progress indicator placement is bottom center of canvas */
I->justify = PSL_BC;
/* The modifiers for labels and progress bars are not identical, so we need to be specific here */
/* Check for:
* 1) common modifiers [+c<dx/dy>][+f<fmt>][+g<fill>][+j<justify>][+o<dx/dy>][+p<pen>][+s<scl>][+t<fmt>]
* 2) the extra [+h[<dx>/<dy>/][<shade>]][+r] for labels
* 3) the extra +a<labelinfo>+w<width> for progress bars
*/
if (gmt_validate_modifiers (GMT, arg, option, allowed_mods, GMT_MSG_ERROR)) n_errors++;
if (gmt_get_modifier (arg, 'c', string) && string[0]) /* Clearance for a text box */
if (gmt_get_pair (GMT, string, GMT_PAIR_DIM_DUP, I->clearance) < 0) n_errors++;
if (gmt_get_modifier (arg, 'f', string)) { /* Gave a separate font for labeling */
if (!string[0]) {
GMT_Report (GMT->parent, GMT_MSG_ERROR, "Option -%c: +f not given any font\n", option);
n_errors++;
}
else
n_errors += gmt_getfont (GMT, string, &(I->font));
}
if (gmt_get_modifier (arg, 'g', I->fill) && I->fill[0]) { /* Primary fill color */
if (gmt_getfill (GMT, I->fill, &fill)) n_errors++;
}
if (gmt_get_modifier (arg, 'r', string)) /* Rounded text box */
I->box = 4;
if (gmt_get_modifier (arg, 'h', string)) { /* Shaded text box fill color +h[<dx>/<dy>/][<shade>] */
strcpy (I->sfill, "gray50"); /* Default shade color */
I->soff[GMT_X] = GMT->session.u2u[GMT_PT][GMT_INCH] * GMT_FRAME_CLEARANCE; /* Default is 4p */
I->soff[GMT_Y] = -I->soff[GMT_X]; /* Set the shadow offsets [default is (4p, -4p)] */
I->box++; /* Rectangular shade = 1 and rounded rectangular shade = 5 */
if (I->fill[0] == '-') {
GMT_Report (GMT->parent, GMT_MSG_ERROR, "Option -%c: Modifier +h requires +g as well\n", option);
n_errors++;
}
else if (string[0]) { /* Gave an argument to +h */
char txt_a[GMT_LEN64] = {""}, txt_b[GMT_LEN64] = {""}, txt_c[GMT_LEN64] = {""};
int n = sscanf (string, "%[^/]/%[^/]/%s", txt_a, txt_b, txt_c);
if (n == 1) /* Just got a new fill */
strcpy (I->sfill, txt_a);
else if (n == 2) { /* Just got a new offset */
if (gmt_get_pair (GMT, string, GMT_PAIR_DIM_DUP, I->soff) < 0) n_errors++;
}
else if (n == 3) { /* Got offset and fill */
I->soff[GMT_X] = gmt_M_to_inch (GMT, txt_a);
I->soff[GMT_Y] = gmt_M_to_inch (GMT, txt_b);
strcpy (I->sfill, txt_c);
}
else n_errors++;
}
if (gmt_getfill (GMT, I->sfill, &fill)) n_errors++;
}
if (gmt_get_modifier (arg, 'j', placement)) { /* Placement on canvas for this item */
if (I->kind == 'L') /* Default label placement is top left of canvas */
gmt_just_validate (GMT, placement, "TL");
else if (strchr ("abc", I->kind)) /* Default circular progress indicator placement is top right of canvas */
gmt_just_validate (GMT, placement, "TR");
else /* Default line progress indicator placement is bottom center of canvas */
gmt_just_validate (GMT, placement, "BC");
I->justify = gmt_just_decode (GMT, placement, PSL_NO_DEF);
}
if (gmt_get_modifier (arg, 'o', string)) /* Offset of refpoint */
if (gmt_get_pair (GMT, string, GMT_PAIR_DIM_DUP, I->off) < 0) n_errors++;
if (gmt_get_modifier (arg, 'p', I->pen) && I->pen[0]) { /* Primary pen */
if (gmt_getpen (GMT, I->pen, &pen)) n_errors++;
}
if (gmt_get_modifier (arg, 't', I->format) && !I->format[0]) {
GMT_Report (GMT->parent, GMT_MSG_ERROR, "Option -%c: +t not given any format\n", option);
n_errors++;
}
if (I->kind == 'L') { /* This is a label item with type the first letter in arg */
if ((c = strchr (arg, '+'))) c[0] = '\0'; /* Chop off all modifiers for now */
t = arg; /* Start of required label type information */
I->mode = MOVIE_LABEL_IS_FRAME; /* Frame number is default for -L just in case nothing is set */
}
else if ((t = strstr (arg, "+a"))) /* A progress indicator wants to be labeled */
t += 2; /* Start of optional progress indicator time information following +a */
if (t) { /* Parse attributes specific to the movie labels */
switch (t[0]) { /* We got some */
case 'c': I->mode = MOVIE_LABEL_IS_COL_C; I->col = atoi (&t[1]); break;
case 't': I->mode = MOVIE_LABEL_IS_COL_T; I->col = atoi (&t[1]); break;
case 'e': I->mode = MOVIE_LABEL_IS_ELAPSED; break;
case 'p': I->mode = MOVIE_LABEL_IS_PERCENT; break;
case 's': I->mode = MOVIE_LABEL_IS_STRING; strncpy (I->format, &t[1], GMT_LEN128-1); break;
case 'f': case '\0': I->mode = MOVIE_LABEL_IS_FRAME; break; /* Frame number is default */
default: /* Not recognized argument */
GMT_Report (GMT->parent, GMT_MSG_ERROR, "Option -%c: Select label flag e|f|p|s, c<col> or t<col>\n", option);
n_errors++;
break;
}
I->kind = toupper ((int)I->kind); /* Use upper case B-F to indicate that labeling is requested */
I->n_labels = (strchr ("EF", I->kind)) ? 2 : 1;
if (I->mode == MOVIE_LABEL_IS_ELAPSED && (gmt_get_modifier (arg, 's', string) || gmt_get_modifier (arg, 'z', string))) {
/* Changed from +z to +s but we do backwards compatibility here */
/* Gave frame time length-scale */
I->scale = atof (string);
}
}
if (c) c[0] = '+'; /* Restore the modifiers */
return (n_errors);
}
GMT_LOCAL unsigned int movie_get_n_frames (struct GMT_CTRL *GMT, char *txt, double framerate, char *def) {
/* Convert user argument in frames or seconds to frames */
char *p = (!txt || !txt[0]) ? def : txt; /* Get frames or times in seconds */
double fval = atof (p); /* Get frames or times in seconds */
gmt_M_unused (GMT);
if (strchr (p, 's')) fval *= framerate; /* Convert from seconds to nearest number of frames */
return (urint (fval));
}
static int parse (struct GMT_CTRL *GMT, struct MOVIE_CTRL *Ctrl, struct GMT_OPTION *options) {
/* This parses the options provided to movie and sets parameters in CTRL.
* Any GMT common options will override values set previously by other commands.
*/
unsigned int n_errors = 0, n_files = 0, k, pos, mag, T, frames;
int n;
bool do_view = false;
char txt_a[GMT_LEN32] = {""}, txt_b[GMT_LEN32] = {""}, arg[GMT_LEN64] = {""}, p[GMT_LEN256] = {""};
char *c = NULL, *s = NULL, *o = NULL, *io = NULL, string[GMT_LEN128] = {""};
double width = 24.0, height16x9 = 13.5, height4x3 = 18.0, dpu = 160.0; /* SI values for dimensions and dpu */
struct GMT_FILL fill; /* Only used to make sure any fill is given with correct syntax */
struct GMT_PEN pen; /* Only used to make sure any pen is given with correct syntax */
struct MOVIE_ITEM *I = NULL;
struct GMT_OPTION *opt = NULL;
struct GMTAPI_CTRL *API = GMT->parent;
if (GMT->current.setting.proj_length_unit == GMT_INCH) { /* Switch from SI to US dimensions in inches given format names */
width = 9.6; height16x9 = 5.4; height4x3 = 7.2; dpu = 400.0;
Ctrl->C.unit = 'i';
}
for (opt = options; opt; opt = opt->next) { /* Look for -D first since frame rate is needed when parsing -E */
if (opt->option == 'D') { /* Display frame rate of movie */
Ctrl->D.active = true;
Ctrl->D.framerate = atof (opt->arg);
}
}
for (opt = options; opt; opt = opt->next) {
switch (opt->option) {
case '<': /* Input file */
if (n_files++ > 0) break;
n_errors += gmt_get_required_file (GMT, opt->arg, opt->option, 0, GMT_IS_DATASET, GMT_IN, GMT_FILE_REMOTE, &(Ctrl->In.file));
break;
case 'A': /* Audio track (but also backwards compatible Animated GIF [Deprecated]) */
n_errors += gmt_M_repeated_module_option (API, Ctrl->A.active);
if (gmt_M_compat_check (GMT, 6)) { /* GMT6 compatibility allows backwards compatible -A[+l<loop>][+s<stride>] for animated GIF */
if ((c = strstr (opt->arg, "+e")) == NULL && (c = gmt_first_modifier (GMT, opt->arg, "ls"))) { /* Process any deprecated modifiers */
GMT_Report (API, GMT_MSG_COMPAT, "Option -A[+l<loop>][+s<stride>] is deprecated - use -F instead\n");
Ctrl->F.active[MOVIE_GIF] = Ctrl->F.active[MOVIE_PNG] = Ctrl->animate = true; /* Old -A implies -Fpng */
pos = 0; /* Reset to start of new word */
while (gmt_getmodopt (GMT, 'A', c, "ls", &pos, p, &n_errors) && n_errors == 0) {
switch (p[0]) {
case 'l': /* Specify loops */
Ctrl->F.loop = true;
Ctrl->F.loops = (p[1]) ? atoi (&p[1]) : 0;
break;
case 's': /* Specify GIF stride, 2,5,10,20,50,100,200,500 etc. */
Ctrl->F.skip = true;
Ctrl->F.stride = atoi (&p[1]);
mag = urint (pow (10.0, floor (log10 ((double)Ctrl->F.stride))));
k = Ctrl->F.stride / mag;
if (!(k == 1 || k == 2 || k == 5)) {
GMT_Report (API, GMT_MSG_ERROR, "Option -A+s: Allowable strides are 2,5,10,20,50,100,200,500,...\n");
n_errors++;
}
break;
default:
break; /* These are caught in gmt_getmodopt so break is just for Coverity */
}
}
c[0] = '\0';
}
if (Ctrl->F.skip || Ctrl->F.loop) break; /* Processed old deprecated +s or +l modifiers */
}
/* Here we process modern -A<soundfile>[+e] syntax */
if (opt->arg[0]) { /* Get audio and optionally stretch to fit video length */
if ((c = strstr (opt->arg, "+e"))) {
Ctrl->A.exact = true;
c[0] = '\0'; /* Remove modifier */
}
Ctrl->A.file = strdup (opt->arg); /* Get audio file name */
if (c) c[0] = '+'; /* Restore modifier */
}
else
n_errors += gmt_default_option_error (GMT, opt);
break;
case 'C': /* Known frame dimension or set a custom canvas size */
n_errors += gmt_M_repeated_module_option (API, Ctrl->C.active);
strncpy (arg, opt->arg, GMT_LEN64-1); /* Get a copy... */
gmt_str_tolower (arg); /* ..so we can make it lower case */
/* 16x9 formats */
if (!strcmp (arg, "8k") || !strcmp (arg, "uhd-2") || !strcmp (arg, "uhd2") || !strcmp (arg, "4320p")) { /* 4320x7680 */
Ctrl->C.dim[GMT_X] = width; Ctrl->C.dim[GMT_Y] = height16x9; Ctrl->C.dim[GMT_Z] = 2.0 * dpu;
}
else if (!strcmp (arg, "4k") || !strcmp (arg, "uhd") || !strcmp (arg, "2160p")) { /* 2160x3840 */
Ctrl->C.dim[GMT_X] = width; Ctrl->C.dim[GMT_Y] = height16x9; Ctrl->C.dim[GMT_Z] = dpu;
}
else if (!strcmp (arg, "1080p") || !strcmp (arg, "fhd") || !strcmp (arg, "hd")) { /* 1080x1920 */
Ctrl->C.dim[GMT_X] = width; Ctrl->C.dim[GMT_Y] = height16x9; Ctrl->C.dim[GMT_Z] = dpu / 2.0;
}
else if (!strcmp (arg, "720p")) { /* 720x1280 */
Ctrl->C.dim[GMT_X] = width; Ctrl->C.dim[GMT_Y] = height16x9; Ctrl->C.dim[GMT_Z] = dpu / 3.0;
}
else if (!strcmp (arg, "540p")) { /* 540x960 */
Ctrl->C.dim[GMT_X] = width; Ctrl->C.dim[GMT_Y] = height16x9; Ctrl->C.dim[GMT_Z] = dpu / 4.0;
} /* 4x3 formats below */
else if (!strcmp (arg, "480p")) { /* 480x854 */
Ctrl->C.dim[GMT_X] = width; Ctrl->C.dim[GMT_Y] = height16x9; Ctrl->C.dim[GMT_Z] = dpu / 6.0;
}
else if (!strcmp (arg, "360p")) { /* 360x640 */
Ctrl->C.dim[GMT_X] = width; Ctrl->C.dim[GMT_Y] = height16x9; Ctrl->C.dim[GMT_Z] = dpu / 8.0;
}
else if (!strcmp (arg, "240p")) { /* 240x426 */
Ctrl->C.dim[GMT_X] = width; Ctrl->C.dim[GMT_Y] = height16x9; Ctrl->C.dim[GMT_Z] = dpu / 12.0;
} /* Below are 4x3 formats */
else if (!strcmp (arg, "uxga") ) { /* 1200x1600 */
Ctrl->C.dim[GMT_X] = width; Ctrl->C.dim[GMT_Y] = height4x3; Ctrl->C.dim[GMT_Z] = 2.5 * dpu / 6.0;
}
else if (!strcmp (arg, "sxga+") ) { /* 1050x1400 */
Ctrl->C.dim[GMT_X] = width; Ctrl->C.dim[GMT_Y] = height4x3; Ctrl->C.dim[GMT_Z] = 2.1875 * dpu / 6.0;
}
else if (!strcmp (arg, "xga") ) { /* 768x1024 */
Ctrl->C.dim[GMT_X] = width; Ctrl->C.dim[GMT_Y] = height4x3; Ctrl->C.dim[GMT_Z] = 1.6 * dpu / 6.0;
}
else if (!strcmp (arg, "sgva") ) { /* 600x800 */
Ctrl->C.dim[GMT_X] = width; Ctrl->C.dim[GMT_Y] = height4x3; Ctrl->C.dim[GMT_Z] = 1.25 * dpu / 6.0;
}
else if (!strcmp (arg, "dvd")) { /* 480x640 */
Ctrl->C.dim[GMT_X] = width; Ctrl->C.dim[GMT_Y] = height4x3; Ctrl->C.dim[GMT_Z] = dpu / 6.0;
}
else { /* Custom canvas dimensions -C<width>[<unit>]x<height>[<unit>]x<dpu>[+c|i] */
if ((c = strstr (arg, "+c"))) /* Got dimensions in dpc units */
c[0] = '\0'; /* Chop off modifier */
else if ((c = strstr (arg, "+i"))) /* Got dimensions in dpi units */
c[0] = '\0'; /* Chop off modifier */
if (c) { /* Got modifier and sides in dpi or dpc -C<width>x<height>x<dpu>[+c|i] */
if ((n = sscanf (arg, "%[^x]x%[^x]x%lg", txt_a, txt_b, &Ctrl->C.dim[GMT_Z])) != 3) {
GMT_Report (API, GMT_MSG_ERROR, "Option -C: Requires dimensions in pixels and either +c or +i for dots per unit\n");
n_errors++;
}
Ctrl->C.dim[GMT_X] = atof (txt_a);
Ctrl->C.dim[GMT_Y] = atof (txt_b);
if (fabs (Ctrl->C.dim[GMT_X] - irint (Ctrl->C.dim[GMT_X])) > 0 || fabs (Ctrl->C.dim[GMT_Y] - irint (Ctrl->C.dim[GMT_Y])) > 0) {
GMT_Report (API, GMT_MSG_ERROR, "Option -C: When either a +c or +i modifier is used, dimensions must be integer pixels\n");
n_errors++;
}
Ctrl->C.dim[GMT_X] = atof (txt_a) / Ctrl->C.dim[GMT_Z];
Ctrl->C.dim[GMT_Y] = atof (txt_b) / Ctrl->C.dim[GMT_Z];
Ctrl->C.unit = c[1]; /* Save inch or cm unit */
}
else if ((n = sscanf (arg, "%[^x]x%[^x]x%lg", txt_a, txt_b, &Ctrl->C.dim[GMT_Z])) != 3) {
GMT_Report (API, GMT_MSG_ERROR, "Option -C: Requires name of a known format or give width x height x dpu string\n");
n_errors++;
}
else { /* Got three items; let's check */
Ctrl->C.dim[GMT_X] = atof (txt_a);
Ctrl->C.dim[GMT_Y] = atof (txt_b);
if (strchr ("cip", txt_a[strlen(txt_a)-1])) /* Width had recognized unit, set it */
Ctrl->C.unit = txt_a[strlen(txt_a)-1];
else if (strchr ("cip", txt_b[strlen(txt_b)-1])) /* Height had recognized unit, set it instead */
Ctrl->C.unit = txt_b[strlen(txt_b)-1];
else /* Must use GMT default setting as unit */
Ctrl->C.unit = GMT->session.unit_name[GMT->current.setting.proj_length_unit][0];
}
}
break;
case 'D': /* Already processed but need to have a case so we can skip */
break;
case 'E': /* Title/fade sequence */
n_errors += gmt_M_repeated_module_option (API, Ctrl->E.active);
if ((c = gmt_first_modifier (GMT, opt->arg, "dfg"))) { /* Process any modifiers */
pos = 0; /* Reset to start of new word */
while (gmt_getmodopt (GMT, 'E', c, "dfg", &pos, p, &n_errors) && n_errors == 0) {
switch (p[0]) {
case 'd': /* Duration of entire title/fade sequence */
Ctrl->E.duration = movie_get_n_frames (GMT, &p[1], Ctrl->D.framerate, "4s");
break;
case 'f': /* In/out fades */
k = (p[1] && strchr ("io", p[1])) ? 2 : 1; /* Did we get a common fade or different for in/out? */
frames = movie_get_n_frames (GMT, &p[k], Ctrl->D.framerate, "1s"); /* Get fade length in number of frames */
if (k == 1) /* Set equal fade frames */
Ctrl->E.fade[GMT_IN] = Ctrl->E.fade[GMT_OUT] = frames;
else if (p[1] == 'i') /* Set input fade frames */
Ctrl->E.fade[GMT_IN] = frames;
else /* Set output fade frames */
Ctrl->E.fade[GMT_OUT] = frames;
break;
case 'g': /* Change fade color */
if (gmt_getfill (GMT, &p[1], &fill)) /* Bad fill */
n_errors++;
else /* Fill is valid, just copy verbatim */
Ctrl->E.fill = strdup (&p[1]);
break;
default:
break; /* These are caught in gmt_getmodopt so break is just for Coverity */
}
}
c[0] = '\0'; /* Chop off modifiers */
}
if (Ctrl->E.duration == 0) Ctrl->E.duration = movie_get_n_frames (GMT, NULL, Ctrl->D.framerate, "4s");
n_errors += gmt_get_required_file (GMT, opt->arg, opt->option, 0, GMT_IS_DATASET, GMT_IN, GMT_FILE_REMOTE, &(Ctrl->E.file));
if (c) c[0] = '+'; /* Restore modifiers */
break;
case 'F': /* Set movie format and optional FFmpeg options */
if ((c = gmt_first_modifier (GMT, opt->arg, "liostv"))) { /* Process any modifiers */
do_view = false;
pos = 0; /* Reset to start of new word */
o = NULL;
while (gmt_getmodopt (GMT, 'F', c, "liostv", &pos, p, &n_errors) && n_errors == 0) {
switch (p[0]) {
case 'l': /* Specify loops for GIF */
Ctrl->F.loop = true;
Ctrl->F.loops = (p[1]) ? atoi (&p[1]) : 0;
break;
case 'i': /* FFmpeg option for input to pass along */
io = strdup(&p[1]); /* Retain start of encoding options for input for later */
break;
case 'o': /* FFmpeg option to pass along */
o = strdup(&p[1]); /* Retain start of encoding options for later */
break;
case 's': /* Specify GIF stride, 2,5,10,20,50,100,200,500 etc. */
Ctrl->F.skip = true;
Ctrl->F.stride = atoi (&p[1]);
mag = urint (pow (10.0, floor (log10 ((double)Ctrl->F.stride))));
k = Ctrl->F.stride / mag;
if (!(k == 1 || k == 2 || k == 5)) {
GMT_Report (API, GMT_MSG_ERROR, "Option -F+s: Allowable strides are 2,5,10,20,50,100,200,500,...\n");
n_errors++;
}
break;
case 't': /* Transparent images */
Ctrl->F.transparent = true;
break;
case 'v': /* Open video in viewer */
do_view = true;
break;
default:
break; /* These are caught in gmt_getmodopt so break is just for Coverity */
}
}
c[0] = '\0'; /* Chop off modifiers */
}
strncpy (arg, opt->arg, GMT_LEN64-1); /* Get a copy of the args (minus encoding options)... */
gmt_str_tolower (arg); /* ...so we can convert it to lower case for comparisons */
if (!strcmp (opt->arg, "none")) { /* Do not make those PNGs at all, just a master plot */
if (gmt_M_compat_check (GMT, 6)) { /* GMT6 compatibility allows -Fnone */
GMT_Report (API, GMT_MSG_COMPAT, "Option -Fnone is deprecated, it is the default action\n");
Ctrl->M.exit = true;
}
else {
GMT_Report (API, GMT_MSG_ERROR, "Option -F: Unrecognized format %s\n", opt->arg);
n_errors++;
}
break;
}
if (!strcmp (opt->arg, "png")) /* Just make those PNGs */
k = MOVIE_PNG;
else if (!strcmp (opt->arg, "gif")) /* Make animated GIF */
k = MOVIE_GIF;
else if (!strcmp (opt->arg, "mp4")) /* Make a MP4 movie */
k = MOVIE_MP4;
else if (!strcmp (opt->arg, "webm")) /* Make a WebM movie */
k = MOVIE_WEBM;
else if (opt->arg[0]) { /* Gave another argument which is invalid */
GMT_Report (API, GMT_MSG_ERROR, "Option -F: Unrecognized format %s\n", opt->arg);
n_errors++;
break;
}
if (opt->arg[0]) { /* Gave a valid argument */
if (Ctrl->F.active[k]) { /* Can only select a format once */
GMT_Report (API, GMT_MSG_ERROR, "Option -F: Format %s already selected\n", opt->arg);
n_errors++;
break;
}
/* Here we have a new video format selected */
Ctrl->F.active[k] = true;
Ctrl->F.view[k] = do_view;
if (k != MOVIE_PNG) Ctrl->animate = true;
if (o) { /* Gave specific encoding options */
if (Ctrl->F.options[k]) gmt_M_str_free (Ctrl->F.options[k]); /* Free old setting first */
Ctrl->F.options[k] = o;
}
if (io) { /* Gave specific encoding options */
if (Ctrl->F.i_options[k]) gmt_M_str_free(Ctrl->F.i_options[k]); /* Free old setting first */
Ctrl->F.i_options[k] = io;
}
}
if (c) c[0] = '+'; /* Now we can restore the optional text we chopped off */
break;
case 'G': /* Canvas fill */
n_errors += gmt_M_repeated_module_option (API, Ctrl->G.active);
if ((c = strstr (opt->arg, "+p"))) { /* Gave outline modifier */
if (c[2] && gmt_getpen (GMT, &c[2], &pen)) { /* Bad pen */
gmt_pen_syntax (GMT, 'G', NULL, "+p<pen> sets pen attributes [no outline]", NULL, 0);
n_errors++;
}
else /* Pen is valid, just copy verbatim */
strncpy (Ctrl->G.pen, &c[2], GMT_LEN64);
c[0] = '\0'; /* Chop off options */
Ctrl->G.mode |= 1;
}
if (opt->arg[0]) { /* Gave fill argument */
if (gmt_getfill (GMT, opt->arg, &fill)) /* Bad fill */
n_errors++;
else { /* Fill is valid, just copy verbatim */
Ctrl->G.fill = strdup (opt->arg);