Log in

View Full Version : Vapoursynth


Pages : 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 [97] 98 99 100

Selur
21st April 2023, 17:52
How can I get the current scan type (tff/bff/progressice) of the clip I'm using, so that I can use it in a script? (I don't want to display it.)

_Al_
22nd April 2023, 20:05
from the props, not sure if I understand

n = 0
try:
field_based = clip.get_frame(n).props.get('_FieldBased', None)
except (vs.Error, IndexError) as e:
raise ValueError(f'requesting prop for a frame: {n} failed, {e}')
print(field_based)

Selur
23rd April 2023, 07:25
Strange, I would have bet I tried that and it didn't work.
Worked now, must have made a typo.
Thanks.

Cu Selur

Selur
25th April 2023, 19:54
Cdblend and srestore both use std.Cache(make_linear=True) which turned in to a noop function in API4 (are there more noop functions? is there some documentation about this?)
Is there a way to get the same effect as "std.Cache(make_linear=True)" with API4 Vapoursynth that would allow to fix those two scripts which now return worse results than before?

Myrsloik
25th April 2023, 21:44
Cdblend and srestore both use std.Cache(make_linear=True) which turned in to a noop function in API4 (are there more noop functions? is there some documentation about this?)
Is there a way to get the same effect as "std.Cache(make_linear=True)" with API4 Vapoursynth that would allow to fix those two scripts which now return worse results than before?

http://www.vapoursynth.com/doc/functions/video/setvideocache.html
Basically this is the closest you get. You can't set the linear mode from scripts but if you need it something else is wrong. Which source filter are you using?

Create an issue where you link the specific versions of the functions you're using, full script and source filter and I'll check it. Should be fairly quick to figure out where things go wrong.

Selur
29th April 2023, 06:31
Trying to assess how broken of a state current filter scripts are, are there more such 'dummy' functions in API4?

Myrsloik
29th April 2023, 10:07
Trying to assess how broken of a state current filter scripts are, are there more such 'dummy' functions in API4?

Nope. Also it only worked by accident previously. The world is threaded now.

Selur
29th April 2023, 10:15
Nope. Also it only worked by accident previously.
Okay, good to know, I just need to throw away all functions containing 'std.Cache(make_linear=True)'. :)
Thanks for the info.

Since I have little hope that anyone will write a sRestore filter, I will have to rethink some of my workflows and probably add an intermediate step to filter with Avisynth.

Cu Selur

Selur
3rd May 2023, 19:08
I can't find any deblenders in Vapoursynth that work with current Vapoursynth R61/62, does anyone know one?

Myrsloik
14th June 2023, 21:14
R63 has been released. Minor bug fixes mostly. This is also the last release I'm going to make official 32 bit binaries for. Nobody downloads them anyway except by accident.

ChaosKing
15th June 2023, 08:51
R63 has been released. Minor bug fixes mostly. This is also the last release I'm going to make official 32 bit binaries for. Nobody downloads them anyway except by accident.

Half of the compiled plugins on Github are only available as 64 bit anyway.

Myrsloik
21st June 2023, 19:25
Speaking of deprecating things. How many win7 users remain nowadays? I'm curious if there actually is a real userbase still remaining.

Emulgator
24th June 2023, 15:01
2x Win10, 2x Win7, 5x WinXP here, 7 single and 1 dual-boot Win7+WinXP.
Paid, abandoned, but working and irreplaceable softwares made that necessary.

Win7 should be minimum supported, or does that restrict coding seriously?
The default compiler switches that intentionally introduce incompatibilities are well known by now and can be avoided most of the time.
Well, I won't need VapourSynth to work under WinXP, I can use a Win10 machine for that.

As long as there is one Swiss Army Knife that can run under more than the latest forced-updating-and then-bluescreening (again) OS...

AviSynth works under these 3 Win generations, and the just recently introduced XP-compatible Audio preview makes it fun again to edit, even restore and encode on WinXP machines.

LigH
25th June 2023, 11:01
A major difference between Windows 7 and 10 is in hardware access and security aspects; most software focused on just calculations should be quite compatible, even when using more modern SIMD instructions Windows 7 did not yet prepare well for, with some caution.

Myrsloik
25th June 2023, 17:08
A major difference between Windows 7 and 10 is in hardware access and security aspects; most software focused on just calculations should be quite compatible, even when using more modern SIMD instructions Windows 7 did not yet prepare well for, with some caution.

I have no reason to remove win7 support yet as it doesn't require additional work to maintain. The only problem is that I can't be bothered to test win7 (or 8.1 for that matter) anymore and therefore check now and then if anyone still actually use it and it receives testing. This is simply a check I do now and then to see if any actual (and possibly bug reporting) users remain.

One of the previous posts also mention xp support in avs+ still but that's intermittent due to a very low volume of testing, even the visual studio runtime breaks now and then because Microsoft doesn't care that much either. Once win7 reaches the same level of neglect or the latest visual studio drops support I'll also drop it in VapourSynth. Obviously.

If someone wants to encode on winxp level hardware with VapourSynth it's still very possible. Just install linux, it'll run much faster due to vastly superior memory management too.

