// ============================================================
// 音乐播放器卡片模板生成器 (AEJSX)
// 用途: 自动生成 "AI写真 + 播放器UI + 歌词滚动 + 闪白转场" AE 工程
// 适用: 类似"小耳朵-明天过后"的卡片风格视频, 换图换歌即可复用
//
// 【使用方法】
// 1. 新建文件夹, 把本脚本放到文件夹根目录
// 2. 建 assets 子文件夹, 放入:
//      assets/scene1.png 或 .mp4/.mov —— 第一段素材(开场图/视频都行)
//      assets/scene2.png 或 .mp4/.mov —— 第二段素材(主图/主视频)
//      assets/card.png     —— 左侧播放器卡片UI(可选, 没有就用AE形状层占位)
//      assets/music.mp3    —— 背景音乐(可选, 没有就静音)
// 3. AE 打开后: 文件 > 脚本 > 运行脚本文件... 选择本脚本
// 4. 等待生成, 主合成名为 "播放器卡片_主合成"
//
// 【替换内容】
// - 换图/换视频: 直接替换 assets 里的素材, 再跑一次脚本(会重建工程)
// - 换歌词: 改脚本顶部 LYRICS 数组
// - 换节奏: 改 TOP_DURATION(第一段时长) / SCALE_START(推进幅度)
// - 换分辨率: 改 COMP_W / COMP_H(如 1080x1920 竖屏, 3840x2160 4K)
// ============================================================

// ---------------- 可调参数 ----------------
var TOP_DURATION = 1.9;        // 第一段(开场)时长, 秒
var TOTAL_DURATION = 10.0;     // 总时长, 秒
var COMP_W = 1920;             // 宽 (可改: 1080x1920竖屏 / 3840x2160 4K / 720p等)
var COMP_H = 1080;             // 高
var FPS = 30;                  // 帧率
var SCALE_START = 100;         // 第二段起始缩放 %
var SCALE_END = 110;           // 第二段结束缩放 % (Ken Burns 推进)
var CARD_POS_X = 0.42;         // 播放器卡片左边界(占画布宽比例)
var LYRIC_FONT_SIZE = 40;      // 歌词字号
var LYRIC_COLOR = [0.85, 0.85, 0.85]; // 歌词颜色(浅灰)
var LYRIC_LINE_SPACING = 52;   // 歌词行距
var LYRIC_START_Y = 720;       // 歌词起始Y(从下往上滚)
var LYRIC_SPEED = 26;          // 歌词每秒上滚像素
var ROTATE_SPEED = 360;        // 唱片旋转速度 度/秒
var FLASH_START = TOP_DURATION - 0.1; // 闪白开始(比转场稍早)
var FLASH_DUR = 0.35;          // 闪白时长

// ---------------- 歌词(默认《明天过后》节选, 可替换) ----------------
var LYRICS = [
  "是否爱上一个人 不问明天过后",
  "山明和水秀 不比你有看头",
  "牵着你的手 一直走到最后",
  "这一刻怎么回头",
  "没有星星的夜空",
  "享受一分钟的感动"
];

// ---------------- 资产路径 ----------------
var ASSET_DIR = Folder($.fileName).fsName + "/assets";
var SCENE1 = ASSET_DIR + "/scene1.png";
var SCENE2 = ASSET_DIR + "/scene2.png";
var CARD   = ASSET_DIR + "/card.png";
var MUSIC  = ASSET_DIR + "/music.mp3";

// scene1/scene2 自动检测: 支持 png/jpg/jpeg/webp/mp4/mov/m4v(图片或视频都行)
function findAsset(base, exts) {
  for (var i = 0; i < exts.length; i++) {
    var p = ASSET_DIR + "/" + base + "." + exts[i];
    if (File(p).exists) return p;
  }
  return null;
}
var SCENE1_PATH = findAsset("scene1", ["png", "jpg", "jpeg", "webp", "mp4", "mov", "m4v"]);
var SCENE2_PATH = findAsset("scene2", ["png", "jpg", "jpeg", "webp", "mp4", "mov", "m4v"]);

function log(msg) { $.writeln("[模板] " + msg); }

function fileExists(p) {
  return File(p).exists;
}

function main() {
  log("开始生成模板工程...");
  var app = app;
  
  // ---------- 1. 新建项目 ----------
  if (app.project) { app.project.close(CloseOptions.DO_NOT_SAVE_CHANGES); }
  var proj = app.newProject();
  
  // ---------- 2. 导入素材 ----------
  var impScene1 = null, impScene2 = null, impCard = null, impMusic = null;
  if (SCENE1_PATH) { impScene1 = proj.importFile(new ImportOptions(File(SCENE1_PATH))); log("导入 scene1: " + SCENE1_PATH); }
  else log("警告: 缺 assets/scene1 (png/jpg/mp4/mov 都行)");
  if (SCENE2_PATH) { impScene2 = proj.importFile(new ImportOptions(File(SCENE2_PATH))); log("导入 scene2: " + SCENE2_PATH); }
  else log("警告: 缺 assets/scene2 (png/jpg/mp4/mov 都行)");
  if (fileExists(CARD))   { impCard = proj.importFile(new ImportOptions(File(CARD))); log("导入 card"); }
  if (fileExists(MUSIC))  { impMusic = proj.importFile(new ImportOptions(File(MUSIC))); log("导入 music"); }
  
  // ---------- 3. 创建主合成 ----------
  var comp = proj.items.addComp("播放器卡片_主合成", COMP_W, COMP_H, 1, TOTAL_DURATION, FPS);
  comp.openInViewer();
  
  // ---------- 4. 第一段: 开场素材(图或视频) (0 ~ TOP_DURATION) ----------
  if (impScene1) {
    var l1 = comp.layers.add(impScene1);
    l1.name = "开场素材";
    l1.property("Position").setValue([COMP_W/2, COMP_H/2]);
    // 如果是视频且时长超过 TOP_DURATION, 截断; 如果视频比 TOP_DURATION 短, 只显示到视频结尾
    if (impScene1.duration && impScene1.duration > 0 && impScene1.duration < TOP_DURATION) {
      l1.outPoint = impScene1.duration;
      log("scene1 是视频, 时长 " + impScene1.duration.toFixed(2) + "s < TOP_DURATION, 截到视频结尾");
    } else {
      l1.outPoint = TOP_DURATION;
    }
    // 轻微推进
    l1.property("Scale").setValueAtTime(0, [SCALE_START+2, SCALE_START+2]);
    l1.property("Scale").setValueAtTime(TOP_DURATION, [SCALE_START+6, SCALE_START+6]);
    // 结尾提亮(配合闪白)
    var l1End = l1.outPoint;
    var eff = l1.property("ADBE Effect Parade").addProperty("ADBE Brightness & Contrast");
    eff.property("ADBE Brightness").setValueAtTime(l1End-0.3, 0);
    eff.property("ADBE Brightness").setValueAtTime(l1End, 40);
  } else {
    // 无素材时用纯色占位
    var solid1 = comp.layers.addSolid([0.1,0.1,0.15], "开场占位", COMP_W, COMP_H, 1);
    solid1.outPoint = TOP_DURATION;
  }
  
  // ---------- 5. 闪白转场层 ----------
  var flash = comp.layers.addSolid([1,1,1], "闪白转场", COMP_W, COMP_H, 1);
  flash.inPoint = FLASH_START;
  flash.outPoint = FLASH_START + FLASH_DUR;
  var flOpacity = flash.property("Opacity");
  flOpacity.setValueAtTime(FLASH_START, 0);
  flOpacity.setValueAtTime(FLASH_START + FLASH_DUR/2, 100);
  flOpacity.setValueAtTime(FLASH_START + FLASH_DUR, 0);
  
  // ---------- 6. 第二段: 主素材(图或视频) (TOP_DURATION ~ 结尾, 右侧 58%) ----------
  var mainLayer = null;
  if (impScene2) {
    mainLayer = comp.layers.add(impScene2);
    mainLayer.name = "主画面_右侧";
    // 定位: 占右侧58%, 即左侧42%留白
    var mw = COMP_W * 0.58;
    var mh = COMP_H;
    mainLayer.property("Position").setValue([COMP_W*0.42 + mw/2, COMP_H/2]);
    // 等比缩放适配 (视频层用 sourceRectAtTime 也能取到)
    var sw = mw / mainLayer.sourceRectAtTime(0, false).width;
    var sh = mh / mainLayer.sourceRectAtTime(0, false).height;
    var fitScale = Math.max(sw, sh) * 100; // cover
    mainLayer.property("Scale").setValueAtTime(TOP_DURATION, [fitScale*SCALE_START/100, fitScale*SCALE_START/100]);
    mainLayer.property("Scale").setValueAtTime(TOP_DURATION+2, [fitScale*SCALE_END/100, fitScale*SCALE_END/100]);
    // 关键帧缓出(勾选 easy ease)
    try {
      mainLayer.property("Scale").setTemporalEaseAtKey(1, [new KeyframeEase(0, 33), new KeyframeEase(0, 66)]);
      mainLayer.property("Scale").setTemporalEaseAtKey(2, [new KeyframeEase(0, 66), new KeyframeEase(0, 33)]);
    } catch(e) {}
    mainLayer.inPoint = TOP_DURATION;
    if (impScene2.duration && impScene2.duration > 0 && impScene2.duration < TOTAL_DURATION - TOP_DURATION) {
      mainLayer.outPoint = TOP_DURATION + impScene2.duration;
      log("scene2 是视频, 时长 " + impScene2.duration.toFixed(2) + "s, 截到 " + mainLayer.outPoint.toFixed(2) + "s");
    }
  } else {
    var solid2 = comp.layers.addSolid([0.55,0.35,0.35], "主画面占位", COMP_W, COMP_H, 1);
    solid2.inPoint = TOP_DURATION;
    solid2.property("Position").setValue([COMP_W*0.71, COMP_H/2]);
    solid2.property("Scale").setValue([58, 100]);
  }
  
  // ---------- 7. 左侧播放器卡片UI ----------
  var cardLayer = null;
  if (impCard) {
    cardLayer = comp.layers.add(impCard);
    cardLayer.name = "播放器卡片UI";
    cardLayer.property("Position").setValue([COMP_W*0.21, COMP_H/2]);
    var cw = COMP_W*0.42;
    var cs = cw / cardLayer.sourceRectAtTime(0, false).width;
    cardLayer.property("Scale").setValue([cs*100, cs*100]);
    cardLayer.inPoint = TOP_DURATION;
    // 淡入
    var cOp = cardLayer.property("Opacity");
    cOp.setValueAtTime(TOP_DURATION, 0);
    cOp.setValueAtTime(TOP_DURATION+0.4, 100);
  }
  
  // ---------- 8. 标题 FASHION / SELF-PORTRAIT ----------
  function makeText(text, size, y, color, font) {
    var layer = comp.layers.addText(text);
    layer.name = "标题_" + text;
    var td = layer.property("Source Text").value;
    td.fontSize = size;
    td.fillColor = color;
    td.font = font || "Arial";
    layer.property("Source Text").setValue(td);
    var p = layer.property("Position");
    p.setValue([COMP_W*0.21, y]);
    layer.inPoint = TOP_DURATION;
    return layer;
  }
  
  makeText("FASHION", 72, 160, [0.9, 0.75, 0.45], "Georgia");
  makeText("SELF-PORTRAIT", 34, 240, [0.8, 0.8, 0.8], "Arial");
  
  // ---------- 9. 黑胶唱片(旋转动画) ----------
  function addRecordLayer(x, y, radius) {
    var vinyl = comp.layers.addSolid([0.08,0.08,0.08], "黑胶唱片", radius*2, radius*2, 1);
    vinyl.property("Position").setValue([x, y]);
    // 中心贴纸(用第二张图的缩略感占位: 加一个小圆)
    var center = comp.layers.addSolid([0.9,0.5,0.5], "唱片贴纸", radius*0.55*2, radius*0.55*2, 1);
    center.property("Position").setValue([x, y]);
    // 旋转表达式
    var rotExpr = "time * " + ROTATE_SPEED;
    vinyl.property("Rotation").expression = rotExpr;
    // 唱片淡入
    vinyl.inPoint = TOP_DURATION;
    center.inPoint = TOP_DURATION;
    return vinyl;
  }
  addRecordLayer(COMP_W*0.18, COMP_H*0.52, 240);
  
  // ---------- 10. 歌词逐行上滚 ----------
  var lyricGroup = comp.layers.addNull();
  lyricGroup.name = "歌词组";
  for (var i = 0; i < LYRICS.length; i++) {
    var ly = comp.layers.addText(LYRICS[i]);
    ly.name = "歌词_" + (i+1);
    var td = ly.property("Source Text").value;
    td.fontSize = LYRIC_FONT_SIZE;
    td.fillColor = LYRIC_COLOR;
    td.font = "Microsoft YaHei";
    ly.property("Source Text").setValue(td);
    // 初始位置: 播放器卡片左侧, 从下往上
    var startY = LYRIC_START_Y + i * LYRIC_LINE_SPACING;
    ly.property("Position").setValue([COMP_W*0.21, startY]);
    // 上滚表达式: 从 TOP_DURATION 开始
    ly.property("Position").expression =
      "x = " + COMP_W*0.21 + ";\n" +
      "y = " + startY + " - (time - " + TOP_DURATION + ") * " + LYRIC_SPEED + ";\n" +
      "if (time < " + TOP_DURATION + ") { y = " + startY + "; }\n" +
      "[x, y]";
    ly.inPoint = TOP_DURATION;
  }
  
  // ---------- 11. 底部控制条 (进度/时间/⏮⏸⏭/♡) ----------
  function addBottomBar() {
    var barY = COMP_H - 90;
    // 进度条背景
    var progBg = comp.layers.addSolid([0.2,0.2,0.2], "进度条底", COMP_W*0.36, 4, 1);
    progBg.property("Position").setValue([COMP_W*0.21, barY-10]);
    // 进度填充(动画)
    var progFill = comp.layers.addSolid([0.9,0.5,0.4], "进度条填充", COMP_W*0.36, 4, 1);
    progFill.property("Position").setValue([COMP_W*0.21, barY-10]);
    progFill.property("Anchor Point").setValue([0, 2]);
    progFill.property("Scale").expression = "[time/" + TOTAL_DURATION + "*100, 100]";
    // 时间文本
    makeText("00:00", 26, barY, [0.85,0.85,0.85]).name = "时间显示";
    // 控制键 ⏮⏸⏭ ♡ (用文本)
    makeText("⏮ ⏸ ⏭ ♡ 41w", 26, barY, [0.85,0.85,0.85]).name = "控制键";
  }
  addBottomBar();
  
  // ---------- 12. 水印黑条 ----------
  var bar = comp.layers.addSolid([0,0,0], "水印黑条", COMP_W, 60, 1);
  bar.property("Position").setValue([COMP_W/2, COMP_H - 30]);
  bar.property("Opacity").setValue(85);
  var wm = comp.layers.addText("XiaoErDuo-For you");
  wm.name = "水印";
  var td = wm.property("Source Text").value;
  td.fontSize = 30;
  td.fillColor = [1,1,1];
  wm.property("Source Text").setValue(td);
  wm.property("Position").setValue([COMP_W/2, COMP_H - 30]);
  
  // ---------- 13. 音乐 ----------
  if (impMusic) {
    var audioLayer = comp.layers.add(impMusic);
    audioLayer.name = "背景音乐";
    audioLayer.inPoint = 0;
    log("已添加背景音乐");
  }
  
  // ---------- 14. 全局滤镜调整层 ----------
  var adj = comp.layers.addAdjustmentLayer();
  adj.name = "全局柔光暖色";
  var lum = adj.property("ADBE Effect Parade").addProperty("ADBE Lumetri");
  try {
    lum.property("ADBE Lumetri Exposures").setValue(0.2);
    lum.property("ADBE Lumetri Temperature").setValue(8);
  } catch(e) {}
  var blur = adj.property("ADBE Effect Parade").addProperty("ADBE Gaussian Blur 2");
  try { blur.property("ADBE Gaussian Blur 2 Radius").setValue(2); } catch(e) {}
  
  log("✅ 工程生成完成!");
  log("  主合成: " + comp.name);
  log("  时长: " + TOTAL_DURATION + "s, " + COMP_W + "x" + COMP_H + ", " + FPS + "fps");
  log("  提示: 替换 assets 里图片/歌词数组后重新运行即可换内容");
}

main();
