Skip to content

Generate 3day Timelapse for 2023-10-06 #8

Generate 3day Timelapse for 2023-10-06

Generate 3day Timelapse for 2023-10-06 #8

Workflow file for this run

name: Timelapse
run-name: "Generate ${{ inputs.timeframe }}day Timelapse for ${{ inputs.start_date }}"
on:
schedule:
- cron: "45 6 * * *"
- cron: "30 6 * * SUN"
workflow_call:
workflow_dispatch:
inputs:
timeframe:
type: number
description: |
Time range (in days) to generate the timelapse video for. Value must
be between no less than 1.
required: true
default: "1"
start_date:
type: string
description: |
Provide a custom start date for the generated timelapse video. The
format should be 'YYYY-MM-DD'. If no start date is provided, it will
be calculated using the 'timeframe' subtracted from the latest date.
required: false
video_title:
type: string
description: |
Provide a custom title for the generated video. Supports several
different placeholders for dynamic values:
'{start_date}', '{end_date}', '{timeframe}', '{date_range}'
default: "Timelapse for {date_range}"
required: false
video_description:
type: string
description: |
Provide a custom description for the generated video. The same
placeholder rules apply as with titles.
default: "Timelapse video for {date_range}"
required: false
video_filename:
type: string
description: |
Provide a custom filename for the generated video. The extension
should be '.mp4'. The same placeholder rules apply here as they do in
the title and description inputs.
default: "timelapse_{date_range}.mp4"
required: false
video_width:
type: number
description: |
Provide a custom width for the generated video. The height will be
calculated automatically based on the aspect ratio of the images.
The default value is 1920.
default: "1920"
required: false
video_fps:
type: number
description: "Provide a custom framerate for the generated video."
default: "15"
required: false
video_crf:
type: number
description: |
Provide a custom CRF value for the generated video. The default value
is 17.
default: "17"
required: false
video_preset:
type: string
description: |
Provide a custom preset for the generated video. The default value is
'slow'.
default: "slow"
required: false
video_movflags:
type: string
description: |
Provide custom movflags for the generated video. The default value is
'+faststart'.
default: "+faststart"
required: false
jobs:
cache:
name: "Cache Assets"
runs-on: ubuntu-latest
steps:
- name: Configure Git
run: |
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
- name: Checkout Repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Cache Assets Directory
id: cache-hit
uses: actions/cache@v3
with:
path: assets
key: ${{ runner.os }}-assets-${{ github.sha }}
restore-keys: |
${{ runner.os }}-assets-${{ github.sha }}
${{ runner.os }}-assets-
metadata:
name: "Collect Metadata and Files"
needs: cache
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 }}
is_scheduled: ${{ steps.collect.outputs.is_scheduled }}
is_workflow_dispatch: ${{ steps.collect.outputs.is_workflow_dispatch }}
is_workflow_call: ${{ steps.collect.outputs.is_workflow_call }}
is_scheduled_daily: ${{ steps.collect.outputs.is_scheduled_daily }}
is_scheduled_weekly: ${{ steps.collect.outputs.is_scheduled_weekly }}
steps:
- name: Restore Cache
uses: actions/cache@v3
with:
path: assets
key: ${{ runner.os }}-assets-${{ github.sha }}
- id: collect
name: "(setup) collect items"
env:
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') }}
INPUT_TIMEFRAME: ${{ fromJson(inputs.timeframe || '1') }}
VIDEO_START_DATE: ${{ (inputs.start_date || '') }}
run: |
all_dates=($(cd assets && env ls -t1 --time=ctime -I "*.jpg"))
VIDEO_TIMEFRAME=1
if [[ "$IS_SCHEDULED" == "true" ]]; then
if [[ "$IS_SCHEDULED_DAILY" == "true" ]]; then
VIDEO_TIMEFRAME=1
elif [[ "$IS_SCHEDULED_WEEKLY" == "true" ]]; then
VIDEO_TIMEFRAME=7
fi
else
VIDEO_TIMEFRAME="${INPUT_TIMEFRAME:-1}"
fi
if ((VIDEO_TIMEFRAME > ${#all_dates[@]})); then
VIDEO_TIMEFRAME=${#all_dates[@]}
elif ((VIDEO_TIMEFRAME > 60)); then
VIDEO_TIMEFRAME=60
fi
if [ -z "${VIDEO_START_DATE:-}" ]; then
# If a start date is not provided, take the latest date
start_date="${all_dates[$((-1 - VIDEO_TIMEFRAME))]}"
end_date="${all_dates[-1]}"
else
start_date="${VIDEO_START_DATE-}"
timeframe="${VIDEO_TIMEFRAME-}"
end_date="$(date -j -v+${timeframe}d -f "%Y-%m-%d" "$start_date" +%Y-%m-%d)"
# if the start_date is later than the latest date in the array
# (index -1), then just use ${all_dates[-1]} as the start date.
if [[ "$end_date" > "${all_dates[-1]}" ]]; then
end_date="${all_dates[-1]}"
start_date="$(date -j -v-${timeframe}d -f "%Y-%m-%d" "$end_date" +%Y-%m-%d)"
fi
# if the start_date is earlier than the earliest date in the
# array (index 0), then just use ${all_dates[0]} as the start date.
if [[ "$start_date" < "${all_dates[0]}" ]]; then
start_date="${all_dates[0]}"
end_date="$(date -j -v+${timeframe}d -f "%Y-%m-%d" "$start_date" +%Y-%m-%d)"
fi
fi
date_range="$start_date"
if [ "$start_date" != "$end_date" ]; then
date_range="$start_date - $end_date"
fi
echo "timeframe='$VIDEO_TIMEFRAME'" >>$GITHUB_OUTPUT
echo "start_date='$start_date'" >>$GITHUB_OUTPUT
echo "end_date='$end_date'" >>$GITHUB_OUTPUT
echo "date_range='$date_range'" >>$GITHUB_OUTPUT
dates=()
files=()
for date in "${all_dates[@]}"; do
if [[ "$date" > "$start_date" || "$date" == "$start_date" ]]; then
if [[ "$date" < "$end_date" || "$date" == "$end_date" ]]; then
dates+=("$date")
files_in_date=($(ls assets/$date/*.jpg))
files=("${files[@]}" "${files_in_date[@]}")
fi
fi
done
# stringify the arrays to json
all_dates_str="$(printf '%s\n' "${all_dates[@]}" | jq -R . | jq --indent 0 -s .)"
dates_str="$(printf '%s\n' "${dates[@]}" | jq -R . | jq --indent 0 -s .)"
files_str="$(printf '%s\n' "${files[@]}" | jq -R . | jq --indent 0 -s .)"
echo "all_dates='${all_dates_str-}'" >>$GITHUB_OUTPUT
echo "dates='${dates_str-}'" >>$GITHUB_OUTPUT
echo "files='${files_str-}'" >>$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
generate_video:
name: "Generate Timelapse Video"
needs: metadata
runs-on: ubuntu-latest
steps:
- name: install ffmpeg
run: brew install ffmpeg
- name: Generate Timelapse
env:
VIDEO_FILES: ${{ needs.metadata.outputs.files }}
VIDEO_DATES: ${{ needs.metadata.outputs.dates }}
VIDEO_TIMEFRAME: ${{ needs.metadata.outputs.timeframe }}
# VIDEO_ALL_DATES: ${{ fromJson(needs.metadata.outputs.all_dates) }}
VIDEO_START_DATE: ${{ needs.metadata.outputs.start_date }}
VIDEO_END_DATE: ${{ needs.metadata.outputs.end_date }}
VIDEO_DATE_RANGE: ${{ needs.metadata.outputs.date_range }}
VIDEO_IS_SCHEDULED: ${{ fromJson(needs.metadata.outputs.is_scheduled) }}
VIDEO_IS_WORKFLOW_DISPATCH: ${{ fromJson(needs.metadata.outputs.is_workflow_dispatch) }}
VIDEO_IS_WORKFLOW_CALL: ${{ fromJson(needs.metadata.outputs.is_workflow_call) }}
VIDEO_IS_SCHEDULED_DAILY: ${{ fromJson(needs.metadata.outputs.is_scheduled_daily) }}
VIDEO_IS_SCHEDULED_WEEKLY: ${{ fromJson(needs.metadata.outputs.is_scheduled_weekly) }}
VIDEO_TITLE: ${{ inputs.video_title }}
VIDEO_DESCRIPTION: ${{ inputs.video_description }}
VIDEO_FILENAME: ${{ inputs.video_filename }}
VIDEO_WIDTH: ${{ inputs.video_width }}
VIDEO_FPS: ${{ inputs.video_fps }}
VIDEO_CRF: ${{ inputs.video_crf }}
VIDEO_PRESET: ${{ inputs.video_preset }}
VIDEO_MOVFLAGS: ${{ inputs.video_movflags }}
run: |
# Create a temp directory for the input files
TIMELAPSE_DIR="$(mktemp -d -t timelapse-XXXXXXXXXX)"
for date in "${VIDEO_DATES[@]}"; do
mkdir -p "$TIMELAPSE_DIR/$date"
cp -r "assets/$date" "$TIMELAPSE_DIR"
done
# Resolve placeholders for title, description, and filename
dates=($VIDEO_DATES)
start_date="${dates[0]}"
end_date="${dates[-1]}"
date_range="$start_date"
if [ "$start_date" != "$end_date" ]; then
date_range="$start_date - $end_date"
fi
declare -A placeholders
placeholders=(
["{start_date}"]=$start_date
["{end_date}"]=$end_date
["{date_range}"]=$date_range
)
for key in "${!placeholders[@]}"; do
VIDEO_TITLE=${VIDEO_TITLE//$key/${placeholders[$key]}}
VIDEO_DESCRIPTION=${VIDEO_DESCRIPTION//$key/${placeholders[$key]}}
VIDEO_FILENAME=${VIDEO_FILENAME//$key/${placeholders[$key]}}
done
OUTPUT_PATH="assets/timelapse/$VIDEO_FILENAME"
mkdir -p "assets/timelapse"
if [ -f "$OUTPUT_PATH" ]; then
# If the file already exists, append a timestamp to the filename
OUTPUT_PATH="assets/timelapse/$(date +%s)_$VIDEO_FILENAME"
fi
echo "path=$OUTPUT_PATH" >>$GITHUB_OUTPUT
echo "VIDEO_TITLE=$VIDEO_TITLE" >>$GITHUB_ENV
echo "VIDEO_DESCRIPTION=$VIDEO_DESCRIPTION" >>$GITHUB_ENV
echo "VIDEO_FILENAME=$VIDEO_FILENAME" >>$GITHUB_ENV
echo "OUTPUT_PATH=$OUTPUT_PATH" >>$GITHUB_ENV
# Generate the timelapse
ffmpeg \
-framerate $VIDEO_FPS \
-pattern_type glob \
-i "$TIMELAPSE_DIR/*/*.jpg" \
-s:v $VIDEO_WIDTH \
-c:v libx264 \
-crf $VIDEO_CRF \
-preset $VIDEO_PRESET \
-tune film \
-movflags $VIDEO_MOVFLAGS \
-metadata title="$VIDEO_TITLE" \
-metadata description="$VIDEO_DESCRIPTION" \
-metadata year="$(date +%Y)" \
-metadata date="$(date +%Y-%m-%d)" \
-metadata comment="Generated by github.com/nberlette/f1" \
-y "$OUTPUT_PATH"
# Cleanup
rm -rf "$TIMELAPSE_DIR"
- name: Commit and Push
env:
OUTPUT_PATH: ${{ env.OUTPUT_PATH }}
VIDEO_TITLE: ${{ env.VIDEO_TITLE }}
run: |
# Upload the timelapse to the repository
git add "$OUTPUT_PATH"
git commit -m "🎬 new timelapse: '$VIDEO_TITLE'"
git push
- name: Upload Artifact
uses: actions/upload-artifact@v3
env:
OUTPUT_PATH: ${{ env.OUTPUT_PATH }}
VIDEO_FILENAME: ${{ env.VIDEO_FILENAME }}
with:
path: ${{ env.OUTPUT_PATH }}
name: ${{ env.VIDEO_FILENAME }}