#!/usr/bin/env python3 import argparse import os import shutil import requests import json import re import sys from pathlib import Path try: import instaloader except Exception as e: print(f"Error: instaloader not installed. Run: pip install -r requirements.txt\n{e}", file=sys.stderr) sys.exit(1) def extract_shortcode_from_url(url: str): m = re.search(r"instagram\.com\/(?:p|reel)\/([^\/?#]+)", url, re.I) return m.group(1) if m else None def main(): parser = argparse.ArgumentParser(description="Download Instagram post media using Instaloader") parser.add_argument('--url', required=True, help='Instagram post/reel URL') parser.add_argument('--out', default='downloads', help='Output directory (default: downloads)') parser.add_argument('--username', default=os.getenv('IG_USERNAME'), help='Instagram username (env IG_USERNAME)') parser.add_argument('--password', default=os.getenv('IG_PASSWORD'), help='Instagram password (env IG_PASSWORD)') parser.add_argument('--sessionfile', default=os.getenv('IG_SESSIONFILE'), help='Instaloader session file path (env IG_SESSIONFILE)') args = parser.parse_args() shortcode = extract_shortcode_from_url(args.url) if not shortcode: print('Error: Could not parse shortcode from URL', file=sys.stderr) sys.exit(2) out_dir = Path(args.out).resolve() out_dir.mkdir(parents=True, exist_ok=True) L = instaloader.Instaloader( download_comments=False, post_metadata_txt_pattern='', download_video_thumbnails=False, save_metadata=False, compress_json=False, quiet=True, ) # Silence logging further to keep stdout clean for the final path only try: L.context.log = lambda *_a, **_k: None # type: ignore[attr-defined] L.context.log_progress = lambda *_a, **_k: None # type: ignore[attr-defined] L.context.error = lambda *_a, **_k: None # type: ignore[attr-defined] L.context.warning = lambda *_a, **_k: None # type: ignore[attr-defined] except Exception: pass # Login if possible if args.sessionfile and args.username: try: L.load_session_from_file(args.username, args.sessionfile) except Exception as e: print(f"Warning: failed to load session from {args.sessionfile}: {e}", file=sys.stderr) elif args.username and args.password: try: L.login(args.username, args.password) except Exception as e: print(f"Warning: login failed: {e}", file=sys.stderr) # Resolve and download try: post = instaloader.Post.from_shortcode(L.context, shortcode) except Exception as e: print(f"Error: resolve failed: {e}", file=sys.stderr) sys.exit(3) # Prefer downloading just the first video directly into out_dir as .mp4 video_url = None try: if getattr(post, 'is_video', False) and getattr(post, 'video_url', None): video_url = post.video_url else: # Sidecar posts may contain one or more videos for node in post.get_sidecar_nodes(): if getattr(node, 'is_video', False) and getattr(node, 'video_url', None): video_url = node.video_url break except Exception: video_url = None dest_path = (out_dir / f"{shortcode}.mp4").resolve() description = '' try: description = getattr(post, 'caption', '') or '' except Exception: description = '' if video_url: try: with requests.get(video_url, stream=True, headers={ 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123 Safari/537.36', 'accept': '*/*', 'referer': 'https://www.instagram.com/', }, timeout=60) as r: r.raise_for_status() with open(dest_path, 'wb') as f: for chunk in r.iter_content(chunk_size=1024 * 256): if chunk: f.write(chunk) print(json.dumps({"video_path": dest_path.as_posix(), "description": description}, ensure_ascii=False)) return except Exception: # Fall back to full download if direct fetch fails pass # Fallback: Use Instaloader's downloader into a temp subdir, then move the first mp4 up try: target = out_dir / shortcode target.mkdir(parents=True, exist_ok=True) L.download_post(post, target.as_posix()) mp4s = sorted(target.glob('*.mp4')) if not mp4s: print('No video found (post may be image-only)', file=sys.stderr) sys.exit(4) src_path = mp4s[0] if dest_path.exists(): dest_path.unlink() shutil.move(src_path.as_posix(), dest_path.as_posix()) print(json.dumps({"video_path": dest_path.as_posix(), "description": description}, ensure_ascii=False)) except Exception as e: print(f"Error: download failed: {e}", file=sys.stderr) sys.exit(3) if __name__ == '__main__': main()