Skip to content
HendrikVreyPublic

About

A Windows player that turns a timelapse into a still picture that is always changing and never seen to change. Built for the video installation One Day.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Drift

A player that turns a timelapse into a still picture that is always changing and never seen to change. Each frame becomes the next through a random, pixel-by-pixel reveal so slow that nobody watching sees a transition; someone who comes back an hour later sees that the shadows have moved. Built for the video installation One Day.

Status: M2, the installation build. The dissolve runs on the GPU in a window, plays a video file or a folder of stills end to end on schedule, remembers playback settings, follows the time of day in clock mode, fills a chosen display from a command line, keeps the display awake, logs a file a day, exits rather than showing an error box, converts a phone's HDR and Display P3 colour for the wall, and ships as an installer, or as one folder to copy behind a television, with a scheduled task to keep it running. The offline renderer writes short videos of each reveal style from the client's own footage so the style can be chosen by eye. The hand-over on the real machine is M3; its page for the gallery, docs/leon.md, is written and waits for the machine's details.


Download

Download Drift-Setup.exe, for 64-bit Windows 10 and 11.

That link is permanent and serves the newest versioned release, from the first one onwards; until a version has been released it serves nothing, and the second link is the one to use. That second link is equally permanent and serves the newest build of master:

Drift-Setup.exe from the latest build of master

Every merge rebuilds it, and nothing is published unless the test suite passes, so a broken commit leaves the previous installer in place rather than replacing it. The version it reports looks like 0.1.0-dev.47, which is the base version plus the build that produced it. GitHub's /releases/latest/ deliberately skips prereleases, which is what keeps the two links apart and both of them permanent.

