Plok
Plok is a lightweight static podcast site generator written in Bash.
You can download the code for plok here.
It scans a directory of MP3 files, extracts metadata using ffprobe, and generates a complete podcast website and RSS feed from simple HTML and XML templates.
Generated output:
index.htmlrss.xml
Plok is designed to be:
- Simple
- Fast
- Portable
- Easy to modify
- Friendly to standard Unix tools
No databases, JavaScript frameworks, package managers, or build systems are required.
Features
- Static HTML generation
- Podcast RSS feed generation
- MP3 metadata extraction
- Simple template system
- Configurable date formatting
- Configurable episode ordering
- Recursive or non-recursive media scanning
- Automatic HTML escaping
- Automatic URL linkification
- Generation timestamp support
- Global template variables
- Minimal dependencies
- Pure Bash
Requirements
Plok requires:
- Bash
- ffprobe (part of FFmpeg)
- sed
Debian / Ubuntu
sudo apt install ffmpeg sedArch Linux
sudo pacman -S ffmpeg sedFedora
sudo dnf install ffmpeg sedInstallation
Clone or download the repository:
git clone https://example.com/plok.git
cd plokMake the script executable:
chmod +x plokRun directly:
./plokOr install system-wide:
sudo install -m755 plok /usr/local/bin/plokThen:
plokProject Structure
Plok expects the following layout:
project/
├── media/
│ ├── 20260120-whatsapp-with-opus.mp3
│ ├── 20260123-plok.mp3
│ └── ...
├── site.conf
├── template.html
├── template.xml
├── item.html
├── item.xml
├── plok
├── index.html
└── rss.xml
Source files:
site.conftemplate.htmltemplate.xmlitem.htmlitem.xml
Generated files:
index.htmlrss.xml
MP3 Filename Format
Every MP3 must begin with a date.
Format:
YYYYMMDD-title.mp3
Examples:
20260123-plok.mp3
20260220-owncast.mp3
20260404-mini-motorways.mp3
The date is used to generate:
- Human-readable website dates
- RSS publication dates
Files without a valid leading date will cause Plok to stop with an error.
Metadata
Plok reads metadata using ffprobe.
The following tags are used:
| Tag | Purpose |
|---|---|
| TITLE | Episode title |
| DESCRIPTION | Episode synopsis |
Example:
ffmpeg -i input.wav
-metadata TITLE="Episode Title"
-metadata DESCRIPTION="Episode description."
output.mp3If no TITLE tag exists, Plok uses:
Untitled
Configuration
Configuration is stored in:
site.conf
Example:
date_format: %d %B %Y
recursive: false
order: reverse
url: https://example.com
Configuration Options
date_format
Controls how episode dates appear on the website.
Example:
date_format: %d %B %Y
Produces:
23 January 2026
recursive
Controls whether Plok scans subdirectories inside media/.
recursive: true
or
recursive: false
order
Controls episode ordering.
Newest first:
order: reverse
Oldest first:
order: forward
url
The base URL of your website.
Example:
url: https://example.com
A trailing slash is optional. Plok removes any trailing slash internally, so the following are treated identically:
url: https://example.com
url: https://example.com/
This value is used to generate:
- RSS enclosure URLs
- RSS item links
- The
{{siteurl}}template variable
Templates
Plok uses four template files.
| File | Purpose |
|---|---|
template.html |
Main HTML page |
item.html |
HTML for each episode |
template.xml |
Main RSS feed |
item.xml |
RSS item for each episode |
Global Template Variables
The following placeholders may be used in template.html and template.xml.
{{items}}
Required.
Replaced with all generated episode entries.
{{generated}}
Optional.
Replaced with the date and time the site was generated.
Example:
<footer>
Generated: {{generated}}
</footer>{{siteurl}}
Optional.
Replaced with the base URL from site.conf.
Example:
<link rel="canonical" href="{{siteurl}}">Result:
<link rel="canonical" href="https://example.com">Because Plok normalises the configured URL, {{siteurl}} never includes a trailing slash.
template.html
Example:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My Podcast</title>
<link rel="canonical" href="{{siteurl}}">
</head>
<body>
<h1>Episodes</h1>
{{items}}
<footer>
Generated: {{generated}}
</footer>
</body>
</html>item.html
Available placeholders:
| Placeholder | Description |
|---|---|
{{title}} |
Episode title |
{{date}} |
Formatted date |
{{url}} |
MP3 path |
{{synopsis}} |
Episode synopsis |
Example:
<article>
<h2>{{title}}</h2>
<div class="article-time">
<time>{{date}}</time>
</div>
<audio controls preload="none">
<source src="{{url}}">
</audio>
<div class="synopsis">
{{synopsis}}
</div>
</article>template.xml
Example:
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>My Podcast</title>
<link>{{siteurl}}</link>
<lastBuildDate>{{generated}}</lastBuildDate>
{{items}}
</channel>
</rss>item.xml
Available placeholders:
| Placeholder | Description |
|---|---|
{{title}} |
Episode title |
{{description}} |
Episode description |
{{link}} |
Full episode URL |
{{pubDate}} |
Publication date |
{{length}} |
File size in bytes |
Example:
<item>
<title><![CDATA[{{title}}]]></title>
<description><![CDATA[{{description}}]]></description>
<link>{{link}}</link>
<guid>{{link}}</guid>
<pubDate>{{pubDate}}</pubDate>
<enclosure url="{{link}}" length="{{length}}" type="audio/mpeg"/>
</item>Description Formatting
Episode descriptions are automatically:
- HTML escaped
- URL linkified
RSS descriptions are left unchanged.
Usage
Run Plok from the project directory:
plokor
./plokGenerated files:
index.htmlrss.xml
Philosophy
Plok intentionally avoids:
- Databases
- JavaScript frameworks
- Static site generators
- Build systems
- Complex templating engines
The goal is to remain:
- Small
- Readable
- Portable
- Hackable
- Unix-friendly