rsync for your music library. Sync your music library to MP3 players, phones, flash drives etc. while automatically converting unsupported files.
- sync only files that have been added/removed/changed
- works with any directory structure (does not force you into a certain way of managing your music library)
- exclude files/directories
- use a local directory or WebDAV (e.g. Nextcloud) as a source
- use a local directory, MTP (Windows-only) or ADB as a target
- convert unsupported files on-the-fly using FFMPEG
- detect file support by extension, container, codec, profile, ...
- override codec settings for certain paths
- fast conversion due to parallelization
- automatically embed album art from the directory into the file
- handle miscellaneous device limitations (configurable)
- unsupported file formats/containers
- unsupported characters in path and tags
- resolve playlists to a directory with the playlist's songs
- sorting by FAT32 file table order
- case-sensitive sorting
- limited directory depth
- album art stretching
This project works on Windows and Linux, macOS is untested.
- required
- .NET 6 SDK
- ffmpeg (including ffprobe)
- optional
- adb (for sync to Android devices via adb)
- flac (including metaflac) to handle flac files with multiple tag values correctly
All of these should be installed so they are are in PATH.
.NET SDK can be found at https://dotnet.microsoft.com/en-us/download for all platforms
Windows:
These have to be added to PATH manually.
On Linux/MacOS you can probably just install these using the package manager (e.g. apt install ffmpeg flac
).
dotnet run --project src/MusicSyncConverter/MusicSyncConverter -- config.json [...]
You can split the configuration file into multiple files and supply multiple config files as arguments, which might be useful if converting for different end devices but with the same directory settings.
There are predefined device configs for Android and a few car stereos in src/MusicSyncConverter/MusicSyncConverter/configs/
Also see example configs below.
The source directory as an URI.
- File system:
file://
(for examplefile://C:/Users/me/Music
orfile://~/Music
orfile:///home/me/Music
) - WebDAV:
http(s)://
(for examplehttps://user:password@nextcloud.example.com/remote.php/dav/files/me/Music
)
The target directory as an URI. Be aware that everything in this directory will be deleted (see KeepInTarget for details).
- File system:
file://
(for examplefile://F:/Music
orfile:///mnt/usb/Music
)- supports the query parameter
?fatSortMode=<mode>
where<mode>
isNone
,Folders
,Files
,FilesAndFolders
to sort the FAT32 table when directories change. This is useful for devices that don't sort files or directory by name.
- supports the query parameter
- (on Windows) MTP using WPD:
wpd://
(for examplewpd://My Android Phone/disk/Music
)- Don't expect this to be rock-solid. It's MTP, what do you expect?
- ADB:
adb://
(for exampleadb://abcdABCD12345678//storage/0815-ACAB/Music
whereabcdABCD12345678
is the device serial number and/storage/0815-ACAB/Music
is the base directory)- this requires ADB to be installed globally (available in
PATH
) or an ADB daemon to be already running
- this requires ADB to be installed globally (available in
Array of files or directories you want to exclude.
Wildcards *
(any directory) and **
(any directory structure) are supported.
Examples:
Audio Books
ignoresAudio Books/
in the root folderMusic/**/Instrumentals
ignoresMusic/Example Artist/Instrumentals
andMusic/Albums/Example Artist/Instrumentals
Music/*/Instrumentals
ignoresMusic/Example Artist/Instrumentals
but notMusic/Albums/Example Artist/Instrumentals
Music/Albums/**/*.m3u
ignoresMusic/Albums/Example Artist/playlist.m3u
but notMusic/Playlists/playlist.m3u
By default, all files in the target directory (excluding hidden files/directories) that don't exist in the source will be deleted.
Files or directories that match one of these globs will not be deleted regardless of sync state.
Wildcards *
(any directory) and **
(any directory structure) are supported.
This is mostly useful if you have to sync to the root directory of e.g. a flash drive and there are other files you want to keep.
Example:
Videos
ignoresVideos/
in the root folder
In case you want to sync your music to multiple devices, it may be helpful to put this section in a seperate file each.
Format conversion works by analyzing the source file, comparing the format against the configured supported formats and converting to the configured fallback format if necessary
The supported format includes:
Extension
: File extension (required)Codec
: Codec as reported by ffprobe, for exampleaac
(required)Profile
: Profile as reported by ffprobe, for exampleLC
for AAC-LCMaxChannels
: Max. number of audio channelsMaxSampleRateHz
: Max. sample rate in HzMaxBitrate
: Max. bitrate in kbit/s
"as reported by ffprobe" =>
Stream #0:0(und): Audio: aac (LC) (mp4a)
Codec ^ ^ Profile
Stream #0:0(und): Audio: aac (HE-AAC)
Codec ^ ^ Profile
Stream #0:0: Audio: mp3
Codec ^
The fallback format includes:
Extension
: File extension (required)Muxer
: Muxer (usually the container format), for examplemp3
,ogg
,ipod
(required)Codec
: Codec as required by ffmpeg, for examplelibmp3lame
,libopus
oraac
(required)Profile
: Profile as required by ffmpegChannels
: Number of audio channelsSampleRateHz
: Sample rate in HzAdditionalFlags
: Additional parameters to pass to ffmpegBitrate
: Bitrate in kbit/s
"as required by ffmpeg" =>
ffmpeg -i input.mp3 -c:a aac -profile:a aac_low
Encoder/Codec ^ ^ Profile (if applicable)
Codec
: Format to use for album covers (mjpeg
= jpg,png
= png)Width
: Cover sizeHeight
: Cover sizeResizeType
: Can be any ofNone
,KeepInputAspectRatio
,ForceOutputAspectRatio
,ForceOutputSize
None
: Always keep original sizeKeepInputAspectRatio
(default): Resize image to fit width and height while maintaining aspect ratioForceOutputAspectRatio
: Resize image to fit width and height and adds a border* to match output aspect ratio (Width
/Height
). Useful for devices that stretch the album art to fit.ForceOutputSize
: Resize image to exactly width and height and adds a border* to match output aspect ratio
* border consists of a blurred version of the album cover
If there are files named cover.png
, cover.jpg
, folder.jpg
, in a song's directory, the album cover is added to the song (if the fallback format includes a cover codec).
If there's already an album cover embedded in the file, that one will be preferred.
Use this as to replace characters that aren't supported by your device (either in path name, tags, or both).
SupportedUnicodeRanges
: Supported Unicode ranges (e.g.BasicLatin
,Latin1Supplement
,GeneralPunctuation
, etc.)SupportedCharacters
: Additional supported charactersReplacements
: Manual replacement chars (forä
->ae
etc.)NormalizationMode
None
: OffNonBmp
: Replace all non-BMP characters (characters above U+FFFF). Usually required on Android as it doesn't support those charactersUnsupported
: Try to replace characters not in supported list
Characters that are unsupported in file/path names will be replaced by _
automatically.
There are two ways to handle m3u
/ m3u8
playlists:
Each playlist is copied to the target directory. All references are updated to point to the correct file if required (e.g. if the file extension changes due to a format conversion).
Each playlist is created as a directory containing the referenced songs.
The EXTINF
name is used as a file name if available (else the source file name is used).
so the playlist Playlists/Test.m3u8
with the contents
#EXTM3U
#EXTINF:253,An Artist - Song 4
..\Artists\An Artist\An Album\04 Song 4.flac
results in the directory Playlists/Test/
with the file An Artist - Song 4.flac
If set, multiple tags (for example multiple artists) will be separated by this character.
If true
, makes the first character of each file/path name uppercase. Useful for devices that sort by ASCII code instead of (case-insensitive) letter.
If set, limits the directory depth to the specified value (when set to 4, One/Two/Three/Four/Five/Test.mp3
becomes One/Two/Three/Four_Five/Test.mp3
)
Use this to overwrite encoder settings depending on the directory that is being converted. These overrides are based on the FallbackFormat, unless the file already fits all limitations the overrides require.
This can be useful for:
- Setting different encoder settings for different types of media (like reducing channel count or bitrate for audio books)
- Limiting maximum bitrate (just setting
MaxBitrate
means all media above this bitrate will be converted to the FallbackFormat bitrate or MaxBitrate, whichever is lower)
The number of workers to use for each step.
- Sync
Z:\Audio
toE:\Audio
- Use Android device config (convert unsupported files to opus, embed album art as 512x512 jpeg)
config.json
:
{
"SourceDir": "file://Z:\\Audio\\",
"TargetDir": "file://E:\\Audio\\"
}
Run using dotnet run --project src/MusicSyncConverter/MusicSyncConverter -- configs/config.device.Android.json config.json
.
- Sync
Z:\Audio
toE:\Audio
- Convert all files (regardless of current format) to 192kbit/s MP3
- Keep/embed album art as 320x320px JPEG
{
"SourceDir": "file://Z:\\Audio\\",
"TargetDir": "file://E:\\Audio\\",
"DeviceConfig": {
"FallbackFormat": {
"Extension": ".mp3",
"Codec": "mp3", // as required by ffmpeg (-c:a aac)
"Muxer": "mp3", // as required by ffmpeg (usually, this is the container format)
"Bitrate": 192 // kbit/s
},
"AlbumArt": {
"Codec": "mjpeg", // format to use for album covers ("mjpeg" = jpg, "png" = png, null = remove album covers)
"Width": 320, // maximum size of album covers
"Height": 320 // maximum size of album covers
},
}
}
- Sync
Z:\Audio
toE:\
- Reorder file table on target (required if the target device doesn't sort files and/or folders by itself and instead uses the FAT order)
- Exclude
Z:\Audio\Webradio
,Z:\Audio\Music\Artists\Nickelback
andZ:\Audio\Music\Artists\**\Instrumentals
(*
and**
wildcards are supported) - Copy all MP3, WMA and AAC-LC files
- Convert all other files to AAC-LC 192kbit/s
- Convert album covers to jpeg with 320x320 px max and add black borders to make them square
- Replace unsupported characters in path names
- Replace non-BMP characters in path names (characters that can't be represented with UCS-2) (required for Android devices)
- Keep all characters in tags as they are
- Change every first character of file/dir names to uppercase so devices that sort case-sensitive work properly
- Resolve playlists to directories
- Override codec settings for
Z:\Audio\Audio Books
so all audio books are converted to 64kbit/s mono to save storage space
{
"SourceDir": "file://Z:\\Audio\\",
"TargetDir": "file://E:\\?fatSortMode=Folders", // Valid Values are "None", "Files", "Folders", "FilesAndFolders", default is "None"
"KeepInTarget": [
"Videos" // don't delete E:\Videos when syncing
]
"Exclude": [
"Webradio",
"Music\\Artists\\Nickelback",
"Music\\Artists\\**\\Instrumentals"
],
"DeviceConfig": {
"SupportedFormats": [
{
"Extension": ".m4a",
"Codec": "aac", // as reported by ffprobe
"Profile": "LC" // as reported by ffprobe
},
{
"Extension": ".mp3",
"Codec": "mp3"
},
{
"Extension": ".wma",
"Codec": "wmav1"
},
{
"Extension": ".wma",
"Codec": "wmav2"
}
],
"FallbackFormat": {
"Extension": ".m4a",
"Codec": "aac", // as required by ffmpeg (-c:a aac)
"Profile": "aac_low", // as required by ffmpeg (-profile:a aac_low), may be omitted
"Muxer": "ipod", // as required by ffmpeg (usually, this is the container format)
"Bitrate": 192 // kbit/s
},
"AlbumArt": {
"Codec": "mjpeg", // format to use for album covers ("mjpeg" = jpg, "png" = png, null = remove album covers)
"Width": 320, // maximum size of album covers
"Height": 320, // maximum size of album covers
"ResizeType": "ForceOutputAspectRatio" // for devices that stretch albumart to fit
},
"PathCharacterLimitations": { // omit this if your device supports unicode
"SupportedChars": "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+-_ (),'[]!&", // all natively supported characters
"Replacements": [ // characters that should be replaced by different characters
{
"Char": "…",
"Replacement": "..."
}
],
"NormalizationMode": "NonBmp" // can be "None", "NonBmp", "Unsupported", "All"
},
"TagCharacterLimitations": null
"ResolvePlaylists": true // convert playlists to directories with the respective files
},
"PathFormatOverrides": {
"Audio Books/**": {
"MaxBitrate": 64,
"MaxChannels": 1
}
},
"NormalizeCase": true, // change every first character of file/dir names to uppercase
"MaxDirectoryDepth": null, // (don't) limit maximum directory depth
"WorkersRead": 8, // max number of threads to use for reading files
"WorkersConvert": 8, // max number of threads to use for converting files
"WorkersWrite": 1 // max number of threads to use for writing (for slow devices like HDDs, SD cards or flash drives, 1 is usually best)
}