Skip to content

jjf3/threadfin_mapping

Repository files navigation

Script Reference — Execution Order & Purpose

This document lists every script in the repository in the order it was written and run during the investigation, what it was trying to do, and what we learned from it.

The first two scripts solved the Threadfin EPG problem. Scripts 3–10 were the Emby guide mapping investigation. Only the last script actually worked for bulk mapping.


Phase 1 — Fixing Threadfin

1. fix_threadfin_epg.py

Run this if: Your Threadfin installation was migrated from another machine and all channels show "Threadfin Dummy" as their EPG source.

What it does:
Reads Threadfin's xepg.db, finds every channel incorrectly mapped to a dummy EPG, and remaps them to the correct XMLTV source using each channel's own tvg-id. Channels with Threadfin-generated hash IDs (local affiliates with no real tvg-id) are left alone.

Why we needed it:
After migrating from Synology to UGREEN NAS, Threadfin got a new M3U provider ID. The old xepg.db still referenced the old ID so every channel fell back to a dummy foxnews.us mapping. 37 channels were broken.

Outcome: ✅ 37 channels remapped. Guide data flowing through Threadfin correctly.


2. fix_native_xepg.py

What it does:
Variant of fix_threadfin_epg.py for the native/internal xepg.db format. Same logic, slightly different JSON structure.

Outcome: ✅ Supplementary fix for the secondary database format.


Phase 2 — Emby Guide Mapping Investigation

The goal of scripts 3–10 was to find a way to programmatically map Emby Live TV channels from XmlTV guide data to Emby Guide Data (Gracenote) in bulk. Each script tested a different hypothesis about where the required ManagementId field could be obtained.


3. emby_channel_update.py

Hypothesis: The standard Emby channel mapping API (/emby/LiveTv/ChannelMappingOptions) returns tuner channel data that can be used for bulk mapping.

What it does:
Authenticates with Emby, fetches the channel list, queries both the NJ Guide Data and XmlTV provider channels, and checks the ChannelMappingOptions endpoint for tuner channel data.

What we found:
TunerChannels always returns an empty array for M3U tuner sources. This endpoint works for HDHomeRun hardware tuners but silently fails for M3U. This is not documented anywhere.

Outcome: ❌ Dead end. The obvious API path doesn't work for M3U.


4. emby_find_providers.py

Hypothesis: We can at least enumerate available guide providers and get their channel ID lists, which we'll need for mapping later.

What it does:
Fetches all configured guide providers and dumps the full provider channel list for each one. This gave us the complete NJ Gracenote channel list (677 channels) with their IDs.

Outcome: ✅ Useful. Got the full NJ guide channel ID lookup table we'd need later.


5. emby_full_mapper.py

Hypothesis: The ManagementId field can be retrieved from the /emby/LiveTv/Channels/{id} endpoint.

What it does:
Fetches full channel detail for each of the 75 channels via the LiveTv channels endpoint, looking for a ManagementId field. Prints all non-empty fields to find where it might be hiding.

What we found:
ManagementId is not present in any LiveTv API response. The field exists in the browser's UI calls but is stripped from the external REST API.

Outcome: ❌ Dead end. Got ManagementId for 0 channels.


6. emby_check_fields.py

Hypothesis: Using the same client headers as the Emby web browser UI (X-Emby-Client, X-Emby-Token, etc.) will unlock access to /emby/Items/{id} and expose ManagementId.

What it does:
Replicates the exact headers seen in browser dev tools network requests and calls /emby/Items/{channel_id} with the ShareLevel fields parameter that the web UI uses.

What we found:
HTTP 404 for all channel IDs. Emby's /emby/Items/ endpoint does not serve Live TV channel objects — they live in a different namespace that isn't accessible via the standard Items API regardless of headers.

Outcome: ❌ Dead end. 404 for all 75 channels.


7. emby_get_mgmt_ids.py

Hypothesis: Adding fields=ShareLevel&ExcludeFields=VideoChapters,VideoMediaSources,MediaStreams to the Items endpoint (matching the exact browser request parameters) will return ManagementId.

What it does:
Tries the /emby/Items/{id}?fields=ShareLevel... endpoint with exact browser parameter replication.

What we found:
Still 404. The browser must be using internal routing that bypasses the external API layer entirely.

Outcome: ❌ Dead end. Parameters don't matter when the endpoint itself returns 404.


8. emby_test_tuner.py

Hypothesis: The TunerHosts endpoint (/emby/LiveTv/TunerHosts/{id}/Channels) exposes channel data including ManagementId.

What it does:
Queries the TunerHosts endpoint directly using the known tuner host ID.

What we found:
HTTP 404. This endpoint doesn't exist or isn't accessible on this version of Emby.

Outcome: ❌ Dead end.


9. emby_guide_mapper.py

Hypothesis: Using the login auth token and posting directly to /emby/LiveTv/ChannelMappings with the Emby internal channel ID (e.g. 766) as the tunerChannelId will work.

What it does:
Authenticates, builds a mapping payload using the channel's Emby internal integer ID as tunerChannelId, and POSTs to the channel mappings endpoint.

What we found:
The POST returns 200 but has no effect. Emby silently ignores mappings where tunerChannelId doesn't match the real ManagementId format.

Outcome: ❌ POST accepted but no channels actually mapped.


10. emby_mapper_final.py / emby_final_v2.py

Hypothesis: The ListingsChannelId and ListingsProviderId fields visible in the channel detail response can be used to construct the correct mapping, or the ManagementId can be derived from the channel data we do have.

What they do:
Multiple attempts to reconstruct or derive the ManagementId from available API data — channel numbers, listing IDs, hash analysis of known values. Also attempted reverse-engineering the hash algorithm (SHA256 and MD5 of stream URLs, M3U source URL, tuner ID).

What we found:
The hash cannot be derived from publicly available data. Emby generates it internally and the algorithm isn't reproducible from the outside. The two known hashes from browser captures shared a common prefix, suggesting a composite input, but no combination of testable inputs reproduced either hash.

Outcome: ❌ Hash algorithm unknown. Cannot derive ManagementId without reading the database.


Phase 3 — The SQLite Breakthrough

At this point all API paths were exhausted. The breakthrough came from querying Emby's local SQLite database directly.

SQLite Query (run manually in terminal)

sqlite3 /path/to/emby/config/data/library.db \
  "SELECT Name, ExternalId FROM MediaItems \
   WHERE ExternalId LIKE 'm3u_%' ORDER BY Name;"

What we found:
The ExternalId column in the MediaItems table contains exactly the hash portion of ManagementId for every M3U channel. The full tunerChannelId that Emby's mapping API requires is:

{tunerHostId}_{ExternalId from database}

This is not exposed by any REST API endpoint. It only exists in the local SQLite database and in browser-side UI network responses.


11. emby_definitive_mapper.py ✅ USE THIS ONE

This is the script that works.

What it does:
Uses two hardcoded dictionaries built from the SQLite query output and the NJ Guide provider channel list:

  • EXTERNAL_IDS — maps each channel name to its database ExternalId
  • CHANNEL_MAP — maps each channel name to its guide provider and Gracenote channel ID

Previews all planned mappings, prompts for confirmation, then POSTs each mapping to /emby/LiveTv/ChannelMappings with the correct tunerChannelId.

Outcome: ✅ All 75 channels mapped successfully. Zero failures.


Quick Reference

Script Purpose Status
fix_threadfin_epg.py Fix Threadfin xepg.db after migration ✅ Run first if migrating
fix_native_xepg.py Fix native xepg.db variant ✅ Run if needed
emby_channel_update.py Diagnostic — ChannelMappingOptions API ❌ Dead end
emby_find_providers.py Diagnostic — enumerate guide providers ✅ Useful reference
emby_full_mapper.py Diagnostic — LiveTv channel endpoint ❌ Dead end
emby_check_fields.py Diagnostic — browser header replication ❌ Dead end
emby_get_mgmt_ids.py Diagnostic — ShareLevel fields param ❌ Dead end
emby_test_tuner.py Diagnostic — TunerHosts endpoint ❌ Dead end
emby_guide_mapper.py Diagnostic — integer channel ID attempt ❌ Silent failure
emby_mapper_final.py / emby_final_v2.py Diagnostic — hash derivation attempts ❌ Dead end
emby_definitive_mapper.py Final working mapper ✅ Use this

The One Command That Made Everything Possible

Before running emby_definitive_mapper.py, run this against your own Emby database to get your channel ExternalId values. Replace the path with wherever your Emby library.db lives:

sqlite3 /volume1/docker/emby/config/data/library.db \
  "SELECT Name, ExternalId FROM MediaItems \
   WHERE ExternalId LIKE 'm3u_%' ORDER BY Name;"

Typical database locations:

  • Docker on Linux/NAS: /path/to/emby/config/data/library.db
  • Windows: C:\Users\<user>\AppData\Roaming\Emby-Server\programdata\data\library.db
  • macOS: ~/.emby-server/programdata/data/library.db

Update EXTERNAL_IDS in emby_definitive_mapper.py with your output, update CHANNEL_MAP with your channel names and Gracenote IDs, and you're done.


Notes

  • This is a personal project built for a specific setup (Fios NJ lineup, Threadfin IPTV proxy, UGREEN NAS running Emby)
  • The channel name strings in CHANNEL_MAP and EXTERNAL_IDS must match your M3U channel names exactly
  • After running, go to Emby Dashboard → Live TV → Refresh Guide Data
  • Guide data from Gracenote (Emby Guide Data) may take up to a week to fully populate the schedule grid, but artwork and current program info should appear immediately

License

Personal project — do whatever you want with it.

About

automate your guide data mappings

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages