1 changed files with 156 additions and 54 deletions
@ -1,85 +1,187 @@
@@ -1,85 +1,187 @@
|
||||
#!/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 |
||||
|
||||
set music_directory "/mnt/DATA/Музыка/" |
||||
|
||||
array set buttons { |
||||
prev 玲 |
||||
toggle ▶ |
||||
next 怜 |
||||
} |
||||
namespace eval mpd { |
||||
array set settings { |
||||
host 127.0.0.1 |
||||
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} { |
||||
exec mpc {*}$args |
||||
} |
||||
proc status {} { |
||||
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 {} { |
||||
set filename [file join $::music_directory [lindex [split [exec mpc -f {%file%}] "\n"] 0]] |
||||
} |
||||
proc state {code} { |
||||
expr {$code == 0 ? {✘} : {✔}} |
||||
} |
||||
|
||||
proc rate {rating} { |
||||
set filename [current_file] |
||||
proc value {data} { |
||||
regsub {^[^:]*:\s+} $data {} |
||||
} |
||||
|
||||
proc current_file {} { |
||||
set filename [file join $::music_directory [value [lindex [cmd currentsong] 0]]] |
||||
} |
||||
|
||||
exec mid3v2 --delete-frames=COMM $filename |
||||
exec mid3v2 --COMM $rating $filename |
||||
proc previous {} { cmd previous } |
||||
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 {} { |
||||
set filename [file join $::music_directory [current_file]] |
||||
proc random {} { |
||||
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]}]} { |
||||
return {} |
||||
} else { |
||||
return [lindex [split $rating " "] end] |
||||
cmd update |
||||
} |
||||
} |
||||
|
||||
proc hl_rating {rating} { |
||||
foreach r $::used_ratings { |
||||
if {[winfo exists .rating.$r]} { |
||||
set c [expr {$r == $rating ? {red2} : {black}}] |
||||
.rating.$r configure -activeforeground $c -foreground $c |
||||
namespace eval gui { |
||||
set used_ratings {1 2 3 4 5 5+} |
||||
|
||||
array set buttons { |
||||
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 {} { |
||||
set ::mpd_status [mpc] |
||||
proc update_status {} { |
||||
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 |
||||
frame .rating -pady 10 |
||||
frame .control -pady 10 |
||||
pack .status -fill x -expand true -padx 20 -pady 20 |
||||
pack [label .status.current -font {Sans 20} -textvariable ::mpd_status] |
||||
|
||||
pack .status -fill x -expand true -padx 20 -pady 20 |
||||
pack [label .status.current -font {Sans 20} -textvariable ::mpd_status] |
||||
pack .rating |
||||
foreach rating $::gui::used_ratings { |
||||
pack [button .rating.$rating -text $rating -font {Sans 60} -width 2 -command "::mpd::rate $rating"] -side left |
||||
} |
||||
|
||||
pack .rating |
||||
foreach rating $::used_ratings { |
||||
pack [button .rating.$rating -text $rating -font {Sans 60} -width 2 -command "rate $rating"] -side left |
||||
} |
||||
pack .control |
||||
foreach btn {previous toggle next} { |
||||
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} |
||||
bind . <KeyPress-Control_L><KeyPress-q> exit |
||||
bind . <KeyPress-Control_R><KeyPress-q> exit |
||||
gui::init |
||||
|
||||
|
Loading…
Reference in new issue