Skip to content

Generate Timelapse for 2023-08-15 - 2023-09-30 #35

Generate Timelapse for 2023-08-15 - 2023-09-30

Generate Timelapse for 2023-08-15 - 2023-09-30 #35

Workflow file for this run

name: Timelapse
run-name: "Generate Timelapse for ${{ format('{0} - {1}', inputs.start_date, inputs.end_date) }}"
on:
# schedule:
# - cron: "45 6 * * *"
# - cron: "30 6 * * SUN"
# workflow_call:
workflow_dispatch:
inputs:
create_pr:
type: boolean
description: "Create a Pull Request for the video?"
default: true
start_date:
type: string
description: "Start date (no earlier than '2023-06-03'):"
required: true
end_date:
type: string
description: "End date, in 'YYYY-MM-DD' format:"
required: true
video_title:
type: string
description: "Video title (supported placeholders are '{date_start}', '{date_end}', '{date_range}'):"
default: "Timelapse for {date_range}"
required: false
video_description:
type: string
description: "Video Description:"
default: "Las Vegas Formula 1 Track construction from {date_range}"
required: false
video_filename:
type: string
description: "Output Filename:"
default: "timelapse_{date_start}_{date_end}.mp4"
required: false
video_size:
description: "Resolution (width x height):"
type: string
default: "1024x768"
required: false
video_fps:
description: "Framerate (fps):"
type: number
default: 5
required: false
video_codec:
description: "Codec (-vcodec flag):"
default: libx264
type: string
required: false
video_format:
type: string
description: "Format flags (-vf flag):"
default: "scale=-2:1080,format=yuv420p"
required: false
jobs:
generate_video:
name: "Generate Timelapse Video"
runs-on: ubuntu-latest
outputs:
files: ${{ steps.collect.outputs.files }}
dates: ${{ steps.collect.outputs.dates }}
all_dates: ${{ steps.collect.outputs.all_dates }}
timeframe: ${{ steps.collect.outputs.timeframe }}
start_date: ${{ steps.collect.outputs.start_date }}
end_date: ${{ steps.collect.outputs.end_date }}
date_range: ${{ steps.collect.outputs.date_range }}
title: ${{ steps.collect.outputs.title }}
description: ${{ steps.collect.outputs.description }}
filename: ${{ steps.collect.outputs.filename }}
env:
CACHE_KEY: ${{ inputs.start_date }}-${{ inputs.end_date }}
IS_SCHEDULED: ${{ ((github.event_name == 'schedule' && 'true') || 'false') }}
IS_WORKFLOW_DISPATCH: ${{ ((github.event_name == 'workflow_dispatch' && 'true') || 'false') }}
IS_SCHEDULED_DAILY: ${{ ((github.event_name == 'schedule' && github.event.schedule == '45 6 * * *' && 'true') || 'false') }}
IS_SCHEDULED_WEEKLY: ${{ ((github.event_name == 'schedule' && github.event.schedule == '30 6 * * SUN' && 'true') || 'false') }}
IS_WORKFLOW_CALL: ${{ ((github.event_name == 'workflow_call' && 'true') || 'false') }}
VIDEO_END_DATE: ${{ (inputs.end_date || '') }}
VIDEO_START_DATE: ${{ (inputs.start_date || '') }}
VIDEO_TITLE: ${{ (inputs.video_title || 'Timelapse for {date_range}') }}
VIDEO_DESCRIPTION: ${{ (inputs.video_description || 'Timelapse video for {date_range}') }}
VIDEO_FILENAME: ${{ (inputs.video_filename || format('timelapse_{0}_-_{1}.mp4', inputs.start_date, inputs.end_date)) }}
GITHUB_REPOSITORY: ${{ github.repository }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_ACTOR: ${{ github.actor }}
steps:
- name: "Checkout Repo (sparse)"
id: collect
run: |
# using custom checkout logic rather than actions/checkout
REPO="https://${GITHUB_ACTOR}:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git"
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git config --global fetch.parallel 32
git clone --filter=blob:none --no-checkout --depth 1 --sparse $REPO .
git sparse-checkout init --cone
git sparse-checkout add . .github/workflows src src/kv src/helpers
all_dates=($(git ls-tree --name-only -d -r HEAD:assets .))
TOTAL_DATES="${#all_dates[@]}"
MAX_TIMEFRAME=60
DEFAULT_TIMEFRAME=7
VIDEO_TIMEFRAME=${DEFAULT_TIMEFRAME}
if [[ "$IS_SCHEDULED" == "true" ]]; then
[[ "$IS_SCHEDULED_DAILY" == "true" ]] && VIDEO_TIMEFRAME=1
[[ "$IS_SCHEDULED_WEEKLY" == "true" ]] && VIDEO_TIMEFRAME=7
fi
if ((VIDEO_TIMEFRAME > TOTAL_DATES)); then
VIDEO_TIMEFRAME="$TOTAL_DATES"
elif ((VIDEO_TIMEFRAME > MAX_TIMEFRAME)); then
VIDEO_TIMEFRAME="$MAX_TIMEFRAME"
fi
start_date="${VIDEO_START_DATE:-}"
end_date="${VIDEO_END_DATE:-}"
if [ -z "${VIDEO_START_DATE:-}" ] && [ -z "${VIDEO_END_DATE:-}" ]; then
start_date="${all_dates[@]:$(($((TOTAL_DATES - 1)) - VIDEO_TIMEFRAME)):1}"
end_date="${all_dates[@]:$((TOTAL_DATES - 1)):1}"
else
start_date="${VIDEO_START_DATE-}"
end_date="${VIDEO_END_DATE-}"
# clamp end_date to the latest date in the array
if [[ "$end_date" > "${all_dates[@]: -1}" ]]; then
end_date="${all_dates[@]: -1}"
if [[ "$start_date" > "$end_date" || "$start_date" == "$end_date" ]]; then
start_date="$(date -d "$end_date -${timeframe} days" +%Y-%m-%d)"
fi
fi
# clamp start_date to the earliest date in the array
if [[ "$start_date" < "${all_dates[@]:0:1}" ]]; then
start_date="${all_dates[@]:0:1}"
if [[ "$end_date" < "$start_date" || "$end_date" == "$start_date" ]]; then
end_date="$(date -d "$start_date +${timeframe} days" +%Y-%m-%d)"
fi
fi
fi
date_range="$start_date"
if [[ "$start_date" != "$end_date" ]]; then
date_range="$start_date - $end_date"
fi
# determine the 'timeframe' as the time between dates, in days.
timeframe=$(($(($(date -d "$end_date" +%s) - $(date -d "$start_date" +%s))) / 86400))
((timeframe <= 0)) && timeframe=1
VIDEO_TIMEFRAME=$timeframe
# collect the dates and files, add them to the sparse checkout
dates=()
files=()
for cur in "${all_dates[@]}"; do
if [[ "$cur" > "$start_date" || "$cur" == "$start_date" ]]; then
if [[ "$cur" < "$end_date" || "$cur" == "$end_date" ]]; then
dates+=("$cur")
git sparse-checkout add "assets/$cur"
files+=($(git ls-files assets/$cur))
fi
fi
done
git checkout
echo "all_dates=$(printf '%s\n' "${all_dates[@]}" | jq -R . | jq -c -s '@json')" >>$GITHUB_OUTPUT
echo "dates=$(printf '%s\n' "${dates[@]}" | jq -R . | jq -c -s '@json')" >>$GITHUB_OUTPUT
echo "files=$(printf '%s\n' "${files[@]}" | jq -R . | jq -c -s '@json')" >>$GITHUB_OUTPUT
VIDEO_TITLE=${VIDEO_TITLE//"{date_start}"/$start_date}
VIDEO_TITLE=${VIDEO_TITLE//"{date_end}"/$end_date}
VIDEO_TITLE=${VIDEO_TITLE//"{date_range}"/$date_range}
VIDEO_DESCRIPTION=${VIDEO_DESCRIPTION//"{date_start}"/$start_date}
VIDEO_DESCRIPTION=${VIDEO_DESCRIPTION//"{date_end}"/$end_date}
VIDEO_DESCRIPTION=${VIDEO_DESCRIPTION//"{date_range}"/$date_range}
VIDEO_FILENAME=${VIDEO_FILENAME//"{date_start}"/$start_date}
VIDEO_FILENAME=${VIDEO_FILENAME//"{date_end}"/$end_date}
VIDEO_FILENAME=${VIDEO_FILENAME//"{date_range}"/"$(echo "$date_range" | tr -d ' ')"}
echo "VIDEO_TITLE=${VIDEO_TITLE}" >>$GITHUB_ENV
echo "VIDEO_DESCRIPTION=${VIDEO_DESCRIPTION}" >>$GITHUB_ENV
echo "VIDEO_FILENAME=${VIDEO_FILENAME}" >>$GITHUB_ENV
echo "VIDEO_TIMEFRAME=${VIDEO_TIMEFRAME}" >>$GITHUB_ENV
echo "VIDEO_START_DATE=${VIDEO_START_DATE}" >>$GITHUB_ENV
echo "VIDEO_END_DATE=${VIDEO_END_DATE}" >>$GITHUB_ENV
echo "timeframe=$timeframe" >>$GITHUB_OUTPUT
echo "start_date=$start_date" >>$GITHUB_OUTPUT
echo "end_date=$end_date" >>$GITHUB_OUTPUT
echo "date_range=$date_range" >>$GITHUB_OUTPUT
echo "title=${VIDEO_TITLE}" >>$GITHUB_OUTPUT
echo "description=${VIDEO_DESCRIPTION}" >>$GITHUB_OUTPUT
echo "filename=${VIDEO_FILENAME}" >>$GITHUB_OUTPUT
echo "is_scheduled=${IS_SCHEDULED}" >>$GITHUB_OUTPUT
echo "is_workflow_dispatch=${IS_WORKFLOW_DISPATCH}" >>$GITHUB_OUTPUT
echo "is_workflow_call=${IS_WORKFLOW_CALL}" >>$GITHUB_OUTPUT
echo "is_scheduled_daily=${IS_SCHEDULED_DAILY}" >>$GITHUB_OUTPUT
echo "is_scheduled_weekly=${IS_SCHEDULED_WEEKLY}" >>$GITHUB_OUTPUT
- id: cache
name: "Cache Assets"
uses: actions/cache@v3
with:
key: ${{ runner.os }}-assets-${{ hashFiles('assets/**/*.jpg') }}
path: assets
restore-keys: |
${{ runner.os }}-assets-${{ env.CACHE_KEY }}
${{ runner.os }}-assets-${{ github.sha }}
${{ runner.os }}-assets-
- name: install ffmpeg
run: |
if ! command -v ffmpeg &>/dev/null; then
if command -v brew &>/dev/null; then
brew install --force ffmpeg
else
NONINTERACTIVE=1 \
sudo apt-get update && \
sudo apt-get install -y ffmpeg --no-install-recommends && \
sudo apt-get clean && \
sudo rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
fi
fi
- id: generate
name: Generate Timelapse
env:
all_dates: ${{ steps.collect.outputs.all_dates }}
start_date: ${{ steps.collect.outputs.start_date }}
end_date: ${{ steps.collect.outputs.end_date }}
date_range: ${{ steps.collect.outputs.date_range }}
timeframe: ${{ steps.collect.outputs.timeframe }}
title: ${{ env.VIDEO_TITLE || steps.collect.outputs.title }}
description: ${{ env.VIDEO_DESCRIPTION || steps.collect.outputs.description }}
path: ${{ steps.collect.outputs.path }}
VIDEO_FILENAME: ${{ env.VIDEO_FILENAME || steps.collect.outputs.filename }}
VIDEO_FPS: ${{ env.VIDEO_FPS || inputs.video_fps }}
VIDEO_CODEC: ${{ env.VIDEO_CODEC || inputs.video_codec }}
VIDEO_SIZE: ${{ env.VIDEO_SIZE || inputs.video_size }}
VIDEO_FORMAT: ${{ env.VIDEO_FORMAT || inputs.video_format }}
VIDEO_DATES: ${{ env.VIDEO_DATES || steps.collect.outputs.dates }}
VIDEO_FILES: ${{ env.VIDEO_FILES || steps.collect.outputs.files }}
RUNNER_TEMP: ${{ runner.temp }}
run: |
TIMELAPSE_DIR="$(mktemp -d -t timelapse-XXXXXXXXXX)"
VIDEO_DATES=($(jq -r 'fromjson | .[]' <<<"$VIDEO_DATES"))
VIDEO_FILES=($(jq -r 'fromjson | .[]' <<<"$VIDEO_FILES"))
for date in "${VIDEO_DATES[@]}"; do
cp -r "./assets/$date" "$TIMELAPSE_DIR/"
done
OUTPUT_DIR="assets/timelapse"
VIDEO_FILENAME="${VIDEO_FILENAME:-"timelapse_${date_range// /_}.mp4"}"
OUTPUT_PATH="${OUTPUT_DIR-}/${VIDEO_FILENAME-}"
mkdir -p "${OUTPUT_DIR-}"
if [ -f "${OUTPUT_PATH-}" ]; then
# If the file already exists, append a timestamp to the filename
OUTPUT_PATH="${OUTPUT_DIR-}/$(date +%s)_${VIDEO_FILENAME:-}"
fi
echo "OUTPUT_PATH=${OUTPUT_PATH:-}" >>$GITHUB_ENV
echo "path=${OUTPUT_PATH:-}" >>$GITHUB_OUTPUT
echo "title=${VIDEO_TITLE:-}" >>$GITHUB_OUTPUT
echo "description=${VIDEO_DESCRIPTION:-}" >>$GITHUB_OUTPUT
echo "filename=${VIDEO_FILENAME:-}" >>$GITHUB_OUTPUT
MAX_FPS=30
VIDEO_FPS=${VIDEO_FPS:-5}
VIDEO_R=$((VIDEO_FPS * 2))
if ((VIDEO_FPS > MAX_FPS)); then
VIDEO_FPS=$MAX_FPS
VIDEO_R=$MAX_FPS
elif ((VIDEO_FPS < 1)); then
VIDEO_FPS=1
VIDEO_R=1
fi
DEBUG_LOG="debug_log_$(date +%Y-%m-%d)_${RANDOM}.log"
echo "debug_log=${DEBUG_LOG-}" >>$GITHUB_OUTPUT
DEBUG_LOG="${RUNNER_TEMP}/${DEBUG_LOG}"
touch "$DEBUG_LOG"
cat >> "$DEBUG_LOG" <<__EOF_1
📸 Image Count:
${#VIDEO_FILES[@]} from ${#VIDEO_DATES[@]} days
📅 Image Dates:
${VIDEO_START_DATE:-} - ${VIDEO_END_DATE:-}
📝 Video Title:
${VIDEO_TITLE:-}
ℹ️ Description:
${VIDEO_DESCRIPTION:-}
📎 Output File:
${VIDEO_FILENAME:-}
🌳 Branch Name:
${BRANCH_NAME:-}
🏁 Flags Used:
-framerate ${VIDEO_FPS}
-pattern_type glob
-i "${TIMELAPSE_DIR-}/*/*.jpg"
-s "${VIDEO_SIZE:-"1024x768"}"
-vcodec ${VIDEO_CODEC:-"libx264"}
-vf "${VIDEO_FORMAT:-"scale=-2:1080,format=yuv420p"}"
-r $((VIDEO_FPS * 2))
-an
-tune film
-movflags +faststart
-metadata title="${title:-}"
-metadata description="${description:-}"
-metadata year="$(date +%Y)"
-metadata date="$(date +%Y-%m-%d)"
-metadata comment="Generated by github.com/${GITHUB_REPOSITORY:-nberlette/f1}"
"${OUTPUT_PATH:-}" 2>&1
------------------------------------------------------------------
FFMPEG OUTPUT
------------------------------------------------------------------
__EOF_1
ffmpeg \
-framerate $VIDEO_FPS \
-pattern_type glob \
-i "${TIMELAPSE_DIR-}/*/*.jpg" \
-s "${VIDEO_SIZE:-"1024x768"}" \
-vcodec ${VIDEO_CODEC:-"libx264"} \
-vf "${VIDEO_FORMAT:-"scale=-2:1080,format=yuv420p"}" \
-r $VIDEO_R \
-an \
-tune film \
-movflags +faststart \
-metadata title="${title:-}" \
-metadata description="${description:-}" \
-metadata year="$(date +%Y)" \
-metadata date="$(date +%Y-%m-%d)" \
-metadata comment="Generated by github.com/${GITHUB_REPOSITORY:-nberlette/f1}" \
"${OUTPUT_PATH:-}" 2>&1 | tee -a "$DEBUG_LOG"
- if: success()
name: Upload Artifact
uses: actions/upload-artifact@v3
continue-on-error: true
env:
OUTPUT_PATH: ${{ env.OUTPUT_PATH }}
VIDEO_FILENAME: ${{ env.VIDEO_FILENAME }}
with:
path: ${{ env.OUTPUT_PATH }}
name: ${{ env.VIDEO_FILENAME }}
- if: always() && inputs.create_pr == 'true'
id: create_pr
name: "Commit + Create Pull Request"
run: |
OUTPUT_PATH=${{ steps.generate.outputs.path }}
VIDEO_TITLE=${{ steps.generate.outputs.title }}
START_DATE=${{ steps.collect.outputs.start_date }}
END_DATE=${{ steps.collect.outputs.end_date }}
TIMEFRAME=${{ steps.collect.outputs.timeframe }}
BRANCH_NAME="timelapse/${START_DATE}${END_DATE:+_${END_DATE}}"
COMMIT_BODY=""
COMMIT_BODY+=$(printf "| Label | %-56s |\n" "Value")
COMMIT_BODY+=$(printf "| :-------------- | %-56s |\n" "$(printf '%-56s' ":" | tr ' ' '-')")
COMMIT_BODY+=$(printf "| **Title** | %-56s |\n" "$VIDEO_TITLE")
COMMIT_BODY+=$(printf "| **Start Date** | %-56s |\n" "$START_DATE")
COMMIT_BODY+=$(printf "| **End Date** | %-56s |\n" "$END_DATE")
COMMIT_BODY+=$(printf "| **Timeframe** | %-56s |\n" "$TIMEFRAME")
COMMIT_BODY+=$(printf "| **Video Size** | %-56s |\n" "$VIDEO_SIZE")
COMMIT_BODY+=$(printf "| **Video FPS** | %-56s |\n" "$VIDEO_FPS")
COMMIT_BODY+=$(printf "| **Video Codec** | %-56s |\n" "$VIDEO_CODEC")
COMMIT_BODY+=$(printf "| **Video Flags** | %-56s |\n" "$VIDEO_FORMAT")
COMMIT_MSG="$(
printf \
"feat: 🎬 new timelapse for %s\n\nVideo Details:\n\n$%s" \
"${VIDEO_START_DATE}${VIDEO_END_DATE:+" - ${VIDEO_END_DATE}" \
"$COMMIT_BODY"
)"
git checkout -b "$BRANCH_NAME"
git add --sparse "$OUTPUT_PATH"
git commit -m "$COMMIT_MSG"
git push --set-upstream origin "$BRANCH_NAME"
VIDEO_URL="https://github.com/${GITHUB_REPOSITORY}/raw/${BRANCH_NAME}/${OUTPUT_PATH}"
PR_BODY=$(printf "## 📝 Video Details\n\n%s\n\n" "$COMMIT_BODY")
PR_BODY+=$(printf "## 📺 Download the full video\n\n- [x] [%s](%s)\n\n---\n\n" "$VIDEO_TITLE" "$VIDEO_URL")
PR_BODY+="👋 Project maintainers, please review and merge this PR when ready!"
PR_BODY+=$'\n- 🤖 The F1 Bot\n\n'
PR_BODY+="> **Note**: if this automated PR is incorrect, please open an issue."
PR_TITLE=$(printf '🎬 New Timelapse for %s (%d days)' "$START_DATE${END_DATE:+ - $END_DATE}" "$TIMEFRAME")
PR_PROJECT="Timelapse"
PR_LABELS=(assets automated timelapse)
ALL_LABELS=($(gh label list --json name --jq '.[].name'))
for label in "${PR_LABELS[@]}"; do
if [[ ! " ${ALL_LABELS[@]} " =~ " ${label} " ]]; then
# create a hex color code for the label (deterministic based on the label name)
label_color=$(echo -n "$label" | md5sum | cut -c1-6)
gh label create "$label" -c "#${label_color}"
fi
done
gh pr create \
-B "main" \
-t "$PR_TITLE" \
-b "$PR_BODY" \
-l "${PR_LABELS}$(printf ',%s' "${PR_LABELS[@]:1}" \
-r "$GITHUB_ACTOR" \
-a "$GITHUB_ACTOR"
- if: always() && env.DEBUG_LOG
name: Upload Debug Log
uses: actions/upload-artifact@v3
continue-on-error: true
env:
DEBUG_LOG: ${{ steps.generate.outputs.debug_log }}
with:
path: ${{ runner.temp }}/${{ env.DEBUG_LOG }}
name: ${{ env.DEBUG_LOG }}