[Bug]: Import breaks for certain file names #802

Closed
opened 2026-04-24 23:22:11 +02:00 by adam · 10 comments
Owner

Originally created by @lkiesow on GitHub (Dec 6, 2022).

Describe the issue

Trying to import a new audiobook from a path like /data/audiobooks/Firstname\ Lastname/Bookname, the scanner just fails since audiobookshelf corrupts the filename.

{"timestamp":"2022-12-06 21:31:52","message":"[fileUtils] Recurse files error Error: ENOENT: no such file or directory, scandir '/audiobooks/Firstname/ Lastname/Bookname/'","levelName":"ERROR","level":4}
{"timestamp":"2022-12-06 21:31:52","message":"[fileUtils] Failed to getFileTimestampsWithIno Error: ENOENT: no such file or directory, stat '/audiobooks/Firstname/ Lastname/Bookname'","levelName":"ERROR","level":4}
{"timestamp":"2022-12-06 21:31:52","message":"[Scanner] Library item has no media files \"/audiobooks/Firstname/ Lastname/Bookname\"","levelName":"WARN","level":3}

It seems like audiobookshelf converts the \ which is part of the folder name to / which turns the second part of the folder name into a subfolder. All attempts at importing the files then fail since the path is no longer correct and the files do not exist.

Audiobookshelf version

v2.2.8

How are you running audiobookshelf?

Docker

Originally created by @lkiesow on GitHub (Dec 6, 2022). ### Describe the issue Trying to import a new audiobook from a path like `/data/audiobooks/Firstname\ Lastname/Bookname`, the scanner just fails since audiobookshelf corrupts the filename. ``` {"timestamp":"2022-12-06 21:31:52","message":"[fileUtils] Recurse files error Error: ENOENT: no such file or directory, scandir '/audiobooks/Firstname/ Lastname/Bookname/'","levelName":"ERROR","level":4} {"timestamp":"2022-12-06 21:31:52","message":"[fileUtils] Failed to getFileTimestampsWithIno Error: ENOENT: no such file or directory, stat '/audiobooks/Firstname/ Lastname/Bookname'","levelName":"ERROR","level":4} {"timestamp":"2022-12-06 21:31:52","message":"[Scanner] Library item has no media files \"/audiobooks/Firstname/ Lastname/Bookname\"","levelName":"WARN","level":3} ``` It seems like audiobookshelf converts the `\` which is part of the folder name to `/` which turns the second part of the folder name into a subfolder. All attempts at importing the files then fail since the path is no longer correct and the files do not exist. ### Audiobookshelf version v2.2.8 ### How are you running audiobookshelf? Docker
adam added the bug label 2026-04-24 23:22:11 +02:00
adam closed this issue 2026-04-24 23:22:11 +02:00
Author
Owner

@advplyr commented on GitHub (Dec 6, 2022):

Ah yeah this is to update Windows paths that use \. There is probably a better way, I just didn't want Windows paths to be stored in the db.

