Aktuelles
Digital Eliteboard - Das Digitale Technik Forum

Registriere dich noch heute kostenlos, um Mitglied zu werden! Sobald du angemeldet bist, kannst du auf unserer Seite aktiv teilnehmen, indem du deine eigenen Themen und Beiträge erstellst und dich über deinen eigenen Posteingang mit anderen Mitgliedern unterhalten kannst! Zudem bekommst du Zutritt zu Bereichen, welche für Gäste verwehrt bleiben

Registriere dich noch heute kostenlos, um Mitglied zu werden! Sobald du angemeldet bist, kannst du auf unserer Seite aktiv teilnehmen, indem du deine eigenen Themen und Beiträge erstellst und dich über deinen eigenen Posteingang mit anderen Mitgliedern unterhalten kannst! Zudem bekommst du Zutritt zu Bereichen, welche für Gäste verwehrt bleiben

EPG für Dachregion D/A/CH und UK/US

Was glaubst du was ich gemacht habe, als ich die DNS gewechselt habe.
In einigen Fällen kann es zu Problemen mit der DNS-Auflösung kommen, wenn das VPN verwendet wird.
Das manuelle Ändern der DNS-Einstellungen kann hier Abhilfe schaffen.
Und genau das habe ich getan, als immer mehr Adressen wie zb. Domainunion und viele andere nicht aufgegangen sind!
Seit ich die DNS gewechselt habe, gehört dieses Problem der Vergangenheit an.
 
Ja. Mag alles sein.
Somit hat surfshark halt einige Domains auf ihrer Blacklist (bzw. die Blacklist der Seitenanbieter, die du nicht erreichst).
Toller Anbieter 😉
...und seit dem du den DNS auf einen öffentlichen geändert hast, surfst ohne jeglichen VPN Schutz, obwohl du den "eingeschaltet" hast.
Ergo: dann kannst du VPN auch ganz weglassen.

Ich schreibe das hier nicht, um DICH zu ärgern, sondern um andere User vor diesem fatalen Fehler zu "schützen"

Aber jeder kann ja machen, was er möchte.🙃
Nur ohne DNS vom VPN Anbieter, ist das im günstigsten😆Fall raus-geworfenes Geld.
 
Zuletzt bearbeitet:
Ach ja, warum findet man diese Empfehlungen im Netz?

Wichtiger Hinweis: Wenn man ein VPN verwendet und gleichzeitig die DNS-Einstellungen manuell ändert, sollte man sicherstellen, dass die DNS-Server, die man verwendet, vertrauenswürdig sind und keine Probleme mit dem VPN-Anbieter verursachen.

Zusammenfassend lässt sich sagen, dass es möglich und manchmal sinnvoll ist, einen anderen DNS-Server als den vom VPN zu verwenden. Die genauen Schritte und Überlegungen hängen von der jeweiligen Situation und den verwendeten Geräten ab.
 
FALLS dein VPN Anbieter eine DNS Leak Protection hat (und die auch funktioniert) dann kannst du das meinetwegen so machen.
Wenn nicht (und davon ist erstmal auszugehen für User, die sich damit nicht beschäftigen) ist die Änderung des DNS auf eine öffentliche, ein riesiges Problem.
 
Kann es sein, dass deine xml.gz nicht als gz gepackt sind, sonder die reine xml?

Ich hatte da mal testweise reingesehen und mich erst gewundert, dass die gepackte gz schon über 150MB groß ist.

Meine eigene xml.gz hat ungefähr ähnliche Inhalte und ist nur 12-15MB groß und erst entpackt dann 120-150MB

Vorteile, wenn du die xml tatsächlich packen würdest:
  • deutlich weniger Traffic auf dem Server
  • deutlich schnellere Ladezeit bei den Clients
 
So mal auf die Schnelle für die Rytec Freunde ein kleines php Script.
Mehrfachauswahl komma-separiert möglich.

Für`s Terminal.

Erster Start nur mit

sudo php epg.php

Kann auch per cron gestartet werden.
Z.B

sudo php epg.php build 1 2 4

(hier im Beispiel automatische Neu-Erstellung der vorhandenen EPGs von 1,2 und 4)

So viele EPG Kombinationen verwalten/erstellen/ändern wie ihr wollt.

Ich hab die xml/gz. jetzt nicht getestet, da auf die Schnelle geschrieben.

(Owner sollte eigentlich www-data sein. Ich hab das jetzt mal auskommentiert. Könnt ihr ja ändern)

Update Version mit MagentaSport Abfrage und anderem Gedöns
PHP:
#!/usr/bin/env php
<?php
declare(strict_types=1);
ini_set('memory_limit', '-1');
set_time_limit(0);    

#const OWNER_USER  = 'www-data';
#const OWNER_GROUP = 'www-data';

$URLS = [
    'DE'   => [
        'http://www.xmltvepg.nl/rytecDE_Basic.xz',
        'http://www.xmltvepg.nl/rytecDE_Common.xz',
        'http://www.xmltvepg.nl/rytecDE_SportMovies.xz',
    ],
    'AT'   => ['http://www.xmltvepg.nl/rytecAT_Basic.xz'],
    'CH'   => ['http://www.xmltvepg.nl/rytecCH_Basic.xz'],
    'IPTV' => ['http://www.xmltvepg.nl/rytecIPTV.xz'],
    'Misc' => ['http://www.xmltvepg.nl/rytecMisc.xz'],
    'NL'   => ['http://www.xmltvepg.nl/rytecNL_Basic.xz'],
    'BE'   => ['http://www.xmltvepg.nl/rytecBE_VL_Basic.xz'],
    'TNT'  => ['http://www.xmltvepg.nl/rytecTNT_Basic.xz'],
    'FR'   => ['http://www.xmltvepg.nl/rytecFR_Mixte.xz'],
    'BG'   => ['http://www.xmltvepg.nl/rytecBG.xz'],
    'CZ'   => ['http://www.xmltvepg.nl/rytecCZ_Basic.xz'],
    'DK'   => ['http://www.xmltvepg.nl/rytecDK_Basic.xz'],
    'GR'   => ['http://www.xmltvepg.nl/rytecGR_Basic.xz'],
    'IT'   => ['http://www.xmltvepg.nl/rytecIT.xz'],
    'PL'   => ['http://www.xmltvepg.nl/azman_basic.xml.xz|rytecPL_Basic.xz'],
    'PT'   => ['http://www.xmltvepg.nl/rytecPT.xz'],
    'HU'   => ['http://www.xmltvepg.nl/rytecHU_Basic.xz'],
    'NO'   => ['http://www.xmltvepg.nl/rytecNO_Basic.xz'],
    'RO'   => ['http://www.xmltvepg.nl/rytecRO_Basic.xz'],
    'HRV'  => ['http://www.xmltvepg.nl/rytecHRV_Basic.xz'],
    'SRB'  => ['http://www.xmltvepg.nl/rytecSRB_Basic.xz'],
    'SVN'  => ['http://www.xmltvepg.nl/rytecSVN_Basic.xz'],
    'SE'   => ['http://www.xmltvepg.nl/rytecSE_Basic.xz'],
    'FI'   => ['http://www.xmltvepg.nl/rytecFI_Basic.xz'],
    'UK'   => ['http://www.xmltvepg.nl/rytecUK_Basic.xz'],
    'ERO'  => ['http://www.xmltvepg.nl/rytecERO.xz'],
    'NWS'  => ['http://www.xmltvepg.nl/rytecNWS.xz'],
];
$EPG_SOURCES = [
    'DE'   => 'Germany',
    'AT'   => 'Austria',
    'CH'   => 'Switzerland',
    'IPTV' => 'IPTV',
    'Misc' => 'Miscellaneous',
    'NL'   => 'Netherlands',
    'BE'   => 'Belgium',
    'TNT'  => 'TNT',
    'FR'   => 'France',
    'BG'   => 'Bulgaria',
    'CZ'   => 'Czech Republic',
    'DK'   => 'Denmark',
    'GR'   => 'Greece',
    'IT'   => 'Italy',
    'PL'   => 'Poland',
    'PT'   => 'Portugal',
    'HU'   => 'Hungary',
    'NO'   => 'Norway',
    'RO'   => 'Romania',
    'HRV'  => 'Croatia',
    'SRB'  => 'Serbia',
    'SVN'  => 'Slovenia',
    'SE'   => 'Sweden',
    'FI'   => 'Finland',
    'UK'   => 'United Kingdom',
    'ERO'  => 'Erotik',
    'NWS'  => 'News',
];

$JSON_FILE  = __DIR__ . '/epgset.json';
$TEMP_DIR   = __DIR__ . '/temp';
$TARGET_DIR = __DIR__ . '/epgs';

function clear_screen(): void {
    if (strncasecmp(PHP_OS, 'WIN', 3) === 0) system('cls'); else system('clear');
}
function log_info(string $message): void {
    $ts = (new DateTimeImmutable('now', new DateTimeZone('Europe/Berlin')))->format('d.m.Y');
    fwrite(STDOUT, "[$ts] $message\n");
}
function run_command(string $cmd): array {
    $out=[]; $code=1; exec($cmd.' 2>&1',$out,$code);
    return ['output'=>implode("\n",$out),'code'=>$code];
}
function safe_chown(string $path): void {}
#function safe_chown(string $path): void {
#    if (function_exists('chown')) @chown($path, OWNER_USER);
#    if (function_exists('chgrp')) @chgrp($path, OWNER_GROUP);
#    @exec('chown '.escapeshellarg(OWNER_USER.':'.OWNER_GROUP).' '.escapeshellarg($path));
#}

function ensure_dirs(string ...$dirs): void {
    foreach ($dirs as $dir) {
        if (!is_dir($dir)) {
            if (!mkdir($dir, 0755, true)) { log_info("Error: cannot create directory: $dir"); exit(1); }
            safe_chown($dir); chmod($dir, 0755);
        }
    }
}
function load_sets(string $file): array {
    if (!file_exists($file)) return ['sets'=>[]];
    $data = json_decode((string)file_get_contents($file), true);
    if (!is_array($data)) return ['sets'=>[]];
    if (!isset($data['sets']) || !is_array($data['sets'])) $data['sets'] = [];
    return $data;
}
function save_sets(string $file, array $data): void {
    file_put_contents($file, json_encode($data, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES));
    safe_chown($file); chmod($file, 0755);
}
function next_id(array $sets): int {
    $max=0; foreach ($sets as $s) { $max = max($max, (int)($s['id'] ?? 0)); } return $max+1;
}
function prompt(string $q, ?string $def=null): string {
    fwrite(STDOUT, $q.($def!==null?" [$def]":"").": ");
    $line = fgets(STDIN); if ($line===false) return $def ?? '';
    $line = trim($line); return ($line==='' && $def!==null) ? $def : $line;
}
function choose_countries(array $sources): array {
    fwrite(STDOUT, "Select countries (comma-separated by index or codes). Example: 1,3,5  or  DE,AT,CH\n\n");
    $codes=array_keys($sources); $i=1;
    foreach ($sources as $code=>$name) { printf("%2d) %-4s  %s\n",$i,$code,$name); $i++; }
    fwrite(STDOUT,"\n");
    $inp = strtoupper(trim(prompt("Enter selection")));
    if ($inp==='') return [];
    $parts = array_map('trim', explode(',',$inp)); $picked=[];
    foreach ($parts as $p) {
        if ($p==='') continue;
        if (ctype_digit($p)) { $j=(int)$p; if ($j>=1 && $j<=count($codes)) $picked[]=$codes[$j-1]; }
        else { if (isset($sources[$p])) $picked[]=$p; }
    }
    return array_values(array_unique($picked));
}
function list_sets(array $sets): void {
    if (empty($sets)) { fwrite(STDOUT,"No saved sets.\n"); return; }
    fwrite(STDOUT,"Saved EPG sets:\n");
    foreach ($sets as $s) {
        $id=$s['id']; $fn=$s['filename']??''; $cc=implode(',', $s['countries']??[]);
        $ts=$s['created_at']??''; $m = !empty($s['magenta']['include']) ? "  (MagentaSport: {$s['magenta']['days']}d)" : "";
        printf("  %d) %-25s  [%s]  %s%s\n",$id,$fn,$cc,$ts,$m);
    }
}

function uuidv4(): string {
    $data = random_bytes(16);
    $data[6] = chr((ord($data[6]) & 0x0f) | 0x40);
    $data[8] = chr((ord($data[8]) & 0x3f) | 0x80);
    return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}
function magenta_cookie_file(string $tmp): string {
    return rtrim($tmp, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.'magenta_cookies.txt';
}
function magenta_csrf_from_cookie(string $cookieFile): ?string {
    if (!is_file($cookieFile)) return null;
    $lines = @file($cookieFile, FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES);
    if (!$lines) return null;
    foreach ($lines as $line) {
        if (strlen($line)===0 || $line[0]==='#') continue;
        $parts = explode("\t", $line);
        if (count($parts)>=7) {
            $name = $parts[5]; $val = $parts[6];
            if ($name==='CSRFSESSION') return trim($val);
        }
    }
    return null;
}
function http_post_json_with_cookies(string $url, string $json, string $cookieFile, array $headers=[]): array {
    $h = [];
    $h[] = 'Content-Type: application/json';
    $h[] = 'Accept: application/json';
    foreach ($headers as $k=>$v) { $h[] = $k.': '.$v; }
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => $json,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_TIMEOUT        => 10,
        CURLOPT_CONNECTTIMEOUT => 5,
        CURLOPT_SSL_VERIFYPEER => true,
        CURLOPT_SSL_VERIFYHOST => 2,
        CURLOPT_HTTPHEADER     => $h,
        CURLOPT_COOKIEFILE     => $cookieFile,
        CURLOPT_COOKIEJAR      => $cookieFile,
        CURLOPT_USERAGENT      => 'EPGBuilder/1.0 (MagentaSport)',
        CURLOPT_HEADER         => false,
    ]);
    $body = curl_exec($ch);
    $code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $err  = curl_error($ch);
    curl_close($ch);
    return ['code'=>$code, 'body'=>$body, 'err'=>$err];
}
function magenta_login(string $tmp): ?string {
    ensure_dirs($tmp);
    $cookieFile = magenta_cookie_file($tmp);
    @unlink($cookieFile);
    $headers = [];
    $tries = 0;
    while ($tries < 120) {
        $mac = uuidv4(); $ter = uuidv4();
        $auth_data = json_encode([
            "areaid"=>"1","cnonce"=>"c4b11948545fb3089720dd8b12c81f8e",
            "mac"=>$mac,"preSharedKeyID"=>"NGTV000001","subnetId"=>"4901",
            "templatename"=>"NGTV","terminalid"=>$ter,"terminaltype"=>"WEB-MTV",
            "terminalvendor"=>"WebTV","timezone"=>"UTC","usergroup"=>"-1",
            "userType"=>3,"utcEnable"=>1
        ], JSON_UNESCAPED_SLASHES);
        $res = http_post_json_with_cookies('https://api.prod.sngtv.magentatv.de/EPG/JSON/Authenticate', $auth_data, $cookieFile);
        if ($res['code']!==200 || $res['body']===false) { $tries++; usleep(100000); continue; }
        $j = json_decode((string)$res['body'], true);
        if (isset($j['retcode']) && $j['retcode']==='-2') { $tries++; usleep(100000); continue; }
        break;
    }
    $csrf = magenta_csrf_from_cookie($cookieFile);
    if (!$csrf) { log_info("Magenta: CSRF cookie not found."); return null; }
    return $cookieFile;
}
function magenta_fetch_channels(string $cookieFile): array {
    $csrf = magenta_csrf_from_cookie($cookieFile);
    if (!$csrf) return [];

    $payload = '{"properties":[{"name":"logicalChannel","include":"/channellist/logicalChannel/contentId,/channellist/logicalChannel/name"}],"metaDataVer":"Channel/1.1","channelNamespace":"2","filterlist":[{"key":"IsHide","value":"-1"}],"returnSatChannel":0}';
    $res = http_post_json_with_cookies(
        'https://api.prod.sngtv.magentatv.de/EPG/JSON/AllChannel',
        $payload,
        $cookieFile,
        ["X_csrftoken"=>$csrf]
    );
    if ($res['code'] !== 200 || $res['body'] === false) return [];

    $j = json_decode((string)$res['body'], true);
    $list = $j['channellist'] ?? [];
    $map = [];

    foreach ($list as $ch) {
        $cid  = (string)($ch['contentId'] ?? '');
        $name = (string)($ch['name'] ?? '');
        if ($cid === '' || $name === '') continue;

        if (preg_match('/^Sport\s+\d+\s+-\s+myTeamTV$/', $name)) {
            $map[$cid] = $name;
        }
    }

    uasort($map, function($a, $b) {
        preg_match('/^Sport\s+(\d+)/', $a, $ma);
        preg_match('/^Sport\s+(\d+)/', $b, $mb);
        return (int)($ma[1] ?? 0) <=> (int)($mb[1] ?? 0);
    });

    return $map;
}
function magenta_time_utc_window(int $days, DateTimeZone $tzLocal): array {
    $today = new DateTimeImmutable('now', $tzLocal);
    $links = [];
    for ($day=0; $day<$days; $day++) {
        $sd = $today->setTime(6,0,0)->modify("+$day day");
        $ed = $today->setTime(5,59,0)->modify("+".($day+1)." day");
        $ts = $sd->setTimezone(new DateTimeZone('UTC'))->format('YmdHis');
        $te = $ed->setTimezone(new DateTimeZone('UTC'))->format('YmdHis');
        $links[] = ['begin'=>$ts, 'end'=>$te];
    }
    return $links;
}
function magenta_fetch_playbills(string $cookieFile, array $channels, int $days): array {
    $csrf = magenta_csrf_from_cookie($cookieFile);
    if (!$csrf) return [];

    $tzLocal  = new DateTimeZone('Europe/Berlin');
    $windows  = magenta_time_utc_window($days, $tzLocal);
    $allowed  = array_fill_keys(array_map('strval', array_keys($channels)), true); // only myTeamTV channels
    $all = [];

    foreach ($windows as $w) {
        $p = [
            "type"=>2,"isFiltrate"=>0,"orderType"=>4,"isFillProgram"=>1,
            "channelNamespace"=>"2","offset"=>0,"count"=>-1,
            "properties"=>[["name"=>"playbill","include"=>implode(",",[
                "subName","id","name","starttime","endtime","channelid",
                "ratingid","genres","introduce","cast","country","pictures",
                "producedate","seasonNum","subNum"
            ])]],
            "endtime"=>$w['end'],"begintime"=>$w['begin']
        ];

        $res = http_post_json_with_cookies(
            'https://api.prod.sngtv.magentatv.de/EPG/JSON/PlayBillList',
            json_encode($p, JSON_UNESCAPED_SLASHES),
            $cookieFile,
            ["X_csrftoken"=>$csrf]
        );
        if ($res['code'] !== 200 || $res['body'] === false) continue;

        $j  = json_decode((string)$res['body'], true);
        $pl = $j['playbilllist'] ?? [];

        foreach ($pl as $prog) {
            $cid = (string)($prog['channelid'] ?? '');
            if ($cid === '' || !isset($allowed[$cid])) continue;

            $stRaw = (string)($prog['starttime'] ?? '');
            $enRaw = (string)($prog['endtime']  ?? '');
            $st = str_replace(' UTC+00:00','',$stRaw);
            $en = str_replace(' UTC+00:00','',$enRaw);

            $stTs = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $st, new DateTimeZone('UTC'));
            $enTs = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $en, new DateTimeZone('UTC'));
            if (!$stTs || !$enTs) continue;

            $all[] = [
                'c_id'    => $cid,                         // string
                'start'   => $stTs->format('YmdHis').' +0000',
                'end'     => $enTs->format('YmdHis').' +0000',
                'title'   => (string)($prog['name'] ?? ''),
                'subtitle'=> $prog['subName']   ?? null,
                'desc'    => $prog['introduce'] ?? null,
            ];
        }
    }

    return $all;
}
function magenta_write_xml(string $tmp, array $channels, array $airings): ?string {
    if (empty($channels)) { log_info("Magenta: no myTeamTV channels."); return null; }

    $file = rtrim($tmp, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'magenta_sport.xml';
    $w = new XMLWriter();
    if (!$w->openURI($file)) { log_info("Magenta: cannot open output file."); return null; }

    $w->startDocument('1.0', 'UTF-8');
    $w->setIndent(true);
    $w->startElement('tv');

    foreach ($channels as $cid => $name) {
        $w->startElement('channel');
        $w->writeAttribute('id', (string)$cid);
        $w->startElement('display-name'); $w->text((string)$name); $w->endElement();
        $w->endElement();
    }
    foreach ($airings as $p) {
        $w->startElement('programme');
        $w->writeAttribute('start', (string)$p['start']);
        $w->writeAttribute('stop',  (string)$p['end']);
        $w->writeAttribute('channel', (string)$p['c_id']);

        $w->startElement('title'); $w->writeAttribute('lang', 'de'); $w->text((string)($p['title'] ?? '')); $w->endElement();
        if (!empty($p['subtitle'])) { $w->startElement('sub-title'); $w->text((string)$p['subtitle']); $w->endElement(); }
        if (!empty($p['desc']))     { $w->startElement('desc');      $w->text((string)$p['desc']);      $w->endElement(); }

        $w->endElement();
    }

    $w->endElement();
    $w->endDocument();
    $w->flush();

    safe_chown($file);
    chmod($file, 0644);
    log_info("Magenta: XML generated -> $file");
    return $file;
}
function magenta_build_xml_into_tmp(string $tmp, int $days=5): ?string {
    log_info("Magenta: authenticating ...");
    $cookie = magenta_login($tmp);
    if (!$cookie) { log_info("Magenta: login failed."); return null; }
    $channels = magenta_fetch_channels($cookie);
    if (empty($channels)) { log_info("Magenta: channel list empty."); return null; }
    log_info("Magenta: fetched ".count($channels)." channels.");
    $air = magenta_fetch_playbills($cookie, $channels, $days);
    log_info("Magenta: collected ".count($air)." programme entries.");
    return magenta_write_xml($tmp, $channels, $air);
}
function download_and_extract(string $url, string $dir): ?string {
    if (strpos($url,'|')!==false) { [$url,$renamed] = explode('|',$url,2); } else $renamed=null;
    $parts = parse_url($url);
    if (!$parts || !isset($parts['scheme']) || !in_array($parts['scheme'], ['http','https'], true)) { log_info("Error: invalid URL $url"); return null; }
    $orig = basename($parts['path'] ?? '');
    if ($orig==='') { log_info("Error: cannot determine filename for $url"); return null; }
    $dl = rtrim($dir, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.$orig;
    $tfn = $renamed ? preg_replace('/\.(?:xz|gz|xml\.gz)$/i','.xml',$renamed)
                    : preg_replace('/\.(?:xz|gz|xml\.gz)$/i','.xml',$orig);
    $target = rtrim($dir, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.$tfn;
    log_info("Downloading $url ...");
    $ch = curl_init($url); $fp = @fopen($dl,'wb');
    if (!$fp) { log_info("Error: cannot open for write: $dl"); return null; }
    curl_setopt_array($ch,[
        CURLOPT_FILE=>$fp, CURLOPT_FOLLOWLOCATION=>true, CURLOPT_FAILONERROR=>true,
        CURLOPT_TIMEOUT=>60, CURLOPT_CONNECTTIMEOUT=>10, CURLOPT_USERAGENT=>'EPGBuilder/1.0',
        CURLOPT_SSL_VERIFYPEER=>true, CURLOPT_SSL_VERIFYHOST=>2,
    ]);
    $res=curl_exec($ch); $code=(int)curl_getinfo($ch,CURLINFO_HTTP_CODE); $err=curl_error($ch);
    curl_close($ch); fclose($fp);
    if ($res===false || $code!==200) { log_info("Error: download failed: $url (HTTP $code) curl: $err"); @unlink($dl); return null; }
    $ext=strtolower(pathinfo($dl, PATHINFO_EXTENSION));
    if ($ext==='xz' || $ext==='gz') {
        log_info("Extracting $dl ...");
        $ok=false;
        if ($ext==='xz') {
            $which = run_command('command -v xz');
            if ($which['code']!==0) log_info("Error: 'xz' not found for $dl");
            else {
                $cmd='xz -dc '.escapeshellarg($dl).' > '.escapeshellarg($target);
                $r = run_command($cmd);
                if (file_exists($target) && filesize($target)>0) $ok=true;
                else log_info("Error: xz decompression failed. Output:\n".$r['output']);
            }
        } else {
            $gz=@gzopen($dl,'rb');
            if ($gz!==false) {
                $out=@fopen($target,'wb');
                if ($out!==false) {
                    while (!gzeof($gz)) { $chunk=gzread($gz,8192); if ($chunk===false) break; fwrite($out,$chunk); }
                    fclose($out); $ok=true;
                } else log_info("Error: cannot open target for write: $target");
                gzclose($gz);
            } else log_info("Error: cannot open gzip file: $dl");
        }
        @unlink($dl); if (!$ok) return null;
    } else { log_info("Error: unsupported extension for $dl"); @unlink($dl); return null; }
    safe_chown($target); chmod($target,0644); log_info("Extracted: $target");
    return $target;
}
function build_epg(string $tmp, string $dst, array $xmls, string $out): void {
    if (empty($xmls)) { log_info("Error: no XML files to process."); exit(1); }
    $final = rtrim($dst, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.$out;
    $part  = $final.'.part';
    $channels=[]; $progs=[];
    log_info("Merging ".count($xmls)." XML files ...");
    foreach ($xmls as $file) {
        if (!is_readable($file)) { log_info("Warn: unreadable: $file"); continue; }
        $r=new XMLReader(); if (!$r->open($file)) { log_info("Warn: cannot open xml: $file"); continue; }
        while ($r->read()) {
            if ($r->nodeType===XMLReader::ELEMENT) {
                $n=strtolower($r->name);
                if ($n==='channel') {
                    $id=$r->getAttribute('id') ?? ''; $x=$r->readOuterXml();
                    if ($id==='') $channels[]=$x; elseif (!isset($channels[$id])) $channels[$id]=$x;
                } elseif ($n==='programme') { $progs[]=$r->readOuterXml(); }
            }
        }
        $r->close();
    }
    $w=new XMLWriter(); if (!$w->openURI($part)) { log_info("Error: cannot open temp file: $part"); exit(1); }
    $w->startDocument('1.0','UTF-8'); $w->setIndent(true);
    $w->startElement('tv'); foreach ($channels as $c) $w->writeRaw($c); foreach ($progs as $p) $w->writeRaw($p); $w->endElement();
    $w->endDocument(); $w->flush();
    if (!@rename($part,$final)) { @unlink($part); log_info("Error: cannot finalize output to $final"); exit(1); }
    safe_chown($final); chmod($final,0755); log_info("EPG saved: $final");
    $gz=$final.'.gz'; $in=@fopen($final,'rb');
    if ($in) {
        $outf=@gzopen($gz,'wb9');
        if ($outf) { while (!feof($in)) { $c=fread($in,8192); if ($c===false) break; gzwrite($outf,$c);} fclose($in); gzclose($outf);
            if (file_exists($gz)) { safe_chown($gz); chmod($gz,0755); log_info("EPG gzipped: $gz"); }
        } else log_info("Warn: cannot open gzip for write: $gz");
    } else log_info("Warn: cannot reopen output for gzip: $final");
}
function cleanup_temp(string $dir): void {
    log_info("Cleaning temp: $dir");
    if (!is_dir($dir)) return;
    $it=new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST);
    foreach ($it as $f) { if ($f->isDir()) @rmdir($f->getRealPath()); else @unlink($f->getRealPath()); }
    @rmdir($dir);
}
function build_from_codes(array $codes, array $URLS, string $TMP, string $DST, ?string $rename=null, bool $includeMagenta=false, int $magentaDays=7): void {
    if (empty($codes)) { log_info("No country codes selected."); exit(1); }
    ensure_dirs($TMP,$DST);
    $selected=[]; foreach ($codes as $c) { if (isset($URLS[$c])) $selected=array_merge($selected,$URLS[$c]); else log_info("Warn: unknown code $c (skipped)"); }
    if (empty($selected) && !$includeMagenta) { log_info("No URLs found for selected codes."); exit(1); }
    $xml=[];
    foreach ($selected as $u) { $f=download_and_extract($u,$TMP); if ($f) $xml[]=$f; }
    if ($includeMagenta) {
        $mf = magenta_build_xml_into_tmp($TMP, max(1,$magentaDays));
        if ($mf) $xml[] = $mf;
    }
    foreach (glob(rtrim($TMP,DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.'*.xml') as $extra) {
        if (!in_array($extra,$xml,true)) $xml[]=$extra;
    }
    if (empty($xml)) { log_info("Error: nothing to merge."); exit(1); }
    $out = $rename ?: implode('_',$codes).'.xml';
    build_epg($TMP,$DST,$xml,$out);
    cleanup_temp($TMP);
    log_info("Done.");
}
$mode = $argv[1] ?? '';
if ($mode === 'build') {
    clear_screen();
    $data = load_sets($JSON_FILE);
    $sets = $data['sets'];
    if (empty($sets)) { log_info("No saved sets in epgset.json. Run interactively to create sets first."); exit(1); }
    $req=[];
    if ($argc>2) {
        for ($i=2;$i<$argc;$i++) {
            $arg=trim($argv[$i]); if ($arg==='') continue;
            foreach (explode(',',$arg) as $p) { $p=trim($p); if ($p!=='' && ctype_digit($p)) $req[]=(int)$p; }
        }
        $req=array_values(array_unique($req));
        if (empty($req)) { log_info("No valid set numbers given. Nothing to build."); exit(1); }
    } else { $req = array_map(fn($s)=>(int)$s['id'],$sets); }
    $byId=[]; foreach ($sets as $s) $byId[(int)$s['id']]=$s;
    foreach ($req as $id) {
        if (!isset($byId[$id])) { log_info("Skip unknown set id: $id"); continue; }
        $s=$byId[$id];
        $codes=array_values(array_unique(array_map('strtoupper',$s['countries'] ?? [])));
        $fn=$s['filename'] ?? (implode('_',$codes).'.xml');
        $incMag = !empty($s['magenta']['include']);
        $magDays = (int)($s['magenta']['days'] ?? 5);
        log_info("Building set #$id -> $fn   [".implode(',',$codes)."]".($incMag?" + MagentaSport($magDays d)":""));
        build_from_codes($codes, $URLS, $TEMP_DIR, $TARGET_DIR, $fn, $incMag, $magDays);
    }
    exit(0);
}

clear_screen();
log_info("EPG Builder (CLI)");
ensure_dirs($TARGET_DIR);
$data = load_sets($JSON_FILE);
$sets = $data['sets'];

while (true) {
    clear_screen();
    fwrite(STDOUT, "\nMenu: [C]reate new set  |  [B]uild existing  |  [D]elete EPG  |  [Q]uit\n");
    $ch = strtolower(prompt("Choose", "c"));
    if ($ch === 'q') {
        log_info("Bye."); exit(0);
    } elseif ($ch === 'b') {
        clear_screen();
        if (empty($sets)) { fwrite(STDOUT,"No saved sets yet.\n"); continue; }
        list_sets($sets);
        $sel = trim(prompt("Enter numbers to build (e.g. 1,3,5) or leave empty to cancel", ""));
        if ($sel==='') continue;
        $ids=[]; foreach (explode(',',$sel) as $p) { $p=trim($p); if ($p!=='' && ctype_digit($p)) $ids[]=(int)$p; }
        $ids=array_values(array_unique($ids));
        if (empty($ids)) { fwrite(STDOUT,"No valid numbers.\n"); continue; }
        $byId=[]; foreach ($sets as $s) $byId[(int)$s['id']]=$s;
        foreach ($ids as $id) {
            if (!isset($byId[$id])) { log_info("Skip unknown set id: $id"); continue; }
            $s=$byId[$id];
            $codes=array_values(array_unique(array_map('strtoupper',$s['countries'] ?? [])));
            $fn=$s['filename'] ?? (implode('_',$codes).'.xml');
            $incMag = !empty($s['magenta']['include']);
            $magDays = (int)($s['magenta']['days'] ?? 5);
            log_info("Building set #$id -> $fn   [".implode(',',$codes)."]".($incMag?" + MagentaSport($magDays d)":""));
            build_from_codes($codes, $URLS, $TEMP_DIR, $TARGET_DIR, $fn, $incMag, $magDays);
        }
        continue;
    } elseif ($ch === 'd') {
        clear_screen();
        if (empty($sets)) { fwrite(STDOUT,"No saved sets yet.\n"); continue; }
        list_sets($sets);
        $sel = trim(prompt("Enter numbers to delete (e.g. 1,3,5) or leave empty to cancel", ""));
        if ($sel==='') continue;
        $ids=[]; foreach (explode(',',$sel) as $p) { $p=trim($p); if ($p!=='' && ctype_digit($p)) $ids[]=(int)$p; }
        $ids=array_values(array_unique($ids));
        if (empty($ids)) { fwrite(STDOUT,"No valid numbers.\n"); continue; }
        $byId=[]; foreach ($sets as $s) $byId[(int)$s['id']]=$s;
        foreach ($ids as $id) {
            if (!isset($byId[$id])) { log_info("Skip unknown set id: $id"); continue; }
            $s=$byId[$id]; $fn=$s['filename'] ?? (implode('_',$s['countries'] ?? []).'.xml');
            $file=$TARGET_DIR.DIRECTORY_SEPARATOR.$fn; $gz=$file.'.gz';
            if (file_exists($file)) { unlink($file); log_info("Deleted: $file"); }
            if (file_exists($gz)) { unlink($gz); log_info("Deleted: $gz"); }
            $ans = strtolower(prompt("Remove set #$id from epgset.json? (y/n)","n"));
            if ($ans==='y') {
                $sets = array_values(array_filter($sets, fn($x)=>(int)$x['id']!==$id));
                $data['sets'] = $sets; save_sets($JSON_FILE, $data);
                log_info("Removed set #$id from epgset.json");
            }
        }
        continue;
    } elseif ($ch === 'c') {
        clear_screen();
        while (true) {
            $codes = choose_countries($EPG_SOURCES);
            if (empty($codes)) { fwrite(STDOUT,"No selection. Cancelled.\n"); break; }
            $def = implode('_',$codes).'.xml';
            $fn  = prompt("Output filename", $def); if (trim($fn)==='') $fn=$def;
            $incMag = strtolower(prompt("Include MagentaSport? (y/n)", "n"))==='y';
            $magDays = 5;
            if ($incMag) {
                $d = trim(prompt("How many days for MagentaSport?", "7"));
                if (ctype_digit($d) && (int)$d>=1 && (int)$d<=14) $magDays=(int)$d;
            }
            $newId = next_id($sets);
            $entry = [
                'id'         => $newId,
                'countries'  => array_values(array_unique(array_map('strtoupper',$codes))),
                'filename'   => $fn,
                'created_at' => (new DateTimeImmutable('now', new DateTimeZone('Europe/Berlin')))->format('d.m.Y'),
                'magenta'    => ['include'=>$incMag,'days'=>$magDays],
            ];
            $sets[]=$entry; $data['sets']=$sets; save_sets($JSON_FILE,$data);
            log_info("Saved as set #$newId  -> $fn  [".implode(',',$entry['countries'])."]".($incMag?" + MagentaSport($magDays d)":""));
            $more = strtolower(prompt("Add another set? (y/n)", "n")); if ($more!=='y') break;
        }
        if (!empty($sets)) {
            list_sets($sets);
            $tb = trim(prompt("Build which set numbers now? (e.g. 1,3) or leave empty to skip", ""));
            if ($tb!=='') {
                $ids=[]; foreach (explode(',',$tb) as $p) { $p=trim($p); if ($p!=='' && ctype_digit($p)) $ids[]=(int)$p; }
                $ids=array_values(array_unique($ids));
                $byId=[]; foreach ($sets as $s) $byId[(int)$s['id']]=$s;
                foreach ($ids as $id) {
                    if (!isset($byId[$id])) { log_info("Skip unknown set id: $id"); continue; }
                    $s=$byId[$id];
                    $codes=array_values(array_unique(array_map('strtoupper',$s['countries'] ?? [])));
                    $fn=$s['filename'] ?? (implode('_',$codes).'.xml');
                    $incMag = !empty($s['magenta']['include']);
                    $magDays = (int)($s['magenta']['days'] ?? 5);
                    log_info("Building set #$id -> $fn   [".implode(',',$codes)."]".($incMag?" + MagentaSport($magDays d)":""));
                    build_from_codes($codes, $URLS, $TEMP_DIR, $TARGET_DIR, $fn, $incMag, $magDays);
                }
            }
        }
        continue;
    } else {
        fwrite(STDOUT, "Invalid choice.\n"); continue;
    }
}
?>
 
Zuletzt bearbeitet:
@Manitu12
Packen wäre eine tolle Sache.
Ansonsten passt alles wunderbar.
Tolle Kiste. Danke für's Teilen.

@salidos
Ich hatte es mal testweise in xampp gestartet, aber da kamen Fehlermeldungen. Vielleicht braucht es da echtes Linux oder ich habe es falsch aufgerufen. Mal sehen, ob's noch andere Rückmeldungen gibt.
 
Ein echtes Linus wäre definitiv von Vorteil 😉
Ansonsten schau, ob du für php auch alles aktiviert hast.
Script funktioniert.
Erster Start nur mit

sudo php epg.php

aufrufen.
Die Zahlen dahinter sind nur wenn cronjob angelegt ist.
 
Zuletzt bearbeitet:
Zurück
Oben