Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Let movie open and play movie if gmt end show is used #7456

Merged
merged 10 commits into from May 16, 2023
14 changes: 8 additions & 6 deletions doc/rst/source/movie.rst
Expand Up @@ -18,13 +18,13 @@ Synopsis
|-T|\ *nframes*\|\ *min*/*max*/*inc*\ [**+n**]\|\ *timefile*\ [**+p**\ *width*]\ [**+s**\ *first*]\ [**+w**\ [*str*]\|\ **W**]
[ |-D|\ *displayrate* ]
[ |-E|\ *titlepage*\ [**+d**\ [*duration*\ [**s**]]][**+f**\ [**i**\|\ **o**]\ [*fade*\ [**s**]]]\ [**+g**\ *fill*] ]
[ |-F|\ *gif*\|\ *mp4*\|\ *webm*\|\ *png*\ [**+l**\ [*n*]][**+o**\ *options*][**+s**\ *stride*][**+t**] ]
[ |-F|\ *gif*\|\ *mp4*\|\ *webm*\|\ *png*\ [**+l**\ [*n*]][**+o**\ *options*][**+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*],[*format*][**+r**\ *dpu*] ]
[ |-M|\ [*frame*],[*format*][**+r**\ *dpu*][**+v**] ]
[ |-P|\ *progress* ]
[ |-Q|\ [**s**] ]
[ |-Sb|\ *background* ]
Expand Down Expand Up @@ -60,7 +60,8 @@ Required Arguments
written using the Bourne shell (.sh), the Bourne again shell (.bash), the csh (.csh)
or DOS batch language (.bat). The script language is inferred from the file extension
and we build hidden movie scripts using the same language. Parameters that can be accessed
are discussed below.
are discussed below. **Note**: If the final **gmt end** statement ends with **show** then
we display the movie master frame (but only if |-M| is active).

.. _-C:

Expand Down Expand Up @@ -153,7 +154,7 @@ Optional Arguments

.. _-F:

**-F**\ *gif*\|\ *mp4*\|\ *webm*\|\ *png*\ [**+l**\ [*n*]][**+o**\ *options*][**+s**\ *stride*][**+t**]
**-F**\ *gif*\|\ *mp4*\|\ *webm*\|\ *png*\ [**+l**\ [*n*]][**+o**\ *options*][**+s**\ *stride*][**+t**][**+v**]
Select a video product. Repeatable to make more than one product. Choose from *gif* (animated GIF),
*mp4* (MPEG-4 movie), *webm* (WebM movie) or just *png* images (implied by all the others). If just
*png* is chosen then no animation will be assembled. No |-F| means no video products are created at
Expand All @@ -166,6 +167,7 @@ Optional Arguments
can limit the frames being used to make a GIF animation by appending *stride* to only use every *stride*
frame, with *stride* being one of a fixed set of strides: 2, 5, 10, 20, 50, 100, 200, and 500.
- **+t** selects generation of transparent PNG images [opaque]; see `Transparency`_ for more details.
- **+v** opens the movie in the default movie viewer.

.. _-G:

Expand Down Expand Up @@ -248,12 +250,12 @@ Optional Arguments

.. _-M:

**-M**\ [*frame*\|\ **f**\|\ **m**\|\ **l**],[*format*][**+r**\ *dpu*]
**-M**\ [*frame*\|\ **f**\|\ **m**\|\ **l**],[*format*][**+r**\ *dpu*][**+v**]
In addition to making the animation sequence, select a single master frame [0] for a cover page. The master frame will
be written to the current directory with name *prefix.format*, where *format* can be one of the
graphics extensions from the allowable graphics :ref:`formats <tbl-formats>` [pdf]. Instead of a frame number
we also recognize the codes **f**\ irst, **m**\ iddle, and **l**\ ast frame. **Note**: For raster frame formats
you may optionally specify an alternate *dpu* of that frame via the **+r** modifier [same dpu as the movie frames].
you may optionally specify an alternate *dpu* of that frame via the **+r** modifier [same dpu as the movie frames]. Finally, to open the master plot in the default viewer, append **+v**.

.. _-P:

