diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e392c0..80d56f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# 📑 Changelog +# đź“‘ Changelog All notable changes to this project will be documented here. Format based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -14,6 +14,34 @@ This project follows [Semantic Versioning](https://semver.org/). --- +## [v3.0.1] - 2025-10-07 +### Added +- **EPG Fallback System** + - Channels without XMLTV data now display “No Guide Data Available”. + - Added `apply_epg_fallback()` helper to ensure all channels have at least one program entry. + - Fallback automatically applied after login and tuner switch. +- **Invalid XML Detection** + - If a user enters the same `.m3u` URL for both M3U and XML, the system treats it as invalid XML and loads fallback placeholders instead. +- **Visual Placeholders** + - `guide.html` updated to render gray, italicized “No Guide Data Available” banners in program grid. + - Works across all existing Light/Dark/Retro themes. + +### Changed +- **Tuner Switching Behavior** + - Active tuner now refreshes immediately without requiring logout/relogin. + - Cached channel/EPG data reloaded dynamically when tuner changes. +- **Login Page UI** + - Redesigned with floating centered box, shadow, and right-aligned RetroIPTVGuide logo. + +### Fixed +- **EPG Cache Sync** + - Prevented guide from displaying outdated EPG after tuner change. + - Corrected case where missing XML data produced empty grid. + + +--- + + ## [3.0.0] - 2025-10-03 ### Added - **Windows Support**: @@ -72,7 +100,7 @@ This project follows [Semantic Versioning](https://semver.org/). - Shows dynamic system info: version, release date, Python version, OS, install path, database path, logs path, uptime. - **Admin Log Management**: - Log file size shown on Logs page (human-readable + color-coded). - - Admin-only “Clear Logs” button added to truncate activity log. + - Admin-only “Clear Logs” button added to truncate activity log. - **Automated Version Bump Tool**: - Added `bump_version.py` to sync `APP_VERSION` in app.py with CHANGELOG.md. - Inserts new version section under `[Unreleased]`. @@ -99,7 +127,7 @@ This project follows [Semantic Versioning](https://semver.org/). - Data is pulled from `app.py` constants and runtime environment, no manual edits required. #### Notes -- **Windows validation pending**: Installer and uninstaller are implemented but require verification on Windows; tracked in ROADMAP “Priority Suggestions”. +- **Windows validation pending**: Installer and uninstaller are implemented but require verification on Windows; tracked in ROADMAP “Priority Suggestions”. --- @@ -119,7 +147,7 @@ This project follows [Semantic Versioning](https://semver.org/). -## [v2.0.0] – 2025-09-24 +## [v2.0.0] – 2025-09-24 ### Added - Tuner URL validation: new validate_tuner_url() function checks XML/M3U inputs before saving. - Detects invalid/empty URLs, unresolvable hostnames, and distinguishes between public vs. private IPs. @@ -157,7 +185,7 @@ This project follows [Semantic Versioning](https://semver.org/). --- -## [v1.x.x] – 2025-09-01 → 2025-09-23 +## [v1.x.x] – 2025-09-01 → 2025-09-23 ### Added - Initial IPTV Flask application with: - User authentication (login/logout, password change). diff --git a/README.md b/README.md index e6c9542..739ad57 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,39 @@ # RetroIPTVGuide -![Version](https://img.shields.io/badge/version-v3.0.0-blue) +![Version](https://img.shields.io/badge/version-v3.0.1-blue) RetroIPTVGuide is an IPTV Web Interface inspired by 90s/2000s cable TV guides. It is designed to work with [ErsatzTV](https://ersatztv.org/) [(GitRepo)](https://github.com/ErsatzTV/ErsatzTV/tree/main) but should support any `.m3u` and `.xml` IPTV source. -⚠️ **Note:** This is still a BETA release. It is not recommended for direct Internet/public-facing deployments. +โ��๏ธ� **Note:** This is still a BETA release. It is not recommended for direct Internet/public-facing deployments. - [Installation / Uninstall Guide](INSTALL.md) - [Changelog](CHANGELOG.md) - [Roadmap](ROADMAP.md) - [License](LICENSE) -## ✨ Features (v3.0.0) -- 🔑 **User Authentication** +## โ�จ Features (v3.0.0) +- ๐�”‘ **User Authentication** - Login/logout system with hashed passwords. - Admin and regular user accounts. - Password change functionality. - Admin-only user management (add/delete users). -- 📡 **Tuner Management** +- ๐�“ก **Tuner Management** - Multiple tuner support stored in `tuners.db`. - Switch between active tuners via the web UI. - Update `.m3u` and `.xml` tuner URLs (persisted in DB). -- 📺 **Guide & Playback** - - Program guide rendered from XMLTV. - - Channel list parsed from M3U playlist. +- ๐�“บ **Guide & Playback** + - Program guide rendered from XMLTV with automatic fallback for missing data. + - Channels without guide entries display “No Guide Data Available”. - Video playback using HTML5 + HLS.js. - Playback events logged with user + channel + timestamp. -- 📑 **Logging** +- ๐�“‘ **Logging** - Activity log (`activity.log`) records authentication events, tuner changes, playback, and admin actions. - Admin-only **Logs page** with real-time log viewing. - Log file size display with color coding. - - Admin-only “Clear Logs” button to truncate logs. -- 🎨 **UI Enhancements** - - Unified header across all pages (Guide, Logs, Add User, Delete User, Change Password, Change Tuner). + - Admin-only โ€�Clear Logsโ€� button to truncate logs. +- ๐��จ **UI Enhancements** + - Unified header across all pages (Guide, Logs, Add User, Delete User, Change Password, Change Tuner, Login). - Active tuner display + live clock in header. - **Themes submenu** with multiple options: - Light @@ -41,8 +41,8 @@ It is designed to work with [ErsatzTV](https://ersatztv.org/) [(GitRepo)](https: - AOL/CompuServe - TV Guide Magazine - Theme persistence stored in browser localStorage, applied instantly across all pages. - - **About Page under Settings menu** — shows version, Python, OS, uptime, paths. -- ⚙️ **System** + - **About Page under Settings menu** โ€” shows version, Python, OS, uptime, paths. +- โ��๏ธ� **System** - Automatic initialization of `users.db` and `tuners.db` on first run. - SQLite databases use WAL mode for better concurrency. - Preloads tuner/channel/guide data from DB on startup. @@ -52,7 +52,7 @@ It is designed to work with [ErsatzTV](https://ersatztv.org/) [(GitRepo)](https: --- -## 🌐 Browser Compatibility +## ๐��� Browser Compatibility This project is designed to work with **all major browsers**. It has been tested on: - Firefox @@ -60,7 +60,7 @@ It has been tested on: - Safari - Edge -## 💻 Tested Devices & OS +## ๐�’ป Tested Devices & OS The web interface has been tested on: - **Ubuntu (desktop/server)** - **iOS (mobile/tablet)** @@ -70,31 +70,31 @@ The web interface has been tested on: - **MacOS** - **Windows** -## 🛠️ Installation Platform +## ๐���๏ธ� Installation Platform - **Debian Based Linux (desktop/server)** - **Windows 10/11** -## 📺 Screenshots +## ๐�“บ Screenshots -## 📺 Guide Page +## ๐�“บ Guide Page ![Guide Screenshot](docs/screenshots/guide.png) -## 📺 Video Pop Out +## ๐�“บ Video Pop Out ![Video Pop Out](docs/screenshots/guide_with_video_breakout.png) -## 📺 Video Pop Out on Desktop +## ๐�“บ Video Pop Out on Desktop ![Desktop Pop Out](docs/screenshots/video_breakout_desktop.png) -## 📺 TV Guide Magazine Theme +## ๐�“บ TV Guide Magazine Theme ![TV Guide Theme](docs/screenshots/TV_Guide_Theme.png) -## 📺 AOL / CompuServe Theme +## ๐�“บ AOL / CompuServe Theme ![AOL / CompuServe Theme](docs/screenshots/AOL_Compuserve_Theme.png) --- -## 🤝 Contributing -Contributions are welcome! Here’s how you can help: +## ๐�ค� Contributing +Contributions are welcome! Hereโ€�s how you can help: 1. **Report Issues**: Found a bug or want to suggest a feature? Open an [issue](../../issues). 2. **Submit Pull Requests**: Fork the repo, make changes, and submit a PR. Please ensure code is tested before submitting. 3. **Improve Documentation**: Help refine the installation guide, add screenshots, or improve explanations in the README. diff --git a/ROADMAP.md b/ROADMAP.md index fc8290f..7704779 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -10,7 +10,7 @@ These are **not yet implemented**, but provide a development path for future rel ### 1. Tuner Management - [x] Add ability to **add/remove tuners** from the UI (v2.3.0). - [x] Add ability to rename tuners via the UI (v2.3.0). -- [ ] Support for **.m3u8 single-channel playlists** as tuner sources. +- [ ] Support for **.m3u8 single-channel playlists** as tuner sources (planned v3.1.0). - Option A: Special-case `.m3u8` handling in parser. - Option B: Add explicit `hls` column to `tuners.db`. - [x] Validate tuner URLs (ping/check format before saving) (v2.0.0). @@ -26,8 +26,8 @@ These are **not yet implemented**, but provide a development path for future rel ### 3. Guide & Playback - [ ] Add **search/filter box** to guide for channels/programs. - [ ] Add ability to set **favorites** for quick channel access. +- [x] Add fallback message (“No Guide Data Available”) for channels missing EPG info (v3.0.1). - [ ] Add **reminders/notifications** for upcoming programs. -- [ ] (Future) Support recording/saving streams for offline playback. ### 4. User Management - [ ] Add role-based access control (admin, regular user, read-only). diff --git a/app.py b/app.py index b0cfbb3..47e405e 100644 --- a/app.py +++ b/app.py @@ -1,4 +1,4 @@ -APP_VERSION = "v3.0.0" +APP_VERSION = "v3.0.1" APP_RELEASE_DATE = "2025-09-26" from flask import Flask, render_template, request, redirect, url_for, flash @@ -225,6 +225,11 @@ def parse_m3u(m3u_url): # ------------------- XMLTV EPG Parsing ------------------- def parse_epg(xml_url): programs = {} + + # ✅ Handle when user pastes same .m3u for XML + if xml_url.lower().endswith(('.m3u', '.m3u8')): + return programs # empty, fallback will fill it later + try: r = requests.get(xml_url, timeout=15) r.raise_for_status() @@ -261,6 +266,23 @@ def parse_epg(xml_url): programs[cid].append({'title': title, 'desc': desc, 'start': start, 'stop': stop}) return programs +# ------------------- EPG Fallback Helper ------------------- +def apply_epg_fallback(channels, epg): + """Ensure each channel has at least one program entry, even if missing in XML.""" + for ch in channels: + tvg_id = ch.get('tvg_id') + if not tvg_id: + continue + if tvg_id not in epg or not epg[tvg_id]: + epg[tvg_id] = [{ + 'title': 'No Guide Data Available', + 'desc': '', + 'start': None, + 'stop': None + }] + return epg + + # ------------------- Routes ------------------- @app.route('/') def home(): @@ -282,6 +304,8 @@ def login(): global cached_channels, cached_epg cached_channels = parse_m3u(m3u_url) cached_epg = parse_epg(xml_url) + # ✅ Apply “No Guide Data Available” fallback + cached_epg = apply_epg_fallback(cached_channels, cached_epg) return redirect(url_for('guide')) else: log_event(username if username else "unknown", "Failed login attempt") @@ -414,6 +438,16 @@ def change_tuner(): log_event(current_user.username, f"Switched active tuner to {new_tuner}") flash(f"Active tuner switched to {new_tuner}") + # ✅ Refresh cached guide data immediately + global cached_channels, cached_epg + tuners = get_tuners() + m3u_url = tuners[new_tuner]["m3u"] + xml_url = tuners[new_tuner]["xml"] + cached_channels = parse_m3u(m3u_url) + cached_epg = parse_epg(xml_url) + # ✅ Apply “No Guide Data Available” fallback + cached_epg = apply_epg_fallback(cached_channels, cached_epg) + elif action == "update_urls": tuner = request.form["tuner"] xml_url = request.form["xml_url"] @@ -485,6 +519,18 @@ def guide(): total_width = slots * SLOT_MINUTES * SCALE minutes_from_start = (now - grid_start).total_seconds() / 60.0 now_offset = int(minutes_from_start * SCALE) + + # --- DEBUG: Show alignment between M3U and EPG --- + #print("\n=== DEBUG: Cached Channels and EPG Keys ===") + #print("First 5 channel IDs from M3U:") + #for ch in cached_channels[:5]: + # print(" ", ch.get('tvg_id')) + + #print("\nFirst 5 EPG keys:") + #for key in list(cached_epg.keys())[:5]: + # print(" ", key) + #print("==========================================\n") + return render_template( 'guide.html', diff --git a/install.sh b/install.sh index 56d3bd5..d17c79a 100644 --- a/install.sh +++ b/install.sh @@ -1,7 +1,7 @@ #!/bin/bash set -e -VERSION="2.1.0" +VERSION="3.0.1" TIMESTAMP=$(date +"%Y-%m-%d_%H-%M-%S") LOGFILE="install_${TIMESTAMP}.log" @@ -220,6 +220,14 @@ else echo "Unsupported environment: $OSTYPE" exit 1 fi + +echo "" +echo "Installation complete!" +echo "End time: $(date)" +echo "Access the server in your browser at: http://:5000" +echo "Default login: admin / strongpassword123" +echo "NOTE: This is a **BETA build**. Do not expose it directly to the public internet." +echo "" } if [ "$ENVIRONMENT" = "GITBASH" ]; then diff --git a/templates/guide.html b/templates/guide.html index 91f8371..cc1ad11 100644 --- a/templates/guide.html +++ b/templates/guide.html @@ -208,6 +208,15 @@ body.retro-magazine .now-line { background:#000; height:3px; } body.retro-magazine #current-tuner { color:#000000 !important; font-weight:bold; } + /* Placeholder when no guide data exists */ + .program.no-guide { + font-style: italic; + color: #999; + background: #2a2a2a; + border: 1px dashed #555; + } + + @@ -299,22 +308,35 @@

Program Info

- {% set cid = ch.tvg_id %} - {% for prog in epg.get(cid, []) %} - {% if prog.start and prog.stop %} - {% set left = ((prog.start - grid_start).total_seconds()/60) * SCALE %} - {% set calc_width = (prog.stop - prog.start).total_seconds()/60 * SCALE %} - {% set width = 24 if calc_width < 24 else calc_width %} -
- {{ prog.title }} -
- {% endif %} - {% endfor %} + {% set cid = ch.tvg_id %} + {% set channel_epg = epg[cid] if cid in epg else [] %} + {% if channel_epg|length == 0 %} +
+ No Guide Data Available +
+ {% else %} + {% for prog in channel_epg %} + {% if prog.title == 'No Guide Data Available' %} +
+ {{ prog.title }} +
+ {% elif prog.start and prog.stop %} + {% set left = ((prog.start - grid_start).total_seconds()/60) * SCALE %} + {% set calc_width = (prog.stop - prog.start).total_seconds()/60 * SCALE %} + {% set width = 24 if calc_width < 24 else calc_width %} +
+ {{ prog.title }} +
+ {% endif %} + {% endfor %} + {% endif %}
diff --git a/templates/login.html b/templates/login.html index fbcead0..a8efcb1 100644 --- a/templates/login.html +++ b/templates/login.html @@ -69,7 +69,7 @@

Login

{% with messages = get_flashed_messages() %} {% if messages %} {% for msg in messages %} -

{{ msg }}

+

{{ msg }}

{% endfor %} {% endif %} {% endwith %}