Log in

View Full Version : Setting filenames and opening files


pbristow
22nd June 2013, 21:34
Hi all, I have a few questions for anyone who is familiar with the internals of AVIsynth. Not urgent. Consider this a moment of light relief from your intensive hacking and modding duties. :)

Q1) If I write a script such as:

Filename = "text" + ".avi"
AVIsource(Filename)


...and invoke it via VirtualDub, at what point does the value "text.avi" get assigned to the variable Filename? Is it during the instantiation of avisynth, when plugins etc. are loaded? Or is it when the controlling application (VDub or whatever) asks for its first frame to be served, and AVIsource is called for the first time, realises it needs a filename, and triggers the evaluation of the preceding line?


Q2) What I'm working around to is the following idea: Suppose I (or someone else) wrote a function or a plug-in that prompts the user for a filename/path (via a standard Windows-style file selector) and then returns that filename/path, and used it as follows:


Filename = SelectFile()
AVIsource(Filename)


When would the user see the filename prompt? Would it pop up as soon as the avisynth script was loaded, or only once the calling app demanded the first frame of video (e.g. when they pressed "play" on a player)?


Q3) Has anyone ever written such a file-selector plug-in, or thought of adding such a function to Avisynth itself?


Cheers. :)

Gavino
22nd June 2013, 21:53
The assignment takes place during script loading, which occurs when the controlling application first opens the script file and before any frames are requested.

So if you had a plugin that prompted for an input file, the prompt would appear on script loading (so-called 'compile time').

For an example of such a plugin (albeit for the output file rather than input file), see SoundOut().

StainlessS
22nd June 2013, 23:11
Such a plugin would need to be implemented as a compile time plugin (as Gavino says) and would be processed in
script order and results available to other following compile time functions (like string concatenation, in script order)
and also standard filter functions (whose constructors are also called in script order).

jordanh
23rd June 2013, 14:22
I really like this idea. It should be supported out of the box.

As i liked it and it sounded like an hour of work, i started implementing.
It really was easy, but unfortunately i still struggle with the win32 wchar and this stuff.. so in the end it took nearly 3,5 hours ;-)

First you need to install microsoft visual c++ redistributeable version 2010, i believe the 32 bit one. Try the x64 one if this fails.
http://www.microsoft.com/de-at/download/confirmation.aspx?id=5555

In attachment, you find the source code and a built version in the /release directory.

Place the dll in the avisynth plugins folder and use it this way:


fileselectionpopup()
directshowsource(SELECTEDFILE)
subtitle (SELECTEDFILE)


This is just an example code, but pretty functional. I will not continue any work on this, but anyone who likes can use this as a starting point.

Gavino
23rd June 2013, 16:19
... use it this way:

fileselectionpopup()
directshowsource(SELECTEDFILE)
subtitle (SELECTEDFILE)

Why not just have the function return a string, as pbristow suggested, rather than setting a (hard-coded?) variable SELECTEDFILE?

filename = fileselectionpopup()
directshowsource(filename)

# also allowing simply:
directshowsource(fileselectionpopup())

jordanh
23rd June 2013, 19:07
Why not just have the function return a string, as pbristow suggested, rather than setting a (hard-coded?) variable SELECTEDFILE?

filename = fileselectionpopup()
directshowsource(filename)

# also allowing simply:
directshowsource(fileselectionpopup())



You are welcome to patch it ;-) (you just need visual studio 2010 c++ express for it, its free. I think its really not more than retruning the "filename" string instead of hardcoded "true" from the fileselectionpopup function...

Other features that may be interesting for this plugin would be to introduce parameters for fileselectionpopup() like
fileselectionpopup(extensions = ".mxf,.mpg...",startfolder = "c:\")

EDIT: acutally, i like setting stuff into hardcoded variable names. This way, it is just more obvious what is going on when you look over a script. It is a kind of standardisation.
The negative sides of hardcoded variable names is clear: first there is the documentation issue, second it can be overwritten. It would be better to use more complex variablenames for this kind of integration.

StainlessS
23rd June 2013, 19:55
Here how to return strings:-


char * strlit="AnyOldName";
int len=strlen(strlit);
char * s=new char[len+1];
if(s==NULL)
env->ThrowError("Cannot Allocate string mem");
strcpy(s,strlit); // dup
char *e=s+len; // point at nul

// make safe copy of string (freed on Avisynth closure)
AVSValue ret = env->SaveString(s,e-s); // e-s is text len only (excl nul) {SaveString uses memcpy)
// AVSValue ret = env->SaveString(s); // alternative, Avisynth uses strlen to ascertain length
// AVSValue ret = env->SaveString(s,-1); // alternative, Avisynth uses strlen to ascertain length
delete [] s; // delete our temp s buffer
return ret; // return Saved Str as AVSValue

// return strlit; // Alternative to MOST of above code char* converted to AVSValue.
// return "AnyOldName"; // Alternative to ALL of above code char* converted to AVSValue.
// String literals are read only and at constant address and so need not be saved.

above untested
EDIT: Dont use the one in red above (Its a bit naughty).

Would be nice if it was as Avisynthesizer, using template to select source clips and auto create an
avs based on template. (EDIT: This is complete BS, create avs from within avs ??? (although it still could have usage))
EDIT: OK, a bit less BS, run template AVS, import clip into that template using fileselector,
which is basically what has been the suggested use by OP (I post like I play chess, without bothering to think)

Fixed name a bad idea, would make selecting multiple files eg input clip and output log files awkward.

Forensic
24th June 2013, 08:10
A more useful variant would be a single AVIsynth command or plug-in call that paused the AVIsynth script while it waited for user input. In this way the input could be assigned to a variable used for anything the coder needed, including opening the designated file name. The feature should have both a default and timer variable that, if specified, set the maximum seconds before the process is aborted to return the specified default value.

A nice plus would be if the input supported a CTRL-V clipboard paste.

Gavino
24th June 2013, 09:52
Here how to return strings:-
...
Worth noting that most of these considerations (use of SaveString and freeing locally allocated memory) also apply when setting a script variable instead of returning a string.

In jordanh's function, there is actually a memory leak of up to 1256 bytes per call (although fairly unimportant in a function that is called only at compile-time and probably only once).

jordanh
24th June 2013, 22:01
OK, now it is getting interesting.
Up in front, i dont see any issues with the "execution time" of this stuff.

From my understanding, it behaves like this:
1) the avisynthplugininit function of all plugins is called at start of runtime, or "compile time" as the other say, in script order
2) getFrame is only called when the initializing application requests a frame.

one just needs to do the SetVar stuff within the init function, thats it. Its just about the script order.

I tend to open a new thread for gathering requirements of a new "external communcation plugin".

First i must ask about the environment everyone is envisioning.
You have to know, in my head it it only gets interesting when it can also be used in non-interactive mode. Meaning that avisynth is invoked by a background service and no user is logged in to the server where avisynth is runing on.
Please state a few comments on this.
I see SNMP, Mail, GUI by Script, commandline and many else communications.
What makes sense, is there anything that gives unlimited power like just calling external programs and returning their return value?

@Stainless: Thats extremely good code up there, thanks! Why not put that to the c++ wiki on avisynth.org? :-)

@forensic: i did not understand the CTRL-V stuff. what kind of usercontext are you reffering to, what exactly is the content of clipboard?

EDIT: we could send preview frames as base64 string (websites?) too :-)

StainlessS
24th June 2013, 23:29
Avisynthplugininit, just lets Avisynth know what functions are available, their names, argument names and types, so it can check
what is legal during compile time. (EDIT: Avisynth does not know what arg values are legal, and your code must do checks on those)

getFrame is only called when the initializing application requests a frame
Yes, but that only happens once the filter graph has been built. (EDIT that frame is returned once graph is built).
Before that, during compile time, as constructors are called and Filtered clips added to the graph, the parts of the graph that has already been
built are available to compile time filters, and even runtime clip functions eg can getframe on already existing partial chain, although
some tricks may be necessary if using standard builtin funcs like eg AverageLuma. (GScript is wonderfull).
EDIT: You can even force a filter constructor/destructor during compile time (after processing using eg GScript) so making any output
file available to subsequent functions (I love automation), useful in a sort of prescan to avoid 2 pass
(EDIT: Do 2 pass but automatically, one after the other, 1st pass without display so possibly a bit quicker) .

like just calling external programs and returning their return value?

RT_Call(), is a function in RT_Stats collection of mainly runtime/compile time functions. Calls external program
(eg MediaInfo) but only returns a status on whether process was successfully started.

RT_Call(String Cmd,bool "Hide"=false,bool "Debug"=false)
Run an external program using supplied cmd command line, hiding the console window if Hide==True.
Debug, If true, send some debug info to debugview including return code from called process.
DOS Filenames should be enclosed in quotes to avoid the system misinterpreting spaces in filenamess, see RT_QuoteStr().
Returns 0 if process successfully started, otherwise non zero (ie 1).
Example to write Aspect Ratio in form "1.333" to file D:\OUT\MEDINFO.TXT using command line version of MediaInfo.
RT_Call(RT_QuoteStr("D:\TEST\MediaInfo.Exe") + " --LogFile=" + RT_Quotestr("D:\OUT\MEDINFO.TXT") + \
" --Inform=Video;%DisplayAspectRatio% " + RT_Quotestr("D:\VID\1.mpg"))


Can then read results file using RT type functions.

EDIT:
There is also an example somewhere on-site using ImageMagic (although I think that spelling is wrong).
CallCmd (based on Call) does pretty much the same thing but is a standard filter function which allows calling
an external command on particular frames, or in constructor,/destructor of filter.

why not put that to the c++ wiki

Because I'm very lazy and hate writing documentation (EDIT: but YOU are very welcome to do that).

By the way, latest Avisynth header for v2.5 is version 3, you used version 2 in FileSelector example.

Would prefer to see something that does not depend on the Foundation class libraries, I'm not a Windows progger
but have done a little WINAPI stuff eg RT_Call mentioned above.

@Forensic, presumably you meant text on clipboard, when I get around to it I'll see if I can figure out how to do that, for RT_Stats
(ie just get text from clipboard rather than CTRL-V, EDIT: if you meant BitMap, then say, I guess it would not be much more difficult).

jordanh
25th June 2013, 01:01
Avisynthplugininit, just lets Avisynth know what functions are available, their names, argument names and types, so it can check
what is legal during compile time.


That means, we have an even earlier point of setting variables than the point where the exported "main" function is used. I dont know what to do with it now, but i feel there are some usecases too.
In the end, the fileselection plugin i sumitted above does the SetVar stuff only when it is exported main function is called in the avs script. Avisynth is still able to build the graph.
I stay at: No problem regarding "execution" time.

Any other feature requests, design proposals or similar?
Especially use-cases for needed userinteraction (besides loading a source file) would be interesting...

jordanh
25th June 2013, 01:18
Because I'm very lazy and hate writing documentation (EDIT: but YOU are very welcome to do that).


Done, thanks for sharing :-)

StainlessS
25th June 2013, 01:37
even earlier point of setting variables
Presumably, there are others that could say better than my guess.
The void pointers within Avisynthplugininit are intended to pass info from that func into your Create type funcs, but only the
Avisynth "Glitterati" seem to know much about that. (eg alloc mem, init and pass secret info to your create func, I think,
also I think, something in Avisynth SDK docs, "Non-clipsample" I think, something to do with fonts and AtExit)

Building the Graph is basically compile time.

Execution time is never going to be an issue with a Fileselector (except that you have to wait for a human to do something, you could wait hours).
EDIT: A Timeout on such a File Selector (as suggested by Forensic) is likely to be a much bigger deal that the FileSelector itself.

Would love to see non Foundation Class Library source (to give me a starter), I could then include similar funcs into RT_stats.
would need FS (Fileselector) for Existing file ie input and non existing files eg Output, also Multiselect files for group selection.
(EDIT: I also usually only use VS6 or Toolkit III compilers and so would not want anything reliant on VS2008 etc, also,
I think Foundation Class Library stuff not available in some Express versions VS and so would want to avoid).

I'm as said earlier, not a windows progger, never actually liked the PC, but all other machines now deceased, so have no alternative except Apple.
Would love it though if you could impart some of you knowledge, so I could copy most of it off you. :)

EDIT: Thanx J, that's now two (I think) entries on Wiki I've got.

J, I have a thread that is really for C to Avisynth CPP quick learner guide that might have something that might interest
you. Obviously you are an accomplished programmer and dont need any kind of beginner guide (much less of a CPP beginner than me)
but there may (or may not) be some things that might be of use, not too long, might be worth a look).

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

EDIT: The main reason I suggest this is I've put in debug messages to see where the init is and constructor/destructor calls are etc.

EDIT: Also, I think I'm correct in saying that during eg Eval/ScriptClip, you are then in a NEW compile time period, where a chunk of text is
evaluated in real time, where previous statements about compile time mostly hold true.

EDIT: RT_Stats has an RT_Debug() function which outputs strings to DebugView Window, great for tracking whats happening,
can also wrap it in ScriptClip or eg FrameEvaluate to see what is happening during Frame Serving stage.

Wilbert
25th June 2013, 14:03
@jordanh,

Please use avisynth.nl the next time. I've added it to http://avisynth.nl/index.php/Cplusplus_API#AddFunction.

jordanh
25th June 2013, 21:38
Thanks Wilbert!
@everything else:
I would like to have a look at avisynth source code in order to learn how and when all this stuff is done.

By now i didnt look too much into the avisynth source code too much, but i am very interested in.
Anyone has a clue where the script parsing and execution stuff is done? i wasnt able to find it in avisynth.cpp or directshow_source.cpp...

@Stainlesss: sorry, i really dont understand your concerns. Refering to the submitted example plugin above, i could do anything i like once the script engine calls the exported (env->AddFunction) function that is called from the avs script in order to request a userinput.
Did you try the plugin?

Groucho2004
25th June 2013, 22:05
Anyone has a clue where the script parsing and execution stuff is done?
src\core\parser

TheFluff
25th June 2013, 22:31
EDIT: acutally, i like setting stuff into hardcoded variable names. This way, it is just more obvious what is going on when you look over a script. It is a kind of standardisation.
The negative sides of hardcoded variable names is clear: first there is the documentation issue, second it can be overwritten. It would be better to use more complex variablenames for this kind of integration.

I'm pretty sure this is the wrongest thing I've ever read on doom9, and I've read a lot of posts with people being wrong here. Please, if you value other people's sanity, make your function return the value.

Gavino
25th June 2013, 22:56
Anyone has a clue where the script parsing and execution stuff is done?

src\core\parser
and the 'entry point' is basically the Import() and Eval() functions in src\core\parser\script.cpp.

jordanh
26th June 2013, 00:25
@TheFluff: sorry for not explaining my reasons, thats a too long story. I basically agree with you, it was a bad decision to use this method in the example above.
However, the plugin above is clearly commented as non productive, example and not continued. You show a strange way to submit feature requests.
The Plugin above cannot be continued, its just a proof of concept. Stainlesss already required non MS Foundation code, so it needs to be rewritten from scratch anyway. (its only about 20 interesting lines of code...)
By now i still only have 3 requirements for the plugin:
* dont use win32 api
* support default and timeout
* return values

..and only one usecase: file selection
Arent there other situations when a user decision would be nice to have?



Back to the Topic:

Meanwhile thanks to Groucho i got out, that functions of interest are:


//avisynth.cpp

// Constructor of ScriptEnvironment:

ScriptEnvironment::ScriptEnvironment()
...
global_var_table->Set("true", true);
global_var_table->Set("false", false);
global_var_table->Set("yes", true);
global_var_table->Set("no", false);

...
PrescanPlugins();//doesnt call any function of the plugin
ExportFilters(); //doesnt call any function of the plugin




This function is parsing a single function and its arguments

//avisynth.cpp

AVSValue ScriptEnvironment::Invoke(const char* name, const AVSValue args, const char** arg_names) {...

//ends up in: (didnt get out what exactly this does)

// ... and we're finally ready to make the call
retval = f->apply(AVSValue(args3, args3_count), f->user_data, this);



..can it be that this function is the one that's used for any dll function call like child->geframe?

Still, i struglle getting out, where the parsing stuff is used from. What i need to get out is where the part of "compilation" or parsing or whatever people above are referring to...

Gavino
26th June 2013, 00:46
This function is parsing a single function and its arguments
AVSValue ScriptEnvironment::Invoke(const char* name, const AVSValue args, const char** arg_names) {...
..can it be that this function is the one that's used for any dll function call like child->geframe?
That is the API function (env->Invoke()) which is used to call any function that has been installed in Avisynth's function table via env->AddFunction(). This includes all built-in script functions and filters, as well as those provided by plugins.
child->GetFrame() is not called via Invoke() - it is called directly by filters.

Still, i struglle getting out, where the parsing stuff is used from. What i need to get out is where the part of "compilation" or parsing or whatever people above are referring to...
As I said in my previous post, the starting point is the Import() function, which is called (via Invoke()) by the VfW interface (main.cpp), or by an Avisynth library client, to parse the script file and create the filter graph.

jordanh
26th June 2013, 00:50
Thanks Gavino!
That was the breakthough.

Avisynthplugininit is used multiple times to look up exported functions of loaded plugins, it may be a bad idea to do something else on the env than exporting functions...

The function that actually opens the avs file and kicks off parsing and first executions is this:


//script.cpp

AVSValue Import(AVSValue args, void*, IScriptEnvironment* env) {...

AVSValue eval_args[] = { (char*)buf, script_name };
result = env->Invoke("Eval", AVSValue(eval_args, 2));
..
}




//avisynth.cpp
//called by Import function above, seems to parse line by line and execute functions in given order (script order)

AVSValue ScriptEnvironment::Invoke(const char* name, const AVSValue args, const char** arg_names) {



My interpretation is, that there is no "compliation" time like referred above, but i am absolutely not sure if i interpret it right. It is just executing each function one after the other, isnt it?

Gavino
26th June 2013, 01:02
My interpretation is, that there is no "compliation" time like referred above, but i am absolutely not sure if i interpret it right. It is just executing each function one after the other, isnt it?
Yes, you're right - the word 'compilation' is a bit misleading and I've never been fully comfortable with it.

Essentially, it is 'interpreting' the script, evaluating expressions, calling functions and, where appropriate, assigning results to variables. The 'compilation' that takes place refers to the building of the filter graph which is the result of the script (a value of type PClip).

This is in contrast to 'runtime', ie the subsequent frame serving phase where frames are requested from the graph by calling its GetFrame() function.

EDIT: You might find this useful:
http://avisynth.nl/index.php/The_script_execution_model

IanB
26th June 2013, 15:40
AvisynthPluginInit2/3 is the entry point called when Avisynth loads an external plugin .dll.

It is called on the first occurrence of a LoadPlugin() call of that .dll. If the dll is already known it is not called a second time.

For plugins that are auto loaded from the plugin folder at IScriptEnvironment creation time, it is called once as the plugin is prescanned and can be called a second time if the .dll has been released and when a defined function is referenced in the script. Referencing a function from a released auto load plugin causes it to be reloaded.

You can actually call any IScriptEnvironment function in your AvisynthPluginInit2/3 routine, but because auto load plugins might be released it is normally accepted that only AddFunction calls should be made. i.e. you should not do anything that latter needs something from the address space of the plugin .dll as it may well not be present at all or at a different memory location.

Such activity should be performed in the Apply function registered by the AddFunction call.

Whenever the Apply function is called the optional registered user_data value is supplied as the second argument. The user_data is of type void* and may be any constant or pointer value that you wish. E.g you may register varying function names with the same Apply function and each with a different user_data value to tell them apart. Or you could provide the address of a structure that all instances of a function can have access to.

pbristow
27th June 2013, 06:51
Can I just say how delighted I am that my suggestion has triggered all this activity? :)

I haven't had a chance to try out jordanh's prototype yet, but I've seen a comment in the discussion requesting CTRL-V (paste) to work. I assume jordonh was just doing proof-of-concept of the integration-with-avisynth part of the problem, rather than worrying about details of how the file selector itself would look and function...? But just to be clear on what I had in mind for that, I'd like something that opens up the standard Windows "Explorer"-type file selector, as seen in many, many existing applications (VirtualDub, for example). That supports CTRL-V for inserting pathnames and filenames into the relevant fields, as well as drag-and-drop, the ability to create new folders and do a bit of filesystem tidying before you open your file, and so on... not to mention integrating well with all sorts of third-party add-ons such as Listary (See http://www.listary.com/ ), which provides a really useful set of tools to stop me getting lost in the vast and twisty labyrinth of my ever-growing filesystem... :) ). I've seen various freeware tools that re-invent the wheel on file selection, with various degees of success, but none of them provide the power - not to mention the consistency of user experience - that you get by just using the standard tool.

Of course, the possible downsides of that are that (a) it's inherently Windows-specific (but presumably the folks working on the Linux port of avisynth, VapourSynth, etc. could produce a version that calls whatever the standard/most popular file selection UI is on the platform in question...?), and (b) there may be some licensing implications (?).

Anyway, thanks for your efforts. I look forward to seeing how this turns out. :)

(You might even shame me into getting my own hands dirty with a compiler again... It's been a couple of years at least since my last attempt died, along with the laptop hard drive upon which I'd just spent two weeks setting up my development environment... :o )

pbristow
27th June 2013, 07:05
Oh, on the "compilation" wording debate: I would call it "instantiation", i.e. the phase during which an instance of the Avisynth working environment is being created. (N.B. sometimes more than one such instance is created by a script, e.g. if it uses MP_Pipeline.)

StainlessS
27th June 2013, 15:37
@Stainlesss: sorry, i really dont understand your concerns. Refering to the submitted example plugin above, i could do anything i like once the script engine calls the exported (env->AddFunction) function that is called from the avs script in order to request a userinput.
Did you try the plugin?

Sorry jordanh, we seem to have misunderstood each other.
I have no real concerns on your submission, perhaps I was a little too wordy (said too much).
I have not as yet tried the plugin, dont need it just yet, and busy doing something else at the moment,
but I will and thank you very much for taking the time to make your submission,
it is much appreciated. :thanks:

pbristow
27th June 2013, 17:31
Just tried out jordanh's prototype: Yes! That's exactly what I wanted it to do. Thankyou! :)

Although I do think assigning the name to a variable is better than fixed variable names. One of my intended uses is for a script that analyses the difference between two or more clips, where these have been created by different versions of an AVS script I'm developing. The idea is to be able to run the comparaison script in one instance of VirtualDub (or directly in Daum PotPlayer), and then select the files to be compared. This will be repeated multiple times, as I generate more test clips with new versions of the main script, home in on which version of processing is working best. Obviously for that, I need separate variable names, but for now I can work around that using "Testfile1 = SELECTEDFILE" etc. before calling the pop-up again.

Oh, and seeing "Save as" in the title bar for an *input* file selector made me panic for a moment. ;)

jordanh
28th June 2013, 00:00
Hehe... finally someone tried it :-)
Thank YOU (pbristow) for this nice project idea! Mostly the simple ideas are the best. Maybe you really find the esteam to pull out development tools again and finish the plugin on your own ;-) ...setting a few parameters for filechooser and returning the value from the function should do it.

@Stainlesss: As i now understand know how things work in detail, i would have had more understanding for what you wrote too - and be able to interpret your messages right.
Basically it was only the wording "compile" in general and especially "compile time plugin" (still don't understand what you mean with that) that made me so much working on this thread.

From my understanding, avisynth is a mix of different stuff, especially the directshow/vfw interface, but the avisynth script language is something i'd prefer to call officially an "interpreted script language".

TheFluff
28th June 2013, 01:05
Avisynth script is neither interpreted nor a scripting language in the traditional senses of those words; if you want to be technical about it, it's more of a graph description language than anything else. The term "Avisynth" generally refers to the entire package of a video filtering library, its public API, the third-party libraries written using that API ("plugins"), the scripting engine that builds filter chains using the video filtering library and the VFW output driver that lets ordinary VFW applications retrieve frames from the filtering library without having to interface with its API.

StainlessS
20th July 2013, 23:09
I finally got around to trying this out (thank you jordanh), had to install vs2010 runtimes (thought I had already installed it),
but am getting an

Evaluate: System exception - Access Violation at this line "fileselectionpopup()".

In Dependency walker (EDIT: for AVS_FilenameSelector.dll), shows message:-

Warning: At least one module has an unresolved import due to a missing export function in a delay-load dependent module.

and hilites the mpr.dll (14/4/2008) 5.1.2600.5512, as the culprit. (WXP32).

Anybody got an idea on what that means ?

EDIT: Seems unresolved import due to WNetRestoreConnectionA which is probably not the cause of the
access violation.

StainlessS
25th July 2013, 22:30
Previous post access violation probably down to the function used, available only Vista+, so a no-no on XP.

Here an XP+ WINAPI solution, not terribly well tested, but something for you to play with.

FSel.dll, FselOpen function.


/*
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*/

// Compile for minimum supported system. MUST be defined before includes.
// NEED to use SDK for updated headers, OR TK3 will give error messages/Warnings about Beta versions.

#define WINVER 0x0501 // XP
#define _WIN32_WINNT 0x0501 // XP
#define NTDDI_VERSION 0x05010300 // XP SP3
#define _WIN32_IE 0x0603 // 0x0603=IE 6 SP2, 0x0700=1e7, 0x0800=1e8
//
//
#include <windows.h>
#include <stdio.h>
#include <commdlg.h> // OS Specific WINVER (Mainly)
#include <Shlobj.h> // IE Specific _WIN32_IE
//
#include "avisynth.h"
//

/*
typedef struct tagOFN {
DWORD lStructSize;
HWND hwndOwner; // NULL no owner
HINSTANCE hInstance;
LPCTSTR lpstrFilter; // the filter
LPTSTR lpstrCustomFilter;
DWORD nMaxCustFilter;
DWORD nFilterIndex; // we default ALWAYS 1 (ie first one)
LPTSTR lpstrFile; // initial filename, also for return filename
DWORD nMaxFile; // size of filename buffer
LPTSTR lpstrFileTitle;
DWORD nMaxFileTitle;
LPCTSTR lpstrInitialDir; // the initial directory or current if null
LPCTSTR lpstrTitle; // Title Bar text, eg 'Open file'
DWORD Flags;
WORD nFileOffset;
WORD nFileExtension;
LPCTSTR lpstrDefExt;
LPARAM lCustData;
LPOFNHOOKPROC lpfnHook;
LPCTSTR lpTemplateName;
#if (_WIN32_WINNT >= 0x0500)
void * pvReserved;
DWORD dwReserved;
DWORD FlagsEx;
#endif // (_WIN32_WINNT >= 0x0500)
} OPENFILENAME, *LPOPENFILENAME;
*/




int dprintf(char* fmt, ...) {
char printString[2048];
char *p=printString;
va_list argp;
va_start(argp, fmt);
vsprintf(p, fmt, argp);
va_end(argp);
for(;*p++;);
--p; // @ null term
if(printString == p || p[-1] != '\n') {
p[0]='\n'; // append n/l if not there already
p[1]='\0';
OutputDebugString(printString);
} else {
OutputDebugString(printString);
}
return p-printString; // strlen printString
}


AVSValue __cdecl FSelOpen(AVSValue args, void* user_data, IScriptEnvironment* env) {
char * myName="FSelOpen: ";
const char * myFilter=
"All files (*.*)|*.*|"
"Avi Files (*.avi;*.XVid;*.DivX)|*.avi;*.XVid;*.DivX|"
"Mpg files (*.vob;*.mpg;*.mpeg;*.m1v;*.m2v;*.mpv;*.tp;*.ts;*.trp;*.m2t;*.m2ts;*.pva;*.vro)|*.vob;*.mpg;*.mpeg;*.m1v;"
"*.m2v;*.mpv;*.tp;*.ts;*.trp;*.m2t;*.m2ts;*.pva;*.vro|"
"Other Vid (*.mkv;*.Wmv;*.mp4;*.flv;*.ram;*.rm)|*.mkv;*.Wmv;*.mp4;*.flv;*.ram;*.rm|"
"Audio Files (*.mp3;*.mpa;*mp1;*.mp2;*.wav)|*.mp3;*.mpa;*mp1;*.mp2;*.wav|"
"Avs files (*.avs;*.avsi)|*.avs;*.avsi|"
"Text files (*.txt;*.log;*.asc)|*.txt;*.log;*.asc|"
"Image files (*.bmp;*.jpg;*.jpe;*.jpeg;*.png;*.tga;*.tif;*.gif;*.tiff)|*.bmp;*.jpg;*.jpe;*.jpeg;*.png;*.tga;*.tif;*.gif;*.tiff|"
"Bat files (*.bat)|*.bat|";
"Exe files (*.exe)|*.cmd";

const char * title =args[0].AsString(NULL);
const char * dir =args[1].AsString(NULL); // Default current dir
const char * filt =args[2].AsString(myFilter);
const char * fn =args[3].AsString("");
const bool multi =args[4].AsBool(false);
if(strlen(filt)>4096-3) env->ThrowError("%sFilter too long",myName);
char filt2[4096];
const char *p=filt;

int ix=0;
while(*p) { // convert from PIPE '|' separated to nul separated filter strings
if(*p=='|') {
filt2[ix++]='\0';
++p;
} else {
filt2[ix++]=*p++;
}
}
filt2[ix++]='\0'; filt2[ix]='\0'; // Double nul term


int size = (multi) ? 65536 : (MAX_PATH * 2);
int len=strlen(fn) + 1;
if(len>size) size=len;
char *szFile = new char [size]; // buffer for file name
if(szFile==NULL) env->ThrowError("%sCannot allocate memory",myName);
strcpy(szFile,fn); // set initial filename
int flgs= \
OFN_PATHMUSTEXIST |
OFN_FILEMUSTEXIST |
OFN_HIDEREADONLY | // hide readonly check box
OFN_LONGNAMES |
OFN_NOCHANGEDIR; // restore original current directory if user changed. Does NOT work for GetOpenFileName.

if(multi) flgs |=(OFN_ALLOWMULTISELECT|OFN_EXPLORER) ;

OPENFILENAME ofn; // common dialog box structure
ZeroMemory(&ofn, sizeof(ofn)); // Initialize OPENFILENAME
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = NULL; // owner window, we dont have one
ofn.lpstrFile = szFile; // filename buff
ofn.nMaxFile = size; // size of filename buff
ofn.lpstrFilter = filt2; // Converted filt string
ofn.nFilterIndex = 1;
ofn.lpstrFileTitle = NULL;
ofn.nMaxFileTitle = 0;
ofn.lpstrInitialDir = dir;
ofn.lpstrTitle=title; // Title shown in Title Bar
ofn.Flags = flgs;
// Display the Open dialog box.

if (GetOpenFileName(&ofn)!=TRUE) {
delete [] szFile;
DWORD ret = CommDlgExtendedError();
if(ret==0) {
dprintf("%sUser CANCELLED",myName);
return 0;
}
switch(ret) {
case FNERR_BUFFERTOOSMALL: dprintf("%sFilename buffer Too Small",myName); break;
case FNERR_INVALIDFILENAME: dprintf("%sInvalid filename",myName); break;
case FNERR_SUBCLASSFAILURE: dprintf("%sSubClass failure (low memory)",myName); break;
case CDERR_DIALOGFAILURE: dprintf("%sDialog box creation failure",myName); break;
case CDERR_FINDRESFAILURE: dprintf("%sFind resource failure",myName); break;
case CDERR_INITIALIZATION: dprintf("%sDialog box initialization failure (usually memory)",myName); break;
case CDERR_LOADRESFAILURE: dprintf("%sLoad resource failure",myName); break;
case CDERR_LOADSTRFAILURE: dprintf("%sLoad string failure",myName); break;
case CDERR_LOCKRESFAILURE: dprintf("%sLock resource failure",myName); break;
case CDERR_MEMALLOCFAILURE: dprintf("%sUnable to allocate memory structures",myName); break;
case CDERR_MEMLOCKFAILURE: dprintf("%sUnable to lock memory associated with handle",myName); break;
case CDERR_NOHINSTANCE: dprintf("%sNo instance handle",myName); break;
case CDERR_NOHOOK: dprintf("%sNo hook",myName); break;
case CDERR_NOTEMPLATE: dprintf("%sNo template",myName); break;
case CDERR_REGISTERMSGFAIL: dprintf("%sRegisterWindowMessage returned an error",myName); break;
case CDERR_STRUCTSIZE: dprintf("%sInvalid lStructSize member",myName); break;
default: dprintf("%sUnknown error",myName); break;
}
return (int)ret;
}
int off=ofn.nFileOffset;
if(!multi || (off>0 && szFile[off-1]=='\\')) { // single filename selected
// dprintf("%s OK Single file = %s",myName,szFile);
AVSValue retstr = env->SaveString(szFile);
delete [] szFile;
return retstr;
}
p=szFile;
int nstr=0;
while(*p) { // Count gotten strings, 1st is dir, then filenames. Ends in double nul.
while(*p) {
++p;
}
++p;
++nstr;
}
int dirlen=off; // including nul (will convert to '\\').
int fullen = (p - szFile - off) + ((nstr - 1) * dirlen) + 1; // +1 for final nul term.
char *mfiles = new char [fullen]; // buffer for multiple expanded file names, '\n' separated
if(mfiles==NULL) {
delete [] szFile;
env->ThrowError("%sCannot allocate multiselect files buffer",myName);
}
char *d=mfiles; // Dest multiselect files buffer
char *s=&szFile[off]; // point at 1st filename node
for(int i=nstr-1;--i>=0;) { // Convert to filenames with full path
char *r=szFile; // point at path
while(*r)
*d++=*r++; // Copy path
*d++='\\'; // backslash separator
while(*s)
*d++=*s++; // append filename
++s; // skip nul
*d++='\n'; // '\n' multiline separator
}
*d='\0'; // nul term
delete [] szFile;
// dprintf("%sOK = %s",myName,mfiles);
AVSValue retstr = env->SaveString(mfiles);
delete [] mfiles;
return retstr;
}


And test script


########
# FSelOpen() by StainlessS
#
# Function FSelOpen(string "title"="Open",string "dir"="",string "filt",string "fn="",bool "multi"=false)
#
# Function to select EXISTING filename. Would need something similar to select SAVE filename, or Folder Select.
#
# Title = Title bar text.
# Dir = Directory, "" = Current
# Filt = Lots, most of below with a few more mpeg. [Displayed text | wildcard] [| more pairs of Displayed text and wildcard, in pairs ONLY].
# first one is default.
# fn = Initially presented filename (if any).
# multi = Multiply Select filenames. Allows selection of more than one filename. RT_Stats v1.20 Required for MULTI=true
#
# Returns
# int, 0, user CANCELLED.
# int, non zero is error (error sent to DebugView window).
# String, Filename selected, Chr(10) separated multiline string if MULTI==true (and multiple files selected).
#
########

TITLE ="Open a File, if you really really want to"
DIR =""
FILT = "All files (*.*)|*.*|"
\ + "Image files (*.bmp;*.jpg;*.jpe;*.jpeg;*.png;*.tga;*.tif;*.gif;*.tiff)|*.bmp;*.jpg;*.jpe;*.jpeg;*.png;*.tga;*.tif;*.gif;*.tiff|"
\ + "Mpg files (*.vob;*.mpg;*.mpeg etc)|*.vob;*.mpg;*.mpeg;*.m1v;*.m2v;*.mpv;*.tp;*.ts;*.trp;*.m2t;*.m2ts;*.pva;*.vro|"
\ + "Avi Files (*.avi)|*.avi|"
\ + "Avs files (*.avs;*.avsi)|*.avs;*.avsi|"
\ + "Text files (*.txt)|*.txt"

FN ="AnyAnyAnyOldIron.txt"
MULTI = false # True requires RT_Stats

result = FSelOpen(title=TITLE,dir=DIR,Filt=FILT,fn=FN,multi=MULTI)

Assert(result.IsString,"FSelOpen: Error="+String(Result))

colorbars

Lines = (!MULTI) ? 1 : RT_TxtQueryLines(result) # Inquire how many filenames we got,
# Can use eg RT_TxtGetLine(result,Line) Where Line valid from 0 to Lines - 1.
(MULTI) ? RT_Debug(Result) : NOP # to DebugViewWindow
result= (MULTI) ? RT_StrReplace(Result,Chr(10),"\n") : result # Convert For SubTitle
result="We got " + String(Lines) + " Filenames\n\n" + result
return Subtitle(result,lsp=0)


EDIT:
Update to v1.01 TAKE 2, Removed FSelOpen() script RT_Stats requirement unless MULTI = true. Here:
Link removed, see post #37.

EDIT: see also post #37 and #38 for additional parts

pbristow
25th July 2013, 23:40
Ooh, this looks powerful! Thanks StainlessS.

[GIVES IT A TRY]

Bwahaha! I love the default filenames. :)