Expand Down
113 changes: 85 additions & 28 deletions src/movie.c
Expand Up @@ -181,8 +181,9 @@ struct MOVIE_CTRL {
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] - repeatable */
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;
Expand Down Expand Up @@ -215,10 +216,11 @@ struct MOVIE_CTRL {
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>] */
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;
Expand Down Expand Up @@ -322,8 +324,8 @@ 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> -N<prefix> -T<nframes>|<min>/<max>/<inc>[+n]|<timefile>[+p<width>][+s<first>][+w[<str>]|W] "
"[-D<rate>] [-E<titlepage>[+d[<duration>[s]]][+f[i|o][<fade>[s]]][+g<fill>]] [-Fgif|mp4|webm|png[+l[<n>]][+o<opts>][+s<stride>][+t]] [-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>]] [-P<progressinfo>] [-Q[s]] [-Sb<background>] "
"[-D<rate>] [-E<titlepage>[+d[<duration>[s]]][+f[i|o][<fade>[s]]][+g<fill>]] [-Fgif|mp4|webm|png[+l[<n>]][+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);
Expand Down Expand Up @@ -389,6 +391,7 @@ static int usage (struct GMTAPI_CTRL *API, int level) {
GMT_Usage (API, -2, "Note: gif|mp4|webm all imply png. Two modifiers are available for mp4 or webm:");
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]. "
Expand Down Expand Up @@ -429,11 +432,12 @@ static int usage (struct GMTAPI_CTRL *API, int level) {
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>]");
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. "
"For a raster master frame you may optionally select another <dpu> via +r [same as movie].");
"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:");
Expand Down Expand Up @@ -680,8 +684,9 @@ static int parse (struct GMT_CTRL *GMT, struct MOVIE_CTRL *Ctrl, struct GMT_OPTI

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, string[GMT_LEN128] = {""};
char *c = NULL, *s = NULL, *o = 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 */
Expand Down Expand Up @@ -844,16 +849,18 @@ static int parse (struct GMT_CTRL *GMT, struct MOVIE_CTRL *Ctrl, struct GMT_OPTI
break;

case 'F': /* Set movie format and optional FFmpeg options */
if ((c = gmt_first_modifier (GMT, opt->arg, "lost"))) { /* Process any modifiers */
if ((c = gmt_first_modifier (GMT, opt->arg, "lostv"))) { /* Process any modifiers */
do_view = false;
pos = 0; /* Reset to start of new word */
while (gmt_getmodopt (GMT, 'F', c, "lost", &pos, p, &n_errors) && n_errors == 0) {
o = NULL;
while (gmt_getmodopt (GMT, 'F', c, "lostv", &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 'o': /* FFmpeg option to pass along */
s = strdup (&p[1]); /* Retain start of encoding options for later */
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;
Expand All @@ -868,6 +875,9 @@ static int parse (struct GMT_CTRL *GMT, struct MOVIE_CTRL *Ctrl, struct GMT_OPTI
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 */
}
Expand Down Expand Up @@ -908,10 +918,11 @@ static int parse (struct GMT_CTRL *GMT, struct MOVIE_CTRL *Ctrl, struct GMT_OPTI
}
/* 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 (s) { /* Gave specific encoding options */
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] = s;
Ctrl->F.options[k] = o;
}
}
if (c) c[0] = '+'; /* Now we can restore the optional text we chopped off */
Expand Down Expand Up @@ -1007,24 +1018,36 @@ static int parse (struct GMT_CTRL *GMT, struct MOVIE_CTRL *Ctrl, struct GMT_OPTI

case 'M': /* Create a single frame plot as well as movie (unless -Q is active) */
n_errors += gmt_M_repeated_module_option (API, Ctrl->M.active);
if ((s = strstr (opt->arg, "+r")) ) { /* Gave specific resolution for master frame */
Ctrl->M.dpu = atof (&s[2]);
Ctrl->M.dpu_set = true;
s[0] = '\0'; /* Truncate for now */
if ((c = gmt_first_modifier (GMT, opt->arg, "rv"))) { /* Process any modifiers */
pos = 0; /* Reset to start of new word */
while (gmt_getmodopt (GMT, 'M', c, "rv", &pos, p, &n_errors) && n_errors == 0) {
switch (p[0]) {
case 'r': /* Gave specific resolution for master frame */
Ctrl->M.dpu = atof (&p[2]);
Ctrl->M.dpu_set = true;
break;
case 'v': /* View master frame */
Ctrl->M.view = true;
break;
default:
break; /* These are caught in gmt_getmodopt so break is just for Coverity */
}
}
c[0] = '\0'; /* Chop off modifiers so we just have -M[<frame>|f|l|m][,format] */
}
if ((c = strchr (opt->arg, ',')) ) { /* Gave frame and format */
if (!strncmp (&c[1], "view", 4U)) /* Check for using 'view' to set with GMT_GRAPHICS_FORMAT */
if ((s = strchr (opt->arg, ',')) ) { /* Gave both frame and format */
if (!strncmp (&s[1], "view", 4U)) /* Check for using 'view' to set with GMT_GRAPHICS_FORMAT */
Ctrl->M.format = strdup (gmt_session_format[API->GMT->current.setting.graphics_format]);
else
Ctrl->M.format = strdup (&c[1]);
c[0] = '\0'; /* Chop off format */
Ctrl->M.format = strdup (&s[1]);
s[0] = '\0'; /* Chop off format specification; now we have just -M[<frame>|f|l|m] */
switch (opt->arg[0]) {
case 'f': Ctrl->M.frame = 0; break;
case 'm': Ctrl->M.update = 1; break;
case 'l': Ctrl->M.update = 2; break;
default: Ctrl->M.frame = atoi (opt->arg); break;
}
c[0] = ','; /* Restore format */
s[0] = ','; /* Restore format specification */
}
else if (isdigit (opt->arg[0]) || (strchr ("fml", opt->arg[0]) && opt->arg[1] == '\0')) { /* Gave just a frame, default to PDF format */
Ctrl->M.format = strdup ("pdf");
Expand All @@ -1035,14 +1058,15 @@ static int parse (struct GMT_CTRL *GMT, struct MOVIE_CTRL *Ctrl, struct GMT_OPTI
default: Ctrl->M.frame = atoi (opt->arg); break;
}
}
else if (opt->arg[0]) /* Must be format, with frame = 0 implicit */
if (strchr ("v", opt->arg[0])) /* Check for using 'view' to set with GMT_GRAPHICS_FORMAT */
else if (opt->arg[0]) { /* Must be format only, with frame = 0 implicit */
if (!strncmp ("view", opt->arg, 4U)) /* Check for using 'view' to set with GMT_GRAPHICS_FORMAT */
Ctrl->M.format = strdup (gmt_session_format[API->GMT->current.setting.graphics_format]);
else
Ctrl->M.format = strdup (opt->arg);
}
else /* Default is PDF of frame 0 */
Ctrl->M.format = strdup ("pdf");
if (s) s[0] = '+'; /* Restore */
if (c) c[0] = '+'; /* Restore modifier */
break;

case 'N': /* Movie prefix and directory name */
Expand Down Expand Up @@ -2306,7 +2330,8 @@ EXTERN_MSC int GMT_movie (void *V_API, int mode, void *args) {
fprintf (fp, "\tgmt set PS_MEDIA %g%cx%g%c DIR_DATA \"%s\"\n", Ctrl->C.dim[GMT_X], Ctrl->C.unit, Ctrl->C.dim[GMT_Y], Ctrl->C.unit, datadir);
}
else if (!strstr (line, "#!/")) { /* Skip any leading shell incantation since already placed */
if (strchr (line, '\n') == NULL) strcat (line, "\n"); /* In case the last line misses a newline */
if (gmt_is_gmt_end_show (line)) sprintf (line, "gmt end\n"); /* Eliminate show from gmt end in this script */
else if (strchr (line, '\n') == NULL) strcat (line, "\n"); /* In case the last line misses a newline */
fprintf (fp, "%s", line); /* Just copy the line as is */
}
}
Expand All @@ -2331,7 +2356,11 @@ EXTERN_MSC int GMT_movie (void *V_API, int mode, void *args) {
fprintf (fp, "\tgmt set PS_MEDIA %g%cx%g%c DIR_DATA \"%s\"\n", Ctrl->C.dim[GMT_X], Ctrl->C.unit, Ctrl->C.dim[GMT_Y], Ctrl->C.unit, datadir);
}
else if (!strstr (line, "#!/")) { /* Skip any leading shell incantation since already placed */
if (strchr (line, '\n') == NULL) strcat (line, "\n"); /* In case the last line misses a newline */
if (gmt_is_gmt_end_show (line)) { /* Want to open the master plot and movie at the end */
sprintf (line, "gmt end\n"); /* Eliminate show from gmt end in this script */
Ctrl->M.view = true; /* Backwards compatibility: Use +v modifier instead */
}
else if (strchr (line, '\n') == NULL) strcat (line, "\n"); /* In case the last line misses a newline */
fprintf (fp, "%s", line); /* Just copy the line as is */
}
}
Expand Down Expand Up @@ -2366,6 +2395,15 @@ EXTERN_MSC int GMT_movie (void *V_API, int mode, void *args) {
goto out_of_here;
}
GMT_Report (API, GMT_MSG_INFORMATION, "Single master plot (frame %d) built: %s.%s\n", Ctrl->M.frame, Ctrl->N.prefix, Ctrl->M.format);
if (Ctrl->M.view) { /* Play the movie master frame via gmt docs */
snprintf (cmd, PATH_MAX, "%s/%s.%s", topdir, Ctrl->N.prefix, Ctrl->M.format);
gmt_filename_set (cmd); /* Protect filename spaces by substitution */
if ((error = GMT_Call_Module (API, "docs", GMT_MODULE_CMD, cmd))) {
GMT_Report (API, GMT_MSG_ERROR, "Failed to call docs\n");
error = GMT_RUNTIME_ERROR;
goto out_of_here;
}
}
if (!Ctrl->Q.active) {
/* Delete the masterfile script */
if (gmt_remove_file (GMT, master_file)) { /* Delete the master_file script */
Expand Down Expand Up @@ -2470,7 +2508,8 @@ EXTERN_MSC int GMT_movie (void *V_API, int mode, void *args) {
}
else if (!strstr (line, "#!/")) { /* Skip any leading shell incantation since already placed */
if (gmt_is_gmt_end_show (line)) sprintf (line, "gmt end\n"); /* Eliminate show from gmt end in this script */
else if (strchr (line, '\n') == NULL) strcat (line, "\n"); /* In case the last line misses a newline */
else if (strchr (line, '\n') == NULL) /* In case the last line misses a newline */
strcat (line, "\n");
fprintf (fp, "%s", line); /* Just copy the line as is */
}
}
Expand Down Expand Up @@ -2616,6 +2655,15 @@ EXTERN_MSC int GMT_movie (void *V_API, int mode, void *args) {
goto out_of_here;
}
GMT_Report (API, GMT_MSG_INFORMATION, "MP4 movie built: %s.mp4\n", Ctrl->N.prefix);
if (Ctrl->F.view[MOVIE_MP4]) { /* Play the movie automatically via gmt docs */
snprintf (cmd, PATH_MAX, "%s.mp4", Ctrl->N.prefix);
gmt_filename_set (cmd); /* Protect filename spaces by substitution */
if ((error = GMT_Call_Module (API, "docs", GMT_MODULE_CMD, cmd))) {
GMT_Report (API, GMT_MSG_ERROR, "Failed to call docs\n");
error = GMT_RUNTIME_ERROR;
goto out_of_here;
}
}
}
if (Ctrl->F.active[MOVIE_WEBM]) {
static char *vpx[2] = {"libvpx", "libvpx-vp9"}, *pix_fmt[2] = {"yuv420p", "yuva420p"};
Expand All @@ -2640,6 +2688,15 @@ EXTERN_MSC int GMT_movie (void *V_API, int mode, void *args) {
goto out_of_here;
}
GMT_Report (API, GMT_MSG_INFORMATION, "WebM movie built: %s.webm\n", Ctrl->N.prefix);
if (Ctrl->F.view[MOVIE_WEBM]) { /* Play the movie automatically via gmt docs */
snprintf (cmd, PATH_MAX, "%s.webm", Ctrl->N.prefix);
gmt_filename_set (cmd); /* Protect filename spaces by substitution */
if ((error = GMT_Call_Module (API, "docs", GMT_MODULE_CMD, cmd))) {
GMT_Report (API, GMT_MSG_ERROR, "Failed to call docs\n");
error = GMT_RUNTIME_ERROR;
goto out_of_here;
}
}
}

/* Prepare the cleanup script */
Expand Down