{"id":543,"date":"2025-03-16T10:05:40","date_gmt":"2025-03-16T14:05:40","guid":{"rendered":"https:\/\/lektrikpuke.com\/blog\/?p=543"},"modified":"2025-03-16T10:05:40","modified_gmt":"2025-03-16T14:05:40","slug":"add-xmltv-data-to-jellyfin-from-hdhomerun","status":"publish","type":"post","link":"https:\/\/lektrikpuke.com\/blog\/2025\/03\/16\/add-xmltv-data-to-jellyfin-from-hdhomerun\/","title":{"rendered":"Add XMLTV Data To JellyFin From HDHomerun"},"content":{"rendered":"<p>I have a NAS on which I have installed Jellyfin.\u00a0 I chose Jellyfin from the usuals, because I didn&#8217;t want to pay for another streaming service (if you have to pay to watch tv, it might as well be a streaming service).<\/p>\n<p>Anyway, jellyfin automatically detected the HDHomerun (2 tuner, old) connected to my network.\u00a0 And while I could watch live tv via the Silicondust\/HDHomerun app, the ability to see all of the tv programming on Jellyfin was just too enticing to pass by.<\/p>\n<p>Turned on logging in scheduled tasks.\u00a0 Errors, added group docker with admin privileges.<\/p>\n<p>synology task scheduler run result interrupted &#8211; https:\/\/community.synology.com\/enu\/forum\/1\/post\/150119?reply=467768<\/p>\n<p>sudo chmod 666 \/var\/run\/docker.sock &#8211; https:\/\/stackoverflow.com\/questions\/48957195\/how-to-fix-docker-got-permission-denied-issue\u00a0 chmod 666.<\/p>\n<p>changed save directory web folder &#8211; issues with writing outside the web folder<\/p>\n<p>then setup user permissions in nas &#8211; control panel &gt; shared folder &gt; web &gt; edit &gt; permissions &gt; system internal user (drop down) &gt; jellyfin initial user (during setup) &gt; give it read access<\/p>\n<p>cron job to get tv guide xml data:<\/p>\n<pre style=\"font-size: 10px;\">&lt;?php\r\n\r\nfunction SaveFile($file, $fileDestination) {\r\n    if (isset($file) &amp;&amp; isset($fileDestination)) {\r\n        try {\r\n            $fHandle = fopen($fileDestination, \"w\");\r\n            if(!$fHandle){\r\n                throw new Exception(\"Could not open file destination ({$fileDestination}).\");\r\n            }\r\n            \r\n            if (fwrite($fHandle, $file) === FALSE) {\r\n                throw new Exception(\"Cannot write to file ({$fileDestination}).\");\r\n            }\r\n\r\n            fclose($fHandle);\r\n        }\r\n        catch (Exception $e) {\r\n            throw $e;\r\n        }\r\n\r\n        return TRUE;\r\n    }\r\n\r\n    return false;\r\n} \/\/ end SaveFile\r\n\r\n\/\/ ip address and hd homerun location on your lan\r\n$hdHomerunUrl = 'http:\/\/xxx.xxx.xxx.xxx\/discover.json';\r\n\r\n$ch = curl_init();\r\n    \r\ncurl_setopt($ch, CURLOPT_URL, $hdHomerunUrl);\r\ncurl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);\r\ncurl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');\r\ncurl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla\/5.0 (Macintosh; Intel Mac OS X 10.11; rv:52.0) Gecko\/20100101 Firefox\/52.0');\r\ncurl_setopt($ch, CURLOPT_ENCODING, \"\");\r\ncurl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0);\r\ncurl_setopt($ch, CURLOPT_TIMEOUT, 120);\r\n\r\n$hdHomeRunData = curl_exec($ch); \/\/ curl hdhomerun for data\r\n\r\ncurl_close($ch);\r\n\r\n$dataDecoded = json_decode($hdHomeRunData, true); \/\/ decode data\r\n\r\n$authCode =  $dataDecoded['DeviceAuth']; \/\/ get device authorization\r\n\r\nif(isset($authCode) &amp;&amp; $authCode != ''){\r\n    \/\/ using device auth, query api.hdhomerun for data\r\n    $xmlTvUrl = 'https:\/\/api.hdhomerun.com\/api\/xmltv?DeviceAuth=' . $authCode;\r\n\r\n    try {\r\n        $ch=curl_init();\r\n\r\n        curl_setopt($ch, CURLOPT_URL, $xmlTvUrl);\r\n        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);\r\n        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');\r\n        \/\/ 'Mozilla\/5.0 (Macintosh; Intel Mac OS X 10.11; rv:52.0) Gecko\/20100101 Firefox\/52.0'\r\n        curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla\/5.0 (Macintosh; Intel Mac OS X 10.11; rv:52.0) Gecko\/20100101 Firefox\/52.0');\r\n        curl_setopt($ch, CURLOPT_ENCODING, \"\");\r\n        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0);\r\n        curl_setopt($ch, CURLOPT_TIMEOUT, 120);\r\n\r\n        $xmlData = curl_exec($ch);\r\n\r\n        curl_close($ch);\r\n    }\r\n    catch (Exception $ex) {\r\n        error_log($ex, 0);\r\n        die();\r\n        exit();\r\n    }\r\n\r\n    $fileLoc = '\/STORAGE_LOCATION_ON_YOUR_NAS\/';\r\n    \r\n    \/\/ tried saving to database\r\n    \/\/ mariadb error Got a packet bigger than 'max_allowed_packet' bytes tried updating - forget it for now\r\n    \r\n    if (isset($xmlData) &amp;&amp; $xmlData) {\r\n        if(file_exists($fileLoc . 'xmltv.xml')){ \/\/ if file exists\r\n            try{\r\n                \/\/ rename appending date\r\n                $renamed = rename($fileLoc . 'xmltv.xml', $fileLoc . 'xmltv-' . date('Y-m-d') . '.xml');\r\n                if(!$renamed){\r\n                    throw new Exception('Something went wrong. Either file was not found or could not rename');\r\n                }\r\n            } \r\n            catch (Exception $e) {\r\n                error_log($e, 0);\r\n                die($e);\r\n                exit();\r\n            }\r\n        }        \r\n\r\n        try{\r\n            SaveFile($xmlData, $fileLoc . 'xmltv.xml'); \/\/ try saving new curled file\r\n        }\r\n        catch (Exception $e){\r\n            error_log($e, 0);\r\n            die($e);\r\n            exit();\r\n        }\r\n    }\r\n}\r\n\r\n<\/pre>\n<p>Set up above script as a cron job.\u00a0 Point jellyfin to storage location for tv guide xml data and you&#8217;re done!\u00a0 Sorry for the scarce details, as I did this quite a while ago.\u00a0 Personally, I have gone back to using hdhomerun&#8217;s recording control, but it&#8217;s still nice to see the tv guide data in Jellyfin.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I have a NAS on which I have installed Jellyfin.\u00a0 I chose Jellyfin from the usuals, because I didn&#8217;t want to pay for another streaming service (if you have to pay to watch tv, it might as well be a &hellip; <a href=\"https:\/\/lektrikpuke.com\/blog\/2025\/03\/16\/add-xmltv-data-to-jellyfin-from-hdhomerun\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[72,148,149],"tags":[246,5,6,89,245,132],"class_list":["post-543","post","type-post","status-publish","format-standard","hentry","category-computer-stuff","category-mysql-computer-stuff","category-php-computer-stuff","tag-guide","tag-mysql","tag-php","tag-script","tag-tv","tag-xml"],"_links":{"self":[{"href":"https:\/\/lektrikpuke.com\/blog\/wp-json\/wp\/v2\/posts\/543","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/lektrikpuke.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/lektrikpuke.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/lektrikpuke.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/lektrikpuke.com\/blog\/wp-json\/wp\/v2\/comments?post=543"}],"version-history":[{"count":1,"href":"https:\/\/lektrikpuke.com\/blog\/wp-json\/wp\/v2\/posts\/543\/revisions"}],"predecessor-version":[{"id":544,"href":"https:\/\/lektrikpuke.com\/blog\/wp-json\/wp\/v2\/posts\/543\/revisions\/544"}],"wp:attachment":[{"href":"https:\/\/lektrikpuke.com\/blog\/wp-json\/wp\/v2\/media?parent=543"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/lektrikpuke.com\/blog\/wp-json\/wp\/v2\/categories?post=543"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/lektrikpuke.com\/blog\/wp-json\/wp\/v2\/tags?post=543"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}