@advplyr commented on GitHub (Dec 6, 2022): Ah yeah this is to update Windows paths that use `\`. There is probably a better way, I just didn't want Windows paths to be stored in the db.
Author
Owner

@lkiesow commented on GitHub (Jan 2, 2023):

Do you know if scandir is always guaranteed to be an absolute path? In that case, it should always start with / on a Unix-like system, and we could sip the replacement in such cases.

@lkiesow commented on GitHub (Jan 2, 2023): Do you know if `scandir` is always guaranteed to be an absolute path? In that case, it should always start with `/` on a Unix-like system, and we could sip the replacement in such cases.
Author
Owner

@advplyr commented on GitHub (Jan 2, 2023):

scandir returns both absolute and relative. Where did you enter that file path?

@advplyr commented on GitHub (Jan 2, 2023): scandir returns both absolute and relative. Where did you enter that file path?
Author
Owner

@lkiesow commented on GitHub (Jan 4, 2023):

You mean the one with \? The scanner picked that up from the file system.

@lkiesow commented on GitHub (Jan 4, 2023): You mean the one with `\`? The scanner picked that up from the file system.
Author
Owner

@lkiesow commented on GitHub (Jan 4, 2023):

Thinking about this for a second… even if it can be a relative path, it should be easy to turn it into an absolute path and then act on that. I'll take a look and will propose a patch soonish.

@lkiesow commented on GitHub (Jan 4, 2023): Thinking about this for a second… even if it can be a relative path, it should be easy to turn it into an absolute path and then act on that. I'll take a look and will propose a patch soonish.
Author
Owner

@lkiesow commented on GitHub (Jan 4, 2023):

Unfortunately, replacement like this seems to be all over the project.
Fixing this won't be as easy as I hoped:

❯ grep -rn '\\\\' server --exclude-dir=libs | grep replace
server/scanner/Scanner.js:562:      var fullPath = Path.posix.join(folder.fullPath.replace(/\\/g, '/'), itemDir)
server/scanner/Scanner.js:567:      var altFullPath = Path.posix.join(folder.fullPath.replace(/\\/g, '/'), altDir)
server/scanner/Scanner.js:581:      var fullPath = Path.posix.join(folder.fullPath.replace(/\\/g, '/'), itemDir)
server/Watcher.js:146:    path = path.replace(/\\/g, '/')
server/Watcher.js:157:    var folder = libwatcher.folders.find(fold => path.startsWith(fold.fullPath.replace(/\\/g, '/')))
server/Watcher.js:162:    var folderFullPath = folder.fullPath.replace(/\\/g, '/')
server/Watcher.js:192:      return path.replace(/\\/g, '/').startsWith(dirpath)
server/Watcher.js:197:    var path = path.replace(/\\/g, '/')
server/controllers/PodcastController.js:33:    var podcastPath = payload.path.replace(/\\/g, '/')
server/Server.js:51:      global.ConfigPath = global.ConfigPath.replace(/\\/g, '/')
server/Server.js:52:      global.MetadataPath = global.MetadataPath.replace(/\\/g, '/')
server/utils/ffmpegHelpers.js:8:  // return path.replace(/'/g, '\'\\\'\'')
server/utils/ffmpegHelpers.js:9:  return path.replace(/\\/g, '/').replace(/ /g, '\\ ').replace(/'/g, '\\\'')
server/utils/scandir.js:183:  const folderPath = folder.fullPath.replace(/\\/g, '/')
server/utils/scandir.js:243:  relPath = relPath.replace(/\\/g, '/')
server/utils/scandir.js:333:  relPath = relPath.replace(/\\/g, '/')
server/utils/scandir.js:348:  relPath = relPath.replace(/\\/g, '/')
server/utils/scandir.js:375:  libraryItemPath = libraryItemPath.replace(/\\/g, '/')
server/utils/scandir.js:376:  var folderFullPath = folder.fullPath.replace(/\\/g, '/')
server/utils/index.js:129:  return path.replace(/\\/g, '/').replace(/%/g, '%25').replace(/#/g, '%23')
server/utils/dbMigration.js:90:  srcPath = srcPath.replace(/\\/g, '/')
server/utils/dbMigration.js:91:  basePath = basePath.replace(/\\/g, '/')
server/utils/fileUtils.js:83:  path = path.replace(/\\/g, '/')
server/utils/fileUtils.js:87:    relPathToReplace = relPathToReplace.replace(/\\/g, '/')
server/objects/mediaTypes/Book.js:185:    coverPath = coverPath.replace(/\\/g, '/')
server/objects/mediaTypes/Music.js:107:    coverPath = coverPath.replace(/\\/g, '/')
server/objects/mediaTypes/Video.js:104:    coverPath = coverPath.replace(/\\/g, '/')
server/objects/mediaTypes/Podcast.js:162:    coverPath = coverPath.replace(/\\/g, '/')
server/objects/Library.js:156:    fullPath = fullPath.replace(/\\/g, '/')
server/objects/Library.js:157:    return this.folders.find(folder => fullPath.startsWith(folder.fullPath.replace(/\\/g, '/')))
server/managers/CoverManager.js:176:    coverPath = coverPath.replace(/\\/g, '/')
server/managers/AudioMetadataManager.js:130:      var coverPath = libraryItem.media.coverPath.replace(/\\/g, '/')
@lkiesow commented on GitHub (Jan 4, 2023): Unfortunately, replacement like this seems to be all over the project. Fixing this won't be as easy as I hoped: ``` ❯ grep -rn '\\\\' server --exclude-dir=libs | grep replace server/scanner/Scanner.js:562: var fullPath = Path.posix.join(folder.fullPath.replace(/\\/g, '/'), itemDir) server/scanner/Scanner.js:567: var altFullPath = Path.posix.join(folder.fullPath.replace(/\\/g, '/'), altDir) server/scanner/Scanner.js:581: var fullPath = Path.posix.join(folder.fullPath.replace(/\\/g, '/'), itemDir) server/Watcher.js:146: path = path.replace(/\\/g, '/') server/Watcher.js:157: var folder = libwatcher.folders.find(fold => path.startsWith(fold.fullPath.replace(/\\/g, '/'))) server/Watcher.js:162: var folderFullPath = folder.fullPath.replace(/\\/g, '/') server/Watcher.js:192: return path.replace(/\\/g, '/').startsWith(dirpath) server/Watcher.js:197: var path = path.replace(/\\/g, '/') server/controllers/PodcastController.js:33: var podcastPath = payload.path.replace(/\\/g, '/') server/Server.js:51: global.ConfigPath = global.ConfigPath.replace(/\\/g, '/') server/Server.js:52: global.MetadataPath = global.MetadataPath.replace(/\\/g, '/') server/utils/ffmpegHelpers.js:8: // return path.replace(/'/g, '\'\\\'\'') server/utils/ffmpegHelpers.js:9: return path.replace(/\\/g, '/').replace(/ /g, '\\ ').replace(/'/g, '\\\'') server/utils/scandir.js:183: const folderPath = folder.fullPath.replace(/\\/g, '/') server/utils/scandir.js:243: relPath = relPath.replace(/\\/g, '/') server/utils/scandir.js:333: relPath = relPath.replace(/\\/g, '/') server/utils/scandir.js:348: relPath = relPath.replace(/\\/g, '/') server/utils/scandir.js:375: libraryItemPath = libraryItemPath.replace(/\\/g, '/') server/utils/scandir.js:376: var folderFullPath = folder.fullPath.replace(/\\/g, '/') server/utils/index.js:129: return path.replace(/\\/g, '/').replace(/%/g, '%25').replace(/#/g, '%23') server/utils/dbMigration.js:90: srcPath = srcPath.replace(/\\/g, '/') server/utils/dbMigration.js:91: basePath = basePath.replace(/\\/g, '/') server/utils/fileUtils.js:83: path = path.replace(/\\/g, '/') server/utils/fileUtils.js:87: relPathToReplace = relPathToReplace.replace(/\\/g, '/') server/objects/mediaTypes/Book.js:185: coverPath = coverPath.replace(/\\/g, '/') server/objects/mediaTypes/Music.js:107: coverPath = coverPath.replace(/\\/g, '/') server/objects/mediaTypes/Video.js:104: coverPath = coverPath.replace(/\\/g, '/') server/objects/mediaTypes/Podcast.js:162: coverPath = coverPath.replace(/\\/g, '/') server/objects/Library.js:156: fullPath = fullPath.replace(/\\/g, '/') server/objects/Library.js:157: return this.folders.find(folder => fullPath.startsWith(folder.fullPath.replace(/\\/g, '/'))) server/managers/CoverManager.js:176: coverPath = coverPath.replace(/\\/g, '/') server/managers/AudioMetadataManager.js:130: var coverPath = libraryItem.media.coverPath.replace(/\\/g, '/') ```
Author
Owner

@advplyr commented on GitHub (Jan 4, 2023):

It seems strange the scanner is pulling it in with the "\". I think normally this will come in as a space.

I have spaces in my folder paths and they don't come in with a "\".

@advplyr commented on GitHub (Jan 4, 2023): It seems strange the scanner is pulling it in with the "\\". I think normally this will come in as a space. I have spaces in my folder paths and they don't come in with a "\\".
Author
Owner

@lkiesow commented on GitHub (Jan 5, 2023):

It seems strange the scanner is pulling it in with the "\". I think normally this will come in as a space.

The \ is part of the file name. It is not an escape character. The watcher is picking up the file name correctly. Using \ as part of a filename is fine on basically all non-windows file systems.

That's why I was looking for a way to just disable the replacement if you are on a Unix-like system.

❯ mkdir '\'; ls -ld '\'
drwxr-xr-x. 2 lars lars 4096 Jan  5 17:45 '\'
@lkiesow commented on GitHub (Jan 5, 2023): > It seems strange the scanner is pulling it in with the "\\". I think normally this will come in as a space. The `\` is part of the file name. It is not an escape character. The watcher is picking up the file name correctly. Using `\` as part of a filename is fine on basically all non-windows file systems. That's why I was looking for a way to just disable the replacement if you are on a Unix-like system. ``` ❯ mkdir '\'; ls -ld '\' drwxr-xr-x. 2 lars lars 4096 Jan 5 17:45 '\' ```
Author
Owner

@advplyr commented on GitHub (Jan 6, 2023):

I'm tackling this one now.

@advplyr commented on GitHub (Jan 6, 2023): I'm tackling this one now.
Author
Owner

@advplyr commented on GitHub (Jan 8, 2023):

Fixed in v2.2.12

@advplyr commented on GitHub (Jan 8, 2023): Fixed in [v2.2.12](https://github.com/advplyr/audiobookshelf/releases/tag/v2.2.12)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/audiobookshelf#802