[Mabox Addon] mb-music-add-url

Hi all,

Small add-on (community edition) for mb-music I’ve been using myself for a while now.

The idea is simple — when I’m browsing and finding new tracks I want to add to my YT playlist, I don’t want to open the CSV, find the right category, paste the URL… that kills the flow completely.

So I wrote mb-music-add-url add-on.

  • Copy the URL
  • hit the hotkey to launch the gui
  • fill in artist and title
  • pick a category from the dropdown (or type a new one)

Back to browsing. :slight_smile:

What it does:

  • reads the URL from your clipboard
  • lets you pick an existing category from your yt_music_list.csv or create a new one on the spot
  • inserts the track at the bottom of that category
  • duplicate check within the category
  • i18n: EN / PL / ES

Standalone script, no changes needed to mb-music itself.
Just drop it in ~/bin, bind a hotkey and go.

#!/bin/bash
# mb-music-add-url
# Add a YouTube/stream URL to the mb-music YT playlist CSV.
# User can pick an existing category or type a new one.

CSV_FILE="$HOME/.config/mabox/mb-music/yt_music_list.csv"
LAST_CAT_FILE="$HOME/.config/mabox/mb-music/last_category"
LOGO="$HOME/.config/mabox/mb-music/mabox-ce.svg"

# Ensure config dir, CSV and logo exist
mkdir -p "$(dirname "$CSV_FILE")"
touch "$CSV_FILE"

[ -f "$LOGO" ] || cat > "$LOGO" << 'SVGEOF'
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256">
  <rect width="256" height="256" fill="#1a1a1a"/>
  <path fill="#F5C400" fill-opacity="0.9" d="M80 240H16V16h224v224h-64V80h-16v160H96V80H80z"/>
  <path fill="#1a1a1a" fill-opacity="0.9" d="M0 0v256h256V0Zm16 16h224v224h-64V80h-16v160H96V80H80v160H16Z"/>
  <rect x="28" y="150" width="200" height="68" rx="6" ry="6" fill="#1a1a1a" fill-opacity="0.72"/>
  <text x="128" y="208"
        font-family="'DejaVu Sans','Liberation Sans',Arial,sans-serif"
        font-size="62" font-weight="700" letter-spacing="6"
        text-anchor="middle" fill="#F5C400">CE</text>
</svg>
SVGEOF

# Use CE logo if available, fall back to theme icon
[ -f "$LOGO" ] && YAD_ICON="$LOGO" || YAD_ICON="media-tape"

# ── i18n ──────────────────────────────────────────────────────────────────────
case "$LANG" in
    pl*)
        NEW_CAT_LABEL="[+ Nowa kategoria]"
        _TITLE="Dodaj utwór do listy mb-music"
        _MSG="Uzupełnij pola i wybierz kategorię.\nWpisz nową nazwę lub wybierz z listy."
        _F_URL="URL :"
        _F_ARTIST="Artysta :"
        _F_TITLE="Tytuł :"
        _F_CAT="Kategoria :"
        _F_NEWCAT="Nowa kategoria :"
        _ERR_CLIP="Schowek nie zawiera prawidłowego adresu URL."
        _ERR_FIELDS="Pola URL, Artysta i Tytuł są wymagane."
        _ERR_NEWCAT="Podaj nazwę nowej kategorii."
        _ERR_DUP="Ten utwór już istnieje w tej kategorii."
        _NOTIFY_ADDED="Dodano do kategorii"
        _TITLE_NEWCAT="Nowa kategoria"
        ;;
    es*)
        NEW_CAT_LABEL="[+ Nueva categoría]"
        _TITLE="Añadir pista a la lista mb-music"
        _MSG="Rellena los campos y elige una categoría.\nEscribe un nombre nuevo o elige de la lista."
        _F_URL="URL :"
        _F_ARTIST="Artista :"
        _F_TITLE="Título :"
        _F_CAT="Categoría :"
        _F_NEWCAT="Nueva categoría :"
        _ERR_CLIP="El portapapeles no contiene una URL válida."
        _ERR_FIELDS="Los campos URL, Artista y Título son obligatorios."
        _ERR_NEWCAT="Escribe un nombre para la nueva categoría."
        _ERR_DUP="Esta pista ya existe en esa categoría."
        _NOTIFY_ADDED="Añadido a la categoría"
        _TITLE_NEWCAT="Nueva categoría"
        ;;
    *)
        NEW_CAT_LABEL="[+ New category]"
        _TITLE="Add track to mb-music list"
        _MSG="Fill in the fields and pick a category.\nType a new name or choose from the list."
        _F_URL="URL :"
        _F_ARTIST="Artist :"
        _F_TITLE="Title :"
        _F_CAT="Category :"
        _F_NEWCAT="New category name :"
        _ERR_CLIP="Clipboard does not contain a valid URL."
        _ERR_FIELDS="URL, Artist and Title are all required."
        _ERR_NEWCAT="Please enter a name for the new category."
        _ERR_DUP="This track already exists in that category."
        _NOTIFY_ADDED="Added to category"
        _TITLE_NEWCAT="New category"
        ;;
