Created
August 3, 2025 13:22
-
-
Save ericek111/60b4e0e04c11202b99615250dd343ca2 to your computer and use it in GitHub Desktop.
Use LSFG in MPV in Linux with the interpolating factor set to the nearest value suitable for the currently used monitor.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // gcc mpv_lsfg.c -o mpv_lsfg -lX11 -lXrandr -lavformat -lavcodec -lavutil -lm | |
| // Change the path to the Lossless.dll library in LSFG_DLL_PATH! | |
| // You can get it by purchasing Lossless Scaling on Steam: https://store.steampowered.com/app/993090/Lossless_Scaling/ | |
| #define _GNU_SOURCE | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <unistd.h> | |
| #include <math.h> | |
| #include <X11/Xlib.h> | |
| #include <X11/extensions/Xrandr.h> | |
| #include <libavformat/avformat.h> | |
| #include <libavcodec/avcodec.h> | |
| double get_video_fps(const char *filename) { | |
| AVFormatContext *fmt_ctx = NULL; | |
| AVStream *video_stream = NULL; | |
| int video_stream_index = -1; | |
| double fps = 0.0; | |
| if (avformat_open_input(&fmt_ctx, filename, NULL, NULL) != 0) { | |
| fprintf(stderr, "Could not open file: %s\n", filename); | |
| return 0.0; | |
| } | |
| if (avformat_find_stream_info(fmt_ctx, NULL) < 0) { | |
| fprintf(stderr, "Could not find stream info\n"); | |
| avformat_close_input(&fmt_ctx); | |
| return 0.0; | |
| } | |
| // find the first video stream | |
| for (unsigned int i = 0; i < fmt_ctx->nb_streams; i++) { | |
| if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { | |
| video_stream_index = i; | |
| break; | |
| } | |
| } | |
| if (video_stream_index == -1) { | |
| fprintf(stderr, "No video stream found\n"); | |
| avformat_close_input(&fmt_ctx); | |
| return 0.0; | |
| } | |
| video_stream = fmt_ctx->streams[video_stream_index]; | |
| // get FPS | |
| AVRational frame_rate = av_guess_frame_rate(fmt_ctx, video_stream, NULL); | |
| if (frame_rate.num > 0 && frame_rate.den > 0) { | |
| fps = av_q2d(frame_rate); | |
| } | |
| avformat_close_input(&fmt_ctx); | |
| return fps; | |
| } | |
| int startMpv(int multiplier, int argc, char* argv[]) { | |
| int err = 0; | |
| char multStr[32]; | |
| sprintf(multStr, "%d", (int) multiplier); | |
| err |= setenv("ENABLE_LSFG", "0", 1); | |
| err |= setenv("LSFG_LEGACY", "1", 1); | |
| err |= setenv("LSFG_MULTIPLIER", multStr, 1); | |
| printf("\n\n>>>>>>>>>>>>>>>>> LSFG_MULTIPLIER = %s\n\n", multStr); | |
| err |= setenv("LSFG_FLOW_SCALE", "1.0", 1); | |
| if (getenv("LSFG_DLL_PATH") == NULL) | |
| err |= setenv("LSFG_DLL_PATH", "/home/erik/opt/Lossless.dll", 1); | |
| // LSFG_PERFORMANCE_MODE, LSFG_HDR_MODE | |
| if (err != 0) { | |
| perror("setenv failed"); | |
| return 1; | |
| } | |
| char** mpv_argv = malloc(sizeof(char*) * (argc + 1)); | |
| if (!mpv_argv) { | |
| perror("malloc failed"); | |
| return 2; | |
| } | |
| mpv_argv[0] = "mpv"; | |
| for (int i = 1; i < argc; ++i) { | |
| mpv_argv[i] = argv[i]; | |
| } | |
| mpv_argv[argc] = NULL; // NULL-terminated for execvp | |
| extern char** environ; // pass the current envvars | |
| if (execvpe(mpv_argv[0], mpv_argv, environ) < 0) { | |
| // fprintf(stderr, "%s\n", explain_execvp(pathname, argv)); | |
| perror("execvp failed"); | |
| } | |
| // if execvp returns, an error occurred | |
| free(mpv_argv); | |
| return 3; | |
| } | |
| int getRefreshRateOfMonitor(double* refreshRateTarget) { | |
| if (!refreshRateTarget) | |
| return 1; | |
| Display *display = XOpenDisplay(NULL); | |
| if (!display) { | |
| fprintf(stderr, "Unable to open X display\n"); | |
| return 2; | |
| } | |
| Window root = DefaultRootWindow(display); | |
| // Get the pointer (mouse cursor) location | |
| Window returned_root, returned_child; | |
| int root_x, root_y, win_x, win_y; | |
| unsigned int mask; | |
| if (!XQueryPointer(display, root, &returned_root, &returned_child, | |
| &root_x, &root_y, &win_x, &win_y, &mask)) { | |
| fprintf(stderr, "Failed to query pointer\n"); | |
| XCloseDisplay(display); | |
| return 3; | |
| } | |
| XRRScreenResources *resources = XRRGetScreenResources(display, root); | |
| if (!resources) { | |
| fprintf(stderr, "Failed to get screen resources\n"); | |
| XCloseDisplay(display); | |
| return 4; | |
| } | |
| RROutput target_output = None; | |
| int ret_code = 1; | |
| for (int i = 0; i < resources->noutput; ++i) { | |
| XRRCrtcInfo* crtc_info = NULL; | |
| XRROutputInfo* output_info = XRRGetOutputInfo(display, resources, resources->outputs[i]); | |
| if (!output_info || output_info->connection == RR_Disconnected || output_info->crtc == 0) { | |
| goto endloop; | |
| } | |
| crtc_info = XRRGetCrtcInfo(display, resources, output_info->crtc); | |
| if (!crtc_info) { | |
| goto endloop; | |
| } | |
| // check if mouse is within this output (monitor) | |
| if (root_x >= crtc_info->x && root_x < crtc_info->x + crtc_info->width && | |
| root_y >= crtc_info->y && root_y < crtc_info->y + crtc_info->height) { | |
| target_output = resources->outputs[i]; | |
| // find the active refresh rate | |
| for (int j = 0; j < output_info->nmode; ++j) { | |
| for (int k = 0; k < resources->nmode; ++k) { | |
| auto mode = resources->modes[k]; | |
| if (mode.id == output_info->modes[j] && mode.id == crtc_info->mode) { | |
| *refreshRateTarget = (double) mode.dotClock / (mode.hTotal * mode.vTotal); | |
| ret_code = 0; | |
| goto endloop; | |
| } | |
| } | |
| } | |
| } | |
| endloop: | |
| if (crtc_info) | |
| XRRFreeCrtcInfo(crtc_info); | |
| if (output_info) | |
| XRRFreeOutputInfo(output_info); | |
| } | |
| if (target_output == None) { | |
| fprintf(stderr, "Monitor under cursor not found\n"); | |
| ret_code = 5; | |
| } | |
| XRRFreeScreenResources(resources); | |
| XCloseDisplay(display); | |
| return ret_code; | |
| } | |
| int main(int argc, char *argv[]) { | |
| if (argc < 2) { | |
| fprintf(stderr, "Usage: %s [...] <video file>\n", argv[0]); | |
| return 1; | |
| } | |
| double videoFps = get_video_fps(argv[argc - 1]); | |
| if (videoFps > 0.0) { | |
| fprintf(stderr, "Video FPS: %.2f\n", videoFps); | |
| } else { | |
| fprintf(stderr, "Could not determine FPS.\n"); | |
| return 2; | |
| } | |
| double refreshRate = 0.0; | |
| if (getRefreshRateOfMonitor(&refreshRate) == 0) { | |
| int multiplier = (int) floor(refreshRate / videoFps); | |
| printf("Refresh rate under mouse: %.2f Hz, final multiplier: %d\n", refreshRate, multiplier); | |
| startMpv(multiplier, argc, argv); | |
| } else { | |
| fprintf(stderr, "Could not determine refresh rate\n"); | |
| } | |
| return 0; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Use the following command to copy the launcher to your local app folder -- change the path to the executable beforehand: | |
| # desktop-file-install --dir=$HOME/.local/share/applications ~/Documents/foss/mpv_lsfg/mpv_lsfg.desktop | |
| [Desktop Entry] | |
| Type=Application | |
| Version=1.0 | |
| Name=MPV with LSFG | |
| Comment=Run MPV interpolated to the refresh rate of the currently focused monitor. | |
| # vvv This is a path to the directory in which our executable resides. vvv | |
| Path=/home/erik/Documents/foss/mpv_lsfg | |
| Exec=mpv_lsfg --player-operation-mode=pseudo-gui -- %U | |
| Icon=mpv | |
| Terminal=false | |
| StartupWMClass=mpv | |
| Categories=AudioVideo;Audio;Video;Player;TV; | |
| MimeType=application/mxf;application/sdp;application/smil;application/x-smil;application/streamingmedia;application/x-streamingmedia;application/vnd.rn-realmedia;application/vnd.rn-realmedia-vbr;video/mpeg;video/x-mpeg2;video/x-mpeg3;video/mp4v-es;video/x-m4v;video/mp4;application/x-extension-mp4;video/divx;video/vnd.divx;video/msvideo;video/x-msvideo;video/ogg;video/quicktime;video/vnd.rn-realvideo;video/x-ms-afs;video/x-ms-asf;application/vnd.ms-asf;video/x-ms-wmv;video/x-ms-wmx;video/x-ms-wvxvideo;video/x-avi;video/avi;video/x-flic;video/fli;video/x-flc;video/flv;video/x-flv;video/x-theora;video/x-theora+ogg;video/x-matroska;video/mkv;application/x-matroska;video/webm;video/x-ogm;video/x-ogm+ogg;application/x-ogm;application/x-ogm-video;application/x-shorten;video/mp2t;application/x-mpegurl;video/vnd.mpegurl;application/vnd.apple.mpegurl;video/3gp;video/3gpp;video/3gpp2;video/vnd.avi; | |
| Name[en_US]=mpv_lsfg.desktop |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment