How to Build a Jyotish-Powered AI Music Composition Pipeline: Swiss Ephemeris → MIDI → Video
A hands-on technical walkthrough of the exact stack we built to pull live planetary data from the Swiss Ephemeris, run it through a Jyotish interpretation layer, translate it into MIDI composition parameters, and generate cinematic visual prompt packs — all automated inside OpenClaw.
The video above is the output. This post is the engineering underneath it. We are going to walk through the exact pipeline we built — from astronomical data ingestion all the way to a compressed, share-ready video asset — so you can replicate it, fork it, and build your own version.
This is not a conceptual overview. This is the actual config, the actual scripts, and the actual mistakes we made along the way.
The Full Pipeline at a Glance
Each stage is a discrete step. Each one can be swapped, upgraded, or forked independently. That modularity is what makes this a real system rather than a one-off hack.
Part 1 — Pulling Planetary Data From the Swiss Ephemeris
The Swiss Ephemeris is the gold standard for astronomical calculation. It is the same engine used by most serious astrology software. The Python binding pyswisseph gives you programmatic access to sidereal positions for any date and time.
Installation
pip install pyswissephbash
Pulling Today's Sidereal Positions
The key flag is swe.FLG_SIDEREAL combined with the Lahiri ayanamsa, which is the standard reference frame used in Jyotish. Without this flag you get tropical positions — which is fine for Western astrology but wrong for Vedic interpretation.
import swisseph as swe
from datetime import datetime, timezone
# Set Lahiri ayanamsa — required for Jyotish / sidereal positions
swe.set_sid_mode(swe.SIDM_LAHIRI)
def get_sidereal_positions(dt: datetime) -> dict:
"""
Pull sidereal planetary positions for a given datetime.
Returns a dict of planet name → (longitude_degrees, sign, degrees_in_sign)
"""
jd = swe.julday(dt.year, dt.month, dt.day,
dt.hour + dt.minute / 60.0)
planets = {
"Sun": swe.SUN,
"Moon": swe.MOON,
"Mercury": swe.MERCURY,
"Venus": swe.VENUS,
"Mars": swe.MARS,
"Jupiter": swe.JUPITER,
"Saturn": swe.SATURN,
"Rahu": swe.MEAN_NODE, # North node
}
SIGNS = [
"Aries","Taurus","Gemini","Cancer",
"Leo","Virgo","Libra","Scorpio",
"Sagittarius","Capricorn","Aquarius","Pisces"
]
results = {}
for name, planet_id in planets.items():
pos, _ = swe.calc_ut(jd, planet_id, swe.FLG_SIDEREAL)
lon = pos[0]
sign_index = int(lon // 30)
deg_in_sign = round(lon % 30, 1)
results[name] = {
"longitude": round(lon, 4),
"sign": SIGNS[sign_index],
"degrees": deg_in_sign
}
# Ketu is always exactly opposite Rahu
rahu_lon = results["Rahu"]["longitude"]
ketu_lon = (rahu_lon + 180) % 360
sign_index = int(ketu_lon // 30)
results["Ketu"] = {
"longitude": round(ketu_lon, 4),
"sign": SIGNS[sign_index],
"degrees": round(ketu_lon % 30, 1)
}
return results
# Run it
now = datetime.now(timezone.utc)
positions = get_sidereal_positions(now)
for planet, data in positions.items():
print(f"{planet:10} {data['sign']:15} {data['degrees']}°")
python
Running this on April 21, 2026 gives you the output we used for this session:
Sun Aries 5.7°
Moon Taurus 10.9°
Mercury Pisces 13.0°
Venus Taurus 0.7°
Mars Pisces 13.7°
Jupiter Gemini 23.3°
Saturn Pisces 13.7°
Rahu Aquarius 12.2°
Ketu Leo 12.2°
output
Why Lahiri? There are several ayanamsa options in pyswisseph. Lahiri is the Indian government standard and is what the vast majority of Jyotish practitioners use. If you are building for a Western astrology use case, skip the swe.set_sid_mode call entirely and use tropical positions.
Part 2 — The Jyotish Interpretation Skill
Raw planetary positions mean nothing to a music composition engine. You need an interpretation layer that converts sign + planet combinations into musical and emotional parameters. This is where the OpenClaw Jyotish skill lives.
Skill File Structure
In your OpenClaw workspace, create the skill file at:
~/.openclaw/agents/main/skills/jyotish-music.mdpath
The Interpretation Config
The skill works as a lookup and synthesis layer. Here is the core mapping structure — the full version of what we run:
# SKILL: Jyotish Music Interpreter
# Purpose: Convert planetary positions into musical composition parameters
## Planet Archetypes
### Sun
- Quality: Solar, assertive, identity, clarity
- Musical role: Lead melodic voice, opening statement
- Default register: Mid-high, bright timbre
- Preferred instruments: Brass, strings (bowed), piano treble
### Moon
- Quality: Emotional, receptive, memory, flow
- Musical role: Harmonic foundation, bass/pad layer
- Default register: Low-mid, warm sustain
- Preferred instruments: Cello, bass, ambient pad, Rhodes
### Mercury
- Quality: Communicative, agile, intellectual, quick
- Musical role: Counter-melody, ornamentation, call-and-response
- Default register: Mid, light articulation
- Preferred instruments: Flute, high strings, marimba, arpeggiated synth
### Venus
- Quality: Beauty, harmony, sensuality, attraction
- Musical role: Harmonic color, chord voicing richness
- Default register: Mid, lush and full
- Preferred instruments: Strings ensemble, Rhodes, acoustic guitar, choir
### Mars
- Quality: Drive, urgency, conflict, courage
- Musical role: Rhythmic engine, tension, syncopation
- Default register: Mid-low, percussive attack
- Preferred instruments: Drums, bass guitar, staccato brass, distorted elements
### Jupiter
- Quality: Expansion, wisdom, generosity, abundance
- Musical role: Secondary melody, harmonic expansion, resolution
- Default register: Mid-high, round and full
- Preferred instruments: French horn, acoustic piano, vocal harmony
### Saturn
- Quality: Discipline, restriction, time, depth
- Musical role: Long held tones, structural anchor, silence
- Default register: Low, sparse, deliberate
- Preferred instruments: Bass clarinet, low strings, deep synth pad, gongs
### Rahu (North Node)
- Quality: Future, obsession, amplification, strangeness
- Musical role: Unusual texture, microtonal element, electronic layer
- Default register: Variable, unexpected
- Preferred instruments: Electronic, processed voice, prepared piano, glitch
### Ketu (South Node)
- Quality: Dissolution, past karma, release, wisdom
- Musical role: Fade elements, reverb tails, resolution passages
- Default register: Fading, sparse, ancient-sounding
- Preferred instruments: Singing bowls, tanpura, fading strings, silence
---
## Sign Modifiers
Apply these modifiers to the planet's base archetype:
| Sign | Modifier |
|-------------|-----------------------------------------------|
| Aries | Fast tempo, rising motif, major key tendency |
| Taurus | Slow tempo, grounded, rich harmonics, drones |
| Gemini | Dual voices, fast runs, light articulation |
| Cancer | Emotional depth, minor key, flowing phrases |
| Leo | Bold, theatrical, strong downbeat, fanfare |
| Virgo | Precise, clean, moderate tempo, detail |
| Libra | Balanced, resolved cadences, symmetrical |
| Scorpio | Intense, chromatic, descending motifs |
| Sagittarius | Expansive, adventurous, modal scales |
| Capricorn | Austere, slow, structural, disciplined |
| Aquarius | Unconventional, electronic, asymmetric |
| Pisces | Dreamy, dissolving, suspended chords, reverb |
---
## Conjunction Rule
When two planets share the same sign within 2 degrees, blend their archetypes.
The closer the conjunction, the more the energies fuse — treat as a single compound voice.
markdown
Part 3 — Translating Jyotish Output Into MIDI Parameters
Once the skill has interpreted the planetary positions, you need to convert the output into actual MIDI parameters. Here is the Python function we use to do that translation:
Planet → MIDI Mapping Table
| Planet / Sign | MIDI Channel | Velocity Range | Note Range | Duration (ticks) |
|---|---|---|---|---|
| ☉ Sun / Aries | Ch 1 | 80–110 | C4–C6 (mid-high) | 240–480 (quarter–half) |
| ☽ Moon / Taurus | Ch 2 | 40–70 | C2–C4 (low) | 480–1920 (half–whole+) |
| ♀ Venus / Taurus | Ch 3 | 55–80 | C3–C5 (mid) | 480–960 (half–whole) |
| ☿ Mercury / Pisces | Ch 4 | 50–85 | C4–C7 (high) | 60–240 (16th–quarter) |
| ♂ Mars / Pisces | Ch 5 | 85–120 | C2–C4 (low-mid) | 120–240 (8th–quarter) |
| ♃ Jupiter / Gemini | Ch 6 | 65–90 | C3–C5 (mid) | 240–960 (quarter–whole) |
| ♄ Saturn / Pisces | Ch 7 | 30–60 | C1–C3 (very low) | 960–3840 (whole–breve) |
| ☊ Rahu / Aquarius | Ch 8 | 20–100 (random) | Any (unpredictable) | Variable, irregular |
MIDI File Generation Script
from midiutil import MIDIFile
import random
def build_planetary_midi(positions: dict, output_path: str, bpm: int = 72):
"""
Takes Swiss Ephemeris sidereal positions and generates a
multi-track MIDI file with one track per planet.
"""
# Sign → scale modifier mapping
SIGN_SCALES = {
"Aries": [0, 2, 4, 5, 7, 9, 11], # Major
"Taurus": [0, 2, 3, 5, 7, 8, 10], # Natural minor
"Gemini": [0, 2, 4, 6, 7, 9, 11], # Lydian
"Cancer": [0, 2, 3, 5, 7, 8, 11], # Harmonic minor
"Leo": [0, 2, 4, 5, 7, 9, 11], # Major (bright)
"Virgo": [0, 2, 3, 5, 7, 9, 10], # Dorian
"Libra": [0, 2, 4, 5, 7, 9, 10], # Mixolydian
"Scorpio": [0, 1, 4, 5, 7, 8, 11], # Double harmonic
"Sagittarius": [0, 2, 4, 7, 9], # Pentatonic major
"Capricorn": [0, 2, 3, 5, 7, 8, 10], # Phrygian
"Aquarius": [0, 2, 3, 6, 7, 8, 11], # Enigmatic
"Pisces": [0, 2, 3, 5, 7, 8, 10], # Minor (dreamy)
}
# Planet → (channel, base_note, velocity_range, duration_ticks)
PLANET_CONFIG = {
"Sun": (0, 60, (80, 110), (2, 4)),
"Moon": (1, 36, (40, 70), (4, 8)),
"Venus": (2, 48, (55, 80), (4, 6)),
"Mercury": (3, 72, (50, 85), (0.5, 1)),
"Mars": (4, 40, (85, 120), (1, 2)),
"Jupiter": (5, 52, (65, 90), (2, 6)),
"Saturn": (6, 28, (30, 60), (8, 16)),
"Rahu": (7, random.randint(24, 96), (20, 100), (1, 8)),
}
num_tracks = len(PLANET_CONFIG)
midi = MIDIFile(num_tracks)
for planet, (channel, base_note, vel_range, dur_range) in PLANET_CONFIG.items():
if planet not in positions:
continue
sign = positions[planet]["sign"]
degrees = positions[planet]["degrees"]
scale = SIGN_SCALES.get(sign, [0, 2, 4, 5, 7, 9, 11])
# Use degree position to seed note selection within scale
degree_factor = degrees / 30.0 # 0.0 to 1.0
midi.addTrackName(channel, 0, f"{planet} in {sign}")
midi.addTempo(channel, 0, bpm)
time = 0
total_beats = 32 # 8 bars of 4/4
while time < total_beats:
# Select note from scale based on degree position
scale_degree = int(degree_factor * len(scale))
note = base_note + scale[scale_degree % len(scale)]
note = max(0, min(127, note)) # MIDI note bounds
velocity = random.randint(*vel_range)
duration = random.uniform(*dur_range)
midi.addNote(channel, channel, note, time, duration, velocity)
# Advance time — Saturn holds long, Mercury skips fast
time += duration + random.uniform(0, duration * 0.5)
with open(output_path, "wb") as f:
midi.writeFile(f)
print(f"MIDI written → {output_path}")
# Run it
build_planetary_midi(
positions=positions,
output_path="~/openclaw_workspace/media/jyotish_2026_04_21.mid",
bpm=68 # Slower tempo — Moon + Venus in Taurus governs the feel
)
python
Dependency: This uses midiutil. Install with pip install MIDIUtil. For more control over CC messages, pitch bend, and instrument assignment, switch to pretty_midi or music21.
Part 4 — Generating Visual Prompt Packs From Planetary Data
The same planetary reading that drives the music also drives the visual world. We run a second prompt generation pass that converts each planet's sign placement into a scene description — ready to drop into Midjourney, Flux, or any image generator.
The Prompt Generation Skill
Add this section to your Jyotish skill file:
## Visual Scene Generation Rules
For each planet, generate ONE scene description.
The scene should:
- Reflect the planet's archetype AND the sign's elemental quality
- Be photorealistic and cinematic, not illustrated or cartoon
- Be 3–5 sentences max — specific, imagistic, atmospheric
- Include: lighting quality, color palette, mood, time of day, primary subject
### Element → Visual Environment Mapping
- Fire signs (Aries, Leo, Sagittarius): golden hour, open landscapes, sunlight
- Earth signs (Taurus, Virgo, Capricorn): natural textures, low light, grounded
- Air signs (Gemini, Libra, Aquarius): open sky, movement, atmospheric perspective
- Water signs (Cancer, Scorpio, Pisces): water surfaces, mist, dissolving edges
### Planet → Subject Archetype
- Sun: a single figure in open light, facing forward
- Moon: a woman near water, facing away, moonlit
- Venus: soft beauty — flowers, fabric, warmth
- Mercury: movement, birds, wind, quick gesture
- Mars: tension, fire, a held breath before action
- Jupiter: expansive landscape, abundance, open sky
- Saturn: ancient stone, long shadows, deep time
- Rahu: something strange at the edge of the frame
- Ketu: an absence, a doorway, something leaving
markdown
Running the Prompt Pack Generation
Inside OpenClaw, trigger the skill with this prompt:
/skill jyotish-music
Using today's planetary positions:
Sun in Aries 5.7°
Moon in Taurus 10.9°
Venus in Taurus 0.7°
Mercury in Pisces 13.0°
Mars in Pisces 13.7°
Saturn in Pisces 13.7°
Jupiter in Gemini 23.3°
Rahu in Aquarius 12.2°
Ketu in Leo 12.2°
Generate:
1. Music parameters for each planet (key, tempo, texture, instrument)
2. A visual scene description for each planet
3. A unified song title and emotional theme for today's session
4. Recommended BPM and root key for the full composition
Output as structured JSON.
openclaw
Part 5 — Video Assembly and Compression
Once you have your generated clips from your image-to-video tool of choice (Runway, Kling, Sora, Luma), the assembly and compression step is handled by ffmpeg. Do not skip the compression step — raw video exports will choke any upload pipeline.
Assemble Clips Into Final Video
# Create a clips manifest file
cat > clips.txt << EOF
file '/home/user/openclaw_workspace/media/clip_moon_taurus.mp4'
file '/home/user/openclaw_workspace/media/clip_sun_aries.mp4'
file '/home/user/openclaw_workspace/media/clip_mars_saturn_pisces.mp4'
file '/home/user/openclaw_workspace/media/clip_jupiter_gemini.mp4'
file '/home/user/openclaw_workspace/media/clip_rahu_aquarius.mp4'
EOF
# Concatenate clips
ffmpeg -f concat -safe 0 -i clips.txt -c copy raw_assembly.mp4
bash
Compress for Social Delivery
# Compress to H.264 — safe for Telegram, YouTube, Instagram
ffmpeg -i raw_assembly.mp4 \
-vcodec libx264 \
-acodec aac \
-crf 23 \
-preset slow \
-movflags +faststart \
-vf "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2" \
share_ready.mp4
bash
Critical lesson learned: Never pass a raw .mov file directly to your OpenClaw agent for upload. Always compress to H.264 first. A raw .mov can be 2–8GB. Telegram's bot API hard caps at 50MB. The compression step above typically gets a 3-minute 1080p video under 80MB with no visible quality loss.
Upload to Telegram via Script
import requests
import os
BOT_TOKEN = os.environ["TELEGRAM_BOT_TOKEN"]
CHAT_ID = os.environ["TELEGRAM_CHAT_ID"]
VIDEO_PATH = os.path.expanduser(
"~/openclaw_workspace/media/share_ready.mp4"
)
def upload_video(path: str, caption: str = ""):
url = f"https://api.telegram.org/bot{BOT_TOKEN}/sendVideo"
with open(path, "rb") as f:
resp = requests.post(url, data={
"chat_id": CHAT_ID,
"caption": caption,
"supports_streaming": True
}, files={"video": f}, timeout=120)
return resp.json()
result = upload_video(
VIDEO_PATH,
caption="🎵 Jyotish Music Session — April 21, 2026\nMoon + Venus in Taurus · Mars + Saturn in Pisces"
)
print(result)
python
Part 6 — Automating the Whole Pipeline as an OpenClaw Cron Job
Running this manually is fine for experimentation. The real power comes when you set this up as a scheduled cron job that runs every morning, pulls fresh planetary data, and drops a new music session output into your Content Office automatically.
Cron Job Config in OpenClaw
crons:
- id: jyotish-music-daily
schedule: "0 6 * * *" # 6:00 AM daily
agent: main
task: |
Run the daily Jyotish music session.
Steps:
1. Call the Swiss Ephemeris Python script to get today's sidereal positions
2. Pass the positions through the jyotish-music skill
3. Generate MIDI parameters and write the MIDI file to workspace/media/
4. Generate the visual prompt pack for today's planetary configuration
5. Save all outputs to workspace/media/jyotish_YYYY_MM_DD/
6. Write a session summary to workspace/reports/jyotish_daily.md
7. Post the summary to Telegram group
tools:
- exec
- fs.write
- telegram.send
yaml
Add this to your config at: ~/.openclaw/agents/main/crons.yaml — then run openclaw gateway restart to activate it. Verify it registered with openclaw status — you should see the cron listed under Tasks.
"The pipeline runs whether you are at your desk or not. That is the point."
Mistakes We Made — So You Don't Have To
- Forgetting swe.set_sid_mode(). Without this you get tropical positions. The music will still work but the Jyotish interpretation will be wrong. Always set Lahiri before calling calc_ut.
- Passing .mov files directly to the agent. The cron job froze three sessions trying to handle a raw video file. Always compress to H.264 before any agent touches the file.
- Not setting --movflags +faststart. Without this flag, the video has to fully download before playback starts. On Telegram this causes a long delay. The flag moves the metadata to the front of the file so it streams immediately.
- Using the wrong Telegram ID. Your personal user ID and your group chat ID are different. The group ID starts with a negative number (-521...). Use
getUpdateson your bot to confirm the right ID before wiring it into the script. - Running duplicate cron sessions. If the gateway restarts mid-cron, you can end up with zombie sessions. Run
openclaw sessions cleanupfollowed by a gateway restart to clear them.
Join the Tech Temple Alpha Group
Daily alpha reports, live pipeline builds, and the real technical conversations behind everything we publish. Free to join — not free to ignore.
Join Tech Temple on Telegram →Keywords: Swiss Ephemeris Python, pyswisseph tutorial, Jyotish MIDI composition, sidereal astrology code, planetary music generation, MIDI from astrology, AI music pipeline, OpenClaw cron job, ffmpeg video compression, Telegram bot video upload, Jyotish skill OpenClaw, planetary data music, AI creative automation, music composition AI, Chief Wizard, Determination Development, AI engineering, agentic creative workflow, midiutil Python, Swiss Ephemeris Lahiri
What Do You Think?
Drop a comment below — questions, pushback, or your own take. This is where the real conversation happens.

