fully containerized, but should still be adaptable to non-container use
This commit is contained in:
10
Dockerfile
Normal file
10
Dockerfile
Normal file
@@ -0,0 +1,10 @@
|
||||
FROM php:fpm
|
||||
RUN apt-get update && apt-get install -y ffmpeg && rm -rf /var/lib/apt/lists/*
|
||||
COPY record.sh cleanup.sh /usr/bin/
|
||||
COPY *.php /var/www/html/
|
||||
ENV BASEURL "$BASEURL"
|
||||
ENV CHANNELS "${CHANNELS:-1}"
|
||||
ENV BITRATE "${BITRATE:-32}"
|
||||
RUN (cd /var/www/html && curl -L https://github.com/JamesHeinrich/getID3/archive/refs/tags/v1.9.21.tar.gz | tar xzf - && mv getID3-* getid3)
|
||||
RUN (cd /var/www/html && curl -L https://github.com/mediaelement/mediaelement/archive/refs/tags/5.0.1.tar.gz | tar xzf - && mv mediaelement-* mediaelement)
|
||||
VOLUME /var/www/html/
|
||||
55
README.md
Normal file
55
README.md
Normal file
@@ -0,0 +1,55 @@
|
||||
StreamShifter
|
||||
=============
|
||||
|
||||
Capture streaming audio from your favorite sources to build a custom podcast stream. I used this for a number of years on a bog-standard Linux/Nginx/PHP-FPM server, and have recently containerized it for use with Docker. It'll record live audio captured from a URL, transcoding it into a common format along the way. You can listen to the most recent stream as it records with HTTP live streaming, or you can listen later to the podcast.
|
||||
|
||||
Standalone Instructions
|
||||
-----------------------
|
||||
|
||||
You'll need a webserver with PHP support, as well as curl and ffmpeg, which are used by the recording script. Unpack to an empty directory, and make sure the server is set to run index.php as an index file.
|
||||
|
||||
record.sh gets called (most likely as a cronjob) to capture and save audio. It needs to be called with the CHANNELS and BITRATE environment variables defined as described below.
|
||||
|
||||
cleanup.sh should be set up to run daily.
|
||||
|
||||
rss.php depends on the BASEURL environment variable:
|
||||
|
||||
```$baseurl=getenv("BASEURL");```
|
||||
|
||||
I don't know where offhand you'd set environment variables for a PHP script that's run by a webserver, so you might find it easier to hardcode your server's address here:
|
||||
|
||||
```$baseurl="https://streams.alfter.us/";```
|
||||
|
||||
Docker Instructions
|
||||
-------------------
|
||||
|
||||
This container builds on the php:fpm container to include tools and scripts for capturing streaming audio, transcoding it to AAC, and storing it as a set of HTTP live streams and a podcast.
|
||||
|
||||
Three environment variables are exposed to configure StreamShifter:
|
||||
|
||||
* BASEURL: base URL under which the site will be served (required)
|
||||
* CHANNELS: number of audio channels to encode (default 1)
|
||||
* BITRATE: encoding bitrate, kbps (default: 32)
|
||||
|
||||
The recordings and scripts to be served are stored in a named volume. Configure your webserver to serve it under a vhost by whatever means you would configure it to serve php:fpm.
|
||||
|
||||
Start the PHP FPM server with something like this:
|
||||
|
||||
```docker run -d --name streamshifter -e BASEURL=https://streamshifter.alfter.us/ -v streamshifter-data:/var/www/html salfter/streamshifter```
|
||||
|
||||
It runs on port 9000. You should set your webserver container to hand off PHP requests to it.
|
||||
|
||||
To schedule recordings, use the record.sh script in a running container (use ```docker exec```) with four parameters, all required:
|
||||
|
||||
* duration, seconds
|
||||
* short name for the stream (no spaces, used as part of filenames)
|
||||
* stream URL
|
||||
* long name for the stream (may have spaces, written into stream metadata)
|
||||
|
||||
Use whatever task scheduler your system provides (crontab, systemd timer unit, etc.) to run the script at the appropriate time. This example saves 10 seconds of the stream from KDWN, a talk-radio station in Las Vegas (streaming URL subject to change at their whim):
|
||||
|
||||
```docker exec -it streamshifter record.sh 10 test "https://14983.live.streamtheworld.com/KDWNAMAAC.aac?tdsdk=js-2.9&pname=TDSdk&pversion=2.9&banners=320x50&sbmid=ddc0cb91-c9a7-46c4-bf53-85841c22c8e4" Test```
|
||||
|
||||
There is also a cleanup script you can run periodically that will remove everything more than one week old:
|
||||
|
||||
```docker exec -it streamshifter cleanup.sh```
|
||||
@@ -1,5 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
source /etc/profile
|
||||
#PATH=$PATH:/usr/local/bin
|
||||
find /var/www/streamshifter/htdocs/audio -name \*.m4a -type f -mtime +7 -delete
|
||||
|
||||
find /var/www/html/audio -name \*.m4a -type f -mtime +7 -delete
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
source /etc/profile
|
||||
#PATH=$PATH:/usr/local/bin
|
||||
cd /var/www/streamshifter/htdocs
|
||||
mkdir -p /var/www/html/audio
|
||||
cd /var/www/html
|
||||
find . -name $2-\*.ts -delete
|
||||
find . -name $2\*.m3u8 -delete
|
||||
|
||||
#transopt="aac -ac 1 -ab 32k -ar 22050" # AAC-LC
|
||||
transopt="libfdk_aac -profile:a aac_he_v2 -ac 2 -ab 32k -ar 22050" # HE-AAC v2
|
||||
transopt="aac -ac ${CHANNELS} -ab ${BITRATE}k -ar 22050" # AAC-LC
|
||||
#transopt="libfdk_aac -profile:a aac_he_v2 -ac ${CHANNELS} -ab ${BITRATE}k -ar 22050" # HE-AAC v2 (requires Gentoo, or a source build)
|
||||
|
||||
curl -sm $1 "$3" 2>/dev/null | ffmpeg -i - -acodec $transopt -metadata title="$4 - `date +'%d %b %Y'`" -metadata artist="$4" -f segment -segment_list $2.m3u8 -segment_time 20 $2-%04d.ts 2>/dev/null
|
||||
ffmpeg -i $2.m3u8 -acodec copy -movflags +faststart -metadata title="$4 - `date +'%d %b %Y'`" -metadata artist="$4" audio/$2-`date +%Y%m%d`.m4a 2>/dev/null
|
||||
|
||||
16
rss.php
16
rss.php
@@ -2,7 +2,7 @@
|
||||
require_once('./getid3/getid3/getid3.php');
|
||||
$getid3=new getID3;
|
||||
|
||||
$baseurl="https://streamshifter.alfter.us/";
|
||||
$baseurl=getenv("BASEURL");
|
||||
|
||||
$doc = new DOMDocument("1.0", "UTF-8");
|
||||
$root = $doc->createElement("rss");
|
||||
@@ -15,12 +15,12 @@
|
||||
$latest=0;
|
||||
|
||||
$c=0; // sort entries into reverse chronological order
|
||||
if ($h=opendir("audio"))
|
||||
if ($h=opendir("/var/www/html/audio"))
|
||||
{
|
||||
while (($e=readdir($h))!==false)
|
||||
if (strlen($e)>4)
|
||||
if (substr_compare($e,".m4a",-4,4)===0)
|
||||
$arr[$c++]=filemtime("audio/".$e)." ".$e;
|
||||
$arr[$c++]=filemtime("/var/www/html/audio/".$e)." ".$e;
|
||||
closedir($h);
|
||||
}
|
||||
sort($arr, SORT_NUMERIC);
|
||||
@@ -32,7 +32,7 @@
|
||||
$item=$doc->createElement("item");
|
||||
|
||||
// extract title from metadata
|
||||
$fi=$getid3->analyze("audio/".$e);
|
||||
$fi=$getid3->analyze("/var/www/html/audio/".$e);
|
||||
getid3_lib::CopyTagsToComments($fi);
|
||||
$item->appendChild($doc->createElement("title",$fi["comments_html"]["title"][0]));
|
||||
$item->appendChild($doc->createElement("author",$fi["comments_html"]["artist"][0]));
|
||||
@@ -45,16 +45,16 @@
|
||||
$enclURL->value=$baseurl."audio/".$e;
|
||||
$encl->appendChild($enclURL);
|
||||
$enclLength=$doc->createAttribute("length");
|
||||
$enclLength->value=filesize($e);
|
||||
$enclLength->value=filesize("/var/www/html/audio/".$e);
|
||||
$encl->appendChild($enclLength);
|
||||
$enclType=$doc->createAttribute("type");
|
||||
$enclType->value="audio/mp4a-latm";
|
||||
$encl->appendChild($enclType);
|
||||
$item->appendChild($encl);
|
||||
$item->appendChild($doc->createElement("pubDate",gmdate(DATE_RSS,date(filemtime("audio/".$e)))));
|
||||
$item->appendChild($doc->createElement("pubDate",gmdate(DATE_RSS,date(filemtime("/var/www/html/audio/".$e)))));
|
||||
$channel->appendChild($item);
|
||||
if ($latest<filemtime("audio/".$e))
|
||||
$latest=filemtime("audio/".$e);
|
||||
if ($latest<filemtime("/var/www/html/audio/".$e))
|
||||
$latest=filemtime("/var/www/html/audio/".$e);
|
||||
}
|
||||
|
||||
$channel->appendChild($doc->createElement("lastBuildDate",gmdate(DATE_RSS,date($latest))));
|
||||
|
||||
Reference in New Issue
Block a user