Welcome to Doom9's Forum, THE in-place to be for everyone interested in DVD conversion.

Before you start posting please read the forum rules. By posting to this forum you agree to abide by the rules.

 

Go Back   Doom9's Forum > Capturing and Editing Video > Avisynth Development

Reply
 
Thread Tools Search this Thread Display Modes
Old 1st March 2012, 03:50   #1  |  Link
maxxon
Registered User
 
maxxon's Avatar
 
Join Date: Jan 2012
Location: On the net
Posts: 76
AviSynthLib v0.52b now released

Yay!! Finally, this has been a while in the making, but I think I've finally got the AviSynthLib library in a way that I want.
EDIT: Just updated v0.5b to v0.51b which theoretically allows use of the AviSynthLib in AviSynth v2.07 or higher. Tested to work with AviSynth v2.58 and higher.
EDIT: Just updated v0.51b to v0.52b which replaces the workaround StrCmp() which was actually case insensitive, to a fully working case sensitive version.

Get AviSynthLib v0.52b (291.5kB) from SourceForge.

With this base library you can use arrays as queues/stacks and things to iterate over. Anyone who writes scripts for AviSynth could benefit from this library. I've tried to make these as fast as possible for running very low and long manipulations, yet somewhat easier to use since 0.1b. For just the casual user though, these are super fast. Iterating and making decisions on over 2000 items takes 2-4 seconds (in comparison, AvsLib took over a minute and a half on 64 elements).

But that's not all, while making usable fast arrays that can store any type a reality (referenceArray.avs), several other libraries fell out too.
  • Native for loops (for.avs)
  • Blocks to contain data in a string format (block.avs)
  • Memory management (memory.avs)
  • Profiling tool (profile.avs)
  • Regression testing (testing.avs)
  • File and path manipulations and searches (file.avs)
  • Variable scope insertion (var.avs/varConst.avs)
  • Additional hex number support (hex.avs)
  • Additional string support (string.avs)
  • Including script files only once with ability to have multiple auto searched include directories (include.avsi)

There is also a usage document (AviSynthLib Usage.pdf) which can help you get started in understanding these libraries.

Some changes that occurred between 0.1b and 0.5b:
  • Replaced old SESA/RA_foreach construct with a new improved model
  • Introduced file, string, include, vars, and varsConst libraries.
  • Added and fixed SESA/RA_findSorted, SESA/RA_setSorted functions.
  • Added ability to take variables into and out of the for loop scope.
  • Fixed some miscellaneous bugs.
  • Added AviSynthLib Usage.odt/AviSynthLib Usage.pdf for your reading pleasure and updated the README.txt file.

I hope you make good use out of this and enjoy these libraries as much as I had making them. Feel free to post questions here in this forum or email me at adrianh[D0T]bsc[@T]gmail[D0T]com. Please put in the subject line the word AviSynthLib so that it will be sorted for optimal reading.

TTFN


|\/|x

P.S. If you are using a version of AviSynth prior to 2.60 *AND* you did not install in C:\Program Files\AviSynth 2.5, you must make a modification to the include.avsi file. Look for this line in that file:
global ScriptDir="C:\Program Files\AviSynth 2.5\plugins\"
You must change that path to your installation directory. Also, make sure you keep the trailing "\plugins\" folder listed.

Also, it should work on versions prior to 2.58. I've not tested this, though if any of you are still running such a system I would be interested in finding out if it works properly.

Last edited by maxxon; 2nd March 2012 at 18:10.
maxxon is offline   Reply With Quote
Old 1st March 2012, 10:06   #2  |  Link
pandy
Registered User
 
Join Date: Mar 2006
Posts: 1,038
Quote:
Originally Posted by RenePaley007 View Post
What is your emai address? I am interested
adrianh[D0T]bsc[@T]gmail[D0T]com
pandy is offline   Reply With Quote
Old 1st March 2012, 11:14   #3  |  Link
hanfrunz
Registered User
 
hanfrunz's Avatar
 
Join Date: Feb 2002
Location: Germany
Posts: 534
Hello maxxon,
can you explain in few words who can use the lib? Is it for plugin developers? Looks very interesting, but i don't understand for what to use it. Maybe i'm getting old

regards,
hanfrunz
hanfrunz is offline   Reply With Quote
Old 1st March 2012, 17:22   #4  |  Link
StainlessS
HeartlessS Usurer
 
StainlessS's Avatar
 
Join Date: Dec 2009
Location: Over the rainbow
Posts: 5,672
Have only just taken a brief look but found something that stuck out immediately.
I see nothing indicating an Avisynth v2.6 requirement, but does use eg
'Script***' functions added in v2.6, eg ScriptDir, ScriptName, ScriptFile
(at least some of them. EDIT: Only ScriptDir).
There is a thread somewhere on synthesized versions of those functions
using Try/Catch but a bit messy.
Perhaps a v2.6 requirement might not be such a bad idea, start from a
solid position.
EDIT: My view, 2.6alpha3 is generally at least as reliable as 2.58 and has
new found problems fixed whereas 2.58 does not.
__________________
I sometimes post sober.
StainlessS@MediaFire ::: AND/OR ::: StainlessS@SendSpace

"Some infinities are bigger than other infinities", but how many of them are infinitely bigger ???

Last edited by StainlessS; 1st March 2012 at 19:04.
StainlessS is offline   Reply With Quote
Old 1st March 2012, 18:05   #5  |  Link
Gavino
Avisynth language lover
 
Join Date: Dec 2007
Location: Spain
Posts: 3,380
Quote:
Originally Posted by maxxon View Post
Native for loops (for.avs)
What do you mean by 'native' here?
__________________
GScript and GRunT - complex Avisynth scripting made easier
Gavino is offline   Reply With Quote
Old 1st March 2012, 22:52   #6  |  Link
maxxon
Registered User
 
maxxon's Avatar
 
Join Date: Jan 2012
Location: On the net
Posts: 76
Quote:
Originally Posted by hanfrunz View Post
Hello maxxon,
can you explain in few words who can use the lib? Is it for plugin developers? Looks very interesting, but i don't understand for what to use it. Maybe i'm getting old

regards,
hanfrunz
Anyone using the AviSynth scripting language. This is an addon for those users to be able to do more interesting things.


|\/|x

Last edited by maxxon; 1st March 2012 at 23:11.
maxxon is offline   Reply With Quote
Old 1st March 2012, 23:07   #7  |  Link
maxxon
Registered User
 
maxxon's Avatar
 
Join Date: Jan 2012
Location: On the net
Posts: 76
Quote:
Originally Posted by StainlessS View Post
Have only just taken a brief look but found something that stuck out immediately.
I see nothing indicating an Avisynth v2.6 requirement, but does use eg
'Script***' functions added in v2.6, eg ScriptDir, ScriptName, ScriptFile
(at least some of them. EDIT: Only ScriptDir).
There is a thread somewhere on synthesized versions of those functions
using Try/Catch but a bit messy.
Perhaps a v2.6 requirement might not be such a bad idea, start from a
solid position.
EDIT: My view, 2.6alpha3 is generally at least as reliable as 2.58 and has
new found problems fixed whereas 2.58 does not.


Thanks for the heads up Stainless, that totally got by me. I've written some workarounds into the include.avsi file and am releasing it under v0.51b. Unfortunately, sourceforge isn't allowing me to update anything at the moment. As soon as it does, I'll upload it.

EDIT: The requirements aren't that stringent. If someone wishes to use it with an older version, they can go ahead. One thing that I found in the new 2.6alpha which could so something odd is that if you concatenate a 4095 character string with a single character string, you can get junk on the end. Since my lib relies on concatenation, this could be a problem, but you would have to hit the library pretty hard.


|\/|x

Last edited by maxxon; 2nd March 2012 at 09:35.
maxxon is offline   Reply With Quote
Old 1st March 2012, 23:11   #8  |  Link
maxxon
Registered User
 
maxxon's Avatar
 
Join Date: Jan 2012
Location: On the net
Posts: 76
Quote:
Originally Posted by Gavino View Post
What do you mean by 'native' here?
I mean, without using an external filter such as GScript. It works entirely within the confines of the AviSynth scripting language.


|\/|x
maxxon is offline   Reply With Quote
Old 3rd March 2012, 06:56   #9  |  Link
maxxon
Registered User
 
maxxon's Avatar
 
Join Date: Jan 2012
Location: On the net
Posts: 76
In the AviSynthLib Usage guide, I found an error in the For Revisited section. It is highlighted here in red.

