🛠️ofs-ng - funscript editor

hello

I come bearing free software. free as in freedom and free as in free of charge.
against my better judgement I’ve decided to revive OFS as ofs-ng.

I started this on a whim pretty much exactly one month ago. This is not a weekend project

It’s a complete rewrite of OFS using claude code. (it is what it is. just being upfront about it)
There’s no backwards compatibility to anything OFS. It’s a clean break. This means old Lua extensions are not compatible.

It’s running on the same stack. C++, OpenGL, SDL3, ImGui and mpv.

I can support windows and linux. no macos. sorry. if you :index_pointing_at_the_viewer: want to support macos be my guest.

This tool is for funscript artisans. :artist_palette::paintbrush:
It doesn’t generate anything for you out of the box.

Features (incomplete)

  • new multi-axis timeline stacked on the z-axis instead of vertically
  • C# plugin system
  • non-destructive editing through processing regions with a node graph editor
  • nodes can be created through C# plugins or C# scripts
  • built-in 3D simulator
  • built-in intra video transcoding flow (using ffmpeg)
  • custom themes
  • QOL
  • less bugs ???

There’s a StarterPlugin template project to create plugins. Plugins compile against a managed Ofs.Api.dll. They don’t need to be recompiled for every version of ofs-ng only if there’s breaking changes in the API.
There’s an API reference and you can look at the source code for the Ofs.Core plugin for more example code. C# API Reference

There’s a regular x86-64-v3 build for modern CPUs of the last 12 years or so and a compat build which should run anywhere.
There’s a pdb zip ignore it. It only is there to debug crash dumps or the release build.
Linux build expects ffmpeg+ffprobe and mpv to be installed on your system.

feedback needed. I don’t know if the 1.1/2.0 funscript export is implemented correctly.

18 Likes

Thank you to the OFS authors for all your hard work. I tried the new version, and for me, the addition of the three-view display in the 3D simulator is really helpful. Previously, I often had to copy surge and pitch over to sway and roll to check the angles, then copy them back after confirming everything was correct. With the three views, this process is much more intuitive.

However, overlaying the multi-axis timeline on the Z-axis feels confusing to me. Not only is it cumbersome to constantly switch back and forth while editing, but it also doesn’t look as clear and intuitive as having them separated. I’m not sure whether the new version simply doesn’t support separating them, or if I just couldn’t find the option. Since I don’t know much about software and haven’t studied the new version in depth, I can only share what I personally feel are the good and not-so-good parts.

author is me myself and I there’s no team working on this.

I was afraid of this criticism. I guess I can add an alternative visualization which lays the axes out stacked vertically. It’s just so space inefficient

1 Like

I understand that you are the sole author. Maybe there was a misunderstanding due to AI translation. Stacking all the multi-track editing parts together is indeed quite inconvenient during production. I also asked some script authors around me, and everyone shares a similar opinion: since we frequently switch back and forth between different tracks for editing, having to switch one by one is really too troublesome. However, it does take up a lot of visual space, which is also a fact. So the script authors I know all have their own preferred layouts. Some put them on the left and right sides, while I personally like to put them at the bottom.

It would be great if, while having the new hybrid mode in the new version, we could still freely choose the layout like in the old version.

Please forgive me if the AI translation is a bit inaccurate in places

I understand and will add an alternate separated mode.

Creating custom layouts in ofs-ng is explicit rather than implicit like it was before.


You need to create you’re own layout. The Default layout is read-only.
Lock Layout must be disabled so that the the little triangle in the top left appears.
triangle
Clicking the triangle reveals the window titlebar with which you can drag and dock the window where you want.
titlebar
Afterwards you save the layout.
This allows multiple layouts between which you can switch easily.

1 Like

understand , thank you

I updated it v0.1.5

2 Likes

(hoping this is the right place to report this bug)

the border around everything increases every time you change something in the theme tab for preferences.

ugh can confirm something with display scaling. I always run 100% display scaling so I never noticed. When I increase it I get the same behavior. looking into it

1 Like

It’s fixed in v0.1.6.
I’m doing semantic versioning wrong again. If I did it right we’d be at v0.6.1 oh well.

That’s awesome, thank you! I’ll use the new version to create it. Also, I hope the plugins from the old version can still be used in the new version, as some of the features are still quite commonly used.

Tried it out a bit. Seems pretty good so far. Some minor things I have noticed that would be nice:

  1. When exporting, it would be nice to default to the location of the file it was open from/the video. Probably should have a setting to choose between the video folder, a specified default folder, or the most recent export folder.
  2. When exporting, I don’t like the .max.funscript name style. Would be nice to have an option to choose the default naming style.
  3. I think it should set metadata.duration to the number of seconds in the video (as an int not float) instead of 0.
  4. I think metadata should be moved to the front/top of the file so that it can be instantly read and edited when you open the file in notepad. As far as I know this fully compatible and non-breaking with existing software as I have seen it in files before and it is just a json anyways.
  5. Might be nice to have an option to pop up the project/metadata page when a script is first opened like old OFS. Not a big deal once you find where the menu is, but it saves a bit of time if you like it.

No hate to trying something new but I just tried this with NixOS and

  1. This was painful creating a nixpkg for.
  2. This requires re-encoding video to have frame by frame seek? Something must’ve gone wrong.

@sithsam
This all seems reasonable.

@VladTheImplier

  1. I don’t know anything about that but I can imagine :pensive_face:
  2. Re-encoding should not be required to have frame by frame seek. I imagine you have some sort of issue?

I personally find it quite funny that even you, the creator of OFS, decided to rewrite OFS with AI tooling rather than continue working on (and bugfixing) the original codebase. Keeping the exact same language/dependency stack as well.

well it’s the definition of tech debt. barely any of the capabilities the original app had where planned to be there. I just kept adding features whenever ideas came up. naturally the code is a mess.
AI makes things a lot easier. the test suite alone has more code than the original OFS :sweat_smile:

I would need to do more testing but I think the linux version isn’t great. Like I said the frame by frame seek doesn’t work. I have to re-encode each video.

I do like the new UI and I want to play with it more but the frame-by-frame is a non-starter.

Also these dependencies are a mess. That really needs resolving.

I only did rudimentary testing on my cachyos installation can’t confirm or deny at this time.
I’d like to go the windows route and just bundle all dependencies in one AppImage.
Care to elaborate what about the dependencies is a mess? Like there’s a lot of ways I can interpret that. :thinking:

The issue from my perspective is dotnet in terms of dependency hell.
There’s a bit of custom build logic that needs to be done to make things work properly

appimage would definitely help but that will probably cause issues with opengl/vulkan

If you’re curious this was what I had to do to make a nix flake and I did need some LLM assistance:

nix flake
{
  description = "ofs-ng nix package";

  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
  };

  outputs = { self, nixpkgs }:
    let
      supportedSystems = [ "x86_64-linux" ];
      forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
    in
    {
      packages = forAllSystems (system:
        let
          pkgs = nixpkgs.legacyPackages.${system};

          dotnet = pkgs.dotnet-sdk_10;
          runtimePrograms = [
            pkgs.ffmpeg
            pkgs.mpv
          ];
          runtimeLibraries = [
            pkgs.alsa-lib
            pkgs.dbus
            pkgs.libGL
            pkgs.libX11
            pkgs.libXScrnSaver
            pkgs.libXcursor
            pkgs.libXext
            pkgs.libXfixes
            pkgs.libXi
            pkgs.libXinerama
            pkgs.libXrandr
            pkgs.libXrender
            pkgs.libdecor
            pkgs.libffi
            pkgs.libXtst
            pkgs.libglvnd
            pkgs.liburing
            pkgs.libusb1
            pkgs.libuuid
            pkgs.libxkbcommon
            pkgs.libxml2
            pkgs.libxslt
            pkgs.mesa
            pkgs.mpv-unwrapped
            pkgs.pipewire
            pkgs.udev
            pkgs.vulkan-loader
            pkgs.wayland
            pkgs.wayland-protocols
          ];
        in
        {
          default = self.packages.${system}.ofs-ng;

          ofs-ng = pkgs.clangStdenv.mkDerivation (pkgs.dotnetCorePackages.addNuGetDeps {
            nugetDeps = ./nuget-deps.nix;
          } (finalAttrs: {
            pname = "ofs-ng";
            version = "0.2.0";

            src = pkgs.fetchFromGitHub {
              owner = "ofs69";
              repo = "ofs-ng";
              rev = "v${finalAttrs.version}";
              hash = "sha256-uhQdxHImwMupwPeB57xzZkoR+tPJV7PIYJUPXq8qJ50=";
              fetchSubmodules = true;
            };

            nativeBuildInputs = [
              pkgs.cmake
              pkgs.gitMinimal
              pkgs.ninja
              pkgs.pkg-config
              pkgs.python3
              pkgs.makeWrapper
              dotnet
            ];

            buildInputs = runtimeLibraries ++ [
              pkgs.xorgproto
            ];

            cmakeFlags = [
              "-GNinja"
              "-DCMAKE_BUILD_TYPE=Release"
              "-DOFS_BUILD_TESTS=OFF"
              "-DOFS_CLANG_TIDY=OFF"
              "-DOFS_SDL_STATIC=ON"
              "-DCMAKE_C_COMPILER=clang"
              "-DCMAKE_CXX_COMPILER=clang++"
            ];

            preConfigure = ''
              export DOTNET_CLI_HOME="$TMPDIR/dotnet-home"
              export NUGET_PACKAGES="$TMPDIR/nuget-packages"
              export DOTNET_NOLOGO=1
              export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
              export DOTNET_CLI_TELEMETRY_OPTOUT=1
            '';

            # build currently does not define an install target the app and
            # runtime assets are emitted into bin/release.
            installPhase = ''
              runHook preInstall

              install -Dm755 ../bin/release/ofs-ng "$out/libexec/ofs-ng/ofs-ng"
              install -Dm644 ../bin/release/data.pak "$out/libexec/ofs-ng/data.pak"

              if [ -d ../bin/release/managed ]; then
                cp -R ../bin/release/managed "$out/libexec/ofs-ng/managed"
              fi

              makeWrapper "$out/libexec/ofs-ng/ofs-ng" "$out/bin/ofs-ng" \
                --prefix PATH : ${pkgs.lib.makeBinPath runtimePrograms} \
                --prefix LD_LIBRARY_PATH : ${pkgs.lib.makeLibraryPath runtimeLibraries} \
                --set DOTNET_ROOT ${dotnet}

              runHook postInstall
            '';

            meta = {
              description = "OpenFunscripter Next Generation";
              homepage = "https://github.com/ofs69/ofs-ng";
              license = pkgs.lib.licenses.gpl3Only;
              platforms = [ "x86_64-linux" ];
              mainProgram = "ofs-ng";
            };
          }));
        });

      apps = forAllSystems (system: {
        default = {
          type = "app";
          program = "${self.packages.${system}.default}/bin/ofs-ng";
        };
      });
    };
}

Added a new build v0.2.1.

  • fixed the managed dependencies being previously loaded via a relative path. now it’s absolute
  • Added an AppImage it’s built on ubuntu 24.04 via CI and it works on my machine ™(which is CachyOS so who knows if it works on other distros :person_shrugging:)
  • removed the .max infix. if someone wants it they can name it manually in the save dialog
  • fixed the metadata duration setting for funscript exports
  • added an app setting for the export directory preference
  • metadata is now sorted to be in the front for funscript exports
  • metadata editor moved to it’s own window removed from the project settings window
    • the showing of the metadata editor is precisely configured wrong. It will show when opening an existing project instead of when creating a new project… so I will change that in a future release

@VladTheImplier I can not wrap my head around that frame stepping issue. and I remain kinda ignorant when it comes to NixOS

3 Likes