social media crossposting tool. 3rd time's the charm
mastodon
misskey
crossposting
bluesky
1import requests
2import subprocess
3import json
4import re, urllib.parse, os
5from util import LOGGER
6
7FILENAME = re.compile(r'filename="?([^\";]*)"?')
8
9def get_filename_from_url(url):
10 try:
11 response = requests.head(url, allow_redirects=True)
12 disposition = response.headers.get('Content-Disposition')
13 if disposition:
14 filename = FILENAME.findall(disposition)
15 if filename:
16 return filename[0]
17 except requests.RequestException:
18 pass
19
20 parsed_url = urllib.parse.urlparse(url)
21 return os.path.basename(parsed_url.path)
22
23def probe_bytes(bytes: bytes) -> dict:
24 cmd = [
25 'ffprobe',
26 '-v', 'error',
27 '-show_format',
28 '-show_streams',
29 '-print_format', 'json',
30 'pipe:0'
31 ]
32 proc = subprocess.run(cmd, input=bytes, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
33
34 if proc.returncode != 0:
35 raise RuntimeError(f"ffprobe failed: {proc.stderr.decode()}")
36
37 return json.loads(proc.stdout)
38
39def compress_image(image_bytes: bytes, quality: int = 90):
40 cmd = [
41 'ffmpeg',
42 '-f', 'image2pipe',
43 '-i', 'pipe:0',
44 '-c:v', 'webp',
45 '-q:v', str(quality),
46 '-f', 'image2pipe',
47 'pipe:1'
48 ]
49
50 proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
51 out_bytes, err = proc.communicate(input=image_bytes)
52
53 if proc.returncode != 0:
54 raise RuntimeError(f"ffmpeg compress failed: {err.decode()}")
55
56 return out_bytes
57
58def download_blob(url: str, max_bytes: int = 5_000_000) -> bytes | None:
59 response = requests.get(url, stream=True, timeout=20)
60 if response.status_code != 200:
61 LOGGER.info("Failed to download %s! %s", url, response)
62 return None
63
64 downloaded_bytes = b""
65 current_size = 0
66
67 for chunk in response.iter_content(chunk_size=8192):
68 if not chunk:
69 continue
70
71 current_size += len(chunk)
72 if current_size > max_bytes:
73 response.close()
74 return None
75
76 downloaded_bytes += chunk
77
78 return downloaded_bytes
79
80
81def get_media_meta(bytes: bytes):
82 probe = probe_bytes(bytes)
83 streams = [s for s in probe['streams'] if s['codec_type'] == 'video']
84 if not streams:
85 raise ValueError("No video stream found")
86
87 media = streams[0]
88 return {
89 'width': int(media['width']),
90 'height': int(media['height']),
91 'duration': float(media.get('duration', probe['format'].get('duration', -1)))
92 }