{"id":17823331,"url":"https://github.com/purarue/plaintext-playlist","last_synced_at":"2025-03-18T16:30:24.866Z","repository":{"id":94610482,"uuid":"266924187","full_name":"purarue/plaintext-playlist","owner":"purarue","description":"An interactive terminal playlist manager; stores playlists as plain text files","archived":false,"fork":false,"pushed_at":"2024-12-27T02:03:12.000Z","size":457,"stargazers_count":11,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-28T10:44:39.164Z","etag":null,"topics":["cli","fzf","fzf-scripts","mpv","music","music-library","playlist","playlist-manager"],"latest_commit_sha":null,"homepage":"","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/purarue.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-05-26T02:18:57.000Z","updated_at":"2024-12-27T02:03:16.000Z","dependencies_parsed_at":"2024-12-27T03:17:01.697Z","dependency_job_id":"d7f251f6-d659-4c03-b298-18e2b55d98eb","html_url":"https://github.com/purarue/plaintext-playlist","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/purarue%2Fplaintext-playlist","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/purarue%2Fplaintext-playlist/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/purarue%2Fplaintext-playlist/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/purarue%2Fplaintext-playlist/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/purarue","download_url":"https://codeload.github.com/purarue/plaintext-playlist/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243940058,"owners_count":20372044,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","fzf","fzf-scripts","mpv","music","music-library","playlist","playlist-manager"],"created_at":"2024-10-27T17:57:30.269Z","updated_at":"2025-03-18T16:30:24.861Z","avatar_url":"https://github.com/purarue.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# plaintext-playlist\n\n![Example Image](./.github/example.png)\n\n```\nUsage: plainplay [-h] [-] [OPTIONS] [COMMAND [ARGS]]\n\n\tAn interactive terminal playlist manager; stores playlists as text files\n\trun without a COMMAND to drop into interactive mode\n\n\t[playlist] specifies either the\n\tname (without the location/.txt extension)\n\tor the location of one of the playlists\n\n\tcurplaying uses my mpv-currently-playing script from\n\thttps://github.com/purarue/mpv-sockets\n\nAdditional Flags:\n\t\n\tadd: A hyphen (-) can be passed with to instead\n\treceive filenames from stdin. expects filenames to\n\tbe in the correct format\n\t(cd to your Music dir and use find for good results)\n\n\te.g.: find somedirectory -name \"*.flac\" | plainplay - add rock\n\n\tresolve: --auto-confirm to automatically\n\tuse the closest match instead of prompting you to choose\n\tone of the closest matching files to fix broken filepaths\n\n\tm3u: --abs to use absolute paths for the generated m3u file,\n\tinstead of paths relative to your Music directory\n\n\tm3u: --duration to include the duration in the m3u file\n\n\te.g. plainplay --abs --duration m3u rock\n\nadd [playlist]                | Adds one or more songs to a playlist\ncurplaying [playlist]         | Adds a currently playing mpv song to a playlist\nremove [playlist]             | Removes one of more songs from a playlist\nplay [playlist]               | Play songs from a playlist\nplayall [playlist]...         | Play songs from multiple playlists\nshuffle [playlist]            | Shuffle songs from a playlist\nshuffleall [playlist]...      | Shuffle songs from multiple playlists\nsingle [playlist]             | Play a single song from a playlist\nlist [playlist]               | List songs in a playlist\nlistall [playlist]...         | List songs from multiple playlists\nunique [playlist]             | Reduce a playlist to unique songs\nexif [playlist]               | Displays exif data for items in a playlist\nm3u [playlist]...             | Create a m3u playlist file from multiple playlists\nedit [playlist]               | Edit a playlist file with your $EDITOR\nplaylist-create [playlist]    | Creates a new playlist - a playlist file\nplaylist-delete [playlist]    | Delete an existing playlist - a playlist file\nplaylist-list                 | List the full paths of each of your playlist files\nplaylistdir                   | Print the location of the playlist directory\ncheck                         | Makes sure that all songs in all your playlists exist\nresolve                       | Attempts to fix broken paths in playlists\n```\n\n## Rationale\n\nI wanted a minimal, scriptable-friendly playlist for my local music, without having to rely on a third party playlist manager/GUI interface.\n\n_This stores playlists as text files, one per playlist, where each line is the (relative) path to a song in the playlist._\n\nThis includes a [`fzf`](https://github.com/junegunn/fzf) backed interactive mode. If you don't provide a playlist, this drops into interactive mode, letting you fuzzy match against playlist names, select multiple songs to add/remove, or select playlists to play/shuffle from.\n\nThis only stores the relative filepath to your base music directory in each file, so you could move your music directory somewhere else and update the environment variable, and everything works, even across computers. However, filenames tend to change, and sometimes you might change the name of an artists' folder, or the name of an album to include metadata. So, `plainplay` has commands to help fix that:\n\n- the `check` command, to make sure none of your playlists are broken; all your filepaths still exist\n- the `resolve` command, which tries to fix the broken paths by using the [distance between](https://github.com/life4/textdistance) the text\n\n`resolve` will use the dice coefficient to try and resolve the broken filepath to an existing filepath in your music directory.\n\n### Configuration/Installation\n\nTo install, download the two scripts `plainplay`/`resolve_cmd_plainplay` and put it on your `$PATH` somewhere, e.g.:\n\n```sh\ngit clone https://github.com/purarue/plaintext-playlist\ncd plaintext-playlist\ncp plainplay resolve_cmd_plainplay ~/.local/bin\n```\n\nCould also use [`basher`](https://github.com/basherpm/basher):\n\n```bash\nbasher install purarue/plaintext-playlist\n```\n\nRequires at least `bash` version 4.0.\n\nExternal dependencies: `mpv`, `fzf`, `python3`,(`pip3 install --user -U textdistance pyfzf_iter`), `ffprobe` (installed with `ffmpeg`), `jq`\n\nThis follows 'Progressive Enhancement' with regard to external dependencies; for example, if you never use `resolve`, the corresponding dependency isn't required.\n\nStores configuration (playlists) at `PLAINTEXT_PLAYLIST_PLAYLISTS` (defaults to `~/.local/share/plaintext_playlist`).\n\nYou must set `PLAINTEXT_PLAYLIST_MUSIC_DIR` as an environment variable, which defines your 'root' music directory. If you don't have one place you keep all your music, you can set your `$HOME` directory, or `/`, which would cause the playlist files to use absolute paths instead. However, that would make the `resolve` function work very slowly, since it would have to search your entire system to find paths to match broken paths against.\n\nFor `zsh` completion support, see [here](https://purarue.xyz/d/_plainplay).\n\n### Basic Scripting\n\nSince the specification/file format is extremely simple, it integrates nicely with lots of shell tools that work on lines of text.\n\nTo add songs:\n\n`cd $HOME/Music \u0026\u0026 find 'Daft Punk' -iname '*fragments of time*.mp3' \u003e\u003e ~/.local/share/plaintext_playlist/electronic.txt`\n\nTo remove songs:\n\n`sed -i -e '/Fragments/d' \"$(plainplay playlistdir)\"/electronic.txt`\n\nPlaylists are played through `mpv`, by using the `--playlist` flag, reading from standard input. The equivalent command without `plainplay` would be:\n\n`cd $HOME/Music \u0026\u0026 mpv --playlist=- \u003c \"$HOME/.local/share/plaintext_playlist/electronic.txt\"`\n\nIf I want to selectively play songs from all my playlists, I can do so like:\n\n```\n$ cd ~/Music\n$ grep -hiE 'mario|runescape|kirby|pokemon' \\\n\t$(find $(plainplay playlistdir) -type f) \\\n\t| shuf | mpv --playlist=-\n```\n\n... which would shuffle songs from my playlists which have paths that match one of `mario|runescape|kirby|pokemon`\n\nCould instead use `listall` to print all the lines in playlists, then `grep` against those:\n\n```\ncd ~/Music \u0026\u0026 play listall $(plainplay playlistdir)/* | grep -i 'mario' | mpv --shuffle --playlist=-\n```\n\nAdditionally, since this is just lines of text, you're free to turn the `playlistdir` into a git-tracked directory; I push to a private git repo periodically just so I have this backed up:\n\n![](https://raw.githubusercontent.com/purarue/plaintext-playlist/master/.github/playlists_git.png)\n\nI have lots of aliases I use to selectively play songs from my playlists ([`functions.sh`](./functions.sh)):\n\n```bash\n# --msg-level=file=error removes the 'reading from stdin...' info message\nalias mpv-from-stdin='mpv --playlist=- --no-audio-display --msg-level=file=error'\nalias mpv-shuffle='mpv-from-stdin --shuffle'\n# change directory to music/playlist directories\nalias cm='cd \"${PLAINTEXT_PLAYLIST_MUSIC_DIR:-${XDG_MUSIC_DIR:-\"${HOME}/Music\"}}\"'\nalias cdpl='cd \"${PLAINTEXT_PLAYLIST_PLAYLISTS}\"'\n# shorthands\nalias play='plainplay'\nalias pplay='plainplay play'\nalias splay='plainplay shuffle'\nalias splayall='fd . \"$PLAINTEXT_PLAYLIST_PLAYLISTS\" -X plainplay shuffleall'\n# list/play all music that matches 'rg' pattern\nplayrg_f() {\n\tcm\n\t# https://github.com/purarue/pura-utils/blob/main/shellscripts/unique\n\tfd . \"$(plainplay playlistdir)\" --type file -X cat | unique | rg -i \"$*\"\n}\n# play all paths that match whatever I pass as positional arguments\nplayrg-_f() {\n\tcm\n\tplayrg_f \"$*\" | mpv-from-stdin\n}\n# use aliases so that the 'cd' actually changes directory in the shell\nalias playrg='cm; playrg_f'\nalias 'playrg-=cm; playrg-_f'\n# fzf to play music\nalias playfzf='cm; rg --color never --with-filename --no-heading \"\" \"${PLAINTEXT_PLAYLIST_PLAYLISTS}/\"*.txt | sed -e \"s|^${PLAINTEXT_PLAYLIST_PLAYLISTS}/||\" | fzf'\nalias 'playfzf-=playfzf | cut -d\":\" -f2- | mpv-from-stdin'\n```\n\nTo create an archive of a playlist, (when in your top-level Music directory) can use tar like:\n\n`tar -cvf playlist_name.tar -T \u003c(plainplay list \u003cplaylistname\u003e)`\n\n### Companion Scripts\n\nAs some more complicated examples of what this enables me to do:\n\nI use `mpv`'s IPC sockets (see my [`mpv-sockets`](https://github.com/purarue/mpv-sockets) scripts) to to send commands to the currently running `mpv` instance. The `mpv-currently-playing` script from there prints the path of the currently playing song. Whenever I'm listening to an album and I want to add a song to a playlist, I do `plainplay curplaying`, it drops me into `fzf` to pick a playlist, and it adds the song that's currently playing to whatever I select.\n\n[`not-in-playlist`](https://github.com/purarue/plaintext_playlist_py/blob/master/bin/not-in-playlist), which I use to find any albums in my music directory which don't have any songs in any of my playlists, i.e. pick a random album in my music directory I haven't listened to yet.\n\n#### Syncing music and playlists to my phone\n\n[`linkmusic`](https://github.com/purarue/plaintext_playlist_py/blob/master/bin/linkmusic) is a `rsync`-like script which creates hardlinks for every file in my playlists into a separate directory (e.g., `~/.local/share/musicsync/`). Then, I use [`syncthing`](https://github.com/syncthing/syncthing) to sync all the songs in my playlists across my computers/onto my phone, without syncing my entire music collection\n\nOn my phone (android), I use [`foobar2000`](https://www.foobar2000.org/apk), which accepts `m3u8` files as playlists. So, using the `plainplay m3u` command, I can [re-create the `m3u8` files](https://purarue.xyz/d/create_playlists.job?dark) in my top-level music directory on my phone, which foobar can then use:\n\n\u003cimg src=\"./.github/phone_playlists.png\" width=\"400\" /\u003e\n\nTo shuffle the `m3u8` files, I wrote a separate tool [`m3u-shuf`](https://github.com/purarue/m3u-shuf)\n\nAn example of me getting the [music/playlist configuration/paths to work across devices](https://github.com/purarue/dotfiles/blob/23e18977a15b3fa4a968626bd3655a7a2a6c8a88/.profile#L79-L104) (`XDG_MUSIC_DIR` and `PLAINTEXT_PLAYLIST_PLAYLISTS`)\n\nPython library [here](https://github.com/purarue/plaintext_playlist_py) which has code to glob the `.txt` files from `plaintext-playlist`, as well as a couple other misc scripts, like [validating id3 data](https://github.com/purarue/plaintext_playlist_py/blob/master/bin/id3stuff), or [removing private (amazon/gracenote) id3 frames](https://github.com/purarue/HPI-personal/blob/master/scripts/mpv-clean-priv-frames) using data saved by [`mpv-history-daemon`](https://github.com/purarue/mpv-history-daemon)\n\n### Specification\n\nTo clarify, the filenames in each playlist file should have no leading `/`. As an example, if `PLAINTEXT_PLAYLIST_MUSIC_DIR=\"${HOME}/Music\"` and you wanted to add a song at `\"${HOME}/Music/ArtistName/AlbumName/Disc2/song.flac\"` to the playlist, the corresponding line would be:\n\n```\nArtistName/AlbumName/Disc2/song.flac\n```\n\n... which is then combined to `\"${HOME}/Music/ArtistName/AlbumName/Disc2/song.flac\"`\n\nIf you don't specify exactly that format, you can run the `check`/`resolve` commands, which will attempt to remove absolute paths/match the closest path and prompt you to update the playlist file.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpurarue%2Fplaintext-playlist","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpurarue%2Fplaintext-playlist","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpurarue%2Fplaintext-playlist/lists"}