huijunchen9260 / fm.awk

File manager written in awk
GNU General Public License v3.0
125 stars 3 forks source link

[Discussion] Ueberzug as image previewer. #3

Closed magpie514 closed 3 years ago

magpie514 commented 3 years ago

Please note that this is not a feature request. Implementing it is your choice entirely and I'm not biased either way. This is entirely for discussion purposes.

For running Ueberzug there are two prep steps. The first step is to create a fifo (mkfifo <path>) somewhere. It can be any name, but needs permissions. A good place to put it could be /dev/shm or /tmp.

The next step is to run ueberzug by tailing the updates from the fifo. tail --follow /dev/shm/ueberzug | ueberzug layer --loader synchronous --parser json works for me nicely, but might need adjusting.

Once that's running one can send a string with a composed JSON setup indicating position, file name and an identifier that only seems to work if it's the same as the file name. Ueberzug will handle everything else by itself. This is a simple example of how to compose the JSON. awk 'BEGIN { printf "{ \"action\": \"add\", \"identifier\": \"%s\", \"x\": 10, \"y\": 10, \"width\":48, \"path\": \"%s\" }\n","/home/magpie/desktop.jpg","/home/magpie/desktop.jpg" > "/dev/shm/ueberzug" }' The various parameters are as follows. action = add, remove. Add adds an image with given identifier and path. Remove takes the given identifier out. x = x position in terminal characters. y = y position in terminal characters. width = width in characters. height = height in characters. I think aspect ratio is in effect and you only need to specify width. path = path to image file. identifier = must be equal to path (different values never worked for me). This can also be used to remove images (cache last path to remove). Once execution is complete ueberzug can be stopped and the fifo removed. Could be left there but better to clean up.

I'm not sure how multiple instances of fm.awk running in different tabs of a tabbed terminal would interact, however. I only use Ueberzug for automating image display from outputs, as opposed to image previewing, so I am not certain of how it will behave in different use cases.

Anyway I hope this helps. With this you should be able to decide whether it's a good idea or not.

huijunchen9260 commented 3 years ago

Does it possible to give me a block of code that can show image on terminal using ueberzug? I've actually tried this out with no luck:

awk 'BEGIN{
    FIFO = "/tmp/preview-fifo"
    system("rm \"" FIFO "\"")
    system("mkfifo \"" FIFO "\"")
    system("ueberzug layer --parser json --silent < \"" FIFO "\" &")
    system("exec 3>\"" FIFO "\"")
    ID = "fmawk-preiew"
    IMAGE = "/home/huijunchen/Picture/CSM_wait.jpg"
    printf("{ \"action\": \"add\", \"identifier\": \"%s\", \"path\": \"%s\", \"x\": %d, \"y\": %d, \"scaler\": \"fit_contain\", \"width\": %d, \"height\": %d }\n", ID, IMAGE, 2, 1, 100, 100) > "FIFO"
    close(FIFO)
}'
magpie514 commented 3 years ago

Yes, I'll give it a shot once I get home, I'll edit this post to not spam alerts. From the looks of it seems fine, although I only managed to make it work if ID is equal to IMAGE, and using tail -f as the way to pipe text from the fifo to Ueberzug, I think this form only pumps whatever is on the fifo at execution time, I need to confirm. Still, I'll make a working example ASAP.

EDIT: Hm, I cannot seem to get it to work entirely from within awk. Doing the exact same steps from bash seems to work fine, but not when invoked from awk. If the FIFO and ueberzug instance are already initialized it's no problem. I wonder if it's failing to initialize because it's using the bash environment to calculate its layout.

huijunchen9260 commented 3 years ago

I am also thinking about something similar to lfimg. What do you think about "outsourcing" all of the preview stuff to shell script? Or it is possible to do it all in awk?

magpie514 commented 3 years ago

It's a possibility, it's using external tools either way, it'll just add one more step to installation (putting the script into $PATH) which isn't a dealbreaker for me. Can display a simple error message if the script is not found in case the user is in a more limited system. Still, at a personal level, I feel it'd be more interesting to figure out a way to properly init Ueberzug using pure awk. I'll investigate further. The fifo seems to be fine and I've successfully reused it for an external Ueberzug instance (as long as it's not created/destroyed while Ueberzug is running it works (programs will silently drop connection to the fifos, it's not an exclusive Ueberzug issue, try it with tail for example)) and passing data to an already active instance also works. I've confirmed the Ueberzug process is properly set up, it just refuses to work. (It needs to be killed at shutdown, though, is there a way to get the PID? I don't think close() works with system() processes. ) On the other hand, I am just thinking, using Ueberzug would not work if using fm.awk through SSH, so a chafa fallback (if available) could be a good thing. I will give it another try later today. It's picking my curiosity.

huijunchen9260 commented 3 years ago

I'll try to provide all information that I know in this reply.

In the fontpreview-ueberzug, it seems that exec 3>"$FIFO" is the key. The comment above this code is a link that is already deprecated for some reason. exec is the command that change the FIFO with file descriptor 3, which is a non-standard one. I've tried to change this to exec 2>"$FIFO" and the fontpreview-ueberzug still works somehow. Not sure why this step seems to be required.

The next possible reason why it is not working in pure awk might because my code above is not doing everything in one shell. If I understand those correctly, system() it is opening a subshell, executing command, and closing this subshell. If this is correct, then the only reason why fontpreview-ueberzug is working, but the awk solution is not working might come from the fact that all the commands are not run in the same subshell.

Some other scripts that is also using ueberzug: nnn, fff, lfimg, vifmimg12

magpie514 commented 3 years ago

That's strange. Yesterday I had a large amount of strayed ueberzug processed spawned from within awk, but forgot to check the parent. Amusingly, running the same lines I had in history, I cannot get ueberzug to spawn at all now, so I can't confirm what the parent process was. Nevermind, it was tail. tail -f FIFO | ueberzug --etcetc & does spawn a lasting process. It just doesn't seem to want to respond (probably because it's not attached to anything), and it's hard to figure out the PID to kill it later (without parsing output of ps or such). Hm. I still haven't had time to look into Ueberzug's init (busy day) but should figure it out tomorrow, I hope. I am suspecting it's missing something like $COLUMNS or $DISPLAY, I'll see about setting those manually from user's environment.

EDIT: I got one of those showertime ideas that is kinda wild and borderline wrong but might kind of work. I'll try to implement it and see if it works if I don't succeed with the above.

magpie514 commented 3 years ago

I managed to cook this abomination:

BEGIN{
    cmd = "bash -xc '\ntrap \"exit 0\" SIGQUIT SIGTERM SIGKILL\nFIFO=\"/dev/shm/testfifo\"\n[[ -p $FIFO ]] || mkfifo \"$FIFO\"\necho $BASHPID\ntail -f \"$FIFO\" | ueberzug layer &\n sleep 5' "
    cmd | getline buf
    print buf
    ID = "fmawk-preview"
    IMAGE = "/home/magpie/desktop.jpg"
    printf("{ \"action\": \"add\", \"identifier\": \"%s\", \"path\": \"%s\", \"x\": %d, \"y\": %d, \"scaler\": \"fit_contain\", \"width\": %d, \"height\": %d }\n", IMAGE, IMAGE, 2, 1, 100, 100) > "/dev/shm/testfifo"
    #system("sleep 22")
    close(cmd)
    #system("rm /dev/shm/testfifo")
}

It...kind of...works? only after the sleep 1 for some reason. There also has to be a good way to dispose of the process. buf has the PID, but if I try to kill by GPID it seems to also kill the script itself (unless I made a mistake along the way). It's kind of cheating and it's a pain to escape but it's a way to not have to separate files. There might be a sh/dash compatible way to do this as well, I'm using bash for familiarity.

Reducing the delay further works, but if it's too small it won't work. I think the ultimate problem could be one of timing? We might be passing the json too fast (by the way, I verified in the source that --parser json is default, no need to specify)

huijunchen9260 commented 3 years ago

Sorry but this code block doesn't seems to work for me. I've tried to run it on both bash and zsh, and the results seem weird:

[huijunchen@HJC ~]$ awk '
BEGIN{
        cmd = "bash -xc '\ntrap \"exit 0\" SIGQUIT SIGTERM SIGKILL\nFIFO=\"/dev/shm/testfifo\"\n[[ -p $FIFO ]] || mkfifo \"$FIFO\"\necho $BASHPID\ntail -f \"$FIFO\" | ueberzug layer &\n sleep 5' "
        cmd | getline buf
        print buf
        ID = "fmawk-preview"
        IMAGE = "/home/huijunchen/Pictures/Deer.jpg"
        printf("{ \"action\": \"add\", \"identifier\": \"%s\", \"path\": \"%s\", \"x\": %d, \"y\": %d, \"scaler\": \"fit_contain\", \"width\": %d, \"height\": %d }\n", IMAGE, IMAGE, 2, 1, 100, 100) > "/dev/shm/testfifo"
        #system("sleep 22")
        close(cmd)
        #system("rm /dev/shm/testfifo")
}
'
zsh: bad pattern: SIGKILLnFIFO="/dev/shm/testfifo"n[[
[1] 2946 2947
mkfifo: invalid option -- 'f'
Try 'mkfifo --help' for more information.
zsh: command not found: n
[huijunchen@HJC ~]$
[1]  + exit 1     mkfifo \"$FIFO\"\necho $BASHPID\ntail -f \"$FIFO\" |
       done       ueberzug layer

Not sure what's wrong with mkfifo and why most of the newline character \n has been n.

magpie514 commented 3 years ago

Must be an escaping issue, bash -c input is kind of delicate. Try to run it as a text file instead.

huijunchen9260 commented 3 years ago

Wow, when I put it into a script, it some works lol. Probably need a key to remove the ueberzug layer or somehow, but pretty surprised that using bash functionality actually works.

magpie514 commented 3 years ago

Heh, yeah, it was one of those "this idea is insane enough to work" moments. Of course one can still use a helper bash script placed externally, but this is a cute approach. We could see into converting it to plain sh/dash, I'm only really using some traps and bashisms out of preference but it's trivial to convert this miniscript.

Wow, when I put it into a script, it some works lol.

Yeah it's definitely a escaping issue. The thing is trying to expand from bash/zsh/your favorite to awk string and from awk string to bash -c input. By putting it in a script we skip one such conversion. That's why the escaping is so bizarre with \\ and such. As for killing the process it's kind of complicated. We know the PID of the bash script, so from there we can find the ueberzug process in the same group and terminate it, but it's going to have to use something like parsing the output of ps to figure out the group.

huijunchen9260 commented 3 years ago

I still wish there's a native awk version for it. Anyway good job for letting it work within awk script by using bash magic. Hope that the simplification can keep going!

huijunchen9260 commented 3 years ago

@magpie514 Do you have any good ideas if I want to move the preview script on pdf, image and video out as a shell script? I thought that would be much cleaner, and support for using shell script to do ueberzug.

magpie514 commented 3 years ago

Hmm, no great ideas, no. Maybe a " start" at BEGIN to get ueberzug running and figuring out its PID so it can be stopped later, and then " " to process the file and pick the correct handler, and finally " stop" to remove ueberzug and clean up?

huijunchen9260 commented 3 years ago

Hmm, no great ideas, no. Maybe a " start" at BEGIN to get ueberzug running and figuring out its PID so it can be stopped later, and then " " to process the file and pick the correct handler, and finally " stop" to remove ueberzug and clean up?

Yeah that's basically what I feel. Do you think that's doable?

magpie514 commented 3 years ago

Should be fine. The "start" portion could extract the bash PID that we can use to get the ueberzug PID for safekeeping in the awk side. The file processing part doesn't need anything special, just boilerplate to get the extension and a case tree. The "stop" would send SIGTERM or SIGKILL to Ueberzug if running.

Some quick observations/ideas:

Basically, fm.awk could be cleaned of all preview stuff and delegate to the script entirely, only, ideally, caching some data to avoid spurious calls.

huijunchen9260 commented 3 years ago

Basically, fm.awk could be cleaned of all preview stuff and delegate to the script entirely, only, ideally, caching some data to avoid spurious calls.

I think I've cleaned the preview stuff already. Right now all the external program-related preview is in graphic_preview() function, and thus should be easily replace by external script.

I wish to do all of this preview stuff in posix shell script, since lots of project mentioned above has already done it lol.

magpie514 commented 3 years ago

Oh yeah, I forgot that part. Yeah sh instead of bash is a good idea.

drewauff commented 3 years ago

not to completely hijack, but image preview via imgcat would be awesome. As an iTerm user this is literally the best option.

huijunchen9260 commented 3 years ago

not to completely hijack, but image preview via imgcat would be awesome. As an iTerm user this is literally the best option.

It should not be hard. Simply change chafa to imgcat with corresponding options would suffice.

huijunchen9260 commented 3 years ago

This code chunk can definitely generate image on terminal using ueberzug:

IMAGE="/home/huijunchen/Pictures/CSM_wait.jpg"
export FIFO_UEBERZUG="$HOME/.cache/fmawk-ueberzug-$$"
mkfifo "$FIFO_UEBERZUG" 2>/dev/null
ueberzug layer -s <"$FIFO_UEBERZUG" -p json &
exec 3>"$FIFO_UEBERZUG" 2>/dev/null
printf '{"action": "add", "identifier": "PREVIEW", "x": "%s", "y": "%s", "width": "%s", "height": "%s", "scaler": "contain", "path": "%s"}\n' "1" "1" "20" "20" "$IMAGE" > "$FIFO_UEBERZUG"
sleep 10
printf '{"action": "remove", "identifier": "PREVIEW"}\n' > "$FIFO_UEBERZUG"
exec 3>&-
rm "$FIFO_UEBERZUG"
magpie514 commented 3 years ago

Mhm, there's not much difficulty as long as it's shell script. I still wonder what exactly is missing when it fails to be invoked from inside awk, natively, though. I still wonder where the file descriptor 3 comes into play, I never required it to get it working.

huijunchen9260 commented 3 years ago

I think start ueberzug using system("ueberzug layer -s <\"$FIFO_UEBERZUG\" -p json &") inside awk would fail is because the subshell that awk opened for ueberzug is closed after executing this command. Since there's no ueberzug to pipe into, there is no content to show.

magpie514 commented 3 years ago

Ah I see, so that's why it spawned fine when invoked through a shell like bash. I see, I see, I somehow had assumed awk would try to start a subshell in those circumstances, it's good to know it doesn't.

Well if nothing else this issue will remain as a nicely educative record of running Ueberzug in strange ways.

huijunchen9260 commented 3 years ago

I am thinking of running image preview using this wrapper script:

export FIFO_UEBERZUG="$HOME/.cache/fmawk-ueberzug-$$"
mkfifo "$FIFO_UEBERZUG" 2>/dev/null
ueberzug layer -s <"$FIFO_UEBERZUG" -p json &
exec 3>"$FIFO_UEBERZUG" 2>/dev/null
# where fm.awk will do printf to pipe to add or remove stuff
fm.awk
exec 3>&-
rm "$FIFO_UEBERZUG"

Not sure whether it will work lol. At least my few attempt that replacing printf with awk script somehow works:

export FIFO_UEBERZUG="$HOME/.cache/fmawk-ueberzug-$$"
mkfifo "$FIFO_UEBERZUG" 2>/dev/null
ueberzug layer -s <"$FIFO_UEBERZUG" -p json &
exec 3>"$FIFO_UEBERZUG" 2>/dev/null
awk 'BEGIN{
    FIFO_UEBERZUG=ENVIRON["FIFO_UEBERZUG"]
    IMAGE="/home/huijunchen/Pictures/CSM_wait.jpg"
    printf "{\"action\": \"add\", \"identifier\": \"PREVIEW\", \"x\": \"%s\", \"y\": \"%s\", \"width\": \"%s\", \"height\": \"%s\", \"scaler\": \"contain\", \"path\": \"%s\"}\n", 1, 1, 20, 20, IMAGE > FIFO_UEBERZUG
    system("sleep 10")
    printf "{\"action\": \"remove\", \"identifier\": \"PREVIEW\"}\n" > FIFO_UEBERZUG
}'
exec 3>&-
rm "$FIFO_UEBERZUG"

Can you test it for me whether the awk variant works for you or not @magpie514 ?

magpie514 commented 3 years ago

image Yep, works fine here! (both with bash and sh)

huijunchen9260 commented 3 years ago

https://github.com/huijunchen9260/fm.awk/commit/9253353162f8c6fe13afb91f7209f9ef00a8e1d4

Can you test this version? Just start the fmawk-ueberzug script

magpie514 commented 3 years ago

image Works perfectly. Turns out a wrapper is quite the elegant solution. Guess I was thinking of it backwards. So far so good, does the job very nicely.

huijunchen9260 commented 3 years ago

The next step I am thinking of is whether I just isolate the preview script into a shell script, I mean those pattern matching stuff can probably be done more efficiently using case statement. Does it possible that you can do this for me? Thanks!

magpie514 commented 3 years ago

If you don't mind waiting a couple days, sure, I can make it happen. Promised to get something else done for right now.

huijunchen9260 commented 3 years ago

If you don't mind waiting a couple days, sure, I can make it happen. Promised to get something else done for right now.

Sure! I am just so tired after implementing this lol. Yet I am so proud of myself lol.

magpie514 commented 3 years ago

Good job so far!

huijunchen9260 commented 3 years ago

Are you trying to separate the preview scripts into shell scripts?

magpie514 commented 3 years ago

Not yet, I'm still being used elsewhere, but shouldn't take much longer now.

magpie514 commented 3 years ago

Alright, are there any specs I should build it around, before getting started?

huijunchen9260 commented 3 years ago

Not sure. My hope is just to individualize those preview parts of the script into an individual shell scripts.

magpie514 commented 3 years ago

I'll try a thing and see what happens.

magpie514 commented 3 years ago

Alright, seems I can easily get the environ for FIFO_UEBERZUG and everything, I wasn't sure if the subshell inherited environment right. OK then, I should have a basic implementation tomorrow.

magpie514 commented 3 years ago

It's mostly set up. I've set it up for the external script to take the file path and the cache path, and returns the image path back to fm.awk where either chafa or ueberzug get called (since fm.awk already has all the drawing data internally, it'd be easier than passing it around, and the dependency checks are already in place). Does this sound alright? Basically it's invoking the external tools to generate an image and then using the available display method (chafa,ueberzug,none) from fm.awk to put it in the right place. I'll need to move a few things around if you agree with this design, but it's generally functional.

If you prefer the script to also handle chafa and ueberzug that can be done as well. Just let me know what you prefer.

huijunchen9260 commented 3 years ago

It sounds good to me!

magpie514 commented 3 years ago

Alright then, in that case I just need to open the PR, but won't be at home until later. I tested it and seemed to work nicely, although it was a bit tricky about quoting. I also tested only PDF, MP4 and plain images, I don't have many samples of other video formats, but I'm guessing they'll be fine.

Only caveat is that it's assuming a JPEG output, or rather, a .jpg extension, which could be tricky when some programs output PNG only, but other than storing the return value of the helper script to delete on next invocation or fm.awk exit I don't know how to solve that in an "universal" way.

magpie514 commented 3 years ago

The last PR should finish everything related to Ueberzug, so closing this.