about summary refs log tree commit diff stats
path: root/pkgs/by-name/mp/mpdpopm/src
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2026-02-19 22:31:49 +0100
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2026-02-19 22:31:49 +0100
commit0f08d6ae51a14144ff791b4a051751a5156707e8 (patch)
tree2b10e911fd4baf59e92c5a8d938019c0265b1bf1 /pkgs/by-name/mp/mpdpopm/src
parentmodules/legacy/beets/plugins/inline: Don't fail on empty `albumartists` (diff)
downloadnixos-config-0f08d6ae51a14144ff791b4a051751a5156707e8.zip
pkgs/mpdpopmd: Support a stats show and setting selection priority for dj
Diffstat (limited to 'pkgs/by-name/mp/mpdpopm/src')
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm/cli.rs233
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm/main.rs (renamed from pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm.rs)421
2 files changed, 437 insertions, 217 deletions
diff --git a/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm/cli.rs b/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm/cli.rs
new file mode 100644
index 00000000..c20bf3fa
--- /dev/null
+++ b/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm/cli.rs
@@ -0,0 +1,233 @@
+use clap::{Parser, Subcommand};
+use std::path::PathBuf;
+
+/// `mppopmd' client
+#[derive(Parser)]
+pub(crate) struct Args {
+    /// path to configuration file
+    #[arg(short, long)]
+    pub(crate) config: Option<PathBuf>,
+
+    /// enable verbose logging
+    #[arg(short, long)]
+    pub(crate) verbose: bool,
+
+    /// enable debug loggin (implies --verbose)
+    #[arg(short, long)]
+    pub(crate) debug: bool,
+
+    #[command(subcommand)]
+    pub(crate) command: SubCommand,
+}
+
+#[derive(Subcommand)]
+pub(crate) enum RatingCommand {
+    /// retrieve the rating for one or more tracks
+    ///
+    /// With no arguments, retrieve the rating of the current song & print it
+    /// on stdout. With one argument, retrieve that track's rating & print it
+    /// on stdout. With multiple arguments, print their ratings on stdout, one
+    /// per line, prefixed by the track name.
+    ///
+    /// Ratings are expressed as an integer between -128 & 128, exclusive, with
+    /// the convention that 0 denotes "un-rated".
+    #[clap(verbatim_doc_comment)]
+    Get {
+        /// Always show the song URI, even when there is only one track
+        #[arg(short, long)]
+        with_uri: bool,
+
+        tracks: Option<Vec<String>>,
+    },
+
+    /// set the rating for one track
+    ///
+    /// With one argument, set the rating of the current song to that argument.
+    /// With a second argument, rate that song at the first argument. Ratings
+    /// may be expressed a an integer between 0 & 255, inclusive.
+    #[clap(verbatim_doc_comment)]
+    Set { rating: i8, track: Option<String> },
+
+    /// increment the rating for one track
+    ///
+    /// With one argument, increment the rating of the current song.
+    /// With a second argument, rate that song at the first argument.
+    #[clap(verbatim_doc_comment)]
+    Inc { track: Option<String> },
+
+    /// decrement the rating for one track
+    ///
+    /// With one argument, decrement the rating of the current song.
+    /// With a second argument, rate that song at the first argument.
+    #[clap(verbatim_doc_comment)]
+    Decr { track: Option<String> },
+}
+
+#[derive(Subcommand)]
+pub(crate) enum PlayCountCommand {
+    /// retrieve the play count for one or more tracks
+    ///
+    /// With no arguments, retrieve the play count of the current song & print it
+    /// on stdout. With one argument, retrieve that track's play count & print it
+    /// on stdout. With multiple arguments, print their play counts on stdout, one
+    /// per line, prefixed by the track name.
+    #[clap(verbatim_doc_comment)]
+    Get {
+        /// Always show the song URI, even when there is only one track
+        #[arg(short, long)]
+        with_uri: bool,
+
+        tracks: Option<Vec<String>>,
+    },
+
+    /// set the play count for one track
+    ///
+    /// With one argument, set the play count of the current song to that argument. With a
+    /// second argument, set the play count for that song to the first.
+    #[clap(verbatim_doc_comment)]
+    Set {
+        play_count: usize,
+        track: Option<String>,
+    },
+}
+
+#[derive(Subcommand)]
+pub(crate) enum LastPlayedCommand {
+    /// retrieve the last played timestamp for one or more tracks
+    ///
+    /// With no arguments, retrieve the last played timestamp of the current
+    /// song & print it on stdout. With one argument, retrieve that track's
+    /// last played time & print it on stdout. With multiple arguments, print
+    /// their last played times on stdout, one per line, prefixed by the track
+    /// name.
+    ///
+    /// The last played timestamp is expressed in seconds since Unix epoch.
+    #[clap(verbatim_doc_comment)]
+    Get {
+        /// Always show the song URI, even when there is only one track
+        #[arg(short, long)]
+        with_uri: bool,
+
+        tracks: Option<Vec<String>>,
+    },
+
+    /// set the last played timestamp for one track
+    ///
+    /// With one argument, set the last played time of the current song. With two
+    /// arguments, set the last played time for the second argument to the first.
+    /// The last played timestamp is expressed in seconds since Unix epoch.
+    #[clap(verbatim_doc_comment)]
+    Set {
+        last_played: u64,
+        track: Option<String>,
+    },
+}
+
+#[derive(Subcommand)]
+pub(crate) enum PlaylistsCommand {
+    /// retrieve the list of stored playlists
+    #[clap(verbatim_doc_comment)]
+    Get {},
+}
+
+#[derive(Subcommand)]
+pub(crate) enum DjCommand {
+    /// Activate the automatic DJ mode on the mpdpopmd daemon.
+    ///
+    /// In this mode, the daemon will automatically add new tracks to the playlist based on a
+    /// recommendation algorithm.
+    #[clap(verbatim_doc_comment)]
+    Start {
+        /// The chance to select a "positive" track
+        #[arg(long, default_value_t = 0.65)]
+        positive_chance: f64,
+
+        /// The chance to select a "neutral" track
+        #[arg(long, default_value_t = 0.5)]
+        neutral_chance: f64,
+
+        /// The chance to select a "negative" track
+        #[arg(long, default_value_t = 0.2)]
+        negative_chance: f64,
+    },
+
+    /// Deactivate the automatic DJ mode on the mpdpopmd daemon.
+    ///
+    /// In this mode, the daemon will automatically add new tracks to the playlist based on a
+    /// recommendation algorithm.
+    #[clap(verbatim_doc_comment)]
+    Stop {},
+}
+
+#[derive(Subcommand)]
+pub(crate) enum SubCommand {
+    /// Change details about rating.
+    Rating {
+        #[command(subcommand)]
+        command: RatingCommand,
+    },
+
+    /// Change details about play count.
+    PlayCount {
+        #[command(subcommand)]
+        command: PlayCountCommand,
+    },
+
+    /// Change details about last played date.
+    LastPlayed {
+        #[command(subcommand)]
+        command: LastPlayedCommand,
+    },
+
+    /// Change details about generated playlists.
+    Playlists {
+        #[command(subcommand)]
+        command: PlaylistsCommand,
+    },
+
+    /// search for songs matching matching a filter and add them to the queue
+    ///
+    /// This command extends the MPD command `searchadd' (which will search the MPD database) to allow
+    /// searches on attributes managed by mpdpopm: rating, playcount & last played time.
+    ///
+    /// The MPD `searchadd' <https://www.musicpd.org/doc/html/protocol.html#command-searchadd> will search
+    /// the MPD database for songs that match a given filter & add them to the play queue. The filter syntax
+    /// is documented here <https://www.musicpd.org/doc/html/protocol.html#filter-syntax>.
+    ///
+    /// This command adds three new terms on which you can filter: rating, playcount & lastplayed. Each is
+    /// expressed as an unsigned integer, with zero interpreted as "not set". For instance:
+    ///
+    ///     mppopm searchadd "(rating > 2)"
+    ///
+    /// Will add all songs in the library with a rating sticker > 2 to the play queue.
+    ///
+    /// mppopm also introduces OR clauses (MPD only supports AND), so that:
+    ///
+    ///     mppopm searchadd "((rating > 2) AND (artist =~ \"pogues\"))"
+    ///
+    /// will add all songs whose artist tag matches the regexp "pogues" with a rating greater than
+    /// 2.
+    #[clap(verbatim_doc_comment)]
+    Searchadd {
+        filter: String,
+
+        /// Respect the casing, when performing the filter evaluation.
+        #[arg(short, long, default_value_t = false)]
+        case_sensitive: bool,
+    },
+
+    /// Modify the automatic DJ mode on the mpdpopmd daemon.
+    ///
+    /// In this mode, the daemon will automatically add new tracks to the playlist based on a
+    /// recommendation algorithm.
+    Dj {
+        #[command(subcommand)]
+        command: DjCommand,
+    },
+
+    /// Show general stats about your music collection.
+    ///
+    /// This includes favorite artist, songs and also the negative ones.
+    Stats {
+    }
+}
diff --git a/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm.rs b/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm/main.rs
index faa651bf..42f01873 100644
--- a/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm.rs
+++ b/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm/main.rs
@@ -26,21 +26,38 @@
 //! along the lines of [mpdcron](https://alip.github.io/mpdcron)). `mppopm` is a command-line client
 //! for `mppopmd`. Run `mppopm --help` for detailed usage.
 
+use std::{collections::HashMap, io::stdout};
+
+use clap::Parser;
 use mpdpopm::{
     clients::{Client, PlayerStatus},
     config::{self, Config},
+    dj::algorithms::Discovery,
     filters::ExpressionParser,
     filters_ast::{FilterStickerNames, evaluate},
     messanges::COMMAND_CHANNEL,
-    storage::{last_played, play_count, rating},
+    storage::{last_played, play_count, rating, skip_count},
 };
 
 use anyhow::{Context, Result, anyhow, bail};
-use clap::{Parser, Subcommand};
+use ratatui::{
+    Terminal, TerminalOptions, Viewport,
+    crossterm::style::Stylize,
+    layout::HorizontalAlignment,
+    prelude::CrosstermBackend,
+    style::{Color, Style},
+    text::Line,
+    widgets::{Bar, BarChart, BarGroup, Block, Borders},
+};
 use tracing::{debug, info, level_filters::LevelFilter, trace};
 use tracing_subscriber::{EnvFilter, Registry, layer::SubscriberExt};
 
-use std::path::PathBuf;
+use crate::cli::{
+    Args, DjCommand, LastPlayedCommand, PlayCountCommand, PlaylistsCommand, RatingCommand,
+    SubCommand,
+};
+
+mod cli;
 
 /// Map `tracks' argument(s) to a Vec of String containing one or more mpd URIs
 ///
@@ -296,219 +313,6 @@ async fn searchadd(client: &mut Client, filter: &str, case_sensitive: bool) -> R
     }
 }
 
-/// `mppopmd' client
-#[derive(Parser)]
-struct Args {
-    /// path to configuration file
-    #[arg(short, long)]
-    config: Option<PathBuf>,
-
-    /// enable verbose logging
-    #[arg(short, long)]
-    verbose: bool,
-
-    /// enable debug loggin (implies --verbose)
-    #[arg(short, long)]
-    debug: bool,
-
-    #[command(subcommand)]
-    command: SubCommand,
-}
-
-#[derive(Subcommand)]
-enum RatingCommand {
-    /// retrieve the rating for one or more tracks
-    ///
-    /// With no arguments, retrieve the rating of the current song & print it
-    /// on stdout. With one argument, retrieve that track's rating & print it
-    /// on stdout. With multiple arguments, print their ratings on stdout, one
-    /// per line, prefixed by the track name.
-    ///
-    /// Ratings are expressed as an integer between -128 & 128, exclusive, with
-    /// the convention that 0 denotes "un-rated".
-    #[clap(verbatim_doc_comment)]
-    Get {
-        /// Always show the song URI, even when there is only one track
-        #[arg(short, long)]
-        with_uri: bool,
-
-        tracks: Option<Vec<String>>,
-    },
-
-    /// set the rating for one track
-    ///
-    /// With one argument, set the rating of the current song to that argument.
-    /// With a second argument, rate that song at the first argument. Ratings
-    /// may be expressed a an integer between 0 & 255, inclusive.
-    #[clap(verbatim_doc_comment)]
-    Set { rating: i8, track: Option<String> },
-
-    /// increment the rating for one track
-    ///
-    /// With one argument, increment the rating of the current song.
-    /// With a second argument, rate that song at the first argument.
-    #[clap(verbatim_doc_comment)]
-    Inc { track: Option<String> },
-
-    /// decrement the rating for one track
-    ///
-    /// With one argument, decrement the rating of the current song.
-    /// With a second argument, rate that song at the first argument.
-    #[clap(verbatim_doc_comment)]
-    Decr { track: Option<String> },
-}
-
-#[derive(Subcommand)]
-enum PlayCountCommand {
-    /// retrieve the play count for one or more tracks
-    ///
-    /// With no arguments, retrieve the play count of the current song & print it
-    /// on stdout. With one argument, retrieve that track's play count & print it
-    /// on stdout. With multiple arguments, print their play counts on stdout, one
-    /// per line, prefixed by the track name.
-    #[clap(verbatim_doc_comment)]
-    Get {
-        /// Always show the song URI, even when there is only one track
-        #[arg(short, long)]
-        with_uri: bool,
-
-        tracks: Option<Vec<String>>,
-    },
-
-    /// set the play count for one track
-    ///
-    /// With one argument, set the play count of the current song to that argument. With a
-    /// second argument, set the play count for that song to the first.
-    #[clap(verbatim_doc_comment)]
-    Set {
-        play_count: usize,
-        track: Option<String>,
-    },
-}
-
-#[derive(Subcommand)]
-enum LastPlayedCommand {
-    /// retrieve the last played timestamp for one or more tracks
-    ///
-    /// With no arguments, retrieve the last played timestamp of the current
-    /// song & print it on stdout. With one argument, retrieve that track's
-    /// last played time & print it on stdout. With multiple arguments, print
-    /// their last played times on stdout, one per line, prefixed by the track
-    /// name.
-    ///
-    /// The last played timestamp is expressed in seconds since Unix epoch.
-    #[clap(verbatim_doc_comment)]
-    Get {
-        /// Always show the song URI, even when there is only one track
-        #[arg(short, long)]
-        with_uri: bool,
-
-        tracks: Option<Vec<String>>,
-    },
-
-    /// set the last played timestamp for one track
-    ///
-    /// With one argument, set the last played time of the current song. With two
-    /// arguments, set the last played time for the second argument to the first.
-    /// The last played timestamp is expressed in seconds since Unix epoch.
-    #[clap(verbatim_doc_comment)]
-    Set {
-        last_played: u64,
-        track: Option<String>,
-    },
-}
-
-#[derive(Subcommand)]
-enum PlaylistsCommand {
-    /// retrieve the list of stored playlists
-    #[clap(verbatim_doc_comment)]
-    Get {},
-}
-
-#[derive(Subcommand)]
-enum DjCommand {
-    /// Activate the automatic DJ mode on the mpdpopmd daemon.
-    ///
-    /// In this mode, the daemon will automatically add new tracks to the playlist based on a
-    /// recommendation algorithm.
-    #[clap(verbatim_doc_comment)]
-    Start {},
-
-    /// Deactivate the automatic DJ mode on the mpdpopmd daemon.
-    ///
-    /// In this mode, the daemon will automatically add new tracks to the playlist based on a
-    /// recommendation algorithm.
-    #[clap(verbatim_doc_comment)]
-    Stop {},
-}
-
-#[derive(Subcommand)]
-enum SubCommand {
-    /// Change details about rating.
-    Rating {
-        #[command(subcommand)]
-        command: RatingCommand,
-    },
-
-    /// Change details about play count.
-    PlayCount {
-        #[command(subcommand)]
-        command: PlayCountCommand,
-    },
-
-    /// Change details about last played date.
-    LastPlayed {
-        #[command(subcommand)]
-        command: LastPlayedCommand,
-    },
-
-    /// Change details about generated playlists.
-    Playlists {
-        #[command(subcommand)]
-        command: PlaylistsCommand,
-    },
-
-    /// search for songs matching matching a filter and add them to the queue
-    ///
-    /// This command extends the MPD command `searchadd' (which will search the MPD database) to allow
-    /// searches on attributes managed by mpdpopm: rating, playcount & last played time.
-    ///
-    /// The MPD `searchadd' <https://www.musicpd.org/doc/html/protocol.html#command-searchadd> will search
-    /// the MPD database for songs that match a given filter & add them to the play queue. The filter syntax
-    /// is documented here <https://www.musicpd.org/doc/html/protocol.html#filter-syntax>.
-    ///
-    /// This command adds three new terms on which you can filter: rating, playcount & lastplayed. Each is
-    /// expressed as an unsigned integer, with zero interpreted as "not set". For instance:
-    ///
-    ///     mppopm searchadd "(rating > 2)"
-    ///
-    /// Will add all songs in the library with a rating sticker > 2 to the play queue.
-    ///
-    /// mppopm also introduces OR clauses (MPD only supports AND), so that:
-    ///
-    ///     mppopm searchadd "((rating > 2) AND (artist =~ \"pogues\"))"
-    ///
-    /// will add all songs whose artist tag matches the regexp "pogues" with a rating greater than
-    /// 2.
-    #[clap(verbatim_doc_comment)]
-    Searchadd {
-        filter: String,
-
-        /// Respect the casing, when performing the filter evaluation.
-        #[arg(short, long, default_value_t = false)]
-        case_sensitive: bool,
-    },
-
-    /// Modify the automatic DJ mode on the mpdpopmd daemon.
-    ///
-    /// In this mode, the daemon will automatically add new tracks to the playlist based on a
-    /// recommendation algorithm.
-    Dj {
-        #[command(subcommand)]
-        command: DjCommand,
-    },
-}
-
 #[tokio::main]
 async fn main() -> Result<()> {
     let args = Args::parse();
@@ -597,8 +401,191 @@ async fn main() -> Result<()> {
             case_sensitive,
         } => searchadd(&mut client, &filter, case_sensitive).await,
         SubCommand::Dj { command } => match command {
-            DjCommand::Start {} => client.send_message(COMMAND_CHANNEL, "dj start").await,
+            DjCommand::Start {
+                positive_chance,
+                neutral_chance,
+                negative_chance,
+            } => {
+                client
+                    .send_message(
+                        COMMAND_CHANNEL,
+                        format!(
+                            "dj start \
+                            --positive-chance {positive_chance} \
+                            --neutral-chance {neutral_chance} \
+                            --negative-chance {negative_chance}"
+                        )
+                        .as_str(),
+                    )
+                    .await
+            }
             DjCommand::Stop {} => client.send_message(COMMAND_CHANNEL, "dj stop").await,
         },
+        SubCommand::Stats {} => {
+            struct Rating {
+                play_count: Option<usize>,
+                skip_count: Option<usize>,
+                last_played: Option<u64>,
+                rating: Option<i8>,
+                dj_weight: i64,
+            }
+            fn vertical_bar<'a>(count: i64, amount: usize) -> Bar<'a> {
+                fn amount_style(amount: usize) -> Style {
+                    let green = (255.0 * (1.0 - ((amount as f64) - 50.0) / 40.0)) as u8;
+                    let color = Color::Rgb(255, green, 0);
+
+                    Style::new().fg(color)
+                }
+
+                Bar::default()
+                    .value(amount as u64)
+                    .label(Line::from(count.to_string()))
+                    .style(amount_style(amount))
+                    .value_style(amount_style(amount).reversed())
+            }
+            macro_rules! top_five {
+                ($(@$convert:tt)? mode = $mode:tt, $rating_map:expr, $key:ident, $($other:ident),* $(,)?) => {
+                    let mut vec = $rating_map
+                        .iter()
+                        .filter_map(|(track, rating)| top_five!(@convert $($convert)? rating.$key).map(|v| (track, v, rating)))
+                        .collect::<Vec<_>>();
+                    vec.sort_by_key(|(_, pc, _)| *pc);
+
+                        top_five!(@gen_mode $mode, vec.iter())
+                        .take(5)
+                        .for_each(|(song, play_count, rating)| {
+                            println!(
+                                concat!("  - {}: {}", $(top_five!(@gen_empty $other)),*),
+                                <String as Clone>::clone(&song).bold().blue(),
+                                play_count.to_string().bold().white(),
+                                $(
+                                    rating
+                                        .$other
+                                        .map(|r| format!(" ({}: {r})", stringify!($other)))
+                                        .unwrap_or(String::new()),
+                                )*
+                            )
+                        });
+                };
+                (@gen_mode top, $expr:expr) => {
+                    $expr.rev()
+                };
+                (@gen_mode bottom, $expr:expr) => {
+                    $expr
+                };
+                (@gen_empty $tt:tt) => {
+                    "{}"
+                };
+                (@convert convert_to_option $tt:expr) => {
+                    Some($tt)
+                };
+                (@convert $tt:expr) => {
+                    $tt
+                }
+            }
+            macro_rules! histogram {
+                ($(@$convert:tt)? $rating_map:expr, $key:ident, $title:literal) => {
+                    let backend = CrosstermBackend::new(stdout());
+                    let viewport = Viewport::Inline(20);
+                    let mut terminal =
+                        Terminal::with_options(backend, TerminalOptions { viewport })?;
+
+                    let result = (|| {
+                        terminal.draw(|frame| {
+                            let line_chart = frame.area();
+
+                            let bars: Vec<Bar> = {
+                                let mut map = HashMap::new();
+                                $rating_map
+                                    .values()
+                                    .filter_map(|rating| histogram!(@convert $($convert)? rating.$key))
+                                    .for_each(|dj_weight| {
+                                        map.entry(dj_weight)
+                                            .and_modify(|e| {
+                                                *e += 1;
+                                            })
+                                            .or_insert(1);
+                                    });
+
+                                let mut vec = map.into_iter().collect::<Vec<(_, _)>>();
+                                vec.sort_by_key(|(pc, _)| *pc);
+
+                                vec.into_iter()
+                            }
+                            .map(|(dj_weight, amount)| vertical_bar(dj_weight.try_into().expect("Should be convertible"), amount))
+                            .collect();
+
+                            let title = Line::from($title).centered();
+                            let chart = BarChart::default()
+                                .data(BarGroup::default().bars(&bars))
+                                .block(
+                                    Block::new()
+                                        .title(title)
+                                        .title_alignment(HorizontalAlignment::Left)
+                                        .borders(Borders::all()),
+                                )
+                                .bar_width(5);
+
+                            frame.render_widget(chart, line_chart);
+                        })?;
+
+                        Ok::<_, anyhow::Error>(())
+                    })();
+
+                    ratatui::restore();
+                    println!();
+                    result?;
+                };
+                (@convert convert_to_option $val:expr) => {
+                    Some($val)
+                };
+                (@convert $val:expr) => {
+                    $val
+                };
+            }
+
+            let all = client.get_all_songs().await?;
+
+            let mut rating_map = HashMap::new();
+            for song in &all {
+                let rating = Rating {
+                    play_count: play_count::get(&mut client, song).await?,
+                    skip_count: skip_count::get(&mut client, song).await?,
+                    last_played: last_played::get(&mut client, song).await?,
+                    rating: rating::get(&mut client, song).await?,
+                    dj_weight: Discovery::weight_track(&mut client, song).await?,
+                };
+                rating_map.insert(song, rating);
+            }
+
+            let played_songs = rating_map
+                .values()
+                .filter(|s| s.last_played.is_some())
+                .count();
+
+            println!(
+                "Songs played: {:.2}%",
+                (played_songs as f64 / all.len() as f64) * 100.0
+            );
+
+            histogram!(rating_map, play_count, "Play counts");
+
+            println!("\nMost played songs:");
+            top_five!(mode = top, rating_map, play_count, skip_count, rating);
+
+            println!("\nMost skipped songs:");
+            top_five!(mode = top, rating_map, skip_count, play_count, rating);
+
+            println!("\nTop songs based on dj weight:");
+            top_five!(@convert_to_option mode = top, rating_map, dj_weight, rating, play_count, skip_count);
+
+            println!("\nBottom 5 songs based on dj weight:");
+            top_five!(@convert_to_option mode = bottom, rating_map, dj_weight, rating, play_count, skip_count);
+
+            println!();
+            histogram!(@convert_to_option rating_map, dj_weight, "Dj weights");
+
+            Ok(())
+        }
     }
 }