A real-time IPTV TV guide, built for Tunarr and any XMLTV/M3U source.
A public demo instance is available here:
- Guide: guide.demo.johnnybegood.fr
- Health: guide.demo.johnnybegood.fr/health.php
- Setup (read-only on demo): guide.demo.johnnybegood.fr/setup.php
This demo runs with sample XMLTV feeds to showcase the interface.
- ποΈ 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
- π Program reminders β save a browser reminder from the program popup and get notified 15 minutes before airing
- π 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)
- ποΈ Category filter β narrow the guide to movies, sports, news, kids content, etc.
- π 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, with browser-side admin key memory for convenience
- π©Ί Admin health page β check config, XMLTV, M3U, response times, redirects, payload size, and metadata coverage/quality
- π¨οΈ 24h printable export β generate a polished daily schedule for PDF/print and PNG export
- π§ͺ Demo mode β drop a
.demofile 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
- 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
git clone https://github.com/Johnnybegood90/GridTV.git
cd GridTV
docker compose up -dThen 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:
PORT=9000 docker compose up -dTo back up or migrate your Docker setup, keep the data/ directory.
git clone https://github.com/Johnnybegood90/GridTV.git /var/www/gridtv
cd /var/www/gridtvchmod 775 /var/www/gridtv
chown -R www-data:www-data /var/www/gridtvThe 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:
# Debian/Ubuntu β adjust version to match your PHP
apt install php8.4-curl php8.4-xml
systemctl reload apache2 # or: systemctl reload nginxShow 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
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;
}
}nginx -t && systemctl reload nginxAdjust
php8.2-fpm.sockto match the PHP-FPM version installed on your server.
Apache vhost
<VirtualHost *:80>
ServerName guide.your-domain.com
DocumentRoot /var/www/gridtv
<Directory /var/www/gridtv>
AllowOverride All
Require all granted
</Directory>
</VirtualHost>a2enmod php8.4 rewrite
systemctl reload apache2Caddy
guide.your-domain.com {
root * /var/www/gridtv
php_fastcgi unix//run/php/php8.2-fpm.sock
file_server
}systemctl reload caddyIf you use
xcaddyor 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:
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.
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.
On the same browser, GridTV also remembers the admin key in localStorage so you do not need to type it again on every visit.
Admin tools are available here once unlocked with the same key:
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
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:
nano /var/www/gridtv/config.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,
"allow_personal_m3u": true
}Instances running the old single-source format (
epg_urlat root) are migrated automatically on first load.
Health page
The health page gives you a quick operational view of your instance without needing SSH first:
- checks whether
config.jsonexists and is writable by PHP - shows the current PHP version and whether
php-curl/php-xmlare loaded - tests each configured XMLTV source
- tests each configured M3U playlist when present
- shows response times, final URL after redirects, content type, and payload size
- highlights XMLTV coverage for things like
category,sub-title,rating,date,desc, logos, and stop times - gives you a rough XMLTV quality score per source
- shows M3U stream counts, HTTP vs HTTPS split, and duplicate stream URLs
By default, the page is protected by the same admin key as setup.php:
http://guide.your-domain.com/health.php
If you enable demo mode with a .demo file, visitors can open the page without the key.
For monitoring tools such as Uptime Kuma, GridTV also exposes a lightweight probe endpoint:
http://guide.your-domain.com/health.php?format=probe
- returns HTTP
200when the instance looks healthy - returns HTTP
503when config, PHP extensions, or the first XMLTV source fail - returns compact JSON suitable for external monitoring
If you prefer a plain text response:
http://guide.your-domain.com/health.php?format=probe&plain=1
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:
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
If you want to expose your instance publicly without letting visitors modify it, create an empty .demo file at the project root:
touch /var/www/gridtv/.demoWhen .demo exists:
setup.phpbecomes read-onlyhealth.phpcan be viewed without entering the admin key- visitors can inspect the current configuration safely
To disable demo mode:
rm /var/www/gridtv/.demo
.demois ignored by Git, just likeconfig.json.
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 # Program reminder modal + 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, <head>, 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, </body></html>
βββ js/ # JavaScript modules (one file per feature)
βββ config.js # Constants + PHP-injected vars (incl. locale)
βββ utils.js # Helpers, clock, EPG fetch
βββ epg.js # Grid rendering
βββ mobile.js # Mobile list view
βββ tooltip.js # Hover tooltip
βββ sources.js # Multi-EPG switcher + Personal EPG
βββ m3u.js # M3U parser
βββ player.js # HLS PiP player
βββ themes.js # Theme switcher
βββ search.js # Search + category filter
βββ reminders.js # Program reminders (localStorage + browser notifications)
βββ program.js # Program info modal + IMDb link
βββ favorites.js # Favorites (localStorage)
βββ updater.js # GitHub update checker
βββ live.js # Live updates + responsive
config.jsonis listed in.gitignoreβ your private URLs will never be pushed to GitHub.
Themes
GridTV ships with 4 built-in themes. To add your own, create a CSS file in themes/ with these metadata comments at the top:
/*
* @name My Theme
* @emoji π
*/
:root {
--bg: #0a0b0d;
--accent: #e8c842;
}Drop it in themes/ β it appears in the theme selector automatically.
Multiple EPG Sources
Configure as many EPG/M3U sources as you want in config.json. A dropdown appears in the topbar when more than one source is defined.
If allow_personal_epg is true, a "β Personal EPG" option appears in the dropdown, letting any visitor enter their own XMLTV/M3U URLs. Their choice is saved in localStorage β zero server impact.
Built-in Player
Click on any channel name or currently airing program to open a PiP player in the bottom-right corner.
Requires an M3U URL in the active source and the php-curl extension. The proxy.php handles HTTPβHTTPS relay transparently.
Advanced configuration
Tweak these constants in src/js/config.js:
const PX_PER_MIN = 5; // Horizontal zoom (pixels per minute)
const GRID_HOURS = 72; // Total timeline duration (hours)
const ROW_H = 80; // Channel row height (px)EPG Compatibility
GridTV parses the standard XMLTV format. Tested with:
Internationalization
GridTV auto-detects the visitor's browser language and serves the matching locale. Supported out of the box: English, French, Spanish.
To add a new language, create a file in locales/ based on an existing one:
cp locales/en.json locales/de.json
# then translate the valuesGridTV will automatically pick it up for browsers with that language set β no code changes needed.
Contributing
PRs are welcome! Got an idea, a fix, or a feature request β open an issue or send a PR.
Roadmap
- Timeline grid with live "now" indicator
- Mobile responsive list view
- Built-in HLS PiP player
- HTTPβHTTPS proxy
- Theme system (4 built-in themes)
- Multi-EPG sources with topbar switcher
- Personal EPG (localStorage)
- Modular codebase (src/tpl + src/js + src/css)
- Docker support
- Search β filter grid by channel name or program title/synopsis (debounced)
- Date in timebar β midnight markers with day/date in the ruler
- Program popup β full details + IMDb search link on any program click
- i18n β EN / FR / ES with browser auto-detection, extensible via locales/
- Favorites β pin channels to the top of the grid (localStorage)
- Setup re-editable β protected by an admin key, auto-generated on first setup
- Admin health page β diagnostics for config, XMLTV, M3U, timing, and metadata coverage
- Update notifications β topbar badge links to latest GitHub release
- Dark/Light auto mode β follow system preference when using the default theme
