From 3bab4bef0d4c814671b217f995f9976cd30a8caf Mon Sep 17 00:00:00 2001 From: Patrick Gaskin Date: Sat, 22 Jul 2023 18:55:59 -0400 Subject: [PATCH 01/22] Handle CSI escape sequences correctly --- ui_curses.c | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/ui_curses.c b/ui_curses.c index 94702b18a..914738e63 100644 --- a/ui_curses.c +++ b/ui_curses.c @@ -1998,6 +1998,36 @@ static void handle_ch(uchar ch) } } +static void handle_csi(void) { + // after ESC[ until 0x40-0x7E (@A–Z[\]^_`a–z{|}~) + // https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_(Control_Sequence_Introducer)_sequences + // https://www.ecma-international.org/wp-content/uploads/ECMA-48_5th_edition_june_1991.pdf + + int c; + int buf[16]; // buffer a reasonable length + size_t buf_n = 0; + int overflow = 0; + + while (1) { + c = getch(); + if (c == ERR || c == 0) { + return; + } + if (buf_n < sizeof(buf)/sizeof(*buf)) { + buf[buf_n++] = c; + } else { + overflow = 1; + } + if (c >= 0x40 && c <= 0x7E) { + break; + } + } + + if (overflow) { + return; + } +} + static void handle_escape(int c) { clear_error(); @@ -2076,8 +2106,11 @@ static void u_getch(void) cbreak(); int e_key = getch(); halfdelay(5); - if (e_key != ERR && e_key != 0) { - handle_escape(e_key); + if (e_key != ERR) { + if (e_key == '[') + handle_csi(); + else if (e_key != 0) + handle_escape(e_key); return; } } From 9a417aec9fbf133c4520549a3e48d32041ac00a7 Mon Sep 17 00:00:00 2001 From: Patrick Gaskin Date: Sat, 22 Jul 2023 18:57:07 -0400 Subject: [PATCH 02/22] Implement option to ignore pasted text in normal mode (#1270) --- Doc/cmus.txt | 4 ++++ options.c | 17 +++++++++++++++++ options.h | 1 + ui_curses.c | 27 +++++++++++++++++++++++++-- 4 files changed, 47 insertions(+), 2 deletions(-) diff --git a/Doc/cmus.txt b/Doc/cmus.txt index 814c5a0d8..58509e10d 100644 --- a/Doc/cmus.txt +++ b/Doc/cmus.txt @@ -898,6 +898,10 @@ altformat_trackwin [`Format String`] Note: if empty, *format_trackwin* is used instead. +block_key_paste (true) + Prevent accidental input by only accepting pasted text in the command line. + Only works on terminals which support bracketed paste. + buffer_seconds (10) [1-300] Size of the player buffer in seconds. diff --git a/options.c b/options.c index 703729a7e..c9ea45750 100644 --- a/options.c +++ b/options.c @@ -93,6 +93,7 @@ int stop_after_queue = 0; int tree_width_percent = 33; int tree_width_max = 0; int pause_on_output_change = 0; +int block_key_paste = 1; int colors[NR_COLORS] = { -1, @@ -1318,6 +1319,21 @@ static void toggle_pause_on_output_change(void *data) pause_on_output_change ^= 1; } +static void get_block_key_paste(void *data, char *buf, size_t size) +{ + strscpy(buf, bool_names[block_key_paste], size); +} + +static void set_block_key_paste(void *data, const char *buf) +{ + parse_bool(buf, &block_key_paste); +} + +static void toggle_block_key_paste(void *data) +{ + block_key_paste ^= 1; +} + /* }}} */ /* special callbacks (id set) {{{ */ @@ -1581,6 +1597,7 @@ static const struct { DN(tree_width_max) DT(pause_on_output_change) DN(pl_env_vars) + DT(block_key_paste) { NULL, NULL, NULL, NULL, 0 } }; diff --git a/options.h b/options.h index 521970778..4b6dd136c 100644 --- a/options.h +++ b/options.h @@ -163,6 +163,7 @@ extern int stop_after_queue; extern int tree_width_percent; extern int tree_width_max; extern int pause_on_output_change; +extern int block_key_paste; extern const char * const aaa_mode_names[]; extern const char * const view_names[NR_VIEWS + 1]; diff --git a/ui_curses.c b/ui_curses.c index 914738e63..cdab23466 100644 --- a/ui_curses.c +++ b/ui_curses.c @@ -149,6 +149,8 @@ static const int default_esc_delay = 25; static char *title_buf = NULL; +static int in_bracketed_paste = 0; + enum { CURSED_WIN, CURSED_WIN_CUR, @@ -1988,7 +1990,9 @@ static void handle_ch(uchar ch) { clear_error(); if (input_mode == NORMAL_MODE) { - normal_mode_ch(ch); + if (!block_key_paste || !in_bracketed_paste) { + normal_mode_ch(ch); + } } else if (input_mode == COMMAND_MODE) { command_mode_ch(ch); update_commandline(); @@ -2026,6 +2030,15 @@ static void handle_csi(void) { if (overflow) { return; } + + if (buf_n == 4) { + // bracketed paste + // https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Bracketed-Paste-Mode + if (buf[0] == '2' && buf[1] == '0' && (buf[2] == '0' || buf[2] == '1') && buf[3] == '~') { + in_bracketed_paste = buf[2] == '0'; + return; + } + } } static void handle_escape(int c) @@ -2046,7 +2059,9 @@ static void handle_key(int key) { clear_error(); if (input_mode == NORMAL_MODE) { - normal_mode_key(key); + if (!block_key_paste || !in_bracketed_paste) { + normal_mode_key(key); + } } else if (input_mode == COMMAND_MODE) { command_mode_key(key); update_commandline(); @@ -2420,6 +2435,10 @@ static void init_all(void) init_curses(); + // enable bracketed paste (will be ignored if not supported) + printf("\033[?2004h"); + fflush(stdout); + if (resume_cmus) { resume_load(); cmus_add(play_queue_append, play_queue_autosave_filename, @@ -2438,6 +2457,10 @@ static void exit_all(void) { endwin(); + // disable bracketed paste + printf("\033[?2004l"); + fflush(stdout); + if (resume_cmus) resume_exit(); options_exit(); From ea01908e2408ecfd8a7e5eb8627e0033d118b47f Mon Sep 17 00:00:00 2001 From: Gavin Troy Date: Fri, 21 Jul 2023 23:36:58 +0100 Subject: [PATCH 03/22] Add option to sort albums by name in tree view By default they are still sorted by date, which is the current behavior. I considered to also add an equivalent of smart_artist_sort, to strip "The" from album names when sorting. It could be nice to add for consistency, but I find it's not that useful when the list of albums under a given artist tends to be quite small anyway. Closes #909. --- Doc/cmus.txt | 3 +++ options.c | 19 +++++++++++++++++++ options.h | 1 + tree.c | 15 +++++++-------- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/Doc/cmus.txt b/Doc/cmus.txt index 58509e10d..8cf6ea407 100644 --- a/Doc/cmus.txt +++ b/Doc/cmus.txt @@ -1089,6 +1089,9 @@ smart_artist_sort (true) names, preventing artists starting with "The" from clumping together. Real `artistsort` tags override this option, if present. +sort_albums_by_name (false) + In tree view (1), albums will be sorted by name rather than date. + id3_default_charset (ISO-8859-1) Default character set to use for ID3v1 and broken ID3v2 tags. diff --git a/options.c b/options.c index c9ea45750..bd4f2957d 100644 --- a/options.c +++ b/options.c @@ -76,6 +76,7 @@ int shuffle = 0; int follow = 0; int display_artist_sort_name; int smart_artist_sort = 1; +int sort_albums_by_name = 0; int scroll_offset = 2; int rewind_offset = 5; int skip_track_info = 0; @@ -756,6 +757,23 @@ static void toggle_smart_artist_sort(void *data) lib_sort_artists(); } +static void get_sort_albums_by_name(void *data, char *buf, size_t size) +{ + strscpy(buf, bool_names[sort_albums_by_name], size); +} + +static void set_sort_albums_by_name(void *data, const char *buf) +{ + parse_bool(buf, &sort_albums_by_name); + lib_sort_artists(); +} + +static void toggle_sort_albums_by_name(void *data) +{ + sort_albums_by_name ^= 1; + lib_sort_artists(); +} + static void get_display_artist_sort_name(void *data, char *buf, size_t size) { strscpy(buf, bool_names[display_artist_sort_name], size); @@ -1554,6 +1572,7 @@ static const struct { DT(continue) DT(continue_album) DT(smart_artist_sort) + DT(sort_albums_by_name) DN(id3_default_charset) DN(icecast_default_charset) DN(lib_sort) diff --git a/options.h b/options.h index 4b6dd136c..c7ec47e3a 100644 --- a/options.h +++ b/options.h @@ -151,6 +151,7 @@ extern int shuffle; extern int follow; extern int display_artist_sort_name; extern int smart_artist_sort; +extern int sort_albums_by_name; extern int scroll_offset; extern int rewind_offset; extern int skip_track_info; diff --git a/tree.c b/tree.c index c383ed22d..f2cd007e6 100644 --- a/tree.c +++ b/tree.c @@ -828,14 +828,11 @@ static void add_album(struct album *album) struct rb_node **new = &(album->artist->album_root.rb_node), *parent = NULL; struct album *found; - /* - * Sort regular albums by date, but sort compilations - * alphabetically. - */ - found = do_find_album(album, - album->artist->is_compilation ? special_album_cmp - : special_album_cmp_date, - &new, &parent); + if (sort_albums_by_name || album->artist->is_compilation) + found = do_find_album(album, special_album_cmp, &new, &parent); + else + found = do_find_album(album, special_album_cmp_date, &new, &parent); + if (!found) { rb_link_node(&album->tree_node, parent, new); rb_insert_color(&album->tree_node, &album->artist->album_root); @@ -1240,6 +1237,8 @@ void tree_sort_artists(void (*add_album_cb)(struct album *), struct rb_node *t_node, *t_tmp; struct album *album = to_album(l_node); + remove_album(album); + add_album(album); rb_for_each_safe(t_node, t_tmp, &album->track_root) { struct tree_track *track = to_tree_track(t_node); From 1849b0ae254e1d37104495639a51cb0bb4246d43 Mon Sep 17 00:00:00 2001 From: mrHeavenli <71031776+mrHeavenli@users.noreply.github.com> Date: Wed, 23 Aug 2023 21:51:26 +0200 Subject: [PATCH 04/22] mpris: use path basename for tracks with no title (#1281) --- mpris.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mpris.c b/mpris.c index 3eae2ba9c..a52a9c077 100644 --- a/mpris.c +++ b/mpris.c @@ -30,6 +30,7 @@ #include "track_info.h" #include "utils.h" #include "uchar.h" +#include "path.h" #define CK(v) \ do { \ @@ -392,6 +393,11 @@ static int mpris_metadata(sd_bus *_bus, const char *_path, u_to_utf8(corrected, ti->title); CK(mpris_msg_append_ss_dict(reply, "xesam:title", corrected)); + } else { + char corrected[u_str_print_size(path_basename(ti->filename))]; + u_to_utf8(corrected, path_basename(ti->filename)); + CK(mpris_msg_append_ss_dict(reply, + "xesam:title", corrected)); } if (ti->album) { char corrected[u_str_print_size(ti->album)]; From 6e9bf6b2bcf86f040b3bd4587c5df4533cad9834 Mon Sep 17 00:00:00 2001 From: Mathieu Lemay Date: Sat, 16 Sep 2023 00:42:35 -0400 Subject: [PATCH 05/22] Add %{totaldiscs} to available format string Allows displaying the number of discs of an album. Can also be used as a condition. Shorthand is `%T`. --- Doc/cmus.txt | 1 + comment.c | 3 ++- track_info.c | 4 ++++ track_info.h | 3 +++ ui_curses.c | 3 +++ 5 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Doc/cmus.txt b/Doc/cmus.txt index 8cf6ea407..1b19c1c0f 100644 --- a/Doc/cmus.txt +++ b/Doc/cmus.txt @@ -1327,6 +1327,7 @@ Special Keys: %A %{albumartist} @br %l %{album} @br %D %{discnumber} @br + %T %{totaldiscs} @br %n %{tracknumber} @br %X %{play_count} @br %t %{title} @br diff --git a/comment.c b/comment.c index 130a76497..1a1b11e2b 100644 --- a/comment.c +++ b/comment.c @@ -195,7 +195,7 @@ int comments_get_date(const struct keyval *comments, const char *key) } static const char *interesting[] = { - "artist", "album", "title", "tracknumber", "discnumber", "genre", + "artist", "album", "title", "tracknumber", "discnumber", "totaldiscs", "genre", "date", "compilation", "partofacompilation", "albumartist", "artistsort", "albumartistsort", "albumsort", "originaldate", @@ -221,6 +221,7 @@ static struct { { "album_artist", "albumartist" }, { "album artist", "albumartist" }, { "disc", "discnumber" }, + { "dicstotal", "totaldiscs" }, { "tempo", "bpm" }, { "track", "tracknumber" }, { "WM/Year", "date" }, diff --git a/track_info.c b/track_info.c index 8857f197a..4d30cb07b 100644 --- a/track_info.c +++ b/track_info.c @@ -74,6 +74,7 @@ void track_info_set_comments(struct track_info *ti, struct keyval *comments) { ti->title = keyvals_get_val(comments, "title"); ti->tracknumber = comments_get_int(comments, "tracknumber"); ti->discnumber = comments_get_int(comments, "discnumber"); + ti->totaldiscs = comments_get_int(comments, "totaldiscs"); ti->date = comments_get_date(comments, "date"); ti->originaldate = comments_get_date(comments, "originaldate"); ti->genre = keyvals_get_val(comments, "genre"); @@ -246,6 +247,7 @@ int track_info_cmp(const struct track_info *a, const struct track_info *b, const switch (key) { case SORT_TRACKNUMBER: case SORT_DISCNUMBER: + case SORT_TOTALDISCS: case SORT_DATE: case SORT_ORIGINALDATE: case SORT_PLAY_COUNT: @@ -291,6 +293,7 @@ static const struct { { "play_count", SORT_PLAY_COUNT }, { "tracknumber", SORT_TRACKNUMBER }, { "discnumber", SORT_DISCNUMBER }, + { "totaldiscs", SORT_TOTALDISCS }, { "date", SORT_DATE }, { "originaldate", SORT_ORIGINALDATE }, { "genre", SORT_GENRE }, @@ -313,6 +316,7 @@ static const struct { { "-play_count", REV_SORT_PLAY_COUNT }, { "-tracknumber", REV_SORT_TRACKNUMBER }, { "-discnumber", REV_SORT_DISCNUMBER }, + { "-totaldiscs", REV_SORT_TOTALDISCS }, { "-date", REV_SORT_DATE }, { "-originaldate", REV_SORT_ORIGINALDATE }, { "-genre", REV_SORT_GENRE }, diff --git a/track_info.h b/track_info.h index 099c41a34..58b46dfba 100644 --- a/track_info.h +++ b/track_info.h @@ -40,6 +40,7 @@ struct track_info { int tracknumber; int discnumber; + int totaldiscs; int date; int originaldate; double rg_track_gain; @@ -78,6 +79,7 @@ typedef size_t sort_key_t; #define SORT_TITLE offsetof(struct track_info, collkey_title) #define SORT_TRACKNUMBER offsetof(struct track_info, tracknumber) #define SORT_DISCNUMBER offsetof(struct track_info, discnumber) +#define SORT_TOTALDISCS offsetof(struct track_info, totaldiscs) #define SORT_DATE offsetof(struct track_info, date) #define SORT_ORIGINALDATE offsetof(struct track_info, originaldate) #define SORT_RG_TRACK_GAIN offsetof(struct track_info, rg_track_gain) @@ -102,6 +104,7 @@ typedef size_t sort_key_t; #define REV_SORT_PLAY_COUNT (REV_SORT__START + offsetof(struct track_info, play_count)) #define REV_SORT_TRACKNUMBER (REV_SORT__START + offsetof(struct track_info, tracknumber)) #define REV_SORT_DISCNUMBER (REV_SORT__START + offsetof(struct track_info, discnumber)) +#define REV_SORT_TOTALDISCS (REV_SORT__START + offsetof(struct track_info, totaldiscs)) #define REV_SORT_DATE (REV_SORT__START + offsetof(struct track_info, date)) #define REV_SORT_ORIGINALDATE (REV_SORT__START + offsetof(struct track_info, originaldate)) #define REV_SORT_RG_TRACK_GAIN (REV_SORT__START + offsetof(struct track_info, rg_track_gain)) diff --git a/ui_curses.c b/ui_curses.c index cdab23466..4dd2919c5 100644 --- a/ui_curses.c +++ b/ui_curses.c @@ -257,6 +257,7 @@ enum { TF_ARTIST, TF_ALBUM, TF_DISC, + TF_TOTAL_DISCS, TF_TRACK, TF_TITLE, TF_PLAY_COUNT, @@ -315,6 +316,7 @@ static struct format_option track_fopts[NR_TFS + 1] = { DEF_FO_STR('a', "artist", 0), DEF_FO_STR('l', "album", 0), DEF_FO_INT('D', "discnumber", 1), + DEF_FO_INT('T', "totaldiscs", 1), DEF_FO_INT('n', "tracknumber", 1), DEF_FO_STR('t', "title", 0), DEF_FO_INT('X', "play_count", 0), @@ -533,6 +535,7 @@ static void fill_track_fopts_track_info(struct track_info *info) fopt_set_str(&track_fopts[TF_ALBUM], info->album); fopt_set_int(&track_fopts[TF_PLAY_COUNT], info->play_count, 0); fopt_set_int(&track_fopts[TF_DISC], info->discnumber, info->discnumber == -1); + fopt_set_int(&track_fopts[TF_TOTAL_DISCS], info->totaldiscs, info->totaldiscs == -1); fopt_set_int(&track_fopts[TF_TRACK], info->tracknumber, info->tracknumber == -1); fopt_set_str(&track_fopts[TF_TITLE], info->title); fopt_set_int(&track_fopts[TF_YEAR], info->date / 10000, info->date <= 0); From 3a7d9eeca2b469cec0b4aca6976f3a67e88f6d87 Mon Sep 17 00:00:00 2001 From: Gavin Troy Date: Sat, 26 Aug 2023 19:56:06 +0100 Subject: [PATCH 06/22] [FFmpeg] Switch to new channel layout api Old api is deprecated and sparks build warnings. --- ip/ffmpeg.c | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/ip/ffmpeg.c b/ip/ffmpeg.c index ea0b26fb0..ce2b42952 100644 --- a/ip/ffmpeg.c +++ b/ip/ffmpeg.c @@ -60,7 +60,7 @@ struct ffmpeg_output { struct ffmpeg_private { AVCodecContext *codec_context; AVFormatContext *input_context; - AVCodec *codec; + AVCodec const *codec; SwrContext *swr; struct ffmpeg_input *input; @@ -141,8 +141,7 @@ static int ffmpeg_open(struct input_plugin_data *ip_data) int err = 0; int i; int stream_index = -1; - int64_t channel_layout = 0; - AVCodec *codec; + AVCodec const *codec; AVCodecContext *cc = NULL; AVFormatContext *ic = NULL; #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101) @@ -239,15 +238,26 @@ static int ffmpeg_open(struct input_plugin_data *ip_data) /* Prepare for resampling. */ swr = swr_alloc(); +#if LIBAVCODEC_VERSION_MAJOR >= 58 + if (cc->ch_layout.order == AV_CHANNEL_ORDER_UNSPEC) + av_channel_layout_default(&cc->ch_layout, cc->ch_layout.nb_channels); + av_opt_set_chlayout(swr, "in_chlayout", &cc->ch_layout, 0); + av_opt_set_chlayout(swr, "out_chlayout", &cc->ch_layout, 0); +#else av_opt_set_int(swr, "in_channel_layout", av_get_default_channel_layout(cc->channels), 0); av_opt_set_int(swr, "out_channel_layout", av_get_default_channel_layout(cc->channels), 0); +#endif av_opt_set_int(swr, "in_sample_rate", cc->sample_rate, 0); av_opt_set_int(swr, "out_sample_rate", cc->sample_rate, 0); av_opt_set_sample_fmt(swr, "in_sample_fmt", cc->sample_fmt, 0); priv->swr = swr; ip_data->private = priv; +#if LIBAVCODEC_VERSION_MAJOR >= 58 + ip_data->sf = sf_rate(cc->sample_rate) | sf_channels(cc->ch_layout.nb_channels); +#else ip_data->sf = sf_rate(cc->sample_rate) | sf_channels(cc->channels); +#endif switch (cc->sample_fmt) { case AV_SAMPLE_FMT_U8: ip_data->sf |= sf_bits(8) | sf_signed(0); @@ -265,8 +275,11 @@ static int ffmpeg_open(struct input_plugin_data *ip_data) } swr_init(swr); ip_data->sf |= sf_host_endian(); - channel_layout = cc->channel_layout; - channel_map_init_waveex(cc->channels, channel_layout, ip_data->channel_map); +#if LIBAVCODEC_VERSION_MAJOR >= 58 + channel_map_init_waveex(cc->ch_layout.nb_channels, cc->ch_layout.u.mask, ip_data->channel_map); +#else + channel_map_init_waveex(cc->channels, cc->channel_layout, ip_data->channel_map); +#endif return 0; } @@ -378,7 +391,11 @@ static int ffmpeg_fill_buffer(AVFormatContext *ic, AVCodecContext *cc, struct ff if (res < 0) res = 0; output->buffer_pos = output->buffer; +#if LIBAVCODEC_VERSION_MAJOR >= 58 + output->buffer_used_len = res * cc->ch_layout.nb_channels * sizeof(int16_t); +#else output->buffer_used_len = res * cc->channels * sizeof(int16_t); +#endif #if LIBAVCODEC_VERSION_MAJOR >= 56 av_frame_free(&frame); #else From d6cd5a5d45db268dd3c244049f17bf3d63144dab Mon Sep 17 00:00:00 2001 From: Gavin Troy Date: Thu, 24 Aug 2023 00:15:30 +0100 Subject: [PATCH 07/22] Reduce R128 gain precision to 2 places of decimal The encoding is only capable of accurately storing 2 decimal places. It's misleading (and ugly) to present it as otherwise when printed. --- track_info.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/track_info.c b/track_info.c index 4d30cb07b..2456a117a 100644 --- a/track_info.c +++ b/track_info.c @@ -106,11 +106,13 @@ void track_info_set_comments(struct track_info *ti, struct keyval *comments) { ti->rg_album_peak = comments_get_double(comments, "replaygain_album_peak"); if (comments_get_signed_int(comments, "r128_track_gain", &r128_track_gain) != -1) { - ti->rg_track_gain = (r128_track_gain / 256.0) + 5; + double rg = (r128_track_gain / 256.0) + 5; + ti->rg_track_gain = round(rg * 100) / 100.0; } if (comments_get_signed_int(comments, "r128_album_gain", &r128_album_gain) != -1) { - ti->rg_album_gain = (r128_album_gain / 256.0) + 5; + double rg = (r128_album_gain / 256.0) + 5; + ti->rg_album_gain = round(rg * 100) / 100.0; } if (comments_get_signed_int(comments, "output_gain", &output_gain) != -1) { From 7a81258179e6b4b1b6432424256bb44d814938ce Mon Sep 17 00:00:00 2001 From: Gavin Troy Date: Mon, 21 Aug 2023 22:14:47 +0100 Subject: [PATCH 08/22] Format whole number replaygain fields more naturally In particular this avoids whole numbers printing with an extra "." at the end. --- format_print.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/format_print.c b/format_print.c index 627d90873..6162b4ac4 100644 --- a/format_print.c +++ b/format_print.c @@ -98,21 +98,12 @@ static void print_num(int num) #define DBL_MAX_LEN (20) -static int format_double(char *buf, int buflen, double num) -{ - int l = snprintf(buf, buflen, "%f", num); - /* skip trailing zeros */ - while (l > 0 && buf[l-1] == '0') - l--; - return l; -} - static void print_double(double num) { char stack[DBL_MAX_LEN], b[DBL_MAX_LEN]; int i, p = 0; - i = format_double(b, DBL_MAX_LEN, num) - 1; + i = snprintf(b, DBL_MAX_LEN, "%g", num) - 1; while (i >= 0) { stack[p++] = b[i]; i--; From 55e71d6d3957e208f9cddefc64f95d4256dd2de6 Mon Sep 17 00:00:00 2001 From: Gavin Troy Date: Thu, 27 Jul 2023 20:12:35 +0100 Subject: [PATCH 09/22] Fix unmute when using softvol Fixes #1278 --- command_mode.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/command_mode.c b/command_mode.c index 22bccdecc..f87719cc8 100644 --- a/command_mode.c +++ b/command_mode.c @@ -1200,14 +1200,23 @@ static int parse_vol_arg(const char *arg, int *value, unsigned int *flags) static void cmd_mute(char *arg) { int l = 0, r = 0; + int *vl, *vr; - if (volume_l == 0 && volume_r == 0) { + if (soft_vol) { + vl = &soft_vol_l; + vr = &soft_vol_r; + } else { + vl = &volume_l; + vr = &volume_r; + } + + if (*vl == 0 && *vr == 0) { // unmute l = mute_vol_l; r = mute_vol_r; } else { - mute_vol_l = volume_l; - mute_vol_r = volume_r; + mute_vol_l = *vl; + mute_vol_r = *vr; } int rc = player_set_vol(l, 0, r, 0); From 5b35ead0d4ecf1a722d2880261c4a675fe4c9e4c Mon Sep 17 00:00:00 2001 From: Inochi Amaoto Date: Sat, 28 Oct 2023 08:43:32 +0800 Subject: [PATCH 10/22] Allow cue plugin to parse cue sheet with multiple files. The cue plugin will fail when parsing multiple FILE entry because of the postion check and fixed file entry. Make the parser more robust so it can paser the cue sheet with multiple files. --- cue.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++---------- cue.h | 3 +-- ip/cue.c | 2 +- 3 files changed, 53 insertions(+), 14 deletions(-) diff --git a/cue.c b/cue.c index 3e8c450fe..a92177d1f 100644 --- a/cue.c +++ b/cue.c @@ -33,6 +33,7 @@ struct cue_track_proto { struct list_head node; + char *file; uint32_t nr; int32_t pregap; int32_t postgap; @@ -42,12 +43,18 @@ struct cue_track_proto { struct cue_meta meta; }; +struct cue_track_file { + struct list_head node; + + char *file; +}; + struct cue_parser { const char *src; size_t len; bool err; - char *file; + struct list_head files; struct list_head tracks; size_t num_tracks; @@ -196,11 +203,33 @@ CUE_PARSE_STR(discnumber); static void cue_parse_file(struct cue_parser *p) { - cue_parse_str(p, &p->file); + struct cue_track_file *f = xnew(struct cue_track_file, 1); + + f->file = NULL; + cue_parse_str(p, &f->file); + + list_add_tail(&f->node, &p->files); +} + +static char* cue_dup_current_file(struct cue_parser *p) +{ + if (list_empty(&p->files)) + return NULL; + + struct list_head *tail = list_prev(&p->files); + char* file = list_entry(tail, struct cue_track_file, node)->file; + return cue_strdup(file, strlen(file)); } static void cue_parse_track(struct cue_parser *p) { + char *curr_file = cue_dup_current_file(p); + + if (!curr_file) { + cue_set_err(p); + return; + } + const char *nr; size_t len = cue_extract_token(p, &nr); @@ -215,6 +244,7 @@ static void cue_parse_track(struct cue_parser *p) .postgap = -1, .index0 = -1, .index1 = -1, + .file = curr_file, }; list_add_tail(&t->node, &p->tracks); @@ -346,7 +376,7 @@ static void cue_parse_line(struct cue_parser *p) static void cue_post_process(struct cue_parser *p) { - if (!p->file || p->num_tracks == 0) { + if (list_empty(&p->files) || p->num_tracks == 0) { cue_set_err(p); return; } @@ -364,6 +394,7 @@ static void cue_post_process(struct cue_parser *p) } last = -1; + char *last_file = NULL; list_for_each_entry(t, &p->tracks, node) { if (t->index0 == -1 && t->index1 == -1) { @@ -377,12 +408,13 @@ static void cue_post_process(struct cue_parser *p) else t->index1 = t->index0 + pregap; } - if (last != -1 && t->index0 < last) { + if (last != -1 && (t->file == last_file && t->index0 < last)) { cue_set_err(p); return; } int32_t postgap = t->postgap != -1 ? t->postgap : 0; last = t->index1 + postgap; + last_file = t->file; } } @@ -396,9 +428,6 @@ static struct cue_sheet *cue_parser_to_sheet(struct cue_parser *p) { struct cue_sheet *s = xnew(struct cue_sheet, 1); - s->file = p->file; - p->file = NULL; - s->tracks = xnew(struct cue_track, p->num_tracks); s->num_tracks = p->num_tracks; s->track_base = cue_last_proto(p)->nr + 1 - p->num_tracks; @@ -408,6 +437,9 @@ static struct cue_sheet *cue_parser_to_sheet(struct cue_parser *p) size_t idx = 0; struct cue_track_proto *t, *prev; list_for_each_entry(t, &p->tracks, node) { + s->tracks[idx].file = t->file; + t->file = NULL; + s->tracks[idx].offset = t->index1 / 75.0; s->tracks[idx].length = -1; @@ -440,12 +472,19 @@ static void cue_meta_free(struct cue_meta *m) static void cue_parser_free(struct cue_parser *p) { struct cue_track_proto *t, *next; + struct cue_track_file *tf, *nexttf; + + list_for_each_entry_safe(tf, nexttf, &p->files, node) { + free(tf->file); + free(tf); + } + list_for_each_entry_safe(t, next, &p->tracks, node) { cue_meta_free(&t->meta); + free(t->file); free(t); } - free(p->file); cue_meta_free(&p->meta); } @@ -458,6 +497,7 @@ struct cue_sheet *cue_parse(const char *src, size_t len) .len = len, }; list_init(&p.tracks); + list_init(&p.files); while (p.len > 0 && !p.err) cue_parse_line(&p); @@ -498,10 +538,10 @@ struct cue_sheet *cue_from_file(const char *file) void cue_free(struct cue_sheet *s) { - free(s->file); - - for (size_t i = 0; i < s->num_tracks; i++) + for (size_t i = 0; i < s->num_tracks; i++) { cue_meta_free(&s->tracks[i].meta); + free(s->tracks[i].file); + } free(s->tracks); cue_meta_free(&s->meta); diff --git a/cue.h b/cue.h index a76566b77..d31cd92bc 100644 --- a/cue.h +++ b/cue.h @@ -32,14 +32,13 @@ struct cue_meta { }; struct cue_track { + char *file; double offset; double length; struct cue_meta meta; }; struct cue_sheet { - char *file; - struct cue_track *tracks; size_t num_tracks; size_t track_base; diff --git a/ip/cue.c b/ip/cue.c index 8b56a4a9c..d4272709b 100644 --- a/ip/cue.c +++ b/ip/cue.c @@ -110,7 +110,7 @@ static int cue_open(struct input_plugin_data *ip_data) goto cue_read_failed; } - child_filename = _make_absolute_path(priv->cue_filename, cd->file); + child_filename = _make_absolute_path(priv->cue_filename, t->file); priv->child = ip_new(child_filename); free(child_filename); From 957564d7a1d7ffe692fe82e18dbf6ad75b334169 Mon Sep 17 00:00:00 2001 From: rodenet-hier Date: Fri, 29 Sep 2023 22:14:06 +0300 Subject: [PATCH 11/22] Move playlist deletion code to new function --- pl.c | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/pl.c b/pl.c index 1954bc3fa..79c160a37 100644 --- a/pl.c +++ b/pl.c @@ -419,18 +419,11 @@ static void pl_save_all(void) pl_save_one(pl); } -static void pl_delete_selected_pl(void) +static void pl_delete(struct playlist *pl) { - if (list_len(&pl_head) == 1) { - error_msg("cannot delete the last playlist"); - return; - } - - if (yes_no_query("Delete selected playlist? [y/N]") != UI_QUERY_ANSWER_YES) + if (list_len(&pl_head) == 1) return; - struct playlist *pl = pl_visible; - struct iter iter; pl_to_iter(pl, &iter); window_row_vanishes(pl_list_win, &iter); @@ -459,6 +452,19 @@ static void pl_delete_selected_pl(void) job_schedule_pl_delete(pdd); } +static void pl_delete_selected_pl(void) +{ + if (list_len(&pl_head) == 1) { + error_msg("cannot delete the last playlist"); + return; + } + + if (yes_no_query("Delete selected playlist? [y/N]") != UI_QUERY_ANSWER_YES) + return; + + pl_delete(pl_visible); +} + static void pl_mark_selected_pl(void) { pl_marked = pl_visible; From b54eff9125a53fc3ab07c19e5e30587c38cfbd82 Mon Sep 17 00:00:00 2001 From: rodenet-hier Date: Fri, 29 Sep 2023 22:29:51 +0300 Subject: [PATCH 12/22] Add pl_delete_all function --- pl.c | 16 ++++++++++++++++ pl.h | 1 + 2 files changed, 17 insertions(+) diff --git a/pl.c b/pl.c index 79c160a37..d93b8998a 100644 --- a/pl.c +++ b/pl.c @@ -465,6 +465,22 @@ static void pl_delete_selected_pl(void) pl_delete(pl_visible); } +void pl_delete_all(void) +{ + struct playlist *pl; + struct playlist *temp; + struct playlist *last = NULL; + list_for_each_entry_safe(pl, temp, &pl_head, node) { + if (list_len(&pl_head) == 1) { + last = pl; + break; + } + pl_delete(pl); + } + pl_create_default(); + pl_delete(last); +} + static void pl_mark_selected_pl(void) { pl_marked = pl_visible; diff --git a/pl.h b/pl.h index 993c1c654..55fa9e290 100644 --- a/pl.h +++ b/pl.h @@ -88,6 +88,7 @@ void pl_invert_marks(void); void pl_mark(char *arg); void pl_unmark(void); void pl_rand(void); +void pl_delete_all(void); void pl_win_mv_after(void); void pl_win_mv_before(void); void pl_win_remove(void); From 212daba2308becf49eb9b61ebe054316f1ee20a6 Mon Sep 17 00:00:00 2001 From: rodenet-hier Date: Fri, 29 Sep 2023 22:36:24 +0300 Subject: [PATCH 13/22] Add "pl-delete-all" command --- Doc/cmus.txt | 3 +++ command_mode.c | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/Doc/cmus.txt b/Doc/cmus.txt index 1b19c1c0f..3d729db20 100644 --- a/Doc/cmus.txt +++ b/Doc/cmus.txt @@ -516,6 +516,9 @@ pl-import [filename] pl-rename Renames the selected playlist. +pl-delete-all + Deletes all playlists. + player-next (*b*) Skips to the next track. diff --git a/command_mode.c b/command_mode.c index f87719cc8..761ca9abc 100644 --- a/command_mode.c +++ b/command_mode.c @@ -1354,6 +1354,11 @@ static void cmd_pl_rename(char *arg) info_msg(":pl-rename only works in view 3"); } +static void cmd_pl_delete_all(char *arg) +{ + pl_delete_all(); +} + static void cmd_version(char *arg) { info_msg(VERSION); @@ -2645,6 +2650,7 @@ struct command commands[] = { { "pl-export", cmd_pl_export, 1, -1, NULL, 0, 0 }, { "pl-import", cmd_pl_import, 0, -1, NULL, 0, 0 }, { "pl-rename", cmd_pl_rename, 1, -1, NULL, 0, 0 }, + { "pl-delete-all", cmd_pl_delete_all, 0, 0, NULL, 0, 0 }, { "push", cmd_push, 0, -1, expand_commands, 0, 0 }, { "pwd", cmd_pwd, 0, 0, NULL, 0, 0 }, { "raise-vte", cmd_raise_vte, 0, 0, NULL, 0, 0 }, From 1c7048380d7ed7f5634c584ecfd423e78525affe Mon Sep 17 00:00:00 2001 From: rodenet-hier Date: Fri, 29 Sep 2023 23:36:01 +0300 Subject: [PATCH 14/22] Add "pl-delete" command --- Doc/cmus.txt | 3 +++ command_mode.c | 6 ++++++ pl.c | 11 +++++++++++ pl.h | 1 + 4 files changed, 21 insertions(+) diff --git a/Doc/cmus.txt b/Doc/cmus.txt index 3d729db20..c8f8e679b 100644 --- a/Doc/cmus.txt +++ b/Doc/cmus.txt @@ -516,6 +516,9 @@ pl-import [filename] pl-rename Renames the selected playlist. +pl-delete + Deletes the playlist with the given name. + pl-delete-all Deletes all playlists. diff --git a/command_mode.c b/command_mode.c index 761ca9abc..c8640cb69 100644 --- a/command_mode.c +++ b/command_mode.c @@ -1354,6 +1354,11 @@ static void cmd_pl_rename(char *arg) info_msg(":pl-rename only works in view 3"); } +static void cmd_pl_delete(char *arg) +{ + pl_delete_by_name(arg); +} + static void cmd_pl_delete_all(char *arg) { pl_delete_all(); @@ -2650,6 +2655,7 @@ struct command commands[] = { { "pl-export", cmd_pl_export, 1, -1, NULL, 0, 0 }, { "pl-import", cmd_pl_import, 0, -1, NULL, 0, 0 }, { "pl-rename", cmd_pl_rename, 1, -1, NULL, 0, 0 }, + { "pl-delete", cmd_pl_delete, 1, 1, NULL, 0, 0 }, { "pl-delete-all", cmd_pl_delete_all, 0, 0, NULL, 0, 0 }, { "push", cmd_push, 0, -1, expand_commands, 0, 0 }, { "pwd", cmd_pwd, 0, 0, NULL, 0, 0 }, diff --git a/pl.c b/pl.c index d93b8998a..fe9ec026e 100644 --- a/pl.c +++ b/pl.c @@ -838,6 +838,17 @@ void pl_rand(void) editable_rand(&pl_visible->editable); } +void pl_delete_by_name(char *name) +{ + struct playlist *pl; + list_for_each_entry(pl, &pl_head, node) { + if (strcmp(pl->name, name) == 0) + break; + } + pl_delete(pl); +} + + void pl_win_mv_after(void) { if (pl_cursor_in_track_window) diff --git a/pl.h b/pl.h index 55fa9e290..b1086bc5e 100644 --- a/pl.h +++ b/pl.h @@ -89,6 +89,7 @@ void pl_mark(char *arg); void pl_unmark(void); void pl_rand(void); void pl_delete_all(void); +void pl_delete_by_name(char *arg); void pl_win_mv_after(void); void pl_win_mv_before(void); void pl_win_remove(void); From f4a4a3824f4cb157dc5e4f92f09cd0f869e95ee7 Mon Sep 17 00:00:00 2001 From: rodenet-hier Date: Mon, 16 Oct 2023 20:08:28 +0300 Subject: [PATCH 15/22] Move pl-delete-all to pl-delete -a --- Doc/cmus.txt | 6 +++--- command_mode.c | 12 +++++------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/Doc/cmus.txt b/Doc/cmus.txt index c8f8e679b..cf71af4d9 100644 --- a/Doc/cmus.txt +++ b/Doc/cmus.txt @@ -516,11 +516,11 @@ pl-import [filename] pl-rename Renames the selected playlist. -pl-delete +pl-delete [-a] Deletes the playlist with the given name. -pl-delete-all - Deletes all playlists. + -a + Deletes all playlists player-next (*b*) Skips to the next track. diff --git a/command_mode.c b/command_mode.c index c8640cb69..d04dfce48 100644 --- a/command_mode.c +++ b/command_mode.c @@ -1356,12 +1356,11 @@ static void cmd_pl_rename(char *arg) static void cmd_pl_delete(char *arg) { - pl_delete_by_name(arg); -} - -static void cmd_pl_delete_all(char *arg) -{ - pl_delete_all(); + int flag = parse_flags((const char **)&arg, "a"); + if (flag == 'a') + pl_delete_all(); + else + pl_delete_by_name(arg); } static void cmd_version(char *arg) @@ -2656,7 +2655,6 @@ struct command commands[] = { { "pl-import", cmd_pl_import, 0, -1, NULL, 0, 0 }, { "pl-rename", cmd_pl_rename, 1, -1, NULL, 0, 0 }, { "pl-delete", cmd_pl_delete, 1, 1, NULL, 0, 0 }, - { "pl-delete-all", cmd_pl_delete_all, 0, 0, NULL, 0, 0 }, { "push", cmd_push, 0, -1, expand_commands, 0, 0 }, { "pwd", cmd_pwd, 0, 0, NULL, 0, 0 }, { "raise-vte", cmd_raise_vte, 0, 0, NULL, 0, 0 }, From 80535c15b17b88980dd7118be4073886bac1738d Mon Sep 17 00:00:00 2001 From: rodenet-hier Date: Mon, 16 Oct 2023 20:11:54 +0300 Subject: [PATCH 16/22] Do pl_create_default on pl_delete of last playlist --- pl.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/pl.c b/pl.c index fe9ec026e..3a1e6dd69 100644 --- a/pl.c +++ b/pl.c @@ -422,7 +422,7 @@ static void pl_save_all(void) static void pl_delete(struct playlist *pl) { if (list_len(&pl_head) == 1) - return; + pl_create_default(); struct iter iter; pl_to_iter(pl, &iter); @@ -454,11 +454,6 @@ static void pl_delete(struct playlist *pl) static void pl_delete_selected_pl(void) { - if (list_len(&pl_head) == 1) { - error_msg("cannot delete the last playlist"); - return; - } - if (yes_no_query("Delete selected playlist? [y/N]") != UI_QUERY_ANSWER_YES) return; @@ -477,7 +472,6 @@ void pl_delete_all(void) } pl_delete(pl); } - pl_create_default(); pl_delete(last); } From c5ab767801be68e19cd8d852085ca896921f1ea3 Mon Sep 17 00:00:00 2001 From: rodenet-hier Date: Mon, 16 Oct 2023 20:18:55 +0300 Subject: [PATCH 17/22] Add error_msg to pl_delete_by_name --- pl.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/pl.c b/pl.c index 3a1e6dd69..be39d8842 100644 --- a/pl.c +++ b/pl.c @@ -460,6 +460,18 @@ static void pl_delete_selected_pl(void) pl_delete(pl_visible); } +void pl_delete_by_name(char *name) +{ + struct playlist *pl; + list_for_each_entry(pl, &pl_head, node) { + if (strcmp(pl->name, name) == 0) { + pl_delete(pl); + return; + } + } + error_msg("couldn't find a playlist named '%s' to delete", name); +} + void pl_delete_all(void) { struct playlist *pl; @@ -832,17 +844,6 @@ void pl_rand(void) editable_rand(&pl_visible->editable); } -void pl_delete_by_name(char *name) -{ - struct playlist *pl; - list_for_each_entry(pl, &pl_head, node) { - if (strcmp(pl->name, name) == 0) - break; - } - pl_delete(pl); -} - - void pl_win_mv_after(void) { if (pl_cursor_in_track_window) From f5fe3333bcf96947dea7f790962e83d55721ce36 Mon Sep 17 00:00:00 2001 From: rodenet-hier Date: Tue, 17 Oct 2023 20:20:16 +0300 Subject: [PATCH 18/22] Fix alphabetical order of pl-delete in docs --- Doc/cmus.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Doc/cmus.txt b/Doc/cmus.txt index cf71af4d9..08bf322c8 100644 --- a/Doc/cmus.txt +++ b/Doc/cmus.txt @@ -505,6 +505,12 @@ mute pl-create Creates a new playlist. +pl-delete [-a] + Deletes the playlist with the given name. + + -a + Deletes all playlists + pl-export Exports the currently selected playlist. The file will be overwritten if it exists. @@ -516,12 +522,6 @@ pl-import [filename] pl-rename Renames the selected playlist. -pl-delete [-a] - Deletes the playlist with the given name. - - -a - Deletes all playlists - player-next (*b*) Skips to the next track. From 22ef2960c9fc0fea1b7523563229d1a2171670a1 Mon Sep 17 00:00:00 2001 From: rodenet-hier Date: Tue, 17 Oct 2023 20:38:40 +0300 Subject: [PATCH 19/22] Remove unnecessary last variable in pl_delete_all --- pl.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/pl.c b/pl.c index be39d8842..3831e7c73 100644 --- a/pl.c +++ b/pl.c @@ -476,15 +476,12 @@ void pl_delete_all(void) { struct playlist *pl; struct playlist *temp; - struct playlist *last = NULL; list_for_each_entry_safe(pl, temp, &pl_head, node) { - if (list_len(&pl_head) == 1) { - last = pl; + if (list_len(&pl_head) == 1) break; - } pl_delete(pl); } - pl_delete(last); + pl_delete(pl); } static void pl_mark_selected_pl(void) From 1f9964a4588884aa3aa8058414e138d9d2bb62e3 Mon Sep 17 00:00:00 2001 From: Gavin Troy Date: Mon, 21 Aug 2023 21:11:24 +0100 Subject: [PATCH 20/22] Fix gbuf to account for print width of control characters Such characters are stored as one byte but print as "<07>" for example. --- gbuf.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gbuf.c b/gbuf.c index 7d52093ef..9f57d7433 100644 --- a/gbuf.c +++ b/gbuf.c @@ -109,10 +109,11 @@ static int gbuf_mark_clipped_text(struct gbuf *buf) void gbuf_add_ustr(struct gbuf *buf, const char *src, int *width) { - gbuf_grow(buf, strlen(src)); + int src_bytes = u_str_print_size(src) - 1; + gbuf_grow(buf, src_bytes); size_t copy_bytes = u_copy_chars(buf->buffer + buf->len, src, width); gbuf_used(buf, copy_bytes); - if (src[copy_bytes] != '\0') { + if (copy_bytes != src_bytes) { gbuf_set(buf, ' ', *width); *width = gbuf_mark_clipped_text(buf); } From a37bfb07f5129518cfaf4c2f0c504a358202a7c3 Mon Sep 17 00:00:00 2001 From: Gavin Troy Date: Mon, 24 Jul 2023 00:47:47 +0100 Subject: [PATCH 21/22] Fix player-next-album / player-prev-album with play_sorted --- lib.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib.c b/lib.c index d6988ba8f..96904b4de 100644 --- a/lib.c +++ b/lib.c @@ -565,7 +565,7 @@ struct track_info *lib_goto_next_album(void) if (play_sorted) track = sorted_album_first_track(track); } else if (play_sorted) { - track = sorted_album_last_track(track); + track = sorted_album_last_track(lib_cur_track); track = (struct tree_track *)simple_list_get_next(&lib_editable.head, (struct simple_track *)track, aaa_mode_filter, true); } else { @@ -592,7 +592,7 @@ struct track_info *lib_goto_prev_album(void) else if (track) track = album_first_track(track->album); } else if (play_sorted) { - track = sorted_album_first_track(track); + track = sorted_album_first_track(lib_cur_track); track = (struct tree_track *)simple_list_get_prev(&lib_editable.head, (struct simple_track *)track, aaa_mode_filter, true); track = sorted_album_first_track(track); From 23afab39902d3d97c47697196b07581305337529 Mon Sep 17 00:00:00 2001 From: Gavin Troy Date: Sun, 13 Aug 2023 13:48:54 +0100 Subject: [PATCH 22/22] Fix ignore_duplicates missing filtered tracks Really just checking for duplicates again when unfiltering. A bit messy but it does the job. Relates to #423. --- lib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib.c b/lib.c index 96904b4de..4ae3d856b 100644 --- a/lib.c +++ b/lib.c @@ -631,7 +631,7 @@ static void hash_add_to_views(void) while (e) { struct track_info *ti = e->ti; - if (!is_filtered(ti)) + if (!is_filtered(ti) && !(ignore_duplicates && track_exists(ti))) views_add_track(ti); e = e->next; }