From 9dc44a2c526e9d6910b64495aaf97f4049c45b92 Mon Sep 17 00:00:00 2001 From: Arlind Date: Sun, 22 Jun 2025 11:22:59 +0200 Subject: [PATCH] Update .gitea/workflows/ci.yml --- .gitea/workflows/ci.yml | 450 +++++++++++++++++++++++++++------------- 1 file changed, 306 insertions(+), 144 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index d40b120..f4fbae8 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -2,8 +2,6 @@ name: CI/CD Pipeline on: push: - branches: - - main paths: - '.gitea/workflows/*' - 'Skins/**/*' @@ -29,13 +27,17 @@ env: REPO_SCREENSHOT_DIR: "${{ github.workspace }}/media/gameplay" REPO_MOD_ICONS_DIR: "${{ github.workspace }}/media/icons" REPO_RANKING_PANEL_DIR: "${{ github.workspace }}/media/panel" + REPO_THUMBNAIL_DIR: "${{ github.workspace }}/media/thumbnail" SETTINGS_JSON_PATH: "/app/danser/settings/default.json" README_PATH: "${{ github.workspace }}/README.md" - REPLAY_PATH: "${{ github.workspace }}/src/replay.osr" + GAMEPLAY_REPLAY_PATH: "/app/danser/custom-replays/yomi_yori.osr" + THUMBNAIL_REPLAY_PATH: "/app/danser/custom-replays/combo_colors.osr" + PANEL_REPLAY_PATH: "/app/danser/custom-replays/2000_gekis.osr" OSK_PATH: "${{ github.workspace }}/export" IMAGE_NAME: osc/skins-image REGISTRY_URL: "https://${{ vars.CONTAINER_REGISTRY }}" OSU_ID: ${{ vars.OSUID }} + DOC_DIR: "${{ github.workspace }}/docs" jobs: generate_everything: @@ -101,8 +103,8 @@ jobs: echo "→ Target skins specified. Using target_skins input…" IFS=',' read -r -a input_skins <<< "$target_skins" for s in "${input_skins[@]}"; do - s="${s#"${s%%[![:space:]]*}"}" # trim leading whitespace - s="${s%"${s##*[![:space:]]}"}" # trim trailing whitespace + s="${s#"${s%%[![:space:]]*}"}" + s="${s%"${s##*[![:space:]]}"}" [[ -n "$s" ]] && skins+=("$s") done echo " ✓ Found ${#skins[@]} skin(s) from target_skins input" @@ -119,7 +121,7 @@ jobs: git diff --name-only -z --diff-filter=AM "$latest_tag" HEAD \ | while IFS= read -r -d '' file; do [[ $file == Skins/* ]] && echo "${file#Skins/}" | cut -d/ -f1 - done + done | sort -u ) echo " ✓ Found ${#skins[@]} added/modified skins" @@ -128,7 +130,7 @@ jobs: git diff --name-only -z --diff-filter=D "$latest_tag" HEAD \ | while IFS= read -r -d '' file; do [[ $file == Skins/* ]] && echo "${file#Skins/}" | cut -d/ -f1 - done + done | sort -u ) if [ "${#deleted_skins[@]}" -gt 0 ]; then for d in "${deleted_skins[@]}"; do @@ -170,6 +172,7 @@ jobs: echo "" echo "[Detect Changed Skin Directories Complete — ${#uniq_skins[@]} skins processed, ${#deleted_skins[@]} skins deleted]" + - name: Pull Git LFS objects for changed skins (and core assets) shell: bash run: | @@ -217,9 +220,8 @@ jobs: shell: bash run: | echo "Creating base directories for assets..." - mkdir -p "$REPO_SCREENSHOT_DIR" "$REPO_MOD_ICONS_DIR" "$REPO_RANKING_PANEL_DIR" "$OSK_PATH" + mkdir -p "$REPO_SCREENSHOT_DIR" "$REPO_MOD_ICONS_DIR" "$REPO_RANKING_PANEL_DIR" "$OSK_PATH" "$REPO_THUMBNAIL_DIR" - # Read the newline-delimited list back into an array readarray -t skins <<< "$ALL_SKINS_DIR" for skin in "${skins[@]}"; do @@ -228,7 +230,8 @@ jobs: "$REPO_SCREENSHOT_DIR/$skin" \ "$REPO_MOD_ICONS_DIR/$skin" \ "$REPO_RANKING_PANEL_DIR/$skin" \ - "$OSK_PATH/$skin" + "$OSK_PATH/$skin" \ + "$REPO_THUMBNAIL_DIR/$skin" done echo "All asset directories created for ${#skins[@]} skins." @@ -279,50 +282,73 @@ jobs: [ ! -d "$SKIN_DIR" ] && { echo "Skipping missing skin: $skin_path"; continue; } SKIN_NAME="$skin_path" - OUT_GIF_DIR="$REPO_SCREENSHOT_DIR/$SKIN_NAME" + OUT_VIDEO_DIR="$REPO_SCREENSHOT_DIR/$SKIN_NAME" OUT_PNG_DIR="$REPO_RANKING_PANEL_DIR/$SKIN_NAME" + OUT_THUMBNAIL_DIR="$REPO_THUMBNAIL_DIR/$SKIN_NAME" echo "" echo "[$INDEX/$SKIN_COUNT] Generating for skin: $SKIN_NAME" LOGFILE="/tmp/danser_log_$INDEX.txt" - FFMPEG_LOG="/tmp/ffmpeg_log_$INDEX.txt" echo " → Generating video..." if ! xvfb-run -a "$DANSER_DIR/danser-cli" \ - -replay "$REPLAY_PATH" -record -skip -start=215 -end=230 -noupdatecheck \ + -replay "$GAMEPLAY_REPLAY_PATH" -record -skip -start=300 -end=307 -noupdatecheck \ -out="$SKIN_NAME" -skin="$SKIN_NAME" >"$LOGFILE" 2>&1; then echo " ✖ Video failed for $SKIN_NAME"; cat "$LOGFILE"; INDEX=$((INDEX+1)); continue fi - echo " → Taking screenshot..." - if ! xvfb-run -a "$DANSER_DIR/danser-cli" \ - -replay "$REPLAY_PATH" -skip -noupdatecheck -ss 243 \ - -out="$SKIN_NAME" -skin="$SKIN_NAME" >>"$LOGFILE" 2>&1; then - echo " ✖ Screenshot failed for $SKIN_NAME"; cat "$LOGFILE"; INDEX=$((INDEX+1)); continue - fi - if [ -f "$DANSER_VIDEO_DIR/$SKIN_NAME.mp4" ]; then - echo " → Converting to GIF..." - if ffmpeg -y -hwaccel cuda -ss 4 -t 10 -i "$DANSER_VIDEO_DIR/$SKIN_NAME.mp4" \ - -filter_complex "[0:v] fps=24,scale=720:-1:flags=lanczos,palettegen [p]; [0:v] fps=24,scale=720:-1:flags=lanczos [x]; [x][p] paletteuse" \ - -c:v gif "$DANSER_VIDEO_DIR/$SKIN_NAME.gif" >"$FFMPEG_LOG" 2>&1; then - mv "$DANSER_VIDEO_DIR/$SKIN_NAME.gif" "$OUT_GIF_DIR/$SKIN_NAME.gif" - echo " ✓ GIF moved to $OUT_GIF_DIR/" + echo " → Trimming MP4 with ffmpeg..." + ffmpeg -hide_banner -loglevel error \ + -ss 5 -t 6.5 \ + -i "$DANSER_VIDEO_DIR/$SKIN_NAME.mp4" \ + -c:v h264_nvenc -preset fast \ + -c:a aac -b:a 128k \ + "$DANSER_VIDEO_DIR/${SKIN_NAME}_trimmed.mp4" + + if [ -f "$DANSER_VIDEO_DIR/${SKIN_NAME}_trimmed.mp4" ]; then + mv "$DANSER_VIDEO_DIR/${SKIN_NAME}_trimmed.mp4" "$DANSER_VIDEO_DIR/$SKIN_NAME.mp4" + mkdir -p "$OUT_VIDEO_DIR" + mv "$DANSER_VIDEO_DIR/$SKIN_NAME.mp4" "$OUT_VIDEO_DIR/$SKIN_NAME.mp4" + echo " ✓ Trimmed MP4 moved to $OUT_VIDEO_DIR/" else - echo " ✖ FFmpeg conversion failed for $SKIN_NAME"; cat "$FFMPEG_LOG" + echo " ✖ ffmpeg trimming failed for $SKIN_NAME" fi else echo " ✖ No MP4 found for $SKIN_NAME" fi + echo " → Taking screenshot..." + if ! xvfb-run -a "$DANSER_DIR/danser-cli" \ + -replay "$PANEL_REPLAY_PATH" -skip -noupdatecheck -ss 28 \ + -out="$SKIN_NAME" -skin="$SKIN_NAME" >>"$LOGFILE" 2>&1; then + echo " ✖ Screenshot failed for $SKIN_NAME"; cat "$LOGFILE"; INDEX=$((INDEX+1)); continue + fi + if [ -f "$DANSER_SCREENSHOT_DIR/$SKIN_NAME.png" ]; then + mkdir -p "$OUT_PNG_DIR" mv "$DANSER_SCREENSHOT_DIR/$SKIN_NAME.png" "$OUT_PNG_DIR/$SKIN_NAME.png" echo " ✓ PNG moved to $OUT_PNG_DIR/" else echo " ✖ No PNG found for $SKIN_NAME" fi + echo " → Taking thumbnail screenshot..." + if ! xvfb-run -a "$DANSER_DIR/danser-cli" \ + -replay "$THUMBNAIL_REPLAY_PATH" -skip -noupdatecheck -ss 1.3 \ + -out="${SKIN_NAME}_thumb" -skin="$SKIN_NAME" >>"$LOGFILE" 2>&1; then + echo " ✖ Thumbnail screenshot failed for $SKIN_NAME"; cat "$LOGFILE"; INDEX=$((INDEX+1)); continue + fi + + if [ -f "$DANSER_SCREENSHOT_DIR/${SKIN_NAME}_thumb.png" ]; then + mkdir -p "$OUT_THUMBNAIL_DIR" + mv "$DANSER_SCREENSHOT_DIR/${SKIN_NAME}_thumb.png" "$OUT_THUMBNAIL_DIR/$SKIN_NAME.png" + echo " ✓ Thumbnail PNG moved to $OUT_THUMBNAIL_DIR/" + else + echo " ✖ No thumbnail PNG found for $SKIN_NAME" + fi + INDEX=$((INDEX + 1)) done @@ -370,18 +396,21 @@ jobs: if [ -n "$name_line" ]; then val="${name_line#*:}" val="$(echo "$val" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')" - sanitized="$(sanitize_filename "$val")" - [ -n "$sanitized" ] && skin_header="$sanitized" + if [ -n "$val" ]; then + sanitized="$(sanitize_filename "$val")" + [ -n "$sanitized" ] && skin_header="$sanitized" + fi fi fi - GIF_DIR="$REPO_SCREENSHOT_DIR/$SKIN_DIR_NAME" + VIDEO_DIR="$REPO_SCREENSHOT_DIR/$SKIN_DIR_NAME" PNG_DIR="$REPO_RANKING_PANEL_DIR/$SKIN_DIR_NAME" + THUMBNAIL_DIR="$REPO_THUMBNAIL_DIR/$SKIN_DIR_NAME" - if [ -f "$GIF_DIR/$SKIN_DIR_NAME.gif" ] && [ "$SKIN_DIR_NAME" != "$skin_header" ]; then - mv -f "$GIF_DIR/$SKIN_DIR_NAME.gif" \ - "$GIF_DIR/$skin_header.gif" || true - echo " ✓ Renamed GIF to $GIF_DIR/$skin_header.gif" + if [ -f "$VIDEO_DIR/$SKIN_DIR_NAME.mp4" ] && [ "$SKIN_DIR_NAME" != "$skin_header" ]; then + mv -f "$VIDEO_DIR/$SKIN_DIR_NAME.mp4" \ + "$VIDEO_DIR/$skin_header.mp4" || true + echo " ✓ Renamed MP4 to $VIDEO_DIR/$skin_header.mp4" fi if [ -f "$PNG_DIR/$SKIN_DIR_NAME.png" ] && [ "$SKIN_DIR_NAME" != "$skin_header" ]; then @@ -390,13 +419,19 @@ jobs: echo " ✓ Renamed PNG to $PNG_DIR/$skin_header.png" fi + if [ -f "$THUMBNAIL_DIR/$SKIN_DIR_NAME.png" ] && [ "$SKIN_DIR_NAME" != "$skin_header" ]; then + mv -f "$THUMBNAIL_DIR/$SKIN_DIR_NAME.png" \ + "$THUMBNAIL_DIR/$skin_header.png" || true + echo " ✓ Renamed thumbnail to $THUMBNAIL_DIR/$skin_header.png" + fi + INDEX=$((INDEX + 1)) done echo "" echo "[Asset Renaming Complete — processed $SKIN_COUNT skins]" - - name: Generate Mod Icons + - name: Generate Mod Icons (WEBP) shell: bash run: | echo "[Mod Icon Generation Job Started]" @@ -438,8 +473,10 @@ jobs: if [ -n "$name_line" ]; then val="${name_line#*:}" val="$(echo "$val" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')" - sanitized="$(sanitize_filename "$val")" - [ -n "$sanitized" ] && skin_header="$sanitized" + if [ -n "$val" ]; then + sanitized="$(sanitize_filename "$val")" + [ -n "$sanitized" ] && skin_header="$sanitized" + fi fi fi @@ -449,7 +486,7 @@ jobs: ICON_FOLDER="$SKIN_DIR" OUTPUT_DIR="$REPO_MOD_ICONS_DIR/$skin_path" mkdir -p "$OUTPUT_DIR" - OUTPUT="$OUTPUT_DIR/${skin_header}-mod-icons.png" + OUTPUT="$OUTPUT_DIR/${skin_header}-mod-icons.webp" row_images=() row_index=1 @@ -475,8 +512,10 @@ jobs: magick montage "${row_images[@]}" \ -tile "1x${#row_images[@]}" -geometry "+10+10" -background none \ - "$OUTPUT" - rm row_*.png + "temp_combined.png" + + magick "temp_combined.png" -define webp:lossless=true "$OUTPUT" + rm temp_combined.png row_*.png echo " ✓ Mod Icons Generated at $OUTPUT" INDEX=$((INDEX + 1)) @@ -487,6 +526,40 @@ jobs: echo "" echo "[Mod Icon Generation Finished — processed $SKIN_COUNT skins]" + - name: Convert PNGs to WEBPs + shell: bash + run: | + echo "[Convert PNG → WEBP Started]" + + if [ -z "${CHANGED_SKINS_FILE:-}" ] || [ ! -s "$CHANGED_SKINS_FILE" ]; then + echo "No skins changed. Skipping conversion." + exit 0 + fi + + mapfile -t skins < "$CHANGED_SKINS_FILE" + [ "${#skins[@]}" -eq 0 ] && { echo "No skins to process. Exiting."; exit 0; } + + convert_pngs_to_webp() { + local base_dir="$1" + local skin_path="$2" + local dir="$base_dir/$skin_path" + echo " → Processing: $dir" + [ ! -d "$dir" ] && { echo " ✖ Directory does not exist: $dir"; return; } + find "$dir" -type f -iname "*.png" | while read -r png; do + webp="${png%.png}.webp" + echo " ↳ Converting: $png → $webp" + magick "$png" -define webp:lossless=false -quality 90 "$webp" && rm -f "$png" + done + } + + for skin_path in "${skins[@]}"; do + [ -z "$skin_path" ] && continue + convert_pngs_to_webp "$REPO_RANKING_PANEL_DIR" "$skin_path" + convert_pngs_to_webp "$REPO_THUMBNAIL_DIR" "$skin_path" + done + + echo "[Convert PNG → WEBP Finished]" + - name: Create OSK Files shell: bash run: | @@ -525,8 +598,10 @@ jobs: if [ -n "$name_line" ]; then val="${name_line#*:}" val="$(echo "$val" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')" - sanitized="$(sanitize_filename "$val")" - [ -n "$sanitized" ] && skin_header="$sanitized" + if [ -n "$val" ]; then + sanitized="$(sanitize_filename "$val")" + [ -n "$sanitized" ] && skin_header="$sanitized" + fi fi fi @@ -549,27 +624,28 @@ jobs: - name: Generate README shell: bash run: | - echo "Starting README generation..." + echo "Generating README index…" sanitize_filename() { echo "$1" | \ tr -d '\000-\037' | \ - sed -e 's#[\\/:\*\?"<>|]#-#g' | \ - sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' + sed -e 's#[\\/:\*\?"<>|]#-#g' -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' } url_encode_path() { - local raw_path="$1" - echo "$raw_path" | tr -d '\r\n' | jq -sRr @uri + local IFS='/' + local parts=($1) + local encoded="" + for part in "${parts[@]}"; do + [ -n "$encoded" ] && encoded+="/" + encoded+=$(printf '%s' "$part" | jq -sRr @uri) + done + echo "$encoded" } SKINS_JSON_FILE="${{ github.workspace }}/.gitea/workflows/skins.json" DESC_FILE=$(mktemp) - echo "Step 1: Extracting descriptions from skins.json..." - jq -r '.descriptions | to_entries[] | "\(.key)=\(.value)"' "$SKINS_JSON_FILE" > "$DESC_FILE" - - echo "Step 2: Starting to build README..." echo "---" > "$README_PATH" echo "gitea: none" >> "$README_PATH" echo "include_toc: true" >> "$README_PATH" @@ -583,33 +659,41 @@ jobs: echo "" >> "$README_PATH" echo "**Go back to [osc/skins]($REGISTRY_URL/osc/skins)**" >> "$README_PATH" echo "" >> "$README_PATH" + echo "**Click on the Skin name to download it, or click on the thumbnail to see more about the skin, including a video preview, screenshots, and mod icons.**" >> "$README_PATH" + echo "" >> "$README_PATH" + + jq -r '.descriptions | to_entries[] | "\(.key)=\(.value)"' "$SKINS_JSON_FILE" > "$DESC_FILE" + jq -r '.order[]?' "$SKINS_JSON_FILE" > order.txt get_desc() { - grep -F -m1 -- "$1=" "$DESC_FILE" 2>/dev/null | cut -d '=' -f2- + grep -F -m1 -- "$1=" "$DESC_FILE" 2>/dev/null | cut -d '=' -f2- || true } - ORDER_FILE=$(mktemp) - echo "Step 3: Extracting order from skins.json..." - jq -r '.order[]?' "$SKINS_JSON_FILE" > "$ORDER_FILE" - - echo "Step 4: Processing ordered skins..." + declare -A ordered while IFS= read -r skin; do - echo " Processing skin (order): $skin" + ordered["$skin"]=1 dir="$DANSER_SKINS_DIR/$skin" - [ ! -d "$dir" ] && { echo " Skipping missing directory: $dir"; continue; } + [ ! -d "$dir" ] && continue - ini_file=$(find "$dir" -maxdepth 1 -iname "skin.ini" | head -n1) + ini_file=$(find "$dir" -maxdepth 1 -iname "skin.ini" | head -n1 || true) skin_header="$skin" + if [ -f "$ini_file" ]; then - line=$(grep -i '^[[:space:]]*Name:' "$ini_file" | head -n1 2>/dev/null || true) - [ -n "$line" ] && { - val="${line#*:}" - skin_header=$(sanitize_filename "$val") - } + name_line=$(grep -a -i -m1 'Name[[:space:]]*:' "$ini_file" || true) + if [ -n "$name_line" ]; then + val="${name_line#*:}" + val="$(echo "$val" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')" + [ -n "$val" ] && skin_header=$(sanitize_filename "$val") + else + skin_header=$(sanitize_filename "$skin") + fi + else + continue fi raw_path="$(printf "%s/%s" "$skin" "$skin_header" | sed 's/^ *//;s/ *$//')" base_path=$(url_encode_path "$raw_path") + echo "## [$skin_header]($REGISTRY_URL/$USER_REPOSITORY/media/tag/$new_tag/export/${base_path}.osk)" >> "$README_PATH" echo "" >> "$README_PATH" @@ -617,71 +701,56 @@ jobs: [ -n "$desc" ] && { echo "$desc" >> "$README_PATH"; echo "" >> "$README_PATH"; } if [ -f "$ini_file" ]; then - author_line=$(grep -i '^[[:space:]]*Author:' "$ini_file" | head -n1 2>/dev/null || true) + author_line=$(grep -i '^[[:space:]]*Author:' "$ini_file" | head -n1 || true) if [ -n "$author_line" ]; then - author="${author_line#*:}" - author="$(sanitize_filename "$author")" + author=$(echo "$author_line" | cut -d ':' -f2- | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') [ -n "$author" ] && { echo "**Author:** $author" >> "$README_PATH"; echo "" >> "$README_PATH"; } fi fi - echo "![$skin_header Gameplay](media/gameplay/${base_path}.gif)" >> "$README_PATH" + echo "[![$skin_header Thumbnail](media/thumbnail/${base_path}.webp)](/docs/${base_path}.md)" >> "$README_PATH" echo "" >> "$README_PATH" - echo "![$skin_header Ranking Panel](media/panel/${base_path}.png)" >> "$README_PATH" - echo "" >> "$README_PATH" - echo "![$skin_header Mods](media/icons/${base_path}-mod-icons.png)" >> "$README_PATH" - echo "" >> "$README_PATH" - done < "$ORDER_FILE" + done < order.txt - declare -A ordered - while IFS= read -r skin; do - ordered["$skin"]=1 - done < "$ORDER_FILE" + for dir in "$DANSER_SKINS_DIR"/*; do + [ -d "$dir" ] || continue + skin="$(basename "$dir")" + [[ -n "${ordered[$skin]}" ]] && continue - echo "Step 5: Processing extra skins..." - if [ ! -s "$ORDER_FILE" ]; then - echo "→ No \`order\` defined in skins.json; skipping extra-skins loop." - else - for dir in "$DANSER_SKINS_DIR"/*; do - [ -d "$dir" ] || continue - skin=$(basename "$dir") - [[ -n "${ordered[$skin]}" ]] && continue + ini_file=$(find "$dir" -maxdepth 1 -iname "skin.ini" | head -n1 || true) + skin_header="$skin" - echo " Writing extra skin: $skin" - ini_file=$(find "$dir" -maxdepth 1 -iname "skin.ini" | head -n1) - skin_header="$skin" - if [ -f "$ini_file" ]; then - line=$(grep -i '^[[:space:]]*Name:' "$ini_file" | head -n1 2>/dev/null || true) - [ -n "$line" ] && { - val="${line#*:}" - skin_header=$(sanitize_filename "$val") - } + if [ -f "$ini_file" ]; then + name_line=$(grep -a -i -m1 'Name[[:space:]]*:' "$ini_file" || true) + if [ -n "$name_line" ]; then + val="${name_line#*:}" + val="$(echo "$val" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')" + [ -n "$val" ] && skin_header=$(sanitize_filename "$val") + else + skin_header=$(sanitize_filename "$skin") fi + else + continue + fi - raw_path="$(printf "%s/%s" "$skin" "$skin_header" | sed 's/^ *//;s/ *$//')" - base_path=$(url_encode_path "$raw_path") - echo "## [$skin_header]($REGISTRY_URL/$USER_REPOSITORY/media/tag/$new_tag/export/${base_path}.osk)" >> "$README_PATH" - echo "" >> "$README_PATH" + raw_path="$(printf "%s/%s" "$skin" "$skin_header" | sed 's/^ *//;s/ *$//')" + base_path=$(url_encode_path "$raw_path") - if [ -f "$ini_file" ]; then - author_line=$(grep -i '^[[:space:]]*Author:' "$ini_file" | head -n1 2>/dev/null || true) - if [ -n "$author_line" ]; then - author="${author_line#*:}" - author="$(sanitize_filename "$author")" - [ -n "$author" ] && { echo "**Author:** $author" >> "$README_PATH"; echo "" >> "$README_PATH"; } - fi + echo "## [$skin_header]($REGISTRY_URL/$USER_REPOSITORY/media/tag/$new_tag/export/${base_path}.osk)" >> "$README_PATH" + echo "" >> "$README_PATH" + + if [ -f "$ini_file" ]; then + author_line=$(grep -i '^[[:space:]]*Author:' "$ini_file" | head -n1 || true) + if [ -n "$author_line" ]; then + author=$(echo "$author_line" | cut -d ':' -f2- | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + [ -n "$author" ] && { echo "**Author:** $author" >> "$README_PATH"; echo "" >> "$README_PATH"; } fi + fi - echo "![$skin_header Gameplay](media/gameplay/${base_path}.gif)" >> "$README_PATH" - echo "" >> "$README_PATH" - echo "![$skin_header Ranking Panel](media/panel/${base_path}.png)" >> "$README_PATH" - echo "" >> "$README_PATH" - echo "![$skin_header Mods](media/icons/${base_path}-mod-icons.png)" >> "$README_PATH" - echo "" >> "$README_PATH" - done - fi + echo "[![$skin_header Thumbnail](media/thumbnail/${base_path}.webp)](/docs/${base_path}.md)" >> "$README_PATH" + echo "" >> "$README_PATH" + done - echo "Step 7: Writing Build History section..." echo "# Build History" >> "$README_PATH" echo "" >> "$README_PATH" echo "| Version | Date |" >> "$README_PATH" @@ -699,7 +768,91 @@ jobs: done fi - echo "README generation completed successfully." + echo "README index generated successfully." + + - name: Generate Per-Skin Pages + shell: bash + run: | + echo "Generating detailed per-skin markdown pages…" + + sanitize_filename() { + echo "$1" | \ + tr -d '\000-\037' | \ + sed -e 's#[\\/:\*\?"<>|]#-#g' | \ + sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' + } + + url_encode_path() { + local IFS='/' + local parts=($1) + local encoded="" + for part in "${parts[@]}"; do + [ -n "$encoded" ] && encoded+="/" + encoded+=$(printf '%s' "$part" | jq -sRr @uri) + done + echo "$encoded" + } + + mkdir -p "$DOC_DIR" + + for dir in "$DANSER_SKINS_DIR"/*; do + [ -d "$dir" ] || continue + + skin=$(basename "$dir") + ini_file=$(find "$dir" -maxdepth 1 -iname "skin.ini" | head -n1 || true) + skin_header="$skin" + + if [ -f "$ini_file" ]; then + line=$(grep -i '^[[:space:]]*Name:' "$ini_file" | head -n1 || true) + if [ -n "$line" ]; then + val="${line#*:}" + val="$(echo "$val" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')" + if [ -n "$val" ]; then + skin_header=$(sanitize_filename "$val") + fi + fi + fi + + raw_path="${skin}/${skin_header}" + base_path=$(url_encode_path "$raw_path") + osk_url="$REGISTRY_URL/$USER_REPOSITORY/media/tag/$new_tag/export/${base_path}.osk" + md_file_path="${DOC_DIR}/${raw_path}.md" + + mkdir -p "$(dirname "$md_file_path")" + + video_url="$REGISTRY_URL/$USER_REPOSITORY/raw/tag/$new_tag/media/gameplay/${base_path}.mp4" + + author="" + if [ -f "$ini_file" ]; then + author_line=$(grep -i '^[[:space:]]*Author:' "$ini_file" | head -n1 || true) + if [ -n "$author_line" ]; then + author=$(echo "$author_line" | cut -d ':' -f2- | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + fi + fi + + { + echo "# [$skin_header]($osk_url)" + echo "" + [ -n "$author" ] && echo "**Author:** $author" + [ -n "$author" ] && echo "" + + echo "## Hitsounds" + echo "" + echo "" + + echo "## Ranking Panel" + echo "![](/media/panel/${base_path}.webp)" + echo "" + + echo "## Mod Icons" + echo "![](/media/icons/${base_path}-mod-icons.webp)" + } > "$md_file_path" + + echo " → Wrote $md_file_path" + done + + echo "Per-skin markdown pages complete." - name: Cleanup Extra Files shell: bash @@ -707,7 +860,6 @@ jobs: set -euo pipefail echo "[Cleanup Extra Files Started]" - [ -d src/docs ] && rm -rf src/docs [ -f how-to-use.md ] && rm -f how-to-use.md readarray -t skins <<< "$ALL_SKINS_DIR" @@ -719,7 +871,34 @@ jobs: sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' } - for root in "$REPO_SCREENSHOT_DIR" "$REPO_RANKING_PANEL_DIR" "$REPO_MOD_ICONS_DIR" "$OSK_PATH"; do + prune_dir() { + local root="$1" + local skin="$2" + local expected="$3" + + for f in "$root"/*; do + [ -f "$f" ] || continue + name="$(basename "$f")" + if printf '%s\n' "${skins[@]}" | grep -Fxq -- "$name"; then + continue + fi + echo " → Removing unexpected root file: $f" + rm -f "$f" + done + + dir="$root/$skin" + [ -d "$dir" ] || return + for f in "$dir"/*; do + [ -e "$f" ] || continue + if [[ "$(basename "$f")" != "$expected" ]]; then + echo " → Removing unexpected file: $f" + rm -f "$f" + fi + done + } + + for root in "$REPO_SCREENSHOT_DIR" "$REPO_RANKING_PANEL_DIR" "$REPO_MOD_ICONS_DIR" "$REPO_THUMBNAIL_DIR" "$OSK_PATH" "$DOC_DIR"; do + [ -d "$root" ] || continue for dir in "$root"/*; do [ -d "$dir" ] || continue name="$(basename "$dir")" @@ -736,34 +915,17 @@ jobs: if [[ -f "$ini" ]]; then raw=$(grep -i '^[[:space:]]*Name:' "$ini" | head -n1 || true) raw="${raw#*:}" + raw="$(echo "$raw" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')" tmp_header=$(sanitize_filename "$raw") - if [[ -n "$tmp_header" ]]; then - header="$tmp_header" - fi + [[ -n "$tmp_header" ]] && header="$tmp_header" fi - expect_gif="$header.gif" - expect_png="$header.png" - expect_icon="$header-mod-icons.png" - expect_osk="$header.osk" - - prune_dir() { - root=$1; expected=$2 - dir="$root/$skin" - [ ! -d "$dir" ] && return - for f in "$dir"/*; do - [ -e "$f" ] || continue - if [[ "$(basename "$f")" != "$expected" ]]; then - echo " → Removing unexpected file: $f" - rm -f "$f" - fi - done - } - - prune_dir "$REPO_SCREENSHOT_DIR" "$expect_gif" - prune_dir "$REPO_RANKING_PANEL_DIR" "$expect_png" - prune_dir "$REPO_MOD_ICONS_DIR" "$expect_icon" - prune_dir "$OSK_PATH" "$expect_osk" + prune_dir "$REPO_SCREENSHOT_DIR" "$skin" "$header.mp4" + prune_dir "$REPO_RANKING_PANEL_DIR" "$skin" "$header.webp" + prune_dir "$REPO_MOD_ICONS_DIR" "$skin" "$header-mod-icons.webp" + prune_dir "$REPO_THUMBNAIL_DIR" "$skin" "$header.webp" + prune_dir "$OSK_PATH" "$skin" "$header.osk" + prune_dir "$DOC_DIR" "$skin" "$header.md" done echo "[Cleanup Extra Files Complete]" @@ -779,7 +941,7 @@ jobs: run: | git config advice.addIgnoredFile false - for p in media/gameplay media/panel media/icons export README.md how-to-use.md src; do + for p in docs/ media/gameplay media/thumbnail media/panel media/icons export README.md how-to-use.md src; do if [ -e "$p" ]; then git add -A "$p" fi