Skip to content

feat: merge all listings into a single XMLTV file - #3

Merged
JCBird1012 merged 2 commits into
mainfrom
xml-merge-img-fix
Sep 21, 2026
Merged

JCBird1012 merged 2 commits into
mainfrom
xml-merge-img-fix

Conversation

@JCBird1012

Copy link
Copy Markdown
Owner

Add --merge to combine every configured listing into one XMLTV document. Channels are deduplicated by channel ID and programmes by channel and start time, so stations carried by more than one lineup appear once.

Merging runs on the already-fetched grid data rather than re-parsing the written XML, so it needs no new runtime dependencies, no subprocess, and no Dockerfile change. Dedupe semantics match jesmannstl/tvmerge (--dedupe first|last) with two refinements: start times are normalized to epoch milliseconds so equivalent timestamps in different formats collide, and channel metadata is union-merged so a lineup missing channelNo inherits it from one that has it.

  • feat: src/merge.ts with mergeListings() and first/last dedupe
  • feat: merge YAML block plus --merge, --mergeOutputFile, --mergeDedupe, --noKeepIndividual and matching MERGE* env vars
  • feat: keepIndividual controls whether per-listing files are written
  • fix: reject a merge outputFile that collides with a listing output
  • fix: use ebyl.tmsimg.com for channel icons
  • test: 24 new tests for merge logic and merge config parsing (47 -> 71)
  • ci: run unit tests and prettier check, add merge integration test
  • style: standardize on 4-space indentation via prettier

Add --merge to combine every configured listing into one XMLTV
document. Channels are deduplicated by channel ID and programmes by
channel and start time, so stations carried by more than one lineup
appear once.

Merging runs on the already-fetched grid data rather than re-parsing
the written XML, so it needs no new runtime dependencies, no
subprocess, and no Dockerfile change. Dedupe semantics match
jesmannstl/tvmerge (--dedupe first|last) with two refinements: start
times are normalized to epoch milliseconds so equivalent timestamps in
different formats collide, and channel metadata is union-merged so a
lineup missing channelNo inherits it from one that has it.

* feat: src/merge.ts with mergeListings() and first/last dedupe
* feat: merge YAML block plus --merge, --mergeOutputFile, --mergeDedupe,
  --noKeepIndividual and matching MERGE* env vars
* feat: keepIndividual controls whether per-listing files are written
* fix: reject a merge outputFile that collides with a listing output
* fix: use ebyl.tmsimg.com for channel icons
* test: 24 new tests for merge logic and merge config parsing (47 -> 71)
* ci: run unit tests and prettier check, add merge integration test
* style: standardize on 4-space indentation via prettier

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The channel-icon host was changed to the likely-typo ebyl.tmsimg.com, which diverges from the known-working emby.tmsimg.com still used for programme thumbnails and will probably break channel logos.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 2 Medium severity

Open (2)
What changed in this PR

This PR adds an opt-in --merge capability to the zap2xml tool so that multiple configured TV listings can be combined into a single XMLTV document. Merging operates on already-fetched grid data (no re-parsing of written XML), deduplicating channels by channel ID and programmes by channel + normalized start time, with configurable first/last dedupe semantics. It also standardizes the codebase on 4-space Prettier formatting and wires unit tests + a merge integration test into CI.

Changes:

  • New src/merge.ts (mergeListings, programmeKey, channel-metadata union merge) plus 24 new merge/config tests.
  • New merge configuration surface: YAML merge block, --merge/--mergeOutputFile/--mergeDedupe/--noKeepIndividual flags, matching MERGE* env vars, and a collision check between merge output and per-listing outputs.
  • Repo-wide reformatting to 4-space indentation (.prettierrc, format/format:check scripts, CI format + unit-test steps), plus a channel-icon host change in src/xmltv.ts.
File Description
src/​merge.ts New merge/dedupe engine; channel metadata union uses ?? which misses empty-string values.
src/​merge.test.ts New unit tests covering dedupe modes, pooling, metadata fill, and immutability.
src/​config.ts Adds MergeConfig, schemas, env/CLI overrides, and merge-output collision assertion.
src/​config.test.ts New tests for merge config parsing and precedence.
src/​index.ts Orchestrates merge flow, keepIndividual behavior, and merged-file writing.
src/​xmltv.ts Reformatting plus channel-icon host changed to ebyl.tmsimg.com (likely a typo of emby).
src/​xmltv.test.ts Reformatting; icon expectation updated to ebyl.tmsimg.com.
src/​tvlistings.ts /​ .test.ts Formatting-only changes.
src/​useragents.ts, rollup.config.ts, eslint.config.mjs Formatting-only changes.
.prettierrc, package.json Prettier 4-space/printWidth config and format scripts.
.github/​workflows/​ci.yml Adds format check, unit tests, and a merge integration test.
README.md Documents the new merge feature and options.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/merge.ts
Comment thread src/xmltv.ts
// New line to replace the hostname
src = src.replace("zap2it.tmsimg.com", "emby.tmsimg.com");
// New line to replace the hostname
src = src.replace("zap2it.tmsimg.com", "ebyl.tmsimg.com");
mergeChannelMetadata used ?? for affiliateName, affiliateCallSign,
channelNo, and thumbnail, so it only fell back to another listing's
value when the existing one was null or undefined. The rest of the
codebase treats an empty string as absent for these fields:
buildChannelsXml guards each with a truthiness check and
channelComparator normalizes channelNo via (a.channelNo || "").trim().

A feed reporting "" instead of null therefore blocked inheritance, and
the merged channel silently lost its number, affiliate name, and icon
even though another listing supplied them. Switch to || to match
callSign and the surrounding conventions.

* test: cover empty-string metadata inheritance
@JCBird1012
JCBird1012 merged commit 01e2aea into main Sep 21, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants