Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 42 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ name: Release
# tag v* publishes them as a GitHub release (a pre-release while the version has a
# suffix such as -preview1), with Docs/releases/<tag>.md as the notes when there is one;
# running the workflow by hand, or a pull request that changes the packaging or the
# video, only keeps them as workflow artifacts.
# video, only keeps them as workflow artifacts. Run by hand with video_for set to a tag,
# it renders only the video and replaces it, and its SHA256SUMS line, on that release.
on:
push:
tags: ['v*']
Expand All @@ -15,6 +16,11 @@ on:
- Util/package-release.sh
- media/launch/**
workflow_dispatch:
inputs:
video_for:
description: Render only the launch video and replace it on this release (a tag, such as v4.0.0)
required: false
default: ''

permissions:
contents: read
Expand All @@ -31,6 +37,7 @@ env:
jobs:
package:
name: ${{ matrix.rid }}
if: github.event_name != 'workflow_dispatch' || inputs.video_for == ''
runs-on: ${{ matrix.os }}
# macOS is untested (README, Platforms): a failure there does not hold the release back.
continue-on-error: ${{ matrix.experimental }}
Expand Down Expand Up @@ -142,7 +149,8 @@ jobs:
name: launch video
runs-on: ubuntu-latest
continue-on-error: true
timeout-minutes: 45
# A render takes about 7 minutes; a failed one can leave hyperframes waiting.
timeout-minutes: 20
steps:
- uses: actions/checkout@v7

Expand All @@ -153,6 +161,10 @@ jobs:
- name: FFmpeg
run: sudo apt-get update -qq && sudo apt-get install -y -qq ffmpeg

# The music is not in the repository; the script fetches the pinned recording.
- name: Music
run: bash media/launch/tools/fetch-music.sh --profile

# lint is static and must pass; check also audits layout and contrast in the
# browser and only warns here. The version is pinned in package.json.
- name: Check and render
Expand Down Expand Up @@ -219,3 +231,31 @@ jobs:
gh release create "$GITHUB_REF_NAME" dist/* \
--repo "$GITHUB_REPOSITORY" --title "IronRuby ${GITHUB_REF_NAME#v}" \
"${notes[@]}" --generate-notes $prerelease

# By hand: a new render of the video on a release that is already out. Only the video
# and its line in SHA256SUMS change; the archives stay as they were published.
replace-video:
name: replace the video on ${{ inputs.video_for }}
if: github.event_name == 'workflow_dispatch' && inputs.video_for != ''
needs: video
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/download-artifact@v8
with:
name: ironruby-launch-video
path: dist

- name: Upload
working-directory: dist
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ inputs.video_for }}
run: |
gh release download "$TAG" --repo "$GITHUB_REPOSITORY" --pattern SHA256SUMS
sum=$(sha256sum ironruby-4-launch.mp4)
{ grep -v ' ironruby-4-launch.mp4$' SHA256SUMS; echo "$sum"; } > SHA256SUMS.new
mv SHA256SUMS.new SHA256SUMS
cat SHA256SUMS
gh release upload "$TAG" ironruby-4-launch.mp4 SHA256SUMS --clobber --repo "$GITHUB_REPOSITORY"
4 changes: 4 additions & 0 deletions Docs/releases/v4.0.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,7 @@ database drivers), and macOS is built but untested. The

Thanks to everyone who built IronRuby at Microsoft and in IronLanguages, and to the prism
and ruby/spec projects, without which none of this would be measurable.

*Music in the launch video: Edvard Grieg, In the Hall of the Mountain King, played by the
Czech National Symphony Orchestra for Musopen; public domain, from
[classicals.de](https://www.classicals.de/grieg-peer-gynt).*
1 change: 1 addition & 0 deletions media/launch/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ renders/
snapshots/
node_modules/
.hyperframes/
assets/music/
24 changes: 20 additions & 4 deletions media/launch/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,22 @@ the title frame.
| `assets/fonts/` | Inter and JetBrains Mono (SIL Open Font License, the licences beside them), so a render needs no network for fonts |
| `assets/images/notepad.png` | [`Samples/Notepad`](../../Samples/Notepad) editing its own source, captured on Windows 11 at 200 % |
| `poster.jpg` | The title frame with a play button |
| `teaser.webp` | 23 s of the film at 960x540 and 10 fps: the title, the irb session, .NET and the Notepad, the end card. Frames seeked from `index.html` in Chrome, encoded with ImageMagick (`magick -delay 10 -loop 0 f*.png -resize 960x540 -quality 72 teaser.webp`), 1.3 MB |
| `teaser.webp` | 23 s of the film at 960x540 and 10 fps: the title, the irb session, .NET and the Notepad, the end card. Frames seeked from `index.html` in Chrome, encoded with ImageMagick (`magick -delay 10 -loop 0 f*.png -resize 960x540 -quality 72 teaser.webp`), 1.3 MB; silent, as images are |
| `tools/fetch-music.sh` | Fetches the music into `assets/music/` (not in git), checked against a pinned SHA-256 |

## Music

Edvard Grieg, *In the Hall of the Mountain King* (Peer Gynt Suite No. 1, Op. 46, IV),
played by the Czech National Symphony Orchestra for [Musopen](https://musopen.org), from
[classicals.de](https://www.classicals.de/grieg-peer-gynt). The recording is in the public
domain (Creative Commons Public Domain Mark 1.0), so it needs no licence; it is credited
here and in the release notes all the same.

The repository does not carry it: `tools/fetch-music.sh` downloads it before a render.
The quiet opening plays under the 2011 scene; at the title the film cuts into the full
orchestra (`data-media-start` on `#music-main`), placed so that the piece's last chord
falls on the end card. `tools/fetch-music.sh --profile` prints the recording's loudness
every half second, which is how the cut was placed.

Every figure on screen comes from the project README (ruby/spec counts, benchmark ratios,
versions), and every line of code runs on IronRuby 4.0. Change the README, change the film.
Expand All @@ -25,13 +40,14 @@ pinned in `package.json`.

```sh
cd media/launch
tools/fetch-music.sh # the music, into assets/music/
npm run lint
npm run check # also audits layout and contrast in the browser
npm run render # renders/ironruby-4-launch.mp4, 60 fps
npm run preview # the HyperFrames studio, to scrub and edit
```

`.github/workflows/release.yml` does the same on Ubuntu for every `v*` tag (and for pull
requests that touch this folder) and attaches the MP4 to the release.

The film is silent: it is meant to play muted in a feed.
requests that touch this folder) and attaches the MP4 to the release. Run by hand with
`video_for: v4.0.0`, it renders only the film and replaces it on that release, along with
its line in `SHA256SUMS`.
16 changes: 16 additions & 0 deletions media/launch/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,15 @@
</head>
<body>
<div id="root" data-composition-id="main" data-start="0" data-width="1920" data-height="1080" data-duration="55">
<!-- Grieg, In the Hall of the Mountain King (public domain; tools/fetch-music.sh fetches
it for the render). The recording is 154.1 s; from its loudness (fetch-music.sh
--profile): the opening is near -40 LUFS, the full orchestra enters at 101 s, the
general pause is 147.5-148.3 s, the final chords 148.4-150.4 s. So the opening
plays under 2011, 11 dB up, and the film cuts in at 104.1 s with the title: the
pause falls just before the end card, and the final chord on it (49.6 s). -->
<audio id="music-intro" src="assets/music/mountain-king.mp3" data-start="0" data-duration="5.9" data-media-start="0" data-volume="3.5" data-track-index="10"></audio>
<audio id="music-main" src="assets/music/mountain-king.mp3" data-start="5.3" data-duration="49.7" data-media-start="104.1" data-track-index="11"></audio>

<div id="backdrop" class="clip" data-start="0" data-duration="55" data-track-index="0">
<div class="grid" id="grid"></div>
<div class="glow" id="glow-ruby"></div>
Expand Down Expand Up @@ -297,6 +306,13 @@
tl.to(state, { v: value, duration: dur, ease: "power2.out", onUpdate: () => { el.textContent = Math.round(state.v).toLocaleString("en-US"); } }, at);
}

// Music: the orchestra is there with the title, and fades out at the very end. The
// opening keeps its static data-volume (3.5, +11 dB): a volume tween is a media
// element's volume, which only goes from 0 to 1, and the render fails beyond that.
// It needs no fade either: the recording begins in silence, and the cut covers its end.
tl.fromTo("#music-main", { volume: 0 }, { volume: 1, duration: 0.25, ease: "none" }, 5.3);
tl.to("#music-main", { volume: 0, duration: 1.2, ease: "none" }, 53.8);

// The backdrop drifts through the whole film.
tl.fromTo("#glow-ruby", { x: 0, y: 0 }, { x: 260, y: 140, duration: 55, ease: "sine.inOut" }, 0);
tl.fromTo("#glow-violet", { x: 0, y: 0 }, { x: -320, y: -180, duration: 55, ease: "sine.inOut" }, 0);
Expand Down
51 changes: 51 additions & 0 deletions media/launch/tools/fetch-music.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/usr/bin/env bash
# Fetches the launch video's music: Edvard Grieg, "In the Hall of the Mountain King"
# (Peer Gynt Suite No. 1, Op. 46, IV), played by the Czech National Symphony Orchestra
# for Musopen, from classicals.de. The recording is in the public domain, marked
# CC PDM 1.0 (https://www.classicals.de/grieg-peer-gynt), so it may be used in the video
# without conditions; the release notes credit it all the same.
#
# media/launch/tools/fetch-music.sh # -> media/launch/assets/music/mountain-king.mp3
# media/launch/tools/fetch-music.sh --profile # and print its loudness every half second
#
# The repository does not carry the recording: the render fetches it, and the checksum
# pins the exact file, so a different download cannot slip into a release unnoticed.
# --profile is how the cuts in index.html were placed (data-media-start) without
# listening: it prints the momentary loudness (EBU R128, LUFS) over time.
set -euo pipefail

URL='https://www.quantumdigitalmedia.de/Classicals-Music/Music/MO%20Collection/Classicals.de%20-%20Grieg%20-%20Peer%20Gynt%20Suite%20No.%201%2C%20Op.%2046%20-%20IV.%20In%20the%20Hall%20Of%20The%20Mountain%20King.zip'
SHA256='78ef3c248c376997d0175ef534ab3243677e4323f4a8ff3cb07f698a3441cd7a' # of the zip, 2024-10-27

here=$(cd "$(dirname "$0")/.." && pwd)
out="$here/assets/music/mountain-king.mp3"
mkdir -p "$(dirname "$out")"
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT

curl -fsSL --retry 3 -o "$tmp/music.zip" "$URL"
actual=$(sha256sum "$tmp/music.zip" | cut -d' ' -f1)
if [ -z "$SHA256" ]; then
echo "fetch-music.sh: no checksum pinned yet; this download is sha256 $actual" >&2
elif [ "$actual" != "$SHA256" ]; then
echo "fetch-music.sh: the download is sha256 $actual, not the pinned $SHA256" >&2
exit 1
fi

unzip -l "$tmp/music.zip"
# The zip also holds a macOS resource fork (__MACOSX/._<name>.mp3), which is not audio.
mp3=$(unzip -Z1 "$tmp/music.zip" | grep -v '^__MACOSX/' | grep -i '\.mp3$' | head -1)
if [ -z "$mp3" ]; then
echo "fetch-music.sh: no MP3 in the download" >&2
exit 1
fi
unzip -p "$tmp/music.zip" "$mp3" > "$out"
echo "fetch-music.sh: $mp3 -> $out ($(wc -c < "$out") bytes)"

if [ "${1:-}" = --profile ]; then
ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1 "$out"
# ebur128 reports every 100 ms; keep every fifth report.
ffmpeg -nostats -hide_banner -i "$out" -af ebur128=metadata=1,ametadata=print:key=lavfi.r128.M -f null - 2>&1 |
awk '/pts_time/ { split($0, a, "pts_time:"); t = a[2] + 0 }
/lavfi.r128.M=/ { split($0, b, "="); if (n++ % 5 == 0) printf "%7.1f s %6.1f LUFS\n", t, b[2] }'
fi
Loading