From fe573e2439b0b5642556125a92791ad5b6ebf9d9 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 20 Apr 2026 17:11:38 +0000
Subject: [PATCH 1/3] Initial plan
From 5c7a3ade203c0fd6c73442b250d1fe2a0ef547f5 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 20 Apr 2026 17:16:22 +0000
Subject: [PATCH 2/3] feat: add 4k remotion render presets across scripts and
workflows
Agent-Logs-Url: https://github.com/Dcode9/MPLayer/sessions/d5f91392-f059-490c-bcdb-bf841f5d098e
Co-authored-by: Dcode9 <194372792+Dcode9@users.noreply.github.com>
---
.github/workflows/phase6-full-pipeline.yml | 6 +++
.github/workflows/phase7-youtube-upload.yml | 6 +++
.github/workflows/test-render.yml | 8 ++-
package.json | 2 +
remotion/Root.jsx | 22 ++++++++
scripts/phase5-render-4k-logo.js | 60 +++++++++++++++++++++
scripts/phase5-render-4k.js | 60 +++++++++++++++++++++
7 files changed, 163 insertions(+), 1 deletion(-)
create mode 100644 scripts/phase5-render-4k-logo.js
create mode 100644 scripts/phase5-render-4k.js
diff --git a/.github/workflows/phase6-full-pipeline.yml b/.github/workflows/phase6-full-pipeline.yml
index 703e1ab..cfd7c4f 100644
--- a/.github/workflows/phase6-full-pipeline.yml
+++ b/.github/workflows/phase6-full-pipeline.yml
@@ -21,6 +21,8 @@ on:
options:
- 16x9-logo
- 16x9-clean
+ - 4k-logo
+ - 4k-clean
max_seconds:
description: Clip duration cap in seconds (0 = full song)
required: false
@@ -76,6 +78,10 @@ jobs:
if [ "$RENDER_PRESET" = "16x9-clean" ]; then
npm run phase5:render:16x9 -- --lyrics=data/phase3-lyrics.json --out=output/phase6-video.mp4 --max-seconds="$PHASE5_MAX_SECONDS"
+ elif [ "$RENDER_PRESET" = "4k-clean" ]; then
+ npm run phase5:render:4k -- --lyrics=data/phase3-lyrics.json --out=output/phase6-video.mp4 --max-seconds="$PHASE5_MAX_SECONDS"
+ elif [ "$RENDER_PRESET" = "4k-logo" ]; then
+ npm run phase5:render:4k:logo -- --lyrics=data/phase3-lyrics.json --out=output/phase6-video.mp4 --max-seconds="$PHASE5_MAX_SECONDS"
else
npm run phase5:render:16x9:logo -- --lyrics=data/phase3-lyrics.json --out=output/phase6-video.mp4 --max-seconds="$PHASE5_MAX_SECONDS"
fi
diff --git a/.github/workflows/phase7-youtube-upload.yml b/.github/workflows/phase7-youtube-upload.yml
index 65c6171..bf2bf87 100644
--- a/.github/workflows/phase7-youtube-upload.yml
+++ b/.github/workflows/phase7-youtube-upload.yml
@@ -21,6 +21,8 @@ on:
options:
- 16x9-logo
- 16x9-clean
+ - 4k-logo
+ - 4k-clean
max_seconds:
description: Clip duration cap (0 means full song)
required: false
@@ -100,6 +102,10 @@ jobs:
if [ "$RENDER_PRESET" = "16x9-clean" ]; then
npm run phase5:render:16x9 -- --lyrics=data/phase3-lyrics.json --out=output/phase7-video.mp4 --max-seconds="$PHASE5_MAX_SECONDS"
+ elif [ "$RENDER_PRESET" = "4k-clean" ]; then
+ npm run phase5:render:4k -- --lyrics=data/phase3-lyrics.json --out=output/phase7-video.mp4 --max-seconds="$PHASE5_MAX_SECONDS"
+ elif [ "$RENDER_PRESET" = "4k-logo" ]; then
+ npm run phase5:render:4k:logo -- --lyrics=data/phase3-lyrics.json --out=output/phase7-video.mp4 --max-seconds="$PHASE5_MAX_SECONDS"
else
npm run phase5:render:16x9:logo -- --lyrics=data/phase3-lyrics.json --out=output/phase7-video.mp4 --max-seconds="$PHASE5_MAX_SECONDS"
fi
diff --git a/.github/workflows/test-render.yml b/.github/workflows/test-render.yml
index 3adfba0..327773f 100644
--- a/.github/workflows/test-render.yml
+++ b/.github/workflows/test-render.yml
@@ -11,6 +11,8 @@ on:
options:
- 16x9-logo
- 16x9-clean
+ - 4k-logo
+ - 4k-clean
max_seconds:
description: Clip duration cap in seconds
required: false
@@ -41,7 +43,7 @@ jobs:
- name: Build test lyrics input
run: npm run phase3:build -- --song-id=TEST_TEMPLATE --query="template test render" --out=data/test-lyrics.json
- - name: Render test video (16:9)
+ - name: Render test video
env:
RENDER_PRESET: ${{ inputs.render_preset }}
PHASE5_MAX_SECONDS: ${{ inputs.max_seconds }}
@@ -53,6 +55,10 @@ jobs:
if [ "$RENDER_PRESET" = "16x9-clean" ]; then
npm run phase5:render:16x9 -- --lyrics=data/test-lyrics.json --out=output/test-video.mp4 --max-seconds="$PHASE5_MAX_SECONDS"
+ elif [ "$RENDER_PRESET" = "4k-clean" ]; then
+ npm run phase5:render:4k -- --lyrics=data/test-lyrics.json --out=output/test-video.mp4 --max-seconds="$PHASE5_MAX_SECONDS"
+ elif [ "$RENDER_PRESET" = "4k-logo" ]; then
+ npm run phase5:render:4k:logo -- --lyrics=data/test-lyrics.json --out=output/test-video.mp4 --max-seconds="$PHASE5_MAX_SECONDS"
else
npm run phase5:render:16x9:logo -- --lyrics=data/test-lyrics.json --out=output/test-video.mp4 --max-seconds="$PHASE5_MAX_SECONDS"
fi
diff --git a/package.json b/package.json
index 7b63623..ac6e020 100644
--- a/package.json
+++ b/package.json
@@ -8,6 +8,8 @@
"phase5:render": "node scripts/phase5-render.js",
"phase5:render:16x9": "node scripts/phase5-render-16x9.js",
"phase5:render:16x9:logo": "node scripts/phase5-render-16x9-logo.js",
+ "phase5:render:4k": "node scripts/phase5-render-4k.js",
+ "phase5:render:4k:logo": "node scripts/phase5-render-4k-logo.js",
"phase7:upload": "node scripts/phase7-upload.js",
"render:test": "node scripts/render-test.js",
"remotion:studio": "remotion studio remotion/index.jsx"
diff --git a/remotion/Root.jsx b/remotion/Root.jsx
index 23de0ce..4992917 100644
--- a/remotion/Root.jsx
+++ b/remotion/Root.jsx
@@ -6,6 +6,8 @@ import templateSongData from './template-song-data.json';
const FPS = 30;
const WIDTH_16X9 = 1920;
const HEIGHT_16X9 = 1080;
+const WIDTH_4K = 3840;
+const HEIGHT_4K = 2160;
const DEFAULT_DURATION_SECONDS = Number(templateSongData.song?.duration || 205);
const DEFAULT_DURATION_FRAMES = Math.max(1, Math.round(DEFAULT_DURATION_SECONDS * FPS));
@@ -51,6 +53,26 @@ export const RemotionRoot = () => {
height={HEIGHT_16X9}
defaultProps={defaultProps16x9Logo}
/>
+
+
+
+
>
);
};
diff --git a/scripts/phase5-render-4k-logo.js b/scripts/phase5-render-4k-logo.js
new file mode 100644
index 0000000..882f20c
--- /dev/null
+++ b/scripts/phase5-render-4k-logo.js
@@ -0,0 +1,60 @@
+#!/usr/bin/env node
+'use strict';
+
+const path = require('node:path');
+
+const {runPhase5Render} = require('./phase5-render');
+
+const PROJECT_ROOT = path.resolve(__dirname, '..');
+
+const parsePositiveNumber = (value) => {
+ const parsed = Number(value);
+ if (!Number.isFinite(parsed) || parsed < 0) {
+ return 0;
+ }
+ return parsed;
+};
+
+const parseArgs = (argv) => {
+ const args = {
+ lyrics: path.join(PROJECT_ROOT, 'data', 'phase3-lyrics.json'),
+ out: path.join(PROJECT_ROOT, 'output', 'phase5-video-4k-logo.mp4'),
+ maxSeconds: parsePositiveNumber(process.env.PHASE5_MAX_SECONDS || '0'),
+ };
+
+ for (const arg of argv) {
+ if (arg.startsWith('--lyrics=')) {
+ const value = arg.slice('--lyrics='.length).trim();
+ args.lyrics = path.isAbsolute(value) ? value : path.join(PROJECT_ROOT, value);
+ } else if (arg.startsWith('--out=')) {
+ const value = arg.slice('--out='.length).trim();
+ args.out = path.isAbsolute(value) ? value : path.join(PROJECT_ROOT, value);
+ } else if (arg.startsWith('--max-seconds=')) {
+ args.maxSeconds = parsePositiveNumber(arg.slice('--max-seconds='.length).trim());
+ }
+ }
+
+ return args;
+};
+
+const main = async () => {
+ const args = parseArgs(process.argv.slice(2));
+
+ const result = await runPhase5Render({
+ ...args,
+ compositionId: 'LyricsTemplateVideo4kLogo',
+ });
+
+ console.log('[phase5-4k-logo] Render complete');
+ console.log(`[phase5-4k-logo] Output: ${path.relative(PROJECT_ROOT, result.out)}`);
+ console.log(
+ `[phase5-4k-logo] Video: ${result.width}x${result.height} @ ${result.fps}fps, ${result.durationInSeconds.toFixed(2)}s (${result.durationInFrames} frames)`,
+ );
+};
+
+if (require.main === module) {
+ main().catch((error) => {
+ console.error(`[phase5-4k-logo] Failed: ${error.message || error}`);
+ process.exit(1);
+ });
+}
diff --git a/scripts/phase5-render-4k.js b/scripts/phase5-render-4k.js
new file mode 100644
index 0000000..09185b0
--- /dev/null
+++ b/scripts/phase5-render-4k.js
@@ -0,0 +1,60 @@
+#!/usr/bin/env node
+'use strict';
+
+const path = require('node:path');
+
+const {runPhase5Render} = require('./phase5-render');
+
+const PROJECT_ROOT = path.resolve(__dirname, '..');
+
+const parsePositiveNumber = (value) => {
+ const parsed = Number(value);
+ if (!Number.isFinite(parsed) || parsed < 0) {
+ return 0;
+ }
+ return parsed;
+};
+
+const parseArgs = (argv) => {
+ const args = {
+ lyrics: path.join(PROJECT_ROOT, 'data', 'phase3-lyrics.json'),
+ out: path.join(PROJECT_ROOT, 'output', 'phase5-video-4k.mp4'),
+ maxSeconds: parsePositiveNumber(process.env.PHASE5_MAX_SECONDS || '0'),
+ };
+
+ for (const arg of argv) {
+ if (arg.startsWith('--lyrics=')) {
+ const value = arg.slice('--lyrics='.length).trim();
+ args.lyrics = path.isAbsolute(value) ? value : path.join(PROJECT_ROOT, value);
+ } else if (arg.startsWith('--out=')) {
+ const value = arg.slice('--out='.length).trim();
+ args.out = path.isAbsolute(value) ? value : path.join(PROJECT_ROOT, value);
+ } else if (arg.startsWith('--max-seconds=')) {
+ args.maxSeconds = parsePositiveNumber(arg.slice('--max-seconds='.length).trim());
+ }
+ }
+
+ return args;
+};
+
+const main = async () => {
+ const args = parseArgs(process.argv.slice(2));
+
+ const result = await runPhase5Render({
+ ...args,
+ compositionId: 'LyricsTemplateVideo4k',
+ });
+
+ console.log('[phase5-4k] Render complete');
+ console.log(`[phase5-4k] Output: ${path.relative(PROJECT_ROOT, result.out)}`);
+ console.log(
+ `[phase5-4k] Video: ${result.width}x${result.height} @ ${result.fps}fps, ${result.durationInSeconds.toFixed(2)}s (${result.durationInFrames} frames)`,
+ );
+};
+
+if (require.main === module) {
+ main().catch((error) => {
+ console.error(`[phase5-4k] Failed: ${error.message || error}`);
+ process.exit(1);
+ });
+}
From 19592a51dffc5568c9d7df998c2ae1fb11e7902b Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 20 Apr 2026 17:17:57 +0000
Subject: [PATCH 3/3] chore: clarify default prop names for 16x9 and 4k
compositions
Agent-Logs-Url: https://github.com/Dcode9/MPLayer/sessions/d5f91392-f059-490c-bcdb-bf841f5d098e
Co-authored-by: Dcode9 <194372792+Dcode9@users.noreply.github.com>
---
remotion/Root.jsx | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/remotion/Root.jsx b/remotion/Root.jsx
index 4992917..590c515 100644
--- a/remotion/Root.jsx
+++ b/remotion/Root.jsx
@@ -11,11 +11,11 @@ const HEIGHT_4K = 2160;
const DEFAULT_DURATION_SECONDS = Number(templateSongData.song?.duration || 205);
const DEFAULT_DURATION_FRAMES = Math.max(1, Math.round(DEFAULT_DURATION_SECONDS * FPS));
-const defaultProps16x9 = {
+const defaultPropsClean = {
songData: templateSongData,
};
-const defaultProps16x9Logo = {
+const defaultPropsLogo = {
songData: templateSongData,
showAnimatedLogo: true,
logoSrc: staticFile('assets/DTunes-transparent.svg'),
@@ -31,7 +31,7 @@ export const RemotionRoot = () => {
fps={FPS}
width={WIDTH_16X9}
height={HEIGHT_16X9}
- defaultProps={defaultProps16x9Logo}
+ defaultProps={defaultPropsLogo}
/>
{
fps={FPS}
width={WIDTH_16X9}
height={HEIGHT_16X9}
- defaultProps={defaultProps16x9}
+ defaultProps={defaultPropsClean}
/>
{
fps={FPS}
width={WIDTH_16X9}
height={HEIGHT_16X9}
- defaultProps={defaultProps16x9Logo}
+ defaultProps={defaultPropsLogo}
/>
{
fps={FPS}
width={WIDTH_4K}
height={HEIGHT_4K}
- defaultProps={defaultProps16x9}
+ defaultProps={defaultPropsClean}
/>
{
fps={FPS}
width={WIDTH_4K}
height={HEIGHT_4K}
- defaultProps={defaultProps16x9Logo}
+ defaultProps={defaultPropsLogo}
/>
>
);