├── .editorconfig
├── .gitattributes
├── I2S_modules.md
├── README.md
├── install.sh
└── srv
└── http
├── assets
├── css
│ ├── addons.css
│ ├── camilla.css
│ ├── colors.css
│ ├── common.css
│ ├── equalizer.css
│ ├── hovercursor.css
│ ├── main.css
│ ├── plugin
│ │ ├── roundslider-1.6.1.min.css
│ │ ├── select2-4.0.13.min.css
│ │ └── simplekeyboard-3.6.2.min.css
│ ├── select2.css
│ ├── settings.css
│ └── simplekeyboard.css
├── data
│ ├── regdomcodes.json
│ ├── system-i2s.json
│ └── titles_with_paren
├── fonts
│ ├── Inconsolata.woff2
│ ├── Lato-Light.woff2
│ ├── Lato-Regular.woff2
│ └── rern.woff2
├── img
│ ├── addons
│ │ ├── thumbdab.jpg
│ │ ├── thumbfran.gif
│ │ ├── thumbgpio.gif
│ │ ├── thumbplst.jpg
│ │ ├── thumbrank.png
│ │ └── thumbwebr.png
│ ├── coverart.svg
│ ├── gpio.svg
│ ├── i2cbackpack.jpg
│ ├── icon.png
│ ├── icon.svg
│ ├── lcd.jpg
│ ├── lcdchar.jpg
│ ├── mpdoled.jpg
│ ├── powerbutton.jpg
│ ├── relays.jpg
│ ├── rotaryencoder.jpg
│ ├── splash.png
│ ├── stopwatch.svg
│ ├── vu.svg
│ └── vuled.jpg
└── js
│ ├── addons.js
│ ├── camilla.js
│ ├── common.js
│ ├── context.js
│ ├── features.js
│ ├── function.js
│ ├── guide.js
│ ├── main.js
│ ├── networks.js
│ ├── passive.js
│ ├── player.js
│ ├── plugin
│ ├── camilladsp_plot-3.0.2.min.js
│ ├── complex-2.4.2.min.js
│ ├── jquery-3.7.1.min.js
│ ├── lazysizes-5.3.2.min.js
│ ├── pica-9.0.1.min.js
│ ├── plotly-basic-3.0.1.min.js
│ ├── qrcode-20200314.min.js
│ ├── select2-4.0.13.min.js
│ └── simplekeyboard-3.8.1.min.js
│ ├── settings.js
│ ├── shortcut.js
│ ├── simplekeyboard.js
│ └── system.js
├── bash
├── albumthumbnail.sh
├── audiocd.sh
├── bashrc
├── bluealsa-dbus.py
├── bluetoothbutton.sh
├── cmd-coverart.sh
├── cmd-list.sh
├── cmd.sh
├── common.sh
├── dab-scan.sh
├── dab-start.sh
├── lcdchar.py
├── motd.sh
├── mpdidle.sh
├── power.sh
├── powerbutton.sh
├── relays-timer.sh
├── relays.sh
├── rotaryencoder.sh
├── scrobble.sh
├── settings
│ ├── addons-data.sh
│ ├── addons.sh
│ ├── camilla-bluetooth.sh
│ ├── camilla-data.sh
│ ├── camilla-devices.sh
│ ├── camilla.sh
│ ├── camilla_audiofileread.py
│ ├── camilla_cooley_tukey.py
│ ├── data-config.sh
│ ├── data-service.sh
│ ├── data-status.sh
│ ├── features-data.sh
│ ├── features.sh
│ ├── networks-bluetooth.sh
│ ├── networks-data.sh
│ ├── networks-scan.sh
│ ├── networks.sh
│ ├── player-asound.sh
│ ├── player-conf.sh
│ ├── player-data.sh
│ ├── player-devices.sh
│ ├── player-wm5102.sh
│ ├── player.sh
│ ├── system-data.sh
│ ├── system-databackup.sh
│ ├── system-datadefault.sh
│ ├── system-datareset.sh
│ ├── system-datarestore.sh
│ ├── system-mount.sh
│ ├── system-storage.sh
│ └── system.sh
├── shairport.sh
├── smbdfree.sh
├── snapclient.sh
├── spotifyd.sh
├── startup.sh
├── startx.sh
├── status-bluetooth.sh
├── status-coverart.sh
├── status-coverartonline.sh
├── status-coverartupnp.py
├── status-dab.sh
├── status-push.sh
├── status-radio.sh
├── status.sh
├── stoptimer.sh
├── tageditor.sh
├── vu.sh
└── websocket.py
├── cmd.php
├── common.php
├── function.php
├── index.php
├── library.php
├── login.php
├── playlist.php
├── settings.php
└── settings
├── addonsprogress.php
├── camilla.php
├── features.php
├── function.php
├── guide.php
├── networks.php
├── player.php
└── system.php
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = tab
6 | indent_size = 4
7 | end_of_line = lf
8 | insert_final_newline = true
9 |
10 | [*.{css,js,jsx,html,md,php,sass}] # Matches multiple types with brace expansion notation
11 | trim_trailing_whitespace = true
12 |
13 | [*.md]
14 | trim_trailing_whitespace = false
15 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Automatically normalize line endings for all text-based files
2 | # http://git-scm.com/docs/gitattributes#_end_of_line_conversion
3 | * text=auto
4 |
5 | # For the following file types, normalize line endings to LF on
6 | # checkin and prevent conversion to CRLF when they are checked out
7 | # (this is required in order to prevent newline related issues like,
8 | # for example, after the build script is run)
9 | .* text eol=lf
10 | *.css text eol=lf
11 | *.html text eol=lf
12 | *.js text eol=lf
13 | *.json text eol=lf
14 | *.md text eol=lf
15 | *.php text eol=lf
16 | *.py text eol=lf
17 | *.sh text eol=lf
18 | *.svg text eol=lf
19 | *.txt text eol=lf
20 | *.xml text eol=lf
21 |
22 | # Ensure those won't be messed up with
23 | *.jpg binary
24 | *.png binary
25 | *.gif binary
26 | *.data binary
27 | *.woff2 binary
28 |
--------------------------------------------------------------------------------
/I2S_modules.md:
--------------------------------------------------------------------------------
1 | ### Supported I2S Modules
2 | ```sh
3 | sed -e '/[{}]/ d' -e 's/\s*"\(.*\)":.*/- \1/' /srv/http/assets/data/system-i2s.json
4 | ```
5 |
6 | - Adafruit MAX98357
7 | - Adafruit UDA1334A
8 | - Allo Boss DAC
9 | - Allo Boss2 DAC
10 | - Allo DigiOne
11 | - Allo DigiOne Signature
12 | - Allo Katana DAC
13 | - Allo Piano 2.0 DAC
14 | - Allo Piano 2.1 DAC
15 | - Allo Piano 2.1 DAC (with Kali Reclocker)
16 | - Allo Piano DAC
17 | - Aoide DAC II
18 | - Apple Pi DAC
19 | - Audioinjector Addons
20 | - Audioinjector Octo
21 | - Audioinjector Stereo
22 | - Audioinjector Ultra
23 | - Audioinjector WM8731
24 | - Audioinjector Zero
25 | - Audiophonics Evo-Sabre DAC 2xES9038Q2M
26 | - Audiophonics I-Sabre DAC ES9023
27 | - Audiophonics I-Sabre AMP DAC ES9023
28 | - Audiophonics I-Sabre DAC ES9028Q2M
29 | - Audiophonics I-Sabre DAC ES9038Q2M
30 | - AudioSense-Pi
31 | - BerryNOS
32 | - ChipDip DAC
33 | - DACBerry AMP2
34 | - DACBerry One+ - RCA
35 | - DACBerry One+ - Digital
36 | - DACBerry Pro
37 | - DACBerry Pro+
38 | - DACBerry RDY+ - RCA
39 | - DACBerry RDY+ - Digital
40 | - Digital Dreamtime Akkordion
41 | - Dion Audio Loco DAC-AMP
42 | - Dion Audio Loco V2 DAC-AMP
43 | - Fe-Pi Audio
44 | - Generic AKM AK4xxx (i2s-dac/rpi-dac)
45 | - Generic AKM AK4xxx (hifiberry-dac)
46 | - Generic Burr-Brown PCM1794
47 | - Generic Burr-Brown PCM510x
48 | - Generic Burr-Brown PCM512x
49 | - Generic Cirrus Logic WM5102
50 | - Generic ESS ES90xx
51 | - Generic RPi-DAC compatible
52 | - HiFiBerry Amp
53 | - HiFiBerry Amp+
54 | - HiFiBerry Amp100
55 | - HiFiBerry Amp2
56 | - HiFiBerry Amp3
57 | - HiFiBerry DAC
58 | - HiFiBerry DAC+
59 | - HiFiBerry DAC+ HD
60 | - HiFiBerry DAC+ Lite
61 | - HiFiBerry DAC+ Pro
62 | - HiFiBerry DAC+ Pro XLR
63 | - HiFiBerry DAC+ RTC
64 | - HiFiBerry DAC+ Zero
65 | - HiFiBerry DAC+ADC
66 | - HiFiBerry DAC+ADC Pro
67 | - HiFiBerry DAC+DSP
68 | - HiFiBerry DAC2 Pro
69 | - HiFiBerry Digi
70 | - HiFiBerry Digi Pro
71 | - HiFiBerry Digi+
72 | - HiFiBerry Digi+ Pro
73 | - HiFiBerry Digi2 Pro
74 | - HiFiBox DAC
75 | - InnoMaker HiFi DAC
76 | - IQaudIO Amp
77 | - IQaudIO Amp (with auto mute)
78 | - IQaudIO Amp (with unmute)
79 | - IQaudIO Codec
80 | - IQaudIO DAC
81 | - IQaudIO DAC Pro
82 | - IQaudIO DAC+
83 | - IQaudIO Digi WM8804
84 | - IQaudIO Pi-DAC PRO
85 | - IQaudIO Pi-DAC Zero
86 | - IQaudIO Pi-DAC+
87 | - IQaudIO Pi-Digi+
88 | - IQaudIO Pi-DigiAMP+
89 | - IQaudIO Pi-DigiAMP+ (with auto mute)
90 | - IQaudIO Pi-DigiAMP+ (with unmute)
91 | - JustBoom Amp HAT
92 | - JustBoom Amp Zero
93 | - JustBoom DAC
94 | - JustBoom DAC HAT
95 | - JustBoom DAC Zero
96 | - JustBoom Digi
97 | - Mamboberry HD DAC+
98 | - Mamboberry Precision DAC+
99 | - Maxim MAX98357A
100 | - MERUS Audio Amp
101 | - NanoSound HiFi DAC Pro
102 | - PecanPi DAC
103 | - PI 2 Design 502DAC
104 | - PI 2 Design 502DAC Pro
105 | - PI 2 Design 503HTA Hybrid Tube Amp
106 | - Picade HAT
107 | - PiFi DAC+
108 | - PiFi DAC HD
109 | - PiFi DAC Zero
110 | - PiFi Digi+
111 | - Pimoroni pHAT DAC
112 | - Pimoroni Audio DAC SHIM
113 | - Blokas Labs Pisound
114 | - Raspberry Pi Codec Zero
115 | - Raspberry Pi DAC+
116 | - Raspberry Pi DAC Pro
117 | - Raspberry Pi DigiAMP+
118 | - RaspiDAC 3
119 | - RaspyPlay 4
120 | - Red Rocks Audio DigiDAC 1
121 | - RPi-DAC
122 | - Soekris dam 1021
123 | - ST400 Dac - Amp
124 | - SuperAudioBoard
125 | - SupTronics X400
126 | - SupTronics X4000K
127 | - Terra-Berry DAC 2
128 | - Waveshare WM8960
129 | - Wolfson Audio
130 | - X10 DAC
131 |
--------------------------------------------------------------------------------
/install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | alias=r1
4 |
5 | . /srv/http/bash/settings/addons.sh
6 |
7 | # 20250530
8 | file=/etc/pacman.conf
9 | grep -q mpd $file && sed -i 's/ mpd//' $file
10 |
11 | # 20250502
12 | if [[ -e $dirmpd/album && $( uniq -d $dirmpd/album ) ]]; then
13 | for t in album latest; do
14 | sort -o $dirmpd/$t{,}
15 | sort -o $dirmpd/$t'byartist'{,}
16 | done
17 | fi
18 |
19 | if ! locale | grep -qi ^LANG=.*utf-*8; then
20 | ! locale -a | grep -q ^C.utf8 && locale-gen C.utf8
21 | localectl set-locale LANG=C.utf8
22 | fi
23 |
24 | file=/etc/systemd/system/cava.service
25 | if ! grep -q ^User $file; then
26 | sed -i -e '/^ExecStart/ i\User=root' -e 's/cava/vu/' $file
27 | ln -s /etc/cava.conf /root/.config/cava
28 | systemctl daemon-reload
29 | file=$dirsystem/vuled.conf
30 | if [[ -e $file ]] && grep -q = $file; then
31 | conf=$( sed 's/.*=//' $file )
32 | echo $conf > $file
33 | fi
34 | [[ -e $dirsystem/vuled ]] && systemctl start cava
35 | fi
36 |
37 | # 20250404
38 | file=/etc/systemd/system/localbrowser.service
39 | if grep -q startx$ $file; then
40 | sed -i -E 's|^(ExecStart=).*|\1/usr/bin/startx /srv/http/bash/startx.sh|' $file
41 | systemctl daemon-reload
42 | rm /etc/X11/xinit/xinitrc
43 | fi
44 |
45 | if [[ $( pacman -Q snapcast ) != 'snapcast 0.31.0-3' ]]; then
46 | pacman -Sy --noconfirm snapcast
47 | if [[ $? == 0 ]]; then
48 | sed -i -e '/^bind_to_address/ d' -e '/^#bind_to_address/ a\bind_to_address = 0.0.0.0' /etc/snapserver.conf
49 | else
50 | echo $warn upgrade snapcast failed.
51 | fi
52 | fi
53 |
54 | #-------------------------------------------------------------------------------
55 | installstart "$1"
56 |
57 | rm -rf /srv/http/assets/{css,js} /srv/http/{bash,settings}
58 |
59 | getinstallzip
60 |
61 | . $dirbash/common.sh
62 | dirPermissions
63 | $dirbash/cmd.sh cachebust
64 | [[ -e $dirsystem/color ]] && $dirbash/cmd.sh color
65 |
66 | installfinish
67 |
--------------------------------------------------------------------------------
/srv/http/assets/css/addons.css:
--------------------------------------------------------------------------------
1 | img {
2 | border: 0
3 | }
4 | hr {
5 | margin-top: 20px;
6 | margin-bottom: 20px;
7 | border: 0;
8 | border-top: 1px solid #eee
9 | }
10 | @media (max-width: 540px) {
11 | .divaddon {
12 | padding: 10px !important;
13 | }
14 | }
15 | #list {
16 | padding: 30px 0 0 25px;
17 | list-style: none;
18 | }
19 | #list li:before {
20 | margin-right: 5px;
21 | content: '\2022';
22 | color: var( --cg60 );
23 | }
24 | #list li.installed:before {
25 | color: #0f0;
26 | }
27 | #list li.update:before,
28 | legend .title.installed.update::before {
29 | animation: blinkopaque 1.5s linear infinite;
30 | }
31 | .source {
32 | display: block;
33 | text-align: right;
34 | color: var( --cg60 );
35 | }
36 | .source i {
37 | font-size: 18px;
38 | }
39 | .revisiontext {
40 | padding-left: 20px;
41 | line-height: 22px;
42 | }
43 | .detailtext i {
44 | color: var( --cg60 );
45 | }
46 | .detailtext a {
47 | color: var( --cml );
48 | }
49 | .divaddon {
50 | margin: 10px 0;
51 | padding: 10px 20px;
52 | border-radius: 6px;
53 | background: var( --cga );
54 | }
55 | .divaddon img {
56 | max-width: 100px;
57 | }
58 | .content {
59 | float: left;
60 | width: calc( 100% - 110px);
61 | }
62 | legend {
63 | width: 100%;
64 | margin-bottom: 5px;
65 | font-size: 14px;
66 | line-height: 30px;
67 | color: var( --cg60 );
68 | border-bottom: 1px solid var( --cg );
69 | }
70 | legend .title {
71 | font-size: 21px;
72 | }
73 | legend .title.installed::before {
74 | content: '• ';
75 | color: #0f0;
76 | }
77 | legend p,
78 | legend a {
79 | display: inline-block;
80 | }
81 | legend > i:last-child {
82 | float: right;
83 | }
84 | legend .status {
85 | font-size: 24px;
86 | }
87 | #hidescrollv {
88 | max-height: calc( 100vh - 110px );
89 | margin-top: 10px;
90 | overflow: hidden;
91 | }
92 | .detailtext {
93 | line-height: 18px;
94 | }
95 | .detailtext code {
96 | padding: 3px;
97 | }
98 | .info {
99 | display: inline-block;
100 | }
101 | i.info {
102 | width: 30px;
103 | }
104 | .thumbnail {
105 | float: right;
106 | width: 100px;
107 | cursor: pointer;
108 | }
109 | a,
110 | a:hover {
111 | text-decoration: none;
112 | }
113 | a:active,
114 | a:hover {
115 | outline: 0
116 | }
117 | .container .infobtn {
118 | min-width: 90px;
119 | }
120 | .infobtn.disabled {
121 | pointer-events: auto;
122 | }
123 | #list,
124 | #list li {
125 | width: fit-content;
126 | width: -moz-fit-content;
127 | line-height: 22px;
128 | color: var( --cw );
129 | }
130 | #list a {
131 | color: #ff8888;
132 | text-decoration: none;
133 | }
134 | #list li,
135 | .divaddon legend,
136 | a {
137 | cursor: pointer;
138 | }
139 | strike {
140 | text-decoration: line-through;
141 | }
142 | li i,
143 | .revision,
144 | .divaddon legend i {
145 | color: var( --cg60 );
146 | }
147 | span,
148 | code,
149 | code i {
150 | color: var( --cw );
151 | }
152 | p,
153 | ul,
154 | .disabled {
155 | color: var( --cw );
156 | }
157 | .install {
158 | background-color: var( --cm );
159 | }
160 | .uninstall {
161 | background-color: #bb2828;
162 | }
163 |
--------------------------------------------------------------------------------
/srv/http/assets/css/colors.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --cd : hsl( 200, 100%, 35% ); /* default main*/
3 | --cw : hsl( 0, 0%, 90% );
4 |
5 | --h : 200;
6 | --s : 100%;
7 | --ml60 : 60%;
8 | --ml40 : 40%;
9 | --ml35 : 35%;
10 | --ml30 : 30%;
11 | --ml20 : 20%;
12 |
13 | --cg75 : hsl( var( --h ), 3%, 75% );
14 | --cg70 : hsl( var( --h ), 3%, 70% );
15 | --cg60 : hsl( var( --h ), 3%, 60% );
16 | --cg50 : hsl( var( --h ), 3%, 50% );
17 | --cgl : hsl( var( --h ), 3%, 40% );
18 | --cg : hsl( var( --h ), 3%, 30% );
19 | --cga : hsl( var( --h ), 3%, 20% );
20 | --cgd : hsl( var( --h ), 3%, 10% );
21 | --cm60 : hsl( var( --h ), var( --s ), var( --ml60 ) );
22 | --cml : hsl( var( --h ), var( --s ), var( --ml40 ) );
23 | --cm : hsl( var( --h ), var( --s ), var( --ml35 ) );
24 | --cma : hsl( var( --h ), var( --s ), var( --ml30 ) );
25 | --cmd : hsl( var( --h ), var( --s ), var( --ml20 ) );
26 |
27 | --shadow-btn : -2px 2px 4px #000;
28 | --shadow-btn-u : -2px -2px 4px #000;
29 | --shadow-bar : 0 0 10px 4px #000;
30 | --shadow-check : -1px 1px 2px 1px #000;
31 | --shadow-menu : 4px 4px 10px #000;
32 | --shadow-menu-l : -4px 4px 10px #000;
33 | --shadow-menu-u : -4px -4px 10px #000;
34 | --shadow-search : -2px 2px 4px #000;
35 | --shadow-select : -1px 1px 1px #000;
36 | --shadow-text : -1px 0 2px #000;
37 | /* w h x y inner(color) w outer(color) w */
38 | --glossy-btn : radial-gradient( 225% 90% at 100% 0%, hsla(0,0%,100%,.3) 50%, hsla(0,0%,100%, 0) 55% );
39 | --glossy-btn-l : radial-gradient( 225% 90% at 100% 0%, hsla(0,0%,100%,.3) 50%, hsla(0,0%,100%, 0) 55% );
40 | --glossy-btn-r : radial-gradient( 300% 90% at 0% 0%, hsla(0,0%,100%,.3) 50%, hsla(0,0%,100%, 0) 55% );
41 | --glossy-btn-m : linear-gradient( to bottom, hsla(0,0%,100%,.3) 0%, hsla(0,0%,100%,.3) 45%, hsla(0,0%,0%,0) 50%, hsla(0,0%,0%,0) 100% );
42 |
43 | --wheel : conic-gradient( in hsl longer hue, red 0 100% );
44 | }
45 |
46 | bl, .bl { color : var( --cm ) !important }
47 | bll, .bll { color : var( --cm60 ) !important }
48 | g { color : var( --cgl ) }
49 | gr, .gr { color : var( --cg60 ) !important }
50 | .grd { color : var( --cg ) !important }
51 | grn, .grn { color : #0f0 !important }
52 | ora, .ora { color : #f80 !important }
53 | pur, .pur { color : #f0f !important }
54 | red, .red { color : #f00 !important }
55 | yl, .yl { color : #ff0 !important }
56 | wh, .wh { color : var( --cw ) !important }
57 |
58 | .bgr { background : var( --cgd ) !important }
59 | .bgr60 { background-color : var( --cg60 ) !important }
60 |
61 | i.bl { color : var( --cml ) !important }
62 | input {
63 | color-scheme: dark;
64 | }
65 |
--------------------------------------------------------------------------------
/srv/http/assets/css/equalizer.css:
--------------------------------------------------------------------------------
1 | #eq .bottom { z-index: 1 }
2 | #eq .vertical { z-index: 0 }
3 |
4 | /* calc var */
5 | :root {
6 | --eqSliderH : 300px;
7 | --eqBandW : 50px;
8 | --eqLabel : calc( var( --eqBandW ) - 10px );
9 | --eqLabelM : 0 5px;
10 | }
11 | @media ( max-height: 450px ) and ( orientation: landscape ) {
12 | #eq { --eqSliderH: calc( 100vh - 150px ) }
13 | }
14 | @media ( max-width: 530px ) {
15 | #eq {
16 | --eqBandW : calc( 100vw / 10 - 4px );
17 | --eqThumbW : calc( var( --eqBandW ) - 8px );
18 | }
19 | }
20 | @media ( max-width: 450px ) {
21 | #eq {
22 | --eqLabel : calc( var( --eqBandW ) - 4px );
23 | --eqLabelM : 0 2px;
24 | }
25 | }
26 | @media ( max-width: 330px ) { /* scaled 0.9 */
27 | #eq { --eqBandW: calc( 100vw / 10 ) }
28 | }
29 | @media ( max-width: 320px ) and ( max-height: 480px ) {
30 | #eq { --eqSliderH: calc( 100vh - 180px ) }
31 | }
32 |
33 | /* size - position */
34 | #eq i { width : var( --eqBandW ) }
35 | #eq .vertical { width : var( --eqSliderH ) }
36 | #eq .label a { width : var( --eqLabel ) }
37 | #eq input { height : var( --eqBandW ) }
38 |
39 | @media ( max-width: 530px ) {
40 | #eq input::-webkit-slider-thumb { width : var( --eqThumbW ) }
41 | #eq input::-moz-range-thumb { width : var( --eqThumbW ) }
42 | } /* <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< */
43 | #eq .label {
44 | margin-bottom: 15px;
45 | white-space: nowrap;
46 | }
47 | #eq .label a {
48 | display: inline-block;
49 | margin: var( --eqLabelM );
50 | height: 17px;
51 | line-height: 17px;
52 | font-size: 15px;
53 | color: var( --cw );
54 | text-align: center;
55 | text-shadow: none;
56 | font-family: Inconsolata;
57 | background: var( --cg );
58 | border-radius: 3px;
59 | cursor: pointer;
60 | }
61 | #eq .label.dn,
62 | #eq .label.up {
63 | max-width: unset;
64 | user-select: none;
65 | -webkit-touch-callout: none;
66 | -webkit-user-select: none;
67 | }
68 | #eq .label.dn a {
69 | background: var( --cgd );
70 | }
71 | #eq .bottom {
72 | position: absolute;
73 | top: 405px;
74 | left: 50%;
75 | transform: translateX( -50% );
76 | }
77 | #eq i {
78 | position: relative;
79 | width: 40px !important;
80 | height: 40px !important;
81 | line-height: 40px !important;
82 | vertical-align: middle;
83 | }
84 | #eq .vertical {
85 | position: relative;
86 | max-width: 300px;
87 | margin: 0;
88 | transform : rotate( -90deg );
89 | transform-origin: top left;
90 | translate: 10px 300px;
91 | }
92 | #eq .vertical::before { /* flat line */
93 | position: absolute;
94 | content: '';
95 | width: 1px;
96 | height: 100%;
97 | right: 150px;
98 | background: var( --cgl );
99 | }
100 | #eq input {
101 | display: block;
102 | position: relative;
103 | max-width: 100%;
104 | touch-action: none; /* fix: android chrome cannot drag */
105 | }
106 | #eq input::-webkit-slider-thumb {
107 | margin-top: -19px;
108 | transform : rotate( 90deg );
109 | }
110 | #eq input::-moz-range-thumb {
111 | transform : rotate( 90deg );
112 | }
113 | #divpreset {
114 | display: inline-block;
115 | width: 230px;
116 | height: 42px;
117 | }
--------------------------------------------------------------------------------
/srv/http/assets/css/hovercursor.css:
--------------------------------------------------------------------------------
1 | /* :hover *********************************************************/
2 | #lib-title a:hover {
3 | color: var( --cm );
4 | }
5 | #lib-list li:hover,
6 | #pl-savedlist li:hover,
7 | #pl-list li:hover,
8 | #lib-list li.updn:hover,
9 | #pl-savedlist li.updn:hover {
10 | background-color: var( --cga );
11 | }
12 | #pl-list li.updn:hover {
13 | background-color: var( --cmd );
14 | }
15 | #bar-top i.active:hover,
16 | #bar-bottom i.active:hover,
17 | #button-library:hover,
18 | #button-playlist:hover,
19 | #playback-controls button.active:hover,
20 | #playback-row .btn.active:hover {
21 | background-color: var( --cml ) !important;
22 | }
23 | #lib-list li:not( .licover ).active:hover,
24 | #pl-savedlist li.active:hover,
25 | #pl-list li.active:hover {
26 | background-color: var( --cm );
27 | }
28 | .menu a:hover,
29 | .submenu:not( .disabled ):hover,
30 | #bar-bottom i:hover,
31 | #button-settings:hover,
32 | #divlyricsartist i:hover,
33 | #playback-controls i:hover,
34 | #playback-row .btn:hover {
35 | background-color: var( --cgl ) !important;
36 | }
37 | .index a:hover,
38 | #button-lib-search:hover,
39 | #pl-manage i:hover,
40 | #button-lib-back:hover,
41 | #button-pl-back:hover,
42 | #button-pl-search:hover {
43 | background-color: var( --cg ) !important;
44 | }
45 | /* :active:hover *********************************************************/
46 | #colorok:active:hover,
47 | #lib-list li:not( .licover ).active:active:hover,
48 | #pl-savedlist li.active:active:hover,
49 | #pl-list li.active:active:hover,
50 | #playback-controls button.active:active:hover,
51 | #playback-row .btn.active:active:hover {
52 | background-color: var( --cma ) !important;
53 | }
54 | .menu a:active:hover,
55 | .submenu:not( .disabled ):active:hover,
56 | #button-lib-search:active:hover,
57 | #button-settings:active:hover,
58 | #lib-list li:active:hover,
59 | #pl-manage i:active:hover,
60 | #button-pl-search:active:hover,
61 | #playback-controls button:active:hover,
62 | #playback-row .btn:active:hover {
63 | background-color: var( --cga ) !important;
64 | }
65 | #lib-list li:active:hover,
66 | #pl-savedlist li:active:hover,
67 | #pl-list li:active:hover,
68 | #power.active,
69 | #screenoff.active,
70 | #power:hover,
71 | #screenoff:hover {
72 | background-color: var( --cgd ) !important;
73 | }
74 | .sub.nohover:hover {
75 | background-color: var( --cg ) !important;
76 | }
77 |
78 | /* cursor *********************************************************/
79 | button,
80 | li:not( .licover ),
81 | .band,
82 | .btn,
83 | .bioback,
84 | .biosimilar,
85 | .button-dab-refresh,
86 | .button-latest-clear,
87 | .button-webradio-new,
88 | .close-root,
89 | .coverart,
90 | .coveredit,
91 | .bkedit,
92 | .emptyadd,
93 | .i-search,
94 | .index a.indexed,
95 | .index i,
96 | .licoverimg,
97 | .liinfo,
98 | .map,
99 | .menu a,
100 | .mode:not( .nodata ),
101 | .point,
102 | .savedlist,
103 | .submenu:not( .disabled ),
104 | #album:not( .albumgray ),
105 | #artist,
106 | #bar-bottom,
107 | #bar-top i,
108 | #button-library,
109 | #button-lib-back,
110 | #button-lib-update,
111 | #button-pl-back,
112 | #button-pl-playlists,
113 | #button-playlist,
114 | #colorcancel,
115 | #colorok,
116 | #coloroption,
117 | #elapsed,
118 | #eq i,
119 | #eq .label a,
120 | #liboptions,
121 | #lib-title a,
122 | #lib-search-close,
123 | #lyrics i,
124 | #pl-manage i,
125 | #pl-search-close,
126 | #playericon.i-audiocd,
127 | #playlist-empty,
128 | #refresh,
129 | #sampling i,
130 | #status,
131 | #thumbupdate,
132 | #time-band,
133 | #time svg,
134 | #title,
135 | #volume-band,
136 | #volume-bar-dn,
137 | #volume-bar-up,
138 | #volume-band-level {
139 | cursor: pointer;
140 | }
141 | .disabled,
142 | #lib-title a:last-of-type:hover {
143 | cursor: default;
144 | }
145 | #lib-title a:last-of-type:hover {
146 | color: var( --cw );
147 | }
148 | #bio .container:focus-visible,
149 | #lyricstext {
150 | outline: none;
151 | }
152 | .content-top > i:focus-visible,
153 | #pl-manage i:focus-visible {
154 | border-radius: 50%;
155 | outline: 2px solid var(--cgl );
156 | outline-offset: -5px;
157 | }
158 |
--------------------------------------------------------------------------------
/srv/http/assets/css/plugin/roundslider-1.6.1.min.css:
--------------------------------------------------------------------------------
1 | /*! roundSlider v1.6.1 | (c) 2015-2020, Soundar | MIT license | http://roundsliderui.com/licence.html */
2 | .rs-ie,.rs-edge,.rs-handle{-ms-touch-action:none;touch-action:none}.rs-control{position:relative;outline:0 none}.rs-container{position:relative}.rs-control *,.rs-control *:before,.rs-control *:after{-webkit-box-sizing:border-box;box-sizing:border-box}.rs-animation .rs-transition{transition:all 0.5s linear 0s}.rs-bar{-webkit-transform-origin:100% 50%;-ms-transform-origin:100% 50%;transform-origin:100% 50%}.rs-control .rs-split .rs-path,.rs-control .rs-overlay1,.rs-control .rs-overlay2{-webkit-transform-origin:50% 100%;-ms-transform-origin:50% 100%;transform-origin:50% 100%}.rs-control .rs-overlay{-webkit-transform-origin:100% 100%;-ms-transform-origin:100% 100%;transform-origin:100% 100%}.rs-rounded .rs-seperator,.rs-split .rs-path{-webkit-background-clip:padding-box;background-clip:padding-box}.rs-disabled{opacity:.35}.rs-inner-container{height:100%;width:100%;position:absolute;top:0;overflow:hidden}.rs-control .rs-quarter div.rs-block{height:200%;width:200%}.rs-control .rs-half.rs-top div.rs-block,.rs-control .rs-half.rs-bottom div.rs-block{height:200%;width:100%}.rs-control .rs-half.rs-left div.rs-block,.rs-control .rs-half.rs-right div.rs-block{height:100%;width:200%}.rs-control .rs-bottom .rs-block{top:auto;bottom:0}.rs-control .rs-right .rs-block{right:0}.rs-block.rs-outer{border-radius:1000px}.rs-block{height:100%;width:100%;display:block;position:absolute;top:0;overflow:hidden;z-index:3}.rs-block .rs-inner{border-radius:1000px;display:block;height:100%;width:100%;position:relative}.rs-overlay{width:50%}.rs-overlay1,.rs-overlay2{width:100%}.rs-overlay,.rs-overlay1,.rs-overlay2{position:absolute;background-color:#fff;z-index:3;top:0;height:50%}.rs-bar{display:block;position:absolute;bottom:0;height:0;z-index:10}.rs-bar.rs-rounded{z-index:5}.rs-bar .rs-seperator{height:0;display:block;float:left}.rs-bar:not(.rs-rounded) .rs-seperator{border-left:none;border-right:none}.rs-bar.rs-start .rs-seperator{border-top:none}.rs-bar.rs-end .rs-seperator{border-bottom:none}.rs-bar.rs-start.rs-rounded .rs-seperator{border-radius:0 0 1000px 1000px}.rs-bar.rs-end.rs-rounded .rs-seperator{border-radius:1000px 1000px 0 0}.rs-full .rs-bar,.rs-half .rs-bar{width:50%}.rs-half.rs-left .rs-bar,.rs-half.rs-right .rs-bar,.rs-quarter .rs-bar{width:100%}.rs-full .rs-bar,.rs-half.rs-left .rs-bar,.rs-half.rs-right .rs-bar{top:50%}.rs-bottom .rs-bar{top:0}.rs-half.rs-right .rs-bar,.rs-quarter.rs-right .rs-bar{right:100%}.rs-handle.rs-move{cursor:move}.rs-readonly .rs-handle.rs-move{cursor:default}.rs-classic-mode .rs-path{display:block;height:100%;width:100%}.rs-split .rs-path{border-radius:1000px 1000px 0 0;overflow:hidden;height:50%;position:absolute;top:0;z-index:2}.rs-control .rs-svg-container{display:block;position:absolute;top:0}.rs-control .rs-bottom .rs-svg-container{top:auto;bottom:0}.rs-control .rs-right .rs-svg-container{right:0}.rs-tooltip{position:absolute;cursor:default;border:1px solid transparent;z-index:10}.rs-full .rs-tooltip{top:50%;left:50%}.rs-bottom .rs-tooltip{top:0}.rs-top .rs-tooltip{bottom:0}.rs-right .rs-tooltip{left:0}.rs-left .rs-tooltip{right:0}.rs-half.rs-top .rs-tooltip,.rs-half.rs-bottom .rs-tooltip{left:50%}.rs-half.rs-left .rs-tooltip,.rs-half.rs-right .rs-tooltip{top:50%}.rs-tooltip .rs-input{outline:0 none;border:none;background:transparent}.rs-tooltip-text{font-family:verdana;font-size:13px;border-radius:7px;text-align:center;color:inherit}.rs-tooltip.rs-edit{padding:5px 8px}.rs-tooltip.rs-hover,.rs-tooltip.rs-edit:hover{border:1px solid #AAA;cursor:pointer}.rs-readonly .rs-tooltip.rs-edit:hover{border-color:transparent;cursor:default}.rs-tooltip.rs-center{margin:0px!important}.rs-half.rs-top .rs-tooltip.rs-center,.rs-half.rs-bottom .rs-tooltip.rs-center{transform:translate(-50%,0)}.rs-half.rs-left .rs-tooltip.rs-center,.rs-half.rs-right .rs-tooltip.rs-center{transform:translate(0,-50%)}.rs-full .rs-tooltip.rs-center{transform:translate(-50%,-50%)}.rs-tooltip.rs-reset{margin:0px!important;top:0px!important;left:0px!important}.rs-handle{border-radius:1000px;outline:0 none;float:left}.rs-handle.rs-handle-square{border-radius:0}.rs-handle-dot{border:1px solid #AAA;padding:6px}.rs-handle-dot:after{display:block;content:"";border:1px solid #AAA;height:100%;width:100%;border-radius:1000px}.rs-seperator{border:1px solid #AAA}.rs-border{border:1px solid #AAA}.rs-path-color{background-color:#FFF}.rs-range-color{background-color:#54BBE0}.rs-bg-color{background-color:#FFF}.rs-handle{background-color:#838383}.rs-handle-dot{background-color:#FFF}.rs-handle-dot:after{background-color:#838383}.rs-path-inherited .rs-path{opacity:.2}.rs-svg-mode .rs-path{stroke:#FFF}.rs-svg-mode .rs-range{stroke:#54BBE0}.rs-svg-mode .rs-border{stroke:#AAA}
--------------------------------------------------------------------------------
/srv/http/assets/css/plugin/simplekeyboard-3.6.2.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * simple-keyboard v3.6.2
4 | * https://github.com/hodgef/simple-keyboard
5 | *
6 | * Copyright (c) Francisco Hodge (https://github.com/hodgef) and project contributors.
7 | *
8 | * This source code is licensed under the MIT license found in the
9 | * LICENSE file in the root directory of this source tree.
10 | *
11 | */.hg-theme-default{background-color:#ececec;border-radius:5px;box-sizing:border-box;font-family:HelveticaNeue-Light,Helvetica Neue Light,Helvetica Neue,Helvetica,Arial,Lucida Grande,sans-serif;overflow:hidden;padding:5px;touch-action:manipulation;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:100%}.hg-theme-default .hg-button span{pointer-events:none}.hg-theme-default button.hg-button{border-width:0;font-size:inherit;outline:0}.hg-theme-default .hg-button{display:inline-block;flex-grow:1}.hg-theme-default .hg-row{display:flex}.hg-theme-default .hg-row:not(:last-child){margin-bottom:5px}.hg-theme-default .hg-row .hg-button-container,.hg-theme-default .hg-row .hg-button:not(:last-child){margin-right:5px}.hg-theme-default .hg-row>div:last-child{margin-right:0}.hg-theme-default .hg-row .hg-button-container{display:flex}.hg-theme-default .hg-button{-webkit-tap-highlight-color:rgba(0,0,0,0);align-items:center;background:#fff;border-bottom:1px solid #b5b5b5;border-radius:5px;box-shadow:0 0 3px -1px rgba(0,0,0,.3);box-sizing:border-box;cursor:pointer;display:flex;height:40px;justify-content:center;padding:5px}.hg-theme-default .hg-button.hg-standardBtn{width:20px}.hg-theme-default .hg-button.hg-activeButton{background:#efefef}.hg-theme-default.hg-layout-numeric .hg-button{align-items:center;display:flex;height:60px;justify-content:center;width:33.3%}.hg-theme-default .hg-button.hg-button-numpadadd,.hg-theme-default .hg-button.hg-button-numpadenter{height:85px}.hg-theme-default .hg-button.hg-button-numpad0{width:105px}.hg-theme-default .hg-button.hg-button-com{max-width:85px}.hg-theme-default .hg-button.hg-standardBtn.hg-button-at{max-width:45px}.hg-theme-default .hg-button.hg-selectedButton{background:rgba(5,25,70,.53);color:#fff}.hg-theme-default .hg-button.hg-standardBtn[data-skbtn=".com"]{max-width:82px}.hg-theme-default .hg-button.hg-standardBtn[data-skbtn="@"]{max-width:60px}.hg-candidate-box{background:#ececec;border-bottom:2px solid #b5b5b5;border-radius:5px;display:inline-flex;margin-top:-10px;max-width:272px;position:absolute;transform:translateY(-100%);-webkit-user-select:none;-moz-user-select:none;user-select:none}ul.hg-candidate-box-list{display:flex;flex:1;list-style:none;margin:0;padding:0}li.hg-candidate-box-list-item{align-items:center;display:flex;height:40px;justify-content:center;width:40px}li.hg-candidate-box-list-item:hover{background:rgba(0,0,0,.03);cursor:pointer}li.hg-candidate-box-list-item:active{background:rgba(0,0,0,.1)}.hg-candidate-box-prev:before{content:"◄"}.hg-candidate-box-next:before{content:"►"}.hg-candidate-box-next,.hg-candidate-box-prev{align-items:center;color:#969696;cursor:pointer;display:flex;padding:0 10px}.hg-candidate-box-next{border-bottom-right-radius:5px;border-top-right-radius:5px}.hg-candidate-box-prev{border-bottom-left-radius:5px;border-top-left-radius:5px}.hg-candidate-box-btn-active{color:#444}
--------------------------------------------------------------------------------
/srv/http/assets/css/select2.css:
--------------------------------------------------------------------------------
1 | .select2-container {
2 | max-width : 230px;
3 | height : 40px;
4 | margin-bottom : 10px;
5 | font-size : 16px;
6 | line-height : 24px;
7 | border-radius : 4px;
8 | box-shadow : var( --shadow-select );
9 | text-align : left;
10 | }
11 | .container .select2-container {
12 | width : 100% !important;
13 | }
14 | .select2-selection {
15 | height: 40px !important;
16 | }
17 | .select2-selection--single {
18 | height : 40px;
19 | background : none;
20 | background : none !important;
21 | border : none !important;
22 | border-radius : 4px !important;
23 | }
24 | .select2-selection__rendered {
25 | line-height : 40px !important;
26 | border-radius : 4px;
27 | color : var( --cw ) !important;
28 | background : var( --cg );
29 | }
30 | .select2-results__option {
31 | padding-left: 8px;
32 | }
33 | .select2-selection__arrow {
34 | height : 40px !important;
35 | width : 30px !important;
36 | }
37 | .select2-search--dropdown {
38 | box-shadow: var( --shadow-search );
39 | }
40 | .select2-dropdown {
41 | background : var( --cga );
42 | border : none;
43 | border-radius : 0;
44 | }
45 | .select2-results__options {
46 | max-height: 360px !important;
47 | }
48 | @media ( max-height: 510px ) {
49 | .select2-results__options {
50 | max-height: 70vh !important;
51 | }
52 | }
53 | .select2-dropdown--above {
54 | box-shadow: var( --shadow-menu-u );
55 | }
56 | .select2-dropdown--below {
57 | box-shadow: var( --shadow-menu-l );
58 | }
59 | .select2-search__field {
60 | font-family : lato;
61 | font-size : 16px;
62 | line-height : 24px;
63 | border : none !important;
64 | border-radius : 4px;
65 | background : #000;
66 | color : var( --cw );
67 | }
68 | .select2-results__option--selected {
69 | color : var( --cw ) !important;
70 | background : var( --cg ) !important;
71 | }
72 | .select2-results__option--highlighted.select2-results__option--selectable {
73 | background: var( --cgl ) !important;
74 | }
75 | .select2-selection__arrow b {
76 | margin-top : -4px !important;
77 | border-color : var( --cw ) transparent transparent transparent !important;
78 | border-width : 5px 5px 0 5px !important;
79 | }
80 | .select2-container--open .select2-selection__arrow b {
81 | margin-top : -5px !important;
82 | border-color : transparent transparent var( --cw ) transparent !important;
83 | border-width : 0 5px 5px 5px !important;
84 | }
85 | .select2-container.disabled .select2-selection__arrow,
86 | .select2-container--disabled .select2-selection__arrow {
87 | display: none;
88 | }
89 | .select2-container.disabled,
90 | .select2-container--disabled {
91 | pointer-events: none;
92 | }
93 | .select2 i {
94 | vertical-align: 0 !important;
95 | }
96 | @media ( max-height: 768px ) {
97 | .select2-results__options {
98 | max-height: calc( ( 100vh - 100px ) / 2 ) !important;
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/srv/http/assets/css/simplekeyboard.css:
--------------------------------------------------------------------------------
1 | #infoBox {
2 | margin-bottom: 200px;
3 | }
4 | /* Simple-keyboard */
5 | #keyboard {
6 | position: fixed;
7 | width: 100%;
8 | bottom: 0;
9 | background: var( --cga ) !important;
10 | box-shadow: var( --shadow-bar );
11 | -webkit-touch-callout: none; /* iOS Safari */
12 | }
13 | .simple-keyboard {
14 | /* max-width: 560px;*/
15 | max-width: 485px;
16 | margin: auto;
17 | padding-right: 3px !important;
18 | background: none !important;
19 | }
20 | .hg-button {
21 | min-width: 25px !important;
22 | padding: 0 !important;
23 | font-family: Lato;
24 | font-size: 22px;
25 | text-shadow: var( --shadow-text );
26 | background: var( --cg ) !important;
27 | box-shadow: var( --shadow-btn ) !important;
28 | border-radius: 4px !important;
29 | border-bottom: none !important;
30 | border-top: 1px solid var( --cg60 );
31 | }
32 | .hg-button:active {
33 | opacity: 0.5;
34 | }
35 | .hg-functionBtn {
36 | width: 17%;
37 | max-width: 75px;
38 | }
39 | .hg-button-space {
40 | width: 50%;
41 | max-width: 205px;
42 | /* max-width: 290px;*/
43 | }
44 | .hg-button-enter {
45 | font-size: 14px;
46 | background: var( --glossy-btn ) !important;
47 | background-color: var( --cm ) !important;
48 | border: none;
49 | }
50 | .hg-button-enter.disabled {
51 | color: var( --cg60 ) !important;
52 | background-color: var( --cg ) !important;
53 | pointer-events: none;
54 | }
55 | .hg-standardBtn,
56 | .hg-button-lock,
57 | .hg-button-numlock,
58 | .hg-button-num,
59 | .hg-button-alpha {
60 | max-width: 40px !important;
61 | }
62 | .hgrow2 { margin-left: 10px }
63 | .hgrow3 { margin-left: 20px }
64 | .hgrow4 { margin-left: 30px }
65 | .bgbl { background: var( --cm ) !important; }
66 |
67 | @media (max-width: 400px) {
68 | .hg-button {
69 | margin-right: 3px !important;
70 | }
71 | .hg-button-space {
72 | max-width: 190px;
73 | }
74 | .hg-button-enter {
75 | font-size: 14px !important;
76 | }
77 | }
78 | @media (max-width: 600px) {
79 | .simple-keyboard { max-width: 545px }
80 | .hgspace { max-width: 265px }
81 | .hgrow2,
82 | .hgrow3,
83 | .hgrow4 {
84 | margin-left: 0;
85 | }
86 | }
87 | @media (max-height: 320px) {
88 | .hg-button {
89 | height: 30px !important;
90 | font-size: 18px !important;
91 | }
92 | }
--------------------------------------------------------------------------------
/srv/http/assets/data/regdomcodes.json:
--------------------------------------------------------------------------------
1 | {
2 | "00": "00",
3 | "Afghanistan": "AF",
4 | "Albania": "AL",
5 | "Algeria": "DZ",
6 | "American Samoa": "AS",
7 | "Andorra": "AD",
8 | "Anguilla": "AI",
9 | "Argentina": "AR",
10 | "Armenia": "AM",
11 | "Aruba": "AW",
12 | "Australia": "AU",
13 | "Austria": "AT",
14 | "Azerbaijan": "AZ",
15 | "Bahamas": "BS",
16 | "Bahrain": "BH",
17 | "Bangladesh": "BD",
18 | "Barbados": "BB",
19 | "Belarus": "BY",
20 | "Belgium": "BE",
21 | "Belize": "BZ",
22 | "Bermuda": "BM",
23 | "Bhutan": "BT",
24 | "Bolivia, Plurinational State of": "BO",
25 | "Bosnia and Herzegovina": "BA",
26 | "Brazil": "BR",
27 | "Brunei Darussalam": "BN",
28 | "Bulgaria": "BG",
29 | "Burkina Faso": "BF",
30 | "Cambodia": "KH",
31 | "Canada": "CA",
32 | "Cayman Islands": "KY",
33 | "Central African Republic": "CF",
34 | "Chad": "TD",
35 | "Chile": "CL",
36 | "China": "CN",
37 | "Christmas Island": "CX",
38 | "Colombia": "CO",
39 | "Costa Rica": "CR",
40 | "Croatia": "HR",
41 | "Cuba": "CU",
42 | "Cyprus": "CY",
43 | "Czechia": "CZ",
44 | "Côte d'Ivoire": "CI",
45 | "Denmark": "DK",
46 | "Dominica": "DM",
47 | "Dominican Republic": "DO",
48 | "Ecuador": "EC",
49 | "Egypt": "EG",
50 | "El Salvador": "SV",
51 | "Estonia": "EE",
52 | "Ethiopia": "ET",
53 | "Finland": "FI",
54 | "France": "FR",
55 | "French Guiana": "GF",
56 | "French Polynesia": "PF",
57 | "Georgia": "GE",
58 | "Germany": "DE",
59 | "Ghana": "GH",
60 | "Greece": "GR",
61 | "Greenland": "GL",
62 | "Grenada": "GD",
63 | "Guadeloupe": "GP",
64 | "Guam": "GU",
65 | "Guatemala": "GT",
66 | "Guyana": "GY",
67 | "Haiti": "HT",
68 | "Honduras": "HN",
69 | "Hong Kong": "HK",
70 | "Hungary": "HU",
71 | "Iceland": "IS",
72 | "India": "IN",
73 | "Indonesia": "ID",
74 | "Iran, Islamic Republic of": "IR",
75 | "Ireland": "IE",
76 | "Israel": "IL",
77 | "Italy": "IT",
78 | "Jamaica": "JM",
79 | "Japan": "JP",
80 | "Jordan": "JO",
81 | "Kazakhstan": "KZ",
82 | "Kenya": "KE",
83 | "Korea, Democratic People's Republic of": "KP",
84 | "Korea, Republic of": "KR",
85 | "Kuwait": "KW",
86 | "Latvia": "LV",
87 | "Lebanon": "LB",
88 | "Lesotho": "LS",
89 | "Liechtenstein": "LI",
90 | "Lithuania": "LT",
91 | "Luxembourg": "LU",
92 | "Macao": "MO",
93 | "Malawi": "MW",
94 | "Malaysia": "MY",
95 | "Maldives": "MV",
96 | "Malta": "MT",
97 | "Marshall Islands": "MH",
98 | "Martinique": "MQ",
99 | "Mauritania": "MR",
100 | "Mauritius": "MU",
101 | "Mayotte": "YT",
102 | "Mexico": "MX",
103 | "Micronesia, Federated States of": "FM",
104 | "Moldova, Republic of": "MD",
105 | "Monaco": "MC",
106 | "Mongolia": "MN",
107 | "Montenegro": "ME",
108 | "Morocco": "MA",
109 | "Nepal": "NP",
110 | "Netherlands": "NL",
111 | "New Zealand": "NZ",
112 | "Nicaragua": "NI",
113 | "Nigeria": "NG",
114 | "North Macedonia": "MK",
115 | "Northern Mariana Islands": "MP",
116 | "Norway": "NO",
117 | "Oman": "OM",
118 | "Pakistan": "PK",
119 | "Palau": "PW",
120 | "Panama": "PA",
121 | "Papua New Guinea": "PG",
122 | "Paraguay": "PY",
123 | "Peru": "PE",
124 | "Philippines": "PH",
125 | "Poland": "PL",
126 | "Portugal": "PT",
127 | "Puerto Rico": "PR",
128 | "Qatar": "QA",
129 | "Romania": "RO",
130 | "Russian Federation": "RU",
131 | "Rwanda": "RW",
132 | "Réunion": "RE",
133 | "Saint Barthélemy": "BL",
134 | "Saint Kitts and Nevis": "KN",
135 | "Saint Lucia": "LC",
136 | "Saint Martin (French part)": "MF",
137 | "Saint Pierre and Miquelon": "PM",
138 | "Saint Vincent and the Grenadines": "VC",
139 | "Samoa": "WS",
140 | "Saudi Arabia": "SA",
141 | "Senegal": "SN",
142 | "Serbia": "RS",
143 | "Singapore": "SG",
144 | "Slovakia": "SK",
145 | "Slovenia": "SI",
146 | "South Africa": "ZA",
147 | "Spain": "ES",
148 | "Sri Lanka": "LK",
149 | "Suriname": "SR",
150 | "Sweden": "SE",
151 | "Switzerland": "CH",
152 | "Syrian Arab Republic": "SY",
153 | "Taiwan, Province of China": "TW",
154 | "Tanzania, United Republic of": "TZ",
155 | "Thailand": "TH",
156 | "Togo": "TG",
157 | "Trinidad and Tobago": "TT",
158 | "Tunisia": "TN",
159 | "Turks and Caicos Islands": "TC",
160 | "Türkiye": "TR",
161 | "Uganda": "UG",
162 | "Ukraine": "UA",
163 | "United Arab Emirates": "AE",
164 | "United Kingdom": "GB",
165 | "United States": "US",
166 | "Uruguay": "UY",
167 | "Uzbekistan": "UZ",
168 | "Vanuatu": "VU",
169 | "Venezuela, Bolivarian Republic of": "VE",
170 | "Viet Nam": "VN",
171 | "Virgin Islands, U.S.": "VI",
172 | "Wallis and Futuna": "WF",
173 | "Yemen": "YE",
174 | "Zimbabwe": "ZW"
175 | }
176 |
--------------------------------------------------------------------------------
/srv/http/assets/data/system-i2s.json:
--------------------------------------------------------------------------------
1 | {
2 | "Adafruit MAX98357": "hifiberry-dac",
3 | "Adafruit UDA1334A": "hifiberry-dac,i2s-mmap",
4 | "Allo Boss DAC": "allo-boss-dac-pcm512x-audio",
5 | "Allo Boss2 DAC": "allo-boss2-dac-audio",
6 | "Allo DigiOne": "allo-digione",
7 | "Allo DigiOne Signature": "allo-digione",
8 | "Allo Katana DAC": "allo-katana-dac-audio",
9 | "Allo Piano 2.0 DAC": "allo-boss-dac-pcm512x-audio",
10 | "Allo Piano 2.1 DAC": "allo-piano-dac-plus-pcm512x-audio",
11 | "Allo Piano 2.1 DAC (with Kali Reclocker)": "allo-piano-dac-plus-pcm512x-audio,glb_mclk",
12 | "Allo Piano DAC": "allo-piano-dac-pcm512x-audio",
13 | "Aoide DAC II": "i2s-dac",
14 | "Apple Pi DAC": "applepi-dac",
15 | "Audioinjector Addons": "audioinjector-addons",
16 | "Audioinjector Octo": "audioinjector-addons",
17 | "Audioinjector Stereo": "audioinjector-wm8731-audio",
18 | "Audioinjector Ultra": "audioinjector-ultra",
19 | "Audioinjector WM8731": "audioinjector-wm8731-audio",
20 | "Audioinjector Zero": "audioinjector-wm8731-audio",
21 | "Audiophonics Evo-Sabre DAC 2xES9038Q2M": "i-sabre-q2m",
22 | "Audiophonics I-Sabre DAC ES9023": "i2s-dac",
23 | "Audiophonics I-Sabre AMP DAC ES9023": "i2s-dac",
24 | "Audiophonics I-Sabre DAC ES9028Q2M": "i2s-dac",
25 | "Audiophonics I-Sabre DAC ES9038Q2M": "i-sabre-q2m",
26 | "AudioSense-Pi": "audiosense-pi",
27 | "BerryNOS": "hifiberry_dac",
28 | "ChipDip DAC": "chipdip-dac",
29 | "DACBerry AMP2": "allo-piano-dac-plus-pcm512x-audio",
30 | "DACBerry One+ - RCA": "iqaudio-dacplus",
31 | "DACBerry One+ - Digital": "justboom-digi",
32 | "DACBerry Pro": "iqaudio-dacplus",
33 | "DACBerry Pro+": "iqaudio-dacplus",
34 | "DACBerry RDY+ - RCA": "iqaudio-dacplus",
35 | "DACBerry RDY+ - Digital": "justboom-digi",
36 | "Digital Dreamtime Akkordion": "akkordion-iqdacplus",
37 | "Dion Audio Loco DAC-AMP": "dionaudio-loco",
38 | "Dion Audio Loco V2 DAC-AMP": "dionaudio-loco-v2",
39 | "Fe-Pi Audio": "fe-pi-audio",
40 | "Generic AKM AK4xxx (i2s-dac/rpi-dac)": "i2s-dac",
41 | "Generic AKM AK4xxx (hifiberry-dac)": "hifiberry-dac",
42 | "Generic Burr-Brown PCM1794": "i2s-dac",
43 | "Generic Burr-Brown PCM510x": "hifiberry-dac",
44 | "Generic Burr-Brown PCM512x": "hifiberry-dacplus",
45 | "Generic Cirrus Logic WM5102": "cirrus-wm5102",
46 | "Generic ESS ES90xx": "i2s-dac",
47 | "Generic RPi-DAC compatible": "i2s-dac",
48 | "HiFiBerry Amp": "hifiberry-amp",
49 | "HiFiBerry Amp+": "hifiberry-amp",
50 | "HiFiBerry Amp100": "hifiberry-amp100",
51 | "HiFiBerry Amp2": "hifiberry-dacplus",
52 | "HiFiBerry Amp3": "hifiberry-amp3",
53 | "HiFiBerry DAC": "hifiberry-dac",
54 | "HiFiBerry DAC+": "hifiberry-dacplus",
55 | "HiFiBerry DAC+ HD": "hifiberry-dacplushd",
56 | "HiFiBerry DAC+ Lite": "i2s-dac",
57 | "HiFiBerry DAC+ Pro": "hifiberry-dacplus",
58 | "HiFiBerry DAC+ Pro XLR": "hifiberry-dacplus",
59 | "HiFiBerry DAC+ RTC": "hifiberry-dac",
60 | "HiFiBerry DAC+ Zero": "hifiberry-dac",
61 | "HiFiBerry DAC+ADC": "hifiberry-dacplusadc",
62 | "HiFiBerry DAC+ADC Pro": "hifiberry-dacplusadcpro",
63 | "HiFiBerry DAC+DSP": "hifiberry-dacplusdsp",
64 | "HiFiBerry DAC2 Pro": "hifiberry-dacplus",
65 | "HiFiBerry Digi": "hifiberry-digi",
66 | "HiFiBerry Digi Pro": "hifiberry-digi-pro",
67 | "HiFiBerry Digi+": "hifiberry-digi",
68 | "HiFiBerry Digi+ Pro": "hifiberry-digi-pro",
69 | "HiFiBerry Digi2 Pro": "hifiberry-digi-pro",
70 | "HiFiBox DAC": "hifiberry-dacplus",
71 | "InnoMaker HiFi DAC": "allo-boss-dac-pcm512x-audio",
72 | "IQaudIO Amp": "iqaudio-dacplus",
73 | "IQaudIO Amp (with auto mute)": "iqaudio-dacplus,auto_mute_amp",
74 | "IQaudIO Amp (with unmute)": "iqaudio-dacplus,unmute_amp",
75 | "IQaudIO Codec": "iqaudio-codec",
76 | "IQaudIO DAC": "iqaudio-dac",
77 | "IQaudIO DAC Pro": "iqaudio-dacplus",
78 | "IQaudIO DAC+": "iqaudio-dacplus",
79 | "IQaudIO Digi WM8804": "iqaudio-digi-wm8804-audio",
80 | "IQaudIO Pi-DAC PRO": "iqaudio-dacplus",
81 | "IQaudIO Pi-DAC Zero": "iqaudio-dacplus",
82 | "IQaudIO Pi-DAC+": "iqaudio-dacplus",
83 | "IQaudIO Pi-Digi+": "iqaudio-digi-wm8804-audio",
84 | "IQaudIO Pi-DigiAMP+": "iqaudio-dacplus",
85 | "IQaudIO Pi-DigiAMP+ (with auto mute)": "iqaudio-dacplus,auto_mute_amp",
86 | "IQaudIO Pi-DigiAMP+ (with unmute)": "iqaudio-dacplus,unmute_amp",
87 | "JustBoom Amp HAT": "justboom-dac",
88 | "JustBoom Amp Zero": "justboom-dac",
89 | "JustBoom DAC": "justboom-dac",
90 | "JustBoom DAC HAT": "justboom-dac",
91 | "JustBoom DAC Zero": "justboom-dac",
92 | "JustBoom Digi": "justboom-digi",
93 | "Mamboberry HD DAC+": "i2s-dac",
94 | "Mamboberry Precision DAC+": "i2s-dac",
95 | "Maxim MAX98357A": "max98357a",
96 | "MERUS Audio Amp": "merus-amp",
97 | "NanoSound HiFi DAC Pro": "hifiberry-dacplus",
98 | "PecanPi DAC": "i2s-dac",
99 | "PI 2 Design 502DAC": "hifiberry-dacplus",
100 | "PI 2 Design 502DAC Pro": "hifiberry-dacplus",
101 | "PI 2 Design 503HTA Hybrid Tube Amp": "hifiberry-dac",
102 | "Picade HAT": "hifiberry-dac",
103 | "PiFi DAC+": "hifiberry-dacplus",
104 | "PiFi DAC HD": "pifi-dac-hd",
105 | "PiFi DAC Zero": "pifi-dac-zero",
106 | "PiFi Digi+": "hifiberry-digi",
107 | "Pimoroni pHAT DAC": "hifiberry-dac",
108 | "Pimoroni Audio DAC SHIM": "hifiberry-dac",
109 | "Blokas Labs Pisound": "pisound",
110 | "Raspberry Pi Codec Zero": "rpi-codeczero",
111 | "Raspberry Pi DAC+": "rpi-dacplus",
112 | "Raspberry Pi DAC Pro": "rpi-dacpro",
113 | "Raspberry Pi DigiAMP+": "rpi-digiampplus",
114 | "RaspiDAC 3": "raspidac3",
115 | "RaspyPlay 4": "hifiberry-dacplus",
116 | "Red Rocks Audio DigiDAC 1": "rra-digidac1-wm8741-audio",
117 | "RPi-DAC": "i2s-dac",
118 | "Soekris dam 1021": "hifiberry-dac",
119 | "ST400 Dac - Amp": "iqaudio-dacplus",
120 | "SuperAudioBoard": "superaudioboard",
121 | "SupTronics X400": "hifiberry-dacplus",
122 | "SupTronics X4000K": "i2s-dac",
123 | "Terra-Berry DAC 2": "hifiberry-dac",
124 | "Waveshare WM8960": "wm8960-soundcard",
125 | "Wolfson Audio": "cirrus-wm5102",
126 | "X10 DAC": "i2s-dac"
127 | }
128 |
--------------------------------------------------------------------------------
/srv/http/assets/data/titles_with_paren:
--------------------------------------------------------------------------------
1 | 4th of July, Asbury Park (Sandy)
2 | 10,000 Years (Peace Is Now)
3 | The 59th Street Bridge Song (Feelin’ Groovy)
4 | Against All Odds (Take a Look at Me Now)
5 | All Night Long (All Night)
6 | Auctioneer (Another Engine)
7 | Bang A Gong (Get It On)
8 | Bloodletting (The Vampire Song)
9 | Brandy (You’re a Fine Girl)
10 | Brass in Pocket (I’m Special)
11 | Break on Through (To the Other Side)
12 | Caffeine, Nicotine, Benzedrine (And Wish Me Luck)
13 | The Chipmunk Song (Christmas Don’t Be Late)
14 | Christmas (Baby, Please Come Home)
15 | Come On Over (All I Want Is You)
16 | Da Doo Ron Ron (When He Walked Me Home)
17 | Damn Sam (I Love a Woman That Rains)
18 | December 1963 (Oh, What A Night)
19 | Don’t Be Stupid (You Know I Love You)
20 | Don’t Forget Me (When I’m Gone)
21 | Don’t Go Away Mad (Just Go Away)
22 | Don’t Know What You Got (Till It’s Gone)
23 | Don’t You (Forget About Me)
24 | Doo Wop (That Thing)
25 | Dreamboat Annie (Fantasy Child)
26 | Dude (Looks Like a Lady)
27 | Earth Angel (Will You Be Mine)
28 | Escape (The Piña Colada Song)
29 | Everybody (Backstreet’s Back)
30 | Everybody’s Free (To Feel Good)
31 | Everybody’s Free (To Wear Sunscreen)
32 | Everything (Between Us)
33 | Exhale (Shoop Shoop)
34 | Exit Music (For a Film)
35 | Falling in Love (Is Hard on the Knees)
36 | Fooled Again (I Don’t Like It)
37 | Forever Afternoon (Tuesday?)
38 | Fuck It (I Don’t Want You Back)
39 | Fuckin’ With My Head (Mountain Dew Rock)
40 | Get Naked (I Got a Plan)
41 | Gimme! Gimme! Gimme! (A Man After Midnight)
42 | Good Riddance (Time of Your Life)
43 | Happy Xmas (War Is Over)
44 | Hard Knock Life (Ghetto Anthem)
45 | He Hit Me (It Felt Like A Kiss)
46 | Hemorrhage (In My Hands)
47 | High 5 (Rock the Catskills)
48 | Home Ain’t Where His Heart Is (Anymore)
49 | How Sweet It Is (To Be Loved By You)
50 | I Believe (When I Fall in Love it Will Be Forever)
51 | I Can’t Go for That (No Can Do)
52 | I Don’t Care (So There)
53 | I Got You (I Feel Good)
54 | I Knew You Were Waiting (For Me)
55 | I Never Loved a Man (The Way I Love You)
56 | I Ran (So Far Away)
57 | I Wanna Dance with Somebody (Who Loves Me)
58 | I Want Your (Hands on Me)
59 | Instant Karma (We All Shine On)
60 | Istanbul (Not Constantinople)
61 | It’s Alright, Ma (I’m Only Bleeding)
62 | It’s Only Rock ’n’ Roll (But I Like It)
63 | It’s The End of the World As We Know It (And I Feel Fine)
64 | I’d Do Anything for Love (But I Won’t Do That)
65 | I’d Lie for You (And That’s the Truth)
66 | I’ll Be Loving You (Forever)
67 | I’m Gonna Be (500 Miles)
68 | I’m Holdin’ On to Love (To Save My Life)
69 | Jesus (Don’t Touch My Baby)
70 | Jump (For My Love)
71 | Lady (You Bring Me Up)
72 | Lily (My One and Only)
73 | Major Tom (Coming Home)
74 | Mama Told Me (Not to Come)
75 | Many Men (Wish Death)
76 | Molly (16 Candles Down the Drain)
77 | Money (That’s What I Want)
78 | Morning Train (Nine to Five)
79 | Movin’ Out (Anthony’s Song)
80 | Na Na Na (Na Na Na Na Na Na Na Na Na)
81 | No-One but You (Only the Good Die Young)
82 | Norwegian Wood (This Bird Has Flown)
83 | Only Girl (In the World)
84 | Operation Spirit (The Tyranny of Tradition)
85 | P.Y.T (Pretty Young Thing)
86 | Pay No Mind (Snoozer)
87 | Platypus (I Hate You)
88 | Pretty Fly (For a White Guy)
89 | Pride (In The Name Of Love)
90 | Puddin N’ Tain (Ask Me Again, I’ll Tell You the Same)
91 | Pulling Mussels (From The Shell)
92 | Rebirth of Slick (Cool Like Dat)
93 | Remember (Walking in the Sand)
94 | Rock! Rock! (Till You Drop)
95 | Rock Me Again and Again and Again and Again and Again and Again (Six Times)
96 | Rocket Man (I Think It’s Going To Be A Long Long Time)
97 | Rockin’ Around (With You)
98 | Rollout (My Business)
99 | Rosalita (Come Out Tonight)
100 | Sad Songs (Say So Much)
101 | Same Ol’ Situation (S.O.S.)
102 | Save a Horse (Ride a Cowboy)
103 | Scary Monsters (and Super Creeps)
104 | Second-Hand Satin Lady (And a Bargain Basement Boy)
105 | Separate Ways (Worlds Apart)
106 | Separate Ways (Worlds Apart)
107 | Sex (I’m A…)
108 | She Got the Goldmine (I Got the Shaft)
109 | The Shoop Shoop Song (It’s in His Kiss)
110 | Single Ladies (Put a Ring On It)
111 | So. Central Rain (I’m Sorry)
112 | Sometimes (Lester Piggot)
113 | Space (I Believe In)
114 | St. Elmo’s Fire (Man in Motion)
115 | Sweet Dreams (Are Made of This)
116 | T.nT. (Terror ‘n Tinseltown)
117 | That’s the Way (I Like It)
118 | That’s Too Bad (Byron Jam)
119 | Theme from The Dukes of Hazzard (Good Ol’ Boys)
120 | There’s A Moon in the Sky (Called the Moon)
121 | This Must Be the Place (Naive Melody)
122 | To Be Young (Is to Be Sad, Is to Be High)
123 | Tonight’s the Night (Gonna Be Alright)
124 | Truckdrivin’ Neighbors Downstairs (Yellow Sweat)
125 | Try (Just a Little Bit Harder)
126 | Turn! Turn! Turn! (to Everything There Is a Season)
127 | Ultra Anxiety (Teenage Style)
128 | Until You Come Back to Me (That’s What I’m Gonna Do)
129 | Wham Rap! (Enjoy What You Do)
130 | The Woman in Me (Needs the Man in You)
131 | You Got It (The Right Stuff)
132 | You’re in My Heart (The Final Acclaim)
133 | You’re Pretty Good Looking (For a Girl)
134 |
--------------------------------------------------------------------------------
/srv/http/assets/fonts/Inconsolata.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rern/rAudio/f5b314fcd3162126f1d4e7bf43578e7357331090/srv/http/assets/fonts/Inconsolata.woff2
--------------------------------------------------------------------------------
/srv/http/assets/fonts/Lato-Light.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rern/rAudio/f5b314fcd3162126f1d4e7bf43578e7357331090/srv/http/assets/fonts/Lato-Light.woff2
--------------------------------------------------------------------------------
/srv/http/assets/fonts/Lato-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rern/rAudio/f5b314fcd3162126f1d4e7bf43578e7357331090/srv/http/assets/fonts/Lato-Regular.woff2
--------------------------------------------------------------------------------
/srv/http/assets/fonts/rern.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rern/rAudio/f5b314fcd3162126f1d4e7bf43578e7357331090/srv/http/assets/fonts/rern.woff2
--------------------------------------------------------------------------------
/srv/http/assets/img/addons/thumbdab.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rern/rAudio/f5b314fcd3162126f1d4e7bf43578e7357331090/srv/http/assets/img/addons/thumbdab.jpg
--------------------------------------------------------------------------------
/srv/http/assets/img/addons/thumbfran.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rern/rAudio/f5b314fcd3162126f1d4e7bf43578e7357331090/srv/http/assets/img/addons/thumbfran.gif
--------------------------------------------------------------------------------
/srv/http/assets/img/addons/thumbgpio.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rern/rAudio/f5b314fcd3162126f1d4e7bf43578e7357331090/srv/http/assets/img/addons/thumbgpio.gif
--------------------------------------------------------------------------------
/srv/http/assets/img/addons/thumbplst.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rern/rAudio/f5b314fcd3162126f1d4e7bf43578e7357331090/srv/http/assets/img/addons/thumbplst.jpg
--------------------------------------------------------------------------------
/srv/http/assets/img/addons/thumbrank.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rern/rAudio/f5b314fcd3162126f1d4e7bf43578e7357331090/srv/http/assets/img/addons/thumbrank.png
--------------------------------------------------------------------------------
/srv/http/assets/img/addons/thumbwebr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rern/rAudio/f5b314fcd3162126f1d4e7bf43578e7357331090/srv/http/assets/img/addons/thumbwebr.png
--------------------------------------------------------------------------------
/srv/http/assets/img/coverart.svg:
--------------------------------------------------------------------------------
1 |
44 |
--------------------------------------------------------------------------------
/srv/http/assets/img/i2cbackpack.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rern/rAudio/f5b314fcd3162126f1d4e7bf43578e7357331090/srv/http/assets/img/i2cbackpack.jpg
--------------------------------------------------------------------------------
/srv/http/assets/img/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rern/rAudio/f5b314fcd3162126f1d4e7bf43578e7357331090/srv/http/assets/img/icon.png
--------------------------------------------------------------------------------
/srv/http/assets/img/icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/srv/http/assets/img/lcd.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rern/rAudio/f5b314fcd3162126f1d4e7bf43578e7357331090/srv/http/assets/img/lcd.jpg
--------------------------------------------------------------------------------
/srv/http/assets/img/lcdchar.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rern/rAudio/f5b314fcd3162126f1d4e7bf43578e7357331090/srv/http/assets/img/lcdchar.jpg
--------------------------------------------------------------------------------
/srv/http/assets/img/mpdoled.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rern/rAudio/f5b314fcd3162126f1d4e7bf43578e7357331090/srv/http/assets/img/mpdoled.jpg
--------------------------------------------------------------------------------
/srv/http/assets/img/powerbutton.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rern/rAudio/f5b314fcd3162126f1d4e7bf43578e7357331090/srv/http/assets/img/powerbutton.jpg
--------------------------------------------------------------------------------
/srv/http/assets/img/relays.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rern/rAudio/f5b314fcd3162126f1d4e7bf43578e7357331090/srv/http/assets/img/relays.jpg
--------------------------------------------------------------------------------
/srv/http/assets/img/rotaryencoder.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rern/rAudio/f5b314fcd3162126f1d4e7bf43578e7357331090/srv/http/assets/img/rotaryencoder.jpg
--------------------------------------------------------------------------------
/srv/http/assets/img/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rern/rAudio/f5b314fcd3162126f1d4e7bf43578e7357331090/srv/http/assets/img/splash.png
--------------------------------------------------------------------------------
/srv/http/assets/img/vu.svg:
--------------------------------------------------------------------------------
1 |
94 |
--------------------------------------------------------------------------------
/srv/http/assets/img/vuled.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rern/rAudio/f5b314fcd3162126f1d4e7bf43578e7357331090/srv/http/assets/img/vuled.jpg
--------------------------------------------------------------------------------
/srv/http/assets/js/addons.js:
--------------------------------------------------------------------------------
1 | var KEYS = [ 'installurl', 'postmessage', 'title', 'uninstall', 'version' ];
2 |
3 | $( '.helphead' ).remove();
4 | if ( V.localhost ) $( 'a' ).removeAttr( 'href' );
5 | $( '.container' ).on( 'click', '.revision', function() {
6 | $this = $( this );
7 | $revisiontext = $this.parent().next();
8 | var hidden = $revisiontext.hasClass( 'hide' );
9 | $revisiontext.toggleClass( 'hide', ! hidden );
10 | $this.toggleClass( 'active' );
11 | } ).on( 'click', '#list li', function() {
12 | alias = $( this ).data( 'alias' );
13 | $( 'html, body' ).scrollTop( $( '#'+ alias ).offset().top - 50 );
14 | } ).on( 'click', '.infobtn', function() {
15 | $this = $( this );
16 | if ( $this.hasClass( 'disabled' ) && ! $this.hasClass( 'uninstall' ) ) {
17 | if ( ! S.status.online ) {
18 | INFO( {
19 | icon : 'addons'
20 | , title : 'Addons'
21 | , message : 'Internet connection is offline.'
22 | } );
23 | }
24 | return
25 | }
26 |
27 | addonData( $this );
28 | if ( 'option' in V.addon ) {
29 | optionGet();
30 | } else {
31 | INFO( {
32 | icon : 'addons'
33 | , title : V.addon.title
34 | , message : V.addon.version ? V.label +' to
$( pacman -Q $PKG )
"
13 | readarray -t lines <<< $( grep -Ev '^#|=$|^$' $1 | awk NF )
14 | [[ ! $lines ]] && echo $config && return
15 |
16 | config+=$'\n'"'.*'' ]] && continue # skip: no value / double codes
26 |
27 | ##### code - hex matched
28 | hex=$( sed -E 's|.*code>(.*) [next line]
30 | case $hex in
31 | 61736172|61736161 ) code=Artist && continue;; # asar|asaa
32 | 6d696e6d ) code=Title && continue;; # minm
33 | 6173616c ) code=Album && continue;; # asal
34 | 50494354 ) code=coverart && continue;; # PICT
35 | 70726772 ) code=progress && continue;; # prgr
36 | 63617073 ) code=state && continue;; # caps
37 | esac
38 | fi
39 |
40 | # no line with code found yet > [next line]
41 | [[ ! $code ]] && continue
42 |
43 | ##### value - base64 decode
44 | base64=$( tr -d '\000' <<< ${line/<*} ) # remove tags and null bytes
45 | # null or not base64 string - reset code= > [next line]
46 | if [[ ! $base64 || ! $base64 =~ ^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$ ]]; then
47 | code=
48 | continue
49 | fi
50 |
51 | if [[ $code == coverart ]]; then
52 | base64 -d <<< $base64 > $dirairplay/coverart.jpg
53 | pushData airplay '{ "coverart": "/data/shm/airplay/coverart.jpg" }'
54 | elif [[ $code == state ]]; then
55 | if [[ $base64 == 'AQ==' ]]; then # 1
56 | play
57 | elif [[ $base64 == 'Ag==' ]]; then # 2
58 | pause
59 | fi
60 | else
61 | data=$( base64 -d <<< $base64 2> /dev/null )
62 | if [[ $code == progress ]]; then # format: start/elapsed/end @44100/s
63 | play
64 | progress=( ${data//\// } )
65 | start=${progress[0]}
66 | current=${progress[1]}
67 | end=${progress[2]}
68 | elapsedms=$( awk 'BEGIN { printf "%.0f", '$(( current - start ))/44.1' }' )
69 | elapsed=$(( ( elapsedms + 500 ) / 1000 ))
70 | Time=$(( ( end - start + 22050 ) / 44100 ))
71 | pushData airplay '{ "elapsed": '$elapsed', "Time": '$Time' }'
72 | timestamp=$( date +%s%3N )
73 | starttime=$(( timestamp - elapsedms ))
74 | echo $elapsed > $dirairplay/elapsed
75 | echo $starttime > $dirairplay/start
76 | echo $timestamp > $dirairplay/timestamp
77 | echo $Time > $dirairplay/Time
78 | $dirbash/status-push.sh
79 | else
80 | echo $data > $dirairplay/$code
81 | pushData airplay '{ "'$code'": "'$( quoteEscape $data )'" }'
82 | fi
83 | fi
84 | code= # reset after $code + $data were set
85 | done
86 |
87 | [[ ! -e $dirairplay/pause ]] && pause # fix - if no state emits (script always exits)
88 |
--------------------------------------------------------------------------------
/srv/http/bash/smbdfree.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # for /etc/samba/smb.conf
4 | df | grep /mnt/MPD/USB | sort | head -1 | awk '{print $2" "$4}'
5 |
--------------------------------------------------------------------------------
/srv/http/bash/snapclient.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # as server - features.sh > this:
4 | # - force clients stop on disabled
5 | # as client - main.js > this:
6 | # - connect
7 | # - disconnect
8 | # as client + server - cmd.sh
9 | # - play > connect
10 | # - stop > disconnect
11 |
12 |
13 | . /srv/http/bash/common.sh
14 |
15 | if [[ $1 == stop ]]; then
16 | systemctl stop snapclient
17 | $dirbash/cmd.sh playerstop
18 | rm -f $dirshm/snapserverip
19 | else
20 | notify snapcast SnapServer "Connect $1 ..."
21 | echo $1 > $dirshm/snapserverip
22 | echo snapcast > $dirshm/player
23 | systemctl start snapclient
24 | fi
25 | $dirbash/status-push.sh
26 |
--------------------------------------------------------------------------------
/srv/http/bash/spotifyd.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # spotifyd.conf > this:
4 | # - spotifyd 'onevent' hook
5 | # env var: ($PLAYER_EVENT still not consistent - used for detect emitted events only)
6 | # $PLAYER_EVENT: load/preload/change/start/play/pause/volumeset
7 | # $TRACK_ID
8 | # $PLAY_REQUEST_ID
9 | # $POSITION_MS
10 | # $DURATION_MS
11 | # $VOLUME
12 |
13 | . /srv/http/bash/common.sh
14 |
15 | ##### start
16 | if ! playerActive spotify; then
17 | echo spotify > $dirshm/player
18 | $dirbash/cmd.sh playerstart
19 | exit
20 | # --------------------------------------------------------------------
21 | fi
22 | [[ $PLAYER_EVENT == volumeset ]] && volumeGet push
23 |
24 | dirspotify=$dirshm/spotify
25 | for key in elapsed expire start state status token; do # var fileKEY=$dirspotify/KEY
26 | printf -v file$key '%s' $dirspotify/$key
27 | done
28 | # token
29 | if [[ -e $fileexpire && $( < $fileexpire ) > $( date +%s ) ]]; then
30 | token=$( < $filetoken )
31 | else
32 | . $dirsystem/spotifykey # base64client, refreshtoken
33 | token=$( curl -s -X POST https://accounts.spotify.com/api/token \
34 | -H "Authorization: Basic $base64client" \
35 | -d grant_type=refresh_token \
36 | -d refresh_token=$refreshtoken \
37 | | grep access_token \
38 | | cut -d'"' -f4 )
39 | if [[ ! $token ]]; then
40 | notify spotify Spotify 'Access token renewal failed.'
41 | exit
42 | # --------------------------------------------------------------------
43 | fi
44 | echo $token > $filetoken
45 | echo $(( $( date +%s ) + 3550 )) > $fileexpire # 10s before 3600s
46 | fi
47 | # data
48 | readarray -t status <<< $( curl -s -X GET https://api.spotify.com/v1/me/player/currently-playing \
49 | -H "Authorization: Bearer $token" \
50 | | jq '.item.album.name,
51 | .item.artists[0].name,
52 | .item.album.images[0].url,
53 | .is_playing,
54 | .item.duration_ms,
55 | .item.name,
56 | .progress_ms,
57 | .timestamp' ) # not -r: 1-to keep escaped characters 2-already quoted
58 | [[ ${status[3]} == true ]] && state=play || state=pause
59 | Time=$(( ( ${status[4]} + 500 ) / 1000 ))
60 | cat << EOF > $filestatus
61 | , "Album" : ${status[0]}
62 | , "Artist" : ${status[1]}
63 | , "coverart" : ${status[2]}
64 | , "sampling" : "48 kHz 320 kbit/s • Spotify"
65 | , "state" : "$state"
66 | , "Time" : $Time
67 | , "Title" : ${status[5]}
68 | EOF
69 | progress=${status[6]}
70 | timestamp=${status[7]}
71 | diff=$(( $( date +%s%3N ) - timestamp ))
72 | elapsed=$(( ( progress + 500 ) / 1000 ))
73 | start=$(( ( timestamp + diff - progress + 500 ) / 1000 ))
74 | cat << EOF > $filestate
75 | elapsed=$elapsed
76 | start=$start
77 | state=$state
78 | Time=$Time
79 | EOF
80 |
81 | $dirbash/status-push.sh
82 |
--------------------------------------------------------------------------------
/srv/http/bash/startup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | . /srv/http/bash/common.sh
4 |
5 | wlandev=$( $dirsettings/networks.sh wlandevice )
6 |
7 | # pre-configure >>>-----------------------------------------------------------
8 | if [[ -e /boot/expand ]]; then # run once
9 | id0=$( < /etc/machine-id )
10 | rm /etc/machine-id
11 | systemd-machine-id-setup
12 | id1=$( < /etc/machine-id )
13 | mv /var/log/journal/{$id0,$id1}
14 | rm /boot/expand
15 | partition=$( mount | grep ' on / ' | cut -d' ' -f1 )
16 | [[ ${partition:0:7} == /dev/sd ]] && dev=${partition:0:-1} || dev=${partition:0:-2}
17 | if (( $( sfdisk -F $dev | awk 'NR==1{print $6}' ) != 0 )); then
18 | echo -e "d\n\nn\n\n\n\n\nw" | fdisk $dev &>/dev/null
19 | partprobe $dev
20 | resize2fs $partition
21 | fi
22 | revision=$( grep ^Revision /proc/cpuinfo )
23 | if [[ ${revision: -3:2} == 12 ]]; then # zero 2
24 | systemctl enable getty@tty1
25 | systemctl disable --now bootsplash localbrowser
26 | sed -i 's/tty3 .*/tty1/' /boot/cmdline.txt
27 | mv /usr/bin/firefox{,.backup}
28 | fi
29 | fi
30 |
31 | backupfile=$( ls /boot/*.gz 2> /dev/null | head -1 )
32 | if [[ -e $backupfile ]]; then
33 | mv "$backupfile" $dirshm/backup.gz
34 | $dirsettings/system-datarestore.sh
35 | fi
36 |
37 | if [[ $wlandev ]]; then
38 | if [[ -e /boot/wifi ]]; then
39 | ssid=$( getVar ESSID /boot/wifi )
40 | sed -E -e '/^#|^\s*$/ d
41 | ' -e "s/\r//; s/^(Interface=).*/\1$wlandev/
42 | " /boot/wifi > "/etc/netctl/$ssid"
43 | rm -f /boot/{accesspoint,wifi} $dirsystem/ap
44 | $dirsettings/networks.sh "profileconnect
45 | $ssid
46 | CMD ESSID"
47 | elif [[ -e /boot/accesspoint ]]; then
48 | mv -f /boot/accesspoint $dirsystem/ap
49 | fi
50 | fi
51 | # pre-configure <<<-----------------------------------------------------------
52 |
53 | logoLcdOled
54 |
55 | [[ -e $dirsystem/soundprofile ]] && $dirsettings/system.sh soundprofileset
56 |
57 | dirbacklight=/sys/class/backlight/rpi_backlight
58 | if [[ -d $dirbacklight ]]; then
59 | chmod 666 $dirbacklight/{brightness,bl_power}
60 | [[ -e $dirsystem/brightness ]] && cat $dirsystem/brightness > $dirbacklight/brightness
61 | fi
62 |
63 | mkdir -p $dirshm/{airplay,embedded,spotify,local,online,sampling,webradio}
64 | chmod -R 777 $dirshm
65 | chown -R http:http $dirshm
66 | echo mpd > $dirshm/player
67 |
68 | lsmod | grep -q -m1 brcmfmac && touch $dirshm/onboardwlan # initial status
69 |
70 | netctllist=$( netctl list )
71 | if [[ -e $dirsystem/ap ]]; then
72 | ap=1
73 | else # if no connections, start accesspoint
74 | [[ $netctllist ]] && sec=30 || sec=5 # wlan || lan
75 | for (( i=0; i < $sec; i++ )); do # wait for connection
76 | ipaddress=$( ipAddress )
77 | [[ $ipaddress ]] && break || sleep 1
78 | done
79 | if [[ $ipaddress ]]; then
80 | readarray -t lines <<< $( grep $dirnas /etc/fstab )
81 | if [[ $lines ]]; then
82 | for line in "${lines[@]}"; do
83 | mp=$( awk '{print $2}' <<< $line )
84 | for i in {1..10}; do
85 | mount "${mp//\\040/ }" && break || sleep 2
86 | done
87 | ! mountpoint -q "$mp" && notify networks NAS "Mount failed: $mp " 10000
88 | done
89 | fi
90 | if systemctl -q is-active nfs-server; then
91 | if [[ -s $filesharedip ]]; then
92 | sharedip=$( < $filesharedip )
93 | for ip in $sharedip; do
94 | notify -ip $ip networks 'Server rAudio' Online
95 | done
96 | fi
97 | appendSortUnique $ipaddress $filesharedip
98 | fi
99 | if [[ $partition ]] && ipOnline 8.8.8.8; then
100 | $dirsettings/system.sh 'timezone
101 | auto
102 | CMD TIMEZONE'
103 | fi
104 | else
105 | if [[ $wlandev && ! $ap ]]; then
106 | if [[ $netctllist ]]; then
107 | [[ ! -e $dirsystem/wlannoap ]] && ap=1
108 | else
109 | ap=1
110 | fi
111 | [[ $ap ]] && touch $dirshm/apstartup
112 | fi
113 | fi
114 | fi
115 |
116 | [[ $ap ]] && $dirsettings/features.sh iwctlap
117 |
118 | if [[ -e $dirsystem/btreceiver ]]; then
119 | mac=$( < $dirsystem/btreceiver )
120 | rm $dirsystem/btreceiver
121 | $dirsettings/networks-bluetooth.sh connect $mac
122 | fi
123 |
124 | if [[ -e $dirshm/btreceiver && -e $dirsystem/camilladsp ]]; then
125 | $dirsettings/camilla-bluetooth.sh btreceiver
126 | else # start mpd.service if not started by networks-bluetooth.sh
127 | $dirsettings/player-conf.sh
128 | fi
129 |
130 | if [[ -e $dirsystem/volumelimit ]]; then
131 | volumeLimit startup
132 | fi
133 |
134 | # after all sources connected ........................................................
135 | if [[ ! -e $dirmpd/mpd.db || -e $dirmpd/updating ]]; then
136 | $dirbash/cmd.sh mpcupdate
137 | elif [[ -e $dirmpd/listing ]]; then
138 | $dirbash/cmd-list.sh &> /dev/null &
139 | fi
140 | # usb wlan || no wlan || not ap + not connected
141 | if (( $( rfkill | grep -c wlan ) > 1 )) || [[ ! $netctllist && ! $ap ]]; then
142 | rmmod brcmfmac_wcc brcmfmac &> /dev/null
143 | fi
144 |
145 | touch $dirshm/startup
146 |
147 | if grep -qs startup=true $dirsystem/autoplay.conf; then
148 | mpcPlayback play
149 | fi
150 | if [[ -e /boot/startup.sh ]]; then
151 | /boot/startup.sh
152 | fi
153 |
154 | udevil clean
155 | lsblk -no path,vendor,model | grep -v ' $' > $dirshm/lsblkusb
156 | if [[ ! -e $diraddons/update ]]; then
157 | data=$( curl -sfL https://github.com/rern/rAudio-addons/raw/main/addonslist.json )
158 | if [[ $? == 0 ]]; then
159 | echo "$data" > $diraddons/addonslist.json
160 | rversion=$( sed -n '/"r1"/,/"version"/ {/version/!d; s/"//g; s/.*: //; p}' <<< $data )
161 | if [[ $rversion != $( < $diraddons/r1 ) ]]; then
162 | touch $diraddons/update
163 | pushData option '{ "addons": true }'
164 | fi
165 | fi
166 | fi
167 |
--------------------------------------------------------------------------------
/srv/http/bash/startx.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | /srv/http/bash/settings/features.sh startx
4 |
--------------------------------------------------------------------------------
/srv/http/bash/status-bluetooth.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # RPi as renderer - status.sh > this:
4 | # - retreive current status from dbus
5 |
6 | . /srv/http/bash/common.sh
7 |
8 | dest=$( < $dirshm/bluetoothdest )
9 | data=$( dbus-send \
10 | --system \
11 | --type=method_call \
12 | --print-reply \
13 | --dest=org.bluez $dest \
14 | org.freedesktop.DBus.Properties.GetAll \
15 | string:org.bluez.MediaPlayer1 \
16 | | grep -E -A1 'string.*Status|string.*Title|string.*Album|string.*Artist|string.*Duration|string.*Position' \
17 | | sed -E 's/^\s*string "|^\s*variant\s*string "|^\s*variant\s*uint32 //; s/"$//' \
18 | | tr '\n' ^ \
19 | | sed 's/\^--\^/\n/g; s/\^$//' )
20 | Artist=$( grep ^Artist <<< $data | cut -d^ -f2 )
21 | Title=$( grep ^Title <<< $data | cut -d^ -f2 )
22 | Album=$( grep ^Album <<< $data | cut -d^ -f2 )
23 | Position=$( grep ^Position <<< $data | cut -d^ -f2 )
24 | Duration=$( grep ^Duration <<< $data | cut -d^ -f2 )
25 | Status=$( grep ^Status <<< $data | cut -d^ -f2 )
26 | case $Status in
27 | paused ) state=pause;;
28 | playing ) state=play;;
29 | stopped ) state=stop;;
30 | esac
31 |
32 | name=$( alphaNumeric $Artist$Album )
33 | onlinefile=$( ls $dirshm/online/$name.* 2> /dev/null ) # jpg / png
34 | if [[ -e $onlinefile ]]; then
35 | coverart="${onlinefile:9}"
36 | else
37 | $dirbash/status-coverartonline.sh "cmd
38 | $Artist
39 | $Album
40 | CMD ARTIST ALBUM" &> /dev/null &
41 | fi
42 | [[ ! $Position ]] && elapsed=false || elapsed=$( calc 0 $Position/1000 )
43 | [[ ! $Duration ]] && Time=false || Time=$( calc 0 $Duration/1000 )
44 | timestamp=$( date +%s%3N )
45 |
46 | data='
47 | , "Artist" : "'$Artist'"
48 | , "Title" : "'$Title'"
49 | , "Album" : "'$Album'"
50 | , "coverart" : "'$coverart'"
51 | , "elapsed" : '$elapsed'
52 | , "sampling" : "Bluetooth"
53 | , "state" : "'$state'"
54 | , "Time" : '$Time'
55 | , "timestamp" : '$timestamp
56 |
57 | echo "$data"
58 |
--------------------------------------------------------------------------------
/srv/http/bash/status-coverart.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | . /srv/http/bash/common.sh
4 |
5 | args2var "$1"
6 |
7 | if playerActive upnp; then
8 | upnp=1
9 | name=$( alphaNumeric $ARTIST$ALBUM )
10 | localfile=$dirshm/local/${name,,}
11 | else
12 | filename=$( basename "$FILE" )
13 | path="/mnt/MPD/$FILE"
14 | [[ -f "$path" ]] && path=$( dirname "$path" )
15 | localfile=$dirshm/local/$( alphaNumeric $path )
16 | fi
17 | # found cover file
18 | if [[ -f $localfile ]]; then
19 | cat $localfile
20 | exit
21 | # --------------------------------------------------------------------
22 | fi
23 | # found embedded
24 | embeddedname=$( alphaNumeric $filename ).jpg
25 | embeddedfile=$dirshm/embedded/$embeddedname
26 | [[ -f "$embeddedfile" ]] && echo ${embeddedfile:9} && exit
27 | # --------------------------------------------------------------------
28 | # found online
29 | covername=$( alphaNumeric $ARTIST$ALBUM )
30 | onlinefile=$( ls -X $dirshm/online/${covername,,}.{jpg,png} 2> /dev/null | head -1 )
31 | [[ -f $onlinefile ]] && echo ${onlinefile:9} && exit
32 | # --------------------------------------------------------------------
33 | ##### cover file
34 | [[ $upnp ]] && coverfile=$( $dirbash/status-coverartupnp.py ) || coverfile=$( coverFileGet "$path" )
35 | if [[ $coverfile ]]; then
36 | echo "$coverfile" | tee $localfile
37 | $dirbash/cmd.sh coverfileslimit
38 | exit
39 | # --------------------------------------------------------------------
40 | fi
41 | ##### embedded
42 | kid3-cli -c "cd \"$path\"" \
43 | -c "select \"$filename\"" \
44 | -c "get picture:$embeddedfile" &> /dev/null # suppress '1 space' stdout
45 | if [[ -f $embeddedfile ]]; then
46 | echo ${embeddedfile:9}
47 | exit
48 | # --------------------------------------------------------------------
49 | fi
50 | [[ ! $ARTIST || ! $ALBUM ]] && exit
51 | # --------------------------------------------------------------------
52 | ##### online
53 | $dirbash/status-coverartonline.sh "cmd
54 | $ARTIST
55 | $ALBUM
56 | CMD ARTIST ALBUM" &> /dev/null &
57 |
--------------------------------------------------------------------------------
/srv/http/bash/status-coverartonline.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | . /srv/http/bash/common.sh
4 |
5 | args2var "$1"
6 |
7 | ### 1 - lastfm ##################################################
8 | if [[ $MODE != webradio || -e $dirshm/radio ]]; then # not webradio || radioparadise / radiofrance
9 | param="album=${ALBUM//&/ and }"
10 | method='method=album.getInfo'
11 | else
12 | artist_title=1
13 | param="track=${ALBUM//&/ and }" # $ALBUM = track
14 | method='method=track.getInfo'
15 | fi
16 | apikey=$( grep -m1 apikeylastfm /srv/http/assets/js/main.js | cut -d"'" -f2 )
17 | data=$( curl -sfG -m 5 \
18 | --data-urlencode "artist=$ARTIST" \
19 | --data-urlencode "$param" \
20 | --data "$method" \
21 | --data "api_key=$apikey" \
22 | --data "format=json" \
23 | http://ws.audioscrobbler.com/2.0 )
24 | if [[ $? == 0 && $data ]]; then
25 | [[ $artist_title ]] && album=$( jq -r '.track.album // empty' <<< $data ) || album=$( jq -r '.album // empty' <<< $data )
26 | [[ $album ]] && image=$( jq -r '.image // empty' <<< $album )
27 | if [[ $image ]]; then
28 | extralarge=$( jq -r '.[3]."#text" // empty' <<< $image )
29 | [[ $extralarge ]] && url=$( sed 's|/300x300/|/_/|' <<< $extralarge ) # get larger size than 300x300
30 | fi
31 | fi
32 | ### 2 - coverartarchive.org #####################################
33 | if [[ ! $url ]]; then
34 | mbid=$( jq -r '.mbid // empty' <<< $album )
35 | if [[ $mbid ]]; then
36 | imgdata=$( curl -sfL -m 10 https://coverartarchive.org/release/$mbid )
37 | [[ $? != 0 ]] && exit
38 | # --------------------------------------------------------------------
39 | url=$( jq -r '.images[0].image // empty' <<< $imgdata )
40 | fi
41 | fi
42 | if [[ $DEBUG ]]; then
43 | [[ ! $url ]] && url="(Not found: $ARTIST - $ALBUM)"
44 | echo coverart: $url
45 | exit
46 | # --------------------------------------------------------------------
47 | fi
48 | [[ ! $url ]] && exit
49 | # --------------------------------------------------------------------
50 | ext=${url/*.}
51 | if [[ $DISCID ]]; then
52 | cover=$diraudiocd/$DISCID.$ext
53 | else
54 | [[ $MODE ]] && prefix=$MODE || prefix=online
55 | name=$( alphaNumeric $ARTIST$ALBUM )
56 | cover=$dirshm/$prefix/${name,,}.$ext
57 | fi
58 | curl -sfL $url -o $cover
59 | [[ ${cover:0:4} == /srv ]] && cover=${cover:9}
60 | pushData cover '{ "cover": "'$cover'" }'
61 |
--------------------------------------------------------------------------------
/srv/http/bash/status-coverartupnp.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import socket
4 | import upnpp
5 |
6 | device = socket.gethostname() +'-UPnP/AV'
7 | srv = upnpp.findTypedService( device, 'avtransport', True ) # AVTransport service
8 | if srv:
9 | retdata = upnpp.runaction( srv, 'GetMediaInfo', ['0'] )
10 | metadata = retdata[ 'CurrentURIMetaData' ]
11 | if metadata:
12 | dirc = upnpp.UPnPDirContent()
13 | dirc.parse( metadata )
14 | if dirc.m_items.size():
15 | mprops = dirc.m_items[0].m_props
16 | if 'upnp:albumArtURI' in mprops: print( mprops['upnp:albumArtURI'] )
17 |
--------------------------------------------------------------------------------
/srv/http/bash/status-dab.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | . /srv/http/bash/common.sh
4 |
5 | . $dirshm/radio
6 | album='DAB Radio'
7 | artist=$( head -1 $file )
8 | filelabel=$dirshm/webradio/DABlabel.txt
9 | filecover=$dirshm/webradio/DABslide.jpg
10 | filetitle=$dirshm/webradio/DABtitle
11 |
12 | while true; do
13 | # title
14 | label=$( awk NF $filelabel )
15 | if [[ ! $label ]] || cmp -s $filelabel $filetitle; then
16 | [[ ! $label ]] && pushData mpdradio '{ "Title": "" }'
17 | sleep 10
18 | continue
19 | fi
20 |
21 | cp -f $filelabel $filetitle
22 | title=$( < $filetitle )
23 | elapsed=$( mpcElapsed )
24 | data='
25 | "Album" : "'$album'"
26 | , "Artist" : "'$artist'"
27 | , "elapsed" : '$elapsed'
28 | , "Title" : "'$title'"'
29 | pushData mpdradio "{ $data }"
30 | # coverart
31 | coverart=
32 | if [[ $( awk NF $filecover ) ]]; then
33 | name=$( alphaNumeric $title )
34 | coverfile=/srv/http/data/shm/webradio/$name.jpg
35 | if ! cmp -s $filecover $coverfile; then # change later than title or multiple covers
36 | cp -f $filecover $coverfile
37 | cover="${coverfile:9}"
38 | sed -i -E "s/^(coverart=).*/\1$cover/" $dirshm/status
39 | fi
40 | fi
41 | pushData cover '{ "cover": "'$cover'" }'
42 | radioStatusFile
43 | sleep 10
44 | done
45 |
--------------------------------------------------------------------------------
/srv/http/bash/status-push.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | [[ -e /dev/shm/usbdac_rules ]] && exit # debounce usbdac.rules
4 | # --------------------------------------------------------------------
5 | . /srv/http/bash/common.sh
6 |
7 | isChanged() {
8 | for k in $@; do
9 | prev=$( sed -n -E '/^'$k'/ {s/.*="*|"*$//g; p}' <<< $statusprev )
10 | [[ $prev != ${!k} ]] && return 0
11 | done
12 | }
13 |
14 | killProcess statuspush
15 | echo $$ > $dirshm/pidstatuspush
16 |
17 | if [[ $1 == statusradio ]]; then # from status-radio.sh radioStatusFile
18 | state=play
19 | statusradio=1
20 | else
21 | status=$( $dirbash/status.sh | jq )
22 | for k in Artist Album Composer Conductor elapsed file player station state Time timestamp Title volume webradio; do
23 | filter+='|^ "'$k'"'
24 | done
25 | statuslines=$( grep -E "${filter:1}" <<< $status )
26 | statusnew=$( sed -E 's/^ *"|,$//g; s/" *: */=/' <<< $statuslines | tee $dirshm/statusnew )
27 | statusprev=$( cat $dirshm/status 2> /dev/null )
28 | . <( echo "$statusnew" )
29 | isChanged Artist Title Album && trackchanged=1
30 | if [[ $webradio == true ]]; then
31 | [[ ! $trackchanged && $state == play ]] && exit
32 | # --------------------------------------------------------------------
33 | else
34 | isChanged state elapsed && statuschanged=1
35 | [[ ! $trackchanged && ! $statuschanged ]] && exit
36 | # --------------------------------------------------------------------
37 | fi
38 | ########
39 | pushData mpdplayer "$status"
40 | if [[ -e $dirsystem/scrobble ]]; then
41 | cp -f $dirshm/status{,prev}
42 | timestampnew=$( grep ^timestamp <<< $statusnew | cut -d= -f2 )
43 | fi
44 | mv -f $dirshm/status{new,}
45 | fi
46 |
47 | if systemctl -q is-active localbrowser && grep -q onwhileplay=true $dirsystem/localbrowser.conf; then
48 | export DISPLAY=:0
49 | [[ $( mpcState ) == play ]] && xset -dpms || xset +dpms
50 | fi
51 |
52 | clientip=$( snapclientIP )
53 | if [[ $clientip ]]; then
54 | status=$( $dirbash/status.sh snapclient )
55 | status='{ '${status/,}' }'
56 | for ip in $clientip; do
57 | pushWebsocket $ip mpdplayer $status
58 | done
59 | fi
60 |
61 | if [[ -e $dirsystem/lcdchar ]]; then
62 | [[ ! $statusradio ]] && jq <<< "{ ${statuslines%,} }" > $dirshm/status.json # remove trailing ,
63 | systemctl restart lcdchar
64 | fi
65 |
66 | [[ $state == play ]] && start_stop=start || start_stop=stop
67 | [[ -e $dirsystem/mpdoled ]] && systemctl $start_stop mpd_oled
68 | [[ -e $dirsystem/vuled || -e $dirsystem/vumeter ]] && systemctl $start_stop cava
69 | [[ -e $dirsystem/vumeter && $state != play ]] && pushData vumeter '{ "val": 0 }'
70 |
71 | [[ -e $dirsystem/librandom && $webradio == false ]] && $dirbash/cmd.sh pladdrandom &
72 |
73 | [[ ! -e $dirsystem/scrobble ]] && exit
74 | # --------------------------------------------------------------------
75 | [[ ! $trackchanged && ! -e $dirshm/elapsed ]] && exit # track changed || prev/next/stop
76 | # --------------------------------------------------------------------
77 | . $dirshm/statusprev
78 | [[ $state == stop || $webradio == true || ! $Artist || ! $Title || $Time -lt 30 ]] && exit
79 | # --------------------------------------------------------------------
80 | if [[ $player != mpd ]]; then
81 | ! grep -q $player=true $dirsystem/scrobble.conf && exit
82 | # --------------------------------------------------------------------
83 | if [[ $state =~ ^(play|pause)$ ]]; then # renderers prev/next
84 | elapsed=$(( ( timestampnew - timestamp ) / 1000 ))
85 | (( $elapsed < $Time )) && echo $elapsed > $dirshm/elapsed
86 | fi
87 | fi
88 | if [[ -e $dirshm/elapsed ]];then
89 | elapsed=$( < $dirshm/elapsed )
90 | rm $dirshm/elapsed
91 | (( $elapsed < 240 && $elapsed < $(( Time / 2 )) )) && exit
92 | # --------------------------------------------------------------------
93 | fi
94 | $dirbash/scrobble.sh "cmd
95 | $Artist
96 | $Title
97 | CMD ARTIST TITLE"&> /dev/null &
98 |
--------------------------------------------------------------------------------
/srv/http/bash/status-radio.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | . /srv/http/bash/common.sh
4 |
5 | . $dirshm/radio
6 | id=$( basename ${file/-*} )
7 | [[ ${id:0:13} == francemusique ]] && id=${id:13}
8 | [[ ! $id ]] && id=francemusique
9 |
10 | case $id in
11 | flac ) id=0;;
12 | mellow ) id=1;;
13 | rock ) id=2;;
14 | global ) id=3;;
15 | fip ) id=7;; # FIP
16 | fipelectro ) id=74;; # Electro
17 | fipgroove ) id=66;; # Groove
18 | fiphiphop ) id=95;; # Hip-Hop
19 | fipjazz ) id=65;; # Jazz
20 | fipmetal ) id=77;; # Metal
21 | fipnouveautes ) id=70;; # Nouveautés
22 | fippop ) id=78;; # Pop
23 | fipreggae ) id=71;; # Reggae
24 | fiprock ) id=64;; # Rock
25 | fipworld ) id=69;; # Monde
26 |
27 | francemusique ) id=4;; # France Musique
28 | baroque ) id=408;; # La Baroque
29 | classiqueplus ) id=402;; # Classique Plus
30 | concertsradiofrance ) id=403;; # Concerts Radio France
31 | easyclassique ) id=401;; # Classique Easy
32 | labo ) id=407;; # Musique de Films
33 | lacontemporaine ) id=406;; # La Contemporaine
34 | lajazz ) id=405;; # La Jazz
35 | ocoramonde ) id=404;; # Ocora Musiques du Monde
36 | opera ) id=409;; # Opéra
37 | esac
38 |
39 | i=0
40 | metadataGet() {
41 | if [[ $id < 4 ]]; then
42 | radioparadise=1
43 | icon=radioparadise
44 | json=$( curl -sGk -m 5 --data-urlencode "chan=$id" https://api.radioparadise.com/api/now_playing )
45 | else
46 | icon=radiofrance
47 | if [[ $id == 95 ]]; then # openapi: only needed by hiphop (no coverart for openapi)
48 | hiphop=1
49 | query='{ "query": "{ live( station: FIP_HIP_HOP ) { song { end track { title albumTitle mainArtists } } } }" }'
50 | json=$( curl -s 'https://openapi.radiofrance.fr/v1/graphql' \
51 | -H 'Accept-Encoding: gzip, deflate, br' \
52 | -H 'Content-Type: application/json' \
53 | -H 'Accept: application/json' \
54 | -H 'Connection: keep-alive' \
55 | -H 'DNT: 1' \
56 | -H 'Origin: https://openapi.radiofrance.fr' \
57 | -H "x-token: 0390600a-5407-4e86-b439-24e5d48427dc" \
58 | --compressed \
59 | --data-binary "$query" )
60 | else # api: current until switched to openapi ( except hophop)
61 | json=$( curl -sGk -m 5 https://api.radiofrance.fr/livemeta/pull/$id )
62 | fi
63 | fi
64 | if [[ ! $json || ${json:0:1} != '{' || $json == *,\"error\":* ]]; then
65 | (( i++ ))
66 | if [[ $i == 1 ]]; then
67 | notify "$icon blink" Metadata 'Retry ...'
68 | pushData mpdradio '{ "Artist": "", "Title": "", "Album": "" }'
69 | elif [[ $i == 10 ]]; then
70 | notify $icon Metadata 'Not available'
71 | systemctl stop radio
72 | exit
73 | # --------------------------------------------------------------------
74 | fi
75 | sleep 1
76 | metadataGet
77 | return
78 | fi
79 |
80 | if [[ $radioparadise ]]; then
81 | readarray -t metadata <<< $( jq -r .artist,.title,.album,.cover,.time <<< $json | sed 's/^null$//' )
82 | countdown=${metadata[4]} # countdown
83 | else
84 | if [[ $hiphop ]]; then
85 | song=$( jq -r '.data.live.song // empty' <<< $json )
86 | if [[ ! $song ]]; then
87 | sleep 5
88 | metadataGet
89 | return
90 | fi
91 |
92 | track=$( jq .track <<< $song )
93 | artists=$( jq -r '.mainArtists[] // empty' <<< $track )
94 | readarray -t metadata <<< "\
95 | ${artists//$'\n'/, }
96 | $( jq -r .title <<< $track )
97 | $( jq -r .albumTitle <<< $track )"
98 | end=$( jq -r .end <<< $song )
99 | else
100 | levels=$( jq '.levels[0] // empty' <<< $json )
101 | position=$( jq .position <<< $levels )
102 | item=$( jq .items[$position] <<< $levels )
103 | step=$( jq .steps[$item] <<< $json )
104 | readarray -t metadata <<< $( jq -r .authors,.title,.titreAlbum,.visual <<< $step | sed 's/^null$//' )
105 | end=$( jq -r .end <<< $step )
106 | fi
107 | now=$( date +%s )
108 | countdown=$(( end - now ))
109 | fi
110 | dataprev="$artist $title $album"
111 | artist=$( quoteEscape ${metadata[0]} )
112 | title=$( quoteEscape ${metadata[1]} )
113 | album=$( quoteEscape ${metadata[2]} )
114 | coverurl=${metadata[3]}
115 |
116 | if [[ ! $title || "$artist $title $album" == $dataprev ]]; then
117 | sleep 5
118 | metadataGet
119 | return
120 | fi
121 |
122 | [[ ! $artist ]] && artist=$( jq -r '.composers // empty' <<< $step )
123 | if [[ ! -e $dirsystem/vumeter ]]; then
124 | if [[ $coverurl ]]; then
125 | name=$( alphaNumeric $artist$title )
126 | ext=${coverurl/*.}
127 | coverfile=$dirshm/webradio/$name.$ext
128 | curl -s $coverurl -o $coverfile
129 | else
130 | name=$( alphaNumeric $artist$album )
131 | coverfile=$( ls -X $dirshm/webradio/${name,,}.{jpg,png} 2> /dev/null | head -1 )
132 | fi
133 | fi
134 | [[ -e $coverfile ]] && coverart=${coverfile:9} || coverart=
135 | data='
136 | "player" : "mpd"
137 | , "Album" : "'$album'"
138 | , "Artist" : "'$artist'"
139 | , "elapsed" : '$( mpcElapsed webradio )'
140 | , "pllength" : '$( mpc status %length% )'
141 | , "state" : "play"
142 | , "Title" : "'$title'"'
143 | if [[ $coverart ]]; then
144 | data+='
145 | , "coverart" : "'$coverart'"'
146 | else
147 | $dirbash/status-coverartonline.sh "cmd
148 | $artist
149 | $album
150 | webradio
151 | CMD ARTIST ALBUM MODE" &> /dev/null &
152 | fi
153 | pushData mpdradio "{ $data }"
154 | if [[ -e $dirsystem/lcdchar ]]; then
155 | data+='
156 | , "Time" : false
157 | , "timestamp" : '$( date +%s%3N )'
158 | , "webradio" : true'
159 | echo "{ $data }" > $dirshm/status.json
160 | fi
161 | [[ -e $dirsystem/scrobble ]] && cp -f $dirshm/status{,prev}
162 | radioStatusFile
163 | [[ $coverart ]] && $dirbash/cmd.sh coverfileslimit
164 | # next fetch
165 | [[ ! $countdown || $countdown -lt 0 ]] && countdown=0
166 | sleep $(( countdown + 5 )) # add 5s delay
167 | metadataGet
168 | }
169 |
170 | metadataGet
171 |
--------------------------------------------------------------------------------
/srv/http/bash/stoptimer.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | . /srv/http/bash/common.sh
4 |
5 | . $dirsystem/stoptimer.conf
6 |
7 | killProcess stoptimer
8 | echo $$ > $dirshm/pidstoptimer
9 |
10 | killProcess relaystimer
11 |
12 | sleep $(( min * 60 ))
13 |
14 | rm $dirshm/pidstoptimer
15 | . <( grep -E '^card|^mixer' $dirshm/output )
16 | volume=$( volumeGet )
17 |
18 | $dirbash/cmd.sh "volume
19 | $volume
20 | 0
21 | $mixer
22 | $card
23 | CMD CURRENT TARGET CONTROL CARD"
24 | $dirbash/cmd.sh playerstop
25 | $dirbash/cmd.sh "volumesetat
26 | 0
27 | $mixer
28 | $card
29 | CMD TARGET CONTROL CARD"
30 |
31 | if [[ $poweroff ]]; then
32 | $dirbash/power.sh
33 | elif [[ -e $dirshm/relayson ]]; then
34 | $dirbash/relays.sh off
35 | fi
36 |
--------------------------------------------------------------------------------
/srv/http/bash/tageditor.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | . /srv/http/bash/common.sh
4 |
5 | args2var "$1"
6 |
7 | path="/mnt/MPD/$FILE"
8 | argslast=${args[@]: -1} # CMD ALBUM ALBUMARTIST ... FILE - omit unchanged
9 | [[ -f $path ]] && istrack=1
10 |
11 | if [[ $FILE != *.cue ]]; then
12 | KEYS=( ${argslast:4:-5} ) # remove CMD and FILE
13 | for K in "${KEYS[@]}"; do
14 | k=${K,,}
15 | v=${!K}
16 | [[ $v == '*' ]] && continue
17 |
18 | [[ $v ]] && v=$( quoteEscape $v )
19 | [[ ! $istrack ]] && all='/*.*'
20 | kid3-cli -c "set $k \"$v\"" "$path"$all
21 | done
22 | [[ $istrack ]] && dirupdate=$( dirname "$FILE" ) || dirupdate=$FILE
23 | else
24 | if [[ $istrack ]]; then
25 | sed -i -E '/^\s+TRACK '$TRACK'/ {
26 | n; s/^(\s+TITLE).*/\1 "'$TITLE'"/
27 | n; s/^(\s+PERFORMER).*/\1 "'$ARTIST'"/
28 | }
29 | ' "$path"
30 | else
31 | [[ $ALBUM ]] && data="\
32 | TITLE $ALBUM"
33 | [[ $ALBUMARTIST ]] && data+="
34 | PERFORMER $ALBUMARTIST"
35 | for k in COMPOSER CONDUCTOR GENRE DATE; do
36 | data+="
37 | REM $k ${!k}"
38 | done
39 | data+="
40 | $( sed -E '/^TITLE|^PERFORMER|^REM/ d; s/^(\s+PERFORMER ).*/\1'$ARTIST'/' "$path" )"
41 | echo "$data" > "$path"
42 | fi
43 | dirupdate=$( dirname "$FILE" )
44 | fi
45 |
46 | $dirbash/cmd.sh "mpcupdate
47 | update
48 | $dirupdate
49 | CMD ACTION PATHMPD"
50 |
--------------------------------------------------------------------------------
/srv/http/bash/vu.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | . /srv/http/bash/common.sh
4 |
5 | pin_0=$( sed 's/$/=0/; s/ /=0\n/g' $dirsystem/vuled.conf )
6 | gpioset -t0 -c0 $pin_0
7 | [[ $1 == stop ]] && exit
8 | # --------------------------------------------------------------------
9 | [[ -e $dirsystem/vumeter ]] && vumeter=1 && j=0
10 | if [[ -e $dirsystem/vuled ]]; then
11 | vuled=1
12 | pL=$( wc -l <<< $pin_0 )
13 | on=( "$pin_0" )
14 | for (( i=1; i < $pL + 1; i++ )); do
15 | on+=( "$( sed '1,'$i' s/0$/1/' <<< $pin_0 )" )
16 | done
17 | fi
18 | cava -p /etc/cava.conf | while read vu; do
19 | v=${vu:0:-1}
20 | if [[ $vuled ]]; then
21 | if (( v > 0 )); then
22 | ir=$(( pL - 1 )) # i - roundup
23 | vr=$(( v + ir )) # v - roundup
24 | v=$(( vr / pL ))
25 | fi
26 | gpioset -t0 -c0 ${on[v]} # p1=0 p2=1 ... : 0-off, 1-on
27 | fi
28 | if [[ $vumeter ]]; then
29 | (( j++ ))
30 | if (( $j == 10 )); then # framerate throttle - push @10 signal
31 | pushData vumeter '{"val":'$v'}'
32 | j=0
33 | fi
34 | fi
35 | done
36 |
--------------------------------------------------------------------------------
/srv/http/bash/websocket.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import asyncio
4 | import json
5 | import subprocess
6 | import websockets
7 |
8 | CLIENTS = set()
9 | IP_CLIENT = dict()
10 |
11 | async def cmd( websocket ):
12 | async for args in websocket:
13 | jargs = json.loads( args )
14 | if 'channel' in jargs: # broadcast
15 | websockets.broadcast( CLIENTS, args ) # { "channel": "CAHNNEL", "data": { ... } }
16 | elif 'filesh' in jargs: # FILE.sh "a\nb\nc"
17 | filesh = '/srv/http/bash/'+ jargs[ 'filesh' ][ 0 ]
18 | jargs[ 'filesh' ][ 0 ] = filesh
19 | subprocess.Popen( jargs[ 'filesh' ] ) # { "filesh": [ "FILE.sh", "a\nb\nc..." ] }
20 | elif 'json' in jargs: # save to NAME.json and broadcast
21 | jargsjson = jargs[ 'json' ] # { "json": { ... }, "name": "NAME" }
22 | jargsname = jargs[ 'name' ]
23 | data = '{ "channel": "'+ jargsname +'", "data": '+ json.dumps( jargsjson ) +' }'
24 | websockets.broadcast( CLIENTS, data )
25 | pathfile = '/srv/http/data/system/'+ jargsname
26 | with open( pathfile +'.json', 'w' ) as f:
27 | json.dump( jargsjson, f, indent=2 )
28 | elif 'client' in jargs:
29 | ip = websocket.remote_address[ 0 ]
30 | if jargs[ 'client' ] == 'add': # { "client": "add" }
31 | CLIENTS.add( websocket )
32 | if ip in IP_CLIENT:
33 | CLIENTS.discard( IP_CLIENT[ ip ] )
34 | IP_CLIENT[ ip ] = websocket
35 | else: # { "client": "" }
36 | await websocket.send( str( IP_CLIENT ) )
37 | # refresh CLIENTS
38 | for IP in IP_CLIENT:
39 | if IP == ip: continue
40 |
41 | if subprocess.call( [ 'ping', '-c', '1', '-w','1', IP ] ) != 0:
42 | CLIENTS.discard( IP_CLIENT[ IP ] )
43 | IP_CLIENT.pop( IP, None )
44 | elif 'status' in jargs: # { "status": "snapclient" } - from status.sh
45 | status = subprocess.run( [ '/srv/http/bash/status.sh', jargs[ 'status' ] ], capture_output=True, text=True )
46 | status = status.stdout.replace( '\n', '\\n' )
47 | await websocket.send( status )
48 | elif 'ping' in jargs:
49 | await websocket.send( 'pong' )
50 |
51 | async def main():
52 | async with websockets.serve( cmd, '0.0.0.0', 8080, max_size=10485760, ping_interval=None, ping_timeout=None ):
53 | await asyncio.Future() # run forever
54 |
55 | asyncio.run( main() )
56 |
--------------------------------------------------------------------------------
/srv/http/cmd.php:
--------------------------------------------------------------------------------
1 | cmd ?? $argv[ 1 ]; // $argv - sort : from cmd-list.sh
4 | $sudo = '/usr/bin/sudo ';
5 | $dirbash = $sudo.'/srv/http/bash/';
6 | $dirsettings = $dirbash.'settings/';
7 | $dirdata = '/srv/http/data/';
8 | $dirshm = $dirdata.'shm/';
9 |
10 | switch( $CMD ) {
11 |
12 | case 'bash':
13 | $args = $post->args ?? '';
14 | if ( is_array( $args ) ) $args = escape( implode( "\n", $args ) );
15 | $command = $dirbash.$post->filesh.' "'.$args.'"';
16 | $result = shell_exec( $command );
17 | echo rtrim( $result );
18 | break;
19 | case 'camilla': // formdata from camilla.js
20 | fileUploadSave( $dirdata.'camilladsp/'.$post->dir.'/'.$_FILES[ 'file' ][ 'name' ] );
21 | exec( $dirsettings.'camilla-data.sh pushrefresh' );
22 | break;
23 | case 'countmnt':
24 | include 'function.php';
25 | echo json_encode( countMnt() );
26 | break;
27 | case 'datarestore': // formdata from system.js
28 | fileUploadSave( $dirshm.'backup.gz' );
29 | $libraryonly = $post->libraryonly ?? '';
30 | exec( $dirsettings.'system-datarestore.sh '.$libraryonly, $output, $result );
31 | if ( $result != 0 ) echo 'Restore failed';
32 | break;
33 | case 'giftype': // formdata from common.js
34 | $tmpfile = $_FILES[ 'file' ][ 'tmp_name' ];
35 | $animated = exec( $sudo.'/usr/bin/gifsicle -I '.$tmpfile.' | grep -q -m1 "image #1" && echo 1 || echo 0' );
36 | echo $animated;
37 | if ( $animated ) move_uploaded_file( $tmpfile, $dirshm.'local/tmp.gif' );
38 | break;
39 | case 'imagereplace': // $.post from function.js
40 | if ( ! is_writable( dirname( $post->file ) ) ) exit( '-1' );
41 | //----------------------------------------------------------------------------------
42 | exec( 'rm -f "'.substr( $post->file, 0, -4 ).'".*' ); // remove existing *.jpg, *.png, *.gif
43 | if ( substr( $post->file, -4 ) === 'jpg' ) {
44 | $base64 = preg_replace( '/^.*,/', '', $post->data ); // data:imgae/jpeg;base64,... > ...
45 | file_put_contents( $post->file, base64_decode( $base64 ) );
46 | } else {
47 | copy( $post->data, $post->file );
48 | }
49 | $args = escape( implode( "\n", [ $post->type, $post->file, $post->current, 'CMD TARGET CURRENT' ] ) );
50 | shell_exec( $dirbash.'cmd-coverart.sh "'.$args.'"' );
51 | break;
52 | case 'login': // $.post from features.js
53 | $filelogin = $dirdata.'system/login';
54 | $pwd = $post->pwd;
55 | if ( file_exists( $filelogin ) ) {
56 | $password = rtrim( file_get_contents( $filelogin ), "\n" );
57 | if ( ! password_verify( $pwd, $password ) ) exit( '-1' );
58 | //----------------------------------------------------------------------------------
59 | }
60 | $filesetting = $filelogin.'setting';
61 | if ( isset( $post->disable ) ) {
62 | unlink( $filelogin );
63 | unlink( $filesetting );
64 | exec( $dirsettings.'features.sh login' );
65 | } else if ( ! isset( $post->loginsetting ) ) {
66 | session_start();
67 | $_SESSION[ 'login' ] = 1;
68 | } else {
69 | $pwd = $post->pwdnew ?: $pwd;
70 | $hash = password_hash( $pwd, PASSWORD_BCRYPT, [ 'cost' => 12 ] );
71 | file_put_contents( $filelogin, $hash );
72 | if ( $post->loginsetting === 'true' ) { // no boolean
73 | touch( $filesetting );
74 | } else {
75 | unlink( $filesetting );
76 | }
77 | exec( $dirsettings.'features.sh login' );
78 | }
79 | break;
80 | case 'logout': // $.post from main.js
81 | session_start();
82 | session_destroy();
83 | break;
84 | case 'sort': // from cmd-list.sh
85 | include 'function.php';
86 | $modes = explode( ' ', $argv[ 2 ] );
87 | foreach( $modes as $mode ) {
88 | $file = '/srv/http/data/mpd/'.$mode;
89 | if ( ! file_exists( $file ) ) continue;
90 |
91 | $lines = file( $file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES );
92 | $data = [];
93 | foreach( $lines as $l ) $data[] = stripSort( $l ).'^x^'.$l;
94 | usort( $data, function( $a, $b ) {
95 | return strnatcasecmp( $a, $b );
96 | } );
97 | $list = '';
98 | foreach( $data as $d ) $list .= mb_substr( $d, 0, 1, 'UTF-8' ).'^^'.explode( '^x^', $d )[ 1 ]."\n";
99 | file_put_contents( $file, $list );
100 | }
101 | break;
102 | case 'timezonelist': // $.post from system.js
103 | $list = timezone_identifiers_list();
104 | $option = '';
105 | foreach( $list as $key => $zone ) {
106 | $datetime = new DateTime( 'now', new DateTimeZone( $zone ) );
107 | $offset = $datetime->format( 'P' );
108 | $zonename = preg_replace( [ '/_/', '/\//' ], [ ' ', ' · ' ], $zone );
109 | $option .= '';
110 | }
111 | echo $option;
112 | break;
113 |
114 | }
115 |
116 | function escape( $string ) {
117 | return preg_replace( '/(["`])/', '\\\\\1', $string ); // \1 inside function - $1 normal
118 | }
119 | function fileUploadSave( $filepath ) {
120 | if ( $_FILES[ 'file' ][ 'error' ] != UPLOAD_ERR_OK ) exit( '-1' );
121 | //----------------------------------------------------------------------------------
122 | move_uploaded_file( $_FILES[ 'file' ][ 'tmp_name' ], $filepath );
123 | }
124 |
--------------------------------------------------------------------------------
/srv/http/common.php:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 | rAudio
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | ';
31 | include 'login.php';
32 | exit;
33 | //----------------------------------------------------------------------------------
34 | }
35 | }
36 | $equalizer = file_exists( '/srv/http/data/system/equalizer' );
37 | $localhost = in_array( $_SERVER[ 'REMOTE_ADDR' ], ['127.0.0.1', '::1'] );
38 |
39 | // plugin: css / js filename with version
40 | $cssfiles = array_slice( scandir( '/srv/http/assets/css/plugin' ), 2 );
41 | foreach( $cssfiles as $file ) {
42 | $name = explode( '-', $file )[ 0 ];
43 | $cfiles[ $name ] = $file;
44 | }
45 | $jsfiles = array_slice( scandir( '/srv/http/assets/js/plugin' ), 2 );
46 | foreach( $jsfiles as $file ) {
47 | $name = explode( '-', $file )[ 0 ];
48 | $jfiles[ $name ] = $file;
49 | }
50 | if ( ! $page ) { // main
51 | $cssp = [];
52 | $css = [ ...$css, 'main', 'hovercursor' ];
53 | $jsp = [ 'jquery', 'pica', 'qrcode' ];
54 | $js = [ 'common', 'context', 'main', 'function', 'passive', 'shortcut' ];
55 | if ( $equalizer ) {
56 | $cssp[] = 'select2';
57 | $css = [ ...$css, 'select2', 'equalizer' ];
58 | $jsp[] = 'select2';
59 | }
60 | $title = 'STATUS';
61 | } else { // settings
62 | $cssp = [];
63 | $css[] = 'settings';
64 | $jsp = [ 'jquery', $networks ? 'qrcode' : 'select2' ];
65 | $js = [ 'common', 'settings', $page ];
66 | if ( ! $guide && ! $networks && ! $addonsprogress ) {
67 | $cssp[] = 'select2';
68 | $css[] = 'select2';
69 | }
70 | if ( $addons ) $css[] = 'addons';
71 | $icon = $page;
72 | $pagetitle = strtoupper( $page );
73 | if ( $addonsprogress ) {
74 | $icon = 'addons';
75 | $pagetitle = 'Addons-Progress';
76 | } else if ( $camilla ) {
77 | $icon = 'camilladsp';
78 | $pagetitle = 'CamillaDSP';
79 | $css = [ ...$css, 'camilla', 'equalizer' ];
80 | $jsp = [ ...$jsp, 'camilladsp_plot', 'complex', 'plotly' ];
81 | } else if ( $guide ) {
82 | $icon = 'help';
83 | $pagetitle = 'User Guide';
84 | $jsp = [];
85 | $js = [ 'guide' ];
86 | }
87 | $title = $pagetitle;
88 | }
89 | $add_guide = $addonsprogress || $guide;
90 | $keyboard = $localhost && ! $add_guide;
91 | if ( $keyboard ) foreach( [ 'cssp', 'css', 'jsp', 'js' ] as $ea ) $$ea[] = 'simplekeyboard';
92 |
93 | $html = '';
94 | $htmlcss = '';
96 | foreach( $css as $c ) $html.= $htmlcss.$c.'.css'.$hash.'">';
97 | $html .= '
98 |
99 |
100 | ';
101 | if ( ! $add_guide ) {
102 | $pageicon = $page ? icon( $page.' page-icon' ) : '';
103 | $html .= '
104 |
105 | '.$logosvg.'
106 |
107 | ';
108 | }
109 | if ( $keyboard ) $html.= '
110 |
111 | ';
112 | echo $html;
113 |
114 | $scripts = '';
115 | $htmljs = '';
117 | foreach( $js as $j ) $scripts.= $htmljs.$j.'.js'.$hash.'">';
118 |
119 | function htmlBottom() {
120 | global $htmlbar, $scripts;
121 | echo '
122 | '.$htmlbar.'
123 |
124 | '.$scripts.'
125 |
126 |
127 | ';
128 | }
129 | function icon( $icon, $id = '', $cmd = '' ) {
130 | $htmlid = $id ? ' id="'.$id.'"' : '';
131 | $htmlcmd = $cmd ? ' data-cmd="'.$cmd.'"' : '';
132 | return '';
133 | }
134 |
--------------------------------------------------------------------------------
/srv/http/login.php:
--------------------------------------------------------------------------------
1 |
27 |
28 |
29 |
30 |
31 |
36 |
37 |
38 |
44 |
45 |
84 |
85 |
86 |