Added new yml
This commit is contained in:
@@ -8,6 +8,7 @@ import os
|
|||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from atproto import Client, client_utils, models
|
from atproto import Client, client_utils, models
|
||||||
from playwright.sync_api import sync_playwright
|
from playwright.sync_api import sync_playwright
|
||||||
|
from moviepy.editor import VideoFileClip
|
||||||
|
|
||||||
# --- Logging Setup ---
|
# --- Logging Setup ---
|
||||||
LOG_PATH = "twitter2bsky.log"
|
LOG_PATH = "twitter2bsky.log"
|
||||||
@@ -199,7 +200,31 @@ def clean_url(url):
|
|||||||
return cleaned_url
|
return cleaned_url
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# --- 4. Formatting & Bluesky Logic ---
|
# --- 4. Video Processing ---
|
||||||
|
def download_and_crop_video(video_url, output_path):
|
||||||
|
"""Downloads the video and crops it to 59 seconds."""
|
||||||
|
try:
|
||||||
|
# Download the video
|
||||||
|
response = httpx.get(video_url, timeout=10)
|
||||||
|
if response.status_code == 200:
|
||||||
|
with open(output_path, 'wb') as f:
|
||||||
|
f.write(response.content)
|
||||||
|
logging.info(f"✅ Video downloaded: {output_path}")
|
||||||
|
|
||||||
|
# Crop the video to 59 seconds
|
||||||
|
video_clip = VideoFileClip(output_path)
|
||||||
|
cropped_clip = video_clip.subclip(0, min(59, video_clip.duration))
|
||||||
|
cropped_clip.write_videofile(output_path, codec='libx264')
|
||||||
|
logging.info(f"✅ Video cropped to 59 seconds: {output_path}")
|
||||||
|
return output_path
|
||||||
|
else:
|
||||||
|
logging.error(f"❌ Failed to download video: {video_url}")
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"❌ Error processing video: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# --- 5. Formatting & Bluesky Logic ---
|
||||||
def get_last_bsky(client, handle):
|
def get_last_bsky(client, handle):
|
||||||
timeline = client.get_author_feed(handle)
|
timeline = client.get_author_feed(handle)
|
||||||
for titem in timeline.feed:
|
for titem in timeline.feed:
|
||||||
@@ -288,16 +313,17 @@ def make_rich(content):
|
|||||||
|
|
||||||
return text_builder
|
return text_builder
|
||||||
|
|
||||||
def get_blob_from_url(image_url, client):
|
def get_blob_from_url(media_url, client):
|
||||||
|
"""Fetches and uploads the media (image or video) and returns the blob."""
|
||||||
try:
|
try:
|
||||||
r = httpx.get(image_url, timeout=10)
|
r = httpx.get(media_url, timeout=10)
|
||||||
if r.status_code == 200:
|
if r.status_code == 200:
|
||||||
return client.upload_blob(r.content).blob
|
return client.upload_blob(r.content).blob
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.warning(f"Could not fetch image {image_url}: {e}")
|
logging.warning(f"Could not fetch media {media_url}: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# --- 5. Main Sync Function ---
|
# --- 6. Main Sync Function ---
|
||||||
def sync_feeds(args):
|
def sync_feeds(args):
|
||||||
logging.info("🔄 Starting sync cycle...")
|
logging.info("🔄 Starting sync cycle...")
|
||||||
try:
|
try:
|
||||||
@@ -324,7 +350,8 @@ def sync_feeds(args):
|
|||||||
for tweet in reversed(tweets):
|
for tweet in reversed(tweets):
|
||||||
tweet_time = arrow.get(tweet.created_on)
|
tweet_time = arrow.get(tweet.created_on)
|
||||||
|
|
||||||
if tweet_time > last_bsky_time: # Only post new tweets
|
#if tweet_time > last_bsky_time: # Only post new tweets
|
||||||
|
if True: # For testing, post all tweets regardless of time
|
||||||
logging.info(f"📝 Found new tweet from {tweet_time}. Posting to Bluesky...")
|
logging.info(f"📝 Found new tweet from {tweet_time}. Posting to Bluesky...")
|
||||||
|
|
||||||
raw_text = tweet.text.strip()
|
raw_text = tweet.text.strip()
|
||||||
@@ -365,10 +392,14 @@ def sync_feeds(args):
|
|||||||
# Inject our dynamic alt text here!
|
# Inject our dynamic alt text here!
|
||||||
images.append(models.AppBskyEmbedImages.Image(alt=dynamic_alt, image=blob))
|
images.append(models.AppBskyEmbedImages.Image(alt=dynamic_alt, image=blob))
|
||||||
elif media.type == "video":
|
elif media.type == "video":
|
||||||
# Handle video uploads if necessary (this part may vary based on your API capabilities)
|
# Download and crop the video
|
||||||
blob = get_blob_from_url(media.media_url_https, bsky_client)
|
video_path = "temp_video.mp4"
|
||||||
|
cropped_video_path = download_and_crop_video(media.media_url_https, video_path)
|
||||||
|
if cropped_video_path:
|
||||||
|
blob = get_blob_from_url(cropped_video_path, bsky_client)
|
||||||
if blob:
|
if blob:
|
||||||
images.append(models.AppBskyEmbedImages.Image(alt=dynamic_alt, image=blob))
|
images.append(models.AppBskyEmbedImages.Image(alt=dynamic_alt, image=blob))
|
||||||
|
os.remove(video_path) # Clean up the temporary video file
|
||||||
|
|
||||||
# 🌐 Posting with Catalan language tag
|
# 🌐 Posting with Catalan language tag
|
||||||
try:
|
try:
|
||||||
@@ -389,7 +420,31 @@ def sync_feeds(args):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"❌ Error during sync cycle: {e}")
|
logging.error(f"❌ Error during sync cycle: {e}")
|
||||||
|
|
||||||
# --- 6. Main Execution ---
|
# --- 7. Download and Crop Video Function ---
|
||||||
|
def download_and_crop_video(video_url, output_path):
|
||||||
|
"""Downloads the video and crops it to 59 seconds."""
|
||||||
|
try:
|
||||||
|
# Download the video
|
||||||
|
response = httpx.get(video_url, timeout=10)
|
||||||
|
if response.status_code == 200:
|
||||||
|
with open(output_path, 'wb') as f:
|
||||||
|
f.write(response.content)
|
||||||
|
logging.info(f"✅ Video downloaded: {output_path}")
|
||||||
|
|
||||||
|
# Crop the video to 59 seconds
|
||||||
|
video_clip = VideoFileClip(output_path)
|
||||||
|
cropped_clip = video_clip.subclip(0, min(59, video_clip.duration))
|
||||||
|
cropped_clip.write_videofile(output_path, codec='libx264')
|
||||||
|
logging.info(f"✅ Video cropped to 59 seconds: {output_path}")
|
||||||
|
return output_path
|
||||||
|
else:
|
||||||
|
logging.error(f"❌ Failed to download video: {video_url}")
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"❌ Error processing video: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# --- 8. Main Execution ---
|
||||||
def main():
|
def main():
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user