From 503856e135cb8615e8a88686f2463ff9f5a40f39 Mon Sep 17 00:00:00 2001 From: teraflops Date: Thu, 29 May 2025 18:51:00 +0200 Subject: [PATCH] initial commit --- LICENSE | 18 + README.md | 60 ++ usr/local/bin/mympc | 635 ++++++++++++++++++ usr/local/share/mympc/add_mood_tag.py | 122 ++++ usr/local/share/mympc/get_top_rated_tracks.py | 33 + usr/local/share/mympc/tags.conf | 8 + usr/local/share/mympc/tags.sh | 45 ++ usr/local/share/zsh/site-functions/_mympc | 189 ++++++ 8 files changed, 1110 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100755 usr/local/bin/mympc create mode 100755 usr/local/share/mympc/add_mood_tag.py create mode 100755 usr/local/share/mympc/get_top_rated_tracks.py create mode 100644 usr/local/share/mympc/tags.conf create mode 100755 usr/local/share/mympc/tags.sh create mode 100644 usr/local/share/zsh/site-functions/_mympc diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d7be675 --- /dev/null +++ b/LICENSE @@ -0,0 +1,18 @@ +MIT No Attribution + +Copyright 2024 teraflops + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ff123d3 --- /dev/null +++ b/README.md @@ -0,0 +1,60 @@ +# mympc + +## Wrapper around the [mpc](https://musicpd.org/clients/mpc/) command line client for controlling a [Music Player Daemon](https://musicpd.org/) server. + +It does zsh completion for the mpc command and provides a few functions to make it easier to use mpc. + +### Installation. + +copy the files in the same path as they are in the repo. Add the following to your .zshrc file: + +```zsh +autoload -Uz compinit +compinit +``` +```bash +Usage: /usr/local/bin/mympc [options] [arguments] + +Options: + --artist [name] Search for songs by artist (comma-separated) + --genre [name] Search for songs by genre + --album [name] Search for songs by album + --track [name] Search for songs by track name (comma-separated) + --year [year] Search for songs by release year (e.g., --year=1990) + --duration [min:sec] Search for songs by duration (e.g., --duration=3:30) + -n, --number [number] Specify the number of songs to add to the playlist (default: 15) + --play Play directly after adding songs to the playlist + --preview Preview the songs that will be added to the playlist without adding them + --save-playlist [name] Save the current playlist with the given name + --remove [track1,track2,...] Remove one or more songs from the playlist, separated by commas + --list-artists List all available artists + --list-genres List all available genres + --list-albums List all available albums + --random Activate random mode + --repeat Activate repeat mode + --get-rating Get the rating of the current song + --rate [0-5] Set the rating of the current song (0-5) + --toprated Generate a playlist of top-rated songs (rating=5) + --add-tag [key=value] Add a custom tag to the current song + --delete-tag [key] Delete a custom tag from the current song + --list-tags List all tags of the current song + --update-tag [key=value] Update a custom tag of the current song + --playlist-by-mood [mood] Generate a playlist based on the specified mood + --playlist-by-mood=[mood] Same as above, using '=' syntax + --help, -h Show this help message + +Examples: + /usr/local/bin/mympc --artist="Pink Floyd,AC/DC" --play -n 10 + /usr/local/bin/mympc --album="The Dark Side of the Moon" --play + /usr/local/bin/mympc --genre="Blues" --year=1980 --play -n 5 + /usr/local/bin/mympc --track="Comfortably Numb,Money" --play + /usr/local/bin/mympc --artist="Pink Floyd" --track="Money,Time" --play + /usr/local/bin/mympc --get-rating + /usr/local/bin/mympc --rate 4 + /usr/local/bin/mympc --toprated --play + /usr/local/bin/mympc --add-tag "mood=happy" + /usr/local/bin/mympc --delete-tag "mood" + /usr/local/bin/mympc --list-tags + /usr/local/bin/mympc --update-tag "mood=energetic" + /usr/local/bin/mympc --playlist-by-mood=happy --play +``` diff --git a/usr/local/bin/mympc b/usr/local/bin/mympc new file mode 100755 index 0000000..df19bb6 --- /dev/null +++ b/usr/local/bin/mympc @@ -0,0 +1,635 @@ +#!/bin/bash + +# mympc: A script to interact with mpc in an efficient and flexible way. + +# ======================== +# Initial Setup +# ======================== + +# Source centralized tag definitions +# Ensure that /usr/local/share/mympc/tags.sh exists and is properly configured +if [[ -f /usr/local/share/mympc/tags.sh ]]; then + source /usr/local/share/mympc/tags.sh +else + echo "Error: /usr/local/share/mympc/tags.sh not found. Please create it with necessary tag definitions." + exit 1 +fi + +# Function to display help message +show_help() { + cat << EOF +Usage: $0 [options] [arguments] + +Options: + --artist [name] Search for songs by artist + --genre [name] Search for songs by genre + --album [name] Search for songs by album + --track [name] Search for songs by track name + --year [year] Search for songs by release year (e.g., --year=1990) + --duration [min:sec] Search for songs by duration (e.g., --duration=3:30) + -n, --number [number] Specify the number of songs to add to the playlist (default: 15) + --play Play directly after adding songs to the playlist + --preview Preview the songs that will be added to the playlist without adding them + --save-playlist [name] Save the current playlist with the given name + --remove [track1,track2,...] Remove one or more songs from the playlist, separated by commas + --list-artists List all available artists + --list-genres List all available genres + --list-albums List all available albums + --random Activate random mode + --repeat Activate repeat mode + --get-rating Get the rating of the current song + --rate [0-5] Set the rating of the current song (0-5) + --toprated Generate a playlist of top-rated songs (rating=5) + --add-tag [key=value] Add a custom tag to the current song + --delete-tag [key] Delete a custom tag from the current song + --list-tags List all tags of the current song + --update-tag [key=value] Update a custom tag of the current song + --playlist-by-mood [mood] Generate a playlist based on the specified mood + --help, -h Show this help message + +Examples: + $0 --artist="Pink Floyd,AC/DC" --play -n 10 + $0 --album="The Dark Side of the Moon" --play + $0 --genre="Blues" --year=1980 --play -n 5 + $0 --track="Comfortably Numb,Money" --play + $0 --artist="Pink Floyd" --track="Money,Time" --play + $0 --get-rating + $0 --rate 4 + $0 --toprated --play + $0 --add-tag "mood=happy" + $0 --delete-tag "mood" + $0 --list-tags + $0 --update-tag "mood=energetic" + $0 --playlist-by-mood=happy --play +EOF +} + +# Function to get the URI of the current song +get_current_song_uri() { + mpc --format %file% current +} + +# Function to check if a tag exists on a song +tag_exists() { + local song="$1" + local key="$2" + mpc sticker "$song" list 2>/dev/null | cut -d'=' -f1 | grep -qw "$key" +} + +# Function to check if a value is in an array +is_in_array() { + local element + for element in "${@:2}"; do + [[ "$element" == "$1" ]] && return 0 + done + return 1 +} + +delete_song_from_playlist() { + local position="$1" + + # Validate that position is a positive integer + if ! [[ "$position" =~ ^[1-9][0-9]*$ ]]; then + echo "Error: Position must be a positive integer." + exit 1 + fi + + # Delete the song at the specified position + if mpc del "$position"; then + echo "Song at position $position deleted from the playlist." + else + echo "Error: Failed to delete song at position $position." + exit 1 + fi +} + +# ======================== +# Initialize Variables +# ======================== + +number_of_songs=15 +play_now=false +preview=false +declare -A search_params +artists=() +genres=() +albums=() +tracks=() +years=() +durations=() +remove_songs=() +playlist_name="" +get_rating=false +set_rating_value="" +toprated=false + +# Tag-related variables +add_tag="" +delete_tag="" +list_tags=false +update_tag="" + +# Playlist by mood variables +mood_value="" +playlist_name_override="" + +# ======================== +# Option Parsing with getopt +# ======================== + +# Define the options +TEMP=$(getopt -o n:h --long artist:,genre:,album:,track:,year:,duration:,number:,play,preview,save-playlist:,remove:,list-artists,list-genres,list-albums,random,repeat,get-rating,rate:,toprated,add-tag:,delete-tag:,list-tags,update-tag:,playlist-by-mood:,playlist-by-mood,help -n "$0" -- "$@") + +# Check if getopt succeeded +if [ $? != 0 ] ; then show_help; exit 1 ; fi + +# Reorder the arguments +eval set -- "$TEMP" + +# Parse the options +while true; do + case "$1" in +--artist) + if [[ -n "${search_params[genre]}" || -n "${search_params[artist]}" ]]; then + echo "Error: --artist cannot be used with --genre or multiple times." + exit 1 + fi + shift + # Usar eval para manejar correctamente las comillas y espacios + artist_names=$(eval echo "$1") + IFS=',' read -ra ARTISTS_ARRAY <<< "$artist_names" + for artist in "${ARTISTS_ARRAY[@]}"; do + artist=$(echo "$artist" | xargs) + if [[ "$artist" =~ [^a-zA-Z0-9\ \.\,\&\'\-\/] ]]; then + echo "Error: Artist names can only contain letters, numbers, spaces, and characters like .,&,',-,/." + exit 1 + fi + artists+=("$artist") + done + search_params[artist]=1 + shift + ;; + --genre) + if [[ -n "${search_params[artist]}" || -n "${search_params[album]}" || -n "${search_params[genre]}" ]]; then + echo "Error: --genre cannot be used with --artist, --album, or multiple times." + exit 1 + fi + shift + genres+=("$1") + search_params[genre]=1 + shift + ;; +--album) + if [[ -n "${search_params[genre]}" || -n "${search_params[album]}" || -n "${search_params[number]}" ]]; then + echo "Error: --album cannot be used with --genre, --number, or multiple times." + exit 1 + fi + shift + # Usar un enfoque más robusto para manejar comillas y apóstrofes + album_name="$1" + # Escapar solo lo necesario para mpc sin usar eval + albums+=("$album_name") + search_params[album]=1 + shift + ;; + +--track) + if [[ -n "${search_params[track]}" ]]; then + echo "Error: --track cannot be used multiple times." + exit 1 + fi + shift + track_names="$1" + IFS=',' read -ra TRACKS_ARRAY <<< "$track_names" + for track in "${TRACKS_ARRAY[@]}"; do + track=$(echo "$track" | xargs) + if [[ "$track" =~ [^a-zA-Z0-9\ \.\,\&\'\-\/] ]]; then + echo "Error: Track names can only contain letters, numbers, spaces, and characters like .,&,',-,/." + exit 1 + fi + tracks+=("$track") + done + search_params[track]=1 + shift + ;; + --year) + if [[ -n "${search_params[year]}" ]]; then + echo "Error: --year cannot be used multiple times." + exit 1 + fi + shift + if ! [[ "$1" =~ ^(19|20)[0-9]{2}$ ]]; then + echo "Error: --year must be a valid year (e.g., 1990)." + exit 1 + fi + years+=("$1") + search_params[year]=1 + shift + ;; + --duration) + if [[ -n "${search_params[duration]}" ]]; then + echo "Error: --duration cannot be used multiple times." + exit 1 + fi + shift + if ! [[ "$1" =~ ^([0-9]+):([0-5][0-9])$ ]]; then + echo "Error: --duration must be in the format min:sec (e.g., 3:30)." + exit 1 + fi + durations+=("$1") + search_params[duration]=1 + shift + ;; + -n|--number) + if [[ -n "${search_params[album]}" || "$toprated" = true || -n "${search_params[number]}" ]]; then + echo "Error: --number cannot be used with --album, --toprated, or multiple times." + exit 1 + fi + shift + number_of_songs="$1" + if ! [[ "$number_of_songs" =~ ^[1-9][0-9]*$ ]]; then + echo "Error: --number must be a positive integer." + exit 1 + fi + search_params[number]=1 + shift + ;; + --play) + play_now=true + shift + ;; + --preview) + preview=true + shift + ;; + --save-playlist) + shift + playlist_name_override="$1" + shift + ;; + --remove) + shift + delete_song="$1" + shift + # Parse the position from "position: song name" + if [[ "$delete_song" =~ ^([0-9]+):\ (.+)$ ]]; then + delete_position="${BASH_REMATCH[1]}" + delete_song_name="${BASH_REMATCH[2]}" + else + echo "Error: Invalid format for --remove. Expected 'position: song name'." + exit 1 + fi + # Call the function to delete the song from the playlist + delete_song_from_playlist "$delete_position" + exit 0 + ;; + --list-artists) + mpc list artist + exit 0 + ;; + --list-genres) + mpc list genre + exit 0 + ;; + --list-albums) + mpc list album + exit 0 + ;; + --random) + mpc random on + echo "Random mode activated." + exit 0 + ;; + --repeat) + mpc repeat on + echo "Repeat mode activated." + exit 0 + ;; + --get-rating) + get_rating=true + shift + ;; + --rate) + if [[ -n "$set_rating_value" ]]; then + echo "Error: --rate cannot be used multiple times." + exit 1 + fi + shift + set_rating_value="$1" + if ! [[ "$set_rating_value" =~ ^[0-5]$ ]]; then + echo "Error: Rating must be between 0 and 5." + exit 1 + fi + shift + ;; + --toprated) + if [[ -n "${search_params[number]}" || "$toprated" = true ]]; then + echo "Error: --toprated cannot be used with --number or multiple times." + exit 1 + fi + toprated=true + shift + ;; + --add-tag) + shift + add_tag="$1" + # Validate the format key=value + if ! [[ "$add_tag" =~ ^[^=]+=[^=]+$ ]]; then + echo "Error: --add-tag must be in the format key=value." + exit 1 + fi + shift + ;; + --delete-tag) + shift + delete_tag="$1" + shift + ;; + --playlist-by-mood=*) + mood_value="${key#*=}" + # Invoke the Python script to handle playlist generation + /usr/local/share/mympc/add_mood_tag.py "$mood_value" + shift # past argument + ;; + --playlist-by-mood) + mood_value="$2" + # Invoke the Python script to handle playlist generation + /usr/local/share/mympc/add_mood_tag.py "$mood_value" + shift # past argument + exit 0 + ;; + --list-tags) + # Handle --list-tags + list_tags=true + shift + ;; + --update-tag) + shift + update_tag="$1" + # Validate the format key=value + if ! [[ "$update_tag" =~ ^[^=]+=[^=]+$ ]]; then + echo "Error: --update-tag must be in the format key=value." + exit 1 + fi + shift + ;; + --help|-h) + show_help + exit 0 + ;; + --) + shift + break + ;; + *) + echo "Invalid option: $1" + echo "Use --help to see available options." + exit 1 + ;; + esac +done + +# ======================== +# Execute Tag Actions +# ======================== + +# Verify if mpc supports the sticker command +if ! mpc help | grep -q 'sticker'; then + echo "Error: Your mpc version does not support the sticker command." + exit 1 +fi + +# List all tags of the current song +if [ "$list_tags" = true ]; then + song_uri=$(get_current_song_uri) + if [[ -z "$song_uri" ]]; then + echo "No song is currently playing." + exit 1 + fi + tags=$(mpc sticker "$song_uri" list 2>/dev/null) + if [ -z "$tags" ]; then + echo "No tags found for the current song." + else + echo "Tags for '$song_uri':" + echo "$tags" + fi + exit 0 +fi + +# Add a custom tag to the current song +if [ -n "$add_tag" ]; then + song_uri=$(get_current_song_uri) + if [[ -z "$song_uri" ]]; then + echo "Error: No song is currently playing." + exit 1 + fi + key="${add_tag%%=*}" + value="${add_tag#*=}" + + # Validate the key and value + if [[ "$key" == "mood" ]]; then + if ! is_in_array "$value" "${allowed_moods[@]}"; then + echo "Error: Invalid mood value '$value'. Allowed values are: ${allowed_moods[*]}" + exit 1 + fi + elif [[ " ${allowed_custom_tags[*]} " != *" $key "* ]]; then + echo "Error: Invalid tag key '$key'. Allowed keys are: ${allowed_custom_tags[*]}" + exit 1 + fi + + mpc sticker "$song_uri" set "$key" "$value" + echo "Tag '$key=$value' added to '$song_uri'." + exit 0 +fi + +# Delete a custom tag from the current song +if [ -n "$delete_tag" ]; then + song_uri=$(get_current_song_uri) + if [[ -z "$song_uri" ]]; then + echo "Error: No song is currently playing." + exit 1 + fi + if tag_exists "$song_uri" "$delete_tag"; then + mpc sticker "$song_uri" delete "$delete_tag" + echo "Tag '$delete_tag' deleted from '$song_uri'." + else + echo "Error: Tag '$delete_tag' does not exist on '$song_uri'." + exit 1 + fi + exit 0 +fi + +# Update a custom tag of the current song +if [ -n "$update_tag" ]; then + song_uri=$(get_current_song_uri) + if [[ -z "$song_uri" ]]; then + echo "Error: No song is currently playing." + exit 1 + fi + key="${update_tag%%=*}" + value="${update_tag#*=}" + + # Validate the key and value + if [[ "$key" == "mood" ]]; then + if ! is_in_array "$value" "${allowed_moods[@]}"; then + echo "Error: Invalid mood value '$value'. Allowed values are: ${allowed_moods[*]}" + exit 1 + fi + elif [[ " ${allowed_custom_tags[*]} " != *" $key "* ]]; then + echo "Error: Invalid tag key '$key'. Allowed keys are: ${allowed_custom_tags[*]}" + exit 1 + fi + + if tag_exists "$song_uri" "$key"; then + mpc sticker "$song_uri" set "$key" "$value" + echo "Tag '$key' updated to '$value' in '$song_uri'." + else + echo "Error: Tag '$key' does not exist on '$song_uri'. Use --add-tag to add it." + exit 1 + fi + exit 0 +fi + +# ======================== +# Execute Rating Actions +# ======================== + +if [ "$get_rating" = true ]; then + song_uri=$(get_current_song_uri) + if [[ -z "$song_uri" ]]; then + echo "No song is currently playing." + exit 1 + fi + rating=$(mpc sticker "$song_uri" get rating 2>/dev/null | sed 's/^rating=//') + if [ -z "$rating" ]; then + echo "No rating found for the current song." + else + echo "Rating of '$song_uri' is: $rating" + fi + exit 0 +fi + +if [ -n "$set_rating_value" ]; then + song_uri=$(get_current_song_uri) + if [[ -z "$song_uri" ]]; then + echo "Error: No song is currently playing." + exit 1 + fi + mpc sticker "$song_uri" set rating "$set_rating_value" + echo "Rating of '$song_uri' set to $set_rating_value." + exit 0 +fi + +# ======================== +# Final Validation +# ======================== + +if [ "$toprated" = false ] && [ "${#search_params[@]}" -eq 0 ]; then + echo "Error: You must specify at least one search criteria." + show_help + exit 1 +fi + +# ======================== +# Build the Search Command +# ======================== + +# Clear the current playlist +mpc clear + +if [ "$toprated" = true ]; then + # Call the Python script to get top-rated tracks + # Ensure that /usr/local/share/mympc/get_top_rated_tracks.py exists and is executable + if [ ! -x /usr/local/share/mympc/get_top_rated_tracks.py ]; then + echo "Error: Python script '/usr/local/share/mympc/get_top_rated_tracks.py' not found or not executable." + exit 1 + fi + + python3 /usr/local/share/mympc/get_top_rated_tracks.py + if [ "$play_now" = true ]; then + mpc play + echo "Playing playlist." + fi + exit 0 +fi + +# Construct the search command +search_cmd="mpc search" + +for key in "${!search_params[@]}"; do + if [[ "$key" == "artist" ]]; then + for artist in "${artists[@]}"; do + # Escapar comillas internas y mantener las externas para mpc + artist_escaped="${artist//\"/\\\"}" + search_cmd+=" artist \"$artist_escaped\"" + done + elif [[ "$key" == "genre" ]]; then + for genre in "${genres[@]}"; do + genre_escaped="${genre//\"/\\\"}" + search_cmd+=" genre \"$genre_escaped\"" + done + elif [[ "$key" == "album" ]]; then + for album in "${albums[@]}"; do + album_escaped="${album//\"/\\\"}" + search_cmd+=" album \"$album_escaped\"" + done + elif [[ "$key" == "track" ]]; then + for track in "${tracks[@]}"; do + track_escaped="${track//\"/\\\"}" + search_cmd+=" title \"$track_escaped\"" + done + elif [[ "$key" == "year" ]]; then + for year in "${years[@]}"; do + search_cmd+=" date \"$year\"" + done + elif [[ "$key" == "duration" ]]; then + for duration in "${durations[@]}"; do + search_cmd+=" duration \"$duration\"" + done + fi +done + +# Debugging: print the search command (uncomment if needed) +# echo "Executing search command: $search_cmd" + +# Execute the search command and capture the songs +songs=$(eval "$search_cmd") + +if [ -z "$songs" ]; then + echo "No songs found with the specified criteria." + exit 1 +else + if [[ -n "${search_params[album]}" || -n "${search_params[track]}" ]]; then + # Add all found songs to the playlist + echo "$songs" | mpc add + num_songs_added=$(echo "$songs" | wc -l) + echo "Added $num_songs_added song(s) to the playlist." + else + # Add a random subset of songs to the playlist + selected_songs=$(echo "$songs" | shuf -n"$number_of_songs") + echo "$selected_songs" | mpc add + num_songs_added=$(echo "$selected_songs" | wc -l) + echo "Added $num_songs_added song(s) to the playlist." + fi +fi + +# ======================== +# Save Playlist +# ======================== + +if [ -n "$playlist_name" ]; then + mpc save "$playlist_name" + echo "Playlist saved as '$playlist_name'." +fi + +# ======================== +# Play Playlist +# ======================== + +if [ "$play_now" = true ]; then + mpc play + echo "Playing playlist." +fi + +exit 0 + diff --git a/usr/local/share/mympc/add_mood_tag.py b/usr/local/share/mympc/add_mood_tag.py new file mode 100755 index 0000000..cd6a566 --- /dev/null +++ b/usr/local/share/mympc/add_mood_tag.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 + +import sys +import argparse +from mpd import MPDClient, CommandError +import random + +# Define the list of allowed mood tags +ALLOWED_MOOD_TAGS = { + "happy", + "sad", + "energetic", + "calm", + "angry", + "relaxed", + "excited", + "melancholic" +} + +def parse_arguments(): + parser = argparse.ArgumentParser( + description="Add songs with a specified mood tag to the MPD playlist." + ) + parser.add_argument( + "mood", + type=str, + help=f"Desired mood tag. Allowed values: {', '.join(sorted(ALLOWED_MOOD_TAGS))}" + ) + parser.add_argument( + "--host", + type=str, + default="localhost", + help="MPD server host (default: localhost)" + ) + parser.add_argument( + "--port", + type=int, + default=6600, + help="MPD server port (default: 6600)" + ) + return parser.parse_args() + +def add_tracks_by_mood(mood, host="localhost", port=6600): + mood = mood.lower() + if mood not in ALLOWED_MOOD_TAGS: + print(f"Error: Invalid mood '{mood}'. Allowed moods are: {', '.join(sorted(ALLOWED_MOOD_TAGS))}.", file=sys.stderr) + sys.exit(1) + + client = MPDClient() + try: + # Connect to the MPD server + client.connect(host, port) + print(f"Connected to MPD server at {host}:{port}.") + + # Clear the current playlist + client.clear() + print("Cleared the current playlist.") + + # Search for songs with the specified mood tag + # Assuming 'mood' is stored as a sticker in the format 'mood=' + # Using sticker_find to search for songs with the 'mood' sticker + response = client.sticker_find('song', '', 'mood') + + # Process the response to filter tracks with the specified mood tag + matching_tracks = [] + for entry in response: + file_path = entry.get('file') + sticker = entry.get('sticker') + if file_path and sticker: + try: + sticker_key, sticker_value = sticker.split('=', 1) + if sticker_key == 'mood' and sticker_value.lower() == mood: + matching_tracks.append(file_path) + except ValueError: + # Skip malformed sticker entries + continue + + if matching_tracks: + # Remove duplicates + unique_tracks = sorted(set(matching_tracks)) + + # Shuffle songs for randomness + random.shuffle(unique_tracks) + + # Add unique songs to the playlist + for track in unique_tracks: + client.add(track) + print(f"Added {len(unique_tracks)} unique song(s) with mood '{mood}' to the playlist.") + + # Optionally, save the playlist with a specific name + # client.save('mood_playlist') + + # Optionally, start playing the playlist + # client.play() + + sys.exit(0) # Success + else: + print(f"No songs found with mood '{mood}'.") + sys.exit(0) # Treat as success since it's not a critical error + + except CommandError as ce: + print(f"MPD command error: {ce}", file=sys.stderr) + sys.exit(1) + except Exception as e: + print(f"An error occurred: {e}", file=sys.stderr) + sys.exit(1) + finally: + # Close the connection to the MPD server + try: + client.close() + client.disconnect() + print("Disconnected from MPD server.") + except Exception: + pass + +def main(): + args = parse_arguments() + add_tracks_by_mood(args.mood, host=args.host, port=args.port) + +if __name__ == "__main__": + main() + diff --git a/usr/local/share/mympc/get_top_rated_tracks.py b/usr/local/share/mympc/get_top_rated_tracks.py new file mode 100755 index 0000000..a2fe9e3 --- /dev/null +++ b/usr/local/share/mympc/get_top_rated_tracks.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 + +from mpd import MPDClient + +def get_top_rated_tracks(): + client = MPDClient() + client.connect("localhost", 6600) # Ajusta el host y el puerto si es necesario + + # Utiliza el URI raíz o un directorio específico + response = client.sticker_find('song', '', 'rating') + + # Procesar la respuesta + tracks = [] + for entry in response: + if 'file' in entry and 'sticker' in entry: + sticker_name, sticker_value = entry['sticker'].split('=', 1) + if sticker_name == 'rating' and sticker_value == '5': + tracks.append(entry['file']) + + if tracks: + for track in tracks: + client.add(track) + print(f"Added {len(tracks)} song(s) with a rating of 5 to the playlist.") + else: + print("there are no rated 5 songs.") + + client.close() + client.disconnect() + +if __name__ == '__main__': + get_top_rated_tracks() + + diff --git a/usr/local/share/mympc/tags.conf b/usr/local/share/mympc/tags.conf new file mode 100644 index 0000000..8cee0b2 --- /dev/null +++ b/usr/local/share/mympc/tags.conf @@ -0,0 +1,8 @@ +# ~/.mympc/tags.conf + +# Tag keys and their possible values +mood=happy,sad,energetic,calm,angry,relaxed,excited,melancholic +genre=rock,pop,jazz,blues,classical +rating=1,2,3,4,5 +# Add more tags and their values as needed + diff --git a/usr/local/share/mympc/tags.sh b/usr/local/share/mympc/tags.sh new file mode 100755 index 0000000..61b5250 --- /dev/null +++ b/usr/local/share/mympc/tags.sh @@ -0,0 +1,45 @@ +# ~/.mympc/tags.sh +# ======================== +# Centralized Tag Definitions for mympc +# ======================== + +# Array of allowed mood values +allowed_moods=( + "happy" + "sad" + "energetic" + "calm" + "angry" + "relaxed" + "excited" + "melancholic" + # Add more moods as needed +) + +# Array of allowed genre values +allowed_genres=( + "rock" + "pop" + "jazz" + "blues" + "classical" + "electronic" + "hiphop" + "country" + # Add more genres as needed +) + +# Array of allowed rating values (optional) +allowed_ratings=( "0" "1" "2" "3" "4" "5" ) + +# Array of other custom tag keys (if any) +allowed_custom_tags=( + "custom1" + "custom2" + # Add more custom tag keys as needed +) + +# ======================== +# End of tags.sh +# ======================== + diff --git a/usr/local/share/zsh/site-functions/_mympc b/usr/local/share/zsh/site-functions/_mympc new file mode 100644 index 0000000..f7d21e8 --- /dev/null +++ b/usr/local/share/zsh/site-functions/_mympc @@ -0,0 +1,189 @@ +#compdef mympc + +# Fixed list of moods +_fixed_moods=("happy" "sad" "energetic" "calm" "angry" "relaxed" "excited" "melancholic") + +_mympc() { + _arguments -s -C \ + '--artist=[Search songs by artist (separated by commas)]:artist names:_my_mpc_artists' \ + '--genre=[Search songs by genre]:genre name:_my_mpc_genres' \ + '--album=[Search songs by album]:album name:_my_mpc_albums' \ + '--track=[Search songs by track (separated by commas)]:track names:_my_mpc_tracks' \ + '--year=[Filter songs by release year]:year:_mympc_years' \ + '--duration=[Filter songs by duration (min:sec)]:duration:_mympc_durations' \ + '--number=[Specify the number of songs]:number of songs:_mympc_number' \ + '-n=[Specify the number of songs]:number of songs:_mympc_number' \ + '--play[Play immediately]' \ + '--preview[Preview songs to be added without adding them]' \ + '--save-playlist=[Save the current playlist]:playlist name:_my_mpc_playlists' \ + '--remove=[Remove songs from the playlist (separated by commas)]:songs to remove:_my_mpc_remove_songs' \ + '--list-artists[List all available artists]' \ + '--list-genres[List all available genres]' \ + '--list-albums[List all available albums]' \ + '--random[Activate random mode]' \ + '--repeat[Activate repeat mode]' \ + '--get-rating[Get the rating of the current song]' \ + '--rate=[Set the current song rating (0-5)]:rating value (0-5):_mympc_ratings' \ + '--toprated[Generate a list with top-rated tracks (5)]' \ + '--add-tag=[Add a custom tag (key=value)]:custom tag:_mympc_add_tags' \ + '--delete-tag=[Delete a custom tag]:tag key:_mympc_delete_tags' \ + '--list-tags[List all tags of the current song]' \ + '--update-tag=[Update a custom tag (key=value)]:custom tag:_mympc_update_tags' \ + '--playlist-by-mood=[Generate a playlist based on mood]:mood:_my_mpc_playlist_by_mood' \ + '--help[Show help message]' \ + '-h[Show help message]' +} + +# Custom autocomplete functions + +_my_mpc_artists() { + compadd -- ${(f)"$(mpc list artist)"} +} + +_my_mpc_genres() { + compadd -- ${(f)"$(mpc list genre)"} +} + +_my_mpc_albums() { + local artist="" + local -a albums + + # Buscar el artista en los argumentos ya escritos + for arg in ${words[@]}; do + if [[ $arg =~ --artist= ]]; then + # Extraer el nombre del artista (manejando espacios escapados) + artist=${arg#--artist=} + artist=${artist//\\ / } + break + fi + done + + if [[ -n $artist ]]; then + # Obtener álbumes específicos del artista (manejando comillas) + albums=(${(f)"$(mpc list album artist "$artist")"}) + else + # Obtener todos los álbumes si no hay artista especificado + albums=(${(f)"$(mpc list album)"}) + fi + + # Mostrar los álbumes como opciones de completado + _describe 'album' albums +} +_my_mpc_tracks() { + compadd -- ${(f)"$(mpc list title)"} +} + +_my_mpc_playlists() { + compadd -- ${(f)"$(mpc lsplaylists)"} +} +_my_mpc_remove_songs() { + # Define arrays for predefined tag values if needed + local _fixed_moods=("happy" "sad" "energetic" "calm" "angry" "relaxed" "excited" "melancholic") + local _fixed_genres=("rock" "pop" "jazz" "blues" "classical" "hiphop" "electronic") + local _fixed_ratings=("1" "2" "3" "4" "5") + + # Fetch the playlist with position and title + local IFS=$'\n' # Handle song titles with spaces + local songs=($(mpc playlist -f "%position%: %title%")) + + # Initialize an array for completions + local -a completions + + for song in "${songs[@]}"; do + completions+=("${song}") + done + + # Provide the completions + compadd -- "${completions[@]}" +} + +_mympc_years() { + local -a years + years=($(mpc list date | grep -E '^(19|20)[0-9]{2}$' | sort -u)) + compadd -- "${years[@]}" +} + +_mympc_durations() { + compadd -- "1:00" "2:00" "3:00" "4:00" "5:00" "6:00" "7:00" "8:00" "9:00" "10:00" # Adjust durations as needed +} + +# Autocomplete for numbers +_mympc_number() { + _numbers +} + +# Autocomplete for rating values +_mympc_ratings() { + compadd -- 0 1 2 3 4 5 +} + +# Autocomplete for adding tags (key=value) +_mympc_add_tags() { + # Get the current word being completed + local tag_word="${words[CURRENT]}" + + # Check if the user has already typed '=' indicating key and is now completing the value + if [[ "$tag_word" == " " ]]; then + # Extract the key from 'key=value' + local key="${tag_word%%=*}" + local value="${tag_word#*=}" + + if [[ "$key" == "mood" ]]; then + # Suggest only predefined mood values + compadd -- "${_fixed_moods[@]}" + else + # For other keys, do not suggest anything to prevent file completions + return 0 + fi + else + # Suggest tag keys + local keys=("mood" "genre" "rating" "custom1" "custom2") + compadd -- "${keys[@]}" + fi +} + +# Autocomplete for updating tags (key=value) +_mympc_update_tags() { + # Get the current word being completed + local tag_word="${words[CURRENT]}" + + if [[ "$tag_word" == " " ]]; then + # Extract the key from 'key=value' + local key="${tag_word%%=*}" + local value="${tag_word#*=}" + + if [[ "$key" == "mood" ]]; then + # Suggest only predefined mood values + compadd -- "${_fixed_moods[@]}" + else + # For other keys, do not suggest anything to prevent file completions + return 0 + fi + else + # Suggest existing tag keys from the current song + local song_uri + song_uri=$(mpc --format %file% current) + local -a keys + keys=($(mpc sticker "$song_uri" list 2>/dev/null | cut -d'=' -f1)) + compadd -- "${keys[@]}" + fi +} + +# Autocomplete for deleting tags (only keys) +_mympc_delete_tags() { + local song_uri + song_uri=$(mpc --format %file% current) + local -a tags + tags=($(mpc sticker "$song_uri" list 2>/dev/null | cut -d'=' -f1)) + compadd -- "${tags[@]}" +} + +# Autocomplete for generating playlist by mood +_my_mpc_playlist_by_mood() { + # Use the predefined mood list for suggestions + _values "mood" "${_fixed_moods[@]}" +} + +# Register the autocompletion function with Zsh +compdef _mympc mympc +