这是CommandExecutor中注册的一种可执行的命令,也是flamenco最核心的渲染功能执行的命令。在CommandExecutor中,它的registry初始化是这样的:

    ce.registry = map[string]commandCallable{
        // misc
        "echo":  ce.cmdEcho,
        "sleep": ce.cmdSleep,
        "exec":  ce.cmdExec,
        // blender
        "blender-render": ce.cmdBlenderRender,
        // ffmpeg
        "frames-to-video": ce.cmdFramesToVideo,
        // file-management
        "move-directory": ce.cmdMoveDirectory,
        "copy-file":      ce.cmdCopyFile,
    }

在这里可以看到blender-render对应的执行命令的函数就是cmdBlenderRender。

cmdBlenderRender函数代码:

// cmdBlender executes the "blender-render" command.
func (ce *CommandExecutor) cmdBlenderRender(ctx context.Context, logger zerolog.Logger, taskID string, cmd api.Command) error {
    cmdCtx, cmdCtxCancel := context.WithCancel(ctx)
    defer cmdCtxCancel() // Ensure the subprocess exits whenever this function returns.

    execCmd, err := ce.cmdBlenderRenderCommand(cmdCtx, logger, taskID, cmd)
    if err != nil {
        return err
    }
    logChunker := NewLogChunker(taskID, ce.listener, ce.timeService)
    lineChannel := make(chan string)
    // Process the output of Blender in its own goroutine.
    wg := sync.WaitGroup{}
    wg.Add(1)
    go func() {
        defer wg.Done()
        for line := range lineChannel {
            ce.processLineBlender(ctx, logger, taskID, line)
        }
    }()

    // Run the subprocess.
    subprocessErr := ce.cli.RunWithTextOutput(ctx,
        logger,
        execCmd,
        logChunker,
        lineChannel,
    )

    // Wait for the processing to stop.
    close(lineChannel)
    wg.Wait()
    ...
    return nil
}

从上面的函数中可以看出当执行blender渲染的命令时,先从cmdBlenderRenderCommand这个函数初始化出一个execCmd,然后在下面开启了一个goroutine,在其中遍历了lineChannel中的每一行line,然后使用processLineBlender对其进行处理,再下面,RunWithTextOutput开启一个子进程来执行命令,并将命令执行产生的cmd输出放到lineChannel中。

看一下processLineBlender如何处理日志的,

var regexpFileSaved = regexp.MustCompile("Saved: '(.*)'")

func (ce *CommandExecutor) processLineBlender(ctx context.Context, logger zerolog.Logger, taskID string, line string) {
    // TODO: check for "Warning: Unable to open" and other indicators of missing
    // files. Flamenco v2 updated the task.Activity field for such situations.

    match := regexpFileSaved.FindStringSubmatch(line)
    if len(match) < 2 {
        return
    }
    filename := match[1]

    logger = logger.With().Str("outputFile", filename).Logger()
    logger.Info().Msg("output produced")

    err := ce.listener.OutputProduced(ctx, taskID, filename)
    if err != nil {
        logger.Warn().Err(err).Msg("error submitting produced output to listener")
    }
}

它从命令执行的输出中找到带有‘Saved:’的那行,然后从中提取出blender渲染产生的文件,再使用OutputProduced上传至manager。在blender中,渲染图片时会有这样一条日志:

这里就是通过这条日志来找到渲染输出文件的。

其他类型的命令也是通过类似的方法执行。