mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-15 21:56:18 -08:00
Merge remote-tracking branch 'origin/master' into claude/dash-clearkey-shaka
# Conflicts: # CLAUDE.md # libs/ui/playback/src/lib/web-player-view/web-player-view.component.ts
This commit is contained in:
commit
5b2dd3b8b0
425 files changed
+59216
-3432
No files matched your search
@@ -217,6 +217,74 @@ describe('catchup.utils', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('applies negative sub-hour XMLTV offsets such as -0030', () => {
|
||||
// Regression: Number('-00') === -0, so a Math.sign()-based conversion
|
||||
// dropped the minutes' sign and treated -0030 as UTC.
|
||||
expect(
|
||||
resolveM3uCatchupUrl(
|
||||
baseChannel,
|
||||
{
|
||||
...archivedProgram,
|
||||
start: '202604100800 -0030',
|
||||
startTimestamp: null,
|
||||
},
|
||||
1_775_820_000
|
||||
)
|
||||
).toBe(
|
||||
'https://streams.example.com/live/channel-1.m3u8?utc=1775809800&lutc=1775820000'
|
||||
);
|
||||
});
|
||||
|
||||
it('applies positive offsets with minutes such as +0530', () => {
|
||||
expect(
|
||||
resolveM3uCatchupUrl(
|
||||
baseChannel,
|
||||
{
|
||||
...archivedProgram,
|
||||
start: '202604100800 +0530',
|
||||
startTimestamp: null,
|
||||
},
|
||||
1_775_820_000
|
||||
)
|
||||
).toBe(
|
||||
'https://streams.example.com/live/channel-1.m3u8?utc=1775788200&lutc=1775820000'
|
||||
);
|
||||
});
|
||||
|
||||
it('applies positive sub-hour XMLTV offsets such as +0030', () => {
|
||||
// Regression: Number('+00') === 0, so a Math.sign()-based conversion
|
||||
// dropped the minutes and treated +0030 as UTC.
|
||||
expect(
|
||||
resolveM3uCatchupUrl(
|
||||
baseChannel,
|
||||
{
|
||||
...archivedProgram,
|
||||
start: '202604100800 +0030',
|
||||
startTimestamp: null,
|
||||
},
|
||||
1_775_820_000
|
||||
)
|
||||
).toBe(
|
||||
'https://streams.example.com/live/channel-1.m3u8?utc=1775806200&lutc=1775820000'
|
||||
);
|
||||
});
|
||||
|
||||
it('treats a -0000 offset as UTC', () => {
|
||||
expect(
|
||||
resolveM3uCatchupUrl(
|
||||
baseChannel,
|
||||
{
|
||||
...archivedProgram,
|
||||
start: '202604100800 -0000',
|
||||
startTimestamp: null,
|
||||
},
|
||||
1_775_820_000
|
||||
)
|
||||
).toBe(
|
||||
'https://streams.example.com/live/channel-1.m3u8?utc=1775808000&lutc=1775820000'
|
||||
);
|
||||
});
|
||||
|
||||
it('preserves catchup metadata when storing parsed playlist items', () => {
|
||||
const playlist = createPlaylistObject('Catchup playlist', {
|
||||
header: {
|
||||
|
||||
@@ -112,9 +112,13 @@ function getEpgProgramTimestampSeconds(
|
||||
Number(hour),
|
||||
Number(minute)
|
||||
);
|
||||
// Derive the sign from the string: for offsets like "-0030" the hour part
|
||||
// is "-00", and Number("-00") === -0, so Math.sign() would drop the
|
||||
// minutes' sign and yield a zero offset instead of -30 minutes.
|
||||
const offsetSign = offsetHours.startsWith('-') ? -1 : 1;
|
||||
const offsetTotalMinutes =
|
||||
Number(offsetHours) * 60 +
|
||||
Math.sign(Number(offsetHours)) * Number(offsetMinutes);
|
||||
offsetSign *
|
||||
(Math.abs(Number(offsetHours)) * 60 + Number(offsetMinutes));
|
||||
|
||||
return Math.floor((utcMillis - offsetTotalMinutes * 60_000) / 1000);
|
||||
}
|
||||
|
||||
@@ -43,6 +43,36 @@ describe('stripCountryPrefix', () => {
|
||||
expect(stripCountryPrefix('US: CNN')).toBe('CNN');
|
||||
});
|
||||
|
||||
it('strips compound quality/provider tags', () => {
|
||||
expect(stripCountryPrefix('4K-DE - The Pitt (2025)')).toBe(
|
||||
'The Pitt (2025)'
|
||||
);
|
||||
expect(stripCountryPrefix('AR-SUBS - Fallout')).toBe('Fallout');
|
||||
expect(stripCountryPrefix('4K-OSN+ - The Last of Us')).toBe(
|
||||
'The Last of Us'
|
||||
);
|
||||
});
|
||||
|
||||
it('strips longer pipe-tagged prefixes', () => {
|
||||
expect(stripCountryPrefix('EXYU| News')).toBe('News');
|
||||
expect(stripCountryPrefix('MULTI| Movies')).toBe('Movies');
|
||||
});
|
||||
|
||||
it('never treats numeric fragments as tags', () => {
|
||||
expect(stripCountryPrefix('1917 - Documentary')).toBe(
|
||||
'1917 - Documentary'
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps bare 4-5 char words before a spaced dash (real titles)', () => {
|
||||
expect(stripCountryPrefix('DUNE - Part Two')).toBe(
|
||||
'DUNE - Part Two'
|
||||
);
|
||||
expect(stripCountryPrefix('ALIEN - Covenant')).toBe(
|
||||
'ALIEN - Covenant'
|
||||
);
|
||||
});
|
||||
|
||||
it('only strips the first tag segment', () => {
|
||||
expect(stripCountryPrefix('ES - A3 - Sports')).toBe('A3 - Sports');
|
||||
});
|
||||
|
||||
@@ -3,9 +3,19 @@
|
||||
* country/group tag — they routinely appear inside real channel names
|
||||
* ("Sky - Sports F1", "Discovery - Science"). Pipes are conventionally
|
||||
* used only as tag separators, so they always count.
|
||||
*
|
||||
* Before a dash a tag is either a compound ("4K-DE", "AR-SUBS", "4K-OSN+"
|
||||
* — the inner hyphen is the tag signal) or a plain 2–3 char code ("US",
|
||||
* "4K"). A bare 4–5 char word before a spaced dash is a real title
|
||||
* ("DUNE - Part Two", "ALIEN - Covenant"), so it is not a tag. Colon tags
|
||||
* stay 2–3 chars — longer acronyms are franchise titles ("NCIS: LA").
|
||||
* Every segment must contain a letter so numbers ("1917 - ...") are safe.
|
||||
*/
|
||||
const DASH_OR_COLON_SEPARATORS = [' - ', '- ', ' -', ': '];
|
||||
const TAG_PREFIX_PATTERN = /^[A-Z0-9]{2,3}$/;
|
||||
const DASH_SEPARATORS = [' - ', '- ', ' -'];
|
||||
const COLON_SEPARATOR = ': ';
|
||||
const TAG_PREFIX_PATTERN =
|
||||
/^(?:(?=[0-9+]*[A-Z])[A-Z0-9+]{2,5}(?:-(?=[0-9+]*[A-Z])[A-Z0-9+]{2,6}){1,2}|(?=[0-9+]*[A-Z])[A-Z0-9+]{2,3})$/;
|
||||
const COLON_TAG_PATTERN = /^(?=[0-9+]*[A-Z])[A-Z0-9+]{2,3}$/;
|
||||
|
||||
interface SeparatorMatch {
|
||||
index: number;
|
||||
@@ -68,10 +78,14 @@ function findTagSeparator(name: string): SeparatorMatch | null {
|
||||
best = { index: pipeIndex, length: 1 };
|
||||
}
|
||||
|
||||
for (const separator of DASH_OR_COLON_SEPARATORS) {
|
||||
for (const separator of [...DASH_SEPARATORS, COLON_SEPARATOR]) {
|
||||
const index = name.indexOf(separator);
|
||||
if (index === -1 || (best && best.index <= index)) continue;
|
||||
if (!TAG_PREFIX_PATTERN.test(name.slice(0, index).trim())) continue;
|
||||
const pattern =
|
||||
separator === COLON_SEPARATOR
|
||||
? COLON_TAG_PATTERN
|
||||
: TAG_PREFIX_PATTERN;
|
||||
if (!pattern.test(name.slice(0, index).trim())) continue;
|
||||
best = { index, length: separator.length };
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user