Selur
26th June 2023, 14:31
I'm using R63 with this script:
# Imports
import vapoursynth as vs
import os
import sys
# getting Vapoursynth core
core = vs.core
# Import scripts folder
scriptPath = 'F:/Hybrid/64bit/vsscripts'
sys.path.insert(0, os.path.abspath(scriptPath))
# Loading Plugins
core.std.LoadPlugin(path="F:/Hybrid/64bit/vsfilters/Support/libmvtools.dll")
core.std.LoadPlugin(path="F:/Hybrid/64bit/vsfilters/FrameFilter/Interframe/svpflow2_vs64.dll")
core.std.LoadPlugin(path="F:/Hybrid/64bit/vsfilters/FrameFilter/Interframe/svpflow1_vs64.dll")
core.std.LoadPlugin(path="F:/Hybrid/64bit/vsfilters/SourceFilter/LSmashSource/vslsmashsource.dll")
# Import scripts
import havsfunc
import filldrops
# source: 'C:\Users\Selur\Desktop\002 - Copia.mkv'
# current color space: YUV420P8, bit depth: 8, resolution: 1920x1080, fps: 23.976, color matrix: 709, yuv luminance scale: limited, scanorder: progressive
# Loading C:\Users\Selur\Desktop\002 - Copia.mkv using LWLibavSource
clip = core.lsmas.LWLibavSource(source="C:/Users/Selur/Desktop/002 - Copia.mkv", format="YUV420P8", stream_index=0, cache=0, prefer_hw=0)
# making sure frame rate is set to 23.976
clip = core.std.AssumeFPS(clip=clip, fpsnum=24000, fpsden=1001)
clip = core.std.SetFrameProp(clip=clip, prop="_FieldBased", intval=0) # progressive
clip = filldrops.InsertSingle(clip=clip,method="mv")
#clip = filldrops.InsertSingle(clip=clip,method="svp_gpu")
#clip = filldrops.InsertSingle(clip=clip,method="svp")

# Output
clip.set_output()
and filldrops.py:
import vapoursynth as vs
core = vs.core

def fillWithMVTools(clip):
super = core.mv.Super(clip, pel=2)
vfe = core.mv.Analyse(super, truemotion=True, isb=False, delta=1)
vbe = core.mv.Analyse(super, truemotion=True, isb=True, delta=1)
return core.mv.FlowInter(clip, super, mvbw=vbe, mvfw=vfe, time=50)

def fillWithRIFE(clip, firstframe=None, rifeModel=1, rifeTTA=False, rifeUHD=False, rifeThresh=0.15):
clip1 = core.std.AssumeFPS(clip, fpsnum=1, fpsden=1)
start = core.std.Trim(clip1, first=firstframe-1, length=1)
end = core.std.Trim(clip1, first=firstframe+1, length=1)
startend = start + end
if clip.format != vs.RGBS:
r = core.resize.Point(startend, format=vs.RGBS, matrix_in_s="709")
if rifeThresh != 0:
r = core.misc.SCDetect(clip=r,threshold=rifeThresh)
r = core.rife.RIFE(r, model=rifeModel, tta=rifeTTA,uhd=rifeUHD)
if clip.format != vs.RGBS:
r = core.resize.Point(r, format=clip.format, matrix_s="709")

r = core.std.Trim(r, first=1, last=1)
r = core.std.AssumeFPS(r, fpsnum=1, fpsden=1)
a = core.std.Trim(clip1, first=0, last=firstframe-1)
b = core.std.Trim(clip1, first=firstframe+1)
join = a + r + b
return core.std.AssumeFPS(join, src=clip)


def fillWithGMFSSUnion(clip, firstframe=None, gmfssModel=0, gmfssThresh=0.15):
from vsgmfss_fortuna import gmfss_fortuna
clip1 = core.std.AssumeFPS(clip, fpsnum=1, fpsden=1)
start = core.std.Trim(clip1, first=firstframe-1, length=1)
end = core.std.Trim(clip1, first=firstframe+1, length=1)
startend = start + end
if clip.format != vs.RGBH:
r = core.resize.Point(startend, format=vs.RGBH, matrix_in_s="709")
r = gmfss_fortuna(r, model=gmfssModel, sc_threshold=gmfssThresh)
if clip.format != vs.RGBH:
r = core.resize.Point(r, format=clip.format, matrix_s="709")
r = core.std.Trim(r, first=1, last=1)
r = core.std.AssumeFPS(r, fpsnum=1, fpsden=1)
a = core.std.Trim(clip1, first=0, last=firstframe-1)
b = core.std.Trim(clip1, first=firstframe+1)
join = a + r + b
return core.std.AssumeFPS(join, src=clip)



def fillWithSVP(clip, firstframe=None, gpu=False):
clip1 = core.std.AssumeFPS(clip, fpsnum=1, fpsden=1)
start = core.std.Trim(clip1, first=firstframe-1, length=1)
end = core.std.Trim(clip1, first=firstframe+1, length=1)
startend = start + end