Alas, the test script fails as I don't have the RT_ functions, but tweaking it to just use "AVISource(result)" works fine for single files. I like the idea of being able to pre-set the filetypes/extensions available (so that you don't mistakenly try to open a non-video file, or open that fuzzy old .mpg with a script that's designed to work with nice clean HiRes .mkv files. :) )

Thanks again.

StainlessS
26th July 2013, 00:36
@pbristow, hope you saw the EDIT:

I don't have the RT_
not that expensive, quite cheap really.
Also, a number of tools to read in eg text files, count multiline strings (whether or
not last line is n/l terminated, sort a multiline pseudo array of int/float strings,
write a results text/log file and more. My current favorite is RT_Subtitle, is FAST,
if you do decide to treat yourself, try the scrolling end credits type snippet, its
quite impressive for a few lines of script (about 5 or 6). :)

EDIT: If I do any further work on this, it will of course be going into RT_Stats plugin,
I'm unlikely to do anything further with FSel.dll, if anyone wants to expand and keep
as a separate, they are welcome.

pbristow
26th July 2013, 09:17
@pbristow, hope you saw the EDIT:


Seen it, downloaded it, thankyou. :)

(I also found I *had* downloaded RT_Stats, just hadn't got around to installing and testing it. )

StainlessS
27th July 2013, 00:25
Update to FSel.dll, v1.01.

Added FselSaveAs() and FSelFolder().

FSelSaveAs script

########
# FSelSaveAs() by StainlessS
#
# Function FSelSaveAs(string "title"="Open",string "dir"="",string "filt",string "fn="")
#
# Function to select filename for Save.
#
# Title = Title bar text.
# Dir = Directory, "" = Current
# Filt = Lots, most of below with a few more mpeg. [Displayed text | wildcard] [| more pairs of Displayed text and wildcard, in pairs ONLY].
# first one is default.
# fn = Initially presented filename (if any).
#
# Returns
# int, 0, user CANCELLED.
# int, non zero is error (error sent to DebugView window).
# String, Filename selected.
########

TITLE ="Save a File, if you think you know what you are doing"
DIR =""
FILT = "All files (*.*)|*.*|"
\ + "Image files (*.bmp;*.jpg;*.jpe;*.jpeg;*.png;*.tga;*.tif;*.gif;*.tiff)|*.bmp;*.jpg;*.jpe;*.jpeg;*.png;*.tga;*.tif;*.gif;*.tiff|"
\ + "Mpg files (*.vob;*.mpg;*.mpeg etc)|*.vob;*.mpg;*.mpeg;*.m1v;*.m2v;*.mpv;*.tp;*.ts;*.trp;*.m2t;*.m2ts;*.pva;*.vro|"
\ + "Avi Files (*.avi)|*.avi|"
\ + "Avs files (*.avs;*.avsi)|*.avs;*.avsi|"
\ + "Text files (*.txt)|*.txt"

FN ="AnyAnyAnyOldIron.txt"

result = FSelSaveAs(title=TITLE,dir=DIR,Filt=FILT,fn=FN)

Assert(result.IsString,"FSelSaveAs: Error="+String(Result))

return colorbars.Subtitle(Result)


FSelFolder script

########
# FSelFolder() by StainlessS
#
# Function FSelFolder(string "title"="",string "dir"=".")
#
# Function to select Folder.
#
# Title = UNDERNEATH the title bar text, for instructions.
# Dir = Directory, Default "." = Current, ""=Root.
#
# Returns
# int, 0, user CANCELLED.
# int, non zero is error (ie -1, error sent to DebugView window, usually selecting non Folder object eg 'My Computer').
# String, Folder selected (minus trailing BackSlash).
########

TITLE ="USE THIS TO SELECT A FOLDER"
DIR="." # Current Dir (Default)
#DIR="" # Root, My Computer
#DIR="D:\avs\" # Something Else
result = FSelFolder(title=TITLE,dir=DIR)

Assert(result.IsString,"FSelFolder: Error="+String(Result))

return colorbars.Subtitle(result)


Original FSelOpen much the same as at #post 32 above.

EDIT: For those that like to watch a movie and read scrolling text at the same time. Requires RT_Stats v1.20.

avi = FSelOpen("Select an AVI",Filt="*.AVI|*.AVI")
Assert(avi.IsString,"FSelOpen: Error="+String(avi))
AVISource(AVI)
txt = FSelOpen("Select a Text file",Filt="*.txt|*.txt")
Assert(txt.IsString,"FSelOpen: Error="+String(txt))
Txt=RT_ReadTxtFromFile(txt)
Return ScriptClip("""RT_Subtitle("%s",Txt,align=2,y=height+100-current_frame,expy=true)""") # Requires RT_Stats v1.20

Prompts for AVI file, then for a Txt file

EDIT: FSelOpen, will only allow selecting existing files. FSelSaveAs, prompts for Overwrite if existing selected.

Update to v1.01 TAKE 2, Removed FSelOpen() script RT_Stats requirement unless MULTI = true. Here:
Link removed, see post #37.

StainlessS
27th July 2013, 19:02
Update to FSelOpen, FSelSaveas and FSelFolder functs, FSel.dll v1.02

Fixed bug in filter scanning, update FSelFolder to resizable dialog box with Makefolder button.

Here:
http://www.mediafire.com/download/82hzcrcpb10g35q/FSelOpen_25_dll_v0.02_20130727%28TAKE3%29.zip

Very last Fsel.dll version, have added to RT_stats.

EDIT: May not be online longer than a month or so.

StainlessS
31st July 2013, 14:23
Post update, added the FSelSaveAs() and FSelFolder() source (with filter parse bug fixed) , see also posts #32 and #37 for other parts of source.
Link to zip in previous post.

AVSValue __cdecl FSelSaveAs(AVSValue args, void* user_data, IScriptEnvironment* env) {
char * myName="FSelSaveAs: ";
const char * myFilter=
"All files (*.*)|*.*|"
"Avi Files (*.avi;*.XVid;*.DivX)|*.avi;*.XVid;*.DivX|"
"Mpg files (*.vob;*.mpg;*.mpeg;*.m1v;*.m2v;*.mpv;*.tp;*.ts;*.trp;*.m2t;*.m2ts;*.pva;*.vro)|*.vob;*.mpg;*.mpeg;*.m1v;"
"*.m2v;*.mpv;*.tp;*.ts;*.trp;*.m2t;*.m2ts;*.pva;*.vro|"
"Other Vid (*.mkv;*.Wmv;*.mp4;*.flv;*.ram;*.rm)|*.mkv;*.Wmv;*.mp4;*.flv;*.ram;*.rm|"
"Audio Files (*.mp3;*.mpa;*mp1;*.mp2;*.wav)|*.mp3;*.mpa;*mp1;*.mp2;*.wav|"
"Avs files (*.avs;*.avsi)|*.avs;*.avsi|"
"Text files (*.txt;*.log;*.asc)|*.txt;*.log;*.asc|"
"Image files (*.bmp;*.jpg;*.jpe;*.jpeg;*.png;*.tga;*.tif;*.gif;*.tiff)|*.bmp;*.jpg;*.jpe;*.jpeg;*.png;*.tga;*.tif;*.gif;*.tiff|"
"Bat files (*.bat)|*.bat";

const char * title =args[0].AsString(NULL);
const char * dir =args[1].AsString(NULL); // Default current dir
const char * filt =args[2].AsString(myFilter);
const char * fn =args[3].AsString("");
if(strlen(filt)>4096-3) env->ThrowError("%sFilter too long",myName);
char filt2[4096];
const char *p=filt;

int ix=0;
while(*p) { // convert from PIPE '|' separated to nul separated filter strings
if(*p=='|') {
filt2[ix++]='\0';
++p;
} else {
filt2[ix++]=*p++;
}
}
filt2[ix++]='\0'; filt2[ix]='\0'; // Double nul term

int size = MAX_PATH * 2;
int len=strlen(fn) + 1;
if(len>size) size=len;
char *szFile = new char [size]; // buffer for file name
if(szFile==NULL) env->ThrowError("%sCannot allocate memory",myName);
strcpy(szFile,fn); // set initial filename
int flgs= \
OFN_OVERWRITEPROMPT |
OFN_LONGNAMES |
OFN_NOCHANGEDIR; // restore original current directory if user changed. Does NOT work for GetOpenFileName.

OPENFILENAME ofn; // common dialog box structure
ZeroMemory(&ofn, sizeof(ofn)); // Initialize OPENFILENAME
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = NULL; // owner window, we dont have one
ofn.lpstrFile = szFile; // filename buff
ofn.nMaxFile = size; // size of filename buff
ofn.lpstrFilter = filt2; // Converted filt string
ofn.nFilterIndex = 1;
ofn.lpstrFileTitle = NULL;
ofn.nMaxFileTitle = 0;
ofn.lpstrInitialDir = dir;
ofn.lpstrTitle=title; // Title shown in Title Bar
ofn.Flags = flgs;

// Display the Open dialog box.
if (GetSaveFileName(&ofn)!=TRUE) {
delete [] szFile;
DWORD ret = CommDlgExtendedError();
if(ret==0) {
dprintf("%sUser CANCELLED",myName);
return 0;
}

switch(ret) {
case FNERR_BUFFERTOOSMALL: dprintf("%sFilename buffer Too Small",myName); break;
case FNERR_INVALIDFILENAME: dprintf("%sInvalid filename",myName); break;
case FNERR_SUBCLASSFAILURE: dprintf("%sSubClass failure (low memory)",myName); break;
case CDERR_DIALOGFAILURE: dprintf("%sDialog box creation failure",myName); break;
case CDERR_FINDRESFAILURE: dprintf("%sFind resource failure",myName); break;
case CDERR_INITIALIZATION: dprintf("%sDialog box initialization failure (usually memory)",myName); break;
case CDERR_LOADRESFAILURE: dprintf("%sLoad resource failure",myName); break;
case CDERR_LOADSTRFAILURE: dprintf("%sLoad string failure",myName); break;
case CDERR_LOCKRESFAILURE: dprintf("%sLock resource failure",myName); break;
case CDERR_MEMALLOCFAILURE: dprintf("%sUnable to allocate memory structures",myName); break;
case CDERR_MEMLOCKFAILURE: dprintf("%sUnable to lock memory associated with handle",myName); break;
case CDERR_NOHINSTANCE: dprintf("%sNo instance handle",myName); break;
case CDERR_NOHOOK: dprintf("%sNo hook",myName); break;
case CDERR_NOTEMPLATE: dprintf("%sNo template",myName); break;
case CDERR_REGISTERMSGFAIL: dprintf("%sRegisterWindowMessage returned an error",myName); break;
case CDERR_STRUCTSIZE: dprintf("%sInvalid lStructSize member",myName); break;
default: dprintf("%sUnknown error",myName); break;
}
return (int)ret;
}
// dprintf("%s OK Single file = %s",myName,szFile);
AVSValue retstr = env->SaveString(szFile);
delete [] szFile;
return retstr;
}




/*
typedef struct _browseinfo {
HWND hwndOwner; // A handle to the owner window for the dialog box.
PCIDLIST_ABSOLUTE pidlRoot; // Root folder to browse from PIDL (NULL = DeskTop)
LPTSTR pszDisplayName; // returned name, Presumed MAX_PATH characters
LPCTSTR lpszTitle; // Title Bar String
UINT ulFlags;
BFFCALLBACK lpfn; // Calback functionm, Can be NULL.
LPARAM lParam;
int iImage;
} BROWSEINFO, *PBROWSEINFO, *LPBROWSEINFO;
*/


int CALLBACK FSelFolderCallback(HWND hwnd,UINT uMsg,LPARAM lp, LPARAM pData) {
// The callback function required to init desired path to folder, rather than root (Desktop)
// meaning of lp depends on uMsg type
// pData is application defined data for the callback function.
char szPath[MAX_PATH*2];
switch(uMsg) {
case BFFM_INITIALIZED: // Selects the specified folder Path
szPath[0]='\0';
{ // Need Brace to init below s without error.
char*s=(char*)pData;
if(s!=NULL) {
if(s[0]=='.'&&s[1]=='\0') {
if(!GetCurrentDirectory(sizeof(szPath), szPath))
szPath[0]='\0';
} else {
strcpy(szPath,s);
}
}
}
SendMessage(hwnd, BFFM_SETSELECTION, TRUE,(LPARAM) szPath);
break;
case BFFM_SELCHANGED: // Indicates the selection has changed.
if (SHGetPathFromIDList((LPITEMIDLIST) lp ,szPath)) { // convert pidl to path
SendMessage(hwnd, BFFM_SETSTATUSTEXT,0,(LPARAM)szPath); // Send path
}
break;
}
return 0; // Always returns 0
}

AVSValue __cdecl FSelFolder(AVSValue args, void* user_data, IScriptEnvironment* env) {
char * myName="FSelDir: ";
const char * title =args[0].AsString("");
const char * dir =args[1].AsString("."); // Default to '.' = current directory

LPMALLOC pMalloc; // Shell allocator
if (!SUCCEEDED(SHGetMalloc(&pMalloc))) { // Did we successfully get the shell mem alloc interaface
env->ThrowError("%sCannot get Shell Alloc Interface",myName);
}

char szFold[MAX_PATH * 2];
strcpy(szFold,dir);
int flgs= \
BIF_STATUSTEXT |
BIF_RETURNFSANCESTORS |
BIF_RETURNONLYFSDIRS | // File system objects only (selectable), DONT SEEM TO WORK PROPER.
BIF_EDITBOX | // EDITBOX (ie 4.0 + Only)
BIF_NEWDIALOGSTYLE; // 6.0 + only (xp+)
BROWSEINFO bi; // Browse structure
ZeroMemory(&bi,sizeof(bi)); // clr
bi.hwndOwner = NULL; // No owner window
bi.pidlRoot = NULL; // Desktop
bi.pszDisplayName = NULL; // We are gonna get path ourselves
bi.lpszTitle = title;
bi.ulFlags = flgs;
bi.lpfn = FSelFolderCallback; // Browse callback function
bi.lParam = (LPARAM)szFold;
//
LPITEMIDLIST pidl = SHBrowseForFolder(&bi); // return NULL on user CANCEL, else pidl, user must free using shell allocator
if (pidl==NULL) {
dprintf("%sUser CANCELLED",myName);
pMalloc->Release(); // done with shell allocator
return 0;
}
bool b = (SHGetPathFromIDList(pidl,szFold)!=0); // Converts an item identifier list to a file system path (MUST be filesystem)
pMalloc->Free(pidl); // free the pidl
pMalloc->Release(); // done with shell allocator
if(!b) {
dprintf("%sNot a Filesystem Object",myName);
return -1;
}
// dprintf("%s OK Single file = %s",myName,szFold);
return env->SaveString(szFold);
}



extern "C" __declspec(dllexport) const char* __stdcall AvisynthPluginInit2(IScriptEnvironment* env) {
env->AddFunction("FSelOpen" ,"[title]s[dir]s[filt]s[fn]s[multi]b",FSelOpen,0);
env->AddFunction("FSelSaveAs","[title]s[dir]s[filt]s[fn]s",FSelSaveAs,0);
env->AddFunction("FSelFolder","[title]s[dir]s",FSelFolder,0);
return 0;
}


EDIT: the "OFN_NOCHANGEDIR; // restore original current directory if user changed. Does NOT work for GetOpenFileName."

comment seems untrue, seems that the file selector has it's own version of current directory which it keeps as user selected
for a certain amount of time and then reverts to current directory when that time expires. Current working directory is not changed
by the functions.