Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Download and store Kling AI generated videos in cloud storage (S3, GCS, Azure). Use when persisting videos or building CDN pipelines. Trigger with phrases like 'klingai storage', 'save klingai video', 'kling ai s3 upload', 'klingai cloud storage'.
.claude/skills/jeremylongshore-klingai-storage-integration/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-05 | ✗→✓ | ▲ Improved | 31% | 0% |
| case-16 | ✗→✓ | ▲ Improved | 106% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 39% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 71% | 0% |
| case-14 | ✗→✓ | ▲ Improved | 85% | 0% |
Kling AI video URLs from task_result.videos[].url are temporary CDN links that expire. You must download and store videos in your own storage. This skill covers S3, GCS, and Azure Blob.
pythonimport requests import os def download_video(video_url: str, output_dir: str = "output") -> str: """Download generated video from Kling CDN.""" os.makedirs(output_dir, exist_ok=True) # Extract filename or generate one filename = video_url.split("/")[-1].split("?")[0] if not filename.endswith(".mp4"): filename = f"kling_{int(time.time())}.mp4" filepath = os.path.join(output_dir, filename) response = requests.get(video_url, stream=True, timeout=120) response.raise_for_status() with open(filepath, "wb") as f: for chunk in response.iter_content(chunk_size=8192): f.write(chunk) size_mb = os.path.getsize(filepath) / (1024 * 1024) print(f"Downloaded: {filepath} ({size_mb:.1f} MB)") return filepath
pythonimport boto3 def upload_to_s3(filepath: str, bucket: str, key_prefix: str = "kling-videos/") -> str: """Upload video to S3 and return public URL.""" s3 = boto3.client("s3") filename = os.path.basename(filepath) s3_key = f"{key_prefix}{filename}" s3.upload_file( filepath, bucket, s3_key, ExtraArgs={"ContentType": "video/mp4", "CacheControl": "max-age=86400"} ) url = f"https://{bucket}.s3.amazonaws.com/{s3_key}" print(f"Uploaded to S3: {url}") return url # Generate signed URL for private buckets def get_signed_url(bucket: str, key: str, expiry: int = 3600) -> str: s3 = boto3.client("s3") return s3.generate_presigned_url( "get_object", Params={"Bucket": bucket, "Key": key}, ExpiresIn=expiry, )
pythonfrom google.cloud import storage def upload_to_gcs(filepath: str, bucket_name: str, prefix: str = "kling-videos/") -> str: """Upload video to GCS and return public URL.""" client = storage.Client() bucket = client.bucket(bucket_name) filename = os.path.basename(filepath) blob = bucket.blob(f"{prefix}{filename}") blob.upload_from_filename(filepath, content_type="video/mp4") blob.make_public() # or use signed URLs for private access print(f"Uploaded to GCS: {blob.public_url}") return blob.public_url # Signed URL for private access def get_gcs_signed_url(bucket_name: str, blob_name: str, expiry_min: int = 60) -> str: from datetime import timedelta client = storage.Client() bucket = client.bucket(bucket_name) blob = bucket.blob(blob_name) return blob.generate_signed_url(expiration=timedelta(minutes=expiry_min))
pythonfrom azure.storage.blob import BlobServiceClient def upload_to_azure(filepath: str, container: str, connection_string: str = None) -> str: """Upload video to Azure Blob Storage.""" conn_str = connection_string or os.environ["AZURE_STORAGE_CONNECTION_STRING"] client = BlobServiceClient.from_connection_string(conn_str) filename = os.path.basename(filepath) blob_client = client.get_blob_client(container=container, blob=f"kling-videos/{filename}") with open(filepath, "rb") as f: blob_client.upload_blob(f, content_type="video/mp4", overwrite=True) url = blob_client.url print(f"Uploaded to Azure: {url}") return url
pythondef generate_and_store(prompt: str, bucket: str, provider: str = "s3"): """Generate video with Kling AI and store in cloud.""" # 1. Generate r = requests.post(f"{BASE}/videos/text2video", headers=get_headers(), json={ "model_name": "kling-v2-master", "prompt": prompt, "duration": "5", "mode": "standard", }).json() task_id = r["data"]["task_id"] # 2. Poll result = poll_task("/videos/text2video", task_id) video_url = result["videos"][0]["url"] # 3. Download filepath = download_video(video_url) # 4. Upload if provider == "s3": return upload_to_s3(filepath, bucket) elif provider == "gcs": return upload_to_gcs(filepath, bucket) elif provider == "azure": return upload_to_azure(filepath, bucket) # 5. Cleanup temp file os.remove(filepath)
pythonimport json def save_with_metadata(filepath: str, task_id: str, prompt: str, model: str): """Save video metadata alongside the file.""" meta = { "task_id": task_id, "prompt": prompt, "model": model, "generated_at": time.strftime("%Y-%m-%dT%H:%M:%SZ"), "filename": os.path.basename(filepath), } meta_path = filepath.replace(".mp4", ".meta.json") with open(meta_path, "w") as f: json.dump(meta, f, indent=2) return meta_path
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 15,010 | 17,176 | +14% | 1 | 1 | 0% | 2,840 | 4,077 | +44% | 0 | 0 | — |
case-02 | fail→fail | 24,050 | 15,518 | -35% | 1 | 1 | 0% | 3,975 | 5,086 | +28% | 0 | 0 | — |
case-03 | fail→fail | 30,602 | 15,786 | -48% | 1 | 1 | 0% | 2,776 | 3,900 | +40% | 0 | 0 | — |
case-04 | fail→fail | 16,944 | 14,386 | -15% | 1 | 1 | 0% | 3,347 | 4,029 | +20% | 0 | 0 | — |
case-05 | fail→pass | 17,727 | 14,558 | -18% | 1 | 1 | 0% | 3,529 | 4,629 | +31% | 0 | 0 | — |
case-06 | pass→pass | 10,354 | 11,605 | +12% | 1 | 1 | 0% | 2,066 | 2,928 | +42% | 0 | 0 | — |
case-16 | fail→pass | 6,089 | 3,449 | -43% | 1 | 1 | 0% | 1,038 | 2,140 | +106% | 0 | 0 | — |
case-07 | fail→pass | 17,760 | 13,823 | -22% | 1 | 1 | 0% | 2,509 | 3,488 | +39% | 0 | 0 | — |
case-08 | pass→pass | 14,015 | 10,681 | -24% | 1 | 1 | 0% | 1,703 | 2,761 | +62% | 0 | 0 | — |
case-09 | pass→pass | 18,935 | 13,741 | -27% | 1 | 1 | 0% | 2,134 | 3,429 | +61% | 0 | 0 | — |
case-10 | fail→pass | 16,016 | 10,958 | -32% | 1 | 1 | 0% | 2,155 | 3,679 | +71% | 0 | 0 | — |
case-11 | pass→pass | 15,824 | 14,294 | -10% | 1 | 1 | 0% | 2,178 | 3,381 | +55% | 0 | 0 | — |
case-12 | pass→pass | 20,479 | 15,633 | -24% | 1 | 1 | 0% | 3,127 | 3,938 | +26% | 0 | 0 | — |
case-13 | fail→fail | 36,228 | 14,791 | -59% | 1 | 1 | 0% | 3,959 | 3,687 | -7% | 0 | 0 | — |
case-14 | fail→pass | 7,083 | 3,749 | -47% | 1 | 1 | 0% | 1,233 | 2,283 | +85% | 0 | 0 | — |
case-15 | fail→pass | 20,838 | 7,190 | -65% | 1 | 1 | 0% | 3,306 | 3,157 | -5% | 0 | 0 | — |
case-17 | pass→pass | 17,062 | 22,776 | +33% | 1 | 1 | 0% | 3,585 | 4,667 | +30% | 0 | 0 | — |
case-18 | pass→pass | 12,579 | 13,510 | +7% | 1 | 1 | 0% | 2,454 | 3,801 | +55% | 0 | 0 | — |
case-19 | fail→pass | 19,311 | 2,906 | -85% | 1 | 1 | 0% | 1,875 | 2,028 | +8% | 0 | 0 | — |
case-20 | pass→pass | 9,312 | 14,522 | +56% | 1 | 1 | 0% | 1,881 | 3,124 | +66% | 0 | 0 | — |
case-21 | pass→fail | 20,689 | 21,167 | +2% | 1 | 1 | 0% | 2,913 | 4,741 | +63% | 0 | 0 | — |
case-22 | pass→pass | 20,509 | 16,344 | -20% | 1 | 1 | 0% | 3,052 | 5,037 | +65% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +27 percentage points is the difference between those two pass rates over the 22 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.