stefanodvx/crunpyroll

You need to implement this function to fix "TOO_MANY_ACTIVE_STREAMS" issue @inks007

Closed this issue · 6 comments

When you request more than 20 streams in CR they blocked temporally the account because right no crunpyroll is not closing streaming after requested, so you need to implement this function (@inks007): smirgol/plugin.video.crunchyroll@23ade04

I tried to do it with this method:

from crunpyroll import enums
from crunpyroll import types
import requests
import crunpyroll

class ClearActiveStream:
	async def clear_active_stream(self: "crunpyroll.Client", episode_id: str, token: str):
		if not episode_id or not token:
			return

		try:
			await self.session.retrieve()
			response = await self.api_request(
				method="DELETE",
				endpoint=f"v1/token/{episode_id}/{token}",
				host=enums.APIHost.PLAY_SERVICE
			)
		except requests.exceptions.RequestException as e:
			# catch timeout or any other possible exception
			print(e)
			return

and then on client.py this change:

	@staticmethod
	def parse_response(response: httpx.Response) -> Optional[Union[Dict, str]]:
		status_code = response.status_code
		text_content = response.text
		message = f"[{status_code}] {text_content}"
		try:
			content = response.json()
		except json.JSONDecodeError:
			content = response.text
		if status_code != 200:
			if status_code == 204:
				return content
			raise CrunpyrollException(message)
		return content

It is possible that there is a better solution, perhaps you who have more experience modifying crunpyroll know how to improve it.

Thanks for the heads up. The delete method seems to work, but the response.text seems to always be empty even if you pass incorrect tokens...

Now I run into HTTP 503 Forbidden when accessing the segments of the following stream too quickly. Perhaps due to rate-limiting on the server-side.

Thanks for the heads up. The delete method seems to work, but the response.text seems to always be empty even if you pass incorrect tokens...

Now I run into HTTP 503 Forbidden when accessing the segments of the following stream too quickly. Perhaps due to rate-limiting on the server-side.

I use delete active streams after i download the files of that episode, i think is the correct way to dont get any error, dont you think?

I have a problem when I launch the episode download and put as fabrebatalla18 says await client.delete_active_stream behind the download function to run when it finishes I get the following error, what exactly am I doing wrong?
Do I have to call some missing import?
Do I have to make another way the call to perform the download, or it is not done this way?

Sorry if you don't understand me well, since I use translator, and I don't understand much of how phyton works since I am trying to adapt to my needs a script that was given to me.

import crunpyroll
import asyncio
import sys
import os
import requests
from pywidevine.cdm import Cdm
from pywidevine.pssh import PSSH
from pywidevine.device import Device
from uuid import uuid4

client = crunpyroll.Client(
*
*
*
)

async def main():
	await client.start()
*
*
*
	#We launch the download
	# yt_dlp_url contains download instructions with yt_dlp.exe
	os.system(yt_dlp_url)
	
	# We wait for it to finish downloading
	await client.delete_active_stream(
	streams.media_id,
	token=streams.token
	)


	# Get Widevine PSSH from manifest
*
*
*
	cdm.parse_license(session_id, license)
	print()
	for key in cdm.get_keys(session_id, "CONTENT"):
		print((f"{key.kid.hex}:{key.key.hex()}"),file=open('./Temporales/Key.txt','a'))
	cdm.close(session_id)

asyncio.run(main())
  File "E:\Crunchy Videos\Temporales\crunpyroll_cr2.py", line 117, in <module>
    asyncio.run(main())
  File "C:\Python311\Lib\asyncio\runners.py", line 190, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "C:\Python311\Lib\asyncio\runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Python311\Lib\asyncio\base_events.py", line 653, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "E:\Crunchy Videos\Temporales\crunpyroll_cr2.py", line 106, in main
    license = await client.get_license(
              ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Python311\Lib\site-packages\crunpyroll\methods\get_license.py", line 34, in get_license
    response = await self.api_request(
               ^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Python311\Lib\site-packages\crunpyroll\client.py", line 117, in api_request
    return Client.parse_response(response, method=method)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Python311\Lib\site-packages\crunpyroll\client.py", line 90, in parse_response
    raise CrunpyrollException(message)
crunpyroll.errors.CrunpyrollException: [401] "Unauthorized"`

I update, if I put it as in the example of @inks007
it works, but before it gave me the same error 503 that's why I changed as fabrebatalla18 said, but I get that error ><
testing now that it goes as inks017 has put it I have tried to download 4 files in a row and I see that I get 4 sessions in connected devices, so I do not know if it really works or is something else, because this should no longer happen, the multiple sessions, right?

	cdm.parse_license(session_id, license)
	print()
	for key in cdm.get_keys(session_id, "CONTENT"):
		print((f"{key.kid.hex}:{key.key.hex()}"),file=open('./Temporales/Key.txt','a'))
	cdm.close(session_id)
	
	# We wait for it to finish downloading
	await client.delete_active_stream(
	streams.media_id,
	token=streams.token
	)
	
asyncio.run(main())

Captra

Where the line says device_id=str(uuid4()),, replace str(uuid4()) with a fixed UUID.

You can generate a UUID below:

from uuid import uuid4
print(str(uuid4()))

Yes, in the end I did just that to have a uuid and leave it fixed.