esac

# ── helpers ───────────────────────────────────────────────────────────────────

warn() {
    yad --warning \
        --center \
        --title="$_TITLE" \
        --window-icon="$YAD_ICON" \
        --image="dialog-error" \
        --text="$1" \
        --button="OK:0" 2>/dev/null
}

# Extract category names (lines ending with ,-) from CSV
get_categories() {
    grep -E ',-$' "$CSV_FILE" | sed 's/,-$//' | sed '/^\s*$/d'
}

# Build a !-separated list for yad combo: "New category" first,
# then last-used category (pre-selected with ^), then the rest.
build_combo() {
    local cats last list
    cats=$(get_categories)
    last=$(cat "$LAST_CAT_FILE" 2>/dev/null | xargs)
    list="$NEW_CAT_LABEL"
    # Add last-used first (pre-selected), skip it in the main loop below
    if [ -n "$last" ] && echo "$cats" | grep -qxF "$last"; then
        list+="!^$last"
    fi
    while IFS= read -r cat; do
        [ -n "$cat" ] || continue
        [ "$cat" = "$last" ] && continue   # already added above
        list+="!$cat"
    done <<< "$cats"
    echo "$list"
}

# Insert $entry right before the next category header after $category,
# or at end of file if it is the last category.
insert_into_category() {
    local category="$1"
    local entry="$2"
    local tmp
    tmp=$(mktemp) || { echo "mktemp failed"; return 1; }

    awk -v cat="$category" -v newline="$entry" '
    BEGIN { found=0; done=0 }
    {
        # When we hit the next category header while inside the target, insert first
        if (!done && found && /,-$/) {
            print newline
            done=1
        }
        print
        # Detect entry into our target category
        if ($0 == cat ",-") found=1
    }
    END {
        # Target was the last category in file
        if (found && !done) print newline
    }
    ' "$CSV_FILE" > "$tmp"

    mv "$tmp" "$CSV_FILE"
}

# Append a new category header + entry at end of file
append_new_category() {
    local category="$1"
    local entry="$2"
    # Add blank line before new category if file is not empty and last line is not blank
    if [ -s "$CSV_FILE" ] && [ -n "$(tail -c1 "$CSV_FILE")" ]; then
        echo "" >> "$CSV_FILE"
    fi
    echo "󰺢 $category,-" >> "$CSV_FILE"
    echo "$entry"      >> "$CSV_FILE"
}

# Check for duplicate entry within a category block
is_duplicate() {
    local category="$1"
    local entry="$2"
    awk -v cat="$category" -v needle="$entry" '
    BEGIN { found=0 }
    { if ($0 == cat ",-") found=1; else if (/,-$/) found=0 }
    found && $0 == needle { exit 1 }
    ' "$CSV_FILE"
    return $?
}

# ── clipboard ─────────────────────────────────────────────────────────────────

CLIPBOARD=$(xclip -o -selection clipboard 2>/dev/null)

