navidrome/navidrome · error

'%s' is a directory

Error message

'%s' is a directory

What it means

fileExists is Navidrome's pre-flight check before invoking ffmpeg on a path (Transcode, ExtractImage, ProbeAudioStream). It stats the path and, if it exists but is a directory, returns this plain error naming the path. ffmpeg itself can't transcode a directory, so the library rejects it early with a clear message instead of an ffmpeg failure.

Source

Thrown at core/ffmpeg/ffmpeg.go:142

func (e *ffmpeg) ExtractImage(ctx context.Context, path string) (io.ReadCloser, error) {
	if _, err := ffmpegCmd(); err != nil {
		return nil, err
	}
	if err := fileExists(path); err != nil {
		return nil, err
	}
	args := createFFmpegCommand(extractImageCmd, path, 0, 0)
	return e.start(ctx, args)
}

func fileExists(path string) error {
	s, err := os.Stat(path)
	if err != nil {
		return err
	}
	if s.IsDir() {
		return fmt.Errorf("'%s' is a directory", path)
	}
	return nil
}

func (e *ffmpeg) Probe(ctx context.Context, files []string) (string, error) {
	if _, err := ffmpegCmd(); err != nil {
		return "", err
	}
	args := createProbeCommand(probeCmd, files)
	log.Trace(ctx, "Executing ffmpeg command", "args", args)
	cmd := exec.CommandContext(ctx, args[0], args[1:]...) // #nosec
	output, _ := cmd.CombinedOutput()
	return string(output), nil
}

func (e *ffmpeg) ProbeAudioStream(ctx context.Context, filePath string) (*AudioProbeResult, error) {
	if _, err := ffmpegCmd(); err != nil {
		return nil, err

View on GitHub (pinned to 4ed7494a32)

Solutions

  1. Fix the source record so the path points at an actual media file, not a folder
  2. Rescan the library to refresh stale/moved entries
  3. Check for name collisions where a directory replaced a file of the same name
  4. Inspect the path in the error: strip the trailing directory or append the real filename
  5. If a symlink resolves to a directory, point it at the file instead

Example fix

// before
opts.FilePath = "/music/Artist/Album"           // directory
// after
opts.FilePath = "/music/Artist/Album/01 track.flac"
Defensive patterns

Strategy: validation

Validate before calling

info, err := os.Stat(filePath)
if err != nil { return err }
if info.IsDir() { return fmt.Errorf("%q is a directory, expected a media file", filePath) }
if !info.Mode().IsRegular() { return fmt.Errorf("%q is not a regular file", filePath) }

Type guard

func isPlayableFile(path string) bool {
	info, err := os.Stat(path)
	return err == nil && info.Mode().IsRegular()
}

Try / catch

rc, err := ffmpeg.Transcode(ctx, opts)
if err != nil {
	if strings.Contains(err.Error(), "is a directory") || statIsDir(opts.FilePath) {
		// fix the path/DB record before retrying
	}
	return err
}

Prevention

When it happens

Trigger: Calling Transcode/ExtractImage/ProbeAudioStream with TranscodeOptions.FilePath (or path) pointing to a directory: a DB media_file record whose Path column holds a folder (bad scan/import), or constructing the path by joining a folder where a filename was expected.

Common situations: Library scanned while files were mid-move leaving folder-only entries; custom integrations passing a folder path from a playlist; a file and directory sharing the same name after a re-organize; broken import scripts writing directory paths into the DB.

Related errors


AI-assisted analysis of navidrome/navidrome@4ed7494a32 (2026-09-01). Data as JSON: /api/errors/2f92c13ebbd7f3d8. Report an issue: GitHub.