Compare commits

...

6 Commits

Author SHA1 Message Date
Tyler Perkins a3b3104df1
Add aerc configs 2023-11-29 21:45:44 -05:00
Tyler Perkins 4bd97ab34e
Remove dcnnt 2023-11-29 21:45:11 -05:00
Tyler Perkins 81e8fa756e
Add dccnt 2023-11-29 21:44:42 -05:00
Tyler Perkins c3a9f0dd1c
Add mpv 2023-11-29 21:44:29 -05:00
Tyler Perkins f1416e6d57
Add htop config 2023-11-29 21:44:15 -05:00
Tyler Perkins efa1021f43
Add conky 2023-11-29 21:43:17 -05:00
15 changed files with 3704 additions and 0 deletions

602
.config/aerc/aerc.conf Normal file
View File

@ -0,0 +1,602 @@
#
# aerc main configuration
[general]
#
# Used as a default path for save operations if no other path is specified.
# ~ is expanded to the current user home dir.
#
#default-save-path=
# If set to "gpg", aerc will use system gpg binary and keystore for all crypto
# operations. If set to "internal", the internal openpgp keyring will be used.
# If set to "auto", the system gpg will be preferred unless the internal
# keyring already exists, in which case the latter will be used.
#
# Default: auto
#pgp-provider=auto
# By default, the file permissions of accounts.conf must be restrictive and
# only allow reading by the file owner (0600). Set this option to true to
# ignore this permission check. Use this with care as it may expose your
# credentials.
#
# Default: false
#unsafe-accounts-conf=false
# Output log messages to specified file. A path starting with ~/ is expanded to
# the user home dir. When redirecting aerc's output to a file using > shell
# redirection, this setting is ignored and log messages are printed to stdout.
#
#log-file=
# Only log messages above the specified level to log-file. Supported levels
# are: trace, debug, info, warn and error. When redirecting aerc's output to
# a file using > shell redirection, this setting is ignored and the log level
# is forced to trace.
#
# Default: info
#log-level=info
# Set the $TERM environment variable used for the embedded terminal.
#
# Default: xterm-256color
#term=xterm-256color
# Display OSC8 strings in the embedded terminal
#
# Default: false
#enable-osc8=false
[ui]
#
# Describes the format for each row in a mailbox view. This is a comma
# separated list of column names with an optional align and width suffix. After
# the column name, one of the '<' (left), ':' (center) or '>' (right) alignment
# characters can be added (by default, left) followed by an optional width
# specifier. The width is either an integer representing a fixed number of
# characters, or a percentage between 1% and 99% representing a fraction of the
# terminal width. It can also be one of the '*' (auto) or '=' (fit) special
# width specifiers. Auto width columns will be equally attributed the remaining
# terminal width. Fit width columns take the width of their contents. If no
# width specifier is set, '*' is used by default.
#
# Default: date<20,name<17,flags>4,subject<*
#index-columns=date<20,name<17,flags>4,subject<*
#
# Each name in index-columns must have a corresponding column-$name setting.
# All column-$name settings accept golang text/template syntax. See
# aerc-templates(7) for available template attributes and functions.
#
# Default settings
#column-date={{.DateAutoFormat .Date.Local}}
#column-name={{index (.From | names) 0}}
#column-flags={{.Flags | join ""}}
#column-subject={{.ThreadPrefix}}{{if .ThreadFolded}}{{printf "{%d}" .ThreadCount}}{{end}}{{.Subject}}
#
# String separator inserted between columns. When the column width specifier is
# an exact number of characters, the separator is added to it (i.e. the exact
# width will be fully available for the column contents).
#
# Default: " "
#column-separator=" "
#
# See time.Time#Format at https://godoc.org/time#Time.Format
#
# Default: 2006-01-02 03:04 PM (ISO 8601 + 12 hour time)
#timestamp-format=2006-01-02 03:04 PM
#
# Index-only time format for messages that were received/sent today.
# If this is not specified, timestamp-format is used instead.
#
#this-day-time-format=
#
# Index-only time format for messages that were received/sent within the last
# 7 days. If this is not specified, timestamp-format is used instead.
#
#this-week-time-format=
#
# Index-only time format for messages that were received/sent this year.
# If this is not specified, timestamp-format is used instead.
#
#this-year-time-format=
#
# Width of the sidebar, including the border.
#
# Default: 20
#sidebar-width=20
#
# Message to display when viewing an empty folder.
#
# Default: (no messages)
#empty-message=(no messages)
# Message to display when no folders exists or are all filtered
#
# Default: (no folders)
#empty-dirlist=(no folders)
# Enable mouse events in the ui, e.g. clicking and scrolling with the mousewheel
#
# Default: false
#mouse-enabled=false
#
# Ring the bell when new messages are received
#
# Default: true
#new-message-bell=true
#
# Template to use for Account tab titles
#
# Default: {{.Account}}
#tab-title-account={{.Account}}
# Marker to show before a pinned tab's name.
#
# Default: `
#pinned-tab-marker='`'
# Template for the left side of the directory list.
# See aerc-templates(7) for all available fields and functions.
#
# Default: {{.Folder}}
#dirlist-left={{.Folder}}
# Template for the right side of the directory list.
# See aerc-templates(7) for all available fields and functions.
#
# Default: {{if .Unread}}{{humanReadable .Unread}}/{{end}}{{if .Exists}}{{humanReadable .Exists}}{{end}}
#dirlist-right={{if .Unread}}{{humanReadable .Unread}}/{{end}}{{if .Exists}}{{humanReadable .Exists}}{{end}}
# Delay after which the messages are actually listed when entering a directory.
# This avoids loading messages when skipping over folders and makes the UI more
# responsive. If you do not want that, set it to 0s.
#
# Default: 200ms
#dirlist-delay=200ms
# Display the directory list as a foldable tree that allows to collapse and
# expand the folders.
#
# Default: false
#dirlist-tree=false
# If dirlist-tree is enabled, set level at which folders are collapsed by
# default. Set to 0 to disable.
#
# Default: 0
#dirlist-collapse=0
# List of space-separated criteria to sort the messages by, see *sort*
# command in *aerc*(1) for reference. Prefixing a criterion with "-r "
# reverses that criterion.
#
# Example: "from -r date"
#
#sort=
# Moves to next message when the current message is deleted
#
# Default: true
#next-message-on-delete=true
# Automatically set the "seen" flag when a message is opened in the message
# viewer.
#
# Default: true
#auto-mark-read=true
# The directories where the stylesets are stored. It takes a colon-separated
# list of directories. If this is unset or if a styleset cannot be found, the
# following paths will be used as a fallback in that order:
#
# ${XDG_CONFIG_HOME:-~/.config}/aerc/stylesets
# ${XDG_DATA_HOME:-~/.local/share}/aerc/stylesets
# /usr/local/share/aerc/stylesets
# /usr/share/aerc/stylesets
#
#stylesets-dirs=
# Uncomment to use box-drawing characters for vertical and horizontal borders.
#
# Default: " "
#border-char-vertical=" "
#border-char-horizontal=" "
# Sets the styleset to use for the aerc ui elements.
#
# Default: default
#styleset-name=default
# Activates fuzzy search in commands and their arguments: the typed string is
# searched in the command or option in any position, and need not be
# consecutive characters in the command or option.
#
# Default: false
#fuzzy-complete=false
# How long to wait after the last input before auto-completion is triggered.
#
# Default: 250ms
#completion-delay=250ms
# The minimum required characters to allow auto-completion to be triggered after
# completion-delay.
#
# Default: 1
#completion-min-chars=1
#
# Global switch for completion popovers
#
# Default: true
#completion-popovers=true
# Uncomment to use UTF-8 symbols to indicate PGP status of messages
#
# Default: ASCII
#icon-unencrypted=
#icon-encrypted=✔
#icon-signed=✔
#icon-signed-encrypted=✔
#icon-unknown=✘
#icon-invalid=⚠
# Reverses the order of the message list. By default, the message list is
# ordered with the newest (highest UID) message on top. Reversing the order
# will put the oldest (lowest UID) message on top. This can be useful in cases
# where the backend does not support sorting.
#
# Default: false
#reverse-msglist-order = false
# Reverse display of the mesage threads. Default order is the the intial
# message is on the top with all the replies being displayed below. The
# reverse option will put the initial message at the bottom with the
# replies on top.
#
# Default: false
#reverse-thread-order=false
# Sort the thread siblings according to the sort criteria for the messages. If
# sort-thread-siblings is false, the thread siblings will be sorted based on
# the message UID in ascending order. This option is only applicable for
# client-side threading with a backend that enables sorting. Note that there's
# a performance impact when sorting is activated.
#
# Default: false
#sort-thread-siblings=false
#[ui:account=foo]
#
# Enable a threaded view of messages. If this is not supported by the backend
# (IMAP server or notmuch), threads will be built by the client.
#
# Default: false
#threading-enabled=false
# Force client-side thread building
#
# Default: false
#force-client-threads=false
# Show thread context enables messages which do not match the current query (or
# belong to the current mailbox) to be shown for context. These messages can be
# styled separately using "msglist_thread_context" in a styleset. This feature
# is not supported by all backends
#
# Default: false
#show-thread-context=false
# Debounce client-side thread building
#
# Default: 50ms
#client-threads-delay=50ms
[statusline]
#
# Describes the format for the status line. This is a comma separated list of
# column names with an optional align and width suffix. See [ui].index-columns
# for more details. To completely mute the status line except for push
# notifications, explicitly set status-columns to an empty string.
#
# Default: left<*,center:=,right>*
#status-columns=left<*,center:=,right>*
#
# Each name in status-columns must have a corresponding column-$name setting.
# All column-$name settings accept golang text/template syntax. See
# aerc-templates(7) for available template attributes and functions.
#
# Default settings
#column-left=[{{.Account}}] {{.StatusInfo}}
#column-center={{.PendingKeys}}
#column-right={{.TrayInfo}}
#
# String separator inserted between columns.
# See [ui].column-separator for more details.
#
#column-separator=" "
# Specifies the separator between grouped statusline elements.
#
# Default: " | "
#separator=" | "
# Defines the mode for displaying the status elements.
# Options: text, icon
#
# Default: text
#display-mode=text
[viewer]
#
# Specifies the pager to use when displaying emails. Note that some filters
# may add ANSI codes to add color to rendered emails, so you may want to use a
# pager which supports ANSI codes.
#
# Default: less -Rc
#pager=less -Rc
#
# If an email offers several versions (multipart), you can configure which
# mimetype to prefer. For example, this can be used to prefer plaintext over
# html emails.
#
# Default: text/plain,text/html
#alternatives=text/plain,text/html
#
# Default setting to determine whether to show full headers or only parsed
# ones in message viewer.
#
# Default: false
#show-headers=false
#
# Layout of headers when viewing a message. To display multiple headers in the
# same row, separate them with a pipe, e.g. "From|To". Rows will be hidden if
# none of their specified headers are present in the message.
#
# Default: From|To,Cc|Bcc,Date,Subject
#header-layout=From|To,Cc|Bcc,Date,Subject
# Whether to always show the mimetype of an email, even when it is just a single part
#
# Default: false
#always-show-mime=false
# Parses and extracts http links when viewing a message. Links can then be
# accessed with the open-link command.
#
# Default: true
#parse-http-links=true
[compose]
#
# Specifies the command to run the editor with. It will be shown in an embedded
# terminal, though it may also launch a graphical window if the environment
# supports it. Defaults to $EDITOR, or vi.
#editor=
#
# When set, aerc will create and read .eml files for composing that have
# non-standard \n linebreaks. This is only relevant if the used editor does not
# support CRLF linebreaks.
#
#lf-editor=false
#
# Default header fields to display when composing a message. To display
# multiple headers in the same row, separate them with a pipe, e.g. "To|From".
#
# Default: To|From,Subject
#header-layout=To|From,Subject
#
# Edit headers into the text editor instead than separate fields.
#
# When this is true, address-book-cmd is not supported and address completion
# is left to the editor itself. Also, displaying multiple headers on the same
# line is not possible.
#
# Default: false
#edit-headers=false
#
# Specifies the command to be used to tab-complete email addresses. Any
# occurrence of "%s" in the address-book-cmd will be replaced with what the
# user has typed so far.
#
# The command must output the completions to standard output, one completion
# per line. Each line must be tab-delimited, with an email address occurring as
# the first field. Only the email address field is required. The second field,
# if present, will be treated as the contact name. Additional fields are
# ignored.
#
# This parameter can also be set per account in accounts.conf.
#address-book-cmd=
# Specifies the command to be used to select attachments. Any occurrence of
# '%s' in the file-picker-cmd will be replaced with the argument <arg>
# to :attach -m <arg>. Any occurence of '%f' will be replaced by the
# location of a temporary file, from which aerc will read the selected files.
#
# If '%f' is not present, the command must output the selected files to
# standard output, one file per line. If it is present, then aerc does not
# capture the standard output and instead reads the files from the temporary
# file which should have the same format.
#file-picker-cmd=
#
# Allow to address yourself when replying
#
# Default: true
#reply-to-self=true
# Warn before sending an email with an empty subject.
#
# Default: false
#empty-subject-warning=false
#
# Warn before sending an email that matches the specified regexp but does not
# have any attachments. Leave empty to disable this feature.
#
# Uses Go's regexp syntax, documented at https://golang.org/s/re2syntax. The
# "(?im)" flags are set by default (case-insensitive and multi-line).
#
# Example:
# no-attachment-warning=^[^>]*attach(ed|ment)
#
#no-attachment-warning=
#
# When set, aerc will generate "format=flowed" bodies with a content type of
# "text/plain; format=flowed" as described in RFC3676. This format is easier to
# handle for some mailing software, and generally just looks like ordinary
# text. To actually make use of this format's features, you'll need support in
# your editor.
#
#format-flowed=false
[multipart-converters]
#
# Converters allow to generate multipart/alternative messages by converting the
# main text/plain part into any other MIME type. Only exact MIME types are
# accepted. The commands are invoked with sh -c and are expected to output
# valid UTF-8 text.
#
# Example (obviously, this requires that you write your main text/plain body
# using the markdown syntax):
#text/html=pandoc -f markdown -t html --standalone
[filters]
#
# Filters allow you to pipe an email body through a shell command to render
# certain emails differently, e.g. highlighting them with ANSI escape codes.
#
# The commands are invoked with sh -c. The following folders are prepended to
# the system $PATH to allow referencing filters from their name only:
#
# ${XDG_CONFIG_HOME:-~/.config}/aerc/filters
# ~/.local/libexec/aerc/filters
# ${XDG_DATA_HOME:-~/.local/share}/aerc/filters
# $PREFIX/libexec/aerc/filters
# $PREFIX/share/aerc/filters
# /usr/libexec/aerc/filters
# /usr/share/aerc/filters
#
# If you want to run a program in your default $PATH which has the same name
# as a builtin filter (e.g. /usr/bin/colorize), use its absolute path.
#
# The following variables are defined in the filter command environment:
#
# AERC_MIME_TYPE the part MIME type/subtype
# AERC_FORMAT the part content type format= parameter
# AERC_FILENAME the attachment filename (if any)
# AERC_SUBJECT the message Subject header value
# AERC_FROM the message From header value
#
# The first filter which matches the email's mimetype will be used, so order
# them from most to least specific.
#
# You can also match on non-mimetypes, by prefixing with the header to match
# against (non-case-sensitive) and a comma, e.g. subject,text will match a
# subject which contains "text". Use header,~regex to match against a regex.
#
text/plain=colorize
text/calendar=calendar
message/delivery-status=colorize
message/rfc822=colorize
#text/html=pandoc -f html -t plain | colorize
#text/html=html | colorize
#text/*=bat -fP --file-name="$AERC_FILENAME"
#application/x-sh=bat -fP -l sh
#image/*=catimg -w $(tput cols) -
#subject,~Git(hub|lab)=lolcat -f
#from,thatguywhodoesnothardwraphismessages=wrap -w 100 | colorize
# This special filter is only used to post-process email headers when
# [viewer].show-headers=true
# By default, headers are piped directly into the pager.
#
.headers=colorize
[openers]
#
# Openers allow you to specify the command to use for the :open and :open-link
# actions on a per-MIME-type basis. The :open-link URL scheme is used to
# determine the MIME type as follows: x-scheme-handler/<scheme>.
#
# {} is expanded as the temporary filename to be opened. If it is not
# encountered in the command, the temporary filename will be appened to the end
# of the command.
#
# Like [filters], openers support basic shell globbing. The first opener which
# matches the part's MIME type (or URL scheme handler MIME type) will be used,
# so order them from most to least specific.
#
# Examples:
# x-scheme-handler/irc=hexchat
# x-scheme-handler/http*=firefox
# text/html=surf -dfgms
# text/plain=gvim {} +125
# message/rfc822=thunderbird
[hooks]
#
# Hooks are triggered whenever the associated event occurs.
#
# Executed when a new email arrives in the selected folder
#mail-received=notify-send "[$AERC_ACCOUNT/$AERC_FOLDER] New mail from $AERC_FROM_NAME" "$AERC_SUBJECT"
#
# Executed when aerc starts
#aerc-startup=aerc :terminal calcurse && aerc :next-tab
#
# Executed when aerc shuts down.
#aerc-shutdown=
[templates]
# Templates are used to populate email bodies automatically.
#
# The directories where the templates are stored. It takes a colon-separated
# list of directories. If this is unset or if a template cannot be found, the
# following paths will be used as a fallback in that order:
#
# ${XDG_CONFIG_HOME:-~/.config}/aerc/templates
# ${XDG_DATA_HOME:-~/.local/share}/aerc/templates
# /usr/local/share/aerc/templates
# /usr/share/aerc/templates
#
#template-dirs=
# The default template to be used for new messages.
#
# default: new_message
#new-message=new_message
# The default template to be used for quoted replies.
#
# default: quoted_reply
#quoted-reply=quoted_reply
# The default template to be used for forward as body.
#
# default: forward_as_body
#forwards=forward_as_body

162
.config/aerc/binds.conf Normal file
View File

@ -0,0 +1,162 @@
# Binds are of the form <key sequence> = <command to run>
# To use '=' in a key sequence, substitute it with "Eq": "<Ctrl+Eq>"
# If you wish to bind #, you can wrap the key sequence in quotes: "#" = quit
<C-p> = :prev-tab<Enter>
<C-PgUp> = :prev-tab<Enter>
<C-n> = :next-tab<Enter>
<C-PgDn> = :next-tab<Enter>
<C-t> = :term<Enter>
? = :help keys<Enter>
<C-c> = :prompt 'Quit?' quit<Enter>
<C-q> = :prompt 'Quit?' quit<Enter>
[messages]
q = :prompt 'Quit?' quit<Enter>
j = :next<Enter>
<Down> = :next<Enter>
<C-d> = :next 50%<Enter>
<C-f> = :next 100%<Enter>
<PgDn> = :next 100%<Enter>
k = :prev<Enter>
<Up> = :prev<Enter>
<C-u> = :prev 50%<Enter>
<C-b> = :prev 100%<Enter>
<PgUp> = :prev 100%<Enter>
g = :select 0<Enter>
G = :select -1<Enter>
J = :next-folder<Enter>
<C-Down> = :next-folder<Enter>
K = :prev-folder<Enter>
<C-Up> = :prev-folder<Enter>
H = :collapse-folder<Enter>
<C-Left> = :collapse-folder<Enter>
L = :expand-folder<Enter>
<C-Right> = :expand-folder<Enter>
v = :mark -t<Enter>
<Space> = :mark -t<Enter>:next<Enter>
V = :mark -v<Enter>
T = :toggle-threads<Enter>
zc = :fold<Enter>
zo = :unfold<Enter>
<Enter> = :view<Enter>
d = :prompt 'Really delete this message?' 'delete-message'<Enter>
D = :delete<Enter>
a = :archive flat<Enter>
A = :unmark -a<Enter>:mark -T<Enter>:archive flat<Enter>
C = :compose<Enter>
m = :compose<Enter>
rr = :reply -a<Enter>
rq = :reply -aq<Enter>
Rr = :reply<Enter>
Rq = :reply -q<Enter>
c = :cf<space>
$ = :term<space>
! = :term<space>
| = :pipe<space>
/ = :search<space>
\ = :filter<space>
n = :next-result<Enter>
N = :prev-result<Enter>
<Esc> = :clear<Enter>
s = :split<Enter>
S = :vsplit<Enter>
[messages:folder=Drafts]
<Enter> = :recall<Enter>
[view]
/ = :toggle-key-passthrough<Enter>/
q = :close<Enter>
O = :open<Enter>
o = :open<Enter>
S = :save<space>
| = :pipe<space>
D = :delete<Enter>
A = :archive flat<Enter>
<C-l> = :open-link <space>
f = :forward<Enter>
rr = :reply -a<Enter>
rq = :reply -aq<Enter>
Rr = :reply<Enter>
Rq = :reply -q<Enter>
H = :toggle-headers<Enter>
<C-k> = :prev-part<Enter>
<C-Up> = :prev-part<Enter>
<C-j> = :next-part<Enter>
<C-Down> = :next-part<Enter>
J = :next<Enter>
<C-Right> = :next<Enter>
K = :prev<Enter>
<C-Left> = :prev<Enter>
[view::passthrough]
$noinherit = true
$ex = <C-x>
<Esc> = :toggle-key-passthrough<Enter>
[compose]
# Keybindings used when the embedded terminal is not selected in the compose
# view
$noinherit = true
$ex = <C-x>
<C-k> = :prev-field<Enter>
<C-Up> = :prev-field<Enter>
<C-j> = :next-field<Enter>
<C-Down> = :next-field<Enter>
<A-p> = :switch-account -p<Enter>
<C-Left> = :switch-account -p<Enter>
<A-n> = :switch-account -n<Enter>
<C-Right> = :switch-account -n<Enter>
<tab> = :next-field<Enter>
<backtab> = :prev-field<Enter>
<C-p> = :prev-tab<Enter>
<C-PgUp> = :prev-tab<Enter>
<C-n> = :next-tab<Enter>
<C-PgDn> = :next-tab<Enter>
[compose::editor]
# Keybindings used when the embedded terminal is selected in the compose view
$noinherit = true
$ex = <C-x>
<C-k> = :prev-field<Enter>
<C-Up> = :prev-field<Enter>
<C-j> = :next-field<Enter>
<C-Down> = :next-field<Enter>
<C-p> = :prev-tab<Enter>
<C-PgUp> = :prev-tab<Enter>
<C-n> = :next-tab<Enter>
<C-PgDn> = :next-tab<Enter>
[compose::review]
# Keybindings used when reviewing a message to be sent
y = :send<Enter>
n = :abort<Enter>
v = :preview<Enter>
p = :postpone<Enter>
q = :choose -o d discard abort -o p postpone postpone<Enter>
e = :edit<Enter>
a = :attach<space>
d = :detach<space>
[terminal]
$noinherit = true
$ex = <C-x>
<C-p> = :prev-tab<Enter>
<C-n> = :next-tab<Enter>
<C-PgUp> = :prev-tab<Enter>
<C-PgDn> = :next-tab<Enter>

78
.config/conky/conky.conf Normal file
View File

@ -0,0 +1,78 @@
-- Conky, a system monitor https://github.com/brndnmtthws/conky
--
-- This configuration file is Lua code. You can write code in here, and it will
-- execute when Conky loads. You can use it to generate your own advanced
-- configurations.
--
-- Try this (remove the `--`):
--
-- print("Loading Conky config")
--
-- For more on Lua, see:
-- https://www.lua.org/pil/contents.html
conky.config = {
alignment = 'bottom_right',
background = true,
border_width = 1,
cpu_avg_samples = 2,
default_color = 'green',
default_outline_color = 'white',
default_shade_color = 'white',
double_buffer = true,
draw_borders = true,
draw_graph_borders = true,
draw_outline = false,
draw_shades = false,
extra_newline = false,
font = 'Hermit:size=12',
gap_x = 60,
gap_y = 60,
minimum_height = 5,
minimum_width = 5,
net_avg_samples = 2,
no_buffers = true,
out_to_console = false,
out_to_ncurses = false,
out_to_stderr = false,
out_to_x = true,
own_window = true,
own_window_class = 'Conky',
own_window_type = 'override',
show_graph_range = true,
show_graph_scale = true,
stippled_borders = 0,
update_interval = 1.0,
uppercase = false,
use_spacer = 'none',
use_xft = true,
own_window_transparent = false,
own_window_argb_visual = true,
own_window_argb_value = 50
}
conky.text = [[
${color white}$color${scroll 39 $sysname ($nodename) $kernel $machine Amogus Amogus Amogus}
$hr
${color white}Uptime:$color $uptime
${color white}Frequency:$color $freq Mhz
${color white}RAM Usage:$color $mem/$memmax - $memperc% ${membar 4}
${color white}CPU Usage:$color $cpu% ${cpubar 4}
${color white}Processes:$color $processes ${color white}Running:$color $running_processes
${color white}IP Addr :$color ${addr eth0}
$hr
${color white}File systems:
/ $color${fs_used /}/${fs_size /} ${fs_bar 6 /}
${color white}Swap:$color $swap/$swapmax - $swapperc% ${swapbar 4}
$hr
${color white}Network:
eth0 ${color}D(${totaldown eth0}) :${downspeed eth0}
U(${totalup eth0}) :${upspeed eth0}
$hr
${color white}Name PID CPU% MEM%
${color white} ${top name 1} ${top pid 1} ${top cpu 1} ${top mem 1}
${color white} ${top name 2} ${top pid 2} ${top cpu 2} ${top mem 2}
${color white} ${top name 3} ${top pid 3} ${top cpu 3} ${top mem 3}
${color white} ${top name 4} ${top pid 4} ${top cpu 4} ${top mem 4}
${color white} ${top name 5} ${top pid 5} ${top cpu 5} ${top mem 5}
]]

61
.config/htop/htoprc Normal file
View File

@ -0,0 +1,61 @@
# Beware! This file is rewritten by htop when settings are changed in the interface.
# The parser is also very primitive, and not human-friendly.
htop_version=3.2.2
config_reader_min_version=3
fields=0 48 17 47 46 18 38 39 40 2 49 1
hide_kernel_threads=1
hide_userland_threads=0
hide_running_in_container=0
shadow_other_users=0
show_thread_names=0
show_program_path=1
highlight_base_name=1
highlight_deleted_exe=1
shadow_distribution_path_prefix=0
highlight_megabytes=1
highlight_threads=1
highlight_changes=0
highlight_changes_delay_secs=5
find_comm_in_cmdline=1
strip_exe_from_cmdline=1
show_merged_command=0
header_margin=0
screen_tabs=0
detailed_cpu_time=0
cpu_count_from_one=0
show_cpu_usage=1
show_cpu_frequency=1
update_process_names=0
account_guest_in_cpu_meter=0
color_scheme=0
enable_mouse=0
delay=15
hide_function_bar=0
header_layout=two_50_50
column_meters_0=LeftCPUs2 Memory Swap NetworkIO
column_meter_modes_0=1 1 1 2
column_meters_1=RightCPUs2 Tasks LoadAverage Uptime
column_meter_modes_1=1 1 2 2
tree_view=0
sort_key=47
tree_sort_key=0
sort_direction=-1
tree_sort_direction=1
tree_view_always_by_pid=0
all_branches_collapsed=0
screen:Main=PID USER PRIORITY PERCENT_MEM PERCENT_CPU NICE M_VIRT M_RESIDENT M_SHARE STATE TIME Command
.sort_key=PERCENT_MEM
.tree_sort_key=PID
.tree_view=0
.tree_view_always_by_pid=0
.sort_direction=-1
.tree_sort_direction=1
.all_branches_collapsed=0
screen:I/O=PID USER IO_PRIORITY IO_RATE IO_READ_RATE IO_WRITE_RATE
.sort_key=IO_RATE
.tree_sort_key=PID
.tree_view=0
.tree_view_always_by_pid=0
.sort_direction=-1
.tree_sort_direction=1
.all_branches_collapsed=0

3
.config/mpv/input.conf Normal file
View File

@ -0,0 +1,3 @@
ctrl+b script-binding toggle_mpvDLNA
; script-binding text_mpvDLNA
: script-binding command_mpvDLNA

3
.config/mpv/mpv.conf Normal file
View File

@ -0,0 +1,3 @@
profile=gpu-hq
fs

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Matthew Woodward
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,102 @@
# mpvDLNA
A plugin to allow mpv to browse and watch content hosted on DLNA servers. Follow the Installation Instructions [here](https://github.com/chachmu/mpvDLNA#installation-instructions)
## Usage
mpvDLNA has two main methods of interacting with DLNA servers.
### Menu
Toggling the Menu will automatically scan for DLNA servers on the network if it does not have default servers defined in the config file. Once it has finished scanning it will display a list of servers. The arrow keys can be used to navigate the menu and selecting an entry in the list with the enter key (or right arrow key) will access it. Hitting the left arrow key will move back a folder. Attempting to access an empty folder will not enter the folder, instead it will turn the folder's name red. Accessing a media file will start playback while also adding playlist entries for the previous and next episodes so you can skip forwards and backwards without issues.
### Text/Command
Command mode opens a psuedo command prompt that allows for a variety of commands.
Both Command and Text mode support a fairly robust case insensitive autocompletion feature for commands (and certain types of arguments) that can also match the input to any part of the result although it sorts its recommendations by how close to the front of the string it found the input. For example, typing `re` might cause the autocompletion to first recommend "B***re***aking Bad" but tabbing through the suggestions you could find "The Wi***re***" (examples taken from the IMDb Most Popular TV Shows page).
File Input is essentially an argument that comes at the end of the command that is required to match an entry on the DLNA server through autocompletion or the command won't execute (Since the command has to be executed on an existing entry). Normal arguments will display a hint under the command input specifying what type of argument is expected. Calling `cd` on `..` will move back a folder.
| Command | Argument | File Input (Y/N) | Explanation |
| :-----: | :---------: | :-------------: | --------------------------------------------- |
| scan | N/A | N | Scan for DLNA servers |
| text | N/A | N | Switch to Text Mode |
| cd | N/A | Y | Access an item (or begin playback) |
| info | N/A | Y | Query the server for metadata |
| ep | Episode # | Y | Find an episode based on episode # ([see more](https://github.com/chachmu/mpvDLNA#more-on-ep)) |
| pep | Episode # | Y | Call ep and begin playback on the file |
| wake | MAC Address | N | Send a wake on lan packet |
Text Mode is similar to Command Mode except it is only for navigating the DLNA server. Essentially it is always running the `cd` command. This makes it ideal for quickly browsing through the DLNA server and starting playback on a specified file.
## Installation Instructions
This script requires an installation of [mpv.io](https://mpv.io) that was built to support javascript and lua.
1. Download the mpvDLNA folder either by cloning the repository or by downloading a zip of the [latest release](https://github.com/chachmu/mpvDLNA/releases) (make sure the folder is named `mpvDLNA`, the releases tend to add a version number to the end which can cause problems)
2. Put the mpvDLNA folder in the `/scripts` folder for mpv (`~/.config/mpv/scripts/` for Linux or macOS or `C:/Users/Username/AppData/Roaming/mpv/scripts/` for Windows).
3. Bind hotkeys (You can set your own but I personally like these keys) by adding these lines to `input.conf` (`~/.config/mpv/input.conf` for Linux or macOS or `C:/Users/Username/AppData/Roaming/mpv/input.conf` for Windows)
* Toggle the Menu: `ctrl+b script-binding toggle_mpvDLNA`
* Toggle Text Input: `; script-binding text_mpvDLNA`
* Toggle Command Input: `: script-binding command_mpvDLNA`
4. Install [uPnPclient](https://github.com/flyte/upnpclient) by running `pip install upnpclient`.
5. If you intend to use the wake on lan feature you will also need to install [pywakeonlan](https://github.com/remcohaszing/pywakeonlan) by running `pip install wakeonlan`
## main.js
This file contains the majority of the code including the gui. It handles storing and managing all of the information but due to limitations with the version of javascript supported by MPV ([MuJS](https://mujs.com)) it passes all DLNA communication through the mpvDLNA.py file. When starting playback of a file the DLNA url of the file is added to the internal MPV playlist and an event is triggered each time a file is loaded that adds the previous and next files to the playlist so you can skip forwards and backwards without issues.
More documentation on the specifics of how main.js works will be added at a later date.
## mpvDLNA.py
This python script supports a few simple operations to detect and browse DLNA servers. It is basically a simple wrapper of [flyte](https://github.com/flyte)'s [uPnPclient](https://github.com/flyte/upnpclient) library. This script also supports sending wake on lan packets using the [pywakeonlan](https://github.com/remcohaszing/pywakeonlan) module.
Supported Commands:
| Command | Explanation |
| ----------- | ----------------------------------------------------------------------------------------------- |
|-l, --list | Takes a timeout in seconds and outputs a list of DLNA Media Servers detected on the network |
|-b, --browse | Takes a DLNA server url and the id of a DLNA element and outputs that element's direct children |
|-i, --info | Takes a DLNA server url and the id of a DLNA element and outputs that element's metadata |
|-w, --wake | Takes a MAC address and attempts to send a wake on lan packet to it |
## Config File Example
The config file must be named mpvDLNA.conf and be placed in a folder called `/script-settings` in the same directory as the `/script` folder (_NOT INSIDE_).
```
# List of server names to automatically add without having to run scan
server_names={Name1}+{Name2}
# List of the server addresses, must correspond with server_names
server_addrs={Address1}+{Address2}
# List of mac addresses to autocomplete for wake on lan
mac_addresses={MAC_ID1}+{MAC_ID2}
# List of mac addresses to send a wake on lan packet to on startup
startup_mac_addresses={MAC_ID1}+{MAC_ID2}
# Font size of menu elements
font_size=35
# Font size of metadata description from the `info` command
description_font_size=10
# Command to use when calling python
python_version=python3
# Length of time to spend searching for DLNA servers (Try increasing this if you are having trouble finding your server, default is 1 second)
timeout = 20
# Number of nodes to fetch when making a request to the DLNA server (default is 2000)
count=5000
```
## More on `ep`
Given an absolute episode number (The episodes total position in the series instead of just its place in a season) and a series `ep` will try to scan through the show's various seasons to find the episode that matches.
This can be rather slow the first time it is called after opening MPV on longer series as mpvDLNA will have to fetch the information about every season preceding the one containing the correct episode and then every episode in that season preceding the episode itself. Successive calls for that series (up to the episode number loaded previously) should be very fast as mpvDLNA will have already stored the information it needs.
Can potentially find the wrong episode if there are missing seasons or incomplete seasons that it can't pull the final episode number from.
# Troubleshooting and Feature Requests
If you have any issues with mpvDLNA or have features you would like to request feel free to [make an issue](https://github.com/chachmu/mpvDLNA/issues/new/choose) or send me an email and I will try to take a look. As a warning I may not respond immediately or be willing to implement every feature but I always welcome feedback!

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,101 @@
/*
* ASSFORMAT.JS (MODULE)
*
* Version: 1.2.0
* Author: VideoPlayerCode
* URL: https://github.com/VideoPlayerCode/mpv-tools
* License: Apache License, Version 2.0
*/
/* jshint -W097 */
/* global mp, module, require */
'use strict';
var Utils = require('MicroUtils');
var Ass = {};
Ass._startSeq = mp.get_property_osd('osd-ass-cc/0');
Ass._stopSeq = mp.get_property_osd('osd-ass-cc/1');
Ass.startSeq = function(output)
{
return output === false ? '' : Ass._startSeq;
};
Ass.stopSeq = function(output)
{
return output === false ? '' : Ass._stopSeq;
};
Ass.esc = function(str, escape)
{
if (escape === false) // Conveniently disable escaping via the same call.
return str;
// Uses the same technique as mangle_ass() in mpv's osd_libass.c:
// - Treat backslashes as literal by inserting a U+2060 WORD JOINER after
// them so libass can't interpret the next char as an escape sequence.
// - Replace `{` with `\{` to avoid opening an ASS override block. There is
// no need to escape the `}` since it's printed literally when orphaned.
// - See: https://github.com/libass/libass/issues/194#issuecomment-351902555
return str.replace(/\\/g, '\\\u2060').replace(/\{/g, '\\{');
};
Ass.size = function(fontSize, output)
{
return output === false ? '' : '{\\fs'+fontSize+'}';
};
Ass.scale = function(scalePercent, output)
{
return output === false ? '' : '{\\fscx'+scalePercent+'\\fscy'+scalePercent+'}';
};
Ass.convertPercentToHex = function(percent, invertValue)
{
// Tip: Use with "invertValue" to convert input range 0.0 (invisible) - 1.0
// (fully visible) to hex range '00' (fully visible) - 'FF' (invisible), for
// use with the alpha() function in a logical manner for end-users.
if (typeof percent !== 'number' || percent < 0 || percent > 1)
throw 'Invalid percentage value (must be 0.0 - 1.0)';
return Utils.toHex(
Math.floor( // Invert range (optionally), and make into a 0-255 value.
255 * (invertValue ? 1 - percent : percent)
),
2 // Fixed-size: 2 bytes (00-FF), as needed for hex in ASS subtitles.
);
};
Ass.alpha = function(transparencyHex, output)
{
return output === false ? '' : '{\\alpha&H'+transparencyHex+'&}'; // 00-FF.
};
Ass.color = function(rgbHex, output)
{
return output === false ? '' : '{\\1c&H'+rgbHex.substring(4, 6)+rgbHex.substring(2, 4)+rgbHex.substring(0, 2)+'&}';
};
Ass.white = function(output)
{
return Ass.color('FFFFFF', output);
};
Ass.gray = function(output)
{
return Ass.color('909090', output);
};
Ass.yellow = function(output)
{
return Ass.color('FFFF90', output);
};
Ass.green = function(output)
{
return Ass.color('90FF90', output);
};
module.exports = Ass;

View File

@ -0,0 +1,181 @@
/*
* MICROUTILS.US (MODULE)
*
* Version: 1.3.0
* Author: VideoPlayerCode
* URL: https://github.com/VideoPlayerCode/mpv-tools
* License: Apache License, Version 2.0
*/
/* jshint -W097 */
/* global mp, module, require */
'use strict';
var Utils = {};
// NOTE: This is an implementation of a non-recursive quicksort, which doesn't
// risk any stack overflows. This function is necessary because of a MuJS <=
// 1.0.1 bug which causes a stack overflow when running its built-in sort() on
// any large array. See: https://github.com/ccxvii/mujs/issues/55
// Furthermore, this performs optimized case-insensitive sorting.
Utils.quickSort = function(arr, options)
{
options = options || {};
var i, sortRef,
caseInsensitive = !!options.caseInsensitive;
if (caseInsensitive) {
sortRef = arr.slice(0);
for (i = sortRef.length - 1; i >= 0; --i)
if (typeof sortRef[i] === 'string')
sortRef[i] = sortRef[i].toLowerCase();
return Utils.quickSort_Run(arr, sortRef);
}
return Utils.quickSort_Run(arr);
};
Utils.quickSort_Run = function(arr, sortRef)
{
if (arr.length <= 1)
return arr;
var hasSortRef = !!sortRef;
if (!hasSortRef)
sortRef = arr; // Use arr instead. Makes a direct reference (no copy).
if (arr.length !== sortRef.length)
throw 'Array and sort-reference length must be identical';
// Adapted from a great, public-domain C algorithm by Darel Rex Finley.
// Original implementation: http://alienryderflex.com/quicksort/
// Ported by VideoPlayerCode and extended to sort via a 2nd reference array,
// to allow sorting the main array by _any_ criteria via the 2nd array.
var refPiv, arrPiv, beg = [], end = [], stackMax = -1, stackPtr = 0, L, R;
beg.push(0); end.push(sortRef.length);
++stackMax; // Tracks highest available stack index.
while (stackPtr >= 0) {
L = beg[stackPtr]; R = end[stackPtr] - 1;
if (L < R) {
if (hasSortRef) // If we have a SEPARATE sort-ref, mirror actions!
arrPiv = arr[L];
refPiv = sortRef[L]; // Left-pivot is fastest, no MuJS math needed!
while (L < R) {
while (sortRef[R] >= refPiv && L < R) R--;
if (L < R) {
if (hasSortRef)
arr[L] = arr[R];
sortRef[L++] = sortRef[R];
}
while (sortRef[L] <= refPiv && L < R) L++;
if (L < R) {
if (hasSortRef)
arr[R] = arr[L];
sortRef[R--] = sortRef[L];
}
}
if (hasSortRef)
arr[L] = arrPiv;
sortRef[L] = refPiv;
if (stackPtr === stackMax) {
beg.push(0); end.push(0); // Grow stacks to fit next elem.
++stackMax;
}
beg[stackPtr + 1] = L + 1;
end[stackPtr + 1] = end[stackPtr];
end[stackPtr++] = L;
} else {
stackPtr--;
// NOTE: No need to shrink stack here. Size-reqs GROW until sorted!
// (Anyway, MuJS is slow at splice() and wastes time if we shrink.)
}
}
return arr;
};
Utils.isInt = function(value)
{
// Verify that the input is an integer (whole number).
return (typeof value !== 'number' || isNaN(value)) ?
false :
(value | 0) === value;
};
Utils._hexSymbols = [
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
];
Utils.toHex = function(num, outputLength)
{
// Generates a fixed-length output, and handles negative numbers properly.
var result = '';
while (outputLength--) {
result = Utils._hexSymbols[num & 0xF] + result;
num >>= 4;
}
return result;
};
Utils.shuffle = function(arr)
{
var m = arr.length, tmp, i;
while (m) { // While items remain to shuffle...
// Pick a remaining element...
i = Math.floor(Math.random() * m--);
// And swap it with the current element.
tmp = arr[m];
arr[m] = arr[i];
arr[i] = tmp;
}
return arr;
};
Utils.trim = function(str)
{
return str.replace(/(?:^\s+|\s+$)/g, ''); // Trim left and right whitespace.
};
Utils.ltrim = function(str)
{
return str.replace(/^\s+/, ''); // Trim left whitespace.
};
Utils.rtrim = function(str)
{
return str.replace(/\s+$/, ''); // Trim right whitespace.
};
Utils.dump = function(value)
{
mp.msg.error(JSON.stringify(value));
};
Utils.benchmarkStart = function(textLabel)
{
Utils.benchmarkTimestamp = mp.get_time();
Utils.benchmarkTextLabel = textLabel;
};
Utils.benchmarkEnd = function()
{
var now = mp.get_time(),
start = Utils.benchmarkTimestamp ? Utils.benchmarkTimestamp : now,
elapsed = now - start,
label = typeof Utils.benchmarkTextLabel === 'string' ? Utils.benchmarkTextLabel : '';
mp.msg.info('Time Elapsed (Benchmark'+(label.length ? ': '+label : '')+'): '+elapsed+' seconds.');
};
module.exports = Utils;

View File

@ -0,0 +1,206 @@
/*
* OPTIONS.JS (MODULE)
*
* Description: JavaScript implementation of mpv's Lua API's config file system,
* via "mp.options.read_options()". See official Lua docs for help.
* https://github.com/mpv-player/mpv/blob/master/DOCS/man/lua.rst#mpoptions-functions
* Version: 2.1.0
* Author: VideoPlayerCode
* URL: https://github.com/VideoPlayerCode/mpv-tools
* License: Apache License, Version 2.0
*/
/* jshint -W097 */
/* global mp, exports, require */
/* This is a slightly modified version of the Options module from VideoPlayerCode
that has been changed to better work with the mpvDLNA plugin.
Specifically it checks an additional location for the config file (/script-opts)
*/
'use strict';
var ScriptConfig = function(options, identifier)
{
if (!options)
throw 'Options table parameter is missing.';
this.options = options;
this.scriptName = typeof identifier === 'string' ? identifier : mp.get_script_name();
this.configFile = null;
// Converts string "val" to same primitive type as "destTypeVal".
var typeConv = function(destTypeVal, val)
{
switch (typeof destTypeVal) {
case 'object':
if (!Array.isArray(destTypeVal))
val = undefined; // Unknown "object" target variable.
else if (typeof val !== 'string')
val = String(val); // Target is array, so use string values.
break;
case 'string':
if (typeof val !== 'string')
val = String(val);
break;
case 'boolean':
if (val === 'yes')
val = true;
else if (val === 'no')
val = false;
else {
mp.msg.error('Error: Can\'t convert '+JSON.stringify(val)+' to boolean!');
val = undefined;
}
break;
case 'number':
var num = parseFloat(val);
if (!isNaN(num))
val = num;
else {
mp.msg.error('Error: Can\'t convert '+JSON.stringify(val)+' to number!');
val = undefined;
}
break;
default:
val = undefined;
}
return val;
};
// Find config file.
if (this.scriptName && this.scriptName.length) {
mp.msg.debug('Reading options for '+this.scriptName+'.');
this.configFile = mp.find_config_file('script-settings/'+this.scriptName+'.conf');
if (!this.configFile) // Try legacy settings location as fallback.
this.configFile = mp.find_config_file('script-opts/'+this.scriptName+'.conf');
if (!this.configFile) // Try legacy settings location as fallback.
this.configFile = mp.find_config_file('lua-settings/'+this.scriptName+'.conf');
}
// Read and parse configuration if found.
var i, len, pos, key, val, isArrayVal, convVal;
if (this.configFile && this.configFile.length) {
try {
var line, configLines = mp.utils.read_file(this.configFile).split(/[\r\n]+/);
for (i = 0, len = configLines.length; i < len; ++i) {
line = configLines[i].replace(/^\s+/, '');
if (!line.length || line.charAt(0) === '#')
continue;
pos = line.indexOf('=');
if (pos < 0) {
mp.msg.warn('"'+this.configFile+'": Ignoring malformatted config line "'+line.replace(/\s+$/, '')+'".');
continue;
}
key = line.substring(0, pos);
val = line.substring(pos + 1);
isArrayVal = false;
if ('[]' === line.substring(pos - 2, pos)) {
key = key.substring(0, key.length - 2);
isArrayVal = true;
}
if (this.options.hasOwnProperty(key)) {
convVal = typeConv(this.options[key], val);
if (typeof convVal !== 'undefined') {
if (Array.isArray(this.options[key])) {
if (isArrayVal)
this.options[key].push(convVal);
else
mp.msg.error('"'+this.configFile+'": Ignoring non-array value for array-based option key "'+key+'".');
}
else
this.options[key] = convVal;
}
else
mp.msg.error('"'+this.configFile+'": Unable to convert value "'+val+'" for key "'+key+'".');
}
else
mp.msg.warn('"'+this.configFile+'": Ignoring unknown key "'+key+'".');
}
} catch (e) {
mp.msg.error('Unable to read configuration file "'+this.configFile+'".');
}
}
else
mp.msg.verbose('Unable to find configuration file for '+this.scriptName+'.');
// Parse command-line options.
if (this.scriptName && this.scriptName.length) {
var cmdOpts = mp.get_property_native('options/script-opts'), rawOpt,
prefix = this.scriptName+'-', keyLen;
len = prefix.length;
for (rawOpt in cmdOpts) {
if (!cmdOpts.hasOwnProperty(rawOpt))
continue;
pos = rawOpt.indexOf(prefix);
if (pos !== 0)
continue;
key = rawOpt.substring(len);
keyLen = key.length;
isArrayVal = false;
if ('[]' === key.substring(keyLen - 2)) {
key = key.substring(0, keyLen - 2);
isArrayVal = true;
}
if (key.length && this.options.hasOwnProperty(key)) {
val = cmdOpts[rawOpt];
convVal = typeConv(this.options[key], val);
if (typeof convVal !== 'undefined') {
if (Array.isArray(this.options[key])) {
if (isArrayVal)
this.options[key].push(convVal);
else
mp.msg.error('script-opts: Ignoring non-array value for array-based option key "'+key+'".');
}
else
this.options[key] = convVal;
}
else
mp.msg.error('script-opts: Unable to convert value "'+val+'" for key "'+key+'".');
}
else
mp.msg.warn('script-opts: Ignoring unknown key "'+key+'".');
}
}
};
ScriptConfig.prototype.getValue = function(key)
{
if (!this.options.hasOwnProperty(key))
throw 'Invalid option "'+key+'"';
return this.options[key];
};
ScriptConfig.prototype.getMultiValue = function(key)
{
// Multi-value format: `{one}+{two}+{three}`.
var i, len,
val = this.getValue(key), // Throws.
result = [];
if (typeof val !== 'string')
throw 'Invalid non-string value in multi-value option "'+key+'"';
len = val.length;
if (len) {
if (val.charAt(0) !== '{' || val.charAt(len - 1) !== '}')
throw 'Missing surrounding "{}" brackets in multi-value option "'+key+'"';
val = val.substring(1, len - 1).split('}+{');
len = val.length;
for (i = 0; i < len; ++i) {
result.push(val[i]);
}
}
return result;
};
// Class `advanced_options()`: Offers extended features such as multi-values.
exports.advanced_options = ScriptConfig;
// Function `read_options()`: Behaves like Lua API (returns plain list of opts).
exports.read_options = function(table, identifier) {
// NOTE: "table" will be modified by reference, just as the Lua version.
var config = new ScriptConfig(table, identifier);
return config.options; // This is the same object as "table".
};

View File

@ -0,0 +1,3 @@
# This code is originally from [VideoPlayerCode](https://github.com/VideoPlayerCode)'s
# [mpv-tools](https://github.com/VideoPlayerCode/mpv-tools/) repository under the [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.html).
## The SelectionMenu.js and Options.js files have been modified to better work with mpvDLNA but the other files are still in their original form.

View File

@ -0,0 +1,649 @@
/*
* SELECTIONMENU.JS (MODULE)
*
* Version: 1.3.1
* Author: VideoPlayerCode
* URL: https://github.com/VideoPlayerCode/mpv-tools
* License: Apache License, Version 2.0
*/
/* jshint -W097 */
/* global mp, module, require, setInterval, clearInterval, setTimeout, clearTimeout */
/* This is a modified version of the SelectionMenu module from VideoPlayerCode
that has been changed to better work with the mpvDLNA plugin.
Specifically it has additional support for:
Coloring empty folders red
Displaying an indicator on currently playing entries
Displaying a text input system
*/
'use strict';
var Ass = require('AssFormat'),
Utils = require('MicroUtils');
var SelectionMenu = function(settings)
{
settings = settings || {};
this.uniqueId = 'M'+String(mp.get_time_ms()).replace(/\./g, '').substring(3)+
Math.floor((100+(Math.random()*899)));
this.metadata = null;
this.title = 'No title';
this.options = [];
this.selectionIdx = 0;
this.cbMenuShow = typeof settings.cbMenuShow === 'function' ? settings.cbMenuShow : null;
this.cbMenuHide = typeof settings.cbMenuHide === 'function' ? settings.cbMenuHide : null;
this.cbMenuLeft = typeof settings.cbMenuLeft === 'function' ? settings.cbMenuLeft : null;
this.cbMenuRight = typeof settings.cbMenuRight === 'function' ? settings.cbMenuRight : null;
this.cbMenuOpen = typeof settings.cbMenuOpen === 'function' ? settings.cbMenuOpen : null;
this.cbMenuUndo = typeof settings.cbMenuUndo === 'function' ? settings.cbMenuUndo : null;
this.maxLines = typeof settings.maxLines === 'number' &&
settings.maxLines >= 3 ? Math.floor(settings.maxLines) : 10;
this.menuFontAlpha = Ass.convertPercentToHex( // Throws if invalid input.
(typeof settings.menuFontAlpha === 'number' &&
settings.menuFontAlpha >= 0 && settings.menuFontAlpha <= 1 ?
settings.menuFontAlpha : 1),
true // Invert input range so "1.0" is visible and "0.0" is invisible.
);
this.menuFontSize = typeof settings.menuFontSize === 'number' &&
settings.menuFontSize >= 1 ? Math.floor(settings.menuFontSize) : 40;
this.originalFontSize = null;
this.hasRegisteredKeys = false; // Also means that menu is active/open.
this.useTextColors = true;
this.currentMenuText = '';
this.typingText = '';
this.active = false; // Menu is visible
this.menuActive = false; // the menu portion of the UI is active
this.typingActive = false; // the typing portion of the UI is active
this.isShowingMessage = false;
this.currentMessageText = '';
this.menuInterval = null;
this.stopMessageTimeout = null;
this.autoCloseDelay = typeof settings.autoCloseDelay === 'number' &&
settings.autoCloseDelay >= 0 ? settings.autoCloseDelay : 5; // 0 = Off.
this.autoCloseActiveAt = 0;
this.keyBindings = { // Default keybindings.
'Menu-Up':{repeatable:true, keys:['up']},
'Menu-Down':{repeatable:true, keys:['down']},
'Menu-Up-Fast':{repeatable:true, keys:['shift+up']},
'Menu-Down-Fast':{repeatable:true, keys:['shift+down']},
'Menu-Left':{repeatable:true, keys:['left']},
'Menu-Right':{repeatable:false, keys:['right']},
'Menu-Open':{repeatable:false, keys:['enter']},
'Menu-Undo':{repeatable:false, keys:['bs']},
'Menu-Help':{repeatable:false, keys:['h']},
'Menu-Close':{repeatable:false, keys:['esc']}
};
// Apply custom rebinding overrides if provided.
// Format: `{'Menu-Open':['a','shift+b']}`
// Note that all "shift variants" MUST be specified as "shift+<key>".
var i, action, key, allKeys, erasedDefaults,
rebinds = settings.keyRebindings;
if (rebinds) {
for (action in rebinds) {
if (!rebinds.hasOwnProperty(action))
continue;
if (!this.keyBindings.hasOwnProperty(action))
throw 'Invalid menu action "'+action+'" in rebindings';
erasedDefaults = false;
allKeys = rebinds[action];
for (i = 0; i < allKeys.length; ++i) {
key = allKeys[i];
if (typeof key !== 'string')
throw 'Invalid non-string key ('+JSON.stringify(key)+') in custom rebindings';
key = key.toLowerCase(); // Unify case of all keys for de-dupe.
key = Utils.trim(key); // Trim whitespace.
if (!key.length)
continue;
if (!erasedDefaults) { // Erase default keys for this action.
erasedDefaults = true;
this.keyBindings[action].keys = [];
}
this.keyBindings[action].keys.push(key);
}
}
}
// Verify that no duplicate bindings exist for the same key.
var boundKeys = {};
for (action in this.keyBindings) {
if (!this.keyBindings.hasOwnProperty(action))
continue;
allKeys = this.keyBindings[action].keys;
for (i = 0; i < allKeys.length; ++i) {
key = allKeys[i];
if (boundKeys.hasOwnProperty(key))
throw 'Invalid duplicate menu bindings for key "'+key+'" (detected in action "'+action+'")';
boundKeys[key] = true;
}
}
};
SelectionMenu.prototype.setMetadata = function(metadata)
{
this.metadata = metadata;
};
SelectionMenu.prototype.getMetadata = function()
{
return this.metadata;
};
SelectionMenu.prototype.setTitle = function(newTitle)
{
if (typeof newTitle !== 'string')
throw 'setTitle: No title value provided';
this.title = newTitle;
};
SelectionMenu.prototype.setOptions = function(newOptions, initialSelectionIdx)
{
if (typeof newOptions === 'undefined')
throw 'setOptions: No options value provided';
this.options = newOptions;
this.selectionIdx = typeof initialSelectionIdx === 'number' &&
initialSelectionIdx >= 0 && initialSelectionIdx < newOptions.length ?
initialSelectionIdx : 0;
};
SelectionMenu.prototype.setCallbackMenuShow = function(newCbMenuShow)
{
this.cbMenuShow = typeof newCbMenuShow === 'function' ? newCbMenuShow : null;
};
SelectionMenu.prototype.setCallbackMenuHide = function(newCbMenuHide)
{
this.cbMenuHide = typeof newCbMenuHide === 'function' ? newCbMenuHide : null;
};
SelectionMenu.prototype.setCallbackMenuLeft = function(newCbMenuLeft)
{
this.cbMenuLeft = typeof newCbMenuLeft === 'function' ? newCbMenuLeft : null;
};
SelectionMenu.prototype.setCallbackMenuRight = function(newCbMenuRight)
{
this.cbMenuRight = typeof newCbMenuRight === 'function' ? newCbMenuRight : null;
};
SelectionMenu.prototype.setCallbackMenuOpen = function(newCbMenuOpen)
{
this.cbMenuOpen = typeof newCbMenuOpen === 'function' ? newCbMenuOpen : null;
};
SelectionMenu.prototype.setCallbackMenuUndo = function(newCbMenuUndo)
{
this.cbMenuUndo = typeof newCbMenuUndo === 'function' ? newCbMenuUndo : null;
};
SelectionMenu.prototype.setUseTextColors = function(value)
{
var hasChanged = this.useTextColors !== value;
this.useTextColors = !!value;
// Update text cache, and redraw menu if visible (otherwise don't show it).
if (hasChanged)
this.renderMenu(null, 1); // 1 = Only redraw if menu is onscreen.
};
SelectionMenu.prototype.isMenuActive = function()
{
return this.active;
};
SelectionMenu.prototype.getSelectedItem = function()
{
if (this.selectionIdx < 0 || this.selectionIdx >= this.options.length)
return '';
else
return this.options[this.selectionIdx];
};
SelectionMenu.prototype._processBindings = function(fnCb)
{
if (typeof fnCb !== 'function')
throw 'Missing callback for _processBindings';
var i, key, allKeys, action, identifier,
bindings = this.keyBindings;
for (action in bindings) {
if (!bindings.hasOwnProperty(action))
continue;
allKeys = bindings[action].keys;
for (i = 0; i < allKeys.length; ++i) {
key = allKeys[i];
identifier = this.uniqueId+'_'+action+'_'+key;
fnCb(
identifier, // Unique identifier for this binding.
action, // What action the key is assigned to trigger.
key, // What key.
bindings[action] // Details about this binding.
);
}
}
};
SelectionMenu.prototype._registerMenuKeys = function()
{
if (this.hasRegisteredKeys)
return;
// Necessary in order to preserve "this" in the called function, since mpv's
// callbacks don't receive "this" if the object's func is keybound directly.
var createFn = function(obj, fn) {
return function() {
obj._menuAction(fn);
};
};
var self = this;
this._processBindings(function(identifier, action, key, details) {
mp.add_forced_key_binding(
key, // What key.
identifier, // Unique identifier for the binding.
createFn(self, action), // Generate anonymous func to execute.
{repeatable:details.repeatable} // Extra options.
);
});
this.hasRegisteredKeys = true;
};
SelectionMenu.prototype._unregisterMenuKeys = function()
{
if (!this.hasRegisteredKeys)
return;
var self = this;
this._processBindings(function(identifier, action, key, details) {
mp.remove_key_binding(
identifier // Remove binding by its unique identifier.
);
});
this.hasRegisteredKeys = false;
};
SelectionMenu.prototype._menuAction = function(action)
{
if (this.isShowingMessage && action !== 'Menu-Close')
return; // Block everything except "close" while showing a message.
switch (action) {
case 'Menu-Up':
case 'Menu-Down':
case 'Menu-Up-Fast':
case 'Menu-Down-Fast':
var maxIdx = this.options.length - 1;
if (action === 'Menu-Up' || action === 'Menu-Up-Fast')
this.selectionIdx -= (action === 'Menu-Up-Fast' ? 10 : 1);
else
this.selectionIdx += (action === 'Menu-Down-Fast' ? 10 : 1);
// Handle wraparound in single-move mode, or clamp in fast-move mode.
if (this.selectionIdx < 0)
this.selectionIdx = (action === 'Menu-Up-Fast' ? 0 : maxIdx);
else if (this.selectionIdx > maxIdx)
this.selectionIdx = (action === 'Menu-Down-Fast' ? maxIdx : 0);
this.renderMenu();
break;
case 'Menu-Left':
case 'Menu-Right':
case 'Menu-Open':
case 'Menu-Undo':
var cbName = 'cb'+action.replace(/-/g, '');
if (typeof this[cbName] === 'function') {
// We don't know what the callback will do, and it may be slow, so
// we'll disable the menu's auto-close timeout while it runs.
this._disableAutoCloseTimeout(); // Soft-disable.
this[cbName](action);
}
break;
case 'Menu-Help':
// List all keybindings to help the user remember them.
var entry, entryTitle, allKeys,
c = this.useTextColors,
helpLines = 0,
helpString = Ass.startSeq(c)+Ass.alpha(this.menuFontAlpha, c),
bindings = this.keyBindings;
for (entry in bindings) {
if (!bindings.hasOwnProperty(entry))
continue;
allKeys = bindings[entry].keys;
if (!entry.match(/^Menu-/) || !allKeys || !allKeys.length)
continue;
entryTitle = entry.substring(5);
if (!entryTitle.length)
continue;
Utils.quickSort(allKeys, {caseInsensitive: true});
++helpLines;
helpString += Ass.yellow(c)+Ass.esc(entryTitle, c)+': '+
Ass.white(c)+Ass.esc('{'+allKeys.join('}, {')+'}', c)+'\n';
}
helpString += Ass.stopSeq(c);
if (!helpLines)
helpString = 'No help available.';
this.showMessage(helpString, 5000);
break;
case 'Menu-Close':
this.hideMenu();
break;
default:
mp.msg.error('Unknown menu action "'+action+'"');
return;
}
this._updateAutoCloseTimeout(); // Soft-update.
};
SelectionMenu.prototype._disableAutoCloseTimeout = function(forceLock)
{
this.autoCloseActiveAt = forceLock ? -2 : -1; // -2 = hard, -1 = soft.
};
SelectionMenu.prototype._updateAutoCloseTimeout = function(forceUnlock)
{
if (!forceUnlock && this.autoCloseActiveAt === -2)
return; // Do nothing while autoclose is locked in "disabled" mode.
this.autoCloseActiveAt = mp.get_time();
};
SelectionMenu.prototype._handleAutoClose = function()
{
if (this.autoCloseDelay <= 0 || this.autoCloseActiveAt <= -1) // -2 = hard, -1 = soft.
return; // Do nothing while autoclose is disabled (0) or locked (< 0).
var now = mp.get_time();
if (this.autoCloseActiveAt <= (now - this.autoCloseDelay))
this.hideMenu();
};
SelectionMenu.prototype._renderActiveText = function()
{
// If nothing is supposed to be showing
if (!this.active)
return;
// Determine which text to render (critical messages take precedence).
var msg = this.isShowingMessage ? this.currentMessageText : (this.menuActive ? this.currentMenuText : '');
if (typeof msg !== 'string')
msg = '';
// Append the typing display to the correct location in the message
if (this.typingActive) {
var newlines = (this.menuActive) ? 3 : 2;
newlines += this.maxLines - msg.split("\n").length;
msg += Ass.startSeq(true) + Ass.alpha("FF", true);
// Make sure we get aligned properly
for (var i = 0; i < newlines; i++) {
msg += "-\n";
}
// This spacing is annoyingly fiddly
if (!this.menuActive)
msg+="\n";
msg += Ass.alpha("00", true);
msg += "--------------\n";
msg += Ass.stopSeq(true);
msg += this.typingText;
}
// Tell mpv's OSD to show the text. It will automatically be replaced and
// refreshed every second while the menu remains open, to ensure that
// nothing else is able to overwrite our menu text.
// NOTE: The long display duration is important, because the JS engine lacks
// real threading, so any slow mpv API calls or slow JS functions will delay
// our redraw timer! Without a long display duration, the menu would vanish.
// NOTE: If a timer misses multiple intended ticks, it will only tick ONCE
// when catching up. So there can thankfully never be any large "backlog"!
mp.osd_message(msg, 1000);
};
SelectionMenu.prototype.renderMenu = function(selectionPrefix, renderMode)
{
var c = this.useTextColors,
finalString;
// Title.
finalString = Ass.startSeq(c)+Ass.alpha(this.menuFontAlpha, c)+
Ass.gray(c)+Ass.scale(75, c)+Ass.esc(this.title, c)+':'+
Ass.scale(100, c)+Ass.white(c)+'\n\n';
// Options.
if (this.options.length > 0) {
// Calculate start/end offsets around focal point.
var startIdx = this.selectionIdx - Math.floor(this.maxLines / 2);
if (startIdx < 0)
startIdx = 0;
var endIdx = startIdx + this.maxLines - 1,
maxIdx = this.options.length - 1;
if (endIdx > maxIdx)
endIdx = maxIdx;
// Increase number of leading lines if we've reached end of list.
var lineCount = (endIdx - startIdx) + 1, // "+1" to count start line too.
lineDiff = this.maxLines - lineCount;
startIdx -= lineDiff;
if (startIdx < 0)
startIdx = 0;
// Format and add all output lines.
var opt;
for (var i = startIdx; i <= endIdx; ++i) {
opt = this.options[i];
if (i === this.selectionIdx)
// NOTE: Prefix stays on screen until cursor-move or re-render.z
finalString += Ass.yellow(c)+'> '+(typeof selectionPrefix === 'string' ?
Ass.esc(selectionPrefix, c)+' ' : '');
// If the menu option has no children to move to
// then it should be colored red and ignored
if (opt.children != null && opt.children.length == 0)
finalString += Ass.color("FF0000", c);
finalString += (
i === startIdx && startIdx > 0 ? '...' :
(
i === endIdx && endIdx < maxIdx ? '...' : Ass.esc(
typeof opt === 'object' ? opt.name : opt,
c
)
)
);
// Display the now playing indicator
if (opt.isPlaying)
finalString += " <==";
if (i === this.selectionIdx || (opt.children != null && opt.children.length == 0))
finalString += Ass.white(c);
if (i !== endIdx)
finalString += '\n';
}
}
// End the Advanced SubStation command sequence.
finalString += Ass.stopSeq(c);
// Update cached menu text. But only open/redraw the menu if it's already
// active OR if we're NOT being prevented from going out of "hidden" state.
this.currentMenuText = finalString;
// Handle render mode:
// 1 = Only redraw if menu is onscreen (doesn't trigger open/redrawing if
// the menu is closed or busy showing a text message); 2 = Don't show/redraw
// at all (good for just updating the text cache silently); any other value
// (incl. undefined, aka default) = show/redraw the menu.
if ((renderMode === 1 && (!this.menuActive || this.isShowingMessage)) || renderMode === 2)
return;
this.showMenu();
};
SelectionMenu.prototype.show = function() {
var justOpened = false;
if (!this.isMenuActive()) {
justOpened = true;
this.originalFontSize = mp.get_property_number('osd-font-size');
mp.set_property('osd-font-size', this.menuFontSize);
// Redraw the currently active text every second and do periodic tasks.
// NOTE: This prevents other OSD scripts from removing our menu text.
var self = this;
if (this.menuInterval !== null)
clearInterval(this.menuInterval);
this.menuInterval = setInterval(function() {
self._renderActiveText();
self._handleAutoClose();
}, 1000);
// Get rid of any lingering "stop message" timeout and message.
this.stopMessage(true);
}
// Display the currently active text instantly.
this._renderActiveText();
if (justOpened) {
// Run "menu show" callback if registered.
if (typeof this.cbMenuShow === 'function') {
this._disableAutoCloseTimeout(); // Soft-disable while CB runs.
this.cbMenuShow('Menu-Show');
}
// Force an update/unlock of the activity timeout when menu opens.
this._updateAutoCloseTimeout(true); // Hard-update.
}
this.active = true;
}
SelectionMenu.prototype.hide = function() {
mp.osd_message('');
if (this.originalFontSize !== null)
mp.set_property('osd-font-size', this.originalFontSize);
if (this.menuInterval !== null) {
clearInterval(this.menuInterval);
this.menuInterval = null;
}
// Get rid of any lingering "stop message" timeout and message.
this.stopMessage(true);
// Run "menu hide" callback if registered.
if (typeof this.cbMenuHide === 'function')
this.cbMenuHide('Menu-Hide');
this.active = false;
}
SelectionMenu.prototype.showMenu = function()
{
if (!this.menuActive) {
this.menuActive = true;
this._registerMenuKeys();
}
if (!this.active)
this.show();
this._renderActiveText();
};
SelectionMenu.prototype.hideMenu = function()
{
if (this.menuActive) {
this.menuActive = false;
this._unregisterMenuKeys();
if (this.active && !this.typingActive)
this.hide();
else
this._renderActiveText();
}
};
SelectionMenu.prototype.showTyping = function()
{
this.typingActive = true;
if (!this.active)
this.show();
this._renderActiveText();
};
SelectionMenu.prototype.hideTyping = function()
{
this.typingActive = false;
if (this.active && !this.menuActive)
this.hide();
else
this._renderActiveText();
if (this.menuActive) {
this._registerMenuKeys();
}
};
SelectionMenu.prototype.showMessage = function(msg, durationMs, clearSelectionPrefix)
{
if (!this.isMenuActive())
return;
if (typeof msg !== 'string')
msg = 'showMessage: Invalid message value.';
if (typeof durationMs !== 'number')
durationMs = 800;
if (clearSelectionPrefix)
this.renderMenu(null, 2); // 2 = Only update text cache (no redraw).
this.isShowingMessage = true;
this.currentMessageText = msg;
this._renderActiveText();
this._disableAutoCloseTimeout(true); // Hard-disable (ignore msg idle time).
var self = this;
if (this.stopMessageTimeout !== null)
clearTimeout(this.stopMessageTimeout);
this.stopMessageTimeout = setTimeout(function() {
self.stopMessage();
}, durationMs);
};
SelectionMenu.prototype.stopMessage = function(preventRender)
{
if (this.stopMessageTimeout !== null) {
clearTimeout(this.stopMessageTimeout);
this.stopMessageTimeout = null;
}
this.isShowingMessage = false;
this.currentMessageText = '';
if (!preventRender)
this._renderActiveText();
this._updateAutoCloseTimeout(true); // Hard-update (last user activity).
};
module.exports = SelectionMenu;

View File

@ -0,0 +1,127 @@
import sys
import upnpclient
from lxml import etree
# Try to import wake on lan
wol = True
try:
import wakeonlan
except ImportError as error:
wol = False
import logging
# important information is passed through stdout so we need to supress
# the output of the upnp client module
logging.getLogger("upnpclient").setLevel(logging.CRITICAL)
logging.getLogger("ssdp").setLevel(logging.CRITICAL)
def wake(mac):
if wol:
try:
wakeonlan.send_magic_packet(mac);
print("packet sent")
except:
print("send failed")
else:
print("import failed")
def info(url, id, count):
device = upnpclient.Device(url)
result = device.ContentDirectory.Browse(ObjectID=id, BrowseFlag="BrowseMetadata", Filter="*", StartingIndex=0, RequestedCount=count, SortCriteria="")
root = etree.fromstring(result["Result"])
# Determine if we should be looking at items or containers
list = root.findall("./item", root.nsmap)
type = "item"
if len(list) == 0:
list = root.findall("./container", root.nsmap)
type = "container"
print(type)
for t in list:
print("")
print(t.findtext("upnp:episodeNumber", "No Episode Number", root.nsmap))
print(t.findtext("dc:description", "No Description", root.nsmap).encode().decode("ascii", errors='ignore'))
def browse(url, id, count):
device = upnpclient.Device(url)
result = device.ContentDirectory.Browse(ObjectID=id, BrowseFlag="BrowseDirectChildren", Filter="*", StartingIndex=0, RequestedCount=count, SortCriteria="")
root = etree.fromstring(result["Result"])
list = {}
# Determine if we should be looking at items or containers
list["item"] = root.findall("./item", root.nsmap)
list["container"] = root.findall("./container", root.nsmap)
for type in list.keys():
print(type + "s:")
for t in list[type]:
print("")
print(t.findtext("dc:title", "untitled", root.nsmap).encode().decode("ascii", errors='ignore'))
print(t.get("id"))
if type == "item":
print(t.findtext("res", "", root.nsmap))
print("----")
def list(timeout):
devices = []
possibleDevices = upnpclient.discover(timeout)
for device in possibleDevices:
if "MediaServer" in device.device_type:
addToList = True
for d in devices:
if d.friendly_name == device.friendly_name:
addToList = False
break
if addToList:
devices.append(device)
for device in devices:
print("")
print(device.friendly_name.encode().decode("ascii", errors='ignore'))
print(device.location)
def help():
print("mpvDLNA.py supports the following commands:")
print("-h, --help Prints the help dialog")
print("-v, --version Prints version information")
print("-l, --list Takes a timeout in seconds and outputs a list of DLNA Media Servers on the network")
print("-b, --browse Takes a DLNA url and the id of a DLNA element and outputs its direct children")
print("-i, --info Takes a DLNA url and the id of a DLNA element and outputs its metadata")
print("-w, --wake Takes a MAC address and attempts to send a wake on lan packet to it")
if len(sys.argv) == 2:
if sys.argv[1] == "-v" or sys.argv[1] == "--version":
print("mpvDLNA.py Plugin Version 2.0.0")
else:
help()
elif len(sys.argv) == 3:
if sys.argv[1] == "-l" or sys.argv[1] == "--list":
list(int(sys.argv[2]))
elif sys.argv[1] == "-w" or sys.argv[1] == "--wake":
wake(sys.argv[2])
else:
help()
elif len(sys.argv) >= 4:
count = 2000
if len(sys.argv) == 5:
count = sys.argv[4]
if sys.argv[1] == "-b" or sys.argv[1] == "--browse":
browse(sys.argv[2], sys.argv[3], count)
elif sys.argv[1] == "-i" or sys.argv[1] == "--info":
info(sys.argv[2], sys.argv[3], count)
else:
help()
else:
help()