if gpu:
super = core.svp1.Super(startend,"{gpu:1}")
else:
super = core.svp1.Super(startend,"{gpu:0}")
vectors= core.svp1.Analyse(super["clip"],super["data"],startend,"{}")
r = core.svp2.SmoothFps(startend,super["clip"],super["data"],vectors["clip"],vectors["data"],"{}")

r = core.std.Trim(r, first=1, last=1)
r = core.std.AssumeFPS(r, fpsnum=1, fpsden=1)
a = core.std.Trim(clip1, first=0, last=firstframe-1)
b = core.std.Trim(clip1, first=firstframe+1)
join = a + r + b
return core.std.AssumeFPS(join, src=clip)

def FillSingleDrops(clip, thresh=0.3, method="mv", rifeModel=0, rifeTTA=False, rifeUHD=False, rifeThresh=0.15, gmfssModel=0, gmfssThresh=0.15, debug=False):
core = vs.core
if not isinstance(clip, vs.VideoNode):
raise ValueError('This is not a clip')

def selectFunc(n, f):
if f.props['PlaneStatsDiff'] > thresh or n == 0:
if debug:
return core.text.Text(clip=clip,text="Org, diff: "+str(f.props['PlaneStatsDiff']),alignment=8)
return clip
else:
if method == "mv":
filldrops=fillWithMVTools(clip)
elif method == "svp":
filldrops=fillWithSVP(clip,n)
elif method == "svp_gpu":
filldrops=fillWithSVP(clip,n,gpu=True)
elif method == "rife":
filldrops = fillWithRIFE(clip,n,rifeModel,rifeTTA,rifeUHD,rifeThresh)
elif method == "gmfssfortuna":
filldrops = fillWithGMFSSUnion(clip,n,gmfssModel,gmfssThresh)
else:
raise vs.Error('FillDrops: Unknown method '+method)
if debug:
return core.text.Text(clip=filldrops,text=method+", diff: "+str(f.props['PlaneStatsDiff']),alignment=8)
return filldrops

diffclip = core.std.PlaneStats(clip, clip[0] + clip)
fixed = core.std.FrameEval(clip, selectFunc, prop_src=diffclip)
return fixed


def InsertSingle(clip, afterEveryX=2, method="mv", rifeModel=0, rifeTTA=False, rifeUHD=False, rifeThresh=0.15, gmfssModel=0, gmfssThresh=0.15, debug=False):
core = vs.core
if not isinstance(clip, vs.VideoNode):
raise ValueError('This is not a clip')

def selectFunc(n):
if n == 0 or n%afterEveryX != 0:
return clip
else:
if method == "mv":
insertFrame=fillWithMVTools(clip)
elif method == "svp":
insertFrame=fillWithSVP(clip,n)
elif method == "svp_gpu":
insertFrame=fillWithSVP(clip,n,gpu=True)
elif method == "rife":
insertFrame = fillWithRIFE(clip,n,rifeModel,rifeTTA,rifeUHD,rifeThresh)
elif method == "gmfssfortuna":
insertFrame = fillWithGMFSSUnion(clip,n,gmfssModel,gmfssThresh)
else:
raise vs.Error('InsertSingle: Unknown method '+method)
return insertFrame.text.Text("Interpolated")

return core.std.FrameEval(clip, selectFunc)

When using method="mv", I get:
Explicitly instantiated a Cache. This is no longer possible and the original clip has been passed through instead.
I think this is the effect of:
added a warning every time the deprecated cache filter is instantiated and ignored source: https://github.com/vapoursynth/vapoursynth/releases/tag/R63
and somehow this is triggered by:

def fillWithMVTools(clip):
super = core.mv.Super(clip, pel=2)
vfe = core.mv.Analyse(super, truemotion=True, isb=False, delta=1)
vbe = core.mv.Analyse(super, truemotion=True, isb=True, delta=1)
return core.mv.FlowInter(clip, super, mvbw=vbe, mvfw=vfe, time=50)



=> How can I fix this or does this require a mvtools update/fix/change to be really compatible with Vapoursynth R63?

Cu Selur

Myrsloik
26th June 2023, 21:36
It's just a warning. The actual behavior hasn't changed from previous versions. I've added it to flush out the last pieces of not fixed code.

In this case I guess mvtools was never updated to api4. I'll have to add it to my todo list...

Selur
27th June 2023, 03:58
Okay, thanks for the info.

Patman
30th August 2023, 07:24
Can anyone help with the following issue?
http://forum.doom9.org/showthread.php?p=1991055#post1991055

For me the changes made by DJATOM are correct.

Myrsloik
18th September 2023, 21:31
R64-RC1 (https://github.com/vapoursynth/vapoursynth/releases/tag/R64-RC1)

Just your average bug fix release to keep up with new things. Will be released in a week or so if nobody finds horrible regressions.

Changes:
added json output of video frame properties to vspipe
fixed clearMap function, previously it would forget to properly clear the error state in maps which could cause crashes in frameeval and other filters
32 bit binaries are no longer provided for windows
updated zimg to fix issues on zen4 cpus
added support for cython 3.x

Myrsloik
27th September 2023, 20:14
R64 has been released.
r64:
fixed compilation on osx where the default standard library doesn't have a full implementation of std::from_chars
added -- as an alternate to . to indicate no output in vspipe since shells have a tendency to expand .
added json output of video frame properties to vspipe
fixed clearMap function, previously it would forget to properly clear the error state in maps which could cause crashes in frameeval and other filters
32 bit binaries are no longer provided for windows
updated zimg to fix issues on zen4 cpus
added support for cython 3.x

Selur
30th September 2023, 13:01
Should R64 simply work as replacement, or should one expect new warnings, errors, not working scripts?

Ci Selur

lansing
2nd October 2023, 08:07
I'm having problem with vapoursynth not releasing all memory in vsedit2 after I closed the file. I had a script opening a dvd video, when I preview it, it took about 530 MB of ram. When I closed the file, there were still 200 MB of ram remained in use. I tried with Virtualdub2 and the same thing happened.

Myrsloik
3rd October 2023, 14:28
I'm having problem with vapoursynth not releasing all memory in vsedit2 after I closed the file. I had a script opening a dvd video, when I preview it, it took about 530 MB of ram. When I closed the file, there were still 200 MB of ram remained in use. I tried with Virtualdub2 and the same thing happened.

New problem in R64 or also in previous versions? Does it happen with trivial scripts too?

lansing
3rd October 2023, 19:05
New problem in R64 or also in previous versions? Does it happen with trivial scripts too?

False alarm. After a few reinstalls of the older version for testing, my problem went away.

Dan64
7th October 2023, 09:47
Hello _Al_,

I found your post very interesting :

Just as a curiosity sort of, you could pipe ffmpeg cmd line in vapoursynth directly in your script.
It is not ideal, it is one way only, no searching etc.
vs script is always python script so all python woo-doo is available in vs script as well. Not sure how many folks realize that.
import vapoursynth as vs
from vapoursynth import core
import subprocess
import ctypes

ffmpeg = r'C:\tools\ffmpeg.exe'
source_path=r'C:\videos\video.mp4'
clip = core.lsmas.LibavSMASHSource(source_path) #this clip is not not needed, just to get width and height
clip = core.std.BlankClip(clip)

w = clip.width
h = clip.height
Ysize = w * h
UVsize = w * h//4
frame_len = w * h * 3 // 2 #YUV420

command = [ ffmpeg, '-i', source_path,'-vcodec', 'rawvideo', '-pix_fmt', 'yuv420p', '-f', 'rawvideo', '-']
pipe = subprocess.Popen(command, stdout = subprocess.PIPE, bufsize=frame_len)

def load_frame(n,f):
try:
vs_frame = f.copy()
for i, size in enumerate([Ysize, UVsize, UVsize]):
ctypes.memmove(vs_frame.get_write_ptr(i), pipe.stdout.read(size), size)
pipe.stdout.flush()
except Exception as e:
raise ValueError(repr(e))
return vs_frame

try:
clip = core.std.ModifyFrame(clip, clip, load_frame)
except ValueError as e:
pipe.terminate()
print(e)

clip.set_output()

The video used for test has resolution: 720x300
The script used for the test is the following:


import vapoursynth as vs
from vapoursynth import core
import subprocess
import ctypes

ffmpeg = r'E:\VideoTest\TestSubs\ffmpeg.exe'
source_path=r'E:\VideoTest\TestSubs\TestVideo.mp4'
# Loading Plugins
core.std.LoadPlugin(path="E:/VideoTest/TestSubs/BestSource.dll") #from https://forum.doom9.org/showthread.php?t=184255
#current color space: YUV420P10, bit depth: 10
#resolution: 720x300, fps: 25, color matrix: 470bg, yuv luminance scale: limited, scanorder: progressive
clip = core.bs.VideoSource(source="E:/VideoTest/TestSubs/TestVideo.mp4") #this clip is not not needed, just to get width and height
# Setting detected color matrix (470bg).
clip = core.std.SetFrameProps(clip, _Matrix=5)
# Setting color transfer info (470bg), when it is not set
clip = clip if not core.text.FrameProps(clip,'_Transfer') else core.std.SetFrameProps(clip, _Transfer=5)
# Setting color primaries info (BT.709), when it is not set
clip = clip if not core.text.FrameProps(clip,'_Primaries') else core.std.SetFrameProps(clip, _Primaries=1)
# Setting color range to TV (limited) range.
clip = core.std.SetFrameProp(clip=clip, prop="_ColorRange", intval=1)
clip = core.std.SetFrameProp(clip=clip, prop="_FieldBased", intval=0) # progressive
# set output frame rate to 25fps (progressive)
clip = core.std.AssumeFPS(clip=clip, fpsnum=25, fpsden=1)
# adjusting output color from: YUV420P8 to YUV420P10
clip = core.resize.Bicubic(clip=clip, format=vs.YUV420P10, range_s="limited")

#clip = core.std.BlankClip(clip)

w = clip.width
h = clip.height
Ysize = w * h
UVsize = w * h//4
frame_len = w * h * 3 // 2 #YUV420

command = [ ffmpeg, '-i', source_path,'-vcodec', 'rawvideo', '-pix_fmt', 'yuv420p', '-f', 'rawvideo', '-']
pipe = subprocess.Popen(command, stdout = subprocess.PIPE, bufsize=frame_len)

def load_frame(n,f):
try:
vs_frame = f.copy()
for i, size in enumerate([Ysize, UVsize, UVsize]):
ctypes.memmove(vs_frame.get_write_ptr(i), pipe.stdout.read(size), size)
pipe.stdout.flush()
except Exception as e:
raise ValueError(repr(e))
return vs_frame

try:
clip = core.std.ModifyFrame(clip, clip, load_frame)
except ValueError as e:
pipe.terminate()
print(e)

clip.set_output()


But I was unable to replicate your script. You can find a script and the video used for the test at the following link: https://filebin.net/4t266xvy94ylt74o/TestVideo_Preview.zip

I commented the creation of BlankClip in order to understand better what is happen. Moreover I added the conversion from: YUV420P8 to YUV420P10 (without I don't see nothing).

In this image you can see the result

https://i.ibb.co/XDcsYCR/preview-ffmpeg-Test-Video-v1-frame.jpg

As you can see is not copied all the frame in output, but only a small part (see square 1) which is duplicated (see square 2). The rectangle 3 represent the part of the original script that is not overridden by ctypes.memmove(). More interesting it is possible to see that the square 3 is not in sync with square 1 & 2.
I was unable to get you script working, could you help me ?

Thanks,
Dan

Dan64
7th October 2023, 17:44
It seems that there is a problem in reading properly the raw video.

I tried the following commands

ffmpeg.exe -i "TestVideo.mp4" -vcodec rawvideo -pix_fmt yuv420p -f rawvideo - | vlc.exe --demux=rawvideo --rawvid-fps=25 --rawvid-width=700 --rawvid-height=300 --rawvid-chroma=I420 -
ffmpeg.exe -i "TestVideo.mp4" -vcodec rawvideo -pix_fmt yuv420p -f rawvideo - | ffplay.exe -f rawvideo -pixel_format yuv420p -video_size 720x300 -i -


and both ffplay and vlc are unable to properly play the video (with ffplay being better than vlc).

It seems to me strange that a problem like this has never been already discovered. So what's wrong ?

_Al_
8th October 2023, 02:22
Quick tested it with another 1920x1080 mp4, 4:2:0 video I have here and it worked, so ffmpeg's rawvideo loaded it ok, but your mp4 file did not work.

I suspect that height mod might be a problem, it is 4 for 300, maybe mod 8 is needed for ffmpeg. Would resizing it to 304 help for example? Source video, not in vapoursynth.

Dan64
8th October 2023, 09:16
Quick tested it with another 1920x1080 mp4, 4:2:0 video I have here and it worked, so ffmpeg's rawvideo loaded it ok, but your mp4 file did not work.

I suspect that height mod might be a problem, it is 4 for 300, maybe mod 8 is needed for ffmpeg. Would resizing it to 304 help for example? Source video, not in vapoursynth.

I found the problem regarding the "ffplay". It was due to the fact that i was running the scripts in PowerShell, by running them in the command dos (cmd.exe) all the version of ffpay tested are woking. Even without resize mod 8.

But the problem on Vapoursynth side still remain...

Dan64
8th October 2023, 10:09
I was finally able to get the following script working

import vapoursynth as vs
from vapoursynth import core
import subprocess
import ctypes

ffmpeg = r'E:\VideoTest\TestSubs\ffmpeg.exe'
source_path=r'E:\VideoTest\TestSubs\TestVideo.mp4'
# Loading Plugins
core.std.LoadPlugin(path="E:/VideoTest/TestSubs/BestSource.dll") #from https://forum.doom9.org/showthread.php?t=184255
#current color space: YUV420P8, bit depth: 8
#resolution: 1280x536, fps: 25, color matrix: 470bg, yuv luminance scale: limited, scanorder: progressive

#this clip is not not needed, just to get width and height
clip = core.bs.VideoSource(source=source_path)
# Setting detected color matrix (470bg).
clip = core.std.SetFrameProps(clip, _Matrix=5)
# Setting color transfer info (470bg), when it is not set
clip = clip if not core.text.FrameProps(clip,'_Transfer') else core.std.SetFrameProps(clip, _Transfer=5)
# Setting color primaries info (BT.709), when it is not set
clip = clip if not core.text.FrameProps(clip,'_Primaries') else core.std.SetFrameProps(clip, _Primaries=1)
# Setting color range to TV (limited) range.
clip = core.std.SetFrameProp(clip=clip, prop="_ColorRange", intval=1)
clip = core.std.SetFrameProp(clip=clip, prop="_FieldBased", intval=0) # progressive
# set output frame rate to 25fps (progressive)
clip = core.std.AssumeFPS(clip=clip, fpsnum=25, fpsden=1)

clip = core.std.BlankClip(clip)

w = clip.width
h = clip.height
Ysize = w * h
Usize = w * h//4
Vsize = w * h//4
frame_len = Ysize + Usize + Vsize #YUV420

command = [ ffmpeg, '-i', source_path,'-vcodec', 'rawvideo', '-pix_fmt', 'yuv420p', '-f', 'rawvideo', '-']

pipe = subprocess.Popen(command, stdout = subprocess.PIPE, bufsize=frame_len)

def load_frame_from_pipe(n,f):
vs_frame = f.copy()
try:
for plane, size in enumerate([Ysize, Usize, Vsize]):
ctypes.memmove(vs_frame.get_write_ptr(plane), pipe.stdout.read(size), size)
pipe.stdout.flush()
except Exception as e:
raise ValueError(repr(e))
return vs_frame

try:
clip = core.std.ModifyFrame(clip, clip, load_frame_from_pipe)
except ValueError as e:
pipe.terminate()
print(e)

clip.set_output()


It seems that the problem was related to mod 8 and to conversion to YUV420P810. :)

P.S.
The video used for test is available here: https://filebin.net/trb7yof9h0g335e0

Myrsloik
9th October 2023, 17:42
R65-RC1 (https://github.com/vapoursynth/vapoursynth/releases/tag/R65-RC1)
frame properties in python are now return as str type instead of bytes when hinted as utf8 printable
fixed how unprintable data is returned from plugin functions in python, previously it would leak a ctypes pointer with no length instead of returning a bytes object
fixed a bug in the avx2 maskedmerge float premultiplied code path that would switch the two input clips
reverted the from_chars code a bit more to make no locale affects float parsing
fixed the sar adjustment for real this time
Test and verify the fixes. Report if there are any scripts that actually break due to the change in how frame properties are returned.

_Al_
9th October 2023, 20:18
It seems that the problem was related to mod 8 and to conversion to YUV420P10.
ffmpeg and vapoursynth have to have same video, it cannot be changed in vapoursynth, because bytes that ffmpeg reads go right into vapoursynth clip planes
, it should be the same.
If source video is 10bit though, then byte sizes would be different, 10bit uses 2bytes per value:
Ysize = w * h * 2
Usize = w * h//2
Vsize = w * h//2
YUVsize = Ysize + Usize + Vsize

Selur
22nd October 2023, 09:34
Has anyone an updated SMDegrain for Vapoursynth? (latest Vapoursynth version I know of is v3.1.2d, current Avisynth version is v4.5.0d afaik.)

Jukus
7th November 2023, 16:01
Upgraded my ArchLinux and now I have a similar error, how to fix it?
Failed to evaluate the script:
Python exception: Expr: failed to convert '129.0' to float, not the whole token could be converted

Traceback (most recent call last):
File "src/cython/vapoursynth.pyx", line 3121, in vapoursynth._vpy_evaluate
File "src/cython/vapoursynth.pyx", line 3122, in vapoursynth._vpy_evaluate
File "/tmp/1/script.vpy", line 30, in
#v = haf.QTGMC(v, Preset='Very Slow', Sharpness=0.4, FPSDivisor=1, TFF=True)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.11/site-packages/havsfunc/havsfunc.py", line 2561, in QTGMC
repair0 = QTGMC_KeepOnlyBobShimmerFixes(binomial0, bobbed, Rep0, RepChroma and ChromaMotion)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.11/site-packages/havsfunc/havsfunc.py", line 3181, in QTGMC_KeepOnlyBobShimmerFixes
)

File "src/cython/vapoursynth.pyx", line 2857, in vapoursynth.Function.__call__
vapoursynth.Error: Expr: failed to convert '129.0' to float, not the whole token could be converted

Myrsloik
7th November 2023, 17:36
Upgraded my ArchLinux and now I have a similar error, how to fix it?
Failed to evaluate the script:
Python exception: Expr: failed to convert '129.0' to float, not the whole token could be converted

Traceback (most recent call last):
File "src/cython/vapoursynth.pyx", line 3121, in vapoursynth._vpy_evaluate
File "src/cython/vapoursynth.pyx", line 3122, in vapoursynth._vpy_evaluate
File "/tmp/1/script.vpy", line 30, in
#v = haf.QTGMC(v, Preset='Very Slow', Sharpness=0.4, FPSDivisor=1, TFF=True)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.11/site-packages/havsfunc/havsfunc.py", line 2561, in QTGMC
repair0 = QTGMC_KeepOnlyBobShimmerFixes(binomial0, bobbed, Rep0, RepChroma and ChromaMotion)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.11/site-packages/havsfunc/havsfunc.py", line 3181, in QTGMC_KeepOnlyBobShimmerFixes
)

