├── README.md ├── albumtag └── img ├── album-in-player.png ├── album-songs.png ├── demo.gif ├── playlist-description.png ├── playlist-selected.png ├── playlist-tracks.png └── playlist-url.png /README.md: -------------------------------------------------------------------------------- 1 | # Albumtag 2 | A POSIX compliant shell script to write metadata to your albums. 3 | 4 | ## ¿How does it work? 5 | First you have to obtain the album, to do this you have to use `yt-dlp` or `youtube-dl` and download a playlist from youtube. This reason of this is because when an artist release an album, its record label distributes it in different platforms like Spotify, Itunes, Apple Music, etc. One of these platforms is Youtube and it uploads the album in the form of an auto-generated playlist, in other words, **you can find pretty much every album officially in youtube through playlists.** 6 | 7 | After downloading the album the script orders the songs according to the track list of the album and renames each one to add a number prefix, then it gives a prompt to the user to write the metadata and applies it to the songs, in order to do this the songs have to have the opus format. This is already done when you use `yt-dlp`, but if you use `youtube-dl` you'll have to convert them to opus, the convertion process is handled by the script when you comment and uncomment some parts of the script which are mentioned in the script itself. 8 | 9 | ## Installation 10 | ``` 11 | git clone https://github.com/fMorales-97/albumtag.git 12 | cd albumtag/ 13 | chmod +x albumtag 14 | mv albumtag ~/.local/bin 15 | ``` 16 | ## Usage 17 | You start by choosing the album, in this case it'll be Michael Jackson's Thriller. 18 | 19 | ![Michael Jackson's Thriller playlist](img/playlist-selected.png) 20 | *If you want to make sure that you'll be downloading the official album, select the playlist whose thumbnail has colored bars at the sides of the album cover. You can further verify that the you've selected the correct playlist by checking the description of the video* 21 | 22 | ![Video description](img/playlist-description.png) 23 | 24 | *As you can see, the channel is the artist's official one and at the end of the description it says "Auto-generated by YouTube".* 25 | 26 | Then you copy the url of the playlist. 27 | 28 | ![The playlist url](img/playlist-url.png) 29 | 30 | Once that's done, you have to run `yt-dlp -e "your-playlist-url" > tracks` to extract the title of each video of the playlist (which are the track names of the album) and redirect those into a new file called "tracks" that will be necessary later on. 31 | 32 | ![Playlist track names](img/playlist-tracks.png) 33 | 34 | I cat'ed that file to make sure everything is fine. 35 | 36 | After having the "tracks" file you can start the download process by typing `yt-dlp --abort-on-error -x --audio-format best --audio-quality 0 "your-playlist-url"` (or `youtube-dl -xf bestaudio/best "your-playlist-url"` if using `youtube-dl`). This will download the audio of each video with the best quality and in opus format. One thing to have in mind is that **you have to write the option** `--abort-on-error` so the download will stop when having an error, this is because the script orders the songs based on creation time before renaming (or converting) them, if you didn't write this option the download would continue to the next song despite having errors and the script **won't be able** to list the songs in the correct album order. 37 | 38 | Now that you have your songs downloaded from the playlist is time to run the script. Here's an example of how it works. 39 | 40 | ![A demo of the script](img/demo.gif) 41 | 42 | When you run it you have to select the "tracks" file you've created, that is used for two things: 43 | 44 | 1. Extract the total number of tracks in the album. 45 | 2. Make sure that the name of every song is correct. 46 | 47 | The script will give you the following results. 48 | 49 | ![Songs renamed](img/album-songs.png) 50 | 51 | ![Songs in the music player](img/album-in-player.png) 52 | 53 | Every song is organised and renamed as in the album and the music player (lollypop in my case) recognises the written metadata. 54 | 55 | ## Dependencies 56 | * `yt-dlp` or `youtube-dl` if you can't get the first one. 57 | * `opustags`. The program that's used to write the metadata. 58 | * `gst-libav` and `gst-plugin-base`. Those are the ogg codecs necessary to play opus files. 59 | 60 | ## General recommendations 61 | If you have the possibility, use `yt-dlp` instead of `youtube-dl`, the download times are much faster in the former than in the latter, when I used `youtube-dl` to download a 11 song album it took like 17 minutes, but when using `yt-dlp` I got it in 20 seconds. It also has more options to download videos. Another reason is because it handles the portion of url inserted in the filenames much better than `youtube-dl`, so it's more easy to deal with (a more detailed explanation in the section below). 62 | 63 | When you download a playlist, the songs will appear where you currently are, so I recommend you to create a folder with the artist's name inside your music directory and then another one with the name of the album inside the artist folder, `cd` into it and then download the playlist. 64 | 65 | The "tracks" file can be named however you want, it's just a descriptive name that I use. 66 | 67 | ## Known issues and solutions 68 | * Because of how the Linux filesystem works, if one or more songs have a slash in the name `yt-dlp` will replace it with an underscore, the same applies if the file has a colon in the name. Even though this cannot be changed in the filename, it can be changed in the metadata after running the script with the following command `EDITOR=vim opustags --in-place --edit 'name of the song.opus'`. This is the second reason why the creation of the "tracks" file is necessary, this way you'll know beforehand when a song has those characters. *You can use whatever editor you want, it doesn't have to be vim.* 69 | * If you are using `youtube-dl` and you have an album which has any number of songs with dashes in the name, the renaming and metadata writing process **won't be effective** on those songs. This is because when you download a playlist with `youtube-dl` the resulting files have a dash after the name followed by a portion of the videos's url, like this "Name of the song-YkjhHhfh045akjsf.m4a", so, in order to remove that portion of url, the script will delete every character from the dash until the dot of the file extension. As you can imagine, if you have a song with a name like this "Name-of-the-song-YkjhHhfh045akjsf.m4a" the script will delete from the first dash (the one after "Name") until the file extension, the only solution I can think of is to rename the file manually after the script is done and also rewriting the metadata title with this command `EDITOR=vim opustags --in-place --edit 'name of the song.opus'`. This is why **is highly recommended to use yt-dlp** since it wraps the url with square brackets, making the task of deleting the url much more safe. 70 | * The script doesn't separate songs of albums with multiple discs automatically, so, if you have an album with more than one disc (for example "The Wall" by Pink Floyd), you have two options: 71 | 1. After the download has completed, create a subdirectory for each disc, then manually move the songs corresponding to each disc into their respective folders and run the script in each folder, when promted the question if the album has more than one disc type `y` and type the disc number. **Don't forget** that you have to copy the "tracks" and move it into its corresponding disc subdirectory. 72 | 2. You can select a range of videos to download from a playlist, so, if you were to download the album "The Wall" which has 26 songs (13 per disc) you could type `yt-dlp --abort-on-error -x --audio-format best --audio-quality 0 --playlist-items 1,13 "playlist-url"` move those files to the corresponding subdirectory and then type `yt-dlp --abort-on-error -x --audio-format best --audio-quality 0 --playlist-items 14,26 "playlist-url"` to get the remaining songs (don't forget to move them to their subdirectory), and finally run the script in each folder. Again, you have to repeat the process with the "tracks" file that's mentioned above. *This method doesn't work when using `youtube-dl` since it doesn't have the option to download portions of a playlist, another reason to use `yt-dlp`.* 73 | * These official auto-generated playlist are not perfect so there's a low chance that you end up with a playlist that has a random video in between, if this is the case you can (again) select a ranges of videos to download, like so `yt-dlp --abort-on-error -x --audio-format best --audio-quality 0 --playlist-items 1-5,7-26 "playlist-url"`, this way you can download from the first to the fifth video, skip the sixth, and continue from the seventh until the end. *Once again, this doesn't work when using `youtube-dl`.* 74 | 75 | ## Questions and answers 76 | Q: ¿Why do I have to use the opus format? 77 | 78 | A: Because it has much better sound quality with similar (if not better) compression rates than mp3, and it's easier to create and edit the metadata with the program `opustags` 79 | 80 | Q: ¿Does it work in any distro? 81 | 82 | A: Since it's a POSIX compliant shell script and as long as you have the dependencies mentioned above, you shouldn't have any problems, although I haven't tested it in any distro other than Arch Linux. Keep in mind that the dependencies names may change depending on the distro so check the availability of those before running this script. 83 | 84 | Q: ¿How can I get the album covers? 85 | 86 | A: For the album covers I use the music player `lollypop` because it can search for the covers in the Spotify database, as well as AudioDB, iTunes, and others, then you select the cover you want to use and it downloads it to that album directory. This works when you have a properly written metadata in your songs. 87 | 88 | Q: ¿Do I have to always type those long commands? 89 | 90 | A: Not at all, you can use aliases, for example in my `.zshrc` I've written `alias yta="yt-dlp --abort-on-error -x --audio-format best --audio-quality 0"` and also `alias etags="EDITOR=vim opustags --in-place --edit"` so feel free to do the same. 91 | 92 | Q: ¿What do I do when the song title is so long that it has ellipses? 93 | 94 | A: If the name of the song is that long (as in the case of some song names of some 70's progressive rock bands) not even the "tracks" file will help because the names in that file comes from the title of the playlist videos, the best thing you can do is look at the name of the song in the description of the video, it will always be complete there. 95 | -------------------------------------------------------------------------------- /albumtag: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Some variables definition 4 | GREEN="\033[0;32m" 5 | RED="\033[0;31m" 6 | BOLD="\033[1m" 7 | NC="\033[0m" 8 | 9 | # Start 10 | [ ! -f "$1" ] && printf "${RED}${BOLD}You have to select the tracks file${NC}\n" && exit 11 | 12 | TRACK=0 13 | TOTAL="$(wc -l < $1)" 14 | 15 | # List the songs by download order and replace the spaces for dregrees symbols 16 | # in order to have the name of the songs be recognised as a single string of 17 | # text instead of separate lines when being iterated inside the loop. 18 | SONGS=$(ls *.opus --time=creation -r | sed "s/ /°/g") 19 | # Comment the line from above and un-comment the line bellow if using youtube-dl 20 | # SONGS=$(ls *.m4a --time=creation -r | sed "s/ /°/g") 21 | 22 | # The loop 23 | for SONG in $SONGS; do 24 | TRACK="$(printf "%02d" "$(expr $TRACK + 1)")" 25 | SONGNAME="$(printf "$SONG" | sed "s/°/ /g;s/ \[.*\.//g;s/opus$/°$TRACK/g" | awk -F ° '{print $2,$1}' | sed "s/\s/ - /")" 26 | printf "Renaming '$(printf $SONG | sed "s/°/ /g;s/ \[.*//g")'\n" 27 | # Revert the degrees symbols to spaces so the script can rename the original 28 | # file 29 | mv "$(printf $SONG | sed 's/°/ /g')" "$SONGNAME.opus" 30 | 31 | # Comment lines from above and un-comment lines from below if using youtube-dl 32 | # SONGNAME="$(printf "$SONG" | sed "s/°/ /g;s/-.*\.//g;s/m4a$/°$TRACK/g" | awk -F ° '{print $2,$1}' | sed "s/\s/ - /")" 33 | # printf "Convirtiendo '$(printf $SONG | sed "s/°/ /g;s/-.*//g")'\n" 34 | 35 | # Revert the degrees symbols to spaces so ffmpeg can recognise the original 36 | # name and change the song's format 37 | # ffmpeg -nostdin -loglevel 16 -i "$(printf $SONG | sed "s/°/ /g")" "$SONGNAME.opus" 38 | done 39 | 40 | # General metadata 41 | EXIT=false 42 | echo 43 | printf "Name of the album:\n"; read -r ALBUM 44 | printf "Name of the artist:\n"; read -r ARTIST 45 | printf "Year of publishing:\n"; read -r DATE 46 | while [ $EXIT = false ]; do 47 | printf "¿How many genres has the album? [1/2/3]\n"; read ANSWER 48 | case $ANSWER in 49 | 1) 50 | printf "Album genre:\n"; read -r GENRE 51 | EXIT=true 52 | ;; 53 | 2) 54 | printf "First genre of the album:\n"; read -r GENRE 55 | printf "Second genre of the album:\n"; read -r GENRE2 56 | EXIT=true 57 | ;; 58 | 3) 59 | printf "First genre of the album:\n"; read -r GENRE 60 | printf "Second genre of the album:\n"; read -r GENRE2 61 | printf "Third genre of the album:\n"; read -r GENRE3 62 | EXIT=true 63 | ;; 64 | *) 65 | echo 66 | printf "${BOLD}${RED}That's not a valid option${NC}\n" 67 | ;; 68 | esac 69 | done 70 | EXIT=false 71 | while [ $EXIT = false ]; do 72 | printf "¿Does the album has more than one disc? [y/n]\n"; read ANSWER 73 | case $ANSWER in 74 | y) 75 | printf "Disc number:\n"; read -r DISCNUMBER 76 | EXIT=true 77 | ;; 78 | n) 79 | EXIT=true 80 | ;; 81 | *) 82 | echo 83 | printf "${RED}${BOLD}That's not a valid option${NC}\n" 84 | ;; 85 | esac 86 | done 87 | echo 88 | TRACK=0 89 | 90 | # Apply metadata 91 | if [ -n "$GENRE2" ]; then 92 | for SONG in *.opus; do 93 | NAME="$(printf "$SONG" | awk '{print substr($0, index($0, $3))}' | sed 's/\.opus$//g')" 94 | TRACK="$(printf "$(expr $TRACK + 1)")" 95 | printf "Writing metadata of '$SONG'\n" 96 | opustags "$SONG" --in-place -D 97 | 98 | # If the album has more than one disc 99 | if [ -n "$DISCNUMBER" ]; then 100 | opustags "$SONG" --in-place -a "TITLE=$NAME" -a "ALBUM=$ALBUM" -a "ARTIST=$ARTIST" -a "DATE=$DATE" -a "GENRE=$GENRE" -a "GENRE=$GENRE2" -a "TRACKNUMBER=$TRACK" -a "TOTALTRACKS=$TOTAL" -a "DISCNUMBER=$DISCNUMBER" 101 | else 102 | opustags "$SONG" --in-place -a "TITLE=$NAME" -a "ALBUM=$ALBUM" -a "ARTIST=$ARTIST" -a "DATE=$DATE" -a "GENRE=$GENRE" -a "GENRE=$GENRE2" -a "TRACKNUMBER=$TRACK" -a "TOTALTRACKS=$TOTAL" 103 | fi 104 | done 105 | elif [ -n "$GENRE3" ]; then 106 | for SONG in *.opus; do 107 | NAME="$(printf "$SONG" | awk '{print substr($0, index($0, $3))}' | sed 's/\.opus$//g')" 108 | TRACK="$(printf "$(expr $TRACK + 1)")" 109 | printf "Writing metadata of '$SONG'\n" 110 | opustags "$SONG" --in-place -D 111 | 112 | # If the album has more than one disc 113 | if [ -n "$DISCNUMBER" ]; then 114 | opustags "$SONG" --in-place -a "TITLE=$NAME" -a "ALBUM=$ALBUM" -a "ARTIST=$ARTIST" -a "DATE=$DATE" -a "GENRE=$GENRE" -a "GENRE=$GENRE2" -a "GENRE=$GENRE3" -a "TRACKNUMBER=$TRACK" -a "TOTALTRACKS=$TOTAL" -a "DISCNUMBER=$DISCNUMBER" 115 | else 116 | opustags "$SONG" --in-place -a "TITLE=$NAME" -a "ALBUM=$ALBUM" -a "ARTIST=$ARTIST" -a "DATE=$DATE" -a "GENRE=$GENRE" -a "GENRE=$GENRE2" -a "GENRE=$GENRE3" -a "TRACKNUMBER=$TRACK" -a "TOTALTRACKS=$TOTAL" 117 | fi 118 | done 119 | else 120 | for SONG in *.opus; do 121 | NAME="$(printf "$SONG" | awk '{print substr($0, index($0, $3))}' | sed 's/\.opus$//g')" 122 | TRACK="$(printf "$(expr $TRACK + 1)")" 123 | printf "Writing metadata of '$SONG'\n" 124 | opustags "$SONG" --in-place -D 125 | 126 | # If the album has more than one disc 127 | if [ -n "$DISCNUMBER" ]; then 128 | opustags "$SONG" --in-place -a "TITLE=$NAME" -a "ALBUM=$ALBUM" -a "ARTIST=$ARTIST" -a "DATE=$DATE" -a "GENRE=$GENRE" -a "TRACKNUMBER=$TRACK" -a "TOTALTRACKS=$TOTAL" -a "DISCNUMBER=$DISCNUMBER" 129 | else 130 | opustags "$SONG" --in-place -a "TITLE=$NAME" -a "ALBUM=$ALBUM" -a "ARTIST=$ARTIST" -a "DATE=$DATE" -a "GENRE=$GENRE" -a "TRACKNUMBER=$TRACK" -a "TOTALTRACKS=$TOTAL" 131 | fi 132 | done 133 | fi 134 | 135 | # Dialog to remove unnecessary files 136 | EXIT=false 137 | while [ $EXIT = false ]; do 138 | echo 139 | printf "¿Do you want to remove unnecessary files? [y/n]\n" && read ANSWER 140 | case "$ANSWER" in 141 | y) 142 | rm tracks 143 | # Comment the line from above and un-comment the line below if using youtube-dl 144 | # rm *.m4a tracks 145 | echo 146 | printf "${GREEN}${BOLD}¡Done!${NC}\n" 147 | EXIT=true 148 | ;; 149 | n) 150 | echo 151 | printf "${GREEN}${BOLD}¡Done!${NC}\n" 152 | EXIT=true 153 | ;; 154 | *) 155 | echo 156 | printf "${RED}${BOLD}That's not a valid option${NC}\n" 157 | ;; 158 | esac 159 | done 160 | -------------------------------------------------------------------------------- /img/album-in-player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fMorales-97/albumtag/f146c1cd39263079b5b0702e9397bf73d67f59ea/img/album-in-player.png -------------------------------------------------------------------------------- /img/album-songs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fMorales-97/albumtag/f146c1cd39263079b5b0702e9397bf73d67f59ea/img/album-songs.png -------------------------------------------------------------------------------- /img/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fMorales-97/albumtag/f146c1cd39263079b5b0702e9397bf73d67f59ea/img/demo.gif -------------------------------------------------------------------------------- /img/playlist-description.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fMorales-97/albumtag/f146c1cd39263079b5b0702e9397bf73d67f59ea/img/playlist-description.png -------------------------------------------------------------------------------- /img/playlist-selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fMorales-97/albumtag/f146c1cd39263079b5b0702e9397bf73d67f59ea/img/playlist-selected.png -------------------------------------------------------------------------------- /img/playlist-tracks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fMorales-97/albumtag/f146c1cd39263079b5b0702e9397bf73d67f59ea/img/playlist-tracks.png -------------------------------------------------------------------------------- /img/playlist-url.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fMorales-97/albumtag/f146c1cd39263079b5b0702e9397bf73d67f59ea/img/playlist-url.png --------------------------------------------------------------------------------