Init7 - my FTTH provider - has a great IPTV offering called TV7 included for free. TV7 is based on UDP multicast, which means the TV signal will arrive with no delay, whilst conventional IPTV transmissions using HLS often limp in with a delay of a seconds or even half a minute.
Another bonus is that TV7 does not compress the TV signal between the source and the viewer: the signal from the TV Station (typically the same you’d get via DVB-C or DVB-S) is forwarded as is without any re-encoding or encryption.
EPG in the Event Information Table
Usually (e.g. with DVB) the EPG data is transmitted on the regular data stream. It’s part of the EIT (Event Information Table) that is broadcast with digital television signals. EIT provides program data for the current show and future shows which can be then used to view on-screen program information, such as title, length, description and more.
Unfortunately, for what seems to be licensing issues, init7 strips the EPG Data which would be transmitted via the EIT table and don’t make it available outside of their own app:
This means I won’t have any EPG data in TVHeadend or PlexDVR 😢.
TV7 EPG Backend Service
While Init7 doesn’t provide EPG data via the EIT or as a separate XMLTV file, they do provide EPG in their own app. So let’s see how/if we can access that data source.
I don’t have access to an Android App (and decompiling iOS code is much more difficult), but it’s easy enough to get the APK from apkpure and decompile it. The URLs are very easy to find in the decompiled files:
Base URL: The app implements a factory pattern to initialize the client-side interface for the app. In core/api/TvApiFactory.java we find the URL which hosts all other endpoints of this service:
BASE_URL="https://tv7api2.tv.init7.net/api/"
Channel List: Next, we will need to get a list of channels - like all the other data access methods this is now of the client-side interface implemented in core/api/TvApi.java:
@GET("tvchannel/")
Call<TvChannelListResponse> tvChannelList();
This gets us a list of all channels and the UUIDs needed to further fetch detailed EPG data.
EPG Data: To get an overview of all currently running programs, the method getCurrentEPG in core/api/TvApi.java calls the endpoint /epg in various configurations:
@GET("epg/?now__lte=true&now__gte=true")
Call<EPGListResponse# getCurrentEPG(@Query("channel") String paramString);
@GET("epg/")
Call<EPGListResponse# getEPG(@Query("channel") String paramString);
@GET("epg/")
Call<EPGListResponse# getEPG(@Query("channel") String paramString1, @Query("limit") int paramInt1, @Query("offset") int paramInt2, @Header("If-None-Match") String paramString2);
@GET("epg/")
Call<EPGListResponse# getEPG(@Query("channel") String paramString, @Query("limit") int paramInt, @Query("now__lte") boolean paramBoolean1, @Query("now__gte") boolean paramBoolean2);
@GET("epg/")
Call<EPGListResponse# getEPG(@Query("channel") String paramString);
There’s two more endpoints which look interesting - allowed/ and replay/ - but for EPG purposes we don’t need them.
Testing it out with curl and jq
Having extracted the above data, it’s now time to play around with the endpoints we have found
Accessing the channel list:
-
First - Downloading the channel list
BASE_URL="https://tv7api2.tv.init7.net/api/" curl "${BASE_URL}tvchannel/" > tvChannelList.json -
Let’s see the full channel list by order number (presumably this is the channel number)
jq '.results[] | "(.ordernum) (.name)"' tvChannelList.json | sort -
show full channel list by last change date
jq '.results[] | "(.changed) (.name)"' tvChannelList.json | sort
Downloading EPG Data
- First - get all epg titles for currently running
BASE_URL="https://tv7api2.tv.init7.net/api/" curl "${BASE_URL}/epg/?now__lte=true&now__gte=true" > getCurrentEPG.json - Let’s see the title for all the running programs
jq ".results[].title" getCurrentEPG.json - download channel specific EPG
BASE_URL="https://tv7api2.tv.init7.net/api/" curl "${BASE_URL}epg/?channel=4c8a7d39-009d-4835-b6f9-69c7268fd9d4" > getEPG-channel.json cat getEPG-channel.json | jq ".results[]"
Checking out allowed/
- From what I can tell this just checks if the IP is part of Init7’s subnet
BASE_URL="https://tv7api2.tv.init7.net/api/" curl "${BASE_URL}allowed/" | jq "."
TV7 EPG Parser
To integrate this with my existing setup, I wrote an small python app which prints out the EPG as an XMLTV string and can be called by a cron job to dump the info into TVHeadends via it’s xmltv.sock epg grabber. This also includes some very basic caching to avoid hammering the backend unnecessarily.
To regularly update the EPG, just feed the result to socat in a cron job:
0 * * * * <...>/tv7-epg-parser/tv7-epg-parser.sh | socat - UNIX-CONNECT:/srv/home/hts/.hts/tvheadend/epggrab/xmltv.sock