File "src/cython/vapoursynth.pyx", line 2857, in vapoursynth.Function.__call__
vapoursynth.Error: Expr: failed to convert '129.0' to float, not the whole token could be converted

Will we ever know the version? The suspense IS KILLING ME!

Jukus
7th November 2023, 18:47
R64-1

Myrsloik
7th November 2023, 19:01
R64-1

It was probably fixed in R65

Selur
11th November 2023, 19:56
Is there a port of ffmpegs colortemperature (https://www.ffmpeg.org/ffmpeg-all.html#toc-colortemperature) for Vapoursynth or an alternative filter or way to do this?

poisondeathray
12th November 2023, 01:38
Is there a port of ffmpegs colortemperature (https://www.ffmpeg.org/ffmpeg-all.html#toc-colortemperature) for Vapoursynth or an alternative filter or way to do this?


a crappy workaround to use any ffmpeg processing in vpy script is ffmpeg pipe into vsrawsource

Selur
13th November 2023, 18:04
I found this (https://stackoverflow.com/questions/11884544/setting-color-temperature-for-a-given-image-like-in-photoshop), which uses OpenCV and using:
import cv2
import muvsfunc_numpy as mufnp
import numpy as np
from PIL import Image

kelvin_table = {
1000: (255,56,0),
1500: (255,109,0),
2000: (255,137,18),
2500: (255,161,72),
3000: (255,180,107),
3500: (255,196,137),
4000: (255,209,163),
4500: (255,219,186),
5000: (255,228,206),
5500: (255,236,224),
6000: (255,243,239),
6500: (255,249,253),
7000: (245,243,255),
7500: (235,238,255),
8000: (227,233,255),
8500: (220,229,255),
9000: (214,225,255),
9500: (208,222,255),
10000: (204,219,255)}

def numpy2pil(np_array: np.ndarray) -> Image:
"""
Convert an HxWx3 numpy array into an RGB Image
"""

assert_msg = 'Input shall be a HxWx3 ndarray'
assert isinstance(np_array, np.ndarray), assert_msg
assert len(np_array.shape) == 3, assert_msg
assert np_array.shape[2] == 3, assert_msg

img = Image.fromarray(np_array, 'RGB')
return img

def pil2numpy(img: Image = None) -> np.ndarray:
"""
Convert an HxW pixels RGB Image into an HxWx3 numpy ndarray
"""
np_array = np.asarray(img)
return np_array

def convert_temp(image, temp):
r, g, b = kelvin_table[temp]
matrix = ( r / 255.0, 0.0, 0.0, 0.0,
0.0, g / 255.0, 0.0, 0.0,
0.0, 0.0, b / 255.0, 0.0 )
img = numpy2pil(image)
return pil2numpy(img.convert('RGB', matrix))

range = "full"
if core.text.FrameProps(clip,'_ColorRange'):
range = "limited"

clip = core.resize.Bicubic(clip=clip, format=vs.RGB24, range_s=range)
clip = mufnp.numpy_process(clip, convert_temp, temp=6500, input_per_plane=False, output_per_plane=False)
it seems to work, but the problem with that is it requires OpenCV and I'm not really sure how 'good' this is.
Instead of using the kelvin_table one could probably use something similar to: https://academo.org/demos/colour-temperature-relationship/ which uses:
/**
* http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code/
* */
function KToRGB(Temperature){

Temperature = Temperature / 100;

if (Temperature <= 66){
Red = 255;
} else {
Red = Temperature - 60;
Red = 329.698727466 * Math.pow(Red, -0.1332047592);
if (Red < 0){
Red = 0;
}
if (Red > 255){
Red = 255;
}
}

if (Temperature <= 66){
Green = Temperature;
Green = 99.4708025861 * Math.log(Green) - 161.1195681661;
if (Green < 0 ) {
Green = 0;
}
if (Green > 255) {
Green = 255;
}
} else {
Green = Temperature - 60;
Green = 288.1221695283 * Math.pow(Green, -0.0755148492);
if (Green < 0 ) {
Green = 0;
}
if (Green > 255) {
Green = 255;
}
}

if (Temperature >= 66){
Blue = 255;
} else {
if (Temperature <= 19){
Blue = 0;
} else {
Blue = Temperature - 10;
Blue = 138.5177312231 * Math.log(Blue) - 305.0447927307;
if (Blue < 0){
Blue = 0;
}
if (Blue > 255){
Blue = 255;
}
}
}

rgb = new Array(Math.round(Red),Math.round(Green),Math.round(Blue));
return rgb;

}

Maybe a c++ port of this would be a cool thing,..

cubicibo
13th November 2023, 20:57
Anythig numpy can go turbo with Numba njit. And in this case even benefit of parallelism thanks to independant processing of the planes. However, muvsfunc would have to be rewritten extensively to only access Python and numpy primitives in its core muvsfunc_numpy module.

Is the sample you posted incomplete? There are no acccess to cv2 and the import is unused.

Selur
13th November 2023, 21:05
You are right, I don't need openCV, I first had it in there for some image handling, but I noticed that just using numpy was faster.
Okay, so the next question is: is numpy really needed?

cubicibo
13th November 2023, 21:31
You can drop Pillow, not numpy.


def convert_temp(frame: np.ndarray, temp: int) -> np.ndarray:
return frame*(np.array(kelvin_table[temp], dtype=float)/255.0)


You may additionally want to np.round() before casting back to np.uint8.

WolframRhodium
14th November 2023, 00:09
colortemperature can be implemented using only vanilla std.Expr + std.ShufflePlanes.

_Al_
14th November 2023, 03:14
You can drop Pillow, not numpy.


def convert_temp(frame: np.ndarray, temp: int) -> np.ndarray:
return frame*(np.array(kelvin_table[temp], dtype=float)/255.0)


You may additionally want to np.round() before casting back to np.uint8.
I tried this and it seams to work, more testing maybe needed. Basics are used, just numpy and vapoursynth, no muvsfunc_numpy , no PIL:

import vapoursynth as vs
from vapoursynth import core
import numpy as np

class Temperature:
KELVIN_TABLE = {
1000: (255,56,0),
1500: (255,109,0),
2000: (255,137,18),
2500: (255,161,72),
3000: (255,180,107),
3500: (255,196,137),
4000: (255,209,163),
4500: (255,219,186),
5000: (255,228,206),
5500: (255,236,224),
6000: (255,243,239),
6500: (255,249,253),
7000: (245,243,255),
7500: (235,238,255),
8000: (227,233,255),
8500: (220,229,255),
9000: (214,225,255),
9500: (208,222,255),
10000: (204,219,255)}

def __init__(self, temp):
self.rgb = self.KELVIN_TABLE[temp]

def change(self, n, f):
f_out = f.copy()
for p in range(3):
npArray = np.asarray(f[p])
npArray = npArray*(np.array(self.rgb[p], dtype=float)/255.0)
np.copyto(np.asarray(f_out[p]), npArray[:,:])
return f_out

clip = core.lsmas.LibavSMASHSource('video.mp4')
clip = clip.resize.Point(format=vs.RGBS, matrix_in_s = '709')
clip = core.std.ModifyFrame(clip, clip, Temperature(4000).change)
clip = clip.resize.Point(format=vs.YUV420P8, matrix_s = '709')
clip.set_output(0)

_Al_
14th November 2023, 05:29
colortemperature can be implemented using only vanilla std.Expr + std.ShufflePlanes.
this seams to perform about the same speed as example above:

import vapoursynth as vs
from vapoursynth import core

KELVIN_TABLE = {
1000: (255,56,0),
1500: (255,109,0),
2000: (255,137,18),
2500: (255,161,72),
3000: (255,180,107),
3500: (255,196,137),
4000: (255,209,163),
4500: (255,219,186),
5000: (255,228,206),
5500: (255,236,224),
6000: (255,243,239),
6500: (255,249,253),
7000: (245,243,255),
7500: (235,238,255),
8000: (227,233,255),
8500: (220,229,255),
9000: (214,225,255),
9500: (208,222,255),
10000: (204,219,255)}

def change_temperature(clip, temp):
rgb = KELVIN_TABLE[temp]
rgb = [value/255.0 for value in rgb]
planes = core.std.SplitPlanes(clip)
planes = [core.std.Expr(plane, expr=[f"x {rgb[i]} *"]) for i, plane in enumerate(planes)]
return core.std.ShufflePlanes(clips=planes, planes=[0], colorfamily=vs.RGB)

clip = core.lsmas.LibavSMASHSource('video.mp4')
clip = clip.resize.Point(format=vs.RGBS, matrix_in_s = '709')
clip = change_temperature(clip, 4000)
clip = clip.resize.Point(format=vs.YUV420P8, matrix_s = '709')
clip.set_output(0)

_Al_
14th November 2023, 06:06
to not use KELVIN_TABLE, and using Selur's code in python to use any temperature, not just selected from a table:

import vapoursynth as vs
from vapoursynth import core
import math

def get_rgb(temp):
temp = temp / 100
if temp <= 66:
r = 255
else:
r = temp - 60
r = 329.698727466 * math.pow(r, -0.1332047592)
r = min(max(0, r), 255)

if temp <= 66:
g = temp
g = 99.4708025861 * math.log(g) - 161.1195681661
else:
g = temp - 60
g = 288.1221695283 * math.pow(g, -0.0755148492)
g = min(max(0, g), 255)

if temp >= 66:
b = 255
else:
if temp <= 19:
b = 0
else:
b = temp - 10
b = 138.5177312231 * math.log(b) - 305.0447927307
b = min(max(0, b), 255)

return round(r), round(g), round(b)

def change_temperature(clip, temp):
rgb = get_rgb(temp)
r, g, b = [value/255.0 for value in rgb]
return core.std.Expr([clip], expr=[f"x {r} *", f"x {g} *", f"x {b} *"])

clip = core.lsmas.LibavSMASHSource('video.mp4')
clip = clip.resize.Point(format=vs.RGBS, matrix_in_s = '709')
clip = change_temperature(clip, 4345)
clip = clip.resize.Point(format=vs.YUV420P8, matrix_s = '709')
clip.set_output(0)
Expr seams to perform faster than working with numpy arrays.

Selur
14th November 2023, 06:46
Nice, thanks.

rgr
23rd November 2023, 10:56
Vapoursynth needs a specific version of Python (3.11)? I have 3.13 and it doesn't want to install.

cubicibo
23rd November 2023, 11:06
You need 3.11.
Why would you even use Python 3.13? It is in an early alpha and the Python fundation strongly advise to not use it [for production].