···11+# `tracksync`: if `rsync` was ID3-aware
22+33+`tracksync` synchronizes music files from a source to a destination, keeping a database for both.
44+55+Tracks need to be added to the source database first, and then can be synced on the destination.
66+77+There is no maximum amount of destinations you can have, each one will maintain its database and can be kept in sync
88+with the source.
99+1010+The destination tree structure is ordered by artist, album, disc number, and track name, as detailed by each ID3 tag.
1111+1212+Pass `-h` to each subcommand to understand how to use it!
1313+1414+`tracksync` can also create hardlinks instead of copies of your files: pass the `--link` flag to `sync` to do so.
1515+1616+## Compiling
1717+1818+You need a [Rust](https://rustup.rs/) compiler.
1919+2020+Once you have that setup:
2121+2222+```sh
2323+git clone https://github.com/gsora/tracksync
2424+cd tracksync
2525+cargo build --release
2626+./target/release/tracksync -h
2727+```
2828+2929+## Filtering
3030+3131+You might want to exclude some tracks from the syncing process, based on various assumption.
3232+3333+Since this tool has been built primarily for my own consumption, I added a programmable way of defining filters.
3434+3535+Each destination can contain a filter written in the [Rhai](https://rhai.rs/): you have the full
3636+power of a regex matching function -- `regex_match` -- and a Turing-complete programming language, have fun!
3737+3838+Filters can be created in-place or read from a file.
3939+4040+Each filter must define the `filter(track)` function in order to be evaluated:
4141+4242+```rhai
4343+fn filter(track) {
4444+ // your logic goes here
4545+ true
4646+}
4747+```
4848+4949+As you can see, `filter` returns a boolean value:
5050+ - `true`: copy this track
5151+ - `false`: the opposite
5252+5353+The `track` argument is an object that contains the following fields:
5454+5555+```rust
5656+pub struct BaseTrack {
5757+ pub title: String,
5858+ pub artist: String,
5959+ pub album: String,
6060+ pub number: i64,
6161+ pub file_path: String,
6262+ pub disc_number: i64,
6363+ pub disc_total: i64,
6464+ pub extension: String,
6565+}
6666+```
6767+6868+As you can see, there's lots of stuff you can do with this functionality.
6969+7070+For example, here's a filter I built to avoid copying instrumental tracks from special edition albums:
7171+7272+```rhai
7373+fn filter(track) {
7474+ track.title.make_lower();
7575+ track.artist.make_lower();
7676+7777+ let excluded_artists = [
7878+ "periphery",
7979+ "i built the sky",
8080+ "anup sastry",
8181+ "louis cole",
8282+ "vulfpeck"
8383+ ];
8484+8585+ for ea in excluded_artists {
8686+ if track.artist == ea {
8787+ return false
8888+ }
8989+ }
9090+9191+ regex_match("instru*", track.title)
9292+}
9393+```
9494+9595+## A note on stability
9696+9797+This is the first CLI tool I wrote in Rust, as a way of making myself familiar with the language: expect bugs.
9898+9999+The database schema might break suddenly, making your source and destination(s) libraries unusable: a `rescan` command
100100+is in the works -- I will make sure to keep those at a minimum.