chriswere.wales

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:

Plok is designed to be:

No databases, JavaScript frameworks, package managers, or build systems are required.


Features


Requirements

Plok requires:

Debian / Ubuntu

sudo apt install ffmpeg sed

Arch Linux

sudo pacman -S ffmpeg sed

Fedora

sudo dnf install ffmpeg sed

Installation

Clone or download the repository:

git clone https://example.com/plok.git
cd plok

Make the script executable:

chmod +x plok

Run directly:

./plok

Or install system-wide:

sudo install -m755 plok /usr/local/bin/plok

Then:

plok

Project 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:

Generated files:


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:

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.mp3

If 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:


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:

RSS descriptions are left unchanged.


Usage

Run Plok from the project directory:

plok

or

./plok

Generated files:


Philosophy

Plok intentionally avoids:

The goal is to remain: