Create a gist now

Instantly share code, notes, and snippets.

@phleagol /Ncmpcpp3
Last active Aug 22, 2017

Embed
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