tools

various tools I have been using throughout the years
Log | Files | Refs | README | LICENSE

commit 0c2d45d9df8938ca790371b719cde8fd4c6c6e92
parent 029901cd0fb66bc83afe39d31e687114ced3f429
Author: mtmn <miro@haravara.org>
Date:   Wed,  6 May 2026 22:20:55 +0200

feat: add mpd scripts

Diffstat:
Ampd/BUILD | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ampd/mpd_add_to_playlist.c | 93+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ampd/mpd_add_to_queue.c | 114+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ampd/mpd_edit_queue.c | 210+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ampd/mpd_update_library.c | 153+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ampd/mpd_update_queue.c | 137+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 770 insertions(+), 0 deletions(-)

diff --git a/mpd/BUILD b/mpd/BUILD @@ -0,0 +1,63 @@ +load("@rules_cc//cc:defs.bzl", "cc_binary") +load("//bazel:local-deploy.bzl", "local_deploy") + +package(default_visibility = ["//visibility:public"]) + +COMMON_COPTS = [ + "-Wall", + "-Wextra", + "-O2", + "-fanalyzer", + "-Wshadow", +] + +cc_binary( + name = "mpd_add_to_playlist", + srcs = ["mpd_add_to_playlist.c"], + copts = COMMON_COPTS, + linkopts = ["-lmpdclient"], + visibility = ["//visibility:public"], +) + +cc_binary( + name = "mpd_add_to_queue", + srcs = ["mpd_add_to_queue.c"], + copts = COMMON_COPTS, + linkopts = ["-lmpdclient"], + visibility = ["//visibility:public"], +) + +cc_binary( + name = "mpd_update_queue", + srcs = ["mpd_update_queue.c"], + copts = COMMON_COPTS, + linkopts = ["-lmpdclient"], + visibility = ["//visibility:public"], +) + +cc_binary( + name = "mpd_edit_queue", + srcs = ["mpd_edit_queue.c"], + copts = COMMON_COPTS, + linkopts = ["-lmpdclient"], + visibility = ["//visibility:public"], +) + +cc_binary( + name = "mpd_update_library", + srcs = ["mpd_update_library.c"], + copts = COMMON_COPTS, + linkopts = ["-lmpdclient"], + visibility = ["//visibility:public"], +) + +local_deploy( + name = "deploy", + srcs = [ + ":mpd_add_to_playlist", + ":mpd_add_to_queue", + ":mpd_update_queue", + ":mpd_edit_queue", + ":mpd_update_library", + ], +) diff --git a/mpd/mpd_add_to_playlist.c b/mpd/mpd_add_to_playlist.c @@ -0,0 +1,93 @@ +#include <mpd/client.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +// #define DEBUG +#ifdef DEBUG +#define D(x) \ + do { \ + x; \ + } while (0) +#else +#define D(x) \ + do { \ + } while (0) +#endif +#define DEFAULT_HOST "localhost" +#define DEFAULT_PORT 6600 +struct mpd_connection *conn() { + // Read host from environment variable, use default if not set + const char *host = getenv("MPD_HOST"); + if (host == NULL || strlen(host) == 0) { + host = DEFAULT_HOST; + } + D(printf("Using host: %s\n", host)); + + // Read port from environment variable, use default if not set + unsigned port = DEFAULT_PORT; + const char *port_str = getenv("MPD_PORT"); + if (port_str != NULL && strlen(port_str) > 0) { + port = (unsigned)atoi(port_str); + } + D(printf("Using port: %u\n", port)); + + D(printf("%s %s:%u\n", "Connecting to", host, port)); + + struct mpd_connection *c = mpd_connection_new(host, port, 0); + enum mpd_error err = mpd_connection_get_error(c); + if (err != 0) { + printf("Error code: %u. View error codes here: " + "https://www.musicpd.org/doc/libmpdclient/error_8h.html\n", + err); + mpd_connection_free(c); + return 0; + } +#ifdef PASS + const char *pass = PASS; + if (mpd_run_password(c, pass) == false) { + printf("%s\n", "Bad password"); + mpd_connection_free(c); + return 0; + } +#endif + D(printf("%s %s:%u\n", "Connected to", host, port)); + return c; +} +int main(int argc, char *argv[]) { + // Check for playlist name argument + if (argc < 2) { + printf("Usage: %s PLAYLIST_NAME\n", argv[0]); + return 1; + } + + const char *playlist = argv[1]; + D(printf("Using playlist: %s\n", playlist)); + + struct mpd_connection *c = conn(); + if (c == 0) + return -1; + + struct mpd_song *curr = mpd_run_current_song(c); + if (curr == NULL) { + printf("No song is currently playing\n"); + mpd_connection_free(c); + return -1; + } + + const char *curr_uri = mpd_song_get_uri(curr); + D(printf("Currently playing: %s\n", curr_uri)); + + if (mpd_run_playlist_add(c, playlist, curr_uri)) { + printf("%s %s %s %s\n", "Added", curr_uri, "to playlist", playlist); + } else { + printf("%s\n", "Some error"); + mpd_song_free(curr); + mpd_connection_free(c); + return -1; + } + // Free resources + mpd_song_free(curr); + mpd_connection_free(c); + + return 0; +} diff --git a/mpd/mpd_add_to_queue.c b/mpd/mpd_add_to_queue.c @@ -0,0 +1,114 @@ +#include <mpd/client.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> +#include <unistd.h> + +// #define DEBUG +#ifdef DEBUG +#define D(x) \ + do { \ + x; \ + } while (0) +#else +#define D(x) \ + do { \ + } while (0) +#endif + +#define DEFAULT_HOST "localhost" +#define DEFAULT_PORT 6600 + +struct mpd_connection *conn() { + const char *host = getenv("MPD_HOST"); + if (host == NULL || strlen(host) == 0) { + host = DEFAULT_HOST; + } + + unsigned port = DEFAULT_PORT; + const char *port_str = getenv("MPD_PORT"); + if (port_str != NULL && strlen(port_str) > 0) { + port = (unsigned)atoi(port_str); + } + + struct mpd_connection *c = mpd_connection_new(host, port, 0); + if (c == NULL) { + fprintf(stderr, "Failed to create MPD connection object\n"); + return NULL; + } + + enum mpd_error err = mpd_connection_get_error(c); + if (err != 0) { + fprintf(stderr, "Error connecting to MPD (%s:%u): %s (code: %u)\n", + host, port, mpd_connection_get_error_message(c), err); + mpd_connection_free(c); + return NULL; + } +#ifdef PASS + const char *pass = PASS; + if (mpd_run_password(c, pass) == false) { + fprintf(stderr, "MPD authentication failed: %s\n", mpd_connection_get_error_message(c)); + mpd_connection_free(c); + return NULL; + } +#endif + return c; +} + +int main(int argc, char *argv[]) { + FILE *input = stdin; + + if (argc > 1) { + input = fopen(argv[1], "r"); + if (input == NULL) { + perror("Error opening file"); + return 1; + } + } else if (isatty(STDIN_FILENO)) { + fprintf(stderr, "Reading from stdin... (Press Ctrl+D to finish, or provide a filename as argument)\n"); + } + + struct mpd_connection *c = conn(); + if (c == NULL) { + if (input != stdin) fclose(input); + return -1; + } + + char *line = NULL; + size_t len = 0; + ssize_t read_bytes; + int added_count = 0; + int failed_count = 0; + + while ((read_bytes = getline(&line, &len, input)) != -1) { + if (read_bytes > 0 && line[read_bytes - 1] == '\n') { + line[read_bytes - 1] = '\0'; + read_bytes--; + } + if (read_bytes > 0 && line[read_bytes - 1] == '\r') { + line[read_bytes - 1] = '\0'; + read_bytes--; + } + + if (read_bytes == 0) continue; + if (line[0] == '#') continue; + + if (!mpd_run_add(c, line)) { + fprintf(stderr, "Failed to add track: %s (Error: %s)\n", line, mpd_connection_get_error_message(c)); + failed_count++; + if (!mpd_connection_clear_error(c)) break; + } else { + printf("Added: %s\n", line); + added_count++; + } + } + + printf("\nSummary: Added %d tracks, failed to add %d tracks.\n", added_count, failed_count); + + free(line); + if (input != stdin) fclose(input); + mpd_connection_free(c); + + return 0; +} diff --git a/mpd/mpd_edit_queue.c b/mpd/mpd_edit_queue.c @@ -0,0 +1,210 @@ +#include <mpd/client.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> +#include <unistd.h> +#include <sys/wait.h> +#include <ctype.h> + +#define DEFAULT_HOST "localhost" +#define DEFAULT_PORT 6600 + +struct mpd_connection *conn() { + const char *host = getenv("MPD_HOST"); + if (host == NULL || strlen(host) == 0) host = DEFAULT_HOST; + + unsigned port = DEFAULT_PORT; + const char *port_str = getenv("MPD_PORT"); + if (port_str != NULL && strlen(port_str) > 0) port = (unsigned)atoi(port_str); + + struct mpd_connection *c = mpd_connection_new(host, port, 0); + if (c == NULL) { + fprintf(stderr, "Failed to create MPD connection object\n"); + return NULL; + } + + if (mpd_connection_get_error(c) != MPD_ERROR_SUCCESS) { + fprintf(stderr, "Error connecting to MPD (%s:%u): %s\n", host, port, mpd_connection_get_error_message(c)); + mpd_connection_free(c); + return NULL; + } + return c; +} + +char* trim_whitespace(char* str) { + char *end; + while(isspace((unsigned char)*str)) str++; + if(*str == 0) return str; + end = str + strlen(str) - 1; + while(end > str && isspace((unsigned char)*end)) end--; + end[1] = '\0'; + return str; +} + +int main() { + struct mpd_connection *c = conn(); + if (c == NULL) return 1; + + struct mpd_status *status = mpd_run_status(c); + if (status == NULL) { + fprintf(stderr, "Error getting status: %s\n", mpd_connection_get_error_message(c)); + mpd_connection_free(c); + return 1; + } + enum mpd_state state = mpd_status_get_state(status); + int current_pos = mpd_status_get_song_pos(status); + int total_queue_len = mpd_status_get_queue_length(status); + mpd_status_free(status); + + char *current_file = NULL; + if (state == MPD_STATE_PLAY || state == MPD_STATE_PAUSE) { + struct mpd_song *song = mpd_run_current_song(c); + if (song != NULL) { + current_file = strdup(mpd_song_get_uri(song)); + mpd_song_free(song); + } + } + + char tmp_template[] = "/tmp/mpd_queue_XXXXXX"; + int fd = mkstemp(tmp_template); + if (fd == -1) { + perror("mkstemp"); + if (current_file) free(current_file); + mpd_connection_free(c); + return 1; + } + + FILE *f = fdopen(fd, "w"); + if (f == NULL) { + perror("fdopen"); + close(fd); + unlink(tmp_template); + if (current_file) free(current_file); + mpd_connection_free(c); + return 1; + } + + if (!mpd_send_list_queue_meta(c)) { + fprintf(stderr, "Error sending queue request\n"); + fclose(f); + unlink(tmp_template); + if (current_file) free(current_file); + mpd_connection_free(c); + return 1; + } + + struct mpd_entity *entity; + while ((entity = mpd_recv_entity(c)) != NULL) { + if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) { + const struct mpd_song *song = mpd_entity_get_song(entity); + fprintf(f, "%s\n", mpd_song_get_uri(song)); + } + mpd_entity_free(entity); + } + fclose(f); + + if (mpd_connection_get_error(c) != MPD_ERROR_SUCCESS) { + fprintf(stderr, "Error reading queue: %s\n", mpd_connection_get_error_message(c)); + unlink(tmp_template); + if (current_file) free(current_file); + mpd_connection_free(c); + return 1; + } + mpd_response_finish(c); + + const char *editor = getenv("EDITOR"); + if (editor == NULL || strlen(editor) == 0) editor = "vi"; + + pid_t pid = fork(); + if (pid == -1) { + perror("fork"); + unlink(tmp_template); + if (current_file) free(current_file); + mpd_connection_free(c); + return 1; + } else if (pid == 0) { + execlp(editor, editor, tmp_template, NULL); + perror("execlp"); + exit(1); + } + + int wstatus; + waitpid(pid, &wstatus, 0); + + f = fopen(tmp_template, "r"); + if (f == NULL) { + perror("fopen"); + unlink(tmp_template); + if (current_file) free(current_file); + mpd_connection_free(c); + return 1; + } + + char **new_tracks = NULL; + int track_count = 0; + char *line = NULL; + size_t len = 0; + ssize_t read_bytes; + int new_pos = -1; + + while ((read_bytes = getline(&line, &len, f)) != -1) { + char *trimmed = trim_whitespace(line); + if (strlen(trimmed) == 0 || trimmed[0] == '#') continue; + + char **temp_tracks = realloc(new_tracks, sizeof(char *) * (track_count + 1)); + if (temp_tracks == NULL) { + perror("realloc"); + goto cleanup; + } + new_tracks = temp_tracks; + + new_tracks[track_count] = strdup(trimmed); + if (new_tracks[track_count] == NULL) { + perror("strdup"); + goto cleanup; + } + + if (current_file && strcmp(new_tracks[track_count], current_file) == 0 && new_pos == -1) { + new_pos = track_count; + } + track_count++; + } + + if (!mpd_command_list_begin(c, false)) { + fprintf(stderr, "Failed to start command list: %s\n", mpd_connection_get_error_message(c)); + goto cleanup; + } + + if (state == MPD_STATE_STOP || current_file == NULL || new_pos == -1) { + mpd_send_clear(c); + for (int i = 0; i < track_count; i++) { + mpd_send_add(c, new_tracks[i]); + } + if (state != MPD_STATE_STOP) mpd_send_play(c); + } else { + for (int i = total_queue_len - 1; i >= 0; i--) { + if (i != current_pos) mpd_send_delete(c, i); + } + for (int i = 0; i < track_count; i++) { + if (i != new_pos) mpd_send_add(c, new_tracks[i]); + } + if (new_pos > 0) mpd_send_move(c, 0, new_pos); + } + + if (!mpd_command_list_end(c) || !mpd_response_finish(c)) { + fprintf(stderr, "Failed to execute command list: %s\n", mpd_connection_get_error_message(c)); + } else { + printf("Queue updated successfully.\n"); + } + +cleanup: + free(line); + if (f) fclose(f); + unlink(tmp_template); + if (current_file) free(current_file); + for (int i = 0; i < track_count; i++) free(new_tracks[i]); + free(new_tracks); + mpd_connection_free(c); + return 0; +} diff --git a/mpd/mpd_update_library.c b/mpd/mpd_update_library.c @@ -0,0 +1,153 @@ +#include <mpd/client.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> +#include <unistd.h> +#include <ctype.h> + +#define DEFAULT_HOST "localhost" +#define DEFAULT_PORT 6600 +#define DEFAULT_BACKOFF 5 + +struct mpd_connection *conn() { + const char *host = getenv("MPD_HOST"); + if (host == NULL || strlen(host) == 0) host = DEFAULT_HOST; + + unsigned port = DEFAULT_PORT; + const char *port_str = getenv("MPD_PORT"); + if (port_str != NULL && strlen(port_str) > 0) port = (unsigned)atoi(port_str); + + struct mpd_connection *c = mpd_connection_new(host, port, 0); + if (c == NULL) { + fprintf(stderr, "Failed to create MPD connection object\n"); + return NULL; + } + + if (mpd_connection_get_error(c) != MPD_ERROR_SUCCESS) { + fprintf(stderr, "Error connecting to MPD (%s:%u): %s\n", host, port, mpd_connection_get_error_message(c)); + mpd_connection_free(c); + return NULL; + } + return c; +} + +char* trim_whitespace(char* str) { + char *end; + while(isspace((unsigned char)*str)) str++; + if(*str == 0) return str; + end = str + strlen(str) - 1; + while(end > str && isspace((unsigned char)*end)) end--; + end[1] = '\0'; + return str; +} + +int main(int argc, char *argv[]) { + if (argc > 1 && (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0)) { + printf("Usage: %s [input_file]\n", argv[0]); + return 0; + } + + FILE *input = stdin; + if (argc > 1) { + input = fopen(argv[1], "r"); + if (input == NULL) { + perror("Error opening file"); + return 1; + } + } else if (isatty(STDIN_FILENO)) { + fprintf(stderr, "Reading from stdin... (Press Ctrl+D to finish)\n"); + } + + char **top_dirs = NULL; + int dir_count = 0; + char *line = NULL; + size_t len = 0; + ssize_t read_bytes; + + while ((read_bytes = getline(&line, &len, input)) != -1) { + char *trimmed = trim_whitespace(line); + if (strlen(trimmed) == 0 || trimmed[0] == '#') continue; + + char *slash = strchr(trimmed, '/'); + if (slash != NULL) *slash = '\0'; + + bool found = false; + for (int i = 0; i < dir_count; i++) { + if (strcmp(top_dirs[i], trimmed) == 0) { + found = true; + break; + } + } + + if (!found) { + char **temp_dirs = realloc(top_dirs, sizeof(char *) * (dir_count + 1)); + if (temp_dirs == NULL) { + perror("realloc"); + break; + } + top_dirs = temp_dirs; + top_dirs[dir_count] = strdup(trimmed); + if (top_dirs[dir_count] == NULL) { + perror("strdup"); + break; + } + dir_count++; + } + } + free(line); + if (input != stdin) fclose(input); + + if (dir_count == 0) { + printf("No directories found in input.\n"); + for (int i = 0; i < dir_count; i++) free(top_dirs[i]); + free(top_dirs); + return 0; + } + + printf("Found %d unique top-level directories to update.\n", dir_count); + + struct mpd_connection *c = conn(); + if (c == NULL) { + for (int i = 0; i < dir_count; i++) free(top_dirs[i]); + free(top_dirs); + return 1; + } + + int backoff = DEFAULT_BACKOFF; + const char *backoff_str = getenv("MPD_UPDATE_BACKOFF"); + if (backoff_str != NULL && strlen(backoff_str) > 0) backoff = atoi(backoff_str); + + int updated_count = 0; + int error_count = 0; + + for (int i = 0; i < dir_count; i++) { + printf("Updating: '%s'\n", top_dirs[i]); + unsigned job_id = mpd_run_update(c, top_dirs[i]); + if (job_id == 0) { + fprintf(stderr, " ✗ Failed: %s\n", mpd_connection_get_error_message(c)); + error_count++; + mpd_connection_clear_error(c); + } else { + printf(" ✓ Success (Job ID: %u)\n", job_id); + updated_count++; + } + if (i < dir_count - 1 && backoff > 0) sleep(backoff); + } + + printf("Update completed!\n"); + printf("Total directories processed: %d\n", dir_count); + printf("Successfully updated: %d\n", updated_count); + printf("Errors: %d\n", error_count); + + struct mpd_status *status = mpd_run_status(c); + if (status != NULL) { + if (mpd_status_get_update_id(status) > 0) printf("MPD is currently updating...\n"); + mpd_status_free(status); + } + + for (int i = 0; i < dir_count; i++) free(top_dirs[i]); + free(top_dirs); + mpd_connection_free(c); + return 0; +} diff --git a/mpd/mpd_update_queue.c b/mpd/mpd_update_queue.c @@ -0,0 +1,137 @@ +#include <mpd/client.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> +#include <unistd.h> + +#define DEFAULT_HOST "localhost" +#define DEFAULT_PORT 6600 + +struct mpd_connection *conn() { + const char *host = getenv("MPD_HOST"); + if (host == NULL || strlen(host) == 0) host = DEFAULT_HOST; + + unsigned port = DEFAULT_PORT; + const char *port_str = getenv("MPD_PORT"); + if (port_str != NULL && strlen(port_str) > 0) port = (unsigned)atoi(port_str); + + struct mpd_connection *c = mpd_connection_new(host, port, 0); + if (c == NULL) { + fprintf(stderr, "Failed to create MPD connection object\n"); + return NULL; + } + + if (mpd_connection_get_error(c) != MPD_ERROR_SUCCESS) { + fprintf(stderr, "Error connecting to MPD (%s:%u): %s\n", host, port, mpd_connection_get_error_message(c)); + mpd_connection_free(c); + return NULL; + } + return c; +} + +int main(int argc, char *argv[]) { + if (argc < 2) { + fprintf(stderr, "Usage: %s <new_queue_file>\n", argv[0]); + return 1; + } + + FILE *input = fopen(argv[1], "r"); + if (input == NULL) { + perror("Error opening file"); + return 1; + } + + struct mpd_connection *c = conn(); + if (c == NULL) { + fclose(input); + return 1; + } + + struct mpd_status *status = mpd_run_status(c); + if (status == NULL) { + fprintf(stderr, "Error getting status: %s\n", mpd_connection_get_error_message(c)); + fclose(input); + mpd_connection_free(c); + return 1; + } + + enum mpd_state state = mpd_status_get_state(status); + int current_pos = mpd_status_get_song_pos(status); + int total_queue_len = mpd_status_get_queue_length(status); + mpd_status_free(status); + + char *current_file = NULL; + if (state == MPD_STATE_PLAY || state == MPD_STATE_PAUSE) { + struct mpd_song *song = mpd_run_current_song(c); + if (song != NULL) { + current_file = strdup(mpd_song_get_uri(song)); + mpd_song_free(song); + } + } + + char **new_tracks = NULL; + int track_count = 0; + char *line = NULL; + size_t len = 0; + ssize_t read_bytes; + int new_pos = -1; + + while ((read_bytes = getline(&line, &len, input)) != -1) { + if (read_bytes > 0 && line[read_bytes - 1] == '\n') line[read_bytes - 1] = '\0'; + if (read_bytes > 1 && line[read_bytes - 2] == '\r') line[read_bytes - 2] = '\0'; + if (strlen(line) == 0 || line[0] == '#') continue; + + char **temp_tracks = realloc(new_tracks, sizeof(char *) * (track_count + 1)); + if (temp_tracks == NULL) { + perror("realloc"); + goto cleanup; + } + new_tracks = temp_tracks; + + new_tracks[track_count] = strdup(line); + if (new_tracks[track_count] == NULL) { + perror("strdup"); + goto cleanup; + } + + if (current_file && strcmp(new_tracks[track_count], current_file) == 0 && new_pos == -1) { + new_pos = track_count; + } + track_count++; + } + + if (!mpd_command_list_begin(c, false)) { + fprintf(stderr, "Failed to start command list: %s\n", mpd_connection_get_error_message(c)); + goto cleanup; + } + + if (state == MPD_STATE_STOP || current_file == NULL || new_pos == -1) { + mpd_send_clear(c); + for (int i = 0; i < track_count; i++) mpd_send_add(c, new_tracks[i]); + if (state != MPD_STATE_STOP) mpd_send_play(c); + } else { + for (int i = total_queue_len - 1; i >= 0; i--) { + if (i != current_pos) mpd_send_delete(c, i); + } + for (int i = 0; i < track_count; i++) { + if (i != new_pos) mpd_send_add(c, new_tracks[i]); + } + if (new_pos > 0) mpd_send_move(c, 0, new_pos); + } + + if (!mpd_command_list_end(c) || !mpd_response_finish(c)) { + fprintf(stderr, "Failed to execute command list: %s\n", mpd_connection_get_error_message(c)); + } else { + printf("Queue updated successfully.\n"); + } + +cleanup: + free(line); + fclose(input); + if (current_file) free(current_file); + for (int i = 0; i < track_count; i++) free(new_tracks[i]); + free(new_tracks); + mpd_connection_free(c); + return 0; +}