Instantly share code, notes, and snippets.

@phleagol /Ncmpcpp3
Last active Aug 22, 2017

Embed
What would you like to do?
Fvwm ncmpcpp
An ncmpcpp music player embedded within an fvwm panel. An additional
fvwmperl module manages the progress bar, coverart and elapsed.
Screenshot: http://i.imgur.com/2X82oyS.png
Files listed below :-
1. PanelNcmpcpp3
The panel definition, with related functions and styles.
The StartNcmpcpp3 function starts the music player.
2. ~/.fvwm/lib/Ncmpcpp3
An fvwmperl module that maintains the progress bar, elapsed, and coverart.
3. ~/.ncmpcpp/config-ohsnap3 - the ncmpcpp config
4. ~/bin/mp3rate - an eyed3 script to change the current tracks rating.*
5. ~/bin/addcolor - perl script to change the terminal colors.
* Track ratings are not native to ncmpcpp, so I have used a hacky approach. In
my music collection, the album-artist tag stores the track rating. Ncmpcpp is
quite happy to display the album-artist tag. The mp3rate script above will
change the rating of the current track, and is accessed via a script. This
has been discussed further here :-
https://www.reddit.com/r/unixporn/comments/5wpm4f/music_player_with_ability_to_rate_songs_ideally/deecgcc/
#!/usr/bin/perl -w
use Term::ExtendedColor::Xresources qw( set_xterm_color ) ;
while (<>) {
if (/^\D*color(\d+):?\s+#?(\w+)/) {
set_xterm_color({ $1 => $2}) ;
}
}
## ┏┓╻┏━╸┏┳┓┏━┓┏━╸┏━┓┏━┓ ┏━╸┏━┓┏┓╻┏━╸╻┏━╸
## ┃┗┫┃ ┃┃┃┣━┛┃ ┣━┛┣━┛ ┃ ┃ ┃┃┗┫┣╸ ┃┃╺┓
## ╹ ╹┗━╸╹ ╹╹ ┗━╸╹ ╹ ┗━╸┗━┛╹ ╹╹ ╹┗━┛
## eyeD3 --to-v2.4 --album-artist="2♥" *mp3
ncmpcpp_directory = "/home/phleagol/.ncmpcpp"
mpd_music_dir = "/home/phleagol/Music"
external_editor = "/usr/bin/vim"
mpd_crossfade_time = "3"
mpd_host = "127.0.0.1"
mpd_port = "6600"
mouse_support = "no"
enable_window_title = "no"
colors_enabled = "yes"
titles_visibility = "no"
header_visibility = "no"
statusbar_visibility = "no"
display_volume_level = "no"
display_bitrate = "no"
display_remaining_time = "no"
progressbar_look = " "
user_interface = "classic"
ignore_leading_the = "yes"
jump_to_now_playing_song_at_start = "yes"
discard_colors_if_item_is_selected = "yes"
autocenter_mode = "yes"
centered_cursor = "yes"
playlist_display_mode = "classic"
song_list_format = "$2{%a} $0$1▏$0$5 {%t}$R $1▏$0$3 {%2A} $0$1▏$0$4 {%6l} $0$1▏$0"
song_columns_list_format = "(2f)[blue]{} (25f)[blue]{t|f} (26f)[black]{a} (6f)[yellow]{lr} (1f)[yellow]{}$b"
## Modified color and format for selected songs.
selected_item_prefix = "$6"
selected_item_suffix = "$0"
## Modified color and format for the current song - may apply to several views.
now_playing_prefix = "$b"
now_playing_suffix = "$/b"
## View one - color for text and titles.
main_window_color = "white"
main_window_highlight_color = "white"
playlist_disable_highlight_delay = "1"
playlist_separate_albums = "no"
browser_display_mode = "classic"
browser_playlist_prefix = "$8P:$4"
search_engine_display_mode = "columns"
playlist_editor_display_mode = "columns"
window_border_color = "yellow"
active_window_border = "red"
## Statusbar has been DISABLED
#!/usr/bin/perl
use strict ;
use warnings ;
use v5.24 ;
use Path::Tiny ;
use Getopt::Std ;
use Scalar::Util::Numeric qw( isint ) ;
my $musicdir = "$ENV{HOME}/music" ;
## Process cmdline args.
my %opt ;
getopts("hudv:", \%opt) or usage() ;
usage() if $opt{h} or keys(%opt) != 1 ;
my ($file, $subdir) = get_current_track() ;
die "file not found: $file" unless -e $file ;
my $rating = get_track_rating($file) ;
## Set to default when rating invalid.
$rating = 2 if not defined $rating or not isint $rating ;
$rating = 2 if $rating > 5 or $rating < 0 ;
if (defined $opt{d} and $rating > 0) {
$rating-- ;
} elsif (defined $opt{u} and $rating < 5) {
$rating++ ;
} elsif (defined $opt{v} and isint $opt{v} and $opt{v} >= 0 and $opt{v} <= 5) {
$rating = $opt{v} ;
} else { usage() ; } ;
set_rating($file, $rating) ;
update_mpd_database($subdir) ;
#### SUBROUTINES
sub usage {
say STDERR " usage: $0 [-hud] [-v rating] " ;
say STDERR "" ;
say STDERR " example: $0 -v 3" ;
say STDERR " $0 -d " ;
exit ;
}
sub get_current_track {
foreach (` mpc -f '::%file%::' `) {
if ( /^::(.+)::$/ ) {
$file = path("$musicdir/$1") ;
$subdir = path($1)->parent ;
return $file, $subdir ;
}
}
}
sub get_track_rating {
my $file = shift ;
foreach (` eyeD3 "$file"`) {
if ( /^.+bum\s+art.+:\s([0-9])♥$/) {
return $1 ;
}
}
}
sub set_rating {
my $file = shift ;
my $rating = shift ;
my $cmd = "eyeD3 --album-artist=${rating}\"" ;
$cmd .= $file ;
$cmd .= "\" 1>/dev/null 2>&1" ;
system($cmd) ;
}
sub update_mpd_database {
my $cmd = "mpc update \"" ;
$cmd .= $subdir ;
$cmd .= "\" 1>/dev/null 2>&1" ;
system($cmd) ;
}
#!/usr/bin/perl
use strict ;
use warnings ;
use v5.24 ;
use lib `fvwm-perllib dir`;
use FVWM::Module ;
use POSIX qw( mkfifo ) ;
use Math::Round qw( nearest ) ;
use Path::Tiny ;
use Image::Magick ;
## Associated fvwmbuttons module, target for send commands.
my $fbmodname = "PanelNcmpcpp3" ;
## Progress bar
my %progress = (
bar => [ (1) x 104 ], cs_dark => 220, cs_light => 221,
) ;
## MPD + music etc.
my $mpd_status = "stopped" ;
my $music_dir = "$ENV{HOME}/music" ;
my ($mpd_file, $mpd_elapsed, $mpd_percent) = (0) x 3 ;
## Coverart etc.
my %coverart = (
geom => "194x194!", tmpdir => path("/tmp/fvwm/eyed3"),
dest => "$ENV{FVWM_USERDIR}/images/cover2.png", id => "coverart",
) ;
## FVWM module.
my $modname = 'Ncmpcpp3' ;
my $module = new FVWM::Module(
Name => $modname,
Debug => 0,
);
## Urxvt terminal to display elapsed time.
my %term = (
name => "Elapsed3", width => 11, geom => "11x1-30000-30000", fifo => "/tmp/fifo2",
font => '-*-ohsnap-bold-*-normal--14-*-*-*-*-*-*-*',
fg => "gray70", bg => "gray9", letsp => 4,
) ;
start_urxvt() ;
## Periodic alarm for signal interrupts during playback.
$SIG{ALRM} = \&polling ;
sleep 3 ;
update_bar() ;
mpd_update() ;
## Listen for incoming SendToModule commands.
$module->addHandler(M_STRING, \&mpd_update);
$module->event_loop ;
#die "You should never be here!" ;
#### SUBROUTINES
sub start_urxvt {
unlink ($term{fifo}) ;
mkfifo($term{fifo}, 0700) or die "mkfifo($term{fifo}) failed: $!" ;
my $cmd = "urxvt -n $term{name} -T $term{name} -g $term{geom} -letsp $term{letsp} " ;
$cmd .= "-fn $term{font} -fg \"$term{fg}\" -bg \"$term{bg}\" -e cat $term{fifo} &" ;
system($cmd) ;
open(FIFO1, ">$term{fifo}") ;
print FIFO1 "\x1b\x5b\x3f\x32\x35\x6c" ; ## make cursor invisible
select(FIFO1) ; ## always flush FIFO1 buffer
$| = 1 ;
}
sub mpd_update {
my $new_file ;
my $new_status = "stopped" ;
foreach (`mpc -f ":::%file%:::"`) {
$new_file = "$music_dir/$1" if /^.*:::(.*):::$/ ;
$new_status = $1 if /^\[(\w+)\].*$/ ;
}
## mp3 file must be valid.
$new_status = "stopped" unless defined $new_file and -e $new_file ;
## Stop mode
if ($new_status eq "stopped") {
$mpd_status = "stopped" ;
$mpd_percent = 0 ;
update_bar() ;
return ;
}
## The same mp3 as before, in either play or pause mode.
if ($new_file eq $mpd_file) {
$mpd_status = $new_status ;
if ($mpd_status eq "playing") {
polling()
} else {
update_elapsed() ;
update_bar() ;
}
## A different mp3 from last time, in play or pause mode.
} else {
$mpd_status = $new_status ;
$mpd_file = $new_file ;
polling() ;
update_cover() ;
}
}
sub polling {
if ($mpd_status eq "playing") {
update_elapsed() ;
update_bar() ;
alarm(1) ;
return ;
} else {
alarm 0 ;
return ;
}
}
sub update_elapsed {
foreach (`mpc status`) {
if (/^[[].+#[^ ]+\s+([:\d]+\/[:\d]+)\s\((\d+)%\)\s*$/) {
$mpd_elapsed = $1 ;
$mpd_percent = $2 ;
printf "\r%$term{width}s", $mpd_elapsed ;
}
}
}
sub update_bar {
my $maxwidth = $#{ $progress{bar} } + 1 ;
my $width = nearest(1, $mpd_percent * $maxwidth / 100) ;
$width = $width < 1 ? 1 : $width > $maxwidth ? $maxwidth : $width ;
my @newbar = (1) x $width ;
push @newbar, (0) x ($maxwidth - $width) ;
for (my $idx = 0 ; $idx < $maxwidth ; $idx++) {
if ($progress{bar}->[$idx] != $newbar[$idx]) {
my $id = $idx + 1 ;
my $cs = $newbar[$idx] ? $progress{cs_light} : $progress{cs_dark} ;
sendtomodule("ChangeButton p$id Colorset $cs") ;
}
}
@{ $progress{bar} } = @newbar ;
}
## For sending commands to the FvwmButtons modules.
sub sendtomodule {
foreach (@_) {
my $cmd = "SendToModule $fbmodname " . $_ ;
$module->send($cmd) ;
}
}
sub update_cover {
my $coverfile = 0 ;
$coverfile = get_mp3_cover() if $mpd_file =~ /[.]mp3$/ ;
$coverfile = get_flac_cover() if $mpd_file =~ /[.]flac$/ ;
if (not $coverfile) {
my $basename = $mpd_file ;
$basename =~ s/[.]\w\w\w\w?$// ;
my $dir = $mpd_file ;
$dir =~ s/[^\/]+$// ;
$coverfile = "$dir/cover.jpg" if -r "$dir/cover.jpg" ;
$coverfile = "$dir/cover.png" if -r "$dir/cover.png" ;
$coverfile = "$basename.jpg" if -r "$basename.jpg" ;
$coverfile = "$basename.png" if -r "$basename.png" ;
}
return 0 unless -r $coverfile ; ## No cover image found.
## Read src cover image into imagemagick.
return 0 if not open(IMAGE, $coverfile) ;
my $err = 1 ;
my $image = Image::Magick->New(quality => 1) ;
$err = $image->Read(file=>\*IMAGE) ;
close(IMAGE) ;
return 0 if $err ;
return 0 if $image->AdaptiveResize(geometry => $coverart{geom}) ;
## Save the finished cover image..
open(IMAGE, ">$coverart{dest}") ;
return 0 if $image->Write(
file => \*IMAGE,
filename => $coverart{dest},
quality => 100,
) ;
sendtomodule("ChangeButton $coverart{id} Icon $coverart{dest}") ;
return 1 ;
}
sub get_flac_cover {
$coverart{tmpdir}->remove_tree ;
$coverart{tmpdir}->mkpath ;
my $file = "$coverart{tmpdir}/zzzz" ;
system("metaflac --export-picture-to=\"$file\" \"$mpd_file\" 1>/dev/null 2>&1") ;
if (-r "$file") { return $file } else { return 0 } ;
}
sub get_mp3_cover {
$coverart{tmpdir}->remove_tree ;
$coverart{tmpdir}->mkpath ;
## Dump embedded images from mp3.
system("eyeD3 --write-images=\"$coverart{tmpdir}\" \"$mpd_file\" 1>/dev/null 2>&1") ;
my @names = (
"FRONT_COVER.png", "FRONT_COVER.jpg", "FRONT_COVER.jpeg",
"FRONT_COVER1.png", "FRONT_COVER1.jpg", "FRONT_COVER1.jpeg",
"OTHER.png", "OTHER.jpg", "OTHER.jpeg",
) ;
## Find a suitably named image.
my $img = 0 ;
foreach my $file (@names) {
if (-r "$coverart{tmpdir}/$file") {
$img = "$coverart{tmpdir}/$file" ;
last ;
}
}
return $img if $img ;
}
## vim: sts=0:noet:syntax=fvwm:foldmethod=marker
## An fvwmbuttons panel for ncmpcpp, with track ratings and coverart.
Style myncmpcpp3 WindowListSkip, VariableSize
Style PanelNcmpcpp3 FixedSize, StartsOnDesk 2
Style PanelNcmpcpp3 Borders, Borderwidth 6, Handlewidth 6
Style PanelNcmpcpp3 UseDecor ThickDark
Style PanelNcmpcpp3 InitialMapCommand Move ++213p ++70p
## Binding for the rating menu
Key L A 4 OpenRatingMenu3
DestroyFunc ExecNcmpcpp3
AddToFunc ExecNcmpcpp3 I Exec exec \
urxvt -T myncmpcpp3 -n myncmpcpp3 -lsp 4 -letsp 1 -g 83x12-30000-30000 \
-bg grey9 -fn $[infostore.ohsnap14] -fb $[infostore.ohsnap14bold] \
-e sh -c "addcolor </tmp/fvwm/ncmpcpp3.colors && exec \
ncmpcpp -c $HOME/.ncmpcpp/config-ohsnap3"
DestroyFunc MPC-IdleLoop3
AddToFunc MPC-IdleLoop3
+ I Schedule 1500 -Exec exec sh -c " \
YUIOPQWER= mpc idleloop | while read arg ; do \
FvwmCommand 'SendToModule Ncmpcpp3 update' ; done"
DestroyFunc StopNcmpcpp3
AddToFunc StopNcmpcpp3
+ I Exec pkill -f YUIOPQWER
+ I Deschedule 5678
+ I Deschedule 5679
+ I KillModule FvwmButtons PanelNcmpcpp3
+ I KillModule Ncmpcpp3
DestroyFunc StartNcmpcpp3
AddToFunc StartNcmpcpp3
+ I StopNcmpcppGui
#+ I StopNcmpcpp2
+ I StopNcmpcpp3
+ I Schedule Periodic 5000 5678 SendToModule Ncmpcpp3 update
+ I Schedule 120000 Deschedule 5678
+ I Schedule 121000 5679 MPC-IdleLoop3
+ I Schedule 1000 Module FvwmButtons PanelNcmpcpp3
#### RATING MENU
DestroyMenu RatingMenu3
AddToMenu RatingMenu3
+ "1" Exec mp3rate -v 1
+ "2" Exec mp3rate -v 2
+ "3" Exec mp3rate -v 3
+ "4" Exec mp3rate -v 4
+ "5" Exec mp3rate -v 5
DestroyMenuStyle RatingMenuStyle3
CopyMenuStyle * RatingMenuStyle3
MenuStyle RatingMenuStyle3 Font $[infostore.ohsnap14]
MenuStyle RatingMenuStyle3 ItemFormat "%|%7.7c%|"
ChangeMenuStyle RatingMenuStyle3 RatingMenu3
DestroyFunc OpenRatingMenu3
AddToFunc OpenRatingMenu3
+ I None (PanelNcmpcpp3, CurrentDesk) Break
+ I Next (PanelNcmpcpp3, CurrentDesk) Focus
+ I Next (PanelNcmpcpp3, CurrentDesk) Schedule 600 Menu RatingMenu3 Context 758p 242p
#### COLORS
## For progress
Colorset 220 bg gray20, fg gray20
#Colorset 221 bg steelblue4, fg steelblue4
Colorset 221 bg gray45, fg gray45
InfoStoreAdd ncmpcpp3_divider #333333
InfoStoreAdd ncmpcpp3_divider_bold #4D4D4D
InfoStoreAdd ncmpcpp3_artist #666666
InfoStoreAdd ncmpcpp3_artist_bold #7F7F7F
InfoStoreAdd ncmpcpp3_title #8C8C8C
InfoStoreAdd ncmpcpp3_title_bold #A6A6A6
InfoStoreAdd ncmpcpp3_rating #666666
InfoStoreAdd ncmpcpp3_rating_bold #7F7F7F
InfoStoreAdd ncmpcpp3_elapsed #A6A6A6
InfoStoreAdd ncmpcpp3_elapsed_bold #BFBFBF
## For ncmpcpp. 0 black, 1 red, 2 green, 3 yellow, 4 blue, 5 magenta, 6 cyan, 7 white
Exec echo "\
*color0 $[infostore.ncmpcpp3_divider]\n\
*color1 $[infostore.ncmpcpp3_artist]\n\
*color2 $[infostore.ncmpcpp3_rating]\n\
*color3 $[infostore.ncmpcpp3_elapsed]\n\
*color4 $[infostore.ncmpcpp3_title]\n\
*color5 #996b99\n\
*color6 #86c1b9\n\
*color7 #666666\n\
*color8 $[infostore.ncmpcpp3_divider_bold]\n\
*color9 $[infostore.ncmpcpp3_artist_bold]\n\
*color10 $[infostore.ncmpcpp3_rating_bold]\n\
*color11 $[infostore.ncmpcpp3_elapsed_bold]\n\
*color12 $[infostore.ncmpcpp3_title_bold]\n\
*color13 #c493c4\n\
*color14 #86c1b9\n\
*color15 #999999\n"\
>/tmp/fvwm/ncmpcpp3.colors
#### FvwmButtons Panel
SetEnv WIDTH 922
DestroyModuleConfig PanelNcmpcpp3: *
*PanelNcmpcpp3: Geometry $[WIDTH]x268
*PanelNcmpcpp3: Columns $[WIDTH]
*PanelNcmpcpp3: Rows 268
*PanelNcmpcpp3: Padding 0 0
*PanelNcmpcpp3: BoxSize fixed
*PanelNcmpcpp3: Frame 0
*PanelNcmpcpp3: Back gray9
*PanelNcmpcpp3: Font $[infostore.ohsnap14]
## Padding along top
*PanelNcmpcpp3: ($[WIDTH]x25)
## Padding to LHS of ncmpcpp
*PanelNcmpcpp3: (25x216)
## Instance of urxvt/ncmpcpp
*PanelNcmpcpp3: (664x216, Swallow myncmpcpp3 ExecNcmpcpp3)
## Padding between ncmpcpp and coverart
*PanelNcmpcpp3: (10x216)
## Coverart window w/border
*PanelNcmpcpp3: (198x198, Container(Frame 0, Columns 198, Rows 198))
*PanelNcmpcpp3: (198x2, Back gray50)
*PanelNcmpcpp3: (2x194, Back gray50)
*PanelNcmpcpp3: (194x194, id coverart, Icon cover2.png)
*PanelNcmpcpp3: (2x194, Back gray50)
*PanelNcmpcpp3: (198x2, Back gray50)
*PanelNcmpcpp3: (End)
## Padding to rhs of coverart
*PanelNcmpcpp3: (25x198)
## Under coverart
*PanelNcmpcpp3: (77x18)
*PanelNcmpcpp3: (121x11)
## More padding to rhs of elapsed
*PanelNcmpcpp3: (25x25)
## Elapsed counter
*PanelNcmpcpp3: (121x14, id elapsed, Swallow "Elapsed3" "Module Ncmpcpp3" )
#*PanelNcmpcpp3: (121x14, id elapsed, Back green)
## Pad to left of progress
*PanelNcmpcpp3: (25x3)
## Progress bar - 104 consecutive 7x3 buttons.
PipeRead ' m="*PanelNcmpcpp3:" ; k=104 ; j=7 ; \
for n in `seq 1 $k` ; do \
echo "$m (${j}x3, id p$n, Back gray40)" ; \
done ; '
## Pad to rhs of progress
*PanelNcmpcpp3: (23x3)
## Thin padding below progress
*PanelNcmpcpp3: (776x4)
## Padding along bottom
*PanelNcmpcpp3: ($[WIDTH]x20)
UnsetEnv WIDTH
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment