From 9668e5fa7710ea1ff76b359323aed587fea500bd Mon Sep 17 00:00:00 2001 From: Malcolm Anderson Date: Fri, 13 Sep 2024 08:58:50 -0700 Subject: [PATCH] Add `Client.send_video` high-level method (#395) Co-authored-by: Ilya (Marshal) --- examples/send_video.py | 16 +++++++ .../atproto_client/client/async_client.py | 45 +++++++++++++++++++ packages/atproto_client/client/client.py | 45 +++++++++++++++++++ .../clients/generate_async_client.py | 6 ++- 4 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 examples/send_video.py diff --git a/examples/send_video.py b/examples/send_video.py new file mode 100644 index 00000000..4541b237 --- /dev/null +++ b/examples/send_video.py @@ -0,0 +1,16 @@ +from atproto import Client + + +def main() -> None: + client = Client() + client.login('my-handle', 'my-password') + + # replace the path to your video file + with open('video.mp4', 'rb') as f: + vid_data = f.read() + + client.send_video(text='Post with video from Python', video=vid_data, video_alt='Text version of the video (ALT)') + + +if __name__ == '__main__': + main() diff --git a/packages/atproto_client/client/async_client.py b/packages/atproto_client/client/async_client.py index 93b9366d..741220f6 100644 --- a/packages/atproto_client/client/async_client.py +++ b/packages/atproto_client/client/async_client.py @@ -129,6 +129,7 @@ async def send_post( 'models.AppBskyEmbedExternal.Main', 'models.AppBskyEmbedRecord.Main', 'models.AppBskyEmbedRecordWithMedia.Main', + 'models.AppBskyEmbedVideo.Main', ] ] = None, langs: t.Optional[t.List[str]] = None, @@ -287,6 +288,50 @@ async def send_image( facets=facets, ) + async def send_video( + self, + text: t.Union[str, TextBuilder], + video: bytes, + video_alt: t.Optional[str] = None, + profile_identify: t.Optional[str] = None, + reply_to: t.Optional['models.AppBskyFeedPost.ReplyRef'] = None, + langs: t.Optional[t.List[str]] = None, + facets: t.Optional[t.List['models.AppBskyRichtextFacet.Main']] = None, + ) -> 'models.AppBskyFeedPost.CreateRecordResponse': + """Send post with attached video. + + Note: + If `profile_identify` is not provided will be sent to the current profile. + + Args: + text: Text of the post. + video: Binary video to attach. + video_alt: Text version of the video. + profile_identify: Handle or DID. Where to send post. + reply_to: Root and parent of the post to reply to. + langs: List of used languages in the post. + facets: List of facets (rich text items). + + Returns: + :obj:`models.AppBskyFeedPost.CreateRecordResponse`: Reference to the created record. + + Raises: + :class:`atproto.exceptions.AtProtocolError`: Base exception. + """ + if video_alt is None: + video_alt = '' + + upload = await self.upload_blob(video) + + return await self.send_post( + text, + profile_identify=profile_identify, + reply_to=reply_to, + embed=models.AppBskyEmbedVideo.Main(video=upload.blob, alt=video_alt), + langs=langs, + facets=facets, + ) + async def get_post( self, post_rkey: str, profile_identify: t.Optional[str] = None, cid: t.Optional[str] = None ) -> 'models.AppBskyFeedPost.GetRecordResponse': diff --git a/packages/atproto_client/client/client.py b/packages/atproto_client/client/client.py index 078ba0a7..2e273e0a 100644 --- a/packages/atproto_client/client/client.py +++ b/packages/atproto_client/client/client.py @@ -120,6 +120,7 @@ def send_post( 'models.AppBskyEmbedExternal.Main', 'models.AppBskyEmbedRecord.Main', 'models.AppBskyEmbedRecordWithMedia.Main', + 'models.AppBskyEmbedVideo.Main', ] ] = None, langs: t.Optional[t.List[str]] = None, @@ -278,6 +279,50 @@ def send_image( facets=facets, ) + def send_video( + self, + text: t.Union[str, TextBuilder], + video: bytes, + video_alt: t.Optional[str] = None, + profile_identify: t.Optional[str] = None, + reply_to: t.Optional['models.AppBskyFeedPost.ReplyRef'] = None, + langs: t.Optional[t.List[str]] = None, + facets: t.Optional[t.List['models.AppBskyRichtextFacet.Main']] = None, + ) -> 'models.AppBskyFeedPost.CreateRecordResponse': + """Send post with attached video. + + Note: + If `profile_identify` is not provided will be sent to the current profile. + + Args: + text: Text of the post. + video: Binary video to attach. + video_alt: Text version of the video. + profile_identify: Handle or DID. Where to send post. + reply_to: Root and parent of the post to reply to. + langs: List of used languages in the post. + facets: List of facets (rich text items). + + Returns: + :obj:`models.AppBskyFeedPost.CreateRecordResponse`: Reference to the created record. + + Raises: + :class:`atproto.exceptions.AtProtocolError`: Base exception. + """ + if video_alt is None: + video_alt = '' + + upload = self.upload_blob(video) + + return self.send_post( + text, + profile_identify=profile_identify, + reply_to=reply_to, + embed=models.AppBskyEmbedVideo.Main(video=upload.blob, alt=video_alt), + langs=langs, + facets=facets, + ) + def get_post( self, post_rkey: str, profile_identify: t.Optional[str] = None, cid: t.Optional[str] = None ) -> 'models.AppBskyFeedPost.GetRecordResponse': diff --git a/packages/atproto_codegen/clients/generate_async_client.py b/packages/atproto_codegen/clients/generate_async_client.py index e4533462..d64e6e68 100644 --- a/packages/atproto_codegen/clients/generate_async_client.py +++ b/packages/atproto_codegen/clients/generate_async_client.py @@ -16,6 +16,7 @@ def gen_client(input_filename: str, output_filename: str) -> None: 'send_post', 'send_image', 'send_images', + 'upload_blob', '_set_session', '_get_and_set_session', '_refresh_and_set_session', @@ -39,10 +40,13 @@ def gen_client(input_filename: str, output_filename: str) -> None: code = code.replace('self.app', 'await self.app') for method in methods: + # TODO(MarshalX): abnormally hacky; rework + code = re.sub(rf'(\[self\.{method}.*\])', r'await asyncio.gather(*\1)', code) + code = code.replace(f'self.{method}(', f'await self.{method}(') code = code.replace(f'super().{method}(', f'await super().{method}(') - code = re.sub(r'(\[self\.upload_blob.*\])', r'await asyncio.gather(*\1)', code) + code = code.replace('gather(*[await', 'gather(*[') # rollback specific case code = DISCLAIMER + code