{"record":{"id":"61e39b400d20c313","repo":"ytdl-org/youtube-dl","slug":"self-parse-json-e-cause-read-none-errormessa","errorCode":null,"errorMessage":"self._parse_json(e.cause.read(), None)['errorMessage']","messagePattern":"self\\._parse_json\\(e\\.cause\\.read\\(\\), None\\)\\['errorMessage'\\]","errorType":"exception","errorClass":"ExtractorError","httpStatus":401,"severity":"error","filePath":"youtube_dl/extractor/playplustv.py","lineNumber":59,"sourceCode":"\n    def _real_initialize(self):\n        email, password = self._get_login_info()\n        if email is None:\n            self.raise_login_required()\n\n        req = PUTRequest(\n            'https://api.playplus.tv/api/web/login', json.dumps({\n                'email': email,\n                'password': password,\n            }).encode(), {\n                'Content-Type': 'application/json; charset=utf-8',\n            })\n\n        try:\n            self._token = self._download_json(req, None)['token']\n        except ExtractorError as e:\n            if isinstance(e.cause, compat_HTTPError) and e.cause.code == 401:\n                raise ExtractorError(self._parse_json(\n                    e.cause.read(), None)['errorMessage'], expected=True)\n            raise\n\n        self._profile = self._call_api('Profiles')['list'][0]['_id']\n\n    def _real_extract(self, url):\n        project_id, media_id = re.match(self._VALID_URL, url).groups()\n        media = self._call_api(\n            'Media', media_id, {\n                'profileId': self._profile,\n                'projectId': project_id,\n                'mediaId': media_id,\n            })['obj']\n        title = media['title']\n\n        formats = []\n        for f in media.get('files', []):\n            f_url = f.get('url')","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/ytdl-org/youtube-dl/blob/956b8c585591b401a543e409accb163eeaaa1193/youtube_dl/extractor/playplustv.py#L41-L77","documentation":"Raised by PlayPlusTV's login when the api.playplus.tv/api/web/login PUT returns HTTP 401. The extractor reads the 401 body, parses JSON, and re-raises its 'errorMessage' field verbatim (expected). The catalog's 'message' is that expression source — the actual text is PlayPlus's (e.g. invalid credentials).","triggerScenarios":"PUT to the login endpoint with email/password the service rejects → 401 with a JSON body containing errorMessage. Only occurs when credentials are supplied (login is mandatory; _call_api needs the token for Profiles/Media).","commonSituations":"Wrong password in .netrc; subscription lapsed so the account can't get a web token; regional PlayPlus (Brazil) service closure; body not JSON on some failures would instead raise a parse error.","solutions":["Verify email/password by logging in at playplus.tv in a browser","Confirm the subscription is active — expired accounts may auth but fail later at Media calls","If the service is discontinued in your region, no fix is possible from the client"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"import requests\nr = requests.put('https://api.playplus.tv/api/web/login',\n                 json={'email': E, 'password': P})\nif r.status_code == 401:\n    raise SystemExit('PlayPlus rejected credentials: ' + r.json().get('errorMessage', '?'))","typeGuard":null,"tryCatchPattern":"try:\n    ydl.extract_info(url)\nexcept ExtractorError as e:\n    if e.expected and ('errorMessage' in str(e) or 'credentials' in str(e).lower()):\n        sys.exit('Fix PlayPlus credentials in .netrc')","preventionTips":["Verify the subscription is active, not just the password","One manual login test before long batches","Expect service-closure errors in affected regions"],"tags":["playplustv","authentication","http-401","login","extractor-error","expected"],"backgroundTag":null,"analyzedSha":"956b8c585591b401a543e409accb163eeaaa1193","analyzedAt":"2026-08-14T18:59:47.863Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}