diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..243cf02
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,41 @@
+# Phase 4 AI provider configuration
+POLLINATIONS_OPENAI_URL=https://text.pollinations.ai/openai
+POLLINATIONS_API_KEY=
+# Alias supported for compatibility with existing secrets naming
+POLLINATIONS_API=
+POLLINATIONS_GITHUB_MODEL=github:gpt-4o-mini
+POLLINATIONS_FALLBACK_MODEL=openai-fast
+
+# Set true to force strict behavior: if Pollinations cannot route a GitHub model,
+# Phase 4 fails unless GLM fallback is configured.
+PHASE4_STRICT_GLM_FALLBACK=false
+
+# GLM-5 fallback (preferred when Pollinations GitHub model is unavailable)
+# Example endpoint style: https://your-glm-endpoint/v1/chat/completions
+GLM5_API_BASE_URL=
+GLM5_API_KEY=
+GLM5_MODEL=glm-5
+
+# Phase 5 render configuration
+PHASE5_LYRICS_JSON=data/phase3-lyrics.json
+PHASE5_DIRECTION_JSON=data/phase4-direction.json
+PHASE5_OUTPUT_VIDEO=output/phase5-video.mp4
+# Optional: cap render duration for CI speed
+PHASE5_MAX_SECONDS=0
+# Optional: tune renderer speed/quality balance
+PHASE5_RENDER_CONCURRENCY=2
+PHASE5_RENDER_CRF=22
+
+# Phase 7 YouTube upload configuration
+YOUTUBE_CLIENT_ID=
+YOUTUBE_CLIENT_SECRET=
+YOUTUBE_REFRESH_TOKEN=
+YOUTUBE_TITLE=
+YOUTUBE_DESCRIPTION=
+YOUTUBE_TAGS=
+YOUTUBE_PRIVACY_STATUS=unlisted
+YOUTUBE_CATEGORY_ID=10
+PHASE7_VIDEO_FILE=output/phase6-video.mp4
+PHASE7_PHASE3_JSON=data/phase3-lyrics.json
+PHASE7_OUTPUT_JSON=data/phase7-youtube-upload.json
+PHASE7_DRY_RUN=false
diff --git a/.github/workflows/phase6-full-pipeline.yml b/.github/workflows/phase6-full-pipeline.yml
new file mode 100644
index 0000000..1302440
--- /dev/null
+++ b/.github/workflows/phase6-full-pipeline.yml
@@ -0,0 +1,103 @@
+name: Phase 6 Full Pipeline Test
+
+on:
+ workflow_dispatch:
+ inputs:
+ song_query:
+ description: Song query used when song_id is empty
+ required: false
+ default: Aaj Ki Raat
+ type: string
+ song_id:
+ description: Optional direct JioSaavn song id
+ required: false
+ default: ''
+ type: string
+ max_seconds:
+ description: Clip duration cap for CI runtime stability
+ required: false
+ default: '30'
+ type: string
+ strict_glm_fallback:
+ description: Fail Phase 4 if Pollinations GitHub model is unavailable and GLM is not configured
+ required: false
+ default: 'false'
+ type: choice
+ options:
+ - 'false'
+ - 'true'
+
+jobs:
+ full-pipeline-test:
+ runs-on: ubuntu-latest
+ timeout-minutes: 45
+ permissions:
+ contents: read
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: npm
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Prepare folders
+ run: mkdir -p data output
+
+ - name: Phase 3 - Fetch song metadata and lyrics timeline
+ env:
+ SONG_QUERY: ${{ inputs.song_query }}
+ SONG_ID: ${{ inputs.song_id }}
+ run: |
+ set -e
+ echo "[phase-6] Starting Phase 3"
+ if [ -n "$SONG_ID" ]; then
+ npm run phase3:build -- --song-id="$SONG_ID" --out=data/phase3-lyrics.json
+ else
+ npm run phase3:build -- --query="$SONG_QUERY" --out=data/phase3-lyrics.json
+ fi
+
+ - name: Phase 4 - Generate animation direction JSON
+ env:
+ POLLINATIONS_API: ${{ secrets.POLLINATIONS_API }}
+ POLLINATIONS_API_KEY: ${{ secrets.POLLINATIONS_API_KEY }}
+ GLM5_API_BASE_URL: ${{ secrets.GLM5_API_BASE_URL }}
+ GLM5_API_KEY: ${{ secrets.GLM5_API_KEY }}
+ GLM5_MODEL: ${{ secrets.GLM5_MODEL }}
+ PHASE4_STRICT_GLM_FALLBACK: ${{ inputs.strict_glm_fallback }}
+ run: |
+ set -e
+ echo "[phase-6] Starting Phase 4"
+ npm run phase4:direction -- --in=data/phase3-lyrics.json --out=data/phase4-direction.json
+
+ - name: Phase 5 - Render dynamic lyrics video
+ env:
+ PHASE5_MAX_SECONDS: ${{ inputs.max_seconds }}
+ PHASE5_RENDER_CONCURRENCY: 2
+ PHASE5_RENDER_CRF: 22
+ run: |
+ set -e
+ echo "[phase-6] Starting Phase 5"
+ npm run phase5:render -- --lyrics=data/phase3-lyrics.json --direction=data/phase4-direction.json --out=output/phase6-video.mp4 --max-seconds="$PHASE5_MAX_SECONDS"
+
+ - name: Print output summary
+ run: |
+ set -e
+ ls -lh output/phase6-video.mp4 data/phase3-lyrics.json data/phase4-direction.json
+ node -e "const {getVideoMetadata}=require('@remotion/renderer'); getVideoMetadata('output/phase6-video.mp4').then((m)=>{console.log(JSON.stringify({width:m.width,height:m.height,fps:m.fps,durationInSeconds:m.durationInSeconds},null,2));}).catch((e)=>{console.error(e);process.exit(1);});"
+
+ - name: Upload Phase 6 artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: phase6-full-pipeline
+ if-no-files-found: error
+ path: |
+ output/phase6-video.mp4
+ data/phase3-lyrics.json
+ data/phase4-direction.json
diff --git a/.github/workflows/phase7-youtube-upload.yml b/.github/workflows/phase7-youtube-upload.yml
new file mode 100644
index 0000000..75e7415
--- /dev/null
+++ b/.github/workflows/phase7-youtube-upload.yml
@@ -0,0 +1,139 @@
+name: Phase 7 YouTube Upload
+
+on:
+ workflow_dispatch:
+ inputs:
+ song_query:
+ description: Song query used when song_id is empty
+ required: false
+ default: Aaj Ki Raat
+ type: string
+ song_id:
+ description: Optional direct JioSaavn song id
+ required: false
+ default: ''
+ type: string
+ max_seconds:
+ description: Clip duration cap (0 means full song)
+ required: false
+ default: '30'
+ type: string
+ youtube_title:
+ description: Optional YouTube title override
+ required: false
+ default: ''
+ type: string
+ youtube_description:
+ description: Optional YouTube description override
+ required: false
+ default: ''
+ type: string
+ youtube_tags:
+ description: Optional comma-separated tags
+ required: false
+ default: ''
+ type: string
+ privacy_status:
+ description: YouTube privacy status
+ required: false
+ default: unlisted
+ type: choice
+ options:
+ - private
+ - unlisted
+ - public
+ strict_glm_fallback:
+ description: Fail if Pollinations GitHub model is unavailable and GLM fallback is not configured
+ required: false
+ default: 'false'
+ type: choice
+ options:
+ - 'false'
+ - 'true'
+
+jobs:
+ render-and-upload:
+ runs-on: ubuntu-latest
+ timeout-minutes: 60
+ permissions:
+ contents: read
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: npm
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Prepare folders
+ run: mkdir -p data output
+
+ - name: Phase 3 - Fetch song and lyrics
+ env:
+ SONG_QUERY: ${{ inputs.song_query }}
+ SONG_ID: ${{ inputs.song_id }}
+ run: |
+ set -e
+ echo "[phase-7] Starting Phase 3"
+ if [ -n "$SONG_ID" ]; then
+ npm run phase3:build -- --song-id="$SONG_ID" --out=data/phase3-lyrics.json
+ else
+ npm run phase3:build -- --query="$SONG_QUERY" --out=data/phase3-lyrics.json
+ fi
+
+ - name: Phase 4 - Generate AI direction
+ env:
+ POLLINATIONS_API: ${{ secrets.POLLINATIONS_API }}
+ POLLINATIONS_API_KEY: ${{ secrets.POLLINATIONS_API_KEY }}
+ GLM5_API_BASE_URL: ${{ secrets.GLM5_API_BASE_URL }}
+ GLM5_API_KEY: ${{ secrets.GLM5_API_KEY }}
+ GLM5_MODEL: ${{ secrets.GLM5_MODEL }}
+ PHASE4_STRICT_GLM_FALLBACK: ${{ inputs.strict_glm_fallback }}
+ run: |
+ set -e
+ echo "[phase-7] Starting Phase 4"
+ npm run phase4:direction -- --in=data/phase3-lyrics.json --out=data/phase4-direction.json
+
+ - name: Phase 5 - Render video
+ env:
+ PHASE5_MAX_SECONDS: ${{ inputs.max_seconds }}
+ PHASE5_RENDER_CONCURRENCY: 2
+ PHASE5_RENDER_CRF: 22
+ run: |
+ set -e
+ echo "[phase-7] Starting Phase 5"
+ npm run phase5:render -- --lyrics=data/phase3-lyrics.json --direction=data/phase4-direction.json --out=output/phase7-video.mp4 --max-seconds="$PHASE5_MAX_SECONDS"
+
+ - name: Phase 7 - Upload to YouTube
+ env:
+ YOUTUBE_CLIENT_ID: ${{ secrets.YOUTUBE_CLIENT_ID }}
+ YOUTUBE_CLIENT_SECRET: ${{ secrets.YOUTUBE_CLIENT_SECRET }}
+ YOUTUBE_REFRESH_TOKEN: ${{ secrets.YOUTUBE_REFRESH_TOKEN }}
+ YOUTUBE_TITLE: ${{ inputs.youtube_title }}
+ YOUTUBE_DESCRIPTION: ${{ inputs.youtube_description }}
+ YOUTUBE_TAGS: ${{ inputs.youtube_tags }}
+ YOUTUBE_PRIVACY_STATUS: ${{ inputs.privacy_status }}
+ PHASE7_VIDEO_FILE: output/phase7-video.mp4
+ PHASE7_PHASE3_JSON: data/phase3-lyrics.json
+ PHASE7_OUTPUT_JSON: data/phase7-youtube-upload.json
+ run: |
+ set -e
+ echo "[phase-7] Starting YouTube upload"
+ npm run phase7:upload
+
+ - name: Upload Phase 7 artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: phase7-youtube-upload
+ if-no-files-found: error
+ path: |
+ output/phase7-video.mp4
+ data/phase3-lyrics.json
+ data/phase4-direction.json
+ data/phase7-youtube-upload.json
diff --git a/.github/workflows/test-render.yml b/.github/workflows/test-render.yml
new file mode 100644
index 0000000..1a693de
--- /dev/null
+++ b/.github/workflows/test-render.yml
@@ -0,0 +1,32 @@
+name: Phase 2 Test Render
+
+on:
+ workflow_dispatch:
+
+jobs:
+ render-test-video:
+ runs-on: ubuntu-latest
+ timeout-minutes: 20
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: npm
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Render test video
+ run: npm run render:test
+
+ - name: Upload rendered video artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: phase-2-test-video
+ path: output/test-video.mp4
+ if-no-files-found: error
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..26648c8
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+node_modules/
+output/*.mp4
+.env
diff --git a/data/phase3-lyrics-fallback.json b/data/phase3-lyrics-fallback.json
new file mode 100644
index 0000000..f5f01aa
--- /dev/null
+++ b/data/phase3-lyrics-fallback.json
@@ -0,0 +1,253 @@
+{
+ "metadata": {
+ "generatedAt": "2026-04-14T08:16:35.627Z",
+ "phase": 3,
+ "timingMode": "approximate",
+ "songSelectionMethod": "search-best-match",
+ "sourceApis": {
+ "jiosaavn": "https://jiosaavn-api-taupe-phi.vercel.app/api",
+ "lyrics": "lyrics.ovh",
+ "lyricsMatchMode": "plain-lyrics-fallback"
+ }
+ },
+ "song": {
+ "id": "DYBuG4Ko",
+ "name": "Yellow",
+ "artist": "Coldplay",
+ "album": "Parachutes",
+ "duration": 266,
+ "language": "english",
+ "year": "2000",
+ "url": "https://www.jiosaavn.com/song/yellow/NDEpRDMEfFw",
+ "image": "https://c.saavncdn.com/254/Parachutes-English-2000-20240529104717-500x500.jpg",
+ "hasLyrics": false,
+ "source": "jiosaavn"
+ },
+ "lyricsRaw": {
+ "provider": "lyrics.ovh",
+ "hasSyncedLyrics": false,
+ "hasPlainLyrics": true,
+ "reference": {
+ "artistName": "Coldplay",
+ "trackName": "Yellow",
+ "endpoint": "https://api.lyrics.ovh/v1/Coldplay/Yellow"
+ }
+ },
+ "lines": [
+ {
+ "text": "[Chris Martin]",
+ "start": 0,
+ "end": 6.186
+ },
+ {
+ "text": "Look at the stars",
+ "start": 6.186,
+ "end": 12.372
+ },
+ {
+ "text": "Look how they shine for you",
+ "start": 12.372,
+ "end": 18.558
+ },
+ {
+ "text": "And everything you do",
+ "start": 18.558,
+ "end": 24.744
+ },
+ {
+ "text": "Yeah, they were all yellow",
+ "start": 24.744,
+ "end": 30.93
+ },
+ {
+ "text": "I came along",
+ "start": 30.93,
+ "end": 37.116
+ },
+ {
+ "text": "I wrote a song for you",
+ "start": 37.116,
+ "end": 43.302
+ },
+ {
+ "text": "And all the things you do",
+ "start": 43.302,
+ "end": 49.488
+ },
+ {
+ "text": "And it was called \"Yellow\"",
+ "start": 49.488,
+ "end": 55.674
+ },
+ {
+ "text": "So then I took my turn",
+ "start": 55.674,
+ "end": 61.86
+ },
+ {
+ "text": "Oh, what a thing to have done",
+ "start": 61.86,
+ "end": 68.047
+ },
+ {
+ "text": "And it was all yellow",
+ "start": 68.047,
+ "end": 74.233
+ },
+ {
+ "text": "[Chris, Jonny & Will]",
+ "start": 74.233,
+ "end": 80.419
+ },
+ {
+ "text": "(Aah) Your skin, oh yeah, your skin and bones",
+ "start": 80.419,
+ "end": 86.605
+ },
+ {
+ "text": "(Ooh) Turn into something beautiful",
+ "start": 86.605,
+ "end": 92.791
+ },
+ {
+ "text": "(Aah) You know, you know I love you so",
+ "start": 92.791,
+ "end": 98.977
+ },
+ {
+ "text": "You know I love you so",
+ "start": 98.977,
+ "end": 105.163
+ },
+ {
+ "text": "[Chris Martin]",
+ "start": 105.163,
+ "end": 111.349
+ },
+ {
+ "text": "I swam across",
+ "start": 111.349,
+ "end": 117.535
+ },
+ {
+ "text": "I jumped across for you",
+ "start": 117.535,
+ "end": 123.721
+ },
+ {
+ "text": "Oh, what a thing to do",
+ "start": 123.721,
+ "end": 129.907
+ },
+ {
+ "text": "'Cause you were all yellow",
+ "start": 129.907,
+ "end": 136.093
+ },
+ {
+ "text": "I drew a line",
+ "start": 136.093,
+ "end": 142.279
+ },
+ {
+ "text": "I drew a line for you",
+ "start": 142.279,
+ "end": 148.465
+ },
+ {
+ "text": "Oh, what a thing to do",
+ "start": 148.465,
+ "end": 154.651
+ },
+ {
+ "text": "And it was all yellow",
+ "start": 154.651,
+ "end": 160.837
+ },
+ {
+ "text": "[Chris, Jonny & Will]",
+ "start": 160.837,
+ "end": 167.023
+ },
+ {
+ "text": "(Aah) Your skin, oh yeah, your skin and bones",
+ "start": 167.023,
+ "end": 173.209
+ },
+ {
+ "text": "(Ooh) Turn into something beautiful",
+ "start": 173.209,
+ "end": 179.395
+ },
+ {
+ "text": "(Aah) And you know",
+ "start": 179.395,
+ "end": 185.581
+ },
+ {
+ "text": "For you, I'd bleed myself dry",
+ "start": 185.581,
+ "end": 191.767
+ },
+ {
+ "text": "For you, I'd bleed myself dry",
+ "start": 191.767,
+ "end": 197.953
+ },
+ {
+ "text": "[ Chris Martin]",
+ "start": 197.953,
+ "end": 204.14
+ },
+ {
+ "text": "It's true, look how they shine for you",
+ "start": 204.14,
+ "end": 210.326
+ },
+ {
+ "text": "Look how they shine for you",
+ "start": 210.326,
+ "end": 216.512
+ },
+ {
+ "text": "Look how they shine for",
+ "start": 216.512,
+ "end": 222.698
+ },
+ {
+ "text": "Look how they shine for you",
+ "start": 222.698,
+ "end": 228.884
+ },
+ {
+ "text": "Look how they shine for you",
+ "start": 228.884,
+ "end": 235.07
+ },
+ {
+ "text": "Look how they shine",
+ "start": 235.07,
+ "end": 241.256
+ },
+ {
+ "text": "[ Chris Martin]",
+ "start": 241.256,
+ "end": 247.442
+ },
+ {
+ "text": "Look at the stars",
+ "start": 247.442,
+ "end": 253.628
+ },
+ {
+ "text": "Look how they shine for you",
+ "start": 253.628,
+ "end": 259.814
+ },
+ {
+ "text": "And all the things that you do",
+ "start": 259.814,
+ "end": 266
+ }
+ ]
+}
\ No newline at end of file
diff --git a/data/phase3-lyrics.json b/data/phase3-lyrics.json
new file mode 100644
index 0000000..47443b6
--- /dev/null
+++ b/data/phase3-lyrics.json
@@ -0,0 +1,160 @@
+{
+ "metadata": {
+ "generatedAt": "2026-04-14T12:33:02.276Z",
+ "phase": 3,
+ "timingMode": "synced",
+ "songSelectionMethod": "search-best-match",
+ "sourceApis": {
+ "jiosaavn": "https://jiosaavn-api-taupe-phi.vercel.app/api",
+ "lyrics": "lrclib",
+ "lyricsMatchMode": "direct-get"
+ }
+ },
+ "song": {
+ "id": "Q6l0a09y",
+ "name": "Aaj Ki Raat",
+ "artist": "Amitabh Bhattacharya, Sachin-Jigar, Madhubanti Bagchi, Divya Kumar",
+ "album": "Stree 2",
+ "duration": 228,
+ "language": "hindi",
+ "year": "2024",
+ "url": "https://www.jiosaavn.com/song/aaj-ki-raat/IV4HARUADko",
+ "audioUrl": "https://aac.saavncdn.com/373/36b1b3637cdeedfaa9a9012453948aa6_320.mp4",
+ "image": "https://c.saavncdn.com/373/Stree-2-Hindi-2024-20240828083834-500x500.jpg",
+ "hasLyrics": true,
+ "source": "jiosaavn"
+ },
+ "lyricsRaw": {
+ "provider": "lrclib",
+ "hasSyncedLyrics": true,
+ "hasPlainLyrics": true,
+ "reference": {
+ "id": 12247214,
+ "trackName": "Aaj Ki Raat",
+ "artistName": "Amitabh Bhattacharya",
+ "albumName": "Stree 2 (Original Motion Picture Soundtrack)"
+ }
+ },
+ "lines": [
+ {
+ "text": "थोड़ी फ़ुर्सत भी, मेरी जाँ, कभी बाँहों को दीजिए",
+ "start": 9.44,
+ "end": 17.59
+ },
+ {
+ "text": "थोड़ी फ़ुर्सत भी, मेरी जाँ, कभी बाँहों को दीजिए",
+ "start": 17.59,
+ "end": 25.74
+ },
+ {
+ "text": "आज की रात मज़ा हुस्न का आँखों से लीजिए",
+ "start": 25.74,
+ "end": 33.94
+ },
+ {
+ "text": "आज की रात मज़ा हुस्न का आँखों से लीजिए",
+ "start": 33.94,
+ "end": 42.29
+ },
+ {
+ "text": "वक़्त बर्बाद ना बिन बात की बातों में कीजिए",
+ "start": 42.29,
+ "end": 50.49
+ },
+ {
+ "text": "वक़्त बर्बाद ना बिन बात की बातों में कीजिए",
+ "start": 50.49,
+ "end": 58.95
+ },
+ {
+ "text": "आज की रात मज़ा हुस्न का आँखों से लीजिए",
+ "start": 58.95,
+ "end": 67.14
+ },
+ {
+ "text": "आज की रात मज़ा हुस्न का आँखों से लीजिए",
+ "start": 67.14,
+ "end": 75.91
+ },
+ {
+ "text": "ओ, जान की क़ुर्बानी ले-ले, दिलबर-जानी",
+ "start": 75.91,
+ "end": 81.04
+ },
+ {
+ "text": "तबाही पक्की है, आग तू, मैं पानी",
+ "start": 81.04,
+ "end": 85.16
+ },
+ {
+ "text": "जान की क़ुर्बानी ले-ले, दिलबर-जानी",
+ "start": 85.16,
+ "end": 89.35
+ },
+ {
+ "text": "तबाही पक्की है, आग तू, मैं पानी",
+ "start": 89.35,
+ "end": 110.65
+ },
+ {
+ "text": "मेरे महबूब, समझिए ज़रा मौक़े की नज़ाकत",
+ "start": 110.65,
+ "end": 120.9
+ },
+ {
+ "text": "आ, मेरे महबूब, समझिए ज़रा मौक़े की नज़ाकत",
+ "start": 120.9,
+ "end": 131.36
+ },
+ {
+ "text": "के ख़रीदी नहीं जा सकती हसीनों की इजाज़त",
+ "start": 131.36,
+ "end": 139.6
+ },
+ {
+ "text": "के ख़रीदी नहीं जा सकती हसीनों की इजाज़त",
+ "start": 139.6,
+ "end": 147.79
+ },
+ {
+ "text": "नाज़ इतना..., मेरी जाँ",
+ "start": 147.79,
+ "end": 156.16
+ },
+ {
+ "text": "नाज़ इतना भी नहीं खोखले वादों पे कीजिए",
+ "start": 156.16,
+ "end": 166.48
+ },
+ {
+ "text": "आज की रात मज़ा हुस्न का आँखों से लीजिए",
+ "start": 166.48,
+ "end": 174.77
+ },
+ {
+ "text": "आज की रात मज़ा हुस्न का आँखों से लीजिए",
+ "start": 174.77,
+ "end": 183.52
+ },
+ {
+ "text": "ओ, जान की क़ुर्बानी ले-ले, दिलबर-जानी",
+ "start": 183.52,
+ "end": 188.42
+ },
+ {
+ "text": "तबाही पक्की है, आग तू, मैं पानी",
+ "start": 188.42,
+ "end": 192.77
+ },
+ {
+ "text": "जान की क़ुर्बानी ले-ले, दिलबर-जानी",
+ "start": 192.77,
+ "end": 196.76
+ },
+ {
+ "text": "तबाही पक्की है, आग तू, मैं पानी",
+ "start": 196.76,
+ "end": 228
+ }
+ ]
+}
\ No newline at end of file
diff --git a/data/phase4-direction.json b/data/phase4-direction.json
new file mode 100644
index 0000000..5299374
--- /dev/null
+++ b/data/phase4-direction.json
@@ -0,0 +1,315 @@
+{
+ "metadata": {
+ "generatedAt": "2026-04-14T11:35:35.367Z",
+ "phase": 4,
+ "sourceLyricsFile": "data/phase3-lyrics.json",
+ "provider": "pollinations-dev-fallback",
+ "modelRequested": "openai-fast",
+ "modelResolved": "gpt-oss-20b",
+ "githubModelSupportedInPollinations": false,
+ "fallbackReason": "Model not found: github:gpt-4o-mini. This is our legacy API - for the full model list and new features, visit https://enter.pollinations.ai | GLM5_API_BASE_URL not set; using development fallback model."
+ },
+ "song": {
+ "id": "Q6l0a09y",
+ "name": "Aaj Ki Raat",
+ "artist": "Amitabh Bhattacharya, Sachin-Jigar, Madhubanti Bagchi, Divya Kumar",
+ "album": "Stree 2",
+ "duration": 228,
+ "language": "hindi",
+ "year": "2024",
+ "url": "https://www.jiosaavn.com/song/aaj-ki-raat/IV4HARUADko",
+ "image": "https://c.saavncdn.com/373/Stree-2-Hindi-2024-20240828083834-500x500.jpg",
+ "hasLyrics": true,
+ "source": "jiosaavn"
+ },
+ "direction": {
+ "styleDescription": "Dreamy romantic with neon accents, blending watercolor textures and subtle particle effects to enhance lyrical flow.",
+ "palette": {
+ "background": "#f0f8ff",
+ "primary": "#2c3e50",
+ "accent": "#e74c3c",
+ "secondary": "#8e44ad"
+ },
+ "animationIdeas": [
+ "Text morphing with watercolor splashes",
+ "Animated vines wrapping lyrics",
+ "Particle burst on key words"
+ ],
+ "sceneDirections": [
+ {
+ "sceneId": "scene-1",
+ "start": 9.44,
+ "end": 58.95,
+ "mood": "soft romantic",
+ "cameraMotion": "slow pan",
+ "backgroundTreatment": "gradient dusk sky",
+ "entrance": "fade in",
+ "exit": "fade out",
+ "color": "#2c3e50",
+ "motionIntensity": 0.3,
+ "lineIndices": [
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5
+ ]
+ },
+ {
+ "sceneId": "scene-2",
+ "start": 58.95,
+ "end": 110.65,
+ "mood": "intense longing",
+ "cameraMotion": "tilt up",
+ "backgroundTreatment": "starry night",
+ "entrance": "zoom in",
+ "exit": "blur",
+ "color": "#e74c3c",
+ "motionIntensity": 0.6,
+ "lineIndices": [
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11
+ ]
+ },
+ {
+ "sceneId": "scene-3",
+ "start": 110.65,
+ "end": 166.48,
+ "mood": "playful mystery",
+ "cameraMotion": "orbit",
+ "backgroundTreatment": "abstract watercolor",
+ "entrance": "slide from left",
+ "exit": "slide to right",
+ "color": "#8e44ad",
+ "motionIntensity": 0.5,
+ "lineIndices": [
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17
+ ]
+ },
+ {
+ "sceneId": "scene-4",
+ "start": 166.48,
+ "end": 228,
+ "mood": "epic climax",
+ "cameraMotion": "dolly out",
+ "backgroundTreatment": "fiery aurora",
+ "entrance": "explode",
+ "exit": "fade to black",
+ "color": "#f0f8ff",
+ "motionIntensity": 0.8,
+ "lineIndices": [
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23
+ ]
+ }
+ ],
+ "lineDirections": [
+ {
+ "lineIndex": 0,
+ "entrance": "fade in",
+ "emphasis": "Match scene mood: soft romantic",
+ "exit": "fade out",
+ "color": "#2c3e50",
+ "motionIntensity": 0.3
+ },
+ {
+ "lineIndex": 1,
+ "entrance": "fade in",
+ "emphasis": "Match scene mood: soft romantic",
+ "exit": "fade out",
+ "color": "#2c3e50",
+ "motionIntensity": 0.3
+ },
+ {
+ "lineIndex": 2,
+ "entrance": "fade in",
+ "emphasis": "Match scene mood: soft romantic",
+ "exit": "fade out",
+ "color": "#2c3e50",
+ "motionIntensity": 0.3
+ },
+ {
+ "lineIndex": 3,
+ "entrance": "fade in",
+ "emphasis": "Match scene mood: soft romantic",
+ "exit": "fade out",
+ "color": "#2c3e50",
+ "motionIntensity": 0.3
+ },
+ {
+ "lineIndex": 4,
+ "entrance": "fade in",
+ "emphasis": "Match scene mood: soft romantic",
+ "exit": "fade out",
+ "color": "#2c3e50",
+ "motionIntensity": 0.3
+ },
+ {
+ "lineIndex": 5,
+ "entrance": "fade in",
+ "emphasis": "Match scene mood: soft romantic",
+ "exit": "fade out",
+ "color": "#2c3e50",
+ "motionIntensity": 0.3
+ },
+ {
+ "lineIndex": 6,
+ "entrance": "zoom in",
+ "emphasis": "Match scene mood: intense longing",
+ "exit": "blur",
+ "color": "#e74c3c",
+ "motionIntensity": 0.6
+ },
+ {
+ "lineIndex": 7,
+ "entrance": "zoom in",
+ "emphasis": "Match scene mood: intense longing",
+ "exit": "blur",
+ "color": "#e74c3c",
+ "motionIntensity": 0.6
+ },
+ {
+ "lineIndex": 8,
+ "entrance": "zoom in",
+ "emphasis": "Match scene mood: intense longing",
+ "exit": "blur",
+ "color": "#e74c3c",
+ "motionIntensity": 0.6
+ },
+ {
+ "lineIndex": 9,
+ "entrance": "zoom in",
+ "emphasis": "Match scene mood: intense longing",
+ "exit": "blur",
+ "color": "#e74c3c",
+ "motionIntensity": 0.6
+ },
+ {
+ "lineIndex": 10,
+ "entrance": "zoom in",
+ "emphasis": "Match scene mood: intense longing",
+ "exit": "blur",
+ "color": "#e74c3c",
+ "motionIntensity": 0.6
+ },
+ {
+ "lineIndex": 11,
+ "entrance": "zoom in",
+ "emphasis": "Match scene mood: intense longing",
+ "exit": "blur",
+ "color": "#e74c3c",
+ "motionIntensity": 0.6
+ },
+ {
+ "lineIndex": 12,
+ "entrance": "slide from left",
+ "emphasis": "Match scene mood: playful mystery",
+ "exit": "slide to right",
+ "color": "#8e44ad",
+ "motionIntensity": 0.5
+ },
+ {
+ "lineIndex": 13,
+ "entrance": "slide from left",
+ "emphasis": "Match scene mood: playful mystery",
+ "exit": "slide to right",
+ "color": "#8e44ad",
+ "motionIntensity": 0.5
+ },
+ {
+ "lineIndex": 14,
+ "entrance": "slide from left",
+ "emphasis": "Match scene mood: playful mystery",
+ "exit": "slide to right",
+ "color": "#8e44ad",
+ "motionIntensity": 0.5
+ },
+ {
+ "lineIndex": 15,
+ "entrance": "slide from left",
+ "emphasis": "Match scene mood: playful mystery",
+ "exit": "slide to right",
+ "color": "#8e44ad",
+ "motionIntensity": 0.5
+ },
+ {
+ "lineIndex": 16,
+ "entrance": "slide from left",
+ "emphasis": "Match scene mood: playful mystery",
+ "exit": "slide to right",
+ "color": "#8e44ad",
+ "motionIntensity": 0.5
+ },
+ {
+ "lineIndex": 17,
+ "entrance": "slide from left",
+ "emphasis": "Match scene mood: playful mystery",
+ "exit": "slide to right",
+ "color": "#8e44ad",
+ "motionIntensity": 0.5
+ },
+ {
+ "lineIndex": 18,
+ "entrance": "explode",
+ "emphasis": "Match scene mood: epic climax",
+ "exit": "fade to black",
+ "color": "#f0f8ff",
+ "motionIntensity": 0.8
+ },
+ {
+ "lineIndex": 19,
+ "entrance": "explode",
+ "emphasis": "Match scene mood: epic climax",
+ "exit": "fade to black",
+ "color": "#f0f8ff",
+ "motionIntensity": 0.8
+ },
+ {
+ "lineIndex": 20,
+ "entrance": "explode",
+ "emphasis": "Match scene mood: epic climax",
+ "exit": "fade to black",
+ "color": "#f0f8ff",
+ "motionIntensity": 0.8
+ },
+ {
+ "lineIndex": 21,
+ "entrance": "explode",
+ "emphasis": "Match scene mood: epic climax",
+ "exit": "fade to black",
+ "color": "#f0f8ff",
+ "motionIntensity": 0.8
+ },
+ {
+ "lineIndex": 22,
+ "entrance": "explode",
+ "emphasis": "Match scene mood: epic climax",
+ "exit": "fade to black",
+ "color": "#f0f8ff",
+ "motionIntensity": 0.8
+ },
+ {
+ "lineIndex": 23,
+ "entrance": "explode",
+ "emphasis": "Match scene mood: epic climax",
+ "exit": "fade to black",
+ "color": "#f0f8ff",
+ "motionIntensity": 0.8
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/data/phase7-upload-dry.json b/data/phase7-upload-dry.json
new file mode 100644
index 0000000..fa66974
--- /dev/null
+++ b/data/phase7-upload-dry.json
@@ -0,0 +1,28 @@
+{
+ "metadata": {
+ "generatedAt": "2026-04-14T13:05:19.457Z",
+ "phase": 7,
+ "dryRun": true,
+ "videoFile": "output/phase5-video.mp4",
+ "sourcePhase3File": "data/phase3-lyrics.json",
+ "privacyStatus": "unlisted",
+ "categoryId": "10"
+ },
+ "request": {
+ "title": "Aaj Ki Raat - Amitabh Bhattacharya | Animated Lyrics",
+ "description": "Aaj Ki Raat - Amitabh Bhattacharya, Sachin-Jigar, Madhubanti Bagchi, Divya Kumar\n\nGenerated with an automated Remotion motion-graphics pipeline.\nGenerated at: 2026-04-14T13:05:19.456Z",
+ "tags": [
+ "Aaj Ki Raat",
+ "Amitabh Bhattacharya",
+ "lyrics",
+ "animated lyrics",
+ "motion graphics",
+ "music"
+ ]
+ },
+ "response": {
+ "videoId": null,
+ "url": null,
+ "raw": null
+ }
+}
\ No newline at end of file
diff --git a/data/test-lyrics.json b/data/test-lyrics.json
new file mode 100644
index 0000000..4bc2eec
--- /dev/null
+++ b/data/test-lyrics.json
@@ -0,0 +1,14 @@
+{
+ "lines": [
+ {
+ "text": "Hello world",
+ "start": 0,
+ "end": 2
+ },
+ {
+ "text": "This is a test video",
+ "start": 2,
+ "end": 5
+ }
+ ]
+}
diff --git a/output/.gitkeep b/output/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..9fcf449
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,3218 @@
+{
+ "name": "mplayer",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "mplayer",
+ "version": "1.0.0",
+ "license": "ISC",
+ "dependencies": {
+ "@fontsource/noto-sans-devanagari": "^5.2.8",
+ "@remotion/bundler": "^4.0.448",
+ "@remotion/renderer": "^4.0.448",
+ "googleapis": "^171.4.0",
+ "react": "^19.2.5",
+ "react-dom": "^19.2.5",
+ "remotion": "^4.0.448"
+ }
+ },
+ "node_modules/@emnapi/core": {
+ "version": "1.9.2",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz",
+ "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/wasi-threads": "1.2.1",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.9.2",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz",
+ "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/wasi-threads": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
+ "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz",
+ "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz",
+ "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz",
+ "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz",
+ "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz",
+ "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz",
+ "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz",
+ "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz",
+ "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz",
+ "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz",
+ "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz",
+ "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz",
+ "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==",
+ "cpu": [
+ "loong64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz",
+ "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==",
+ "cpu": [
+ "mips64el"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz",
+ "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz",
+ "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz",
+ "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz",
+ "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz",
+ "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz",
+ "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz",
+ "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz",
+ "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz",
+ "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz",
+ "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz",
+ "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz",
+ "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@fontsource/noto-sans-devanagari": {
+ "version": "5.2.8",
+ "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-devanagari/-/noto-sans-devanagari-5.2.8.tgz",
+ "integrity": "sha512-UyCfyCEX2+QVUDQWb9y2MRTg1O3bTcXixzD2xQfapFOtw4hpH1rfDIbTXulZ4SUEsbB/wWeyyEL9b93Nmwd6Gg==",
+ "license": "OFL-1.1",
+ "funding": {
+ "url": "https://github.com/sponsors/ayuhito"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/source-map": {
+ "version": "0.3.11",
+ "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
+ "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.25"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@mediabunny/aac-encoder": {
+ "version": "1.39.2",
+ "resolved": "https://registry.npmjs.org/@mediabunny/aac-encoder/-/aac-encoder-1.39.2.tgz",
+ "integrity": "sha512-KD6KADVzAnW7tqhRFGBOX4uaiHbd0Yxvg0lfthj3wJLAEEgEBAvi43w+ZXWeEn54X/jpabrLe4bW/eYFFvlbUA==",
+ "license": "MPL-2.0",
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/sponsors/Vanilagy"
+ },
+ "peerDependencies": {
+ "mediabunny": "^1.0.0"
+ }
+ },
+ "node_modules/@mediabunny/flac-encoder": {
+ "version": "1.39.2",
+ "resolved": "https://registry.npmjs.org/@mediabunny/flac-encoder/-/flac-encoder-1.39.2.tgz",
+ "integrity": "sha512-VwBr3AzZTPEEPvt4aladZiXwOf3W293eq213zDupGQi/taS8WWNqDd3eBdf8FfvlbXATfbRiycXDKyQ0HlOZaQ==",
+ "license": "MPL-2.0",
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/sponsors/Vanilagy"
+ },
+ "peerDependencies": {
+ "mediabunny": "^1.0.0"
+ }
+ },
+ "node_modules/@mediabunny/mp3-encoder": {
+ "version": "1.39.2",
+ "resolved": "https://registry.npmjs.org/@mediabunny/mp3-encoder/-/mp3-encoder-1.39.2.tgz",
+ "integrity": "sha512-3rrodrGnUpUP8F2d1aRUl8IvjqK3jegkupbOzvOokooSAO5rXk2Lr5jZe7TnPeiVGiXfmnoJ7s9uyUOHlCd8qw==",
+ "license": "MPL-2.0",
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/sponsors/Vanilagy"
+ },
+ "peerDependencies": {
+ "mediabunny": "^1.0.0"
+ }
+ },
+ "node_modules/@module-federation/error-codes": {
+ "version": "0.22.0",
+ "resolved": "https://registry.npmjs.org/@module-federation/error-codes/-/error-codes-0.22.0.tgz",
+ "integrity": "sha512-xF9SjnEy7vTdx+xekjPCV5cIHOGCkdn3pIxo9vU7gEZMIw0SvAEdsy6Uh17xaCpm8V0FWvR0SZoK9Ik6jGOaug==",
+ "license": "MIT"
+ },
+ "node_modules/@module-federation/runtime": {
+ "version": "0.22.0",
+ "resolved": "https://registry.npmjs.org/@module-federation/runtime/-/runtime-0.22.0.tgz",
+ "integrity": "sha512-38g5iPju2tPC3KHMPxRKmy4k4onNp6ypFPS1eKGsNLUkXgHsPMBFqAjDw96iEcjri91BrahG4XcdyKi97xZzlA==",
+ "license": "MIT",
+ "dependencies": {
+ "@module-federation/error-codes": "0.22.0",
+ "@module-federation/runtime-core": "0.22.0",
+ "@module-federation/sdk": "0.22.0"
+ }
+ },
+ "node_modules/@module-federation/runtime-core": {
+ "version": "0.22.0",
+ "resolved": "https://registry.npmjs.org/@module-federation/runtime-core/-/runtime-core-0.22.0.tgz",
+ "integrity": "sha512-GR1TcD6/s7zqItfhC87zAp30PqzvceoeDGYTgF3Vx2TXvsfDrhP6Qw9T4vudDQL3uJRne6t7CzdT29YyVxlgIA==",
+ "license": "MIT",
+ "dependencies": {
+ "@module-federation/error-codes": "0.22.0",
+ "@module-federation/sdk": "0.22.0"
+ }
+ },
+ "node_modules/@module-federation/runtime-tools": {
+ "version": "0.22.0",
+ "resolved": "https://registry.npmjs.org/@module-federation/runtime-tools/-/runtime-tools-0.22.0.tgz",
+ "integrity": "sha512-4ScUJ/aUfEernb+4PbLdhM/c60VHl698Gn1gY21m9vyC1Ucn69fPCA1y2EwcCB7IItseRMoNhdcWQnzt/OPCNA==",
+ "license": "MIT",
+ "dependencies": {
+ "@module-federation/runtime": "0.22.0",
+ "@module-federation/webpack-bundler-runtime": "0.22.0"
+ }
+ },
+ "node_modules/@module-federation/sdk": {
+ "version": "0.22.0",
+ "resolved": "https://registry.npmjs.org/@module-federation/sdk/-/sdk-0.22.0.tgz",
+ "integrity": "sha512-x4aFNBKn2KVQRuNVC5A7SnrSCSqyfIWmm1DvubjbO9iKFe7ith5niw8dqSFBekYBg2Fwy+eMg4sEFNVvCAdo6g==",
+ "license": "MIT"
+ },
+ "node_modules/@module-federation/webpack-bundler-runtime": {
+ "version": "0.22.0",
+ "resolved": "https://registry.npmjs.org/@module-federation/webpack-bundler-runtime/-/webpack-bundler-runtime-0.22.0.tgz",
+ "integrity": "sha512-aM8gCqXu+/4wBmJtVeMeeMN5guw3chf+2i6HajKtQv7SJfxV/f4IyNQJUeUQu9HfiAZHjqtMV5Lvq/Lvh8LdyA==",
+ "license": "MIT",
+ "dependencies": {
+ "@module-federation/runtime": "0.22.0",
+ "@module-federation/sdk": "0.22.0"
+ }
+ },
+ "node_modules/@napi-rs/wasm-runtime": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.7.tgz",
+ "integrity": "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.5.0",
+ "@emnapi/runtime": "^1.5.0",
+ "@tybys/wasm-util": "^0.10.1"
+ }
+ },
+ "node_modules/@remotion/bundler": {
+ "version": "4.0.448",
+ "resolved": "https://registry.npmjs.org/@remotion/bundler/-/bundler-4.0.448.tgz",
+ "integrity": "sha512-vCjEGYIQ7bA/Ba64B1t3kG1VBxG5ciJhKuZtgd3r/DE0VM64blf+v5obZYc/zU6Rs0I9Bex8geambP/VNBIyqQ==",
+ "license": "SEE LICENSE IN LICENSE.md",
+ "dependencies": {
+ "@remotion/media-parser": "4.0.448",
+ "@remotion/studio": "4.0.448",
+ "@remotion/studio-shared": "4.0.448",
+ "@rspack/core": "1.7.6",
+ "@rspack/plugin-react-refresh": "1.6.1",
+ "esbuild": "0.25.0",
+ "loader-utils": "2.0.4",
+ "postcss": "8.5.1",
+ "postcss-value-parser": "4.2.0",
+ "react-refresh": "0.18.0",
+ "remotion": "4.0.448",
+ "source-map": "0.7.3",
+ "style-loader": "4.0.0",
+ "webpack": "5.105.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@remotion/compositor-darwin-arm64": {
+ "version": "4.0.448",
+ "resolved": "https://registry.npmjs.org/@remotion/compositor-darwin-arm64/-/compositor-darwin-arm64-4.0.448.tgz",
+ "integrity": "sha512-vjFdLhGZhXFqqO+HKOQnaKFLVNW2SUNz6jf087gL8V3PLn5HQnmu5t1k1nyQfSxsXEeOhmwMIFy6gD41N5DJOw==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@remotion/compositor-darwin-x64": {
+ "version": "4.0.448",
+ "resolved": "https://registry.npmjs.org/@remotion/compositor-darwin-x64/-/compositor-darwin-x64-4.0.448.tgz",
+ "integrity": "sha512-Ymb0fBiNNTp8iQCYcYwodDCXVXb4x0tQM6hKgDRUyWyXgLZWid37eAJcYZntjgCVQQXsXe2q1j7DnLjGEhI32w==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@remotion/compositor-linux-arm64-gnu": {
+ "version": "4.0.448",
+ "resolved": "https://registry.npmjs.org/@remotion/compositor-linux-arm64-gnu/-/compositor-linux-arm64-gnu-4.0.448.tgz",
+ "integrity": "sha512-cYr22U3USY/B4Xj55gQiq9IgbfsF8YQKxt9kUDIi96wYNHV1Wwsdcwxp/FtUxh7JLet3XhNAeShjSvQTj1sN+g==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@remotion/compositor-linux-arm64-musl": {
+ "version": "4.0.448",
+ "resolved": "https://registry.npmjs.org/@remotion/compositor-linux-arm64-musl/-/compositor-linux-arm64-musl-4.0.448.tgz",
+ "integrity": "sha512-aKRU3P88BFXfyF9UDqp3NkG2s/RXnR6um+UZrJXCTRlFXaMzH+j4ysReMvijkIMLfE2OoKg8WAu63tGD/EfMmQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@remotion/compositor-linux-x64-gnu": {
+ "version": "4.0.448",
+ "resolved": "https://registry.npmjs.org/@remotion/compositor-linux-x64-gnu/-/compositor-linux-x64-gnu-4.0.448.tgz",
+ "integrity": "sha512-HdbR3Plifz1+VLO8VGtnP2+uW0PiX378jUQA+k0/anXqa4BDgLX5GptLeHK7bU0Iio/Hbu6wsEGLMmDr80U1/Q==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@remotion/compositor-linux-x64-musl": {
+ "version": "4.0.448",
+ "resolved": "https://registry.npmjs.org/@remotion/compositor-linux-x64-musl/-/compositor-linux-x64-musl-4.0.448.tgz",
+ "integrity": "sha512-7OK/AtkKWFFrzgZF7Ti6Ld9vIsQ4Uh61k9iKPsmAw0f3CeXHJIuG0m3uhcLoqaBsD35fnL9D12vHgXPHpHyjng==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@remotion/compositor-win32-x64-msvc": {
+ "version": "4.0.448",
+ "resolved": "https://registry.npmjs.org/@remotion/compositor-win32-x64-msvc/-/compositor-win32-x64-msvc-4.0.448.tgz",
+ "integrity": "sha512-qjygadAQ+lEBmWRqM+KX1qWy69eXQLCkJ9xUkaLDWgtPkKNglvh+sBa2Nd9hp84ye4u+7fUO8MxKEPVSt2f54g==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@remotion/licensing": {
+ "version": "4.0.448",
+ "resolved": "https://registry.npmjs.org/@remotion/licensing/-/licensing-4.0.448.tgz",
+ "integrity": "sha512-weQ+yJLJN+NEeKKMlkHT9cydUSjxwGnteHqedAd3ffaBUOpRQthWJWQwLhCupqdFyDdkRt0Rtnh5ib7/dSByPA==",
+ "license": "MIT"
+ },
+ "node_modules/@remotion/media-parser": {
+ "version": "4.0.448",
+ "resolved": "https://registry.npmjs.org/@remotion/media-parser/-/media-parser-4.0.448.tgz",
+ "integrity": "sha512-lZANRTl/EfjfFZgysm7GcnO9WgZdofdA67XcQ5oyrzmth5pCxcKcnv/ruKSlbBYCX8r2U2Bd+nknCcXg5e0DCw==",
+ "license": "Remotion License https://remotion.dev/license"
+ },
+ "node_modules/@remotion/media-utils": {
+ "version": "4.0.448",
+ "resolved": "https://registry.npmjs.org/@remotion/media-utils/-/media-utils-4.0.448.tgz",
+ "integrity": "sha512-q6yrn0GriPq+npBh3Bg+8y6i+slLkpFFAZSr3u2S1p+bWRD1ijN3AQaJZ1LBHD4dCPTQh2Lr4UYHUUeoxKD90Q==",
+ "license": "MIT",
+ "dependencies": {
+ "mediabunny": "1.39.2",
+ "remotion": "4.0.448"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@remotion/player": {
+ "version": "4.0.448",
+ "resolved": "https://registry.npmjs.org/@remotion/player/-/player-4.0.448.tgz",
+ "integrity": "sha512-lXwiJvAqlaUTwt5NGoGma9Md5IF9nf4Zetn6OAaNk0zTiXegUN42IFBSmzKZC8qUwNV10IpeLsTlQQEv4ntU1Q==",
+ "license": "SEE LICENSE IN LICENSE.md",
+ "dependencies": {
+ "remotion": "4.0.448"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@remotion/renderer": {
+ "version": "4.0.448",
+ "resolved": "https://registry.npmjs.org/@remotion/renderer/-/renderer-4.0.448.tgz",
+ "integrity": "sha512-ST3Gfa7Ai0G4hqkWvk6J438tRXCVypv+x8KqKdfbIUK4v7A12eg/a1hlu5Sms7ncpFOnm10BwdtoyNpAlf68eg==",
+ "license": "SEE LICENSE IN LICENSE.md",
+ "dependencies": {
+ "@remotion/licensing": "4.0.448",
+ "@remotion/streaming": "4.0.448",
+ "execa": "5.1.1",
+ "extract-zip": "2.0.1",
+ "remotion": "4.0.448",
+ "source-map": "^0.8.0-beta.0",
+ "ws": "8.17.1"
+ },
+ "optionalDependencies": {
+ "@remotion/compositor-darwin-arm64": "4.0.448",
+ "@remotion/compositor-darwin-x64": "4.0.448",
+ "@remotion/compositor-linux-arm64-gnu": "4.0.448",
+ "@remotion/compositor-linux-arm64-musl": "4.0.448",
+ "@remotion/compositor-linux-x64-gnu": "4.0.448",
+ "@remotion/compositor-linux-x64-musl": "4.0.448",
+ "@remotion/compositor-win32-x64-msvc": "4.0.448"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@remotion/renderer/node_modules/source-map": {
+ "version": "0.8.0-beta.0",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz",
+ "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==",
+ "deprecated": "The work that was done in this beta branch won't be included in future versions",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "whatwg-url": "^7.0.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@remotion/streaming": {
+ "version": "4.0.448",
+ "resolved": "https://registry.npmjs.org/@remotion/streaming/-/streaming-4.0.448.tgz",
+ "integrity": "sha512-RThhasaaxaQ6hoW+SkaNiaq4HwmwYkxfIJhsxZIrby9wLKGVOh1nG/GSfMSGaeJWsRp3KsLuqiwuQw2EuINQOw==",
+ "license": "MIT"
+ },
+ "node_modules/@remotion/studio": {
+ "version": "4.0.448",
+ "resolved": "https://registry.npmjs.org/@remotion/studio/-/studio-4.0.448.tgz",
+ "integrity": "sha512-6uGyBFze2cDApuCf8OkJSy+ZIlWjJxVUuU29JF3vQ9OCz+OMqcJt0L+SxfUGvKihlq4PxG7Y75BMJOGuCiEMoA==",
+ "license": "MIT",
+ "dependencies": {
+ "@remotion/media-utils": "4.0.448",
+ "@remotion/player": "4.0.448",
+ "@remotion/renderer": "4.0.448",
+ "@remotion/studio-shared": "4.0.448",
+ "@remotion/web-renderer": "4.0.448",
+ "@remotion/zod-types": "4.0.448",
+ "mediabunny": "1.39.2",
+ "memfs": "3.4.3",
+ "open": "^8.4.2",
+ "remotion": "4.0.448",
+ "semver": "7.5.3",
+ "source-map": "0.7.3",
+ "zod": "4.3.6"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@remotion/studio-shared": {
+ "version": "4.0.448",
+ "resolved": "https://registry.npmjs.org/@remotion/studio-shared/-/studio-shared-4.0.448.tgz",
+ "integrity": "sha512-XQWQt+i67kMEBeCXDM0Kd4XK/j2Q0aqwjjSyqz9fbnh4KYh3QGrd9M1AnG9EUBbskCkfzt7isy2d8M8LcRvTBg==",
+ "license": "MIT",
+ "dependencies": {
+ "remotion": "4.0.448"
+ }
+ },
+ "node_modules/@remotion/web-renderer": {
+ "version": "4.0.448",
+ "resolved": "https://registry.npmjs.org/@remotion/web-renderer/-/web-renderer-4.0.448.tgz",
+ "integrity": "sha512-iPsoLPxxiHKWZPbTZLKKxygcISZP5+rajODeWNlM+qrEN001Ardzf2MlqY+Zb/qHWsdAotw+cBPVVAv0JVZPQw==",
+ "license": "UNLICENSED",
+ "dependencies": {
+ "@mediabunny/aac-encoder": "1.39.2",
+ "@mediabunny/flac-encoder": "1.39.2",
+ "@mediabunny/mp3-encoder": "1.39.2",
+ "@remotion/licensing": "4.0.448",
+ "mediabunny": "1.39.2",
+ "remotion": "4.0.448"
+ },
+ "peerDependencies": {
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0"
+ }
+ },
+ "node_modules/@remotion/zod-types": {
+ "version": "4.0.448",
+ "resolved": "https://registry.npmjs.org/@remotion/zod-types/-/zod-types-4.0.448.tgz",
+ "integrity": "sha512-3Hvtz30SrnKq0H7PokzrF12XS/d4BOdEt3xB3Y9obpy9JYPqDRI2p0OUpdrpJv03+4hxG6GhI1N38Of0xCMYcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "remotion": "4.0.448"
+ },
+ "peerDependencies": {
+ "zod": "4.3.6"
+ }
+ },
+ "node_modules/@rspack/binding": {
+ "version": "1.7.6",
+ "resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-1.7.6.tgz",
+ "integrity": "sha512-/NrEcfo8Gx22hLGysanrV6gHMuqZSxToSci/3M4kzEQtF5cPjfOv5pqeLK/+B6cr56ul/OmE96cCdWcXeVnFjQ==",
+ "license": "MIT",
+ "optionalDependencies": {
+ "@rspack/binding-darwin-arm64": "1.7.6",
+ "@rspack/binding-darwin-x64": "1.7.6",
+ "@rspack/binding-linux-arm64-gnu": "1.7.6",
+ "@rspack/binding-linux-arm64-musl": "1.7.6",
+ "@rspack/binding-linux-x64-gnu": "1.7.6",
+ "@rspack/binding-linux-x64-musl": "1.7.6",
+ "@rspack/binding-wasm32-wasi": "1.7.6",
+ "@rspack/binding-win32-arm64-msvc": "1.7.6",
+ "@rspack/binding-win32-ia32-msvc": "1.7.6",
+ "@rspack/binding-win32-x64-msvc": "1.7.6"
+ }
+ },
+ "node_modules/@rspack/binding-darwin-arm64": {
+ "version": "1.7.6",
+ "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.7.6.tgz",
+ "integrity": "sha512-NZ9AWtB1COLUX1tA9HQQvWpTy07NSFfKBU8A6ylWd5KH8AePZztpNgLLAVPTuNO4CZXYpwcoclf8jG/luJcQdQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rspack/binding-darwin-x64": {
+ "version": "1.7.6",
+ "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-1.7.6.tgz",
+ "integrity": "sha512-J2g6xk8ZS7uc024dNTGTHxoFzFovAZIRixUG7PiciLKTMP78svbSSWrmW6N8oAsAkzYfJWwQpVgWfFNRHvYxSw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rspack/binding-linux-arm64-gnu": {
+ "version": "1.7.6",
+ "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.7.6.tgz",
+ "integrity": "sha512-eQfcsaxhFrv5FmtaA7+O1F9/2yFDNIoPZzV/ZvqvFz5bBXVc4FAm/1fVpBg8Po/kX1h0chBc7Xkpry3cabFW8w==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rspack/binding-linux-arm64-musl": {
+ "version": "1.7.6",
+ "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.7.6.tgz",
+ "integrity": "sha512-DfQXKiyPIl7i1yECHy4eAkSmlUzzsSAbOjgMuKn7pudsWf483jg0UUYutNgXSlBjc/QSUp7906Cg8oty9OfwPA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rspack/binding-linux-x64-gnu": {
+ "version": "1.7.6",
+ "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.7.6.tgz",
+ "integrity": "sha512-NdA+2X3lk2GGrMMnTGyYTzM3pn+zNjaqXqlgKmFBXvjfZqzSsKq3pdD1KHZCd5QHN+Fwvoszj0JFsquEVhE1og==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rspack/binding-linux-x64-musl": {
+ "version": "1.7.6",
+ "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-1.7.6.tgz",
+ "integrity": "sha512-rEy6MHKob02t/77YNgr6dREyJ0e0tv1X6Xsg8Z5E7rPXead06zefUbfazj4RELYySWnM38ovZyJAkPx/gOn3VA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rspack/binding-wasm32-wasi": {
+ "version": "1.7.6",
+ "resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-1.7.6.tgz",
+ "integrity": "sha512-YupOrz0daSG+YBbCIgpDgzfMM38YpChv+afZpaxx5Ml7xPeAZIIdgWmLHnQ2rts73N2M1NspAiBwV00Xx0N4Vg==",
+ "cpu": [
+ "wasm32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@napi-rs/wasm-runtime": "1.0.7"
+ }
+ },
+ "node_modules/@rspack/binding-win32-arm64-msvc": {
+ "version": "1.7.6",
+ "resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.7.6.tgz",
+ "integrity": "sha512-INj7aVXjBvlZ84kEhSK4kJ484ub0i+BzgnjDWOWM1K+eFYDZjLdAsQSS3fGGXwVc3qKbPIssFfnftATDMTEJHQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rspack/binding-win32-ia32-msvc": {
+ "version": "1.7.6",
+ "resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.7.6.tgz",
+ "integrity": "sha512-lXGvC+z67UMcw58In12h8zCa9IyYRmuptUBMItQJzu+M278aMuD1nETyGLL7e4+OZ2lvrnnBIcjXN1hfw2yRzw==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rspack/binding-win32-x64-msvc": {
+ "version": "1.7.6",
+ "resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.7.6.tgz",
+ "integrity": "sha512-zeUxEc0ZaPpmaYlCeWcjSJUPuRRySiSHN23oJ2Xyw0jsQ01Qm4OScPdr0RhEOFuK/UE+ANyRtDo4zJsY52Hadw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rspack/core": {
+ "version": "1.7.6",
+ "resolved": "https://registry.npmjs.org/@rspack/core/-/core-1.7.6.tgz",
+ "integrity": "sha512-Iax6UhrfZqJajA778c1d5DBFbSIqPOSrI34kpNIiNpWd8Jq7mFIa+Z60SQb5ZQDZuUxcCZikjz5BxinFjTkg7Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@module-federation/runtime-tools": "0.22.0",
+ "@rspack/binding": "1.7.6",
+ "@rspack/lite-tapable": "1.1.0"
+ },
+ "engines": {
+ "node": ">=18.12.0"
+ },
+ "peerDependencies": {
+ "@swc/helpers": ">=0.5.1"
+ },
+ "peerDependenciesMeta": {
+ "@swc/helpers": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rspack/lite-tapable": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@rspack/lite-tapable/-/lite-tapable-1.1.0.tgz",
+ "integrity": "sha512-E2B0JhYFmVAwdDiG14+DW0Di4Ze4Jg10Pc4/lILUrd5DRCaklduz2OvJ5HYQ6G+hd+WTzqQb3QnDNfK4yvAFYw==",
+ "license": "MIT"
+ },
+ "node_modules/@rspack/plugin-react-refresh": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@rspack/plugin-react-refresh/-/plugin-react-refresh-1.6.1.tgz",
+ "integrity": "sha512-eqqW5645VG3CzGzFgNg5HqNdHVXY+567PGjtDhhrM8t67caxmsSzRmT5qfoEIfBcGgFkH9vEg7kzXwmCYQdQDw==",
+ "license": "MIT",
+ "dependencies": {
+ "error-stack-parser": "^2.1.4",
+ "html-entities": "^2.6.0"
+ },
+ "peerDependencies": {
+ "react-refresh": ">=0.10.0 <1.0.0",
+ "webpack-hot-middleware": "2.x"
+ },
+ "peerDependenciesMeta": {
+ "webpack-hot-middleware": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@tybys/wasm-util": {
+ "version": "0.10.1",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
+ "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@types/dom-mediacapture-transform": {
+ "version": "0.1.11",
+ "resolved": "https://registry.npmjs.org/@types/dom-mediacapture-transform/-/dom-mediacapture-transform-0.1.11.tgz",
+ "integrity": "sha512-Y2p+nGf1bF2XMttBnsVPHUWzRRZzqUoJAKmiP10b5umnO6DDrWI0BrGDJy1pOHoOULVmGSfFNkQrAlC5dcj6nQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/dom-webcodecs": "*"
+ }
+ },
+ "node_modules/@types/dom-webcodecs": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmjs.org/@types/dom-webcodecs/-/dom-webcodecs-0.1.13.tgz",
+ "integrity": "sha512-O5hkiFIcjjszPIYyUSyvScyvrBoV3NOEEZx/pMlsu44TKzWNkLVBBxnxJz42in5n3QIolYOcBYFCPZZ0h8SkwQ==",
+ "license": "MIT"
+ },
+ "node_modules/@types/eslint": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
+ "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "*",
+ "@types/json-schema": "*"
+ }
+ },
+ "node_modules/@types/eslint-scope": {
+ "version": "3.7.7",
+ "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz",
+ "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/eslint": "*",
+ "@types/estree": "*"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "license": "MIT"
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "25.6.0",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz",
+ "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.19.0"
+ }
+ },
+ "node_modules/@types/yauzl": {
+ "version": "2.10.3",
+ "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
+ "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@webassemblyjs/ast": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz",
+ "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@webassemblyjs/helper-numbers": "1.13.2",
+ "@webassemblyjs/helper-wasm-bytecode": "1.13.2"
+ }
+ },
+ "node_modules/@webassemblyjs/floating-point-hex-parser": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz",
+ "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==",
+ "license": "MIT"
+ },
+ "node_modules/@webassemblyjs/helper-api-error": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz",
+ "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==",
+ "license": "MIT"
+ },
+ "node_modules/@webassemblyjs/helper-buffer": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz",
+ "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==",
+ "license": "MIT"
+ },
+ "node_modules/@webassemblyjs/helper-numbers": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz",
+ "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==",
+ "license": "MIT",
+ "dependencies": {
+ "@webassemblyjs/floating-point-hex-parser": "1.13.2",
+ "@webassemblyjs/helper-api-error": "1.13.2",
+ "@xtuc/long": "4.2.2"
+ }
+ },
+ "node_modules/@webassemblyjs/helper-wasm-bytecode": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz",
+ "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==",
+ "license": "MIT"
+ },
+ "node_modules/@webassemblyjs/helper-wasm-section": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz",
+ "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==",
+ "license": "MIT",
+ "dependencies": {
+ "@webassemblyjs/ast": "1.14.1",
+ "@webassemblyjs/helper-buffer": "1.14.1",
+ "@webassemblyjs/helper-wasm-bytecode": "1.13.2",
+ "@webassemblyjs/wasm-gen": "1.14.1"
+ }
+ },
+ "node_modules/@webassemblyjs/ieee754": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz",
+ "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==",
+ "license": "MIT",
+ "dependencies": {
+ "@xtuc/ieee754": "^1.2.0"
+ }
+ },
+ "node_modules/@webassemblyjs/leb128": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz",
+ "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@xtuc/long": "4.2.2"
+ }
+ },
+ "node_modules/@webassemblyjs/utf8": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz",
+ "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==",
+ "license": "MIT"
+ },
+ "node_modules/@webassemblyjs/wasm-edit": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz",
+ "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@webassemblyjs/ast": "1.14.1",
+ "@webassemblyjs/helper-buffer": "1.14.1",
+ "@webassemblyjs/helper-wasm-bytecode": "1.13.2",
+ "@webassemblyjs/helper-wasm-section": "1.14.1",
+ "@webassemblyjs/wasm-gen": "1.14.1",
+ "@webassemblyjs/wasm-opt": "1.14.1",
+ "@webassemblyjs/wasm-parser": "1.14.1",
+ "@webassemblyjs/wast-printer": "1.14.1"
+ }
+ },
+ "node_modules/@webassemblyjs/wasm-gen": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz",
+ "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==",
+ "license": "MIT",
+ "dependencies": {
+ "@webassemblyjs/ast": "1.14.1",
+ "@webassemblyjs/helper-wasm-bytecode": "1.13.2",
+ "@webassemblyjs/ieee754": "1.13.2",
+ "@webassemblyjs/leb128": "1.13.2",
+ "@webassemblyjs/utf8": "1.13.2"
+ }
+ },
+ "node_modules/@webassemblyjs/wasm-opt": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz",
+ "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==",
+ "license": "MIT",
+ "dependencies": {
+ "@webassemblyjs/ast": "1.14.1",
+ "@webassemblyjs/helper-buffer": "1.14.1",
+ "@webassemblyjs/wasm-gen": "1.14.1",
+ "@webassemblyjs/wasm-parser": "1.14.1"
+ }
+ },
+ "node_modules/@webassemblyjs/wasm-parser": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz",
+ "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@webassemblyjs/ast": "1.14.1",
+ "@webassemblyjs/helper-api-error": "1.13.2",
+ "@webassemblyjs/helper-wasm-bytecode": "1.13.2",
+ "@webassemblyjs/ieee754": "1.13.2",
+ "@webassemblyjs/leb128": "1.13.2",
+ "@webassemblyjs/utf8": "1.13.2"
+ }
+ },
+ "node_modules/@webassemblyjs/wast-printer": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz",
+ "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==",
+ "license": "MIT",
+ "dependencies": {
+ "@webassemblyjs/ast": "1.14.1",
+ "@xtuc/long": "4.2.2"
+ }
+ },
+ "node_modules/@xtuc/ieee754": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
+ "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@xtuc/long": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
+ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/acorn": {
+ "version": "8.16.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
+ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-import-phases": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz",
+ "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.13.0"
+ },
+ "peerDependencies": {
+ "acorn": "^8.14.0"
+ }
+ },
+ "node_modules/agent-base": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
+ "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "fast-uri": "^3.0.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ajv-formats": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
+ "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "ajv": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ajv-keywords": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
+ "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3"
+ },
+ "peerDependencies": {
+ "ajv": "^8.8.2"
+ }
+ },
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.10.18",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.18.tgz",
+ "integrity": "sha512-VSnGQAOLtP5mib/DPyg2/t+Tlv65NTBz83BJBJvmLVHHuKJVaDOBvJJykiT5TR++em5nfAySPccDZDa4oSrn8A==",
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/big.js": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
+ "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/bignumber.js": {
+ "version": "9.3.1",
+ "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz",
+ "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==",
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.2",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz",
+ "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "baseline-browser-mapping": "^2.10.12",
+ "caniuse-lite": "^1.0.30001782",
+ "electron-to-chromium": "^1.5.328",
+ "node-releases": "^2.0.36",
+ "update-browserslist-db": "^1.2.3"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/buffer-crc32": {
+ "version": "0.2.13",
+ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
+ "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/buffer-equal-constant-time": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/buffer-from": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+ "license": "MIT"
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001788",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001788.tgz",
+ "integrity": "sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/chrome-trace-event": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz",
+ "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0"
+ }
+ },
+ "node_modules/commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "license": "MIT"
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/data-uri-to-buffer": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
+ "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/define-lazy-prop": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
+ "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/ecdsa-sig-formatter": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.336",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.336.tgz",
+ "integrity": "sha512-AbH9q9J455r/nLmdNZes0G0ZKcRX73FicwowalLs6ijwOmCJSRRrLX63lcAlzy9ux3dWK1w1+1nsBJEWN11hcQ==",
+ "license": "ISC"
+ },
+ "node_modules/emojis-list": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
+ "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/end-of-stream": {
+ "version": "1.4.5",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
+ "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
+ "license": "MIT",
+ "dependencies": {
+ "once": "^1.4.0"
+ }
+ },
+ "node_modules/enhanced-resolve": {
+ "version": "5.20.1",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz",
+ "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==",
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.3.0"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/error-stack-parser": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz",
+ "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==",
+ "license": "MIT",
+ "dependencies": {
+ "stackframe": "^1.3.4"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-module-lexer": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz",
+ "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==",
+ "license": "MIT"
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz",
+ "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.25.0",
+ "@esbuild/android-arm": "0.25.0",
+ "@esbuild/android-arm64": "0.25.0",
+ "@esbuild/android-x64": "0.25.0",
+ "@esbuild/darwin-arm64": "0.25.0",
+ "@esbuild/darwin-x64": "0.25.0",
+ "@esbuild/freebsd-arm64": "0.25.0",
+ "@esbuild/freebsd-x64": "0.25.0",
+ "@esbuild/linux-arm": "0.25.0",
+ "@esbuild/linux-arm64": "0.25.0",
+ "@esbuild/linux-ia32": "0.25.0",
+ "@esbuild/linux-loong64": "0.25.0",
+ "@esbuild/linux-mips64el": "0.25.0",
+ "@esbuild/linux-ppc64": "0.25.0",
+ "@esbuild/linux-riscv64": "0.25.0",
+ "@esbuild/linux-s390x": "0.25.0",
+ "@esbuild/linux-x64": "0.25.0",
+ "@esbuild/netbsd-arm64": "0.25.0",
+ "@esbuild/netbsd-x64": "0.25.0",
+ "@esbuild/openbsd-arm64": "0.25.0",
+ "@esbuild/openbsd-x64": "0.25.0",
+ "@esbuild/sunos-x64": "0.25.0",
+ "@esbuild/win32-arm64": "0.25.0",
+ "@esbuild/win32-ia32": "0.25.0",
+ "@esbuild/win32-x64": "0.25.0"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
+ "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^4.1.1"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esrecurse/node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/events": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
+ "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.x"
+ }
+ },
+ "node_modules/execa": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
+ "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
+ "license": "MIT",
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^6.0.0",
+ "human-signals": "^2.1.0",
+ "is-stream": "^2.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^4.0.1",
+ "onetime": "^5.1.2",
+ "signal-exit": "^3.0.3",
+ "strip-final-newline": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "node_modules/extend": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
+ "license": "MIT"
+ },
+ "node_modules/extract-zip": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
+ "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "debug": "^4.1.1",
+ "get-stream": "^5.1.0",
+ "yauzl": "^2.10.0"
+ },
+ "bin": {
+ "extract-zip": "cli.js"
+ },
+ "engines": {
+ "node": ">= 10.17.0"
+ },
+ "optionalDependencies": {
+ "@types/yauzl": "^2.9.1"
+ }
+ },
+ "node_modules/extract-zip/node_modules/get-stream": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
+ "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
+ "license": "MIT",
+ "dependencies": {
+ "pump": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "license": "MIT"
+ },
+ "node_modules/fast-uri": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
+ "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/fd-slicer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
+ "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
+ "license": "MIT",
+ "dependencies": {
+ "pend": "~1.2.0"
+ }
+ },
+ "node_modules/fetch-blob": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
+ "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/jimmywarting"
+ },
+ {
+ "type": "paypal",
+ "url": "https://paypal.me/jimmywarting"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "node-domexception": "^1.0.0",
+ "web-streams-polyfill": "^3.0.3"
+ },
+ "engines": {
+ "node": "^12.20 || >= 14.13"
+ }
+ },
+ "node_modules/formdata-polyfill": {
+ "version": "4.0.10",
+ "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
+ "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
+ "license": "MIT",
+ "dependencies": {
+ "fetch-blob": "^3.1.2"
+ },
+ "engines": {
+ "node": ">=12.20.0"
+ }
+ },
+ "node_modules/fs-monkey": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz",
+ "integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==",
+ "license": "Unlicense"
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gaxios": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz",
+ "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "extend": "^3.0.2",
+ "https-proxy-agent": "^7.0.1",
+ "node-fetch": "^3.3.2"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/gcp-metadata": {
+ "version": "8.1.2",
+ "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz",
+ "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "gaxios": "^7.0.0",
+ "google-logging-utils": "^1.0.0",
+ "json-bigint": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/get-stream": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/glob-to-regexp": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
+ "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/google-auth-library": {
+ "version": "10.6.2",
+ "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz",
+ "integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "base64-js": "^1.3.0",
+ "ecdsa-sig-formatter": "^1.0.11",
+ "gaxios": "^7.1.4",
+ "gcp-metadata": "8.1.2",
+ "google-logging-utils": "1.1.3",
+ "jws": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/google-logging-utils": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz",
+ "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/googleapis": {
+ "version": "171.4.0",
+ "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-171.4.0.tgz",
+ "integrity": "sha512-xybFL2SmmUgIifgsbsRQYRdNrSAYwxWZDmkZTGjUIaRnX5jPqR8el/cEvo6rCqh7iaZx6MfEPS/lrDgZ0bymkg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "google-auth-library": "^10.2.0",
+ "googleapis-common": "^8.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/googleapis-common": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-8.0.1.tgz",
+ "integrity": "sha512-eCzNACUXPb1PW5l0ULTzMHaL/ltPRADoPgjBlT8jWsTbxkCp6siv+qKJ/1ldaybCthGwsYFYallF7u9AkU4L+A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "extend": "^3.0.2",
+ "gaxios": "^7.0.0-rc.4",
+ "google-auth-library": "^10.1.0",
+ "qs": "^6.7.0",
+ "url-template": "^2.0.8"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "license": "ISC"
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/html-entities": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz",
+ "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/mdevils"
+ },
+ {
+ "type": "patreon",
+ "url": "https://patreon.com/mdevils"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/human-signals": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
+ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=10.17.0"
+ }
+ },
+ "node_modules/is-docker": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
+ "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
+ "license": "MIT",
+ "bin": {
+ "is-docker": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-stream": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-wsl": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
+ "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
+ "license": "MIT",
+ "dependencies": {
+ "is-docker": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "license": "ISC"
+ },
+ "node_modules/jest-worker": {
+ "version": "27.5.1",
+ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
+ "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "merge-stream": "^2.0.0",
+ "supports-color": "^8.0.0"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ }
+ },
+ "node_modules/json-bigint": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
+ "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "bignumber.js": "^9.0.0"
+ }
+ },
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "license": "MIT"
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/jwa": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
+ "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer-equal-constant-time": "^1.0.1",
+ "ecdsa-sig-formatter": "1.0.11",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/jws": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
+ "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
+ "license": "MIT",
+ "dependencies": {
+ "jwa": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/loader-runner": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz",
+ "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.11.5"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/loader-utils": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
+ "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
+ "license": "MIT",
+ "dependencies": {
+ "big.js": "^5.2.2",
+ "emojis-list": "^3.0.0",
+ "json5": "^2.1.2"
+ },
+ "engines": {
+ "node": ">=8.9.0"
+ }
+ },
+ "node_modules/lodash.sortby": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
+ "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==",
+ "license": "MIT"
+ },
+ "node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/mediabunny": {
+ "version": "1.39.2",
+ "resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.39.2.tgz",
+ "integrity": "sha512-VcrisGRt+OI7tTPrziucJoCIPYIS/DEWY37TqzQVLWSUUHiyvsiRizEypQ3FOlhfIZ4ytAG/Mw4zxfetCTyKUg==",
+ "license": "MPL-2.0",
+ "peer": true,
+ "workspaces": [
+ "packages/*"
+ ],
+ "dependencies": {
+ "@types/dom-mediacapture-transform": "^0.1.11",
+ "@types/dom-webcodecs": "0.1.13"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/sponsors/Vanilagy"
+ }
+ },
+ "node_modules/memfs": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.3.tgz",
+ "integrity": "sha512-eivjfi7Ahr6eQTn44nvTnR60e4a1Fs1Via2kCR5lHo/kyNoiMWaXCNJ/GpSd0ilXas2JSOl9B5FTIhflXu0hlg==",
+ "license": "Unlicense",
+ "dependencies": {
+ "fs-monkey": "1.0.3"
+ },
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "license": "MIT"
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/neo-async": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
+ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
+ "license": "MIT"
+ },
+ "node_modules/node-domexception": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
+ "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
+ "deprecated": "Use your platform's native DOMException instead",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/jimmywarting"
+ },
+ {
+ "type": "github",
+ "url": "https://paypal.me/jimmywarting"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.5.0"
+ }
+ },
+ "node_modules/node-fetch": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
+ "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
+ "license": "MIT",
+ "dependencies": {
+ "data-uri-to-buffer": "^4.0.0",
+ "fetch-blob": "^3.1.4",
+ "formdata-polyfill": "^4.0.10"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/node-fetch"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.37",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz",
+ "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==",
+ "license": "MIT"
+ },
+ "node_modules/npm-run-path": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
+ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "license": "MIT",
+ "dependencies": {
+ "mimic-fn": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/open": {
+ "version": "8.4.2",
+ "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz",
+ "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==",
+ "license": "MIT",
+ "dependencies": {
+ "define-lazy-prop": "^2.0.0",
+ "is-docker": "^2.1.1",
+ "is-wsl": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pend": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
+ "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "license": "ISC"
+ },
+ "node_modules/postcss": {
+ "version": "8.5.1",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz",
+ "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.8",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "license": "MIT"
+ },
+ "node_modules/pump": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
+ "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
+ "license": "MIT",
+ "dependencies": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.15.1",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz",
+ "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.2.5",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz",
+ "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.2.5",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz",
+ "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "scheduler": "^0.27.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.5"
+ }
+ },
+ "node_modules/react-refresh": {
+ "version": "0.18.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz",
+ "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/remotion": {
+ "version": "4.0.448",
+ "resolved": "https://registry.npmjs.org/remotion/-/remotion-4.0.448.tgz",
+ "integrity": "sha512-4NBSmQRIzntZiLEtOU/bGMQXfnGgUsBoyrewdTRyYzPl+qnvlBjqwxcntsu6SDY/W7ucEwKZUTc7GDOcFhIuAA==",
+ "license": "SEE LICENSE IN LICENSE.md",
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/scheduler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "license": "MIT"
+ },
+ "node_modules/schema-utils": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz",
+ "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/json-schema": "^7.0.9",
+ "ajv": "^8.9.0",
+ "ajv-formats": "^2.1.1",
+ "ajv-keywords": "^5.1.0"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/semver": {
+ "version": "7.5.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz",
+ "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==",
+ "license": "ISC",
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
+ "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "license": "ISC"
+ },
+ "node_modules/source-map": {
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
+ "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-support": {
+ "version": "0.5.21",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
+ "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
+ "node_modules/source-map-support/node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/stackframe": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz",
+ "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==",
+ "license": "MIT"
+ },
+ "node_modules/strip-final-newline": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/style-loader": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-4.0.0.tgz",
+ "integrity": "sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 18.12.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "webpack": "^5.27.0"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "node_modules/tapable": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.2.tgz",
+ "integrity": "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/terser": {
+ "version": "5.46.1",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.1.tgz",
+ "integrity": "sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "@jridgewell/source-map": "^0.3.3",
+ "acorn": "^8.15.0",
+ "commander": "^2.20.0",
+ "source-map-support": "~0.5.20"
+ },
+ "bin": {
+ "terser": "bin/terser"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/terser-webpack-plugin": {
+ "version": "5.4.0",
+ "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.4.0.tgz",
+ "integrity": "sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "jest-worker": "^27.4.5",
+ "schema-utils": "^4.3.0",
+ "terser": "^5.31.1"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "webpack": "^5.1.0"
+ },
+ "peerDependenciesMeta": {
+ "@swc/core": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "uglify-js": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tr46": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz",
+ "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==",
+ "license": "MIT",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD",
+ "optional": true
+ },
+ "node_modules/undici-types": {
+ "version": "7.19.2",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz",
+ "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==",
+ "license": "MIT"
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/url-template": {
+ "version": "2.0.8",
+ "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz",
+ "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==",
+ "license": "BSD"
+ },
+ "node_modules/watchpack": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz",
+ "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==",
+ "license": "MIT",
+ "dependencies": {
+ "glob-to-regexp": "^0.4.1",
+ "graceful-fs": "^4.1.2"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/web-streams-polyfill": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
+ "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
+ "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==",
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/webpack": {
+ "version": "5.105.0",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.0.tgz",
+ "integrity": "sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@types/eslint-scope": "^3.7.7",
+ "@types/estree": "^1.0.8",
+ "@types/json-schema": "^7.0.15",
+ "@webassemblyjs/ast": "^1.14.1",
+ "@webassemblyjs/wasm-edit": "^1.14.1",
+ "@webassemblyjs/wasm-parser": "^1.14.1",
+ "acorn": "^8.15.0",
+ "acorn-import-phases": "^1.0.3",
+ "browserslist": "^4.28.1",
+ "chrome-trace-event": "^1.0.2",
+ "enhanced-resolve": "^5.19.0",
+ "es-module-lexer": "^2.0.0",
+ "eslint-scope": "5.1.1",
+ "events": "^3.2.0",
+ "glob-to-regexp": "^0.4.1",
+ "graceful-fs": "^4.2.11",
+ "json-parse-even-better-errors": "^2.3.1",
+ "loader-runner": "^4.3.1",
+ "mime-types": "^2.1.27",
+ "neo-async": "^2.6.2",
+ "schema-utils": "^4.3.3",
+ "tapable": "^2.3.0",
+ "terser-webpack-plugin": "^5.3.16",
+ "watchpack": "^2.5.1",
+ "webpack-sources": "^3.3.3"
+ },
+ "bin": {
+ "webpack": "bin/webpack.js"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependenciesMeta": {
+ "webpack-cli": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/webpack-sources": {
+ "version": "3.3.4",
+ "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.4.tgz",
+ "integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/whatwg-url": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz",
+ "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==",
+ "license": "MIT",
+ "dependencies": {
+ "lodash.sortby": "^4.7.0",
+ "tr46": "^1.0.1",
+ "webidl-conversions": "^4.0.2"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "license": "ISC"
+ },
+ "node_modules/ws": {
+ "version": "8.17.1",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
+ "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "license": "ISC"
+ },
+ "node_modules/yauzl": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
+ "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer-crc32": "~0.2.3",
+ "fd-slicer": "~1.1.0"
+ }
+ },
+ "node_modules/zod": {
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
+ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
+ "license": "MIT",
+ "peer": true,
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..c6c3ca8
--- /dev/null
+++ b/package.json
@@ -0,0 +1,36 @@
+{
+ "name": "mplayer",
+ "version": "1.0.0",
+ "description": "",
+ "scripts": {
+ "phase3:build": "node scripts/phase3-fetch-song-lyrics.js",
+ "phase4:direction": "node scripts/phase4-generate-direction.js",
+ "phase5:render": "node scripts/phase5-render-video.js",
+ "phase5:render:ci": "PHASE5_MAX_SECONDS=30 node scripts/phase5-render-video.js",
+ "phase7:upload": "node scripts/phase7-upload-youtube.js",
+ "phase7:upload:dry": "PHASE7_DRY_RUN=true node scripts/phase7-upload-youtube.js",
+ "render:test": "node scripts/render-test-video.js",
+ "test": "npm run render:test"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/Dcode9/MPLayer.git"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "type": "commonjs",
+ "bugs": {
+ "url": "https://github.com/Dcode9/MPLayer/issues"
+ },
+ "homepage": "https://github.com/Dcode9/MPLayer#readme",
+ "dependencies": {
+ "@fontsource/noto-sans-devanagari": "^5.2.8",
+ "@remotion/bundler": "^4.0.448",
+ "@remotion/renderer": "^4.0.448",
+ "googleapis": "^171.4.0",
+ "react": "^19.2.5",
+ "react-dom": "^19.2.5",
+ "remotion": "^4.0.448"
+ }
+}
diff --git a/public/phase5-audio-source.mp4 b/public/phase5-audio-source.mp4
new file mode 100644
index 0000000..89b8dc6
Binary files /dev/null and b/public/phase5-audio-source.mp4 differ
diff --git a/remotion/LyricsVideo.jsx b/remotion/LyricsVideo.jsx
new file mode 100644
index 0000000..ac3cf5d
--- /dev/null
+++ b/remotion/LyricsVideo.jsx
@@ -0,0 +1,575 @@
+import React from 'react';
+import {AbsoluteFill, Audio, interpolate, staticFile, useCurrentFrame, useVideoConfig} from 'remotion';
+import '@fontsource/noto-sans-devanagari/400.css';
+import '@fontsource/noto-sans-devanagari/700.css';
+
+const DEFAULT_PALETTE = {
+ background: '#0b1021',
+ primary: '#f8fafc',
+ accent: '#22d3ee',
+ secondary: '#f97316',
+};
+
+const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
+
+const asNumber = (value, fallback = 0) => {
+ const parsed = Number(value);
+ return Number.isFinite(parsed) ? parsed : fallback;
+};
+
+const asString = (value, fallback = '') => {
+ if (typeof value === 'string' && value.trim()) {
+ return value.trim();
+ }
+
+ return fallback;
+};
+
+const toFrame = (seconds, fps) => {
+ const secondsNumber = Number(seconds);
+ if (Number.isNaN(secondsNumber)) {
+ return 0;
+ }
+
+ return Math.max(0, Math.round(secondsNumber * fps));
+};
+
+const normalizeColor = (value, fallback) => {
+ const color = asString(value);
+ const isHex = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(color);
+ return isHex ? color.toLowerCase() : fallback;
+};
+
+const hexToRgb = (hex) => {
+ const color = normalizeColor(hex, '#000000');
+ const clean = color.slice(1);
+ const full =
+ clean.length === 3
+ ? `${clean[0]}${clean[0]}${clean[1]}${clean[1]}${clean[2]}${clean[2]}`
+ : clean;
+
+ return {
+ r: parseInt(full.slice(0, 2), 16),
+ g: parseInt(full.slice(2, 4), 16),
+ b: parseInt(full.slice(4, 6), 16),
+ };
+};
+
+const rgba = (hex, alpha = 1) => {
+ const {r, g, b} = hexToRgb(hex);
+ return `rgba(${r}, ${g}, ${b}, ${clamp(alpha, 0, 1)})`;
+};
+
+const shiftColor = (hex, amount) => {
+ const {r, g, b} = hexToRgb(hex);
+ const multiplier = 1 + amount;
+ const nextR = clamp(Math.round(r * multiplier), 0, 255);
+ const nextG = clamp(Math.round(g * multiplier), 0, 255);
+ const nextB = clamp(Math.round(b * multiplier), 0, 255);
+ return `rgb(${nextR}, ${nextG}, ${nextB})`;
+};
+
+const channelLuminance = (value) => {
+ const normalized = value / 255;
+ if (normalized <= 0.03928) {
+ return normalized / 12.92;
+ }
+
+ return ((normalized + 0.055) / 1.055) ** 2.4;
+};
+
+const contrastRatio = (hexA, hexB) => {
+ const a = hexToRgb(normalizeColor(hexA, '#ffffff'));
+ const b = hexToRgb(normalizeColor(hexB, '#000000'));
+
+ const lumaA =
+ 0.2126 * channelLuminance(a.r) +
+ 0.7152 * channelLuminance(a.g) +
+ 0.0722 * channelLuminance(a.b);
+ const lumaB =
+ 0.2126 * channelLuminance(b.r) +
+ 0.7152 * channelLuminance(b.g) +
+ 0.0722 * channelLuminance(b.b);
+
+ const lighter = Math.max(lumaA, lumaB);
+ const darker = Math.min(lumaA, lumaB);
+ return (lighter + 0.05) / (darker + 0.05);
+};
+
+const pickReadableColor = (backgroundHex, preferredHex, fallbackHex = '#f8fafc') => {
+ const preferred = normalizeColor(preferredHex, fallbackHex);
+ const fallback = normalizeColor(fallbackHex, '#f8fafc');
+
+ if (contrastRatio(preferred, backgroundHex) >= 4) {
+ return preferred;
+ }
+
+ if (contrastRatio(fallback, backgroundHex) >= 4) {
+ return fallback;
+ }
+
+ return contrastRatio('#ffffff', backgroundHex) > contrastRatio('#111827', backgroundHex)
+ ? '#ffffff'
+ : '#111827';
+};
+
+const resolveAudioSource = (audio) => {
+ if (!audio || typeof audio !== 'object' || !audio.src) {
+ return null;
+ }
+
+ if (audio.mode === 'static') {
+ return staticFile(audio.src);
+ }
+
+ return audio.src;
+};
+
+const getBeatPulse = ({frame, fps, lines}) => {
+ const tempoBpm = 94;
+ const beatFrames = Math.max(1, Math.round((60 / tempoBpm) * fps));
+ const beatPhase = (frame % beatFrames) / beatFrames;
+ const beatDistance = Math.min(beatPhase, 1 - beatPhase);
+ const basePulse = clamp(1 - beatDistance * 8.4, 0, 1);
+
+ const lyricOnsetWindow = Math.max(4, Math.round(fps * 0.18));
+ let lyricOnsetPulse = 0;
+
+ for (const line of lines) {
+ const start = toFrame(line?.start, fps);
+ const distance = Math.abs(frame - start);
+ if (distance <= lyricOnsetWindow) {
+ lyricOnsetPulse = Math.max(lyricOnsetPulse, 1 - distance / lyricOnsetWindow);
+ }
+ }
+
+ return clamp(basePulse * 0.45 + lyricOnsetPulse * 0.92, 0, 1);
+};
+
+const getPalette = (direction) => {
+ const rawPalette = direction?.palette || {};
+ return {
+ background: normalizeColor(rawPalette.background, DEFAULT_PALETTE.background),
+ primary: normalizeColor(rawPalette.primary, DEFAULT_PALETTE.primary),
+ accent: normalizeColor(rawPalette.accent, DEFAULT_PALETTE.accent),
+ secondary: normalizeColor(rawPalette.secondary, DEFAULT_PALETTE.secondary),
+ };
+};
+
+const findActiveScene = (scenes, currentSeconds) => {
+ if (!Array.isArray(scenes) || scenes.length === 0) {
+ return null;
+ }
+
+ const inRange = scenes.find((scene) => {
+ const start = asNumber(scene?.start, 0);
+ const end = Math.max(start + 0.2, asNumber(scene?.end, start + 1));
+ return currentSeconds >= start && currentSeconds < end;
+ });
+
+ return inRange || scenes[0] || null;
+};
+
+const sceneBackground = ({scene, palette, beatPulse, timeSeconds}) => {
+ const treatment = asString(scene?.backgroundTreatment, '').toLowerCase();
+ const sceneColor = normalizeColor(scene?.color, palette.primary);
+ const drift = Math.sin(timeSeconds * 0.9) * 0.08;
+ const beatBoost = beatPulse * 0.17;
+ const layerA = shiftColor(palette.background, -0.24 + drift + beatBoost * 0.35);
+ const layerB = shiftColor(palette.accent, -0.24 + beatBoost);
+ const layerC = shiftColor(palette.secondary, -0.3 + beatBoost * 0.7);
+
+ const xA = 18 + Math.sin(timeSeconds * 0.58) * 11 + beatPulse * 10;
+ const yA = 22 + Math.cos(timeSeconds * 0.74) * 8;
+ const xB = 79 + Math.cos(timeSeconds * 0.7) * 12 - beatPulse * 7;
+ const yB = 74 + Math.sin(timeSeconds * 0.62) * 9;
+
+ const baseLayer = `radial-gradient(circle at ${xA}% ${yA}%, ${rgba(layerB, 0.31)} 0%, transparent 45%), radial-gradient(circle at ${xB}% ${yB}%, ${rgba(layerC, 0.27)} 0%, transparent 42%)`;
+
+ if (treatment.includes('aurora')) {
+ return `${baseLayer}, linear-gradient(140deg, ${layerA} 0%, ${layerB} 54%, ${shiftColor(layerC, -0.06)} 100%)`;
+ }
+
+ if (treatment.includes('watercolor')) {
+ return `${baseLayer}, linear-gradient(135deg, ${shiftColor(sceneColor, -0.25 + beatBoost * 0.4)} 0%, ${shiftColor(
+ palette.background,
+ 0.06
+ )} 45%, ${shiftColor(palette.background, -0.1)} 100%)`;
+ }
+
+ if (treatment.includes('fiery')) {
+ return `${baseLayer}, linear-gradient(150deg, ${shiftColor('#ff6b2d', -0.45 + beatBoost * 0.45)} 0%, ${shiftColor(
+ '#ffbe0b',
+ -0.55 + beatBoost * 0.25
+ )} 45%, ${shiftColor(palette.background, -0.25)} 100%)`;
+ }
+
+ if (treatment.includes('night')) {
+ return `${baseLayer}, linear-gradient(145deg, ${shiftColor(palette.background, -0.35)} 0%, ${shiftColor(
+ palette.background,
+ -0.15 + beatBoost * 0.2
+ )} 60%, ${shiftColor(sceneColor, -0.25)} 100%)`;
+ }
+
+ if (treatment.includes('dusk')) {
+ return `${baseLayer}, linear-gradient(155deg, ${shiftColor('#f97316', -0.2 + beatBoost * 0.25)} 0%, ${shiftColor(
+ '#ec4899',
+ -0.2 + beatBoost * 0.2
+ )} 35%, ${shiftColor(palette.background, -0.25)} 100%)`;
+ }
+
+ return `${baseLayer}, linear-gradient(140deg, ${layerA} 0%, ${shiftColor(palette.background, -0.14)} 62%, ${shiftColor(
+ sceneColor,
+ -0.31
+ )} 100%)`;
+};
+
+const buildOrbStyles = ({timeSeconds, beatPulse, palette}) => {
+ const accent = normalizeColor(palette.accent, DEFAULT_PALETTE.accent);
+ const secondary = normalizeColor(palette.secondary, DEFAULT_PALETTE.secondary);
+
+ return [
+ {
+ width: 520,
+ height: 520,
+ left: -130 + Math.sin(timeSeconds * 0.65) * 35,
+ top: -120 + Math.cos(timeSeconds * 0.58) * 28,
+ background: `radial-gradient(circle, ${rgba(accent, 0.33 + beatPulse * 0.16)} 0%, ${rgba(accent, 0)} 68%)`,
+ transform: `scale(${1 + beatPulse * 0.08})`,
+ },
+ {
+ width: 460,
+ height: 460,
+ right: -100 + Math.cos(timeSeconds * 0.53) * 40,
+ bottom: -115 + Math.sin(timeSeconds * 0.49) * 32,
+ background: `radial-gradient(circle, ${rgba(secondary, 0.3 + beatPulse * 0.14)} 0%, ${rgba(secondary, 0)} 70%)`,
+ transform: `scale(${1 + beatPulse * 0.09})`,
+ },
+ {
+ width: 360,
+ height: 360,
+ left: '50%',
+ top: '14%',
+ marginLeft: -180 + Math.sin(timeSeconds * 0.48) * 26,
+ background: `radial-gradient(circle, ${rgba('#ffffff', 0.06 + beatPulse * 0.08)} 0%, ${rgba('#ffffff', 0)} 75%)`,
+ transform: `scale(${1 + beatPulse * 0.06})`,
+ },
+ ];
+};
+
+const parseDirectionalOffset = (descriptor, baseDistance, axis) => {
+ const raw = asString(descriptor, '').toLowerCase();
+ if (!raw) {
+ return 0;
+ }
+
+ if (axis === 'x') {
+ if (raw.includes('left')) return -baseDistance;
+ if (raw.includes('right')) return baseDistance;
+ return 0;
+ }
+
+ if (raw.includes('up')) return -baseDistance;
+ if (raw.includes('down')) return baseDistance;
+ return 0;
+};
+
+const resolveSceneByLineIndex = (scenes) => {
+ const map = new Map();
+ if (!Array.isArray(scenes)) {
+ return map;
+ }
+
+ for (const scene of scenes) {
+ const lineIndices = Array.isArray(scene?.lineIndices) ? scene.lineIndices : [];
+ for (const rawIndex of lineIndices) {
+ const index = asNumber(rawIndex, -1);
+ if (index >= 0 && !map.has(index)) {
+ map.set(index, scene);
+ }
+ }
+ }
+
+ return map;
+};
+
+const resolveDirectionForLine = ({lineIndex, directionMap, sceneByLine, palette}) => {
+ const lineDirection = directionMap.get(lineIndex) || null;
+ const scene = sceneByLine.get(lineIndex) || null;
+
+ return {
+ entrance: asString(lineDirection?.entrance, asString(scene?.entrance, 'fade up')),
+ exit: asString(lineDirection?.exit, asString(scene?.exit, 'soft fade')),
+ emphasis: asString(lineDirection?.emphasis, `match scene mood ${asString(scene?.mood, 'lyrical')}`),
+ color: normalizeColor(lineDirection?.color, normalizeColor(scene?.color, palette.primary)),
+ motionIntensity: clamp(
+ asNumber(lineDirection?.motionIntensity, asNumber(scene?.motionIntensity, 0.45)),
+ 0,
+ 1
+ ),
+ };
+};
+
+const emphasisStrength = (emphasis, motionIntensity) => {
+ const text = asString(emphasis, '').toLowerCase();
+ let factor = 0.025 + motionIntensity * 0.05;
+
+ if (text.includes('intense') || text.includes('epic') || text.includes('climax')) {
+ factor += 0.05;
+ }
+
+ if (text.includes('subtle') || text.includes('soft')) {
+ factor -= 0.01;
+ }
+
+ return clamp(factor, 0.01, 0.14);
+};
+
+export const LyricsVideo = ({lines = [], direction = {}, audio = null}) => {
+ const frame = useCurrentFrame();
+ const {fps} = useVideoConfig();
+ const currentSeconds = frame / fps;
+ const beatPulse = getBeatPulse({frame, fps, lines});
+
+ const palette = getPalette(direction);
+ const sceneDirections = Array.isArray(direction?.sceneDirections) ? direction.sceneDirections : [];
+ const activeScene = findActiveScene(sceneDirections, currentSeconds);
+ const directionMap = new Map(
+ (Array.isArray(direction?.lineDirections) ? direction.lineDirections : [])
+ .map((entry) => [asNumber(entry?.lineIndex, -1), entry])
+ .filter(([index]) => index >= 0)
+ );
+ const sceneByLine = resolveSceneByLineIndex(sceneDirections);
+
+ const visibleLines = lines
+ .map((line, lineIndex) => {
+ const start = toFrame(line.start, fps);
+ const end = Math.max(start + 1, toFrame(line.end, fps));
+
+ return {
+ line,
+ lineIndex,
+ start,
+ end,
+ };
+ })
+ .filter((item) => frame >= item.start && frame <= item.end);
+
+ const background = sceneBackground({
+ scene: activeScene,
+ palette,
+ beatPulse,
+ timeSeconds: currentSeconds,
+ });
+
+ const overlayOrbs = buildOrbStyles({
+ timeSeconds: currentSeconds,
+ beatPulse,
+ palette,
+ });
+
+ const audioSrc = resolveAudioSource(audio);
+
+ const beatGlow = `radial-gradient(circle at ${50 + Math.sin(currentSeconds * 1.7) * 15}% ${52 + Math.cos(
+ currentSeconds * 1.3
+ ) * 14}%, ${rgba(palette.accent, 0.12 + beatPulse * 0.26)} 0%, transparent 54%)`;
+
+ const readablePrimary = pickReadableColor(palette.background, palette.primary, '#f8fafc');
+ const panelBase = normalizeColor('#111827', '#111827');
+
+ return (
+
+ {audioSrc ? : null}
+
+
+
+ {overlayOrbs.map((orbStyle, index) => {
+ return (
+
+ );
+ })}
+
+
+
+ {visibleLines.map((item, visibleIndex) => {
+ const {line, lineIndex, start, end} = item;
+ const durationFrames = Math.max(1, end - start);
+ const directionForLine = resolveDirectionForLine({
+ lineIndex,
+ directionMap,
+ sceneByLine,
+ palette,
+ });
+
+ const intensity = directionForLine.motionIntensity;
+ const fadeFrames = Math.min(Math.max(6, Math.round(10 + intensity * 10)), Math.floor(durationFrames / 2));
+ const enterEnd = Math.min(end, start + fadeFrames);
+ const exitStart = Math.max(start, end - fadeFrames);
+
+ const opacity = interpolate(frame, [start, enterEnd, exitStart, end], [0, 1, 1, 0], {
+ extrapolateLeft: 'clamp',
+ extrapolateRight: 'clamp',
+ });
+
+ const entranceDistance = 34 + intensity * 90;
+ const exitDistance = 26 + intensity * 110;
+ const enterXStart = parseDirectionalOffset(directionForLine.entrance, entranceDistance, 'x');
+ const enterYStart = parseDirectionalOffset(directionForLine.entrance, entranceDistance, 'y');
+ const exitXEnd = parseDirectionalOffset(directionForLine.exit, exitDistance, 'x');
+ const exitYEnd = parseDirectionalOffset(directionForLine.exit, exitDistance, 'y');
+
+ const enterX = interpolate(frame, [start, enterEnd], [enterXStart, 0], {
+ extrapolateLeft: 'clamp',
+ extrapolateRight: 'clamp',
+ });
+ const enterY = interpolate(frame, [start, enterEnd], [enterYStart, 0], {
+ extrapolateLeft: 'clamp',
+ extrapolateRight: 'clamp',
+ });
+ const exitX = interpolate(frame, [exitStart, end], [0, exitXEnd], {
+ extrapolateLeft: 'clamp',
+ extrapolateRight: 'clamp',
+ });
+ const exitY = interpolate(frame, [exitStart, end], [0, exitYEnd], {
+ extrapolateLeft: 'clamp',
+ extrapolateRight: 'clamp',
+ });
+
+ const localProgress = durationFrames > 0 ? (frame - start) / durationFrames : 0;
+ const pulseAmount = emphasisStrength(directionForLine.emphasis, intensity);
+ const pulse = 1 + Math.sin(localProgress * Math.PI) * pulseAmount;
+
+ let baseScale = interpolate(frame, [start, enterEnd], [0.88 - intensity * 0.2, 1], {
+ extrapolateLeft: 'clamp',
+ extrapolateRight: 'clamp',
+ });
+
+ const exitDescriptor = directionForLine.exit.toLowerCase();
+ if (exitDescriptor.includes('explode')) {
+ baseScale = interpolate(frame, [exitStart, end], [baseScale, 1.08 + intensity * 0.18], {
+ extrapolateLeft: 'clamp',
+ extrapolateRight: 'clamp',
+ });
+ }
+
+ const stackOffset = (visibleIndex - (visibleLines.length - 1) / 2) * (64 + intensity * 32);
+ const floatOffset = Math.sin((frame - start) / (fps * 0.68)) * (2 + intensity * 6);
+ const fontSize = Math.round(58 + intensity * 18);
+ const sweepProgress = interpolate(frame, [start, enterEnd], [0, 1], {
+ extrapolateLeft: 'clamp',
+ extrapolateRight: 'clamp',
+ });
+ const lineColor = pickReadableColor(panelBase, directionForLine.color, readablePrimary);
+ const panelTranslateY = enterY + exitY + stackOffset + floatOffset;
+
+ return (
+
+
+
+
+
+
+ {line.text}
+
+
+ );
+ })}
+
+ );
+};
diff --git a/remotion/Root.jsx b/remotion/Root.jsx
new file mode 100644
index 0000000..bfe07f7
--- /dev/null
+++ b/remotion/Root.jsx
@@ -0,0 +1,72 @@
+import React from 'react';
+import {Composition} from 'remotion';
+import {LyricsVideo} from './LyricsVideo';
+
+const defaultProps = {
+ song: {
+ name: 'Test Song',
+ artist: 'Unknown Artist',
+ },
+ lines: [
+ {text: 'Hello world', start: 0, end: 2},
+ {text: 'This is a test video', start: 2, end: 5},
+ ],
+ audio: null,
+ direction: {
+ styleDescription: 'Readable cinematic typography with subtle motion.',
+ palette: {
+ background: '#0f172a',
+ primary: '#f8fafc',
+ accent: '#22d3ee',
+ secondary: '#f59e0b',
+ },
+ animationIdeas: ['Gentle fade-up entrances', 'Mild push-in emphasis', 'Soft exits'],
+ sceneDirections: [],
+ lineDirections: [],
+ },
+};
+
+const calculateDurationInFrames = (props, fps) => {
+ const lines = Array.isArray(props?.lines) ? props.lines : [];
+ const maxLineEnd = lines.reduce((maxValue, line) => {
+ const end = Number(line?.end);
+ if (!Number.isFinite(end)) {
+ return maxValue;
+ }
+
+ return Math.max(maxValue, end);
+ }, 0);
+
+ const songDuration = Number(props?.song?.duration);
+ const explicitDuration = Number(props?.durationInSeconds);
+ const hasExplicitDuration = Number.isFinite(explicitDuration) && explicitDuration > 0;
+ const baseSeconds = hasExplicitDuration
+ ? Math.max(5, explicitDuration)
+ : Math.max(maxLineEnd, Number.isFinite(songDuration) ? songDuration : 0, 5);
+
+ return Math.max(150, Math.ceil((baseSeconds + 0.5) * fps));
+};
+
+export const RemotionRoot = () => {
+ return (
+ {
+ const mergedProps = {
+ ...metadataDefaults,
+ ...props,
+ };
+
+ return {
+ durationInFrames: calculateDurationInFrames(mergedProps, 30),
+ };
+ }}
+ />
+ );
+};
diff --git a/remotion/index.jsx b/remotion/index.jsx
new file mode 100644
index 0000000..d831f7b
--- /dev/null
+++ b/remotion/index.jsx
@@ -0,0 +1,4 @@
+import {registerRoot} from 'remotion';
+import {RemotionRoot} from './Root';
+
+registerRoot(RemotionRoot);
diff --git a/scripts/lib/ai-director.js b/scripts/lib/ai-director.js
new file mode 100644
index 0000000..3c0e070
--- /dev/null
+++ b/scripts/lib/ai-director.js
@@ -0,0 +1,551 @@
+const DEFAULT_TIMEOUT_MS = Number(process.env.PHASE4_HTTP_TIMEOUT_MS || 60000);
+const MAX_AI_RETRIES = Number(process.env.PHASE4_AI_RETRIES || 2);
+const POLLINATIONS_OPENAI_URL =
+ process.env.POLLINATIONS_OPENAI_URL || 'https://text.pollinations.ai/openai';
+const POLLINATIONS_API_KEY = process.env.POLLINATIONS_API_KEY || process.env.POLLINATIONS_API || '';
+const POLLINATIONS_GITHUB_MODEL = process.env.POLLINATIONS_GITHUB_MODEL || 'github:gpt-4o-mini';
+const POLLINATIONS_FALLBACK_MODEL = process.env.POLLINATIONS_FALLBACK_MODEL || 'openai-fast';
+const GLM5_API_BASE_URL = process.env.GLM5_API_BASE_URL || '';
+const GLM5_API_KEY = process.env.GLM5_API_KEY || '';
+const GLM5_MODEL = process.env.GLM5_MODEL || 'glm-5';
+const STRICT_GLM_FALLBACK =
+ String(process.env.PHASE4_STRICT_GLM_FALLBACK || '').toLowerCase() === 'true';
+
+const DEFAULT_PALETTE = {
+ background: '#0f172a',
+ primary: '#f8fafc',
+ accent: '#22d3ee',
+ secondary: '#f59e0b',
+};
+
+const DEFAULT_ANIMATION_IDEAS = [
+ 'Use kinetic typography that accelerates at emotional peaks.',
+ 'Blend gentle background parallax with lyric-driven transitions.',
+ 'Emphasize repeated hooks with slightly stronger scale pulses.',
+];
+
+const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
+const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
+
+const asString = (value, fallback = '') => {
+ if (typeof value === 'string' && value.trim()) {
+ return value.trim();
+ }
+
+ return fallback;
+};
+
+const asNumber = (value, fallback = 0) => {
+ const parsed = Number(value);
+ return Number.isFinite(parsed) ? parsed : fallback;
+};
+
+const ensureHexColor = (value, fallback) => {
+ const color = asString(value);
+ const hexRegex = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
+ return hexRegex.test(color) ? color.toLowerCase() : fallback;
+};
+
+const resolveChatCompletionUrl = (baseUrl) => {
+ const cleaned = asString(baseUrl).replace(/\/+$/, '');
+ if (!cleaned) {
+ throw new Error('Missing API base URL for chat completion request.');
+ }
+
+ if (cleaned.endsWith('/chat/completions') || cleaned.endsWith('/openai')) {
+ return cleaned;
+ }
+
+ return `${cleaned}/chat/completions`;
+};
+
+const fetchWithTimeout = async (url, options, timeoutMs = DEFAULT_TIMEOUT_MS) => {
+ const controller = new AbortController();
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
+
+ try {
+ return await fetch(url, {...options, signal: controller.signal});
+ } finally {
+ clearTimeout(timeout);
+ }
+};
+
+const extractContentFromResponse = (payload) => {
+ const firstChoice = payload?.choices?.[0]?.message;
+ if (!firstChoice) {
+ return '';
+ }
+
+ if (typeof firstChoice.content === 'string') {
+ return firstChoice.content;
+ }
+
+ if (Array.isArray(firstChoice.content)) {
+ return firstChoice.content
+ .map((item) => (typeof item === 'string' ? item : item?.text || ''))
+ .join('');
+ }
+
+ if (typeof firstChoice.reasoning_content === 'string') {
+ return firstChoice.reasoning_content;
+ }
+
+ return '';
+};
+
+const callOpenAICompatible = async ({url, apiKey, model, messages, temperature = 0.2, maxTokens = 3500}) => {
+ const endpoint = resolveChatCompletionUrl(url);
+ const headers = {
+ 'Content-Type': 'application/json',
+ };
+
+ if (apiKey) {
+ headers.Authorization = `Bearer ${apiKey}`;
+ }
+
+ let lastError;
+
+ for (let attempt = 1; attempt <= MAX_AI_RETRIES + 1; attempt += 1) {
+ try {
+ const response = await fetchWithTimeout(endpoint, {
+ method: 'POST',
+ headers,
+ body: JSON.stringify({
+ model,
+ messages,
+ temperature,
+ max_tokens: maxTokens,
+ }),
+ });
+
+ const bodyText = await response.text();
+ let payload;
+
+ try {
+ payload = JSON.parse(bodyText);
+ } catch (error) {
+ throw new Error(`AI provider returned non-JSON response: ${bodyText.slice(0, 240)}`);
+ }
+
+ if (!response.ok || payload.error) {
+ const providerMessage =
+ payload?.error?.message || payload?.error || bodyText.slice(0, 240) || `HTTP ${response.status}`;
+ throw new Error(String(providerMessage));
+ }
+
+ const content = extractContentFromResponse(payload);
+ if (!content) {
+ throw new Error('AI provider returned an empty completion message.');
+ }
+
+ return {
+ content,
+ model: payload.model || model,
+ };
+ } catch (error) {
+ lastError = error;
+ if (attempt > MAX_AI_RETRIES) {
+ break;
+ }
+
+ await sleep(attempt * 1200);
+ }
+ }
+
+ throw lastError;
+};
+
+const extractJsonObject = (content) => {
+ const trimmed = content.trim();
+
+ const fenceMatch = trimmed.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/i);
+ if (fenceMatch) {
+ return JSON.parse(fenceMatch[1]);
+ }
+
+ try {
+ return JSON.parse(trimmed);
+ } catch (error) {
+ const start = trimmed.indexOf('{');
+ const end = trimmed.lastIndexOf('}');
+ if (start === -1 || end === -1 || end <= start) {
+ throw error;
+ }
+
+ const candidate = trimmed.slice(start, end + 1);
+ return JSON.parse(candidate);
+ }
+};
+
+const probePollinationsGithubModel = async () => {
+ try {
+ await callOpenAICompatible({
+ url: POLLINATIONS_OPENAI_URL,
+ apiKey: POLLINATIONS_API_KEY,
+ model: POLLINATIONS_GITHUB_MODEL,
+ temperature: 0,
+ maxTokens: 32,
+ messages: [
+ {
+ role: 'user',
+ content: 'Return strict JSON only: {"supported":true}',
+ },
+ ],
+ });
+
+ return {
+ supported: true,
+ reason: 'Pollinations accepted GitHub model identifier.',
+ };
+ } catch (error) {
+ const message = String(error.message || error);
+ if (message.toLowerCase().includes('model not found') || message.includes('404')) {
+ return {
+ supported: false,
+ reason: message,
+ };
+ }
+
+ return {
+ supported: false,
+ reason: `GitHub-model probe failed: ${message}`,
+ };
+ }
+};
+
+const buildDirectionPrompt = ({song, lines}) => {
+ const compactLines = lines.slice(0, 12).map((line, index) => ({
+ lineIndex: index,
+ text: line.text,
+ start: line.start,
+ end: line.end,
+ }));
+
+ const desiredSceneCount = clamp(Math.ceil(lines.length / 6), 3, 8);
+
+ return [
+ 'You are a motion graphics director for an animated lyrics video.',
+ 'Return valid JSON only with no markdown.',
+ 'The JSON must follow this exact shape:',
+ '{',
+ ' "styleDescription": "string",',
+ ' "palette": {"background":"#hex","primary":"#hex","accent":"#hex","secondary":"#hex"},',
+ ' "animationIdeas": ["string", "string", "string"],',
+ ' "sceneDirections": [',
+ ' {"sceneId":"scene-1","lineStartIndex":0,"lineEndIndex":5,"mood":"string","cameraMotion":"string","backgroundTreatment":"string","entrance":"string","exit":"string","color":"#hex","motionIntensity":0.5}',
+ ' ]',
+ '}',
+ 'Constraints:',
+ '- Keep colors readable for lyrics videos.',
+ '- motionIntensity must be between 0 and 1 inclusive.',
+ `- Return ${desiredSceneCount} sceneDirections covering all lyric lines.`,
+ '- lineStartIndex and lineEndIndex must be valid indexes for the full lyrics list.',
+ '- sceneIds should be unique.',
+ '',
+ `Song: ${song?.name || 'Unknown Song'} by ${song?.artist || 'Unknown Artist'}`,
+ `Duration: ${song?.duration || 0} seconds`,
+ `Total lyric lines: ${lines.length}`,
+ `Sample lyric lines JSON (first ${compactLines.length} lines): ${JSON.stringify(compactLines)}`,
+ ].join('\n');
+};
+
+const autoGenerateScenes = (lines, chunkSize = 6) => {
+ const scenes = [];
+ let sceneCounter = 1;
+
+ for (let i = 0; i < lines.length; i += chunkSize) {
+ const slice = lines.slice(i, i + chunkSize);
+ if (slice.length === 0) {
+ continue;
+ }
+
+ scenes.push({
+ sceneId: `scene-${sceneCounter}`,
+ start: asNumber(slice[0].start, 0),
+ end: asNumber(slice[slice.length - 1].end, asNumber(slice[0].end, 0)),
+ mood: 'lyric-driven cinematic energy',
+ cameraMotion: 'subtle push-in with gentle drift',
+ backgroundTreatment: 'soft gradient with grain texture',
+ lineIndices: slice.map((_, index) => i + index),
+ });
+
+ sceneCounter += 1;
+ }
+
+ return scenes;
+};
+
+const normalizeDirection = (rawDirection, lines) => {
+ if (!rawDirection || typeof rawDirection !== 'object' || Array.isArray(rawDirection)) {
+ throw new Error('Direction payload is not a JSON object.');
+ }
+
+ const palette = {
+ background: ensureHexColor(rawDirection.palette?.background, DEFAULT_PALETTE.background),
+ primary: ensureHexColor(rawDirection.palette?.primary, DEFAULT_PALETTE.primary),
+ accent: ensureHexColor(rawDirection.palette?.accent, DEFAULT_PALETTE.accent),
+ secondary: ensureHexColor(rawDirection.palette?.secondary, DEFAULT_PALETTE.secondary),
+ };
+
+ const ideas = Array.isArray(rawDirection.animationIdeas)
+ ? rawDirection.animationIdeas.map((item) => asString(item)).filter(Boolean)
+ : [];
+
+ let sceneDirections = [];
+ if (Array.isArray(rawDirection.sceneDirections)) {
+ sceneDirections = rawDirection.sceneDirections
+ .map((scene, idx) => {
+ let lineIndices = [];
+
+ if (Array.isArray(scene?.lineIndices)) {
+ lineIndices = scene.lineIndices
+ .map((lineIndex) => asNumber(lineIndex, -1))
+ .filter((lineIndex) => lineIndex >= 0 && lineIndex < lines.length);
+ }
+
+ if (lineIndices.length === 0) {
+ const startIndex = clamp(asNumber(scene?.lineStartIndex, 0), 0, Math.max(lines.length - 1, 0));
+ const endIndex = clamp(
+ asNumber(scene?.lineEndIndex, startIndex),
+ startIndex,
+ Math.max(lines.length - 1, startIndex)
+ );
+
+ lineIndices = [];
+ for (let index = startIndex; index <= endIndex; index += 1) {
+ lineIndices.push(index);
+ }
+ }
+
+ const startFromLines =
+ lineIndices.length > 0
+ ? Math.min(...lineIndices.map((lineIndex) => asNumber(lines[lineIndex].start, 0)))
+ : 0;
+ const endFromLines =
+ lineIndices.length > 0
+ ? Math.max(...lineIndices.map((lineIndex) => asNumber(lines[lineIndex].end, startFromLines + 2)))
+ : startFromLines + 2;
+
+ const start = asNumber(scene?.start, startFromLines);
+ const end = Math.max(start + 0.5, asNumber(scene?.end, endFromLines));
+
+ return {
+ sceneId: asString(scene?.sceneId, `scene-${idx + 1}`),
+ start,
+ end,
+ mood: asString(scene?.mood, 'lyrical and cinematic'),
+ cameraMotion: asString(scene?.cameraMotion, 'gentle push-in'),
+ backgroundTreatment: asString(scene?.backgroundTreatment, 'gradient + texture'),
+ entrance: asString(scene?.entrance, 'fade-up'),
+ exit: asString(scene?.exit, 'soft fade-out'),
+ color: ensureHexColor(scene?.color, palette.primary),
+ motionIntensity: clamp(asNumber(scene?.motionIntensity, 0.45), 0, 1),
+ lineIndices,
+ };
+ })
+ .filter((scene) => scene.lineIndices.length > 0);
+ }
+
+ if (sceneDirections.length === 0) {
+ sceneDirections = autoGenerateScenes(lines);
+ }
+
+ const sceneLineHints = new Map();
+ for (const scene of sceneDirections) {
+ for (const lineIndex of scene.lineIndices) {
+ sceneLineHints.set(lineIndex, {
+ entrance: scene.entrance || 'fade-up',
+ exit: scene.exit || 'soft fade-out',
+ color: ensureHexColor(scene.color, palette.primary),
+ motionIntensity: clamp(asNumber(scene.motionIntensity, 0.45), 0, 1),
+ emphasis: `Match scene mood: ${scene.mood}`,
+ });
+ }
+ }
+
+ const byLineIndex = new Map();
+ if (Array.isArray(rawDirection.lineDirections)) {
+ for (const item of rawDirection.lineDirections) {
+ const index = asNumber(item?.lineIndex, -1);
+ if (index < 0 || index >= lines.length) {
+ continue;
+ }
+
+ byLineIndex.set(index, {
+ lineIndex: index,
+ entrance: asString(item.entrance, sceneLineHints.get(index)?.entrance || 'fade-up'),
+ emphasis: asString(item.emphasis, 'highlight the lyrical keyword'),
+ exit: asString(item.exit, sceneLineHints.get(index)?.exit || 'soft fade-out'),
+ color: ensureHexColor(item.color, sceneLineHints.get(index)?.color || palette.primary),
+ motionIntensity: clamp(
+ asNumber(item.motionIntensity, sceneLineHints.get(index)?.motionIntensity || 0.45),
+ 0,
+ 1
+ ),
+ });
+ }
+ }
+
+ const lineDirections = lines.map((line, index) => {
+ if (byLineIndex.has(index)) {
+ return byLineIndex.get(index);
+ }
+
+ return {
+ lineIndex: index,
+ entrance: sceneLineHints.get(index)?.entrance || 'fade-up',
+ emphasis: sceneLineHints.get(index)?.emphasis || 'keep readability with mild scale emphasis',
+ exit: sceneLineHints.get(index)?.exit || 'soft fade-out',
+ color: sceneLineHints.get(index)?.color || palette.primary,
+ motionIntensity: sceneLineHints.get(index)?.motionIntensity || 0.4,
+ };
+ });
+
+ return {
+ styleDescription: asString(
+ rawDirection.styleDescription,
+ 'Cinematic lyric typography with expressive but readable motion and high contrast colors.'
+ ),
+ palette,
+ animationIdeas: ideas.length > 0 ? ideas : DEFAULT_ANIMATION_IDEAS,
+ sceneDirections,
+ lineDirections,
+ };
+};
+
+const requestDirection = async ({provider, model, apiUrl, apiKey, song, lines}) => {
+ const prompt = buildDirectionPrompt({song, lines});
+
+ const firstResponse = await callOpenAICompatible({
+ url: apiUrl,
+ apiKey,
+ model,
+ messages: [
+ {
+ role: 'system',
+ content:
+ 'You create JSON-only motion direction for animated lyric videos. Do not add markdown or prose outside JSON.',
+ },
+ {
+ role: 'user',
+ content: prompt,
+ },
+ ],
+ temperature: 0.45,
+ maxTokens: 1200,
+ });
+
+ try {
+ const parsed = extractJsonObject(firstResponse.content);
+ return {
+ provider,
+ model: firstResponse.model,
+ parsed,
+ };
+ } catch (error) {
+ const repairPrompt = [
+ 'Repair the following content into strict valid JSON only.',
+ 'Do not change meaning. Keep all keys from the target schema.',
+ `Broken content: ${firstResponse.content}`,
+ ].join('\n');
+
+ const repairResponse = await callOpenAICompatible({
+ url: apiUrl,
+ apiKey,
+ model,
+ messages: [
+ {
+ role: 'system',
+ content: 'You output strict JSON only.',
+ },
+ {
+ role: 'user',
+ content: repairPrompt,
+ },
+ ],
+ temperature: 0,
+ maxTokens: 1400,
+ });
+
+ const parsed = extractJsonObject(repairResponse.content);
+ return {
+ provider,
+ model: repairResponse.model,
+ parsed,
+ };
+ }
+};
+
+const selectProvider = async () => {
+ const githubProbe = await probePollinationsGithubModel();
+
+ if (githubProbe.supported) {
+ return {
+ selectedProvider: 'pollinations-github-model',
+ model: POLLINATIONS_GITHUB_MODEL,
+ apiUrl: POLLINATIONS_OPENAI_URL,
+ apiKey: POLLINATIONS_API_KEY,
+ githubModelSupported: true,
+ fallbackReason: '',
+ };
+ }
+
+ if (GLM5_API_BASE_URL) {
+ return {
+ selectedProvider: 'glm5-fallback',
+ model: GLM5_MODEL,
+ apiUrl: GLM5_API_BASE_URL,
+ apiKey: GLM5_API_KEY,
+ githubModelSupported: false,
+ fallbackReason: githubProbe.reason,
+ };
+ }
+
+ if (STRICT_GLM_FALLBACK) {
+ throw new Error(
+ 'Pollinations GitHub model is unavailable and GLM-5 fallback is not configured. Set GLM5_API_BASE_URL and GLM5_API_KEY (if required).'
+ );
+ }
+
+ return {
+ selectedProvider: 'pollinations-dev-fallback',
+ model: POLLINATIONS_FALLBACK_MODEL,
+ apiUrl: POLLINATIONS_OPENAI_URL,
+ apiKey: POLLINATIONS_API_KEY,
+ githubModelSupported: false,
+ fallbackReason: `${githubProbe.reason} | GLM5_API_BASE_URL not set; using development fallback model.`,
+ };
+};
+
+const generateAnimationDirection = async ({song, lines}) => {
+ if (!Array.isArray(lines) || lines.length === 0) {
+ throw new Error('Cannot generate direction without lyric lines.');
+ }
+
+ const providerConfig = await selectProvider();
+ const directionResponse = await requestDirection({
+ provider: providerConfig.selectedProvider,
+ model: providerConfig.model,
+ apiUrl: providerConfig.apiUrl,
+ apiKey: providerConfig.apiKey,
+ song,
+ lines,
+ });
+
+ const direction = normalizeDirection(directionResponse.parsed, lines);
+
+ return {
+ direction,
+ providerReport: {
+ provider: providerConfig.selectedProvider,
+ modelRequested: providerConfig.model,
+ modelResolved: directionResponse.model,
+ githubModelSupported: providerConfig.githubModelSupported,
+ fallbackReason: providerConfig.fallbackReason || null,
+ },
+ };
+};
+
+module.exports = {
+ generateAnimationDirection,
+};
diff --git a/scripts/lib/jiosaavn-client.js b/scripts/lib/jiosaavn-client.js
new file mode 100644
index 0000000..9ece75f
--- /dev/null
+++ b/scripts/lib/jiosaavn-client.js
@@ -0,0 +1,278 @@
+const DEFAULT_JIOSAAVN_ENDPOINTS = [
+ 'https://jiosaavn-api-taupe-phi.vercel.app/api',
+ 'https://jiosaavn-api-v2.vercel.app/api',
+ 'https://saavn.me/api',
+ 'https://jio-saavn-api-red.vercel.app/api',
+];
+
+const REQUEST_TIMEOUT_MS = Number(process.env.PHASE3_HTTP_TIMEOUT_MS || 12000);
+const RETRIES_PER_ENDPOINT = Number(process.env.PHASE3_JIOSAAVN_RETRIES || 2);
+
+const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
+
+const getEndpoints = () => {
+ const custom = process.env.JIOSAAVN_API_ENDPOINTS;
+ if (!custom) {
+ return DEFAULT_JIOSAAVN_ENDPOINTS;
+ }
+
+ const parsed = custom
+ .split(',')
+ .map((endpoint) => endpoint.trim())
+ .filter(Boolean);
+
+ return parsed.length > 0 ? parsed : DEFAULT_JIOSAAVN_ENDPOINTS;
+};
+
+const fetchJson = async (url) => {
+ const controller = new AbortController();
+ const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
+
+ try {
+ const response = await fetch(url, {signal: controller.signal});
+ if (!response.ok) {
+ const body = await response.text().catch(() => '');
+ throw new Error(`HTTP ${response.status} for ${url}${body ? `: ${body.slice(0, 120)}` : ''}`);
+ }
+
+ return await response.json();
+ } finally {
+ clearTimeout(timeout);
+ }
+};
+
+const requestWithEndpointFallback = async (pathWithQuery) => {
+ const endpoints = getEndpoints();
+ const errors = [];
+
+ for (const endpoint of endpoints) {
+ const base = endpoint.endsWith('/') ? endpoint.slice(0, -1) : endpoint;
+ const path = pathWithQuery.startsWith('/') ? pathWithQuery : `/${pathWithQuery}`;
+ const url = `${base}${path}`;
+
+ for (let attempt = 1; attempt <= RETRIES_PER_ENDPOINT; attempt += 1) {
+ try {
+ const data = await fetchJson(url);
+ return {data, endpoint: base};
+ } catch (error) {
+ errors.push(`${base} (attempt ${attempt}): ${error.message}`);
+ if (attempt < RETRIES_PER_ENDPOINT) {
+ await sleep(attempt * 400);
+ }
+ }
+ }
+ }
+
+ const message = errors.length > 0 ? errors.join(' | ') : 'Unknown API error';
+ throw new Error(`JioSaavn request failed across all endpoints. ${message}`);
+};
+
+const normalizeSong = (song) => {
+ if (!song) {
+ return null;
+ }
+
+ const parseQualityScore = (value) => {
+ const direct = Number(value);
+ if (Number.isFinite(direct)) {
+ return direct;
+ }
+
+ const fromString = String(value || '').match(/(\d{2,3})/);
+ if (!fromString) {
+ return 0;
+ }
+
+ return Number(fromString[1]);
+ };
+
+ const extractAudioUrl = () => {
+ const candidateLists = [
+ song.downloadUrl,
+ song.downloadUrls,
+ song.more_info?.downloadUrl,
+ song.moreInfo?.downloadUrl,
+ ].filter((candidate) => Array.isArray(candidate));
+
+ const flattened = [];
+ for (const list of candidateLists) {
+ list.forEach((item, index) => {
+ if (typeof item === 'string') {
+ flattened.push({
+ url: item,
+ qualityScore: Math.max(0, 10 - index),
+ });
+ return;
+ }
+
+ if (item?.url) {
+ flattened.push({
+ url: item.url,
+ qualityScore: parseQualityScore(item.quality || item.bitrate || item.kbps) || Math.max(0, 10 - index),
+ });
+ }
+ });
+ }
+
+ if (flattened.length === 0) {
+ return null;
+ }
+
+ const best = flattened
+ .filter((entry) => typeof entry.url === 'string' && entry.url.startsWith('http'))
+ .sort((a, b) => b.qualityScore - a.qualityScore)[0];
+
+ return best?.url || null;
+ };
+
+ const artist =
+ song.artists?.primary?.map((item) => item.name).join(', ') ||
+ song.primaryArtists ||
+ song.artist ||
+ 'Unknown Artist';
+
+ const image =
+ song.image?.[2]?.url ||
+ song.image?.[1]?.url ||
+ song.image?.[0]?.url ||
+ null;
+
+ return {
+ id: song.id || null,
+ name: song.name || song.title || 'Unknown Song',
+ artist,
+ album: song.album?.name || song.album || null,
+ duration: Number(song.duration || 0),
+ language: song.language || null,
+ year: song.year || null,
+ url: song.url || null,
+ audioUrl: extractAudioUrl(),
+ image,
+ hasLyrics: Boolean(song.hasLyrics),
+ source: 'jiosaavn',
+ };
+};
+
+const extractSongsArray = (payload) => {
+ if (!payload) {
+ return [];
+ }
+
+ if (Array.isArray(payload.data)) {
+ return payload.data;
+ }
+
+ if (Array.isArray(payload.data?.results)) {
+ return payload.data.results;
+ }
+
+ if (Array.isArray(payload.results)) {
+ return payload.results;
+ }
+
+ return [];
+};
+
+const normalizeForScore = (value) =>
+ String(value || '')
+ .toLowerCase()
+ .replace(/[^a-z0-9\s]/g, ' ')
+ .replace(/\s+/g, ' ')
+ .trim();
+
+const scoreSongCandidate = (song, query) => {
+ const normalizedQuery = normalizeForScore(query);
+ const name = normalizeForScore(song.name);
+ const artist = normalizeForScore(song.artist);
+
+ if (!normalizedQuery) {
+ return 0;
+ }
+
+ let score = 0;
+ if (name === normalizedQuery) {
+ score += 100;
+ }
+ if (name.includes(normalizedQuery)) {
+ score += 50;
+ }
+ if (normalizedQuery.includes(name) && name.length > 3) {
+ score += 25;
+ }
+ if (artist.includes(normalizedQuery)) {
+ score += 15;
+ }
+
+ return score;
+};
+
+const searchSongs = async (query, limit = 8) => {
+ const encodedQuery = encodeURIComponent(query);
+ const {data, endpoint} = await requestWithEndpointFallback(
+ `/search/songs?query=${encodedQuery}&limit=${limit}`
+ );
+
+ const songs = extractSongsArray(data).map(normalizeSong).filter(Boolean);
+ return {songs, endpoint};
+};
+
+const getSongById = async (songId) => {
+ const {data, endpoint} = await requestWithEndpointFallback(`/songs/${encodeURIComponent(songId)}`);
+ const songs = extractSongsArray(data).map(normalizeSong).filter(Boolean);
+ return {song: songs[0] || null, endpoint};
+};
+
+const resolveSongMetadata = async ({songId, query}) => {
+ if (songId) {
+ const {song, endpoint} = await getSongById(songId);
+ if (!song) {
+ throw new Error(`Song not found for id: ${songId}`);
+ }
+
+ return {
+ song,
+ endpoint,
+ selectionMethod: 'song-id',
+ };
+ }
+
+ if (!query) {
+ throw new Error('Either songId or query is required.');
+ }
+
+ const {songs, endpoint} = await searchSongs(query, 10);
+ if (songs.length === 0) {
+ throw new Error(`No songs found for query: ${query}`);
+ }
+
+ const ranked = songs
+ .map((song) => ({song, score: scoreSongCandidate(song, query)}))
+ .sort((a, b) => b.score - a.score);
+
+ let selectedSong = ranked[0].song;
+ if (selectedSong?.id) {
+ try {
+ const details = await getSongById(selectedSong.id);
+ if (details.song) {
+ selectedSong = {
+ ...selectedSong,
+ ...details.song,
+ audioUrl: details.song.audioUrl || selectedSong.audioUrl || null,
+ };
+ }
+ } catch (error) {
+ // Keep best-effort search result when details endpoint is unavailable.
+ }
+ }
+
+ return {
+ song: selectedSong,
+ endpoint,
+ selectionMethod: 'search-best-match',
+ candidates: songs.slice(0, 5),
+ };
+};
+
+module.exports = {
+ resolveSongMetadata,
+};
diff --git a/scripts/lib/lyrics-client.js b/scripts/lib/lyrics-client.js
new file mode 100644
index 0000000..e7db6ea
--- /dev/null
+++ b/scripts/lib/lyrics-client.js
@@ -0,0 +1,168 @@
+const REQUEST_TIMEOUT_MS = Number(process.env.PHASE3_HTTP_TIMEOUT_MS || 12000);
+const LRCLIB_BASE_URL = process.env.LRCLIB_BASE_URL || 'https://lrclib.net';
+const LYRICS_OVH_BASE_URL = process.env.LYRICS_OVH_BASE_URL || 'https://api.lyrics.ovh';
+
+const unique = (values) => [...new Set(values.filter(Boolean))];
+
+const cleanToken = (value) =>
+ String(value || '')
+ .replace(/\s+\([^)]*\)$/g, '')
+ .replace(/\s+-\s+[^-]+$/g, '')
+ .replace(/\s+feat\.?\s+.+$/i, '')
+ .trim();
+
+const fetchJson = async (url) => {
+ const controller = new AbortController();
+ const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
+
+ try {
+ const response = await fetch(url, {signal: controller.signal});
+ if (!response.ok) {
+ return null;
+ }
+
+ return await response.json();
+ } catch (error) {
+ return null;
+ } finally {
+ clearTimeout(timeout);
+ }
+};
+
+const normalizeForScore = (value) =>
+ String(value || '')
+ .toLowerCase()
+ .replace(/[^a-z0-9\s]/g, ' ')
+ .replace(/\s+/g, ' ')
+ .trim();
+
+const scoreLyricCandidate = (candidate, songName, artistName) => {
+ const title = normalizeForScore(candidate.trackName || candidate.name);
+ const artist = normalizeForScore(candidate.artistName);
+ const targetTitle = normalizeForScore(songName);
+ const targetArtist = normalizeForScore(artistName);
+
+ let score = 0;
+ if (title === targetTitle) {
+ score += 80;
+ }
+ if (title.includes(targetTitle)) {
+ score += 30;
+ }
+ if (artist.includes(targetArtist)) {
+ score += 25;
+ }
+ if (candidate.syncedLyrics) {
+ score += 15;
+ }
+
+ return score;
+};
+
+const getFromLrcLib = async ({songName, artistName}) => {
+ const query = new URLSearchParams({
+ track_name: songName,
+ artist_name: artistName,
+ });
+
+ const directUrl = `${LRCLIB_BASE_URL}/api/get?${query.toString()}`;
+ const direct = await fetchJson(directUrl);
+ if (direct && (direct.syncedLyrics || direct.plainLyrics)) {
+ return {
+ provider: 'lrclib',
+ matchedBy: 'direct-get',
+ syncedLyrics: direct.syncedLyrics || '',
+ plainLyrics: direct.plainLyrics || '',
+ raw: {
+ id: direct.id || null,
+ trackName: direct.trackName || songName,
+ artistName: direct.artistName || artistName,
+ albumName: direct.albumName || null,
+ },
+ };
+ }
+
+ const searchUrl = `${LRCLIB_BASE_URL}/api/search?${query.toString()}`;
+ const results = await fetchJson(searchUrl);
+ if (!Array.isArray(results) || results.length === 0) {
+ return null;
+ }
+
+ const best = results
+ .map((item) => ({item, score: scoreLyricCandidate(item, songName, artistName)}))
+ .sort((a, b) => b.score - a.score)[0]?.item;
+
+ if (!best || (!best.syncedLyrics && !best.plainLyrics)) {
+ return null;
+ }
+
+ return {
+ provider: 'lrclib',
+ matchedBy: 'search',
+ syncedLyrics: best.syncedLyrics || '',
+ plainLyrics: best.plainLyrics || '',
+ raw: {
+ id: best.id || null,
+ trackName: best.trackName || songName,
+ artistName: best.artistName || artistName,
+ albumName: best.albumName || null,
+ },
+ };
+};
+
+const getFromLyricsOvh = async ({songName, artistName}) => {
+ const endpoint = `${LYRICS_OVH_BASE_URL}/v1/${encodeURIComponent(artistName)}/${encodeURIComponent(songName)}`;
+ const response = await fetchJson(endpoint);
+
+ if (!response || !response.lyrics) {
+ return null;
+ }
+
+ return {
+ provider: 'lyrics.ovh',
+ matchedBy: 'plain-lyrics-fallback',
+ syncedLyrics: '',
+ plainLyrics: response.lyrics,
+ raw: {
+ artistName,
+ trackName: songName,
+ endpoint,
+ },
+ };
+};
+
+const fetchLyricsWithFallback = async (song) => {
+ const primaryArtist = cleanToken((song.artist || '').split(',')[0]);
+ const titleCandidates = unique([song.name, cleanToken(song.name)]);
+ const artistCandidates = unique([primaryArtist, song.artist]);
+
+ for (const title of titleCandidates) {
+ for (const artist of artistCandidates) {
+ const lrcResult = await getFromLrcLib({songName: title, artistName: artist});
+ if (lrcResult) {
+ return lrcResult;
+ }
+ }
+ }
+
+ for (const title of titleCandidates) {
+ for (const artist of artistCandidates) {
+ const fallback = await getFromLyricsOvh({songName: title, artistName: artist});
+ if (fallback) {
+ return fallback;
+ }
+ }
+ }
+
+ return {
+ provider: 'none',
+ matchedBy: 'none',
+ syncedLyrics: '',
+ plainLyrics: '',
+ raw: null,
+ };
+};
+
+module.exports = {
+ fetchLyricsWithFallback,
+};
diff --git a/scripts/lib/lyrics-transform.js b/scripts/lib/lyrics-transform.js
new file mode 100644
index 0000000..9cab7d9
--- /dev/null
+++ b/scripts/lib/lyrics-transform.js
@@ -0,0 +1,139 @@
+const MIN_LINE_DURATION_SEC = 0.8;
+
+const roundSeconds = (value) => Math.round(value * 1000) / 1000;
+
+const cleanText = (value) =>
+ String(value || '')
+ .replace(/\r/g, '')
+ .trim();
+
+const parseTimestamp = (minutes, seconds, fraction = '') => {
+ const mm = Number(minutes);
+ const ss = Number(seconds);
+
+ let ff = 0;
+ if (fraction) {
+ if (fraction.length === 1) {
+ ff = Number(fraction) / 10;
+ } else if (fraction.length === 2) {
+ ff = Number(fraction) / 100;
+ } else {
+ ff = Number(fraction) / 1000;
+ }
+ }
+
+ return mm * 60 + ss + ff;
+};
+
+const parseSyncedLyrics = (lrcText, durationInSeconds = 0) => {
+ const source = cleanText(lrcText);
+ if (!source) {
+ return [];
+ }
+
+ const rows = source.split('\n');
+ const timestampRegex = /\[(\d{1,2}):(\d{2})(?:\.(\d{1,3}))?\]/g;
+ const timed = [];
+
+ for (const row of rows) {
+ const text = cleanText(row.replace(timestampRegex, ''));
+ if (!text) {
+ continue;
+ }
+
+ const matches = [...row.matchAll(timestampRegex)];
+ if (matches.length === 0) {
+ continue;
+ }
+
+ for (const match of matches) {
+ timed.push({
+ text,
+ start: parseTimestamp(match[1], match[2], match[3]),
+ });
+ }
+ }
+
+ timed.sort((a, b) => a.start - b.start);
+
+ const lines = [];
+ for (let i = 0; i < timed.length; i += 1) {
+ const current = timed[i];
+ const next = timed[i + 1];
+
+ let end = next ? next.start : current.start + 3;
+ if (durationInSeconds > 0 && !next) {
+ end = durationInSeconds;
+ }
+
+ end = Math.max(current.start + MIN_LINE_DURATION_SEC, end);
+
+ lines.push({
+ text: current.text,
+ start: roundSeconds(current.start),
+ end: roundSeconds(end),
+ });
+ }
+
+ return lines;
+};
+
+const splitPlainLyrics = (plainLyrics) => {
+ const source = cleanText(plainLyrics);
+ if (!source) {
+ return [];
+ }
+
+ return source
+ .split('\n')
+ .map((line) => cleanText(line))
+ .filter((line) => line.length > 0);
+};
+
+const approximateTimeline = (plainLyrics, durationInSeconds = 0) => {
+ const textLines = splitPlainLyrics(plainLyrics);
+ if (textLines.length === 0) {
+ return [];
+ }
+
+ const fallbackDuration = Math.max(textLines.length * 3, 30);
+ const totalDuration = durationInSeconds > 0 ? durationInSeconds : fallbackDuration;
+ const slot = totalDuration / textLines.length;
+
+ return textLines.map((text, index) => {
+ const start = index * slot;
+ const end = Math.max(start + MIN_LINE_DURATION_SEC, (index + 1) * slot);
+ return {
+ text,
+ start: roundSeconds(start),
+ end: roundSeconds(end),
+ };
+ });
+};
+
+const buildTimeline = ({syncedLyrics, plainLyrics, durationInSeconds = 0}) => {
+ const synced = parseSyncedLyrics(syncedLyrics, durationInSeconds);
+ if (synced.length > 0) {
+ return {
+ timingMode: 'synced',
+ lines: synced,
+ };
+ }
+
+ const approximated = approximateTimeline(plainLyrics, durationInSeconds);
+ if (approximated.length > 0) {
+ return {
+ timingMode: 'approximate',
+ lines: approximated,
+ };
+ }
+
+ return {
+ timingMode: 'none',
+ lines: [],
+ };
+};
+
+module.exports = {
+ buildTimeline,
+};
diff --git a/scripts/phase3-fetch-song-lyrics.js b/scripts/phase3-fetch-song-lyrics.js
new file mode 100644
index 0000000..6b0adc8
--- /dev/null
+++ b/scripts/phase3-fetch-song-lyrics.js
@@ -0,0 +1,103 @@
+const fs = require('node:fs/promises');
+const path = require('node:path');
+const {resolveSongMetadata} = require('./lib/jiosaavn-client');
+const {fetchLyricsWithFallback} = require('./lib/lyrics-client');
+const {buildTimeline} = require('./lib/lyrics-transform');
+
+const PROJECT_ROOT = path.resolve(__dirname, '..');
+
+const parseArgs = (argv) => {
+ const options = {
+ query: process.env.SONG_QUERY || '',
+ songId: process.env.SONG_ID || '',
+ out: process.env.PHASE3_OUTPUT_JSON || 'data/lyrics-video-input.json',
+ };
+
+ for (const arg of argv) {
+ if (arg.startsWith('--query=')) {
+ options.query = arg.slice('--query='.length);
+ } else if (arg.startsWith('--song-id=')) {
+ options.songId = arg.slice('--song-id='.length);
+ } else if (arg.startsWith('--out=')) {
+ options.out = arg.slice('--out='.length);
+ }
+ }
+
+ if (!options.query && !options.songId) {
+ throw new Error('Provide --query="song name" or --song-id="id" (or SONG_QUERY/SONG_ID env).');
+ }
+
+ return options;
+};
+
+const resolveOutputPath = (filePath) => {
+ if (path.isAbsolute(filePath)) {
+ return filePath;
+ }
+
+ return path.join(PROJECT_ROOT, filePath);
+};
+
+const main = async () => {
+ try {
+ const options = parseArgs(process.argv.slice(2));
+
+ console.log('[phase-3] Resolving song metadata from JioSaavn...');
+ const songResolution = await resolveSongMetadata({
+ songId: options.songId,
+ query: options.query,
+ });
+
+ const {song} = songResolution;
+ console.log(`[phase-3] Selected song: ${song.name} - ${song.artist}`);
+
+ console.log('[phase-3] Fetching lyrics (prefer synced/LRC)...');
+ const lyricPayload = await fetchLyricsWithFallback(song);
+
+ console.log('[phase-3] Converting lyrics to Remotion timeline JSON...');
+ const timeline = buildTimeline({
+ syncedLyrics: lyricPayload.syncedLyrics,
+ plainLyrics: lyricPayload.plainLyrics,
+ durationInSeconds: song.duration,
+ });
+
+ if (timeline.lines.length === 0) {
+ throw new Error('No lyrics found from synced or plain providers for this song.');
+ }
+
+ const output = {
+ metadata: {
+ generatedAt: new Date().toISOString(),
+ phase: 3,
+ timingMode: timeline.timingMode,
+ songSelectionMethod: songResolution.selectionMethod,
+ sourceApis: {
+ jiosaavn: songResolution.endpoint,
+ lyrics: lyricPayload.provider,
+ lyricsMatchMode: lyricPayload.matchedBy,
+ },
+ },
+ song,
+ lyricsRaw: {
+ provider: lyricPayload.provider,
+ hasSyncedLyrics: Boolean(lyricPayload.syncedLyrics),
+ hasPlainLyrics: Boolean(lyricPayload.plainLyrics),
+ reference: lyricPayload.raw,
+ },
+ lines: timeline.lines,
+ };
+
+ const outputPath = resolveOutputPath(options.out);
+ await fs.mkdir(path.dirname(outputPath), {recursive: true});
+ await fs.writeFile(outputPath, JSON.stringify(output, null, 2));
+
+ console.log(`[phase-3] Lyrics JSON written to: ${path.relative(PROJECT_ROOT, outputPath)}`);
+ console.log(`[phase-3] Timing mode: ${timeline.timingMode}`);
+ console.log(`[phase-3] Total lyric lines: ${timeline.lines.length}`);
+ } catch (error) {
+ console.error('[phase-3] Failed:', error.message);
+ process.exit(1);
+ }
+};
+
+main();
diff --git a/scripts/phase4-generate-direction.js b/scripts/phase4-generate-direction.js
new file mode 100644
index 0000000..21cd475
--- /dev/null
+++ b/scripts/phase4-generate-direction.js
@@ -0,0 +1,91 @@
+const fs = require('node:fs/promises');
+const path = require('node:path');
+const {generateAnimationDirection} = require('./lib/ai-director');
+
+const PROJECT_ROOT = path.resolve(__dirname, '..');
+
+const parseArgs = (argv) => {
+ const options = {
+ input: process.env.PHASE4_INPUT_JSON || 'data/phase3-lyrics.json',
+ output: process.env.PHASE4_OUTPUT_JSON || 'data/phase4-animation-direction.json',
+ };
+
+ for (const arg of argv) {
+ if (arg.startsWith('--in=')) {
+ options.input = arg.slice('--in='.length);
+ } else if (arg.startsWith('--out=')) {
+ options.output = arg.slice('--out='.length);
+ }
+ }
+
+ return options;
+};
+
+const resolveProjectPath = (targetPath) => {
+ if (path.isAbsolute(targetPath)) {
+ return targetPath;
+ }
+
+ return path.join(PROJECT_ROOT, targetPath);
+};
+
+const readJsonFile = async (filePath) => {
+ const raw = await fs.readFile(filePath, 'utf-8');
+ return JSON.parse(raw);
+};
+
+const validatePhase3Input = (payload) => {
+ if (!payload || typeof payload !== 'object') {
+ throw new Error('Input JSON is invalid.');
+ }
+
+ if (!Array.isArray(payload.lines) || payload.lines.length === 0) {
+ throw new Error('Input JSON must include a non-empty lines array.');
+ }
+};
+
+const main = async () => {
+ try {
+ const options = parseArgs(process.argv.slice(2));
+ const inputPath = resolveProjectPath(options.input);
+ const outputPath = resolveProjectPath(options.output);
+
+ console.log(`[phase-4] Reading lyrics timeline from ${path.relative(PROJECT_ROOT, inputPath)}...`);
+ const phase3Data = await readJsonFile(inputPath);
+ validatePhase3Input(phase3Data);
+
+ console.log('[phase-4] Generating animation direction JSON via AI...');
+ const aiResult = await generateAnimationDirection({
+ song: phase3Data.song,
+ lines: phase3Data.lines,
+ });
+
+ const outputPayload = {
+ metadata: {
+ generatedAt: new Date().toISOString(),
+ phase: 4,
+ sourceLyricsFile: path.relative(PROJECT_ROOT, inputPath),
+ provider: aiResult.providerReport.provider,
+ modelRequested: aiResult.providerReport.modelRequested,
+ modelResolved: aiResult.providerReport.modelResolved,
+ githubModelSupportedInPollinations: aiResult.providerReport.githubModelSupported,
+ fallbackReason: aiResult.providerReport.fallbackReason,
+ },
+ song: phase3Data.song,
+ direction: aiResult.direction,
+ };
+
+ await fs.mkdir(path.dirname(outputPath), {recursive: true});
+ await fs.writeFile(outputPath, JSON.stringify(outputPayload, null, 2));
+
+ console.log(`[phase-4] Animation direction written to ${path.relative(PROJECT_ROOT, outputPath)}.`);
+ console.log(`[phase-4] Provider used: ${outputPayload.metadata.provider}`);
+ console.log(`[phase-4] Direction lines: ${outputPayload.direction.lineDirections.length}`);
+ console.log(`[phase-4] Scene count: ${outputPayload.direction.sceneDirections.length}`);
+ } catch (error) {
+ console.error('[phase-4] Failed:', error.message);
+ process.exit(1);
+ }
+};
+
+main();
diff --git a/scripts/phase5-render-video.js b/scripts/phase5-render-video.js
new file mode 100644
index 0000000..3dcd289
--- /dev/null
+++ b/scripts/phase5-render-video.js
@@ -0,0 +1,327 @@
+const fs = require('node:fs/promises');
+const path = require('node:path');
+const {bundle} = require('@remotion/bundler');
+const {renderMedia, selectComposition} = require('@remotion/renderer');
+
+const PROJECT_ROOT = path.resolve(__dirname, '..');
+const ENTRY_POINT = path.join(PROJECT_ROOT, 'remotion/index.jsx');
+const PUBLIC_DIR = path.join(PROJECT_ROOT, 'public');
+const HTTP_TIMEOUT_MS = Number(process.env.PHASE5_HTTP_TIMEOUT_MS || 20000);
+
+const parseArgs = (argv) => {
+ const options = {
+ lyrics: process.env.PHASE5_LYRICS_JSON || 'data/phase3-lyrics.json',
+ direction: process.env.PHASE5_DIRECTION_JSON || 'data/phase4-direction.json',
+ output: process.env.PHASE5_OUTPUT_VIDEO || 'output/phase5-video.mp4',
+ maxSeconds: Number(process.env.PHASE5_MAX_SECONDS || 0),
+ };
+
+ for (const arg of argv) {
+ if (arg.startsWith('--lyrics=')) {
+ options.lyrics = arg.slice('--lyrics='.length);
+ } else if (arg.startsWith('--direction=')) {
+ options.direction = arg.slice('--direction='.length);
+ } else if (arg.startsWith('--out=')) {
+ options.output = arg.slice('--out='.length);
+ } else if (arg.startsWith('--max-seconds=')) {
+ options.maxSeconds = Number(arg.slice('--max-seconds='.length));
+ }
+ }
+
+ return options;
+};
+
+const resolveProjectPath = (targetPath) => {
+ if (path.isAbsolute(targetPath)) {
+ return targetPath;
+ }
+
+ return path.join(PROJECT_ROOT, targetPath);
+};
+
+const fileExists = async (filePath) => {
+ try {
+ await fs.access(filePath);
+ return true;
+ } catch (error) {
+ return false;
+ }
+};
+
+const readJson = async (filePath) => {
+ const raw = await fs.readFile(filePath, 'utf-8');
+ return JSON.parse(raw);
+};
+
+const inferAudioExtension = (audioUrl) => {
+ const allowed = new Set(['.mp3', '.m4a', '.aac', '.wav', '.ogg', '.webm', '.mp4']);
+
+ try {
+ const parsed = new URL(audioUrl);
+ const extension = path.extname(parsed.pathname).toLowerCase();
+ return allowed.has(extension) ? extension : '.m4a';
+ } catch (error) {
+ return '.m4a';
+ }
+};
+
+const downloadAudioToPublic = async (audioUrl) => {
+ const controller = new AbortController();
+ const timeout = setTimeout(() => controller.abort(), HTTP_TIMEOUT_MS);
+
+ try {
+ const response = await fetch(audioUrl, {signal: controller.signal});
+ if (!response.ok) {
+ throw new Error(`HTTP ${response.status}`);
+ }
+
+ const arrayBuffer = await response.arrayBuffer();
+ const byteLength = arrayBuffer.byteLength || 0;
+ if (byteLength < 1024) {
+ throw new Error('Audio payload too small');
+ }
+
+ await fs.mkdir(PUBLIC_DIR, {recursive: true});
+ const extension = inferAudioExtension(audioUrl);
+ const fileName = `phase5-audio-source${extension}`;
+ const targetPath = path.join(PUBLIC_DIR, fileName);
+ await fs.writeFile(targetPath, Buffer.from(arrayBuffer));
+
+ return {
+ mode: 'static',
+ src: fileName,
+ sourceUrl: audioUrl,
+ };
+ } finally {
+ clearTimeout(timeout);
+ }
+};
+
+const normalizeDirection = (directionPayload) => {
+ if (!directionPayload || typeof directionPayload !== 'object') {
+ return {
+ styleDescription: 'Fallback direction',
+ palette: {},
+ animationIdeas: [],
+ sceneDirections: [],
+ lineDirections: [],
+ };
+ }
+
+ const direct = directionPayload.direction && typeof directionPayload.direction === 'object'
+ ? directionPayload.direction
+ : directionPayload;
+
+ return {
+ styleDescription: direct.styleDescription || 'Fallback direction',
+ palette: direct.palette || {},
+ animationIdeas: Array.isArray(direct.animationIdeas) ? direct.animationIdeas : [],
+ sceneDirections: Array.isArray(direct.sceneDirections) ? direct.sceneDirections : [],
+ lineDirections: Array.isArray(direct.lineDirections) ? direct.lineDirections : [],
+ };
+};
+
+const normalizeLines = (payload) => {
+ const lines = Array.isArray(payload?.lines) ? payload.lines : [];
+
+ return lines
+ .map((line) => ({
+ text: String(line?.text || '').trim(),
+ start: Number(line?.start),
+ end: Number(line?.end),
+ }))
+ .filter((line) => line.text.length > 0)
+ .filter((line) => Number.isFinite(line.start) && Number.isFinite(line.end))
+ .filter((line) => line.end > line.start + 0.05)
+ .sort((a, b) => a.start - b.start);
+};
+
+const trimPayloadForCi = ({lines, direction, maxSeconds}) => {
+ if (!Number.isFinite(maxSeconds) || maxSeconds <= 0) {
+ return {lines, direction};
+ }
+
+ const lineIndexMap = new Map();
+ const trimmedLines = [];
+
+ lines.forEach((line, originalIndex) => {
+ if (line.start >= maxSeconds) {
+ return;
+ }
+
+ const end = Math.min(maxSeconds, line.end);
+ if (end <= line.start + 0.05) {
+ return;
+ }
+
+ const nextLine = {
+ ...line,
+ end,
+ };
+
+ lineIndexMap.set(originalIndex, trimmedLines.length);
+ trimmedLines.push(nextLine);
+ });
+
+ const lineDirections = direction.lineDirections
+ .map((lineDirection) => {
+ const previous = Number(lineDirection?.lineIndex);
+ if (!Number.isFinite(previous) || !lineIndexMap.has(previous)) {
+ return null;
+ }
+
+ return {
+ ...lineDirection,
+ lineIndex: lineIndexMap.get(previous),
+ };
+ })
+ .filter(Boolean);
+
+ const sceneDirections = direction.sceneDirections
+ .map((scene) => {
+ const lineIndices = (Array.isArray(scene?.lineIndices) ? scene.lineIndices : [])
+ .map((lineIndex) => Number(lineIndex))
+ .filter((lineIndex) => Number.isFinite(lineIndex) && lineIndexMap.has(lineIndex))
+ .map((lineIndex) => lineIndexMap.get(lineIndex));
+
+ if (lineIndices.length === 0) {
+ return null;
+ }
+
+ const start = Math.min(...lineIndices.map((lineIndex) => trimmedLines[lineIndex].start));
+ const end = Math.max(...lineIndices.map((lineIndex) => trimmedLines[lineIndex].end));
+
+ return {
+ ...scene,
+ start,
+ end,
+ lineIndices,
+ };
+ })
+ .filter(Boolean);
+
+ return {
+ lines: trimmedLines,
+ direction: {
+ ...direction,
+ sceneDirections,
+ lineDirections,
+ },
+ };
+};
+
+const main = async () => {
+ try {
+ const options = parseArgs(process.argv.slice(2));
+
+ const lyricsPath = resolveProjectPath(options.lyrics);
+ const directionPath = resolveProjectPath(options.direction);
+ const outputPath = resolveProjectPath(options.output);
+
+ if (!(await fileExists(lyricsPath))) {
+ throw new Error(`Lyrics input file not found: ${path.relative(PROJECT_ROOT, lyricsPath)}`);
+ }
+
+ console.log(`[phase-5] Reading lyrics timeline: ${path.relative(PROJECT_ROOT, lyricsPath)}`);
+ const lyricsPayload = await readJson(lyricsPath);
+ const lines = normalizeLines(lyricsPayload);
+
+ if (lines.length === 0) {
+ throw new Error('No valid lyric lines found in input.');
+ }
+
+ let directionPayload = {
+ styleDescription: 'Fallback direction',
+ palette: {},
+ animationIdeas: [],
+ sceneDirections: [],
+ lineDirections: [],
+ };
+
+ if (await fileExists(directionPath)) {
+ console.log(`[phase-5] Reading direction JSON: ${path.relative(PROJECT_ROOT, directionPath)}`);
+ const directionJson = await readJson(directionPath);
+ directionPayload = normalizeDirection(directionJson);
+ } else {
+ console.log(
+ `[phase-5] Direction JSON missing at ${path.relative(PROJECT_ROOT, directionPath)}. Using graceful fallback direction.`
+ );
+ }
+
+ const trimmed = trimPayloadForCi({
+ lines,
+ direction: directionPayload,
+ maxSeconds: options.maxSeconds,
+ });
+
+ let audioInput = null;
+ const songAudioUrl = String(lyricsPayload.song?.audioUrl || '').trim();
+ if (songAudioUrl) {
+ console.log('[phase-5] Preparing audio source...');
+ try {
+ audioInput = await downloadAudioToPublic(songAudioUrl);
+ console.log(`[phase-5] Audio downloaded for static embedding: public/${audioInput.src}`);
+ } catch (error) {
+ audioInput = {
+ mode: 'remote',
+ src: songAudioUrl,
+ sourceUrl: songAudioUrl,
+ };
+ console.log(`[phase-5] Audio download failed (${error.message}). Falling back to remote source.`);
+ }
+ } else {
+ console.log('[phase-5] No audio URL found in song metadata. Rendering video without audio.');
+ }
+
+ const inputProps = {
+ song: lyricsPayload.song || {},
+ lines: trimmed.lines,
+ direction: trimmed.direction,
+ audio: audioInput,
+ durationInSeconds:
+ options.maxSeconds > 0
+ ? options.maxSeconds
+ : Math.max(
+ Number(lyricsPayload.song?.duration || 0),
+ trimmed.lines.reduce((maxValue, line) => Math.max(maxValue, line.end), 0),
+ 5
+ ),
+ };
+
+ await fs.mkdir(path.dirname(outputPath), {recursive: true});
+
+ console.log('[phase-5] Bundling Remotion project...');
+ const bundled = await bundle({
+ entryPoint: ENTRY_POINT,
+ webpackOverride: (config) => config,
+ });
+
+ console.log('[phase-5] Selecting dynamic composition...');
+ const composition = await selectComposition({
+ serveUrl: bundled,
+ id: 'LyricsVideo',
+ inputProps,
+ });
+
+ console.log(`[phase-5] Rendering output video: ${path.relative(PROJECT_ROOT, outputPath)}`);
+ await renderMedia({
+ composition,
+ serveUrl: bundled,
+ codec: 'h264',
+ concurrency: Number(process.env.PHASE5_RENDER_CONCURRENCY || 2),
+ crf: Number(process.env.PHASE5_RENDER_CRF || 22),
+ outputLocation: outputPath,
+ inputProps,
+ });
+
+ console.log('[phase-5] Render completed successfully.');
+ console.log(`[phase-5] Lines rendered: ${trimmed.lines.length}`);
+ console.log(`[phase-5] Scene directions used: ${trimmed.direction.sceneDirections.length}`);
+ console.log(`[phase-5] Per-line directions used: ${trimmed.direction.lineDirections.length}`);
+ } catch (error) {
+ console.error('[phase-5] Failed:', error.message);
+ process.exit(1);
+ }
+};
+
+main();
diff --git a/scripts/phase7-upload-youtube.js b/scripts/phase7-upload-youtube.js
new file mode 100644
index 0000000..76fdaa0
--- /dev/null
+++ b/scripts/phase7-upload-youtube.js
@@ -0,0 +1,297 @@
+const fs = require('node:fs');
+const fsp = require('node:fs/promises');
+const path = require('node:path');
+const {google} = require('googleapis');
+
+const PROJECT_ROOT = path.resolve(__dirname, '..');
+
+const DEFAULTS = {
+ video: process.env.PHASE7_VIDEO_FILE || 'output/phase6-video.mp4',
+ phase3: process.env.PHASE7_PHASE3_JSON || 'data/phase3-lyrics.json',
+ output: process.env.PHASE7_OUTPUT_JSON || 'data/phase7-youtube-upload.json',
+ title: process.env.YOUTUBE_TITLE || '',
+ description: process.env.YOUTUBE_DESCRIPTION || '',
+ tags: process.env.YOUTUBE_TAGS || '',
+ privacyStatus: process.env.YOUTUBE_PRIVACY_STATUS || 'unlisted',
+ categoryId: process.env.YOUTUBE_CATEGORY_ID || '10',
+ dryRun: String(process.env.PHASE7_DRY_RUN || '').toLowerCase() === 'true',
+};
+
+const YOUTUBE_MAX_TITLE_LENGTH = 100;
+const YOUTUBE_MAX_DESCRIPTION_LENGTH = 5000;
+
+const parseArgs = (argv) => {
+ const options = {...DEFAULTS};
+
+ for (const arg of argv) {
+ if (arg.startsWith('--video=')) {
+ options.video = arg.slice('--video='.length);
+ } else if (arg.startsWith('--phase3=')) {
+ options.phase3 = arg.slice('--phase3='.length);
+ } else if (arg.startsWith('--out=')) {
+ options.output = arg.slice('--out='.length);
+ } else if (arg.startsWith('--title=')) {
+ options.title = arg.slice('--title='.length);
+ } else if (arg.startsWith('--description=')) {
+ options.description = arg.slice('--description='.length);
+ } else if (arg.startsWith('--tags=')) {
+ options.tags = arg.slice('--tags='.length);
+ } else if (arg.startsWith('--privacy=')) {
+ options.privacyStatus = arg.slice('--privacy='.length);
+ } else if (arg.startsWith('--category=')) {
+ options.categoryId = arg.slice('--category='.length);
+ } else if (arg === '--dry-run') {
+ options.dryRun = true;
+ }
+ }
+
+ return options;
+};
+
+const resolveProjectPath = (targetPath) => {
+ if (path.isAbsolute(targetPath)) {
+ return targetPath;
+ }
+
+ return path.join(PROJECT_ROOT, targetPath);
+};
+
+const fileExists = async (filePath) => {
+ try {
+ await fsp.access(filePath);
+ return true;
+ } catch (error) {
+ return false;
+ }
+};
+
+const readJsonIfExists = async (filePath) => {
+ if (!(await fileExists(filePath))) {
+ return null;
+ }
+
+ try {
+ const raw = await fsp.readFile(filePath, 'utf-8');
+ return JSON.parse(raw);
+ } catch (error) {
+ return null;
+ }
+};
+
+const clampText = (text, maxLength) => {
+ const source = String(text || '').trim();
+ if (source.length <= maxLength) {
+ return source;
+ }
+
+ return `${source.slice(0, maxLength - 1).trim()}…`;
+};
+
+const normalizePrivacyStatus = (value) => {
+ const normalized = String(value || '').trim().toLowerCase();
+ const allowed = new Set(['private', 'unlisted', 'public']);
+ return allowed.has(normalized) ? normalized : 'unlisted';
+};
+
+const dedupeTags = (tags) => {
+ const seen = new Set();
+ const unique = [];
+
+ for (const rawTag of tags) {
+ const tag = String(rawTag || '').trim();
+ if (!tag) {
+ continue;
+ }
+
+ const key = tag.toLowerCase();
+ if (seen.has(key)) {
+ continue;
+ }
+
+ seen.add(key);
+ unique.push(tag);
+ }
+
+ return unique.slice(0, 30);
+};
+
+const buildUploadMetadata = ({phase3Payload, options}) => {
+ const song = phase3Payload?.song || {};
+ const songName = String(song.name || '').trim();
+ const artist = String(song.artist || '').trim();
+ const firstArtist = artist.split(',')[0]?.trim() || artist;
+
+ const fallbackTitle = [songName, firstArtist].filter(Boolean).join(' - ');
+ const titleCandidate = options.title || (fallbackTitle ? `${fallbackTitle} | Animated Lyrics` : 'Animated Lyrics Video');
+ const title = clampText(titleCandidate, YOUTUBE_MAX_TITLE_LENGTH);
+
+ const generatedDescription = [
+ songName && artist ? `${songName} - ${artist}` : 'Animated lyrics video',
+ '',
+ 'Generated with an automated Remotion motion-graphics pipeline.',
+ `Generated at: ${new Date().toISOString()}`,
+ ].join('\n');
+ const description = clampText(options.description || generatedDescription, YOUTUBE_MAX_DESCRIPTION_LENGTH);
+
+ const providedTags = String(options.tags || '')
+ .split(',')
+ .map((item) => item.trim())
+ .filter(Boolean);
+
+ const inferredTags = [
+ songName,
+ firstArtist,
+ 'lyrics',
+ 'animated lyrics',
+ 'motion graphics',
+ 'music',
+ ];
+
+ const tags = dedupeTags([...providedTags, ...inferredTags]);
+
+ return {
+ title,
+ description,
+ tags,
+ categoryId: String(options.categoryId || '10'),
+ privacyStatus: normalizePrivacyStatus(options.privacyStatus),
+ };
+};
+
+const serializeUploadOutput = ({options, metadata, videoPath, phase3Path, responseData, dryRun}) => {
+ const videoId = responseData?.id || null;
+
+ return {
+ metadata: {
+ generatedAt: new Date().toISOString(),
+ phase: 7,
+ dryRun,
+ videoFile: path.relative(PROJECT_ROOT, videoPath),
+ sourcePhase3File: path.relative(PROJECT_ROOT, phase3Path),
+ privacyStatus: metadata.privacyStatus,
+ categoryId: metadata.categoryId,
+ },
+ request: {
+ title: metadata.title,
+ description: metadata.description,
+ tags: metadata.tags,
+ },
+ response: {
+ videoId,
+ url: videoId ? `https://www.youtube.com/watch?v=${videoId}` : null,
+ raw: responseData || null,
+ },
+ };
+};
+
+const getAuthEnv = () => {
+ return {
+ clientId: process.env.YOUTUBE_CLIENT_ID || '',
+ clientSecret: process.env.YOUTUBE_CLIENT_SECRET || '',
+ refreshToken: process.env.YOUTUBE_REFRESH_TOKEN || '',
+ };
+};
+
+const ensureAuthConfigured = (authEnv) => {
+ const missing = [];
+ if (!authEnv.clientId) missing.push('YOUTUBE_CLIENT_ID');
+ if (!authEnv.clientSecret) missing.push('YOUTUBE_CLIENT_SECRET');
+ if (!authEnv.refreshToken) missing.push('YOUTUBE_REFRESH_TOKEN');
+
+ if (missing.length > 0) {
+ throw new Error(`Missing required YouTube credentials: ${missing.join(', ')}`);
+ }
+};
+
+const parseUploadError = (error) => {
+ const providerError = error?.response?.data?.error || error?.response?.data || error?.message || error;
+
+ if (typeof providerError === 'string') {
+ return providerError;
+ }
+
+ try {
+ return JSON.stringify(providerError);
+ } catch (jsonError) {
+ return String(error?.message || 'Unknown upload error');
+ }
+};
+
+const uploadVideo = async ({videoPath, metadata}) => {
+ const authEnv = getAuthEnv();
+ ensureAuthConfigured(authEnv);
+
+ const oauth2Client = new google.auth.OAuth2(authEnv.clientId, authEnv.clientSecret);
+ oauth2Client.setCredentials({refresh_token: authEnv.refreshToken});
+
+ const youtube = google.youtube({version: 'v3', auth: oauth2Client});
+
+ const response = await youtube.videos.insert({
+ part: ['snippet', 'status'],
+ requestBody: {
+ snippet: {
+ title: metadata.title,
+ description: metadata.description,
+ tags: metadata.tags,
+ categoryId: metadata.categoryId,
+ },
+ status: {
+ privacyStatus: metadata.privacyStatus,
+ selfDeclaredMadeForKids: false,
+ },
+ },
+ media: {
+ body: fs.createReadStream(videoPath),
+ },
+ });
+
+ return response.data;
+};
+
+const main = async () => {
+ try {
+ const options = parseArgs(process.argv.slice(2));
+ const videoPath = resolveProjectPath(options.video);
+ const phase3Path = resolveProjectPath(options.phase3);
+ const outputPath = resolveProjectPath(options.output);
+
+ if (!(await fileExists(videoPath))) {
+ throw new Error(`Video file not found: ${path.relative(PROJECT_ROOT, videoPath)}`);
+ }
+
+ const phase3Payload = await readJsonIfExists(phase3Path);
+ const metadata = buildUploadMetadata({phase3Payload, options});
+
+ console.log(`[phase-7] Upload target video: ${path.relative(PROJECT_ROOT, videoPath)}`);
+ console.log(`[phase-7] Privacy: ${metadata.privacyStatus}`);
+ console.log(`[phase-7] Title: ${metadata.title}`);
+
+ let uploadResponse = null;
+ if (options.dryRun) {
+ console.log('[phase-7] Dry-run mode enabled; skipping YouTube upload call.');
+ } else {
+ console.log('[phase-7] Uploading video to YouTube...');
+ uploadResponse = await uploadVideo({videoPath, metadata});
+ console.log(`[phase-7] Upload successful. Video ID: ${uploadResponse.id}`);
+ }
+
+ const outputPayload = serializeUploadOutput({
+ options,
+ metadata,
+ videoPath,
+ phase3Path,
+ responseData: uploadResponse,
+ dryRun: options.dryRun,
+ });
+
+ await fsp.mkdir(path.dirname(outputPath), {recursive: true});
+ await fsp.writeFile(outputPath, JSON.stringify(outputPayload, null, 2));
+
+ console.log(`[phase-7] Upload metadata written to: ${path.relative(PROJECT_ROOT, outputPath)}`);
+ } catch (error) {
+ console.error('[phase-7] Failed:', parseUploadError(error));
+ process.exit(1);
+ }
+};
+
+main();
diff --git a/scripts/render-test-video.js b/scripts/render-test-video.js
new file mode 100644
index 0000000..67711c0
--- /dev/null
+++ b/scripts/render-test-video.js
@@ -0,0 +1,52 @@
+const fs = require('node:fs/promises');
+const path = require('node:path');
+const {bundle} = require('@remotion/bundler');
+const {renderMedia, selectComposition} = require('@remotion/renderer');
+
+const PROJECT_ROOT = path.resolve(__dirname, '..');
+const ENTRY_POINT = path.join(PROJECT_ROOT, 'remotion/index.jsx');
+const INPUT_JSON = path.join(PROJECT_ROOT, 'data/test-lyrics.json');
+const OUTPUT_VIDEO = path.join(PROJECT_ROOT, 'output/test-video.mp4');
+
+const readInputProps = async () => {
+ const contents = await fs.readFile(INPUT_JSON, 'utf-8');
+ return JSON.parse(contents);
+};
+
+const main = async () => {
+ try {
+ console.log('[phase-1] Loading test lyrics JSON...');
+ const inputProps = await readInputProps();
+
+ await fs.mkdir(path.dirname(OUTPUT_VIDEO), {recursive: true});
+
+ console.log('[phase-1] Bundling Remotion project...');
+ const bundled = await bundle({
+ entryPoint: ENTRY_POINT,
+ webpackOverride: (config) => config,
+ });
+
+ console.log('[phase-1] Selecting composition...');
+ const composition = await selectComposition({
+ serveUrl: bundled,
+ id: 'LyricsVideo',
+ inputProps,
+ });
+
+ console.log('[phase-1] Rendering video to output/test-video.mp4...');
+ await renderMedia({
+ composition,
+ serveUrl: bundled,
+ codec: 'h264',
+ outputLocation: OUTPUT_VIDEO,
+ inputProps,
+ });
+
+ console.log('[phase-1] Render completed successfully.');
+ } catch (error) {
+ console.error('[phase-1] Render failed:', error);
+ process.exit(1);
+ }
+};
+
+main();