# Accept: http(s) URLs or local playable files
if [[ -z "$CLIPBOARD" ]] || \
   [[ ! "$CLIPBOARD" =~ ^https?:// && ! -f "$CLIPBOARD" ]]; then
    CLIPBOARD=""
fi

# Focus URL field when empty so user can paste; otherwise jump to Artist
FOCUS_FIELD=2
[[ -z "$CLIPBOARD" ]] && FOCUS_FIELD=1

# ── main form ─────────────────────────────────────────────────────────────────

COMBO=$(build_combo)

ENTRY=$(yad --form \
    --center \
    --title="$_TITLE" \
    --window-icon="$YAD_ICON" \
    --image="document-save" \
    --text="$_MSG" \
    --width=480 \
    --focus-field="$FOCUS_FIELD" \
    --field="$_F_URL"    "$CLIPBOARD" \
    --field="$_F_ARTIST" "" \
    --field="$_F_TITLE"  "" \
    --field="$_F_CAT:CB" "$COMBO" \
    2>/dev/null)

[ -z "$ENTRY" ] && exit 0   # user cancelled

URL=$(echo    "$ENTRY" | cut -d'|' -f1 | xargs)
ARTIST=$(echo "$ENTRY" | cut -d'|' -f2 | xargs)
TITLE=$(echo  "$ENTRY" | cut -d'|' -f3 | xargs)
CATEGORY=$(echo "$ENTRY" | cut -d'|' -f4 | xargs)

# Strip trailing | if yad adds one
CATEGORY="${CATEGORY%|}"

# ── validate required fields ──────────────────────────────────────────────────

if [[ -z "$URL" || -z "$ARTIST" || -z "$TITLE" ]]; then
    warn "$_ERR_FIELDS"
    exit 1
fi

TRACK_ENTRY="$ARTIST - $TITLE,$URL"

# ── category logic ────────────────────────────────────────────────────────────

if [[ "$CATEGORY" == "$NEW_CAT_LABEL" || -z "$CATEGORY" ]]; then
    # Ask for new category name
    NEW_CAT=$(yad --entry \
        --center \
        --title="$_TITLE_NEWCAT" \
        --window-icon="$YAD_ICON" \
        --image="folder-new" \
        --text="$_F_NEWCAT" \
        2>/dev/null)

    [ -z "$NEW_CAT" ] && exit 0   # cancelled
    NEW_CAT=$(echo "$NEW_CAT" | xargs)

    if [[ -z "$NEW_CAT" ]]; then
        warn "$_ERR_NEWCAT"
        exit 1
    fi

    append_new_category "$NEW_CAT" "$TRACK_ENTRY"
    CATEGORY="$NEW_CAT"

else
    # Check if category actually exists (user may have typed a new name in combo)
    if grep -qF "${CATEGORY},-" "$CSV_FILE"; then
        # Duplicate check
        if ! is_duplicate "$CATEGORY" "$TRACK_ENTRY"; then
            warn "$_ERR_DUP"
            exit 1
        fi
        insert_into_category "$CATEGORY" "$TRACK_ENTRY"
    else
        # Typed a new name not in the list
        append_new_category "$CATEGORY" "$TRACK_ENTRY"
    fi
fi

echo "$CATEGORY" > "$LAST_CAT_FILE"
notify-send -i "$YAD_ICON" -t 5000 "$ARTIST - $TITLE" "$_NOTIFY_ADDED: $CATEGORY"

Here at the time the idea started

EDIT: Added Category dropdown translation
EDIT 02: Added - Last used category
EDIT 03: Added Mabox community edition logo (notification) :wink:

1 Like

Nice.

I’ll check it out.

I created a keybind, but it doesn’t work. Is there some way to reload rc.xml without rebooting that I should be aware of. First time actually editing it in my entire lifetime. Should be noted that the script works perfectly if called from the terminal.

1 Like

To reload

openbox --reconfigure

Or from menu below edit rc.xml.

PS: it could be there is also a hotkey in rc.xml for openbox --reconfigure. I lost it, so cannot show you.

1 Like

NOTE:
Avoid ,and#and maybe other special characters in the fields.

() can be used.

When mb-music menu is not appearing a character could be the source.

UPdate:

Small change.

Artist field active instead of url field for quicker action.

1 Like

That works. Thank you.

1 Like

(code top of topic)

Update:

Added function: Last used Category

1 Like