{"id":13561329,"url":"https://github.com/Moonbase59/azuracast_xmltv","last_synced_at":"2025-04-03T17:30:37.354Z","repository":{"id":201059679,"uuid":"706852451","full_name":"Moonbase59/azuracast_xmltv","owner":"Moonbase59","description":"Create rich XMLTV Tuner, EPG and RSS feeds from an AzuraCast Web Radio.","archived":false,"fork":false,"pushed_at":"2024-06-28T18:49:39.000Z","size":693,"stargazers_count":12,"open_issues_count":0,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-11-04T13:37:15.675Z","etag":null,"topics":["azuracast","epg","iptv","iptv-m3u","radio","rss","rss-feed","rss-generator","tv","webradio","xmltv","xmltv-epg-data"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Moonbase59.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"custom":["https://www.paypal.com/donate/?hosted_button_id=PBPR63362LDEU"]}},"created_at":"2023-10-18T18:35:58.000Z","updated_at":"2024-09-14T10:37:57.000Z","dependencies_parsed_at":null,"dependency_job_id":"aa9692dd-6f3c-495a-b533-10c5a387569d","html_url":"https://github.com/Moonbase59/azuracast_xmltv","commit_stats":null,"previous_names":["moonbase59/azuracast_xmltv"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Moonbase59%2Fazuracast_xmltv","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Moonbase59%2Fazuracast_xmltv/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Moonbase59%2Fazuracast_xmltv/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Moonbase59%2Fazuracast_xmltv/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Moonbase59","download_url":"https://codeload.github.com/Moonbase59/azuracast_xmltv/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247046754,"owners_count":20874715,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["azuracast","epg","iptv","iptv-m3u","radio","rss","rss-feed","rss-generator","tv","webradio","xmltv","xmltv-epg-data"],"created_at":"2024-08-01T13:00:54.926Z","updated_at":"2025-04-03T17:30:37.025Z","avatar_url":"https://github.com/Moonbase59.png","language":"Python","funding_links":["https://www.paypal.com/donate/?hosted_button_id=PBPR63362LDEU"],"categories":["Miscellaneous Utilities"],"sub_categories":[],"readme":"# azuracast_xmltv\n\n## Create rich XMLTV Tuner, EPG and RSS feeds from an [AzuraCast](https://www.azuracast.com/) Web Radio.\n\n**If you like what you got, please consider to [![Donate with Paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/donate/?hosted_button_id=PBPR63362LDEU). Thank you! ❤️**\n\n\u003e _Don’t miss the [Discussions](https://github.com/Moonbase59/azuracast_xmltv/discussions)! New versions are announced there, and useful hints._\n\n\u003e _**Did you know? You can follow my [commits](https://github.com/Moonbase59/azuracast_xmltv/commits.atom), the [discussions](https://github.com/Moonbase59/azuracast_xmltv/discussions.atom) or just the [announcements](https://github.com/Moonbase59/azuracast_xmltv/discussions/categories/announcements.atom) using your RSS/Atom feed reader!** Just copy-paste the links into your reader as a new subscription and you’ll never miss anything again._\n\nThe big picture behind this is to create **standards-compliant files** to\n- **easily access your station(s)** from almost any media player or server\n- get **better distribution** by providing interested listeners with\n  - an easy access to your stations and streams\n  - an electronic program guide (EPG) so they can actively tune in to your shows\n  - RSS Feeds with show info and direct links to the web player \u0026 streams\n\nTherefore I recommend regenerating the files periodically and **linking to them on your website**, so your listeners can point their media centers/players directly at these links and stay up-to-date with your station(s) automatically.\n\nUnfortunately, there isn’t a \"standard\" location for this yet. Maybe we should all start using `https://domain.tld/xmltv` for that, and\n- put an `index.html` (or other) file there that lists the available links in human-readable form,\n- use this as a base location for the `m3u` and `xml` files (i.e., get your station data by pointing to `https://domain.tld/xmltv/station.m3u` and get the EPG by pointing to `https://domain.tld/xmltv/domain.tld.xml`).\n- from version 0.10.0 on, a gzip-compressed version can be made available at `https://domain.tld/xmltv/domain.tld.xml.gz`.\n\nIt would make things so much easier for server operators and listeners alike.\n\nYou’ll get **one M3U file per station** (containing that station’s streams), **one RSS file per station** (containing that station’s scheduled programs, web player and stream links), and **one XML EPG file per AzuraCast instance** (containing the scheduled programs for all your stations). Since v0.14.0, you also get an **additional M3U file per AzuraCast instance** (containing ALL station’s streams).\n\n### Some screenshots: This is what it looks like\n\nWhen validated, you can import your M3U \"tuner\" and the XML EPG into your media center and **enjoy a beautiful LiveTV EPG** (and playout, of course):\n\n![Live-TV – Mozilla Firefox_004](https://github.com/Moonbase59/azuracast_xmltv/assets/3706922/81ad9ccb-6d37-45e5-bc00-cb9ba5eff678)  \n_EPG in Jellyfin_\n\n![screenshot00001](https://github.com/Moonbase59/azuracast_xmltv/assets/3706922/765941a4-a4be-4bfd-8e22-f8fbf8f2559d)  \n_EPG in KODI_\n\nSome applications have no EPG support (yet), but are still nice to use. **You can use the M3U tuner file `azuracast_xmltv` generates** with these:\n\n![Moonbase_001](https://github.com/Moonbase59/azuracast_xmltv/assets/3706922/4a5f84a0-64e9-4360-87dd-58e836fd1610)  \n_Hypnotix, the Linux Mint IPTV player (no EPG yet)_\n\n![Robert Long - Komisch_001](https://github.com/Moonbase59/azuracast_xmltv/assets/3706922/aa22201a-0c37-47ae-8f03-11c54f6c3b72)  \n_Celluloid, a GTK+ frontend for mpv (no EPG)_\n\n![News (1324) - cloud syvi net – Mozilla Firefox_001](https://github.com/Moonbase59/azuracast_xmltv/assets/3706922/20cb0cbe-7f38-4fc9-9b8a-07747207e29c)  \n_Station RSS 2.0 Feed, as seen by Nextcloud (v0.12.0 and up)_\n\n\nNote all this _may_ work on Windows machines, but I don’t know. I’m a Linux guy. You’ll run it on your AzuraCast server anyway, right?\n\n\n## Installation\n\n- Download `azuracast_xmltv`, put it in a location that’s in your path (on Linux servers, you can use `/usr/local/bin`, on desktop systems `~/.local/bin` or `~/bin` is usually also good).\n- On a remote server, you can use _wget_ or _curl_ to download it:\n  ```bash\n  wget azuracast_xmltv https://raw.githubusercontent.com/Moonbase59/azuracast_xmltv/master/azuracast_xmltv\n  ```\n  ```bash\n  curl -o azuracast_xmltv https://raw.githubusercontent.com/Moonbase59/azuracast_xmltv/master/azuracast_xmltv\n  ```\n- Make it executable (`chmod +x azuracast_xmltv`).\n- Optionally, open in a text editor and modify defaults near the beginning of the file.\n\nPerform a trial run, just executing `azuracast_xmltv`. Depending on your operating system, chances are that not all required Python modules are installed, and `azuracast_xmltv` will complain about that.\n\nOn Debian-like systems, you can easily install the missing modules using `pip3`. So let’s assume `azuracast_xmltv` complains: `ModuleNotFoundError: No module named 'lxml'`. Just go and install it:\n```bash\npip3 install lxml\n```\n\nThen try again until all required modules are there. You have to do this only once. (On my Ubuntu 22.04 AzuraCast server, only the modules `lxml` and `tzlocal` were missing.)\n\n_Hint:_ If you don’t even have `pip3`, a `sudo apt install python3-pip` helps. ;-)\n\nFor every new (or modified) station, use the `-m`/`--m3u` option on the first run, to generate its M3U file. On further runs, this can be omitted and `azuracast_xmltv` will only generate fresh EPG XML files.\n\nOn servers, just set up a _cron job_ for the software to update the EPG periodically.\nLet’s assume you have saved the program as `/usr/local/bin/azuracast_xmltv`.\n\nUse `crontab -e` to edit your crontab file, and add an entry like this:\n```crontab\n# get new EPG data for Jellyfin every 12 hours, 2 minutes past the hour\n# avoiding clashes with the AzuraCast demo instance just being reset.\n2 */12 * * * /usr/local/bin/azuracast_xmltv -u https://demo.azuracast.com\n```\nAdd any command line options you want, of course.\n\nIf you don’t want the success/failure mails, simply send its output to `/dev/null`:\n```crontab\n# get new EPG data for Jellyfin every 12 hours, 2 minutes past the hour\n# avoiding clashes with the AzuraCast demo instance just being reset.\n2 */12 * * * /usr/local/bin/azuracast_xmltv -u https://demo.azuracast.com \u003e /dev/null\n```\n\n### Install on a real AzuraCast server (Ubuntu 22.04)\n\nProbably after trying out the above on a local machine, you might want to install `azuracast_xmltv` on your real AzuraCast server. Let’s assume you run AzuraCast on a remote Ubuntu 22.04 server in a Docker container (standard install) and you wish the output files to be available under `https://yourdomain.com/xmltv` as I suggested.\n\n1. Log into your AzuraCast and create an API key for use with `azuracast_xmltv`. You’ll find the function under _My Account_. **Copy the key to a safe place and _keep it secret_!** After creating, **you will not be able to view the key again!**\n\n   ![Auswahl_285](https://github.com/Moonbase59/azuracast_xmltv/assets/3706922/79d6c031-d9d7-47d5-bcd4-eb3888d2df93)\n\n2. `ssh` into your AzuraCast server instance and become `root` (`sudo su`).\n\n3. Install `azuracast_xmltv` as shown above. Put it in `/usr/local/bin`.\n\n4. Change into the `/var/azuracast` folder and create a subfolder `xmltv`. This will later hold the files for your AzuraCast website.\n\n   ```bash\n    cd /var/azuracast\n    mkdir xmltv\n    ```\n\n5. Modify the `docker-compose.override.yml` file and add an entry for the `xmltv` folder in the `volumes` section:\n\n   ```bash\n   nano docker-compose.override.yml\n   ```\n\n   ```yaml\n   services:\n     web:\n       volumes:\n         - /var/azuracast/xmltv:/var/azuracast/www/web/xmltv\n   ```\n\n6. Restart your AzuraCast so it can pick up the new settings:\n\n   ```bash\n   docker-compose down\n   docker-compose up -d\n   ```\n\n7. Create a _crontab_ entry to run `azuracast_xmltv` periodically, let’s say twice a day, at 2 minutes past the hour:\n\n   ```bash\n   crontab -e\n   ```\n\n   ```crontab\n   2 */12 * * * /usr/local/bin/azuracast_xmltv -o /var/azuracast/xmltv -u https://yourdomain.com -a 'your_api_key_here' -f -m -t \u003e /dev/null\n   ```\n\n   Save the file.\n\n   I left the `-m` option in here (will create M3U files), since my mounts might change, but you can also leave it out and create the M3U manually once. I also set the `-f` option to create _filler_ program entries. Use any options you want here.\n\n   _Note:_ I also use the new `-t`/`--tvgurl` option here, since your files can now be found under your server’s `/xmltv/` path. This allows more modern software (like KODI) to automatically find the EPG data file (XML) belonging to the M3U.\n\n8. Now change into the `xmltv` folder and run `azuracast_xmltv` once to check there are no errors, and get the initial set of files. You don’t want to wait for the next automatic update—it might be almost 12 hours away… Use _the exact same command you put into the crontab_ for this, just to be sure everything works.\n\n   ```bash\n   /usr/local/bin/azuracast_xmltv -o /var/azuracast/xmltv -u https://yourdomain.com -a 'your_api_key_here' -f -m -t -g\n   ```\n   Don’t forget to include the `-m` option, so it’ll create M3U files.\n\n9. You should now be able to access the XMLTV data on your public AzuraCast website:\n\n   ```\n   https://yourdomain.com/xmltv/yourstation.m3u\n   https://yourdomain.com/xmltv/yourdomain.com.xml\n   https://yourdomain.com/xmltv/yourdomain.com.xml.gz\n   ```\n\n   Instead of `yourstation`, use the _station shortcode_ you have used when setting up your station. This is the \"URL Stub\" you might have changed under _Edit Station Profile → Profile_. You can see this field only in Advanced Mode:\n\n   ![Auswahl_284](https://github.com/Moonbase59/azuracast_xmltv/assets/3706922/08b51d0b-0a3b-4e88-a340-4abe3afc6193)\n\n   _Hint:_ Even if underscores `_` are shown in the example, **only use alphanumeric** characters and digits, and the hyphen `-` to comply with [RFC2838](https://www.rfc-editor.org/rfc/rfc2838.html). Underscores are not allowed in a domain name.\n\n\n11. **Congratulations!** You can now **publish the above links on your website so your listeners will know where to point their media players and where to get the EPG!**\n\n    Try it out using any media center or player I mentioned, or just do a quick test with an audio player like _Audacious_ or _VLC_.\n\n    If your client supports the compressed gzip-format (.gz) for the EPG, you should use it. It reduces transmission time and bandwidth.\n\n    And don’t forget to **log out** from your AzuraCast `ssh` session.\n\n12. Optional fine-tuning:\n\n    a) If your M3U shows some non-public streams and you don’t want that, use the `-p` option to only include streams that are marked _public_ in AzuraCast.\n\n    b) You might want to edit the programme _text_ that `azuracast_xmltv` generates for the EPG listings and adapt these for your stations. The code is very well documented, just `ssh` into your server and edit it: `sudo nano /usr/local/bin/azuracast_xmltv`. The template texts near the beginning of the file are highly configurable (they use `{moustache}`-type variables) so you should be able to adapt to your needs easily.\n\n\n## Usage\n\nFrom the help screen:\n```\nusage: azuracast_xmltv [-h] [-v] [-u URL] [-i URL] [-c URL] [-d DAYS] [-f]\n                       [-o FOLDER] [-a APIKEY] [-p] [-m] [--group GROUP] [-r]\n                       [-t] [-g] [--rss]\n\nCreate XMLTV Tuner, EPG and RSS Feed files from an AzuraCast Web Radio.\n\noptions:\n  -h, --help            show this help message and exit\n  -v, --version         show program's version number and exit\n  -u URL, --url URL     base URL to an AzuraCast instance (default:\n                        https://demo.azuracast.com)\n  -i URL, --icon URL    URL to a channel icon; will use station's default\n                        album art if omitted (default: None)\n  -c URL, --customplayer URL\n                        URL to a custom web player; modifies {player_url} and\n                        {request_url} variables (default: None)\n  -d DAYS, --days DAYS  number of days in the future [1-30] to include in the\n                        EPG (default: 7)\n  -f, --fillgaps        fill gaps between programmes with a 'General Rotation'\n                        entry (default: False)\n  -o FOLDER, --output FOLDER\n                        output folder for XMLTV files (default: )\n  -a APIKEY, --apikey APIKEY\n                        AzuraCast API key; allows creating much better EPG\n                        data, see below (default: None)\n  -p, --public          include only public stations \u0026 streams (default:\n                        False)\n  -m, --m3u             create M3U XMLTV Tuner file(s); only needed on first\n                        run or after changes in AzuraCast (default: False)\n  --group GROUP         set M3U 'group-title' to something other than station\n                        name (see below) (default: )\n  -r, --radio           add 'radio=\"true\"' tags to M3U #EXTINF; allows\n                        distinction between radio and TV channels (see below)\n                        (default: True)\n  -t, --tvgurl          add 'tvg-url' tags to M3U file; allows software to\n                        find the corresponding EPG automatically (see below)\n                        (default: False)\n  -g, --gzip            additionally output a gzip-compressed (.gz) version of\n                        the EPG XML file; many clients can use this format,\n                        and it reduces transmission time and bandwidth\n                        (default: True)\n  --rss                 create/update RSS 2.0 Feed(s); one feed per station\n                        (default: False)\n\nazuracast_xmltv can create XMLTV M3U Tuner, XML EPG and RSS 2.0 Feed files for\nboth your own and other AzuraCast stations.\n\nFor much better programme data to be generated, create an AzuraCast API key\nand use the -a/--apikey option, which allows:\n  - using otherwise invisible mounts, like an added video stream,\n  - showing listener request info if a playlist has requests enabled\n  - adding a presenter image on live shows, and (mis-)using the streamer\ncomment field as a description\n  - showing extra info for syndicated content (remote playlists)\n\n--group GROUP modifies the 'group-title' in M3U Tuner files. It normally\ncontains the station name. Using this, you can group all stations of an\nAzuraCast server under a common name, or use something like 'Radio-DE' to\nmerge with larger, existing lists. A very few clients, like KODI, support\nmultiple groups. Separate these with a semicolon ';'. Empty values will be\nreplaced by the station name: ';Radio-DE' --\u003e 'Your Station;Radio-DE',\n'Radio;;Radio-DE' --\u003e 'Radio;Your Station;Radio-DE'.\n\n-r/--radio adds a 'radio=\"true\"' tag to the M3U #EXTINF lines, if a stream’s\ndisplay name doesn’t contain any of the words 'Video', 'TV', 'Testbild',\n'mpeg', 'mpg', 'm2t', 'm2ts', 'ts' (you can customize this list).\nThis is for software like KODI, which can distinguish between Radio and TV\nchannels and displays these in separate menus.\n\n-t/--tvgurl adds 'url-tvg' and 'x-tvg-url' tags to the M3U Tuner files. This\nhelps media center software like KODI to automatically locate the\ncorresponding EPG data file, but only works if the generated M3U and XML files\nare available under the '/xmltv' path of your AzuraCast server.\nSee installation instructions at\nhttps://github.com/Moonbase59/azuracast_xmltv.\n\nOutput files are named after the (sanitized) station shortcode (\"URL Stub\" in\nAzuraCast), and the server base URL.\n\nEdit './azuracast_xmltv' using a text editor to change some defaults near the\ntop of the file. No worries, everything is well documented.\n\nPlease report any issues to\nhttps://github.com/Moonbase59/azuracast_xmltv/issues.\n```\n\n## Sample output from the AzuraCast demo station\n\n```bash\nazuracast_xmltv -u https://demo.azuracast.com -m\n```\n\n### XMLTV Tuner file (one per station)\nXMLTV/IPTV \"Tuner\" files are M3U files, in a special `#EXTM3U` format.\n\n`azuracast_xmltv` names its output file after your station’s **shortcode** (called _URL Stub_ in the UI). Ideally, this should only contain alphanumeric characters and the '-' (minus or hyphen).\n\n**Sample XMLTV Tuner file `azuratest_radio.m3u`**\n\n```m3u\n#EXTM3U\n#EXTINF:-1 tvg-name=\"/radio.mp3 (128kbps MP3)\" tvg-id=\"azuratest_radio.demo.azuracast.com\" group-title=\"AzuraTest Radio\" tvg-logo=\"https://demo.azuracast.com/api/station/1/art/0\",/radio.mp3 (128kbps MP3)\nhttps://demo.azuracast.com/listen/azuratest_radio/radio.mp3\n#EXTINF:-1 tvg-name=\"/mobile.mp3 (64kbps MP3)\" tvg-id=\"azuratest_radio.demo.azuracast.com\" group-title=\"AzuraTest Radio\" tvg-logo=\"https://demo.azuracast.com/api/station/1/art/0\",/mobile.mp3 (64kbps MP3)\nhttps://demo.azuracast.com/listen/azuratest_radio/mobile.mp3\n#EXTINF:-1 tvg-name=\"AzuraTest Radio (HLS)\" tvg-id=\"azuratest_radio.demo.azuracast.com\" group-title=\"AzuraTest Radio\" tvg-logo=\"https://demo.azuracast.com/api/station/1/art/0\",AzuraTest Radio (HLS)\nhttps://demo.azuracast.com/hls/azuratest_radio/live.m3u8\n```\n\nBeginning with `azuracast_xmltv` version 0.7.0, the M3U file entries (mount points) will be _sorted_ by their display name, and the _default mount_ put at the top.\n\nBeginning with `azuracast_xmltv` version 0.18.0 and AzuraCast Rolling Release\n\\#c8bcee0 (2023-12-20 1:55), HLS streams set as default will \"trump\" Icecast/Shoutcast\ndefault streams and be put at the top.\n\nThis is mainly intended for players that immediately start playing when opening an M3U file (they should play the default mount first), but also helpful for humans. We just like sorted lists. ;-) Putting HLS first provides easier use for \"road warriors\", on smartphones and car stereos, due to automatic bandwidth adjustment in areas with varying reception conditions.\n\nMore modern playout/media center software like KODI can automatically find the EPG file that corresponds to a M3U, thus reducing manual intervention and setup.\n\nBeginning with version 0.7.0, you can use the `-t`/`--tvgurl` option to enable this feature. It _requires_ that your generated M3U and XML are reachable from the Internet under your AzuraCast server’s `/xmltv/` path. See the Installation instructions on how to achieve this.\n\n### XMLTV Electronic Program Guide (EPG) file (one per server)\nXMLTV/IPTV EPG data files are XML files containing channel and program information. They must be compliant with the [XMLTV DTD](https://github.com/XMLTV/xmltv/blob/master/xmltv.dtd) and can be validated using the `tv_validate_file` tool, which can be installed on Debian-like systems with `sudo apt install xmltv-util`.\n\nThis package brings some other nice utilities, just try `tv_to_text \u003cyourstation\u003e.xml`.\n\nThe XML files `azuracast_xmltv` produces are roughly named after [RFC2838 - Uniform Resource Identifiers for Television Broadcasts](https://www.rfc-editor.org/rfc/rfc2838.html). They _look_ like DNS names, but _aren’t_, really. We use these names as TV Guide \"channel IDs\", to hold the M3U and XML data together, so that automated tools like [xTeVe](https://github.com/xteve-project/xTeVe), and media servers like [Jellyfin](https://jellyfin.org/) or [KODI](https://kodi.tv/) can auto-assign channels and you have less work.\n\nThere are some dumber media servers (like Plex, for an example) that don’t handle many M3U/XML files well. This is the reason why we create only _one_ EPG data file per AzuraCast instance (including all its stations).\n\n\n**Sample XMLTV EPG file `demo.azuracast.com.xml`**\n\n```xml\n\u003c?xml version='1.0' encoding='UTF-8'?\u003e\n\u003c!DOCTYPE tv SYSTEM \"xmltv.dtd\"\u003e\n\u003ctv date=\"20231018210640 +0200\" source-info-url=\"https://demo.azuracast.com\" source-info-name=\"demo.azuracast.com\" generator-info-name=\"azuracast_xmltv 0.5.0\" generator-info-url=\"https://github.com/Moonbase59/azuracast_xmltv\"\u003e\n  \u003cchannel id=\"azuratest_radio.demo.azuracast.com\"\u003e\n    \u003cdisplay-name\u003eAzuraTest Radio\u003c/display-name\u003e\n    \u003cicon src=\"https://demo.azuracast.com/api/station/1/art/0\"/\u003e\n  \u003c/channel\u003e\n\u003c/tv\u003e\n```\n\nToo sad, the AzuraCast demo instance has nothing on schedule right now… Let me show you part of an EPG file from my evaluation station (URLs modified):\n\n**Sample XMLTV EPG file `niteradio.example.com.xml`**\n\n```xml\n\u003c?xml version='1.0' encoding='UTF-8'?\u003e\n\u003c!DOCTYPE tv SYSTEM \"xmltv.dtd\"\u003e\n\u003ctv date=\"20231018214854 +0200\" source-info-url=\"https://example.com\" source-info-name=\"example.com\" generator-info-name=\"azuracast_xmltv 0.5.0\" generator-info-url=\"https://github.com/Moonbase59/azuracast_xmltv\"\u003e\n  \u003cchannel id=\"niteradio.example.com\"\u003e\n    \u003cdisplay-name\u003eNite Radio\u003c/display-name\u003e\n    \u003cicon src=\"https://example.com/api/station/1/art/0\"/\u003e\n  \u003c/channel\u003e\n  \u003cprogramme start=\"20231018060000 +0200\" stop=\"20231018120000 +0200\" channel=\"niteradio.example.com\"\u003e\n    \u003ctitle lang=\"en\"\u003ePop (requests enabled)\u003c/title\u003e\n    \u003csub-title lang=\"en\"\u003eYour favorite station, YOUR music!\u003c/sub-title\u003e\n    \u003cdesc lang=\"en\"\u003ePlaylist: Pop\n\nThe request lines are open! Make this program YOURS by adding a request.\n\nGo to https://example.com/public/niteradio, click on ›Request Song‹ and select your favorite.\n\u003c/desc\u003e\n    \u003ccredits/\u003e\n    \u003ccategory lang=\"en\"\u003eMusic\u003c/category\u003e\n  \u003c/programme\u003e\n  \u003cprogramme start=\"20231018120000 +0200\" stop=\"20231018160000 +0200\" channel=\"niteradio.example.com\"\u003e\n    \u003ctitle lang=\"en\"\u003eLive: Moonbase\u003c/title\u003e\n    \u003cdesc lang=\"en\"\u003eStreamer: Moonbase\n\nLive Show, hosted by Moonbase.\n\nIn his unmistakable way, Moonbase presents highlights of music history: Kraut and progressive rock, classic, glam and hard rock. Some metal, too. Some of his shows are centered around the finest electronic music in existence, most notably the Berlin School.\n\nYou CAN be excited. Or just have fun!\n\u003c/desc\u003e\n    \u003ccredits\u003e\n      \u003cpresenter\u003eMoonbase\u003cimage type=\"person\"\u003ehttps://example.com/api/station/1/streamer/1%7C1678618644/art\u003c/image\u003e\u003c/presenter\u003e\n    \u003c/credits\u003e\n    \u003ccategory lang=\"en\"\u003eMusic\u003c/category\u003e\n  \u003c/programme\u003e\n\n  ...\n\n  \u003cprogramme start=\"20231021180000 +0200\" stop=\"20231022000000 +0200\" channel=\"niteradio.example.com\"\u003e\n    \u003ctitle lang=\"en\"\u003eHeart Dance from London, UK\u003c/title\u003e\n    \u003cdesc lang=\"en\"\u003ePlaylist: Heart Dance from London, UK\n\n(Syndicated content.)\n\u003c/desc\u003e\n    \u003ccredits/\u003e\n    \u003ccategory lang=\"en\"\u003eMusic\u003c/category\u003e\n  \u003c/programme\u003e\n  \u003cprogramme start=\"20231022000000 +0200\" stop=\"20231022060000 +0200\" channel=\"niteradio.example.com\"\u003e\n    \u003ctitle lang=\"en\"\u003eNuit électronique (requests enabled)\u003c/title\u003e\n    \u003csub-title lang=\"en\"\u003eYour favorite station, YOUR music!\u003c/sub-title\u003e\n    \u003cdesc lang=\"en\"\u003ePlaylist: Nuit électronique\n\nThe request lines are open! Make this program YOURS by adding a request.\n\nGo to https://example.com/public/niteradio, click on ›Request Song‹ and select your favorite.\n\u003c/desc\u003e\n    \u003ccredits/\u003e\n    \u003ccategory lang=\"en\"\u003eMusic\u003c/category\u003e\n  \u003c/programme\u003e\n\u003c/tv\u003e\n```\n\nNow _this_ looks like some program, right?\n\n## RSS 2.0 Feeds (one per station)\n\nFrom version 0.12.0, `azuracast_xmltv` can optionally generate **RSS 2.0 Feeds**, too. Using the `--rss` option, these can be created/updated together with the EPG in one go. We produce one file per station, which can easily be cached for great performance.\n\nThe RSS feeds build on the already existing, flexible customization logic and can even use HTML code (although some readers strip some HTML elements). You get a chronological feed with all your shows, image, description, web player and stream links. My generator uses modern technologies, `\u003cmedia:…\u003e` elements and all.\n\n### Here are some screenshots\n\n![Auswahl_296](https://github.com/Moonbase59/azuracast_xmltv/assets/3706922/07b69b8c-9dbe-444d-9bb9-a48f39682bdb)  \n_Station RSS Feed as seen by Nextcloud._\n\nScheduled programmes are presented in a chronological list, click on a list entry for more information, or click the globe icon to directly jump to the station’s web player to make a request.\n\n![News (1324) - cloud syvi net – Mozilla Firefox_001](https://github.com/Moonbase59/azuracast_xmltv/assets/3706922/c0bc9a8e-3cb2-4fdf-8571-c5bea147f7e4)  \n_Single programme as seen by Nextcloud_\n\nThe description is highly configurable, as with the EPG, but you can even use HTML here.\n\nA click on the image opens the web player.\n\n![Nite Radio – Mozilla Firefox_032](https://github.com/Moonbase59/azuracast_xmltv/assets/3706922/3ff33950-cde5-4bf1-b821-89d6a824795a)  \n_Station RSS Feed as seen by the Firefox \"Livemarks\" extension_\n\nThe RSS feed looks good even in the most basic feed reader. A click on the title opens the web player, but here you also have _direct links to all the station’s streams_. Listeners enjoy the information, and your station is always only one click away.\n\n![Liferea_001](https://github.com/Moonbase59/azuracast_xmltv/assets/3706922/fff3ad14-da01-4104-b94a-4bb66eaa80c8)  \n_Station RSS Feed as seen by Liferea (a famous Linux feed reader)_\n\n![Liferea_002](https://github.com/Moonbase59/azuracast_xmltv/assets/3706922/e05bd58d-a684-470d-ad37-0228eabfc951)  \n_You can even use the web player within some feed readers_\n\nThe RSS 2.0 Feeds we generate are _standards-compliant_ and usually _validate_ just fine. Some validators have problems with feed items lying in the future, that can safely be ignored.\n\nWe use the most _modern technologies_ and the _most compatible_ set of features, so chances are good that a feature _is already there_ when feed readers finally catch up. Currently, not every feed reader supports all features.\n\n\n## A note on mount point display names\n\nIt makes no sense for an EPG to have a zillion entries called `/radio.mp3 (128kbps MP3)`. Seriously. A station might not even have a logo, so how would you distinguish all these in a large EPG?\n\nThe above will happen when you leave the mount point _Display Name_ empty, it’s just AzuraCast’s default.\n\nMy suggestion: Change the _Display Name_ of your station’s mount points to something meaningful that everyone can easily find and distinguish in the EPG. `azuracast_xmltv` will _automatically pick up the change_ when you run it with the `-m`/`--m3u` option next time.\n\nAs an example, I used\n- Nite Radio (128kbps AAC)\n- Nite Radio (128kbps MP3)\n- Nite Radio Testbild\n- Nite Radio Video-Stream\n\nThe HLS stream (if you have one), will automatically be named `\u003cYour Station Name\u003e (HLS)`.\n\n### Distinguishing audio and video streams\n\nSome media center software (like _KODI_) can distinguish between \"Radio\" and \"TV\" and puts streams under \"Radio\" and \"TV\" menus, respectively. The distinction is made by a flag `radio=\"true\"`on the M3U `#EXTINF` lines.\n\nFrom version 0.11.0, `azuracast_xmltv` fully supports this feature if the `-r`/`--radio` option is used (default).\n\n`azuracast_xmltv` will look for a list of keywords in your stream name, and flag it a video stream if any of the following keywords are found in the stream display name: `Video`, `TV`, `Testbild`, `mpeg`, `mpg`, `m2t`, `m2ts`, `ts`. The comparison is case-insensitive, also expanding some language-specific characters, like the German \"ß\" to \"ss\".\n\nYou can edit the default keyword list near the beginning of the file. It is called `videostream_keywords`.\n\nIn _KODI_ this will later look like this:\n\n![screenshot00010](https://github.com/Moonbase59/azuracast_xmltv/assets/3706922/32f4657e-9b8c-413a-8a25-fb07e4053ecb)  \n_Radio: audio streams only_\n\n![screenshot00009](https://github.com/Moonbase59/azuracast_xmltv/assets/3706922/6cf1fe7b-4342-45af-870b-2738f543fdc4)  \n_TV: video streams only_\n\n### Grouping streams and stations\n\n_To be supplied._\n\nRead the built-in help in the meantime: `azuracast_xmltv --help`.\n\n\n## Fill the gaps between scheduled shows\n\nYou don’t have many scheduled shows but a 24/7 station and **want to show that something is playing** in the EPG?\n\n`azuracast_xmltv` has the concept of _fillers_. Just use the `-f`/`--fillgaps` option and it will create nice programme entries for the times in between shows, and your listeners will know something is playing on the station.\n\nThe filler logic takes the playlist names that constitute your general rotation (enabled, type default, not on schedule), so **name your playlists wisely**. Let’s say you had a general rotation built of the 'Classic Rock', 'Folk Rock' and 'Hard Rock' playlist. `azuracast_xmltv` then generates a `{playlists}` variable for you that looks like `Classic Rock, Folk Rock \u0026 Hard Rock` and can be used in the configuration.\n\nThe default filler **can be configured in the options** near the start of the script file:\n\n```python\n# \"Gap Filler\" text to be shown when a programme is actually a gap filler.\n# After parsing, the gap_filler_title will be used as playlist name and the result\n# RE-PARSED by the requests_enabled parser, if ANY of the involved playlists has\n# requests enabled. Descriptions from here and the requests enabled parsing will\n# be APPENDED to each other (the request text coming beneath).\n\n# this will be used as a programme's title\ngap_filler_title = \"24/7 Rock\"\n# this will be used as a programme's subtitle\ngap_filler_subtitle = \"{playlists}\"\n# this will be the programme's description\n# Your text should make sense even if {playlists} is empty!\n# Multiple successive blanks will be automatically replaced by a single blank.\ngap_filler_description = \"\"\"Your favorite sound, 24 hours a day, 7 days a week.\nThe best {playlists} in {year} — just here, on {station_name}.\"\"\"\n```\n\nWith our example, the generated EPG playlist entry would then look like this:\n\n```xml\n\u003cprogramme start=\"20231020170000 +0200\" stop=\"20231020180000 +0200\" channel=\"niteradio.example.com\"\u003e\n  \u003ctitle lang=\"en\"\u003e24/7 Rock\u003c/title\u003e\n  \u003csub-title lang=\"en\"\u003eClassic Rock, Folk Rock \u0026amp; Hard Rock\u003c/sub-title\u003e\n  \u003cdesc lang=\"en\"\u003eYour favorite sound, 24 hours a day, 7 days a week.\nThe best Classic Rock, Folk Rock \u0026amp; Hard Rock in 2023 — just here, on Nite Radio.\n\nProgram Copyright © 2023 Nite Radio — Non-public test \u0026amp; evaluation server only\nVisit us on https://example.com\u003c/desc\u003e\n  \u003ccredits/\u003e\n  \u003ccategory lang=\"en\"\u003eMusic\u003c/category\u003e\n\u003c/programme\u003e\n```\n\n(The copyright part coming from another element, namely the `append_to_description` text.)\n\n\n## Validation and other nice tools\n\nBefore going public, you should always check if your setup _validates_ as correct XMLTV data.\n\nOn Debian-like systems, install the `xmltv-util` package:\n```bash\nsudo apt install xmltv-util\n```\nthen _validate_ your EPG file using:\n```bash\ntv_validate_file niteradio.example.com.xml\nValidated ok.\n```\n\nHere is another nice trick: Output the EPG as text in your terminal!\n\n```\ntv_to_text niteradio.example.com.xml\n10-18 (Wednesday)\n\n06:00--12:00\tPop (requests enabled) // Your favorite station, YOUR music!\tNite Radio\n12:00--16:00\tLive: Moonbase\tNite Radio\n\n10-21 (Saturday)\n\n18:00--00:00\tHeart Dance from London, UK\tNite Radio\n\n10-22 (Sunday)\n\n00:00--06:00\tNuit électronique (requests enabled) // Your favorite station, YOUR music!\tNite Radio\n\nGenerated from example.com by azuracast_xmltv 0.5.0.\n```\n\nOr even with long descriptions:\n\n```\ntv_to_text --with-desc radio.niteradio.net.xml\n10-18 (Wednesday)\n\n06:00--12:00\tPop (requests enabled) // Your favorite station, YOUR music!\tNite Radio\tPlaylist: Pop  The request lines are open! Make this program YOURS by adding a request.  Go to https://example.com/public/niteradio, click on ›Request Song‹ and select your favorite.\n12:00--16:00\tLive: Moonbase\tNite Radio\tStreamer: Moonbase  Live Show, hosted by Moonbase.  In his unmistakable way, Moonbase presents highlights of music history: Kraut and progressive rock, classic, glam and hard rock. Some metal, too. Some of his shows are centered around the finest electronic music in existence, most notably the Berlin School.  You CAN be excited. Or just have fun!\n\n10-21 (Saturday)\n\n18:00--00:00\tHeart Dance from London, UK\tNite Radio\tPlaylist: Heart Dance from London, UK  (Syndicated content.)\n\n10-22 (Sunday)\n\n00:00--06:00\tNuit électronique (requests enabled) // Your favorite station, YOUR music!\tNite Radio\tPlaylist: Nuit électronique  The request lines are open! Make this program YOURS by adding a request.  Go to https://example.com/public/niteradio, click on ›Request Song‹ and select your favorite.\n\nGenerated from example.com by azuracast_xmltv 0.5.0.\n```\n\n## API Key\n\n`azuracast_xmltv` _will_ work with any AzuraCast instance that uses scheduled programming, even stations that might not be your own. Please don’t overuse this—polling for a fresh EPG every 12, 24, or 48 hours is usually enough!\n\nWhen using _your own station_, I recommend setting up an API key in your AzuraCast and use the `-a`/`--apikey` option, which will enable enhanced functionality and provide a much richer EPG.\n\nIf you use an API key, it will allow:\n- using otherwise invisible mounts, like an added video stream\n- customizable Live Show title/sub-title/description (streamer/DJ info)*\n- customizable Live Show DJ info (mis-using Streamer Comment field)\n- Live Show DJ image (if provided in AzuraCast); this is added as an\n  image of type \"person\" in the programme's `\u003cpresenter\u003e` info.\n  Not all clients may support this (they'll just ignore it).\n- customizable listener requests info (for playlists that have requests enabled)\n- customizable syndication info (for playlists that are remote streams)\n- gap filler `{playlists}` info\n\n\\* This also works without an API key.\n\n## Making `azuracast_xmltv` your own\n\nThis is just a Python script, so you can open it using a text editor. (Windows users: No editors that produce a BOM, please! Use something like [Notepad++](https://notepad-plus-plus.org/).)\n\nNear the beginning of the file, you’ll find many user-customizable options, most notably the program title/sub-title and description text to go with…\n- _live shows_ (streamer/DJ),\n- _syndicated content_ (remote playlists),\n- playlists that have _listener requests_ enabled,\n- _gap filler_ programmes, and\n- _RSS Feeds_ (HTML allowed here).\n\nYou can also set defaults for most options here. These will be shown when `--help` is invoked.\n\n### `azuracast_xmltv` as a Python module\n\n`azuracast_xmltv` can be used and imported as a Python module. Just rename (or symlink) it to have a `.py` file extension.\n\nAll usual module functions should work, like `import`, help and documentation with tools like `pdoc`.\n\n### `{moustache}`-type variables that can be used in customization\n\nUsing this type of marking a _variable_ part of text is commonly used. While executing, `azuracast_xmltv` will replace these with the actual content, for example replace `{year}` with `2023` (if the programme starts in 2023). `azuracast_xmltv` will also automatically remove any leftover extra whitespace after replacing the variables.\n\n\"Extra whitespace\" is, for instance, multiple blanks in succession or extra linefeeds at the end of the text. This can happen when a variable to be replaced is actually empty.\n\n**An example:**\n\nYou might have specified `The best {playlists} in {year}.` in the `gap_filler_description`, which would normally expand to `The best Classic Rock, Folk Rock \u0026 Hard Rock in 2023.`\n\nBut you haven’t used an _API key_, which is needed to use `{playlists}`, so we would get `The best  in 2023.` The double space looks awful, right?\n\n`azuracast_xmltv` will automatically detect this and correct to `The best in 2023.` Much better!\n\n#### Always available\n\n- `{station_name}`\n- `{station_description}`\n- `{station_website}` — the station website URL (this is _not_ the AzuraCast URL)\n- `{player_url}`\\*\\* — station’s web player URL\n- `{year}` — start year of the programme\n- `{category}` — global category, usually \"Music\"\n\n#### In `requests_enabled`\n\n- `{playlist}` — playlist name\n- `{request_url}`\\*\\* — station’s web player URL\n\n#### In `remote`\\*\n\n- `{playlist}` — playlist name\n- `{remote_url}` — remote URL used in the playlist\n\n#### In `live`\n\n- `{presenter}` — streamer/DJ name\n- `{image_url}`\\* — streamer’s image URL\n- `{comments}`\\* — AzuraCast’s Streamer Comments field content.  \n  _Note:_ Comments should be used with care: This field was originally meant for _internal_ remarks only, **you could leak data!**\n\n#### In `gap_filler`\n\n- `{playlists}`\\* — comma-separated list of playlist names that make up the general rotation (enabled, type default, not on schedule).  \n  So if your general rotation was made up of the playlists `Classic Rock`, `Folk Rock` and `Hard Rock`, it would show `Classic Rock, Folk Rock \u0026 Hard Rock`.\n\n#### In `rss_feed_description`\n\n- `{title}` — programme title\n- `{subtitle}` — programme sub-title\n- `{airdate}` — date of programme start \"YYYY-MM-DD\"\n- `{airtime}` — programme start \u0026 end time \"HH:MM–HH:MM\"\n- `{airtime_length}` — programme length \"HH:MM\"\n- `{desc}` — original programme description, generated from above elements. Newlines in `{desc}` will automatically be converted to `\u003cbr/\u003e`.\n\n\\* = This can only be used with an API key, i.e., on your own station.\n\n\\*\\* = URL to AzuraCast’s web player, or a custom player specified using the `-c`/`--customplayer` option. Use the latter to point listeners to a customized player on your station’s website instead of the default one.\n\n## FAQ: Frequently Asked Questions\n\n### I get `Check URLs! – 'http://…/api/status' redirected to 'https://…/api/status' [301, 200]`\n\nThis can happen in the initial server verification phase and is actually a _warning_. `azuracast_xmltv` will try to continue if it can find a live AzuraCast server.\n\nIn the case shown, your request was redirected from a `http://` to a `https://` URL.\nSince HTTP status 301 means \"moved permanently\", you should re-invoke the command,\nthis time using the `https://` URL. The URL you give on the commandline is used\nto construct the URLs in the various M3U, XML and RSS files, and you wouldn’t want\nto publicize \"wrong\" URLs. Even if they work, it looks unprofessional and generates\nunnecessary traffic.\n\nThere _are_ situations where you want to _keep_ the un-redirected URLs, though.\nLet’s assume your server is in maintenenace and you have a _temporary_ redirect\nto a backup server for the time being (307 Temporary Redirect). In this case,\nyou want to keep the \"old\" URLs and just let `azuracast_xmltv` continue.\n\n### I get `HTTPError – 403 Client Error: Forbidden for url: …`\n\nIn almost all cases, this means the API key given didn’t work. `azuracast_xmltv` will continue as if no API key had been given.\n\nThe user who generated the API key in AzuraCast must have appropriate rights to access the station’s …\n- mounts,\n- playlists, and\n- streamers/DJs.\n\nOr maybe there was a typo or a character left out while copying the API key.\n\n### I get `HTTPError – 500 Server Error: Internal Server Error for url: https://…/api/station/STATION_ID/mounts`\n\nThis means the station with ID `STATION_ID` has no mounts. Maybe they aren’t set up yet, or the station is HLS-only and doesn’t use Icecast or Shoutcast.\n\n`azuracast_xmltv` will continue normally.\n\n### How do you handle different timezones?\n\nTimezones should be handled correctly: Linux servers, AzuraCast, `azuracast_xmltv`, the XMLTV EPG file standard, and the clients know about timezones.\n\nYou can set your station’s timezone in AzuraCast, and `azuracast_xmltv` will simply use the timezone of the machine it runs on. Since `azuracast_xmltv` normally runs on your AzuraCast server (which should be set up correctly, ask your system administrator), all should be well.\n\nIf you experience any problems (like EPG hours being offset), it is usually the client software’s fault (the EPG reading application). The files `azuracast_xmltv` generates contain the correct timezone UTC offsets as specified in the protocol.\n\n### What about daylight savings time?\n\nBoth AzuraCast and `azuracast_xmltv` handle this correctly, if the server has been set up correctly.\n\nHere is an example of a programme that runs on 2023-10-29 00:00–06:00 in Germany. Now that night at 03:00 clocks in Germany will be reset to 02:00 since daylight savings time ends. The generated EPG code looks like this:\n\n```xml\n\u003cprogramme start=\"20231029000000 +0200\" stop=\"20231029060000 +0100\" channel=\"niteradio.example.com\"\u003e\n  \u003ctitle lang=\"en\"\u003eNuit électronique\u003c/title\u003e\n  \u003cdesc lang=\"en\"\u003ePlayliste: Nuit électronique\u003c/desc\u003e\n  \u003ccredits/\u003e\n  \u003ccategory lang=\"en\"\u003eMusic\u003c/category\u003e\n\u003c/programme\u003e\n```\n\nAs you can see, the UTC time offset at the start of the programme is `+0200`, and `+0100` at its end. A standards-compliant EPG client should be able to handle this correctly. The programme starts at 00:00 daylight savings time and ends at 06:00 normal time, making for an actual duration of 7 hours.\n\n### What about the gzip-compressed `.gz` file?\n\n#### Should I use it?\n\nAbsolutely. Many clients like _KODI_ and _xTeVe_ support reading this format. Since files are _much_ smaller, it decreases transmission time and bandwidth drastically.\n\n#### Why provide _both_ `.xml` and `.xml.gz`?\n\nSome clients don’t yet support the modern, compressed version of the EPG. Offer both and let the client decide which to use.\n\nThis is also the reason why the `url-tvg` and `x-tvg-url` entries in the M3U file (if you use the `-t`/`--tvgurl` option) point to the `.xml` version of the file, _not_ to the gzipped version.\n\n\n## But wait… What do I _do_ with these files now?\n\nI can’t give support for the many applications that use this format, but here’s a short list of apps I have tested or know they work fine with `azuracast_xmltv`:\n\n- [Jellyfin](https://jellyfin.org/)\\* – The Free Software Media System\n- [KODI](https://kodi.tv/)\\* – Entertainment Center\n- [Plex](https://www.plex.tv) – (non-free)\n- [Emby](https://emby.media/) – (non-free)\n- [TVHeadend](https://tvheadend.org/)\\* – TV Streaming Server and Recorder\n- [Hypnotix](https://github.com/linuxmint/hypnotix)\\* – The Linux Mint IPTV player (no EPG yet)\n- [Celluloid](https://github.com/celluloid-player/celluloid)\\* – A simple GTK+ frontend for mpv (no EPG); default video player in Linux Mint\n- [VLC](https://www.videolan.org/vlc/)\\* – The VLC media player (no EPG)\n- xTeVe\\* ([GitHub](https://github.com/xteve-project/xTeVe)) ([Documentation](https://github.com/xteve-project/xTeVe-Documentation/blob/master/en/configuration.md)) – M3U Proxy, recommended\n- [Liferea](https://lzone.de/liferea/)\\*, [Thunderbird](https://www.thunderbird.net/)\\*, [Nextcloud](https://nextcloud.com/)\\* – are/have good RSS feed readers (just a random selection, because I use all of these)\n\nMany others are out there in the wild. Consult their documentation to find how to set up \"XMLTV\" or \"IPTV\". In KODI, some are under \"PVR …\" (Personal Video Recorder).\n\n### Further reading\n\nHere are some links for looking up related items:\n\n- The [XMLTV DTD](https://github.com/XMLTV/xmltv/blob/master/xmltv.dtd)\\*\n- [XMLTV.org](https://xmltv.org)\n- [Kodi: IPTV einrichten](https://www.heise.de/tipps-tricks/Kodi-IPTV-einrichten-4676549.html) – (heise online; German)\n- [What is Live TV, PVR and Radio?](https://kodi.wiki/view/PVR) – (KODI FAQ)\n- [PVR IPTV Simple Client](https://kodi.wiki/view/Add-on:PVR_IPTV_Simple_Client)\\* – IPTV client for KODI\n- [Kodinerds](https://www.kodinerds.net/) – KODI-related German Forum\n- [Kodinerds IPTV - Freie und legale Streams für Kodi](https://github.com/jnk22/kodinerds-iptv) – Free and legal streams for IPTV; German, but has international channels.\n- [ErsatzTV](https://ersatztv.org/)\\* – Your Personal IPTV Server\n- The [RSS 2.0 Specification](https://www.rssboard.org/rss-specification)\\* – RSS Advisory Board\n\n\\* Free and Open Source Software I personally use and recommend.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FMoonbase59%2Fazuracast_xmltv","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FMoonbase59%2Fazuracast_xmltv","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FMoonbase59%2Fazuracast_xmltv/lists"}