The installer is per-user. It asks for no administrator rights and shows no UAC prompt, it installs to %LOCALAPPDATA%\Programs\Drift with ffmpeg and its licence beside the exe and the installation documents under docs\, and it writes nothing to the registry but its own uninstall entry. No other account on the machine is touched, no file type is claimed (Drift opens video, but it is a player for one installation and not the machine's video player), and nothing runs at startup until Start with Windows is ticked on the panel (or the scheduled task in docs/installation.md is created by hand). Beyond accepting the licence, the one choice it offers is a desktop shortcut, unticked by default; the install folder is fixed.

Uninstalling removes the program and leaves %LOCALAPPDATA%\Drift (the settings, the appearance choice and the logs) in place: a log that says why the wall went dark is worth more than a clean uninstall. Delete that folder by hand to remove everything.

Windows SmartScreen will warn you the first time, because nothing here is code-signed. That is a real warning and worth treating as one: it means Windows cannot confirm who built this. Expect it to be more insistent for an installer than it is for a bare executable, because SmartScreen weights installers more heavily. If that is not a trade you want to make, build it yourself: the source is right here.

There is no portable ZIP. Publishing the folder yourself, below, is the no-install route, and it is the route the installation checklist was first written for.


What it does

Open a video. The first frame is shown. Over the chosen duration (ten seconds by default) the second frame replaces it one pixel at a time in a random order that has no direction, no edge and no pattern; at exactly the duration the second frame is complete and the third begins replacing it. Nothing else happens: no cuts, no fades, no motion.

Three reveal styles, one drop-down:

Style What it is
Random Every pixel flips at its own random moment. The specification's algorithm.
Even The same flip, ordered by a blue-noise tile so the revealed pixels are spread evenly at every scale, with no clumps and no gaps.
Soft Each pixel fades over a short window instead of flipping, for content where two frames are unrelated textures (leaves in wind).

The picture is aspect-fitted on black. F11 fills the screen: the title bar, the panel and the cursor hide after three seconds still and come back when the mouse moves; Escape returns the window. Space plays and pauses, Ctrl+O opens, Ctrl+Q quits; the close button at the top right quits too.

A folder of stills is a source too (Open stills): the photographs are shown in name order, IMG_2 before IMG_10, one per frame, at full still-image quality. That is the right source for a real day, because a phone's timelapse mode does not keep a fixed interval and a folder from an intervalometer does. JPEG, PNG, HEIC, TIFF, BMP and WebP; a JPEG's orientation tag is honoured; a stray still of another shape is shown at its own shape on black rather than stretched.

Colour is converted for the wall. A television driven by a PC is a BT.709 display, and phones do not shoot BT.709: an iPhone's ordinary video and its HEIC photographs are Display P3, its HDR video is HLG under Dolby Vision, and HDR10 from a camera or a phone is PQ. Shown untouched, P3 arrives slightly desaturated and HDR arrives dark and grey (a white sheet of paper under PQ at little more than half brightness). Drift reads what the file says about its colour and has ffmpeg convert the primaries to BT.709 and, for HDR, the transfer too, so the picture on the wall is the picture the phone showed. The read-out names the footage's colour when it is not plain BT.709 (Display P3, HDR (HLG)), the log says what was converted, and footage that is BT.709, untagged, or standard definition (whose primaries are within a few levels of BT.709, less than the converter's own error) takes the pipe it always took. Not read: an ICC profile inside a JPEG, which ffmpeg ignores, so a P3 JPEG shows as BT.709; an iPhone set to save HEIC is the way round that. A tag ffmpeg refuses outright (a logarithmic transfer, a cICP chunk naming reserved primaries) is replaced by BT.709 so the file plays as it is; the log names the tag.

The installation is set up on the panel. Set up in the title bar shows a second row: Follow the clock, with the hours the footage was filmed across (two pickers, 24-hour; the same time at both ends is the whole day) and what the wall shows outside them (the day's last frame held, or a black screen); the HDR footage look; and Start with Windows. Ticking Follow the clock opens the piece again at the frame the time of day names and takes the seconds and the loop away from the panel, because the day decides both; unticking it gives them back. Start with Windows registers the scheduled task described below for the open piece and the display the window is on, after one Windows permission prompt (the Task Scheduler refuses anything less), and unticking it removes the task; the box shows whether the task exists. The person operating the wall is the client, so everything the installation needs is on the panel and remembered; the command line below overrides it for one run.

What is remembered between runs, in %LOCALAPPDATA%\Drift\settings.json: the last video or folder, the seconds per frame, the reveal style, the softness, the cell size, whether it loops, whether it follows the clock, the hours, what the wall shows outside them, and the HDR look. A double-click on the exe always starts empty; the recorded source path is not reopened automatically. The settings are restored. The file can be edited by hand; a field it cannot use falls back to its default and the log names it.

Appearance is selected in the title bar: System (the default) follows Windows, including changes while Drift is open; Light and Dark keep the chosen theme. The choice is saved separately in %LOCALAPPDATA%\Drift\appearance.json, for example { "theme": "Dark" }. Both themes keep the picture itself on black. The title bar and playback controls hide together in full screen and return when the mouse moves.

What it says when something is wrong, in the panel and never in a dialog: Unable to open selected video., Video format or codec is not supported., Selected file no longer exists., Duration must be greater than 0.5 seconds., and for a folder with no pictures in it, No pictures were found in the selected folder. The log is in %LOCALAPPDATA%\Drift\logs\, one file per day named by the date, thirty days kept; a day that grows past four megabytes is moved aside once and started again. A crash is written there with the state the piece was in, and the process exits with code 70 instead of showing a Windows error box, so the scheduled task starts it again.

The command line

Everything the panel can set, the command line can set, and a switch that is absent leaves the remembered setting in force. The scheduled task names only what the panel cannot remember (the piece, full screen, the display, playing at once), so a change made on the panel takes effect at the machine's next start without the task being registered again:

Drift.exe [file-or-folder] [--duration 10] [--style random|even|soft] [--softness 0.1] [--block 1|2]
          [--loop] [--autoplay] [--fullscreen] [--display 1] [--clock 06:00-18:00] [--outside hold|black]
          [--hdr clip|soft]
  • --clock 06:00-18:00 is the installation's way of playing: the frame on the wall is chosen from the time of day, so the piece is at its morning in the morning whatever time the machine was switched on. The window's length divided by the number of frames is the transition duration (a twelve-hour capture at ten-second intervals comes to ten seconds a frame), the duration box shows it and takes no typing, and the last frame is complete exactly as the window closes. Outside the hours the wall holds that frame (--outside hold, the default) or goes black (--outside black), and the next morning's first frame dissolves out of whichever it was. A machine that sleeps, or a player started at half past, joins the day where the clock says and catches up by skipping frames rather than playing them fast. A window may cross midnight (22:00-04:00); a window equal at both ends is the whole day. The clock implies --autoplay, and the day is the loop, so --loop is ignored with it. Given on the command line, the clock, the hours and --outside are applied through the panel and remembered like anything set there.
  • --fullscreen --display 1 fills the display Windows lists first (the TV, once it is the only display); a number past the end falls back to the primary and says so. When the displays change (a television switched off and on), the window puts itself back on the configured display.
  • --autoplay starts playing once the source has opened; --loop dissolves the last frame into the first.
  • --hdr clip (the default) or --hdr soft is what becomes of the parts of HDR footage brighter than a white sheet of paper, once it is brought down to the wall's range. clip puts that white on the wall's white, so the room is as bright as the camera saw it, and the sky through the window, brighter than paper, is white too. soft keeps the gradation in that sky and the whole picture is dimmer for it, with paper at about two thirds. A look, chosen by eye on the real footage; it changes nothing for footage that is not HDR. Remembered from the panel like the clock.
  • A switch that is wrong is shown in the panel and in the log, in words naming it, and whatever else was readable is still applied. A path that is not there is refused in the specification's words.
  • Unattended means retried. In clock mode or with --loop, a failure (the footage disk not yet mounted at boot, a morning that could not start) is retried every thirty seconds until it succeeds, with the message in the panel meanwhile and the retries in the log. A player somebody is using leaves the message for them to read.

The installation

docs/installation.md is the one-page checklist for the machine behind the television: the installer or the published folder, the piece and its hours set on the panel, automatic sign-in, the scheduled task (registered by Start with Windows on the panel, or by hand from docs/scheduler/Drift.xml: one task that starts Drift at logon and every five minutes if it is not running, with no time limit and a restart on failure, running as the signed-in user), the power plan, and nothing on top of the picture. Drift itself keeps the display awake while it plays, turns Windows' error boxes off for itself and for ffmpeg, and rebuilds its graphics context if the driver resets. The installer puts the checklist, the page for the gallery and the task's XML under docs\ beside the exe, so the machine behind the television needs no repository on it.

Publishing the folder to copy instead (ffmpeg is placed beside the exe, with its licence; a publish without ffmpeg fetched is refused):

dotnet publish src\Drift.App -c Release -r win-x64 --self-contained -o publish\Drift

Building and running

.NET 10 SDK on Windows. ffmpeg is not committed; fetch it once as third-party/ffmpeg/README.md says, then:

dotnet build Drift.slnx
dotnet test Drift.slnx
dotnet run --project src\Drift.App -- "C:\path\to\timelapse.mov"

The window starts empty until Open video or Open stills is pressed. Only a file or folder explicitly named on the command line opens automatically; the previous selection is never reopened on a normal launch.

Running it from a build is the no-install route, and it is the answer to the SmartScreen warning above.

Building the installer

Also needs Inno Setup on the PATH. Publish the folder the installer packs first; the publish target puts ffmpeg, the licences and the installation documents inside it:

dotnet publish src\Drift.App\Drift.App.csproj -c Release -r win-x64 --self-contained true -p:Version=0.1.0 -o publish\win-x64
iscc installer\Drift.iss /DAppVersion=0.1.0 /DNumericVersion=0.1.0

Drift-Setup.exe lands in dist\. NumericVersion is separate because Inno Setup's VersionInfoVersion must be strictly numeric, and a version like 0.1.0-dev.47 is not. .github/workflows/release.yml runs exactly these commands, after fetching ffmpeg and checking it against the hash pinned in third-party/ffmpeg/README.md: every push to master rebuilds the rolling installer, and a v* tag publishes a versioned draft release.

The preview renderer

Writes a video of each style with the transitions sped up, so a ten-second reveal can be watched in a message:

dotnet run --project tools\Drift.Preview -- "C:\path\to\timelapse.mov" --out artifacts\preview --style all --speed 20 --seconds 20

--speed 1 --seconds 10 --vp9 10 renders one transition in real time at a held quality, which is the experiment that says what a pre-rendered day would cost (see the plan's section 5.3): at 720p a twelve-hour day comes to 13 GB and still loses a fifth of the revealed pixels to the codec at mid-transition; at 4K it is over 100 GB. The player exists because a codec cannot carry this effect.

How it is built

src/Drift.Core          pure: the clock, the reveal order, the state machine, the settings, the CPU compositor
src/Drift.Media         the only project that starts a process: ffprobe, ffmpeg in (a file or a stills folder), ffmpeg out, and the elevated schtasks behind Start with Windows
src/Drift.App           Avalonia window, the OpenGL canvas, the panel, the settings file, the log, the two Windows calls
tools/Drift.Preview     the offline renderer
tests/                  xunit: 549 tests across the three layers, plus the acceptance and seek tests on the sample clip when it is present
assets/blue-noise       the 512 x 512 reveal tile and the script that generated it
assets/icon             the icon and the script that generated it
docs/                   the installation checklist, the page for the gallery and the scheduled task
installer/              the Inno Setup script; .github/workflows builds it on every push and on every v* tag
third-party/ffmpeg      where ffmpeg lives (LGPL v3; binaries fetched, licence committed)
  • The effect is one fragment shader over two textures, run at source resolution into a framebuffer and drawn aspect-fitted in a second pass. The shuffled coordinate list the specification describes is implemented as a per-pixel random threshold compared against progress, which reveals pixels in exactly the same random order with no list, no per-frame CPU work and no memory growth.
  • The same arithmetic exists in C# (Drift.Core.Reveal.Compositor), so the offline renderer and the player draw the same picture, and a test asserts the shader still carries the same constants.
  • Three frames exist at once: current and next on the GPU, and the frame after next already decoded, so a flip never waits on the decoder. Buffers are allocated at open; nothing is allocated per frame.
  • The clock is scheduled, not measured. When a transition completes, the next one starts from where this one should have ended, so a twelve-hour day does not drift. In clock mode the schedule is the time of day itself: the same clock is fed the time since the window opened and anchored at the transition that time names, and a source is opened at the frame that transition needs (a seek for a video, the list from that still for a folder) rather than decoded from the start. The seek is to the midpoint between the container's own timestamps for the frame before and the frame wanted, read from the index without decoding, because a phone timelapse is a variable-rate file and a seek computed from its average rate lands a frame late.
  • ffmpeg is a child process reached over a pipe, started with an argument list and never a shell string; every read is bounded by the size the probe reported and every read has a timeout. A file that crashes the decoder crashes something holding nothing.
  • Colour conversion is ffmpeg's own (the scale filter's colour management, in the same pipe), asked for only when the probe reports primaries other than BT.709 or an HDR transfer, and applied to every pipe opened on a source, the morning's lead frame included, so no transition dissolves between two ideas of white. The choice of it was measured against fixtures encoded by the standards' own formulae: zimg's HLG applies the system gamma per channel and comes out wrong on colour, libplacebo needs a GPU through Vulkan, and swscale is exact on greys, within a dozen levels on saturated colours and up to thirty on the pure primaries at the very edge of the gamut, where a photograph of a room never goes. A tag it refuses is retagged before any other filter runs, because a stills folder's fit filter is a swscale pass of its own.
  • A stills folder is the same pipe through ffmpeg's concat demuxer, from a list file written to the temporary folder and deleted with the pipe. The first still is probed with its frame decoded, because a JPEG's orientation is reported on the frame and not on the stream, and a fit-and-pad filter holds every frame at that size so a stray still cannot change the byte count of a frame or arrive squashed.
  • No networking code anywhere, and a test fails the build if any project names it.

Licence

Source-available, not open source. Drift is free to download, read, compile and run, for anything, including commercially, at work and as part of an exhibition. You may not modify it, redistribute it or sell it. The footage you play is yours and so is the work it is shown as; Drift owns no picture. Copyright Vreyheid Consulting (Pty) Ltd; the terms are in LICENSE, and anything the licence does not permit can be asked for. Third-party components and their licences are listed in THIRD-PARTY-NOTICES.md; ffmpeg is LGPL v3 and its licence ships beside its binaries.

About

A Windows player that turns a timelapse into a still picture that is always changing and never seen to change. Built for the video installation One Day.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages