{"record":{"id":"2f92c13ebbd7f3d8","repo":"navidrome/navidrome","slug":"s-is-a-directory","errorCode":null,"errorMessage":"'%s' is a directory","messagePattern":"'(.+?)' is a directory","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/ffmpeg/ffmpeg.go","lineNumber":142,"sourceCode":"\nfunc (e *ffmpeg) ExtractImage(ctx context.Context, path string) (io.ReadCloser, error) {\n\tif _, err := ffmpegCmd(); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := fileExists(path); err != nil {\n\t\treturn nil, err\n\t}\n\targs := createFFmpegCommand(extractImageCmd, path, 0, 0)\n\treturn e.start(ctx, args)\n}\n\nfunc fileExists(path string) error {\n\ts, err := os.Stat(path)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif s.IsDir() {\n\t\treturn fmt.Errorf(\"'%s' is a directory\", path)\n\t}\n\treturn nil\n}\n\nfunc (e *ffmpeg) Probe(ctx context.Context, files []string) (string, error) {\n\tif _, err := ffmpegCmd(); err != nil {\n\t\treturn \"\", err\n\t}\n\targs := createProbeCommand(probeCmd, files)\n\tlog.Trace(ctx, \"Executing ffmpeg command\", \"args\", args)\n\tcmd := exec.CommandContext(ctx, args[0], args[1:]...) // #nosec\n\toutput, _ := cmd.CombinedOutput()\n\treturn string(output), nil\n}\n\nfunc (e *ffmpeg) ProbeAudioStream(ctx context.Context, filePath string) (*AudioProbeResult, error) {\n\tif _, err := ffmpegCmd(); err != nil {\n\t\treturn nil, err","sourceCodeStart":124,"sourceCodeEnd":160,"githubUrl":"https://github.com/navidrome/navidrome/blob/4ed7494a3293a9e9e647897ebfb9be327efd981b/core/ffmpeg/ffmpeg.go#L124-L160","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Fix the source record so the path points at an actual media file, not a folder","Rescan the library to refresh stale/moved entries","Check for name collisions where a directory replaced a file of the same name","Inspect the path in the error: strip the trailing directory or append the real filename","If a symlink resolves to a directory, point it at the file instead"],"exampleFix":"// before\nopts.FilePath = \"/music/Artist/Album\"           // directory\n// after\nopts.FilePath = \"/music/Artist/Album/01 track.flac\"","handlingStrategy":"validation","validationCode":"info, err := os.Stat(filePath)\nif err != nil { return err }\nif info.IsDir() { return fmt.Errorf(\"%q is a directory, expected a media file\", filePath) }\nif !info.Mode().IsRegular() { return fmt.Errorf(\"%q is not a regular file\", filePath) }","typeGuard":"func isPlayableFile(path string) bool {\n\tinfo, err := os.Stat(path)\n\treturn err == nil && info.Mode().IsRegular()\n}","tryCatchPattern":"rc, err := ffmpeg.Transcode(ctx, opts)\nif err != nil {\n\tif strings.Contains(err.Error(), \"is a directory\") || statIsDir(opts.FilePath) {\n\t\t// fix the path/DB record before retrying\n\t}\n\treturn err\n}","preventionTips":["Always pass the full path to a media file, never a folder","Rescan the library after moving files so DB paths stay valid","Guard against name collisions between files and directories","Validate paths coming from playlists or external integrations"],"tags":["go","ffmpeg","filesystem","bad-input"],"backgroundTag":"path-is-a-directory","analyzedSha":"4ed7494a3293a9e9e647897ebfb9be327efd981b","analyzedAt":"2026-09-01T05:03:05.018Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}