# πΊ GridTV
> A real-time IPTV TV guide, built for [Tunarr](https://github.com/chrisbenincasa/tunarr) and any XMLTV/M3U source.





---

---
## π Demo
A public demo instance is available here:
π https://guide.demo.johnnybegood.fr/
Health page: https://guide.demo.johnnybegood.fr/health.php
Setup page (read only on demo): https://guide.demo.johnnybegood.fr/setup.php
This demo runs with sample XMLTV feeds to showcase the interface.
---
## β¨ Features
- ποΈ **Timeline grid** β horizontal EPG-style view with a live "now" indicator
- π
**Date in timebar** β midnight markers show the day/date so you always know where you are
- π± **Responsive** β optimized list view on mobile
- β±οΈ **Live progress bar** on the currently airing program
- πΆοΈ **Past programs** automatically dimmed
- π **Hover tooltip** β title, synopsis, season/episode, schedule, duration
- π¬ **Program popup** β click any program for full details + IMDb search link
- π **One-click copy** buttons for EPG & M3U URLs in the topbar
- βΆοΈ **Built-in HLS player** β click any channel or live program to watch in a PiP overlay
- π **HTTPβHTTPS proxy** β streams Tunarr over HTTP transparently from an HTTPS page
- π¨ **Theme system** β drop a CSS file in `themes/` and it appears in the menu automatically
- π‘ **Multi-EPG sources** β configure multiple EPG/M3U sources, switch from the topbar
- π€ **Personal EPG** β optionally let visitors use your instance with their own EPG/M3U URLs (saved in localStorage)
- π **Search** β filter the grid by channel name or program title/synopsis (debounced)
- β **Favorites** β pin channels to the top of the grid, persisted in localStorage
- π **i18n** β auto-detects browser language, supports EN / FR / ES (add your own in `locales/`)
- βοΈ **Re-editable setup** β protected by an admin key, no SSH required to update config
- π©Ί **Admin health page** β check config, XMLTV, M3U, response times, and metadata coverage
- π¨οΈ **24h printable export** β generate a polished daily schedule for PDF/print and PNG export
- π§ͺ **Demo mode** β drop a `.demo` file at the project root to expose admin pages safely in read-only mode
- π **Update notifications** β a badge appears in the topbar when a new release is available on GitHub
- π **Auto-reload** EPG every 30 minutes
- 0οΈβ£ **Zero build tooling** β vanilla PHP/JS/CSS, with bundled local assets
---
## π Installation
### Requirements
- A web server running **PHP 8.0+** with **php-curl** and **php-xml** extensions
- **Apache** or **Nginx** β or just use **Docker**
- An **EPG source in XMLTV format** (e.g. Tunarr, Jellyfin, xTeVe...)
- *(Optional)* An **M3U playlist** β required for the built-in player
---
### Option A β Docker (recommended)
```bash
git clone https://github.com/Johnnybegood90/GridTV.git
cd GridTV
docker compose up -d
```
Then open `http://localhost:8080` and follow the setup wizard.
Docker stores the generated configuration in `./data/config.json` on the host.
To run on a custom port:
```bash
PORT=9000 docker compose up -d
```
To back up or migrate your Docker setup, keep the `data/` directory.
---
### Option B β Native PHP host
#### 1. Clone the repo
```bash
git clone https://github.com/Johnnybegood90/GridTV.git /var/www/gridtv
cd /var/www/gridtv
```
#### 2. Set permissions
```bash
chmod 775 /var/www/gridtv
chown -R www-data:www-data /var/www/gridtv
```
#### 3. Install php-curl + php-xml
The built-in player uses `proxy.php` to relay HTTP streams over HTTPS, and the admin tools parse XMLTV directly. This requires **php-curl** and **php-xml**:
```bash
# Debian/Ubuntu β adjust version to match your PHP
apt install php8.4-curl php8.4-xml
systemctl reload apache2 # or: systemctl reload nginx
```
#### 4. Reverse proxy examples
Show reverse proxy / vhost examples
These examples are intentionally minimal. Replace `guide.your-domain.com` with your domain and adjust the PHP socket or upstream target to match your host.
Nginx + PHP-FPM
```nginx
server {
listen 80;
server_name guide.your-domain.com;
root /var/www/gridtv;
index index.php;
location / {
try_files $uri $uri/ /index.php;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
}
}
```
```bash
nginx -t && systemctl reload nginx
```
> Adjust `php8.2-fpm.sock` to match the PHP-FPM version installed on your server.
Apache vhost
```apache
ServerName guide.your-domain.com
DocumentRoot /var/www/gridtv
AllowOverride All
Require all granted
```
```bash
a2enmod php8.4 rewrite
systemctl reload apache2
```
Caddy
```caddy
guide.your-domain.com {
root * /var/www/gridtv
php_fastcgi unix//run/php/php8.2-fpm.sock
file_server
}
```
```bash
systemctl reload caddy
```
> If you use `xcaddy` or a distro package, keep the same site block and only adapt the PHP-FPM socket path.
Traefik (Docker labels)
Use this if GridTV runs in Docker and Traefik is your front proxy:
```yaml
services:
gridtv:
build: .
volumes:
- ./data:/data
labels:
- "traefik.enable=true"
- "traefik.http.routers.gridtv.rule=Host(`guide.your-domain.com`)"
- "traefik.http.routers.gridtv.entrypoints=websecure"
- "traefik.http.routers.gridtv.tls=true"
- "traefik.http.services.gridtv.loadbalancer.server.port=80"
```
You still need a running Traefik instance with `websecure` configured and DNS pointing to it.
#### 5. First launch β Setup
Open your browser at `http://guide.your-domain.com`.
GridTV detects the missing config and automatically redirects you to the setup page:
| Field | Description |
|---|---|
| **Group name** | Displayed top-left in the topbar |
| **EPG sources** | Add one or more XMLTV sources, each with an optional M3U URL |
| **Personal EPG** | Toggle to allow visitors to use your instance with their own EPG/M3U |
Once submitted, `config.json` is created on the server. **The setup page becomes inaccessible until you re-enter your admin key.**
Admin tools are available here once unlocked with the same key:
```text
http://guide.your-domain.com/health.php
http://guide.your-domain.com/export.php
```
`health.php` is admin-only by default. `export.php` is public so visitors can print or save the daily guide.
---
Editing the config later
### Editing the config later
You can re-open the setup page at any time using the admin key generated during first setup:
```
http://guide.your-domain.com/setup.php
```
Enter your admin key to unlock the configuration form. The key is stored in `config.json` under `admin_key` β if you lose it, you can retrieve it there via SSH.
Or edit `config.json` directly:
```bash
nano /var/www/gridtv/config.json
```
```json
{
"group_name": "MyGroup TV",
"epg_sources": [
{
"name": "Main",
"epg_url": "http://192.168.0.3:8000/api/xmltv.xml",
"m3u_url": "http://192.168.0.3:8000/api/channels.m3u"
},
{
"name": "Sports",
"epg_url": "http://192.168.0.3:8001/api/xmltv.xml",
"m3u_url": ""
}
],
"allow_personal_epg": true
}
```
> Instances running the old single-source format (`epg_url` at root) are **migrated automatically** on first load.
---
Health page
## π©Ί Health page
The health page gives you a quick operational view of your instance without needing SSH first:
- checks whether `config.json` exists and is writable by PHP
- shows the current PHP version and whether `php-curl` / `php-xml` are loaded
- tests each configured XMLTV source
- tests each configured M3U playlist when present
- shows response times, final URL after redirects, content type, and basic metadata counts
- highlights XMLTV coverage for things like `category`, `sub-title`, `rating`, and `date`
By default, the page is protected by the same admin key as `setup.php`:
```text
http://guide.your-domain.com/health.php
```
If you enable demo mode with a `.demo` file, visitors can open the page without the key.
---
24h export
## π¨οΈ 24h export
The export page builds a printable 24-hour TV guide from the current XMLTV source:
- filter by source
- choose the target date
- restrict the export to a time window (`00h-24h`, morning, afternoon, evening, prime time)
- limit the number of visible channels
- choose between a readable card layout or a dense timeline
- export to PDF via the browser print dialog
- export to PNG directly from the page
Public URL:
```text
http://guide.your-domain.com/export.php
```
This page is intentionally public so you can share a βTV guide sheetβ with other people.
---
Demo mode
## π§ͺ Demo mode
If you want to expose your instance publicly without letting visitors modify it, create an empty `.demo` file at the project root:
```bash
touch /var/www/gridtv/.demo
```
When `.demo` exists:
- `setup.php` becomes read-only
- `health.php` can be viewed without entering the admin key
- visitors can inspect the current configuration safely
To disable demo mode:
```bash
rm /var/www/gridtv/.demo
```
> `.demo` is ignored by Git, just like `config.json`.
---
Project structure
## π Project structure
```
gridtv/
βββ index.php # Entry point
βββ setup.php # Setup + re-configuration (admin key protected)
βββ health.php # Admin diagnostics page
βββ export.php # 24h printable export (PDF/image friendly)
βββ proxy.php # HTTPβHTTPS stream proxy
βββ version.json # Current version (used for update check)
βββ Dockerfile
βββ docker-compose.yml
βββ .gitignore # local instance files excluded
βββ locales/ # i18n translation files
β βββ en.json
β βββ fr.json
β βββ es.json
βββ themes/ # CSS theme files
β βββ default.css
β βββ magazine.css
β βββ cyberpunk.css
β βββ steampunk.css
βββ src/
βββ config.php # Config loader, migration, locale detection
βββ css/ # CSS modules (one file per feature)
β βββ base.css # Variables, reset, global layout
β βββ topbar.css # Topbar, nav, source switcher, theme selector
β βββ grid.css # Grid, channels, programs, ruler, tooltip, loading
β βββ mobile.css # Mobile list view
β βββ player.css # HLS PiP player
β βββ modals.css # Personal EPG modal
β βββ search.css # Search bar + highlight
β βββ program.css # Program info modal + midnight marker
β βββ favorites.css# Favorite button + separator
β βββ updater.css # Update notification badge
βββ tpl/ # HTML templates
β βββ head.php # DOCTYPE, , includes CSS modules
β βββ topbar.php # Topbar (nav, source switcher, search, theme)
β βββ grid.php # Grid + mobile + PiP + overlays
β βββ modals.php # Program info modal + Personal EPG modal
β βββ footer.php # JS modules loader,