Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Client.send_video method #386

Closed
Meorge opened this issue Sep 10, 2024 · 21 comments · Fixed by #395
Closed

Add Client.send_video method #386

Meorge opened this issue Sep 10, 2024 · 21 comments · Fixed by #395
Labels
enhancement New feature or request

Comments

@Meorge
Copy link
Contributor

Meorge commented Sep 10, 2024

With video support coming to Bluesky very soon, I have code for a video-posting bot mostly ready to go.

I noticed that while we have Client.send_image and Client.send_images for easy image uploading, there are no corresponding Client.send_video or Client.send_videos methods. It appears there is already code written for handling video uploading on a lower level of the API, and it just needs to have the higher-level Client wrappers written for it.

This might be something I could try my hand at, although it wouldn't surprise me that others are already working on it and/or are much more experienced with the codebase than I am (that is to say, they have experience with the codebase, haha).

@capyvara
Copy link

I'm not sure it will be that simple, have you been able to make a video work?

I've tried using the uploadBlob + embed and it runs but doesn't work at the end (says video not found on the post on bsky), by looking at their client repo it uses https://video.bsky.app/xrpc/app.bsky.video.uploadVideo URL to actually upload the video, with a JWT token obtained from com.atproto.server.getServiceAuth, I think this is to somehow trigger server processing.

I've tried to use this api and it returns an "Unauthorized: uploads_disabled" so I can't test yet.

@Meorge
Copy link
Contributor Author

Meorge commented Sep 11, 2024

I haven't been able to make one work yet either, unfortunately; I'd expect that they would have uploads through the API disabled until they open it up publicly. As such, it is entirely possible it'll end up being more complicated, but I guess there won't really be a way to know until the API is opened up 😅

@Meorge
Copy link
Contributor Author

Meorge commented Sep 11, 2024

I copied and modified the send_images/send_image methods to make a send_video method, and so far am getting what sounds like the same results as you (video doesn't load on the website). Again, I'm hoping that this is just due to them not flipping the switch on public video support. Once they announce it's available, I can try running my test again and if it works, open a PR for this.

@MarshalX
Copy link
Owner

As far as I can tell this will not work by just copy-pasting send_image. Because we need to use the upload_video method. Which will spawn a backend job to process the video. We need to wait for the finish of the job. Only after that, we will obtain a blob reference which could be attached as embed to the post. The waiting should be implemented by polling status using the get job status method

@Meorge
Copy link
Contributor Author

Meorge commented Sep 11, 2024

Ah, alright, that makes sense. If I have time later (and someone else hasn't started working on a solution) I can give that a try!

@MarshalX
Copy link
Owner

It looks like calling upload_blob also will work but in a bit different way. You will post the video right after the upload, but it will not be available until the server picks it up for processing and ends the processing. Also, if you are not allowed to post a video yet the video will never be processed.

image

@Meorge
Copy link
Contributor Author

Meorge commented Sep 11, 2024

So with one of my accounts now supporting video uploads in the web GUI, I tried running a script to upload a video using some code from upload_video, but ran into an error earlier than expected...

from atproto import Client
from atproto_client.models import AppBskyVideoUploadVideo, AppBskyEmbedVideo
from atproto_client.models.utils import get_response_model
from time import sleep

def main() -> None:
    client = Client()
    client.login("username", "password")

    # replace the path to your video file
    with open("video.mp4", "rb") as f:
        vid_data = f.read()

    response = client.invoke_procedure(
        'app.bsky.video.uploadVideo',
        data=vid_data,
        input_encoding='video/mp4',
        output_encoding='application/json'
    )

    response_model = get_response_model(response, AppBskyVideoUploadVideo.Response)

    succeeded = False
    while True:
        print(f"Progress: {response_model.job_status.progress}, status: {response_model.job_status.state}")
        status = response_model.job_status.state
        if status == "JOB_STATE_COMPLETED":
            succeeded = True
            break
        if status == "JOB_STATE_FAILED":
            print(f"Error: {response_model.job_status.error}")
            break
        sleep(1)

    if succeeded:
        print(f"Blob ref {response_model.job_status.blob}")
        client.send_post(
            text="hello there",
            embed=[AppBskyEmbedVideo.Main(video=response_model.job_status.blob, alt="a cool video")],
        )
    else:
        print("failed")


if __name__ == "__main__":
    main()

Crashed with the following:

Traceback (most recent call last):
  File "/Users/malcolmanderson/Documents/Repositories/atproto-send-videos-test/test_send_video.py", line 47, in <module>
    main()
  File "/Users/malcolmanderson/Documents/Repositories/atproto-send-videos-test/test_send_video.py", line 15, in main
    response = client.invoke_procedure(
               ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/malcolmanderson/Documents/Repositories/atproto-send-videos-test/.venv/lib/python3.12/site-packages/atproto_client/client/base.py", line 115, in invoke_procedure
    return self._invoke(InvokeType.PROCEDURE, url=self._build_url(nsid), params=params, data=data, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/malcolmanderson/Documents/Repositories/atproto-send-videos-test/.venv/lib/python3.12/site-packages/atproto_client/client/client.py", line 41, in _invoke
    return super()._invoke(invoke_type, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/malcolmanderson/Documents/Repositories/atproto-send-videos-test/.venv/lib/python3.12/site-packages/atproto_client/client/base.py", line 122, in _invoke
    return self.request.post(**kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/malcolmanderson/Documents/Repositories/atproto-send-videos-test/.venv/lib/python3.12/site-packages/atproto_client/request.py", line 161, in post
    return _parse_response(self._send_request('POST', *args, **kwargs))
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/malcolmanderson/Documents/Repositories/atproto-send-videos-test/.venv/lib/python3.12/site-packages/atproto_client/request.py", line 151, in _send_request
    _handle_request_errors(e)
  File "/Users/malcolmanderson/Documents/Repositories/atproto-send-videos-test/.venv/lib/python3.12/site-packages/atproto_client/request.py", line 50, in _handle_request_errors
    raise exception
  File "/Users/malcolmanderson/Documents/Repositories/atproto-send-videos-test/.venv/lib/python3.12/site-packages/atproto_client/request.py", line 149, in _send_request
    return _handle_response(response)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/malcolmanderson/Documents/Repositories/atproto-send-videos-test/.venv/lib/python3.12/site-packages/atproto_client/request.py", line 79, in _handle_response
    raise exceptions.RequestException(error_response)
atproto_client.exceptions.RequestException: Response(success=False, status_code=404, content=XrpcError(error='XRPCNotSupported', message='XRPCNotSupported'), headers={'x-powered-by': 'Express', 'access-control-allow-origin': '*', 'ratelimit-limit': '3000', 'ratelimit-remaining': '2997', 'ratelimit-reset': '1726090794', 'ratelimit-policy': '3000;w=300', 'cache-control': 'private', 'vary': 'Authorization, Accept-Encoding', 'content-type': 'application/json; charset=utf-8', 'content-length': '57', 'etag': 'W/"39-WI+yvBMnUIXHN9mmjmhFBSzX9h4"', 'date': 'Wed, 11 Sep 2024 21:36:43 GMT', 'keep-alive': 'timeout=90', 'strict-transport-security': 'max-age=63072000'})

So it sounds like here it didn't even get to the stage of listening for a response on the job status :(

@MarshalX
Copy link
Owner

@Meorge I don't have access to the video yet :( so I can not see the network traffic of the official app. I guess that they have different video services. And we should send requests to the brand new service instead of PDS. Could you check network tab in browser dev tools?

@Meorge
Copy link
Contributor Author

Meorge commented Sep 11, 2024

GOT IT WORKING!!

https://bsky.app/profile/turnabout-ca-dmv.meorge.com

def main() -> None:
    client = Client()
    client.login("adsfasd", "adsfdas")

    # replace the path to your video file
    with open("video.mp4", "rb") as f:
        vid_data = f.read()

    blob = client.upload_blob(vid_data)
    client.send_post(
        text="christmas",
        embed=AppBskyEmbedVideo.Main(video=blob.blob, alt="a cool video"),
    )

And funnily enough, all of the previous upload_blob attempts also are now working properly.

@MarshalX
Copy link
Owner

I think this upload_blob is good enough to be the basic high-level method. same as send_image. Because proper usage of job_id, handling user cancellation, and displaying the progress bar of the video processing could not be covered by SDK for 100% of developer needs. Everyone will have a different approach to it.

We could provide an advanced example that will show how to use a processing job and display a progress bar in the terminal output

@MarshalX MarshalX changed the title Add Client.send_video and Client.send_videos methods Add Client.send_video method Sep 12, 2024
@MarshalX
Copy link
Owner

renamed since bsky allows only 1 video per post

@MarshalX MarshalX added the enhancement New feature or request label Sep 12, 2024
@capyvara
Copy link

@Meorge I've tried this upload_blob approach (copied the same code) and it hasn't worked for be, it still shows "Video not found", has it worked more than once with you?

@MarshalX
Copy link
Owner

MarshalX commented Sep 13, 2024

@Meorge I've tried this upload_blob approach (copied the same code) and it hasn't worked for be, it still shows "Video not found", has it worked more than once with you?

It means that you don't have access to post videos. Do you have upload video button in the app?

Upd. The another reason is that video is under processing and will show up soon

@capyvara
Copy link

Yes, it works on the interface (they made the rollout to 100% yesterday), has it work with you @MarshalX ? may be something wrong I did here.

@MarshalX
Copy link
Owner

Yes, it works on the interface (they made the rollout to 100% yesterday), has it work with you @MarshalX ? may be something wrong I did here.

Yes it works fine

@capyvara
Copy link

Really doesn't work here, tried even with multiple accounts, just to be sure, using published PyPI atproto 0.0.53 and Python 3.11.4 and not dev branch.

[tool.poetry.dependencies]
python = "~3.11"
atproto = "^0.0.53"
requests = "^2.32.3"

@MarshalX
Copy link
Owner

pls show the code and the video file

@Meorge
Copy link
Contributor Author

Meorge commented Sep 13, 2024

@Meorge I've tried this upload_blob approach (copied the same code) and it hasn't worked for be, it still shows "Video not found", has it worked more than once with you?

Yes, it has - all of the posts on this account, save for one, were done using my branch of the code and the upload_blob approach.

I have seen that the video will often not show up immediately - it'll say "Video not found" when the post is made. But a couple seconds later the actual video will show up.

Can you paste the exact script you're using here (minus credentials of course)? What is the video file you're trying to upload? Additionally/alternatively, could you try using the version on the branch from my PR and the send_video method there?

@capyvara
Copy link

Code:

from atproto import Client, models

def main() -> None:
    client = Client()
    client.login('...', '...')

    # replace the path to your video file
    with open("scream.mp4", "rb") as f:
        vid_data = f.read()

    blob = client.upload_blob(vid_data)
    client.send_post(
        text="christmas",
        embed=models.AppBskyEmbedVideo.Main(video=blob.blob, alt="a cool video"),
    )

if __name__ == '__main__':
    main()

But is seems to have worked after I changed the video file, in this three posts first was uploaded using their interface, second was the same video but via api, third was a different video, since I've used the initial video to try the api first (with uploadVideo route) it may got messed up somehow on their backend?

image

@MarshalX
Copy link
Owner

MarshalX commented Sep 13, 2024

@capyvara Yes, it looks like video service issues. I will provide guides on how to use the video service soon. where will it be possible to get a well described failure

@MarshalX
Copy link
Owner

@capyvara i ve added a basic example of how to work with video service here: #400

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants