import asyncio import math import os import tempfile import modal from modal.cls import ClsMixin import tweepy from suno_utils.worker.loader import S3Loader from suno_utils.worker.modal_base import get_modal_base_image, MODAL_MOUNTS from suno_utils.worker.chirp_worker import ChirpV0Worker from suno_utils.worker.schema import QueueItem aws_secret = modal.Secret.from_name("studio-aws") twitter_secret = modal.Secret.from_name("studio-twitter") def download_model_wrapper_4(): ChirpV0Worker.download_models() image = get_modal_base_image() STUB_NAME = "twitter-botv2" stub = modal.Stub(STUB_NAME, image=image) class DummyLoader(S3Loader): pass @stub.cls( cpu=1.0, secrets=[ aws_secret, twitter_secret, ], mounts=MODAL_MOUNTS, ) class TwitterStub(ClsMixin): def __enter__(self): self.worker = DummyLoader(bg_image="/suno/models/assets/wave-bg-2.png") @modal.method() async def upload_to_twitter( self, tmp_file, tweet_text="https://discord.com/invite/QRrnYufqZV", ): # Set up api (twitter api v1) and client (twitter api v2) consumer_key = os.environ["TWITTER_CONSUMER_KEY"] consumer_secret = os.environ["TWITTER_CONSUMER_SECRET"] access_token = os.environ["TWITTER_ACCESS_TOKEN"] access_token_secret = os.environ["TWITTER_ACCESS_TOKEN_SECRET"] auth = tweepy.OAuth1UserHandler(consumer_key, consumer_secret) auth.set_access_token( access_token, access_token_secret, ) api = tweepy.API(auth) client = tweepy.Client( consumer_key=consumer_key, consumer_secret=consumer_secret, access_token=access_token, access_token_secret=access_token_secret, ) # Get the file info file_info = os.stat(tmp_file.name) # Calculate real file size tmp_file.seek(0, 0) tmp_file.read(file_info.st_size + 50000) size = tmp_file.tell() print("file size = ", size) # Initiate the chunked upload media = api.chunked_upload_init( total_bytes=size, media_type="video/mp4", media_category="tweet_video", ) print("initialized chunk upload: ", str(media)) # Upload the chunks one by one chunksize = max(int(math.ceil(file_info.st_size / 1000)), 50000) print("chunksize = ", chunksize) tmp_file.seek(0, 0) num_chunks = int(math.ceil(file_info.st_size / chunksize)) print("num_chunks =", num_chunks) for i in range(0, num_chunks): data_chunk = tmp_file.read(chunksize) upload_response = api.chunked_upload_append( media_id=media.media_id, media=data_chunk, segment_index=i ) # Finalize the upload upload_response = api.chunked_upload_finalize(media.media_id) print("Finalized upload: ", str(upload_response)) print(api.get_media_upload_status(media.media_id).processing_info["state"]) # Poll the upload status until it's done (wait for up to 10 seconds) for i in range(20): await asyncio.sleep(1) state = api.get_media_upload_status(media.media_id).processing_info["state"] print(i, state) if state == "succeeded": break # The upload should now be complete and you can post a status with this media await asyncio.sleep(0.5) response = client.create_tweet(text=tweet_text, media_ids=[media.media_id]) url = f"https://twitter.com/intent/tweet?text=Wanna%20hear%20me%20%23chirp?%20https://twitter.com/i/status/{response.data['id']}" # url = f"https://twitter.com/i/status/{response.data['id']}" return url @modal.method() async def upload_from_clip_id( self, clip_id, gen_message_id, batch_n, tweet_text="https://discord.com/invite/QRrnYufqZV", callback_url: str | None = None, ): from suno_utils.worker.settings import s3_client with tempfile.NamedTemporaryFile(mode="r+b", suffix=".mp4") as tmp_file: s3_client.download_fileobj( "suno-data-uploads", f"studio/uploads/{clip_id}.mp4", tmp_file, ) print("Start upload for clip id: ", clip_id) url = await self.upload_to_twitter(tmp_file, tweet_text=tweet_text) print(url) self.worker.notify_finish( QueueItem(id=clip_id, metadata={}, callback_url=callback_url), { "id": clip_id, "model": "chirp_v0", "type": "make_tweet", "tweet": url, "gen_message_id": gen_message_id, "batch_n": batch_n, }, queue_name="results:q", ) @stub.local_entrypoint() async def main(): model = TwitterStub() gen_request_id = "fc22f413-34de-4867-845c-049dbf53f266" clip_id = "3d478f8a-dbff-4ebd-8fd2-237ea5b6de33_0" # print("clip_id", clip_id) model.upload_from_clip_id.call( clip_id, gen_request_id, 1, "https://discord.com/invite/QRrnYufqZV", "https://troll-selected-presumably.ngrok-free.app/computed/", )