diff --git a/doc/rst/source/movie.rst b/doc/rst/source/movie.rst index 594cd8ff18a..8bbd0ea7420 100644 --- a/doc/rst/source/movie.rst +++ b/doc/rst/source/movie.rst @@ -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* ] @@ -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: @@ -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 @@ -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: @@ -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 ` [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: diff --git a/src/movie.c b/src/movie.c index 3c5850defa8..056e4d1a2a4 100644 --- a/src/movie.c +++ b/src/movie.c @@ -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[+l][+o][+s][+t] - repeatable */ + struct MOVIE_F { /* -F[+l][+o][+s][+t][+v] - repeatable */ bool active[MOVIE_N_FORMATS]; + bool view[MOVIE_N_FORMATS]; bool transparent; bool loop; bool skip; @@ -215,10 +216,11 @@ struct MOVIE_CTRL { struct MOVIE_L { /* Repeatable: -L[e|f|c#|t#|s][+c][+f][+g][+h[//][]][+j][+o][+p][+r][+t][+s] */ bool active; } L; - struct MOVIE_M { /* -M[|f|l|m][,format][+r] */ + struct MOVIE_M { /* -M[|f|l|m][,format][+r][+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; @@ -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 -C|xx -N -T|//[+n]|[+p][+s][+w[]|W] " - "[-D] [-E[+d[[s]]][+f[i|o][[s]]][+g]] [-Fgif|mp4|webm|png[+l[]][+o][+s][+t]] [-G[][+p]] [-H] " - "[-I] [-K[+f[i|o][[s]]][+g][+p[i|o]]] [-L] [-M[|f|m|l,][][+r]] [-P] [-Q[s]] [-Sb] " + "[-D] [-E[+d[[s]]][+f[i|o][[s]]][+g]] [-Fgif|mp4|webm|png[+l[]][+o][+s][+t][+v]] [-G[][+p]] [-H] " + "[-I] [-K[+f[i|o][[s]]][+g][+p[i|o]]] [-L] [-M[|f|m|l,][][+r][+v]] [-P] [-Q[s]] [-Sb] " "[-Sf] [%s] [-W[]] [-Z[s]] [%s] [-x[[-]]] [%s]\n", name, GMT_V_OPT, GMT_f_OPT, GMT_PAR_OPT); if (level == GMT_SYNOPSIS) return (GMT_MODULE_SYNOPSIS); @@ -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 frame only [all]. " @@ -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[|f|m|l,][][+r]"); + GMT_Usage (API, 1, "\n-M[|f|m|l,][][+r][+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 . 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 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 [same as movie]."); + GMT_Usage (API, 3, "+v Open the master frame in the default viewer."); GMT_Usage (API, 1, "\n-P"); 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:"); @@ -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 */ @@ -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; @@ -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 */ } @@ -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 */ @@ -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[|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[|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"); @@ -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 */ @@ -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 */ } } @@ -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 */ } } @@ -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 */ @@ -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 */ } } @@ -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"}; @@ -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 */