Quote:
For Revisited
Well, now that we know about vars and varsConst, we can use that in our for loops too. Just insert the vars object prior to the start index. If you remember our previous example:

Code:
var1Ref = blankclip.mem_new
result = for(0, 2, """
	var1Ref=""""+var1Ref+""""
	var1Ref.mem_set(version)
	for_returnStop("done")
"""", "").for_result
assert(result == "done")
var1Ref.mem_get
This now can be rewritten like this

Code:
var1 = blankclip
result = for(vars_push("var1").eval, 0, 2, """
	var1 = version
	for_returnStop("done")
"""", "").eval.for_result
assert(result == "done")
var1
which still does the same thing but is a bit cleaner.
Note that if you had done this without the .eval, an error would have been generated, followed by the stack info:

Code:
Avisynth error:
Didn't eval last vars_push.
(C:\Program Files\AviSynth 2.5\plugins\AviSynthLib\vars.avs, line 330)
(C:\Program Files\AviSynth 2.5\plugins\AviSynthLib\vars.avs, line 209)
(C:\Program Files\AviSynth 2.5\plugins\aviSynthLib\for.avs, line 173)
(testFile.avs, line 16)
Unfortunately, this is actually reporting the error after it has passed the missing .eval so the stack position is only coincidentally close to where it happened. If it were possible to get the stack info at any time, I would be able to pull that data at time of push, making this somewhat easier to debug.

Ahhh well. Such is life. At least it does say what function call it was that was forgotten to be evaled.

This has been revised in the new version, but I will not release that as yet. The standalone .pdf doc will be updated, but I'm not updating the version in the .7z archive. I'll probably, in fact, remove this from the archive next time around so that I will be able to do mods on that doc without impacting the release build.


|\/|x

Last edited by maxxon; 3rd March 2012 at 07:01.
maxxon is offline   Reply With Quote
Old 7th March 2012, 18:29   #10  |  Link
maxxon
Registered User
 
maxxon's Avatar
 
Join Date: Jan 2012
Location: On the net
Posts: 76
BTW, if anyone actually thinks these libraries are useful or think of something else that could be done, please feel free to post.


|\/|x

Last edited by maxxon; 7th March 2012 at 18:32.
maxxon is offline   Reply With Quote
Old 7th March 2012, 22:07   #11  |  Link
jmac698
Registered User
 
Join Date: Jan 2006
Posts: 1,860
Hi,
I've written a function to substitute actual variables into a masktools expression. Instead of x+" "+y+" +" stuff you could just write argstr="x y +". I thought of a few ways to interface this, I could call something like makeexp("x y +",x,y) or else use eval and just pass something easier (I forget exactly now).
In order to get this working I had to write a parser in avisynth script! There is heavy use of search and replace on strings. I could probably use your lib for this, but even, add it to your lib - however this seems mostly specific to create arguments for masktools. Would that be within the scope of what you want in the library?
jmac698 is offline   Reply With Quote
Old 13th March 2012, 08:21   #12  |  Link
maxxon
Registered User
 
maxxon's Avatar
 
Join Date: Jan 2012
Location: On the net
Posts: 76
Quote:
Originally Posted by jmac698 View Post
Hi,
I've written a function to substitute actual variables into a masktools expression. Instead of x+" "+y+" +" stuff you could just write argstr="x y +". I thought of a few ways to interface this, I could call something like makeexp("x y +",x,y) or else use eval and just pass something easier (I forget exactly now).
In order to get this working I had to write a parser in avisynth script! There is heavy use of search and replace on strings. I could probably use your lib for this, but even, add it to your lib - however this seems mostly specific to create arguments for masktools. Would that be within the scope of what you want in the library?
Interesting... I'm not sure what you would use this for, but could be interesting.

The reference arrays could do something similar. Say you had a 2 or more element array, and you wished to separate the values with spaces and end the list with a " +". You could do this (assuming that array is the array in question):
Code:
array.RA_csv(" ")+" +"
So to do exactly what you were saying with x and y it would look like this:
Code:
array=RA_new(x, y)
argstr=array.RA_csv(" ")+" +"
array.RA_delete
I have written a parser in the AviSynth scripting language, but I do find it 'slow' (I was testing a structure implementation). I was finding it taking a second to do something which I could do in 10s of milliseconds using native structures such as function calls.

However, I would be interested in your ideas. Please let me know what purpose this would be for so that we can discuss this further.




|\/|x
maxxon is offline   Reply With Quote
Old 14th March 2012, 05:22   #13  |  Link
jmac698
Registered User
 
Join Date: Jan 2006
Posts: 1,860
Hi,
Something like this.
The point is to turn "x*y" into "1 2 *" for masktools. The reason is that the complex expressions I had to make for masktools was too hard to write, debug, or create.
Code:
expr=test(1,2)
  #returns "1 2 *"
messageclip(expr)

function test(int x, int y){
  global tmp_x=x
  global tmp_y=y
  makeexpr("x*y")
}

function makeexpr(string template){
  template=mt_polish(template)
  template=leftstr(template,strlen(template)-1)
  expr=StrSplit(template,0,"")
  leftstr(expr,strlen(expr)-1)
}

function var_repl(string base, int currpos, string currexpr){
  sep="$"#separator starting variable name (ends with non-alphabet character)
  subbase=midstr(base,currpos+StrLen(sep))#remainder from currpos
  pos = FindStr(subbase, sep)#find the next separator
  currsubexpr=(pos==0)?midstr(subbase,1):midstr(subbase,1,pos-1)#the leftmost subexpr, i.e. from currpos to space-1/end
  return (pos == 0 || subbase=="") \
  ? currexpr+process(currsubexpr) : StrSplit( \
    base, currpos+pos, currexpr+process(currsubexpr) \
    )
}

function process(string subexpr){
  knownops="*"
  #return "$"+subexpr
  #"+-*/%^sincostanasinacosatanexplogabsroundclipminmax&|&!@&u|uu@u~u<<u>>u&s|ss@s~s<<s>>s<><=>=!====pi?"
  findstr(subexpr,knownops)<>0 ? subexpr+" " : eval("string(tmp_"+subexpr+")")+" "
}

function strClassify(string s, string special){
  #Classify a character as symbol (1), number (2), letter (4), special (8) or null (0)
  s=leftstr(s,1)
  s=="" ? 0  : \
  s==special ? 8 : \
  (s>="a" && s<="z") || (s>="A" && s<="Z") ? 4 : \
  s>="0" && s<="9" ? 2 : \
  1
}

function FindStrClass(string base, int classmask, string special){
  #Return position of specified character class
  #symbol=1, number=2, letter=4, special=8, null=0
  #these can be combined for classmask, 
  #e.g. maskclass=14 is special or letter or number
  FindStrClass2(base, classmask, special, 1)
}

function FindStrClass2(string base, int classmask, string special, int currpos){
  substr=midstr(base,currpos,1)
  t=binbool(strClassify(substr, special),classmask,"and")==0 || currpos==strlen(base)+1
  t ? currpos-1 : \
  FindStrClass2(base, classmask, special, currpos+1)
}

function binbool(int a, int b, string op){
  #Find the binary AND or OR on a and b
  op=lcase(op)
  op=op=="or"?"||" : \
  op=="and"?"&&" : \
  strlen(op)==1?op+op:op
  binbool2(a, b, op, 0)
}

function binbool2(int a, int b, string op, int currresult){
  m=max(a,b)
  pwr2=int(pow(2,floor(log(m)/log(2))))
  bit_a=floor(a/pwr2)
  bit_b=floor(b/pwr2)
  t=eval(string(bool(bit_a))+op+string(bool(bit_b)))?pwr2:0
  currresult=currresult+t
  a=a-bit_a*pwr2
  b=b-bit_b*pwr2
  a+b==0?currresult:binbool2(a, b, op, currresult)
}

function bool(int n){
  #Return false if n=0, true otherwise
  n==0?false:true
}
jmac698 is offline   Reply With Quote
Old 15th March 2012, 02:22   #14  |  Link
maxxon
Registered User
 
maxxon's Avatar
 
Join Date: Jan 2012
Location: On the net
Posts: 76
Well jmac, if it is something that you think would be useful to the community and wish to contribute, then great.

However, if you want to add it to AviSynthLib, then I would require it to abide by the naming conventions to keep it from potentially interfering with other libraries as well as commenting the interfaces (both 'private' and 'public', where the private one is for maintainers and the public is for library users) as a minimum. Looks like you have done some inline commenting which is good.

The coding standards are pretty minimal. All functions and global variables related to the library need to:
  1. Be located in a file which:
    1. Has a GPL or compatible agreement at the beginning
    2. Followed by a verbose description of what the library does, caveats and potentially a usage guide
    3. Followed by a log.
    4. My suggestion is that you just copy and paste from one of my libraries and modify as needed.
  2. Be prefixed with the same name or acronym, followed by:
    1. One underscore ('_') (no that's not supposed to be a face ) to represent a function or global name for public consumption.
    2. Two or more underscores to represent internal a function or global not to be used directy by external users of the library. I usually use two for private and three for autogenerated functions/globals, but whatev.
You would also need an account on sourceforge.net and I would then give you access to the AviSynthLib project. I will try and give you a hand if you are not familiar with this.

Please note, you don't have to combine with my library, but you are more than welcome to.

EDIT: Also note, that if you are doing this as a one shot, that's fine, but then really comment your code please so that other maintainers can figure out what you have done. By adding to this library, it would be great if you can at least maintain your own code.


|\/|x

Last edited by maxxon; 15th March 2012 at 02:26.
maxxon is offline   Reply With Quote
Old 15th March 2012, 08:02   #15  |  Link
jmac698
Registered User
 
Join Date: Jan 2006
Posts: 1,860
I'm trying this, it's pretty wonky though:
Code:
forResult = for(2,11,"""
 for_returnContinue(string(value(forState) + forIndex))
 ""","1").for_result
messageclip(forResult)
Could you make forState take a var so I could use an int? There must be a way to make a more normal for loop like
Code:
s=0
for(1,10,"""
s=s+forIndex
""")
jmac698 is offline   Reply With Quote
Old 15th March 2012, 10:21   #16  |  Link
Gavino
Avisynth language lover
 
Join Date: Dec 2007
Location: Spain
Posts: 3,380
Quote:
Originally Posted by jmac698 View Post
There must be a way to make a more normal for loop
Yes. It's called GScript.
__________________
GScript and GRunT - complex Avisynth scripting made easier
Gavino is offline   Reply With Quote
Old 15th March 2012, 17:17   #17  |  Link
maxxon
Registered User
 
maxxon's Avatar
 
Join Date: Jan 2012
Location: On the net
Posts: 76
Quote:
Originally Posted by jmac698 View Post
I'm trying this, it's pretty wonky though:
Could you make forState take a var so I could use an int?
It is possible that I could, but then I'd have to kill you.

The thing is, this is a generic for loop intended for certain optimizations and building strings while being as fast as possible without going outside and using a filter like GScript. You can do something like you want without the string conversion stuff, but it would probably look more like this:
Code:
result = 1
for(vars_push("result").eval, 2,11,"""
 result = result + forIndex
""","").eval
messageclip(result)

Quote:
Originally Posted by jmac698 View Post
There must be a way to make a more normal for loop like
Code:
s=0
for(1,10,"""
  s=s+forIndex
""")
Yeah, somewhat normal like this:
Code:
s=0
for(vars_push("s").eval, 1,10,"""
  s=s+forIndex
""", "").eval
This pushs the "s" variable out of the current scope and pops it into the scope of the for loop. The final eval then pops it back into the original scope.

I've been thinking about making the forState init value optional, but the foreach functions require it just to be able to determine which foreach is to be used. Because of this, I've been keeping it in to keep it consistent. This may change in the future, but I'm not sure for now.

Oh and in case you don't want to type forIndex but would prefer a different name, you could do this:
Code:
s=0
for(vars_push("s").eval, 1,10,"""
  s=s+i
""", "", stateVar="i").eval
I would recommend reading the AviSynth Usage.pdf for more info. It should also be in your scripts folder already as it is part of the download of the library.

Quote:
Originally Posted by Gavino View Post
Yes. It's called GScript.
Plugger!

Seriously though, I've not yet benchmarked GScript's for loop, but I've refrained from using it just because the core of my library is loop heavy and just the loading and unloading of the GScript plugin would probably have a negative performance impact. But this is yet to be determined empirically. However, you could put your entire script within it minimizing the load/unload overhead.


|\/|x

Last edited by maxxon; 15th March 2012 at 17:33. Reason: Added explanation
maxxon is offline   Reply With Quote
Old 15th March 2012, 17:58   #18  |  Link
Gavino
Avisynth language lover
 
Join Date: Dec 2007
Location: Spain
Posts: 3,380
Quote:
Originally Posted by maxxon View Post
I've not yet benchmarked GScript's for loop, but I've refrained from using it just because the core of my library is loop heavy and just the loading and unloading of the GScript plugin would probably have a negative performance impact.
The plugin is only loaded once, no matter how many times it is called. The only overhead(*) of using GScript is that the string argument is effectively parsed twice, first by Avisynth as a string literal and then again as script code by the GScript parser. Even this (small) overhead can be avoided by using GImport.

(*)Also bear in mind that this is only a compile-time overhead and does not affect frame-serving performance.
__________________
GScript and GRunT - complex Avisynth scripting made easier
Gavino is offline   Reply With Quote
Old 15th March 2012, 19:59   #19  |  Link
maxxon
Registered User
 
maxxon's Avatar
 
Join Date: Jan 2012
Location: On the net
Posts: 76
Quote:
Originally Posted by Gavino View Post
The plugin is only loaded once, no matter how many times it is called. The only overhead(*) of using GScript is that the string argument is effectively parsed twice, first by Avisynth as a string literal and then again as script code by the GScript parser. Even this (small) overhead can be avoided by using GImport.

(*)Also bear in mind that this is only a compile-time overhead and does not affect frame-serving performance.
Well, I'll try this soon. Made a test branch in AviSynthLib to see if I can use gimport in place of import in my include.avsi file (also had to rename for function symbol in my lib so not to name clash with GScript) and under AviSynth 2.60, it did something very unexpected. While loading my core, ScriptDir said it was in the wrong directory, which caused my core loadup to bail since it couldn't find a file.

Tired to replicate with something simple, but could not. Under AviSynth 2.54 there was no issue as a call to include properly sets up the ScriptDir variable. The ScriptDir seems to be affected by the GImport, but not sure how as it doesn't always happen. I don't get a problem when I don't use GImport in either version of AviSynth.

Other than that, it appeared not to add any load overhead.

I'll look into further profiling this later.


|\/|x

EDIT: Ok, I found the problem. Looks like it is because ScriptDir is being set to what file GImport is being called from, not the file where the function that is calling GImport is being called from. See attached zip file for an example. Replace gimport with import in the z.avs file to see how it should work.

EDIT: Moving attachment and bug to this thread.

Last edited by maxxon; 15th March 2012 at 20:29.
maxxon is offline   Reply With Quote
Old 2nd February 2013, 18:37   #20  |  Link
StainlessS
HeartlessS Usurer
 
StainlessS's Avatar
 
Join Date: Dec 2009
Location: Over the rainbow
Posts: 5,672
Quote:
Originally Posted by maxxon View Post
:if you concatenate a 4095 character string with a single character string, you can get junk on the end. Since my lib relies on concatenation, this could be a problem, but you would have to hit the library pretty hard.
Hi Maxxon, nice to see you about again.

Dont know if you implemented a script fix for string cat 4096 problem, but you could use RT_Stats RT_StrAddStr() func to avoid. there is also an RT_TxtAddStr() func to concatenate strings with a Chr(10) separator (I use it to create pseudo arrays as strings with good effect). The RT_ lib also has hex conversion funcs with a bug fix and also more general number conversion funcs base 2 -> 36.
Give it a peek.

http://forum.doom9.org/showthread.php?t=165479

EDIT: NOTE, 4096 and HexValue bugs are fixed in v2.6a4
__________________
I sometimes post sober.
StainlessS@MediaFire ::: AND/OR ::: StainlessS@SendSpace

"Some infinities are bigger than other infinities", but how many of them are infinitely bigger ???

Last edited by StainlessS; 2nd February 2013 at 18:44.
StainlessS is offline   Reply With Quote
Reply

Tags
array, avisynthlib, fast, o(1)

Thread Tools Search this Thread
Search this Thread:

Advanced Search
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT +1. The time now is 08:56.


Powered by vBulletin® Version 3.8.11
Copyright ©2000 - 2018, vBulletin Solutions Inc.