Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 36 additions & 9 deletions src/guacenc/guacenc.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,22 @@
#include <getopt.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char* argv[]) {

int i;

/* Load defaults */
bool force = false;
const char* out_file = NULL;
int width = GUACENC_DEFAULT_WIDTH;
int height = GUACENC_DEFAULT_HEIGHT;
int bitrate = GUACENC_DEFAULT_BITRATE;

/* Parse arguments */
int opt;
while ((opt = getopt(argc, argv, "s:r:f")) != -1) {
while ((opt = getopt(argc, argv, "s:r:fo:")) != -1) {

/* -s: Dimensions (WIDTHxHEIGHT) */
if (opt == 's') {
Expand All @@ -65,6 +67,10 @@ int main(int argc, char* argv[]) {
else if (opt == 'f')
force = true;

/* -o: Output file ("-" for STDOUT) */
else if (opt == 'o')
out_file = optarg;

/* Invalid option */
else {
goto invalid_options;
Expand Down Expand Up @@ -95,6 +101,14 @@ int main(int argc, char* argv[]) {
return 0;
}

/* A single explicit output cannot receive the encodings of multiple
* input files */
if (out_file != NULL && total_files != 1) {
guacenc_log(GUAC_LOG_ERROR, "Only one input file may be given if "
"the output is specified with the -o option.");
goto invalid_options;
}

guacenc_log(GUAC_LOG_INFO, "%i input file(s) provided.", total_files);

guacenc_log(GUAC_LOG_INFO, "Video will be encoded at %ix%i "
Expand All @@ -106,15 +120,27 @@ int main(int argc, char* argv[]) {
/* Get current filename */
const char* path = argv[i];

/* Generate output filename */
char out_path[4096];
int len = snprintf(out_path, sizeof(out_path), "%s.m4v", path);
/* Use the explicitly-provided output path, if any, streaming to
* STDOUT if "-" was given as the output */
const char* out_path;
char out_buffer[4096];
if (out_file != NULL)
out_path = strcmp(out_file, "-") ? out_file : GUACENC_STDOUT_PATH;

/* Otherwise, generate output filename from input filename */
else {

int len = snprintf(out_buffer, sizeof(out_buffer), "%s.m4v", path);

/* Do not write if filename exceeds maximum length */
if (len >= sizeof(out_buffer)) {
guacenc_log(GUAC_LOG_ERROR, "Cannot write output file for "
"\"%s\": Name too long", path);
continue;
}

out_path = out_buffer;
Comment thread
necouchman marked this conversation as resolved.

/* Do not write if filename exceeds maximum length */
if (len >= sizeof(out_path)) {
guacenc_log(GUAC_LOG_ERROR, "Cannot write output file for \"%s\": "
"Name too long", path);
continue;
}

/* Attempt encoding, log granular success/failure at debug level */
Expand Down Expand Up @@ -148,6 +174,7 @@ int main(int argc, char* argv[]) {
" [-s WIDTHxHEIGHT]"
" [-r BITRATE]"
" [-f]"
" [-o OUTPUT]"
" [FILE]...\n", argv[0]);

return 1;
Expand Down
6 changes: 6 additions & 0 deletions src/guacenc/guacenc.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,11 @@
*/
#define GUACENC_DEFAULT_LOG_LEVEL GUAC_LOG_INFO

/**
* The libavformat URL of the current process' STDOUT, as used when encoded
* video should be streamed to STDOUT rather than written to a file.
*/
#define GUACENC_STDOUT_PATH "pipe:1"

#endif

15 changes: 15 additions & 0 deletions src/guacenc/man/guacenc.1.in
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ guacenc \- Guacamole video encoder
[\fB-s\fR \fIWIDTH\fRx\fIHEIGHT\fR]
[\fB-r\fR \fIBITRATE\fR]
[\fB-f\fR]
[\fB-o\fR \fIOUTPUT\fR]
[\fIFILE\fR]...
.
.SH DESCRIPTION
Expand All @@ -46,6 +47,13 @@ overridden with the \fB-s\fR and \fB-r\fR options respectively. Existing files
will not be overwritten; the encoding process for any input file will be
aborted if it would result in overwriting an existing file.
.P
Alternatively, the \fB-o\fR option may be used to specify the output file
explicitly. In typical UNIX fashion, specifying "-" as the output file will
cause the encoded video to be streamed to STDOUT rather than written to a
file, allowing the video to be piped to another process or streamed as a
download while it is still being encoded. As only a single output may be
specified, only one \fIFILE\fR may be given if \fB-o\fR is used.
.P
Guacamole acquires a write lock on recordings as they are being written. By
default,
.B guacenc
Expand Down Expand Up @@ -75,6 +83,13 @@ Overrides the default behavior of
.B guacenc
such that input files will be encoded even if they appear to be recordings of
in-progress Guacamole sessions.
.TP
\fB-o\fR \fIOUTPUT\fR
Writes the encoded video to \fIOUTPUT\fR instead of a file named after the
input file. If \fIOUTPUT\fR is "-", the encoded video will be streamed to
STDOUT rather than written to a file; as all log messages are written to
STDERR, they will not interfere with the video stream. Only one \fIFILE\fR may
be given when this option is used.
.
.SH SEE ALSO
.BR guaclog (1)
28 changes: 24 additions & 4 deletions src/guacenc/video.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include <libavformat/avformat.h>
#endif
#include <libavutil/common.h>
#include <libavutil/dict.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
#include <guacamole/client.h>
Expand All @@ -41,6 +42,7 @@
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
Expand All @@ -54,8 +56,16 @@ guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name,
int ret;
int failed_header = 0;

/* The container format cannot be guessed from a pipe URL and must be
* specified explicitly. The "ipod" container is used, matching the
* container that would be guessed from the ".m4v" extension of the output
* files normally produced */
bool is_pipe = (strncmp(path, "pipe:", 5) == 0);
const char* format_name = is_pipe ? "ipod" : NULL;

Comment on lines +59 to +65

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this an appropriate assumption to make universally, or should this be configurable, as well? Are there situations where users may want to stream in a format different than iPod?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"ipod" was already an assumption being made universally before, just not explicitly. When writing to a file, the third argument to avformat_alloc_output_context2() is NULL so libavformat guesses the container from the extension of the output path. Since that was hardcoded to "FILE.m4v" it was always making the same guess based on ".m4v" -- 'ipod'. This is a pretty universally supported format, even in web browsers, so I think it's a good default. Maybe someone will want it to be configurable in the future, but we can just wait for that feature request.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that it was assumed by virtue of the fact that all files were necessarily m4v files. Now we're giving folks an option on what to name the files, which implicitly allows them to also adjust the format - and we're giving them an option to pipe everything to stdout, but forcing them to use the ipod format when going to stdout.

I'm not going to block this PR from going through if you don't want to do this, it's just something that seems to be in the same spirit as the ability to send it to stdout, and change the filename.

[It's also worth noting that simply having the ability to send to stdout means that you could also pipe it back into ffmpeg and reformat it that way, but you may start to lose the fidelity of the video that way due to lossy encodings, etc.]

/* allocate the output media context */
avformat_alloc_output_context2(&container_format_context, NULL, NULL, path);
avformat_alloc_output_context2(&container_format_context, NULL,
format_name, path);
if (container_format_context == NULL) {
guacenc_log(GUAC_LOG_ERROR, "Failed to determine container from output file name");
goto fail_codec;
Expand Down Expand Up @@ -130,8 +140,17 @@ guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name,
}
}

/* A pipe cannot be seeked backwards to finalize the container once
* encoding is complete, so fragmented MP4 must be used to produce a
* usable video stream */
AVDictionary* header_options = NULL;
if (is_pipe)
av_dict_set(&header_options, "movflags",
"frag_keyframe+empty_moov", 0);

/* write the stream header, if needed */
ret = avformat_write_header(container_format_context, NULL);
ret = avformat_write_header(container_format_context, &header_options);
av_dict_free(&header_options);
if (ret < 0) {
guacenc_log(GUAC_LOG_ERROR, "Error occurred while writing output file header.");
failed_header = true;
Expand Down Expand Up @@ -163,8 +182,9 @@ guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name,
fail_output_file:
avio_close(container_format_context->pb);

/* Delete the file that was created if it was actually created */
if (unlink(path) == -1 && errno != ENOENT)
/* Delete the file that was created if it was actually created (no file
* exists to be deleted if output was written to a pipe) */
if (!is_pipe && unlink(path) == -1 && errno != ENOENT)
guacenc_log(GUAC_LOG_WARNING, "Failed output file \"%s\" could not "
"be automatically deleted: %s", path, strerror(errno));

Expand Down
Loading