{"record":{"id":"d205329fded8915b","repo":"ytdl-org/youtube-dl","slug":"stream-is-offline","errorCode":null,"errorMessage":"Stream is offline","messagePattern":"Stream is offline","errorType":"exception","errorClass":"ExtractorError","httpStatus":null,"severity":"warning","filePath":"youtube_dl/extractor/picarto.py","lineNumber":50,"sourceCode":"        data = self._download_json(\n            'https://ptvintern.picarto.tv/ptvapi', channel_id, query={\n                'query': '''{\n  channel(name: \"%s\") {\n    adult\n    id\n    online\n    stream_name\n    title\n  }\n  getLoadBalancerUrl(channel_name: \"%s\") {\n    url\n  }\n}''' % (channel_id, channel_id),\n            })['data']\n        metadata = data['channel']\n\n        if metadata.get('online') == 0:\n            raise ExtractorError('Stream is offline', expected=True)\n        title = metadata['title']\n\n        cdn_data = self._download_json(\n            data['getLoadBalancerUrl']['url'] + '/stream/json_' + metadata['stream_name'] + '.js',\n            channel_id, 'Downloading load balancing info')\n\n        formats = []\n        for source in (cdn_data.get('source') or []):\n            source_url = source.get('url')\n            if not source_url:\n                continue\n            source_type = source.get('type')\n            if source_type == 'html5/application/vnd.apple.mpegurl':\n                formats.extend(self._extract_m3u8_formats(\n                    source_url, channel_id, 'mp4', m3u8_id='hls', fatal=False))\n            elif source_type == 'html5/video/mp4':\n                formats.append({\n                    'url': source_url,","sourceCodeStart":32,"sourceCodeEnd":68,"githubUrl":"https://github.com/ytdl-org/youtube-dl/blob/956b8c585591b401a543e409accb163eeaaa1193/youtube_dl/extractor/picarto.py#L32-L68","documentation":"Raised by PicartoIE (live-stream site) when the GraphQL channel metadata reports online == 0. It is an expected error meaning the channel exists but is not currently broadcasting, so there is no live stream to record. It fires before any CDN/load-balancer requests are made.","triggerScenarios":"Running yt-dlp/youtube-dl on a picarto.tv/channel URL while the streamer is offline. The GraphQL query for channel_id returns metadata with 'online': 0.","commonSituations":"Scheduled recordings that start before the streamer goes live; following channels across time zones; channel ended the stream moments before the check.","solutions":["Retry when the channel is actually live — check picarto.tv in a browser first","Wrap the call in a polling loop with a long interval (e.g. re-check every 60s) and start recording once online != 0","Use --live-from-start or a monitoring script that waits for the channel to come online"],"exampleFix":"# before\nyoutube_dl 'https://picarto.tv/SomeOfflineChannel'\n# after\n# poll until live, then record\nwhile ! youtube_dl --simulate 'https://picarto.tv/SomeOfflineChannel' 2>/dev/null; do sleep 60; done\nyoutube_dl 'https://picarto.tv/SomeOfflineChannel'","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"while True:\n    try:\n        ydl.download(['https://picarto.tv/%s' % channel])\n        break\n    except ExtractorError as e:\n        if e.expected and 'offline' in str(e):\n            time.sleep(60)\n            continue\n        raise","preventionTips":["Poll channel status on an interval before attempting capture","Treat 'Stream is offline' as a retryable condition, not an error to report","Use yt-dlp's --wait-for-video for live channels instead of manual loops"],"tags":["picarto","live-stream","offline","extractor-error","expected"],"backgroundTag":null,"analyzedSha":"956b8c585591b401a543e409accb163eeaaa1193","analyzedAt":"2026-08-14T18:59:47.863Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}