├── CHANGELOG ├── QUICK-INSTALL.md ├── README.md ├── automix.php ├── config.php ├── download.php ├── index.php ├── install-remote.sh ├── jamulus-headless.service ├── jamulus-sudoers.txt ├── screenshots ├── screenshot1.png ├── screenshot2.png ├── screenshot3.png └── screenshot4.png └── worker.php /CHANGELOG: -------------------------------------------------------------------------------- 1 | JAMULUS RECORDNG REMOTE 2 | 3 | 0.4 (2021-01-08) 4 | - added y/n question to install-service.sh and install-remote.sh 5 | - on index.php, added display of free memory on server 6 | - moved commands definition from config.php to worker.php 7 | 8 | 0.3 (2021-01-04) 9 | - changed label of Reload button to "Refresh list" 10 | - corrections in instructions 11 | - new QUICK INSTALL guide 12 | 13 | 0.3 (2020-12-29) 14 | - Initial version 15 | -------------------------------------------------------------------------------- /QUICK-INSTALL.md: -------------------------------------------------------------------------------- 1 | ## Quick install 2 | This is a no-brain sequence of commands that you may run on a fresh instance of Ubuntu 18.04 to install everything from Jamulus to the Recording remote. 3 | You can just copy the following lines and paste them in the server shell: 4 | 5 | ``` 6 | wget https://raw.githubusercontent.com/corrados/jamulus/master/distributions/installscripts/install4ubuntu.sh 7 | 8 | sh install4ubuntu.sh 9 | 10 | sudo apt-get install zip 11 | 12 | wget https://github.com/vdellamea/jamulus-server-remote/archive/main.zip 13 | 14 | unzip main.zip 15 | 16 | cd jamulus-server-remote-main 17 | 18 | sh install-service.sh 19 | 20 | sh install-remote.sh 21 | ``` 22 | 23 | After this, the system is already up and running: you can reach it via `http://your.ip.address` where the IP address is the same you use to connect via Jamulus. 24 | However, **please remember to change the passwords!** You may edit the `config.php` file with the `nano` text editor: 25 | 26 | `sudo nano /var/www/html/config.php` 27 | 28 | You have to modify at least: 29 | 30 | `$ADMINPASSWORD= "your password";` 31 | 32 | `$MUSICIANSPASSWORD= "another password";` 33 | 34 | And eventually also: 35 | 36 | `$SERVERNAME="Your band name";` 37 | 38 | When you have finished, `CTRL+X` will let you save and exit (confirm with `y`). 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jamulus Recording Remote - 0.6 (2021-05-12) 2 | 3 | A light-weight web-based interface for Jamulus headless server when installed on a Linux system. No frills, supersimple. Version 0.6 is compatible with Jamulus 3.7; users of previous versions should download version 0.4.1. 4 | 5 | Jamulus Recording Remote allows to start and stop recordings, and at the end zip them to be downloaded via the Web. The current version is tested on Ubunto 20.04 Minimal. Installing on an already running system requires some adaptation (details in the Details section below). 6 | 7 | **Warning: use it at your own risk** 8 | *Jamulus Recording Remote has not yet been thoroughly examined for security issues, thus use it at your own risk, in particular if on a server running continuously. In particular, to be safe, Apache has to be set on https only.* 9 | 10 | ## Prerequisites 11 | Jamulus should be installed according to official [instructions](https://jamulus.io/wiki/Server-Linux) for a headless server, i.e., with a service named `jamulus-headless`. 12 | 13 | The rest of this README refers to Ubuntu 20.04, but it should be easily adaptable to other Linux platforms. 14 | 15 | ## Update from previous version 16 | 17 | If you already had the Remote + experimental automix installed, just replace the html pages. 18 | 19 | ## Quick install 20 | 21 | **If you have a personalised install of Jamulus already running, do not follow these instructions, but read the details at the bottom to adapt the system to your local needs.** 22 | 23 | Be sure to have zip: 24 | 25 | `sudo apt-get install zip` 26 | 27 | Download the code: 28 | 29 | `wget https://github.com/vdellamea/jamulus-server-remote/archive/main.zip` 30 | 31 | Unzip it: 32 | 33 | `unzip main.zip` 34 | 35 | enter the unzipped directory: 36 | 37 | `cd jamulus-server-remote-main` 38 | 39 | Then run the `install-remote.sh` script to install the web-based remote. With a browser, go to the server address (IP or domain), and you will find the interface; enter the password you have set in the configuration (`/var/www/html/config.php`). 40 | 41 | The service section of the service description should appear as below: some of the fields are different from the standard one, some are new. If you are in a new installation, just copy the `jamulus-headless.service` provided here in the right place. 42 | ``` 43 | [Service] 44 | Type=simple 45 | User=jamulus 46 | Group=www-data 47 | UMask=0002 48 | NoNewPrivileges=true 49 | ProtectSystem=true 50 | ProtectHome=false 51 | Nice=-20 52 | IOSchedulingClass=realtime 53 | IOSchedulingPriority=0 54 | ``` 55 | 56 | ## Configuration 57 | 58 | In principle, the `config.php` (under `/var/www/html`) is the only place where to put hands: password, paths, and also the real shell commands to allow for personalization. Change the following values according to your local configuration or taste: 59 | 60 | This is at your taste: server, band name, your cat name...: 61 | 62 | `$SERVERNAME="Your band name";` 63 | 64 | Please change the passwords: 65 | 66 | `$ADMINPASSWORD= "******";` 67 | 68 | `$MUSICIANSPASSWORD= "******";` 69 | 70 | 71 | 72 | If you set this one to true, in the Session box you can see some extra output, which can help in debugging: 73 | 74 | `$DEBUG=false;` 75 | 76 | ## Usage 77 | Access to the commands is protected by the password you set in the configuration file. Musicians too need to enter a password to access zipped files. 78 | 79 | 80 | 81 | At each first access, the interface expects Jamulus to have *recording disabled* (there is no obvious way to determine its status from code). Thus the "toggle on/off" button is off, and the "Start new" is disabled. This also means that just one admin at a time must access the interface, to avoid mishaps. Then, the toggle button activate/disactivate recording, the Start new button start a new recording. 82 | 83 | 84 | 85 | At the end of each execution, buttons trigger a refresh of the Sessions textarea, where recordings are shown with their size. However, you may also reload to update the size of the last recording. 86 | 87 | At the end, you can zip all the original files of the session (as `orig-YYYY-MM-DD.zip` file). "Delete WAVs" deletes all sessions (the WAV files); "Delete ZIPs" deletes all ZIP files, so be careful. 88 | 89 | 90 | ## Automix and consolidate 91 | 92 | The *automix* will generate an automatically mixed stereo MP3 from each recording session. Of course, the automix is just a rough preview, with no level adjustment. Panning is done in two ways, discussed below. 93 | 94 | In addition to that, a *consolidate* feature is present because needed for automix, but it is also usable independently. This is aimed at producing WAV (or other formats like mp3 and flac) files that can be used with DAWs different from Reaper and Audacity: tracks all begin at time 0, so it is easy to import them in any DAW, not only Reaper and Audacity. 95 | 96 | After having run automix or consolidate, you may download the zipped version of the files as `mix-YYYY-MM-DD.zip` and `consolidated-YYYY-MM-DD.zip`. 97 | 98 | Beware: you need additional storage to run the scripts (although only temporarily). 99 | 100 | Automix and consolidate can be run also independently from the web system, by running `php automix.php` with appropriate parameters: 101 | 102 | --automix (default) / --consolidate 103 | 104 | Is a full Recordings directory or a single session? 105 | 106 | --single (default) / --all 107 | 108 | Files: 109 | 110 | --in path_to_recordings directory 111 | 112 | --out path_to_generated (default: current dir) 113 | 114 | Options: 115 | 116 | --format (wav,mp3,opus, ...) (default: mp3 for automix, wav for consolidate) 117 | 118 | --normalize audio normalization, default off - not good yet 119 | 120 | --debug add extra output 121 | 122 | --help this one 123 | 124 | ### Automix configuration 125 | 126 | Without any specific configuration, the system attempts to pan the tracks in a uniformly distributed way. One player: centered; two: one per side (but not totally); three: one centered, one left, one right; etc. They are ordered from left to right in alphabetical order by the profile name, and this may help to set the panning for large groups: e.g., you may ask singers to prefix their name with a number (01, 02, 03...). 127 | 128 | However, since the system is prevalently aimed at private servers, in config.php you may set the name of each musician/singer exactly as in their Jamulus profile, and set the position relative to left (1.0= all left, 0= all right, 0.5= center, etc). Tracks are recognised by the profile name, and thus can be panned in an informed way (e.g., drums and bass in the middle, guitars well spaced, etc). 129 | 130 | ``` 131 | $BANDMATES=array( 132 | 'Jimi' =>0.55, 133 | 'Eric' =>0.45, 134 | 'John' =>0.5, 135 | 'Patti' =>0.5, 136 | 'Stevie'=> 0.3, 137 | ); 138 | ``` 139 | 140 | If you run `automix.php` independently, the same settings are to be put in the automix.php file, because it does not use config.php. 141 | 142 | In addition to that, you may set the format for the consolidated tracks, as mp3, wav, flac (actually, any format managed by ffmpeg). Normalization is not yet good. 143 | 144 | ``` 145 | $CFORMAT="mp3"; // also flac, wav, etc 146 | $AUDIONORMALIZATION=false; //Experimental - not yet good 147 | ``` 148 | 149 | *No need to check the rest if you installed from scratch as described above.* 150 | 151 | # Details for adapting to different platforms 152 | 153 | The following description is aimed at explaining what the installation script does, and it can be useful for those that want to install the interface on an already running server, or on a different distribution, or for any other reason. 154 | 155 | ## Installation 156 | This is a motivated walk through the install-remoth script. Download the code from this repository. 157 | 158 | Install dependencies: this should be adapted to the package manager of your distribution (yum, dnf). Some other software might be missing, like zip/unzip, some might be already installed (e.g., acl). 159 | ``` 160 | sudo apt-get install apache2 php libapache2-mod-php ffmpeg acl 161 | ``` 162 | E.g., for Fedora: 163 | ``` 164 | sudo dnf install httpd php ffmpeg zip 165 | ``` 166 | 167 | 168 | Prepare web document root: in principle this should be sufficient for most distributions, however you might want to put the PHP files in some subdirectory. 169 | ``` 170 | sudo rm /var/www/html/index.html 171 | sudo cp *.php /var/www/html/ 172 | ``` 173 | Ownership of the files should be given to the user running apache. E.g., in Fedora it is not `www-data` but `apache`. It should be changed wherever www-data appears. 174 | ``` 175 | sudo chown -R www-data /var/www/html/ 176 | sudo chgrp -R www-data /var/www/html/ 177 | ``` 178 | 179 | If you followed the original instructions, the jamulus user is not created with a home dir. The following aims at setting it. 180 | ``` 181 | sudo usermod -d /home/jamulus jamulus 182 | sudo mkhomedir_helper jamulus 183 | ``` 184 | 185 | Then you need recording, mix and consolidate directories. These instruction should be good on any platform, except for the chgrp to www-data that might need adaptation. 186 | ``` 187 | # recording songs directory 188 | sudo mkdir /home/jamulus/recording 189 | sudo chgrp www-data /home/jamulus/recording/ 190 | sudo chmod g+rwx /home/jamulus/recording/ 191 | sudo chmod g+s /home/jamulus/recording/ 192 | sudo setfacl -d -m g::rwx /home/jamulus/recording/ 193 | # mixed songs directory 194 | sudo mkdir /home/jamulus/mix 195 | sudo chgrp www-data /home/jamulus/mix/ 196 | sudo chmod g+rwx /home/jamulus/mix/ 197 | sudo chmod g+s /home/jamulus/mix/ 198 | sudo setfacl -d -m g::rwx /home/jamulus/mix/ 199 | # consolidated tracks directory 200 | sudo mkdir /home/jamulus/consolidated 201 | sudo chgrp www-data /home/jamulus/consolidated/ 202 | sudo chmod g+rwx /home/jamulus/consolidated/ 203 | sudo chmod g+s /home/jamulus/consolidated/ 204 | sudo setfacl -d -m g::rwx /home/jamulus/consolidated/ 205 | ``` 206 | 207 | Finally, you have to add sudo capabilities to Apache for 2 commands. This is the tricky part. You have to give privileges to Apache for running commands as `sudo` by adding a file in `sudoers.d`. However, any mistake in doing this may result in loosing sudo privileges, thus use exclusively the `sudo visudo` command if you have to modify something, because it does syntax checks. 208 | The jamulus-sudoers.txt file should be adapted both regarding the user to which privileges are given, and the name of the service to be called (jamulus-headless vs jamulus or whatever you called it). It is highly suggested to use visudo to edit it, so copy it in place, then `sudo visudo /etc/sudoers.d/jamulus` . Be very careful. `visudo` does syntax checking and avoids mistakes, but if you use a different editor and make a mistake, all sudo privileges become locked. If visudo starts with the `vi` editor and you are not a nerd, try `sudo EDITOR=/bin/nano visudo /etc/sudoers.d/jamulus`. 209 | 210 | This might not be sufficient due to extra layers of protection in the system (SELinux, default on Fedora and Centos). You might need to [disable it](https://www.cyberciti.biz/faq/disable-selinux-on-centos-7-rhel-7-fedora-linux/) or do further work to enable Apache to call systemctl, but I cannot help on this. 211 | 212 | ``` 213 | sudo cp jamulus-sudoers.txt /etc/sudoers.d/jamulus 214 | ``` 215 | The content is 216 | ``` 217 | www-data ALL=(ALL)NOPASSWD: /bin/systemctl kill -s SIGUSR1 jamulus 218 | www-data ALL=(ALL)NOPASSWD: /bin/systemctl kill -s SIGUSR2 jamulus 219 | ``` 220 | 221 | Shell commands used by the Remote may need personalization if you want to adapt the scripts to an already existing installation (e.g., service name, the same as in the sudoers file. Here the current commands you can find in `worker.php` (two for sure that may need modifications are toggle and newrec, for the service name): 222 | ```php 223 | "toggle" => "sudo /bin/systemctl kill -s SIGUSR2 jamulus-headless ", 224 | "newrec" => "sudo /bin/systemctl kill -s SIGUSR1 jamulus-headless ", 225 | "compress" => "cd $RECORDINGS ; rm orig-$today.zip; zip -r orig-$today.zip Jam* ", 226 | "listrec" => "du -sh $RECORDINGS/Jam* ", 227 | "freespace" => "df -h --output=avail $RECORDINGS ", 228 | "delwav" => "rm -fr $RECORDINGS/Jam* ", 229 | "delzip" => "rm -fr $RECORDINGS/*.zip ", 230 | "ffmpeg" => "ffmpeg -loglevel quiet ", 231 | "checkstereo" => "ffmpeg -i ", 232 | "maxvolume" =>"ffmpeg -i ", 233 | "ffprobe" => "ffprobe -show_entries stream=duration -of compact=p=0:nk=1 -v 0 ", 234 | "zipmix" => "rm $RECORDINGS/mix-$today.zip; cd $MIX; zip $RECORDINGS/mix-$today.zip *.mp3 ; rm $MIX/*.mp3 ", 235 | "cleancons" => "rm $RECORDINGS/consolidated-$today.zip", 236 | "zipcons1" => "cd $CONSOLIDATED; zip -r $RECORDINGS/consolidated-$today.zip Jam* ; rm -fr Jam* ", 237 | "cleantmp" => "rm -fr /var/tmp/Jam-* ", 238 | ``` 239 | 240 | The service file now allows for writing in the home directory of the user, which is created when creating the user. However, this is not mandatory: if you installed everything according to official instructions, the jamulus user likely will not have a home directory. If you want to put the recordings elsewhere, check that the privileges set in the service file are adequate (and sorry, I am not able to help on this). 241 | These are the extras vs. the standard install: 242 | ``` 243 | Group=www-data 244 | UMask=0002 245 | ProtectHome=false 246 | ``` 247 | 248 | 249 | 250 | 251 | 252 | -------------------------------------------------------------------------------- /automix.php: -------------------------------------------------------------------------------- 1 | value, 14 | // name exactly as in Jamulus profile, 15 | // value: 1= left only, 0= right only, 0.5= center, etc 16 | // Names not specified are automatically panned. 17 | 18 | $BANDMATES=array( 19 | 'Jimi' =>0.85, 20 | 'Eric' =>0.45, 21 | 'Carol' =>0.53, 22 | 'Patti' =>0.5, 23 | 'Stevie'=> 0.11, 24 | ); 25 | // File format for consolidated tracks 26 | //TODO set bitrate 27 | $CFORMAT="mp3"; //.wav, .opus ... any format managed by ffmpeg 28 | //default dir is current dir for automix 29 | $MIX="./"; 30 | $DEBUG=true; 31 | $OPERATION='automix'; 32 | $DIRTYPE='single'; 33 | $AUDIONORMALIZATION=false; 34 | 35 | //command line options 36 | $optlist=array("automix","consolidate","single", "all","debug","format::","in:", "out:", "help", "normalize"); 37 | 38 | $options=getopt("",$optlist); 39 | if(isset($options['debug'])) $DEBUG=true; 40 | if(isset($options['consolidate'])) {$OPERATION='consolidate'; $CFORMAT='wav';} 41 | if(isset($options['all'])) $DIRTYPE='all'; 42 | if(isset($options['format'])) $CFORMAT=$options['format']; 43 | if(isset($options['in'])) $RECORDINGS=$options['in']; 44 | if(isset($options['out'])) $MIX=$options['out']; 45 | if(isset($options['normalize'])) $AUDIONORMALIZATION=true; 46 | 47 | if($DEBUG) var_dump($options); 48 | 49 | if(isset($options['help']) || !isset($RECORDINGS)) die(automix_help()); 50 | 51 | $today=date("Ymd"); 52 | 53 | //If you need to adapt the Remote to your server, check the following commands 54 | if(!$DEBUG) $FFMPEG_LOG="-loglevel quiet "; 55 | $COMMANDS=array( 56 | "ffmpeg" => "ffmpeg $FFMPEG_LOG -hide_banner ",//-loglevel quiet 57 | "checkstereo" => "ffmpeg $FFMPEG_LOG -hide_banner -i ", 58 | "maxvolume" =>" $FFMPEG_LOG -hide_banner -i ", 59 | "zipmix" => "zip mix.zip *.mp3 ; rm $MIX/*.mp3 ", 60 | "ffprobe" => "ffprobe -hide_banner -show_entries stream=duration -of compact=p=0:nk=1 -v 0 ", 61 | ); 62 | 63 | print("AUTOMIX 0.51 - part of Jamulus Recording Remote\n"); 64 | 65 | //if($argc<3) die("php automix.php single|all path_to_recordings\n"); 66 | 67 | if($OPERATION=='automix' && $DIRTYPE=='all') { 68 | print("Generating automix for all the sessions in $RECORDINGS.\n"); 69 | $sessions=glob("$RECORDINGS/Jam-*",GLOB_ONLYDIR); 70 | foreach($sessions as $s) { 71 | print("- $s \n"); 72 | $from=$s; 73 | $to="/var/tmp/".basename($from)."/"; 74 | mkdir($to); 75 | consolidate_tracks($from,$to, ".wav"); 76 | $out=generate_mix($to); 77 | if($DEBUG) print_r($out); 78 | } 79 | } 80 | else if($OPERATION=='automix' && $DIRTYPE=='single'){ 81 | print("Generating automix for session $RECORDINGS.\n"); 82 | $to="/var/tmp/".basename($RECORDINGS)."/"; 83 | mkdir($to); 84 | consolidate_tracks($RECORDINGS,$to, ".wav"); 85 | $out=generate_mix($to); 86 | if($DEBUG) print_r($out); 87 | } 88 | else if($OPERATION=='consolidate' && $DIRTYPE=='single'){ 89 | print("Consolidating tracks for session $RECORDINGS to $MIX .\n"); 90 | mkdir($MIX); 91 | consolidate_tracks($RECORDINGS,$MIX."/", $CFORMAT); 92 | } 93 | else if($OPERATION=='consolidate' && $DIRTYPE=='all'){ 94 | print("Consolidating tracks for all the sessions in $RECORDINGS .\n"); 95 | $sessions=glob("$RECORDINGS/Jam-*",GLOB_ONLYDIR); 96 | foreach($sessions as $s) { 97 | print("- $s \n"); 98 | $from=$s; 99 | $to=$MIX."/".basename($from)."/"; 100 | if($DEBUG) print("TO: $MIX - $from - $to. \n"); 101 | mkdir($to); 102 | consolidate_tracks($from,$to, $CFORMAT); 103 | } 104 | } 105 | else die(automix_help()); 106 | 107 | } 108 | 109 | 110 | //FUNCTIONS 111 | 112 | function generate_mix($session) { 113 | global $RECORDINGS; 114 | global $MIX; 115 | global $COMMANDS; 116 | global $BANDMATES; 117 | global $DEBUG; 118 | $ffmpeg=$COMMANDS['ffmpeg']; 119 | $checkstereo=$COMMANDS['checkstereo']; 120 | $filename=basename($session); 121 | $dir=glob($session."/*.wav"); 122 | $chantotal=0; 123 | $unknowns=0; 124 | 125 | //scan tracks, check known clients, check mono/stereo 126 | foreach($dir as $t) { 127 | //Is it a known client or not? 128 | if(!known(basename($t))) $unknowns++; 129 | 130 | exec($checkstereo.$t." 2>&1 | grep Guessed",$out,$rec); 131 | $stereo=substr(trim($out[0]),-6)=="stereo"; 132 | $tracks[$t]=$stereo; 133 | $chantotal1=$chantotal+1; 134 | if($stereo) { 135 | $channels[$t]=array("c".$chantotal,"c".$chantotal1); 136 | $chantotal=$chantotal+2; 137 | } 138 | else 139 | { 140 | $channels[$t]=array("c".$chantotal,"c".$chantotal); 141 | $chantotal=$chantotal+1; 142 | } 143 | unset($out); 144 | } 145 | 146 | $clients=sizeof($tracks); 147 | 148 | //STEREO PANNING 149 | if($unknowns>0) $offset=0.5/$unknowns; 150 | $step=$offset*2; 151 | $lmult=-0.5; 152 | $numchans=0; 153 | foreach($channels as $t=>$chans){ 154 | $inputs.="-i $t "; 155 | //if client is known, use the value from config 156 | if(known(basename($t))) { 157 | $ltmp=$BANDMATES[known_name(basename($t))]; 158 | $rtmp=1-$ltmp; 159 | } 160 | else { 161 | if($numchans==0) 162 | $rtmp=$offset; 163 | else 164 | $rtmp=$rtmp+$step; 165 | 166 | $ltmp=1-$rtmp; 167 | $numchans++; 168 | if($DEBUG) print("PANNING ".basename($t).": $ltmp - $rtmp \n"); 169 | } 170 | $ltmp=round($ltmp,3); 171 | $rtmp=round($rtmp,3); 172 | $left.=$ltmp."*".$chans[0]."+"; 173 | $right.=$rtmp."*".$chans[1]."+"; 174 | } 175 | 176 | // remove trailing '+' 177 | $left=substr($left,0,-1); 178 | $right=substr($right,0,-1); 179 | 180 | $volumeplus=$clients; 181 | $automixcommand= 182 | "$ffmpeg $inputs -filter_complex \"amerge=inputs=". $clients. 183 | ",volume=$volumeplus"."dB,pan=stereo|FL<$left|FR<$right". 184 | "[a]\" -map \"[a]\" $MIX"."$filename.mp3\n"; 185 | 186 | exec($automixcommand,$out,$ret); 187 | if($DEBUG) { 188 | print("AUTOMIX:".$automixcommand."\n"); 189 | print_r($out); 190 | } 191 | unset($out); 192 | } 193 | 194 | 195 | /////////////////////////////////////////// 196 | function consolidate_tracks($dir, $outdir, $format) { 197 | global $AUDIONORMALIZATION; 198 | global $COMMANDS; 199 | global $DEBUG; 200 | $ffprobe=$COMMANDS['ffprobe']; 201 | $checkmaxvolume=$COMMANDS['maxvolume']; 202 | 203 | // scan .lof file to read track offsets in seconds 204 | $lof=file($dir."/".basename($dir).".lof"); 205 | foreach($lof as $f){ 206 | $tmp=explode(" ",trim($f)); 207 | $offset[str_replace("\"","",$tmp[1])]=$tmp[3]; 208 | } 209 | 210 | $list=glob($dir."/*.wav"); 211 | 212 | $maxduration=0;//this will become the total length of the session 213 | 214 | foreach($list as $t){ 215 | $tmp=explode("-",substr(basename($t),0,-4)); 216 | //check duration of each track 217 | exec($ffprobe.$t, 218 | $out); 219 | $duration=$out[0];unset($out); 220 | if($DEBUG) print("DURATION: ".$ffprobe.$t."\n"); 221 | 222 | //check max volume 223 | $maxvolumecommand=$checkmaxvolume.$t. 224 | ' -af "volumedetect" -f null /dev/null 2>&1 |grep max_volume'; 225 | exec($maxvolumecommand,$outvol,$retvol); 226 | if($DEBUG) print("VOLUME: ".$maxvolumecommand."\n"); 227 | $outvol2=explode(":",trim($outvol[0])); 228 | $outvol2=explode(" ",substr($outvol2[1],0,-2)); 229 | $maxvolume=$outvol2[1]; 230 | unset($outvol); 231 | unset($outvol2); 232 | 233 | //if a client name is available, use it 234 | if(!isset($tracks[$tmp[1]]['name'])) $tracks[$tmp[1]]['name']='____'; 235 | if($tmp[0]<>'____') $tracks[$tmp[1]]['name']=$tmp[0]; 236 | 237 | // real duration is offset + duration 238 | $newdur=$offset[basename($t)]+$duration; 239 | // look for the longest 240 | if($newdur>$maxduration) $maxduration=$newdur; 241 | 242 | $tracks[$tmp[1]]['segments'][$t]= 243 | array( 'frame'=>$tmp[2], 244 | 'channels'=>$tmp[3], 245 | 'offset'=>$offset[basename($t)], 246 | 'duration'=>$duration, 247 | 'newdur'=>$newdur, 248 | 'maxvolume'=>$maxvolume, 249 | ); 250 | } 251 | 252 | if($DEBUG) print_r($tracks); 253 | 254 | foreach ($tracks as $ip=>$t){ 255 | $trackdur=0; 256 | $maxoffset=0; 257 | $inputs=""; $delays=""; $amix=""; 258 | //$format contains the file extension: ffmpeg will generate in such format 259 | //TODO set bitrate! 260 | $outname=$outdir.$t['name']."-consolidated.".$format; 261 | if($t['name']=='____') $outname=$outdir.$ip."-consolidated.".$format; 262 | $c=0; 263 | 264 | //sort by offset to reorder when many WAVs 265 | //and decide whether the channel should be stereo or mono 266 | $orderedwavs=array(); 267 | foreach($t['segments'] as $tr=>$s){ 268 | $orderedwavs[$tr]=$s['offset']; 269 | $numberofchannels=$s['channels']; 270 | } 271 | asort($orderedwavs); 272 | 273 | $monostereo='mono';if($numberofchannels==2) $monostereo='stereo'; 274 | 275 | $previousdur=0; 276 | foreach($orderedwavs as $tr=>$o){ 277 | $s=$t['segments'][$tr]; 278 | $inputs.="-i $tr "; 279 | $delay=round(1000*($s['offset']-$previousdur),0); 280 | $maxvolume=-$s['maxvolume']; 281 | 282 | //Volumes are all brought to 0dB 283 | //this tries to save who had set the volume too low 284 | $volumeincrease=""; 285 | if($maxvolume>0 && $maxvolume<7) 286 | $volumeincrease=" ,volume=".$maxvolume."dB"; 287 | $delays.= 288 | "[$c]aformat=sample_fmts=s16:sample_rates=48000". 289 | ":channel_layouts=$monostereo". 290 | ",adelay=".$delay."|".$delay.$volumeincrease."[a$c]; "; 291 | $amix.="[a$c]"; 292 | $c++; 293 | if($s['offset']>$maxoffset) { 294 | $trackdur=($s['offset']+$s['duration']); 295 | $maxoffset=$s['offset']; 296 | } 297 | $previousdur=$s['offset']+$s['duration']; 298 | } 299 | 300 | 301 | $total=$c; 302 | $silence="";$silencedelay="";$silenceamix=""; 303 | $missingtime=round($maxduration-$previousdur,3); 304 | $trackdur=round(1000*$trackdur,0); 305 | // if the consolidated track is shorter than the maximum duration, 306 | // add a silence track that lasts as the maximum 307 | if($missingtime>0) { 308 | $total=$c+1; 309 | $silence=" -f lavfi -i anullsrc=r=48000 "; 310 | $tracksilence=round($maxduration,3); 311 | $silencedelay="[$c]atrim=duration=".$missingtime."[a$c];"; 312 | $silenceamix="[a$c]"; 313 | } 314 | 315 | //Audio normalization: does not give reliable results 316 | if($AUDIONORMALIZATION) $audionorm=", dynaudnorm=t=0.1 "; 317 | // if($AUDIONORMALIZATION) $audionorm=", loudnorm=tp=0.0, aresample=48000"; 318 | 319 | $command="ffmpeg -hide_banner $inputs $silence". 320 | " -filter_complex \"$delays $silencedelay $amix". 321 | $silenceamix."concat=n=$total:v=0:a=1 $audionorm". "\" $outname\n"; 322 | 323 | 324 | exec($command, $outcommand); 325 | if($DEBUG) { 326 | print("CONSOLIDATE: ".$command."\n"); 327 | print_r($outcommand); 328 | } 329 | unset($outcommand); 330 | } 331 | } 332 | 333 | function known($track) { 334 | global $BANDMATES; 335 | $ret=false; 336 | foreach($BANDMATES as $m=>$n) 337 | if($ret=(substr($track,0,strlen($m))==$m)) 338 | break; 339 | return $ret; 340 | } 341 | 342 | function known_name($track) { 343 | global $BANDMATES; 344 | $ret=false; 345 | foreach($BANDMATES as $m=>$n) { 346 | $ret=substr($track,0,strlen($m)); 347 | if($ret==$m) break; 348 | } 349 | return $ret; 350 | } 351 | 352 | function isCommandLineInterface() 353 | { 354 | return (php_sapi_name() === 'cli'); 355 | } 356 | 357 | function automix_help() { 358 | ?> 359 | AUTOMIX v0.51 - part of Jamulus Recording Remote 360 | Choose an operation: 361 | --automix (default) 362 | --consolidate 363 | Is a full Recordings directory or a single session? 364 | --single (default) 365 | --all 366 | Files: 367 | --in path_to_recordings directory 368 | --out path_to_generated (default: current dir) 369 | Options: 370 | --format (wav,mp3,opus) (default: mp3 for automix, wav for consolidate) 371 | --normalize audio normalization, default off - not good yet 372 | --debug add extra output 373 | --help this one 374 | 378 | 379 | 380 | -------------------------------------------------------------------------------- /config.php: -------------------------------------------------------------------------------- 1 | left percentage (right is 1-left) 22 | $BANDMATES=array( 23 | 'Jimi' =>0.55, 24 | 'Eric' =>0.45, 25 | 'John' =>0.5, 26 | 'Patti' =>0.5, 27 | 'Stevie'=> 0.3, 28 | ); 29 | 30 | ?> 31 | -------------------------------------------------------------------------------- /download.php: -------------------------------------------------------------------------------- 1 | 36 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | Jamulus Recording Remote 28 | 50 | 51 | 52 | 53 | $SERVERNAME\n"); 55 | if( 56 | (isset($_SESSION['admin'])&& ($_SESSION['admin']==$ADMINPASSWORD)) || 57 | (isset($_POST['apwd'])&& ($_POST['apwd']==$ADMINPASSWORD)) 58 | ) { 59 | $_SESSION['admin']=$ADMINPASSWORD; 60 | ?> 61 |

Jamulus Recording Remote

62 |

Recording

63 |

64 | 67 | 68 | 71 |

72 | 73 |

Sessions

74 |
75 | 78 | Free: - 79 |
80 |
81 | 82 |
83 |

Finish

84 |

85 |

86 | 89 | 90 | 93 | 94 | 97 | 98 |
99 |
100 | 103 | 107 | 108 |
109 |

Download

110 |
Original tracks |  111 | Consolidated |  112 | Mixed 113 | 114 |
115 | 116 | 189 | 190 | 201 |

Files

202 |

Zipped WAVs

203 |

204 | Consolidated files 205 |

206 |

Mixed MP3

207 | 211 |

Musicians

212 |
213 | 214 | 215 |

216 |

Admin

217 | 218 | 219 | 220 |
221 | 222 | 226 |
227 |
228 | 229 |
230 |
231 | Jamulus Recording Remote 232 |
233 | 234 | 235 | -------------------------------------------------------------------------------- /install-remote.sh: -------------------------------------------------------------------------------- 1 | echo -n "JAMULUS RECORDING REMOTE INSTALLATION v0.6. Are you sure? (y/n)? " 2 | read answer 3 | if [ "$answer" != "${answer#[Yy]}" ] ;then 4 | 5 | sudo apt-get install apache2 php libapache2-mod-php ffmpeg acl 6 | 7 | # prepare web document root 8 | sudo rm /var/www/html/index.html 9 | sudo cp *.php /var/www/html/ 10 | sudo chown -R www-data /var/www/html/ 11 | sudo chgrp -R www-data /var/www/html/ 12 | 13 | # home dir for the jamulus user 14 | sudo usermod -d /home/jamulus jamulus 15 | sudo mkhomedir_helper jamulus 16 | 17 | # recording directory 18 | sudo mkdir /home/jamulus/recording 19 | sudo chgrp www-data /home/jamulus/recording/ 20 | sudo chmod g+rwx /home/jamulus/recording/ 21 | sudo chmod g+s /home/jamulus/recording/ 22 | sudo setfacl -d -m g::rwx /home/jamulus/recording/ 23 | 24 | # mixed songs directory 25 | sudo mkdir /home/jamulus/mix 26 | sudo chgrp www-data /home/jamulus/mix/ 27 | sudo chmod g+rwx /home/jamulus/mix/ 28 | sudo chmod g+s /home/jamulus/mix/ 29 | sudo setfacl -d -m g::rwx /home/jamulus/mix/ 30 | 31 | # consolidated tracks directory 32 | sudo mkdir /home/jamulus/consolidated 33 | sudo chgrp www-data /home/jamulus/consolidated/ 34 | sudo chmod g+rwx /home/jamulus/consolidated/ 35 | sudo chmod g+s /home/jamulus/consolidated/ 36 | sudo setfacl -d -m g::rwx /home/jamulus/consolidated/ 37 | 38 | # add sudo capabilities to Apache for 2 commands 39 | sudo cp jamulus-sudoers.txt /etc/sudoers.d/jamulus 40 | 41 | else 42 | echo -n "JAMULUS RECORDING REMOTE INSTALLATION canceled." 43 | fi 44 | -------------------------------------------------------------------------------- /jamulus-headless.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Jamulus headless server 3 | After=network.target 4 | StartLimitIntervalSec=0 5 | 6 | [Service] 7 | Type=simple 8 | User=jamulus 9 | Group=www-data 10 | UMask=0002 11 | NoNewPrivileges=true 12 | ProtectSystem=true 13 | ProtectHome=false 14 | Nice=-20 15 | IOSchedulingClass=realtime 16 | IOSchedulingPriority=0 17 | 18 | #### Change this to publish this server, set genre, location and other parameters. 19 | #### See https://jamulus.io/wiki/Command-Line-Options #### 20 | ExecStart=/usr/bin/jamulus-headless -s -F -d -n -T -w "Your message" --norecord -R "/home/jamulus/recording" 21 | 22 | 23 | Restart=on-failure 24 | RestartSec=30 25 | StandardOutput=journal 26 | StandardError=inherit 27 | SyslogIdentifier=jamulus 28 | 29 | 30 | [Install] 31 | WantedBy=multi-user.target 32 | -------------------------------------------------------------------------------- /jamulus-sudoers.txt: -------------------------------------------------------------------------------- 1 | www-data ALL=(ALL)NOPASSWD: /bin/systemctl kill -s SIGUSR1 jamulus-headless 2 | www-data ALL=(ALL)NOPASSWD: /bin/systemctl kill -s SIGUSR2 jamulus-headless 3 | -------------------------------------------------------------------------------- /screenshots/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vdellamea/jamulus-server-remote/0513180cc915bacc352c8e34e919334291358c15/screenshots/screenshot1.png -------------------------------------------------------------------------------- /screenshots/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vdellamea/jamulus-server-remote/0513180cc915bacc352c8e34e919334291358c15/screenshots/screenshot2.png -------------------------------------------------------------------------------- /screenshots/screenshot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vdellamea/jamulus-server-remote/0513180cc915bacc352c8e34e919334291358c15/screenshots/screenshot3.png -------------------------------------------------------------------------------- /screenshots/screenshot4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vdellamea/jamulus-server-remote/0513180cc915bacc352c8e34e919334291358c15/screenshots/screenshot4.png -------------------------------------------------------------------------------- /worker.php: -------------------------------------------------------------------------------- 1 | "sudo /bin/systemctl kill -s SIGUSR2 jamulus-headless ", 15 | "newrec" => "sudo /bin/systemctl kill -s SIGUSR1 jamulus-headless ", 16 | "compress" => "cd $RECORDINGS ; rm orig-$today.zip; zip -r orig-$today.zip Jam* ", 17 | "compressday" => "cd $RECORDINGS ; rm $today.zip; zip -r $today.zip Jam-$today-* ", 18 | "listrec" => "du -sh $RECORDINGS/Jam* ", 19 | "freespace" => "df -h --output=avail $RECORDINGS ", 20 | "delwav" => "rm -fr $RECORDINGS/Jam* ", 21 | "delzip" => "rm -fr $RECORDINGS/*.zip ", 22 | "ffmpeg" => "ffmpeg -loglevel quiet ", 23 | "checkstereo" => "ffmpeg -i ", 24 | "maxvolume" =>"ffmpeg -i ", 25 | "ffprobe" => "ffprobe -show_entries stream=duration -of compact=p=0:nk=1 -v 0 ", 26 | "zipmix" => "rm $RECORDINGS/mix-$today.zip; cd $MIX; zip $RECORDINGS/mix-$today.zip *.mp3 ; rm $MIX/*.mp3 ", 27 | "cleancons" => "rm $RECORDINGS/consolidated-$today.zip", 28 | "zipcons1" => "cd $CONSOLIDATED; zip -r $RECORDINGS/consolidated-$today.zip Jam* ; rm -fr Jam* ", 29 | "cleantmp" => "rm -fr /var/tmp/Jam-* ", 30 | ); 31 | 32 | include("automix.php"); 33 | 34 | $stderr=""; 35 | if($DEBUG) { 36 | print_r($_POST); 37 | print_r($_SESSION); 38 | $stderr=" 2>&1"; 39 | } 40 | if(isset($_SESSION['admin'])&& ($_SESSION['admin']==$ADMINPASSWORD)) { 41 | if($DEBUG) print_r($_SESSION); 42 | if(!isset($_POST['exec'])) die("No, thanks."); 43 | $out=array(); 44 | switch ($_POST['exec']){ 45 | case 'toggle': 46 | exec($COMMANDS['toggle'].$stderr,$out,$ret); 47 | break; 48 | case 'newrec': 49 | exec($COMMANDS['newrec'].$stderr,$out,$ret); 50 | break; 51 | case 'compress': 52 | exec($COMMANDS['compress'].$stderr,$out,$ret); 53 | break; 54 | case 'compressday': 55 | exec($COMMANDS['compressday'].$stderr,$out,$ret); 56 | break; 57 | case 'cleanwav': 58 | exec($COMMANDS['delwav'].$stderr,$out,$ret); 59 | break; 60 | case 'cleanzip': 61 | exec($COMMANDS['delzip'].$stderr,$out,$ret); 62 | break; 63 | case 'listrec': 64 | break; 65 | case 'automix': 66 | $sessions=glob("$RECORDINGS/Jam-*"); 67 | foreach($sessions as $s) { 68 | $to="/var/tmp/".basename($s)."/"; 69 | mkdir($to); 70 | consolidate_tracks($s,$to,".wav"); 71 | $out=generate_mix($to); 72 | exec($COMMANDS['cleantmp'].$stderr,$out,$ret); 73 | } 74 | exec($COMMANDS['zipmix'].$stderr,$out,$ret); 75 | break; 76 | case 'consolidate': 77 | exec($COMMANDS['cleancons'].$stderr, $out,$ret); 78 | $sessions=glob("$RECORDINGS/Jam-*"); 79 | foreach($sessions as $s) { 80 | $to=$CONSOLIDATED.basename($s)."/"; 81 | mkdir($to); 82 | consolidate_tracks($s,$to,$CFORMAT); 83 | exec($COMMANDS['zipcons1'].$stderr,$out,$ret); 84 | } 85 | break; 86 | case 'freespace': 87 | exec($COMMANDS['freespace'],$freemem); 88 | print($freemem[1]); 89 | break; 90 | die("No, thanks."); 91 | } 92 | //if the $DEBUG variable is set in config.php, let's show the results of the call 93 | if($DEBUG) {print_r($ret);print("\n");print_r($out);} 94 | 95 | //every command will return the recording directory content 96 | if($_POST['exec']<>'freespace') { 97 | exec($COMMANDS['listrec'],$list); 98 | foreach($list as $line) { 99 | $tmp=explode("\t",str_replace($RECORDINGS."/","",$line)); 100 | print($tmp[1]."\t".$tmp[0]."\n"); 101 | } 102 | } 103 | } 104 | 105 | 106 | ?> 107 | 108 | --------------------------------------------------------------------------------