Billionmail/BillionMail · error

create output dir: %w

Error message

create output dir: %w

What it means

CompositeVideo creates the output directory (os.MkdirAll on filepath.Dir(cfg.OutputPath)) before running ffmpeg. If directory creation fails, the error is wrapped as 'create output dir: %w' and compositing is aborted, since ffmpeg would otherwise fail writing the output file. The wrapped cause is the standard filesystem error (permission denied, path is a file, etc.).

Source

Thrown at core/internal/service/video_gen/composite.go:190

	pipY := cfg.Height - pipH - 20

	return fmt.Sprintf(
		"[%d:v]scale=%d:%d[pip];[%s][pip]overlay=%d:%d:shortest=1[vpip]",
		lipSyncIdx, pipW, pipH, baseVideo, pipX, pipY,
	)
}

// CompositeVideo runs FFmpeg to combine screenshots + audio into a final video.
// Requires: ffmpeg installed and in PATH.
func CompositeVideo(ctx context.Context, cfg CompositeConfig) (*CompositeResult, error) {
	args := BuildFFmpegArgs(cfg)
	if args == nil {
		return nil, fmt.Errorf("no scenes provided")
	}

	// Ensure output directory exists
	if err := os.MkdirAll(filepath.Dir(cfg.OutputPath), 0755); err != nil {
		return nil, fmt.Errorf("create output dir: %w", err)
	}

	cmd := exec.CommandContext(ctx, "ffmpeg", args...)
	out, err := cmd.CombinedOutput()
	if err != nil {
		return nil, fmt.Errorf("ffmpeg compositing failed: %w\noutput: %s", err, string(out))
	}

	// Calculate total duration from scenes
	var totalDuration time.Duration
	for _, scene := range cfg.Scenes {
		totalDuration += scene.Duration
	}

	return &CompositeResult{
		VideoPath: cfg.OutputPath,
		Duration:  totalDuration,
	}, nil

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Read the wrapped cause (e.g. 'permission denied') and fix filesystem permissions on the output parent directory
  2. Verify cfg.OutputPath's directory component is not an existing file; remove or rename it
  3. Ensure the container/volume is writable (mount a writable volume, don't write into a read-only layer)
  4. Check disk space and mount status if the output volume is a network mount

Example fix

// before
cfg := video_gen.CompositeConfig{OutputPath: "/out/final.mp4"} // /out not writable
// after
cfg := video_gen.CompositeConfig{OutputPath: "/tmp/video-out/final.mp4"} // writable dir
os.MkdirAll("/tmp/video-out", 0o755)
Defensive patterns

Strategy: validation

Validate before calling

dir := filepath.Dir(cfg.OutputPath)
if info, err := os.Stat(dir); err == nil && !info.IsDir() {
    return fmt.Errorf("%s exists and is not a directory", dir)
}
if err := os.MkdirAll(dir, 0o755); err != nil {
    return fmt.Errorf("output dir not creatable: %w", err)
}

Try / catch

res, err := video_gen.CompositeVideo(ctx, cfg)
if err != nil {
    if strings.Contains(err.Error(), "create output dir") && strings.Contains(err.Error(), "permission denied") {
        return fmt.Errorf("fix write permissions on %s: %w", filepath.Dir(cfg.OutputPath), err)
    }
    return err
}

Prevention

When it happens

Trigger: os.MkdirAll(filepath.Dir(cfg.OutputPath), 0755) fails — parent path is an existing regular file, no write permission on the parent directory, read-only filesystem, or invalid path characters.

Common situations: OutputPath pointing inside a read-only container filesystem; a file existing where a directory is expected; running as non-root user without permission on the output root; NFS/mounted volume unavailable.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05). Data as JSON: /api/errors/bcfc5d1a15498fe0. Report an issue: GitHub.