TV7 Teaser

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:

@fiber7_ch (8 Jun 2018): Lieferant hat uns mitgeteilt, dass man EIT "aus rechtlichen Gründen" ausfiltern müsse. #keineAhnugObDasStimmt

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.

📺 📖 Extract XMLTV EPG from the init7 IPTV backend

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