1 changed files with 156 additions and 54 deletions
@ -1,85 +1,187 @@ |
|||||||
#!/usr/bin/env tclsh |
#!/usr/bin/env tclsh |
||||||
|
|
||||||
# Ghetto MPD controller |
# Copyright (C) 2020, Maxim Lihachev, <envrm@yandex.ru> |
||||||
|
# |
||||||
|
# This program is free software: you can redistribute it and/or modify it |
||||||
|
# under the terms of the GNU General Public License as published by the Free |
||||||
|
# Software Foundation, version 3. |
||||||
|
# |
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT |
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
||||||
|
# more details. |
||||||
|
# |
||||||
|
# You should have received a copy of the GNU General Public License along with |
||||||
|
# this program. If not, see <https://www.gnu.org/licenses/>. |
||||||
|
|
||||||
|
# |
||||||
|
# Offhand MPD controller |
||||||
|
# |
||||||
|
# Requirements: |
||||||
|
# |
||||||
|
# - mid3v2 |
||||||
|
# |
||||||
|
|
||||||
package require Tk |
package require Tk |
||||||
|
|
||||||
set music_directory "/mnt/DATA/Музыка/" |
set music_directory "/mnt/DATA/Музыка/" |
||||||
|
|
||||||
array set buttons { |
namespace eval mpd { |
||||||
prev 玲 |
array set settings { |
||||||
toggle ▶ |
host 127.0.0.1 |
||||||
next 怜 |
port 6600 |
||||||
} |
} |
||||||
|
|
||||||
set used_ratings {1 2 3 4 5 5+} |
proc cmd {args} { |
||||||
|
set mpd [socket $::mpd::settings(host) $::mpd::settings(port)] |
||||||
|
|
||||||
|
puts $mpd $args |
||||||
|
puts $mpd close |
||||||
|
flush $mpd |
||||||
|
|
||||||
|
set mpd_answer [split [read $mpd] "\n"] |
||||||
|
set mpd_status [lindex $mpd_answer 0] |
||||||
|
set answer_code [lindex $mpd_answer end-1] |
||||||
|
|
||||||
|
if {$answer_code == "OK"} { |
||||||
|
if {![string match "ACK*" $answer_code]} { |
||||||
|
return [lrange $mpd_answer 1 end-2] |
||||||
|
} else { |
||||||
|
return $answer_code |
||||||
|
} |
||||||
|
} else { |
||||||
|
return "ERROR: $mpd_answer" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
proc mpc {args} { |
proc status {} { |
||||||
exec mpc {*}$args |
set currentsong [::mpd::cmd currentsong] |
||||||
} |
set status [::mpd::cmd status] |
||||||
|
|
||||||
|
if {![string match "ERROR:*" $currentsong] && ![string match "ERROR:*" $status]} { |
||||||
|
array set song {} |
||||||
|
foreach metadata [list {*}$currentsong {*}$status] { |
||||||
|
regexp -expanded {^([^:]+):\s+(.*)$} $metadata match key value |
||||||
|
set song([string tolower $key]) $value |
||||||
|
} |
||||||
|
|
||||||
|
if {$song(state) != "stop"} { |
||||||
|
if {[info exists song(comment)]} { |
||||||
|
::gui::hl_rating $song(comment) |
||||||
|
} |
||||||
|
|
||||||
|
return [format "♫ %s/%s: %s - %s\n\[%s - %s\]\n~ %s ~\n\[%s\] repeat: %s random: %s single: %s consume: %s" \ |
||||||
|
[incr song(song)] $song(playlistlength) \ |
||||||
|
$song(artist) $song(title) $song(date) \ |
||||||
|
$song(album) $song(genre) $song(state) \ |
||||||
|
[state $song(repeat)] [state $song(random)] \ |
||||||
|
[state $song(single)] [state $song(consume)]] |
||||||
|
} else { |
||||||
|
return "\[stopped\]" |
||||||
|
} |
||||||
|
} else { |
||||||
|
return "${currentsong}\n${status}" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
proc current_file {} { |
proc state {code} { |
||||||
set filename [file join $::music_directory [lindex [split [exec mpc -f {%file%}] "\n"] 0]] |
expr {$code == 0 ? {✘} : {✔}} |
||||||
} |
} |
||||||
|
|
||||||
proc rate {rating} { |
proc value {data} { |
||||||
set filename [current_file] |
regsub {^[^:]*:\s+} $data {} |
||||||
|
} |
||||||
|
|
||||||
|
proc current_file {} { |
||||||
|
set filename [file join $::music_directory [value [lindex [cmd currentsong] 0]]] |
||||||
|
} |
||||||
|
|
||||||
exec mid3v2 --delete-frames=COMM $filename |
proc previous {} { cmd previous } |
||||||
exec mid3v2 --COMM $rating $filename |
proc next {} { cmd next } |
||||||
|
|
||||||
hl_rating $rating |
proc toggle {} { |
||||||
|
set state [value [lindex [cmd status] 8]] |
||||||
|
|
||||||
mpc update |
if {$state == "stop" || $state == ""} { |
||||||
} |
cmd play |
||||||
|
} else { |
||||||
|
cmd pause |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
proc consume {} { |
||||||
|
set consume [value [lindex [cmd status] 4]] |
||||||
|
cmd consume [expr {$consume == 1 ? 0 : 1}] |
||||||
|
} |
||||||
|
|
||||||
proc current_rating {} { |
proc random {} { |
||||||
set filename [file join $::music_directory [current_file]] |
set random [value [lindex [cmd status] 2]] |
||||||
|
cmd random [expr {$random == 1 ? 0 : 1}] |
||||||
|
} |
||||||
|
|
||||||
|
proc rate {rating} { |
||||||
|
set filename [current_file] |
||||||
|
|
||||||
|
exec mid3v2 --delete-frames=COMM $filename |
||||||
|
exec mid3v2 --COMM $rating $filename |
||||||
|
|
||||||
if {[catch {set rating [exec -ignorestderr exiftool -Comment $filename 2>/dev/null]}]} { |
cmd update |
||||||
return {} |
|
||||||
} else { |
|
||||||
return [lindex [split $rating " "] end] |
|
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
proc hl_rating {rating} { |
namespace eval gui { |
||||||
foreach r $::used_ratings { |
set used_ratings {1 2 3 4 5 5+} |
||||||
if {[winfo exists .rating.$r]} { |
|
||||||
set c [expr {$r == $rating ? {red2} : {black}}] |
array set buttons { |
||||||
.rating.$r configure -activeforeground $c -foreground $c |
previous 玲 |
||||||
|
toggle ▶ |
||||||
|
next 怜 |
||||||
|
} |
||||||
|
|
||||||
|
proc hl_rating {rating} { |
||||||
|
foreach r $::gui::used_ratings { |
||||||
|
if {[winfo exists .rating.$r]} { |
||||||
|
set c [expr {$r == $rating ? {red2} : {black}}] |
||||||
|
.rating.$r configure -activeforeground $c -foreground $c |
||||||
|
} |
||||||
} |
} |
||||||
} |
} |
||||||
} |
|
||||||
|
|
||||||
proc update_status {} { |
proc update_status {} { |
||||||
set ::mpd_status [mpc] |
set ::mpd_status [::mpd::status] |
||||||
|
|
||||||
hl_rating [current_rating] |
after 1000 ::gui::update_status |
||||||
|
} |
||||||
|
|
||||||
after 1000 update_status |
proc init {} { |
||||||
} |
frame .status |
||||||
|
frame .rating -pady 10 |
||||||
|
frame .control -pady 10 |
||||||
|
|
||||||
frame .status |
pack .status -fill x -expand true -padx 20 -pady 20 |
||||||
frame .rating -pady 10 |
pack [label .status.current -font {Sans 20} -textvariable ::mpd_status] |
||||||
frame .control -pady 10 |
|
||||||
|
|
||||||
pack .status -fill x -expand true -padx 20 -pady 20 |
pack .rating |
||||||
pack [label .status.current -font {Sans 20} -textvariable ::mpd_status] |
foreach rating $::gui::used_ratings { |
||||||
|
pack [button .rating.$rating -text $rating -font {Sans 60} -width 2 -command "::mpd::rate $rating"] -side left |
||||||
|
} |
||||||
|
|
||||||
pack .rating |
pack .control |
||||||
foreach rating $::used_ratings { |
foreach btn {previous toggle next} { |
||||||
pack [button .rating.$rating -text $rating -font {Sans 60} -width 2 -command "rate $rating"] -side left |
pack [button .control.$btn -text $::gui::buttons($btn) -font {Sans 80} -width 4 -command "::mpd::$btn"] -side left |
||||||
} |
} |
||||||
|
|
||||||
pack .control |
#---------------------------------------------------------------------------------------- |
||||||
foreach btn {prev toggle next} { |
|
||||||
pack [button .control.$btn -text $buttons($btn) -font {Sans 80} -width 4 -command "mpc $btn"] -side left |
|
||||||
} |
|
||||||
|
|
||||||
update_status |
update_status |
||||||
|
|
||||||
|
bind . <KeyPress-z> {::mpd::random} |
||||||
|
bind . <KeyPress-R> {::mpd::consume} |
||||||
|
|
||||||
|
bind . <KeyPress-Control_L><KeyPress-q> exit |
||||||
|
bind . <KeyPress-Control_R><KeyPress-q> exit |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
bind . <KeyPress-z> {mpc random} |
gui::init |
||||||
bind . <KeyPress-Control_L><KeyPress-q> exit |
|
||||||
bind . <KeyPress-Control_R><KeyPress-q> exit |
|
||||||
|
|
||||||
|
Loading…
Reference in new issue