Log in

View Full Version : SplitFilename() function for splitting filenames [2011/11/22]


vampiredom
22nd November 2011, 06:14
This is loosely related to this thread (http://forum.doom9.org/showthread.php?t=145464) and I thought of posting there but I did not want to veer too much off-topic. So, here it is...

######################################################################################################################
# SplitFilename()
# 2011/11/22
# a function for splitting filenames into different parts. The "which" parameter determines which parts are returned
#
# parameter: filename (string)
# -----------------
# This should be a filename or a path. Both forward slashes and backslashes are allowed
# It works with both files and folders, relative and absolute paths.
# If the supplied string is a path to a folder, it should end in a / or \
#
# which (integer)
# ---------------
# This parameter should be an integer between -2 and 4, and returns the following result:
# Example: C:\Folder\Subfolder\Filename.ext
# |
# | -2 --> drive + folder C:\Folder\Subfolder\
# | -1 --> drive + folder + basename C:\Folder\Subfolder\Filename
# | 0 --> basename + ext Filename.ext
# | 1 --> drive C:
# | 2 --> folder \Path\Subfolder\
# | 3 --> basename Filename
# | 4 --> ext .ext
#
# An empty string is returned if there is no appropriate result
# (such as a relative path, a filename with no path or a file with no extension)
# Values outside of the range of -2 to 4 also return an empty string
# When returned, drive letter always ends with : and extension always begins with a .
#
#######################################################################################################################



function SplitFilename(
\ string filename,
\ int which
\ ) {

# first get the extension. If there is a slash after the ".", then it is not an extension but a dotted folder name
ext = RightStr(filename, FindStr(RevStr(filename), "."))
ext = (FindStr(ext, "/") > 0 || FindStr(ext, "\") > 0) ? "" : ext

# now determine drive name, folder name, and file basename
# allow both "/" and "\" for folder names
drive = LeftStr(filename, FindStr(filename, ":"))
dir1 = (Findstr(filename, "\") > 0) ? LeftStr(filename, StrLen(filename) - FindStr(RevStr(filename), "\") +1) : ""
dir2 = (Findstr(filename, "/") > 0) ? LeftStr(filename, StrLen(filename) - FindStr(RevStr(filename), "/") +1) : ""
dir = (StrLen(dir1) > StrLen(dir2)) ? dir1 : dir2

folder = (StrLen(dir) > 0) ? RightStr(dir, StrLen(dir) - StrLen(drive)) : ""
basename = MidStr(filename, StrLen(drive)+StrLen(folder)+1, StrLen(filename) - StrLen(drive) - StrLen(folder) - StrLen(ext))

# copy extension to filename and clear extension for filenames that begin with .
isDotFile = (ext != "" && basename == "") ? true : false
dotBase = ext
basename = (isDotFile) ? dotBase : basename
ext = (isDotFile) ? "" : ext


(filename == "") ? ""
\ : (which == -2) ? drive + folder
\ : (which == -1) ? drive + folder + basename
\ : (which == 0) ? basename + ext
\ : (which == 1) ? drive
\ : (which == 2) ? folder
\ : (which == 3) ? basename
\ : (which == 4) ? ext
\ : ""
}

A quick way to test exactly how this works can be accomplished by running a script like this:

f = "C:\Folder\Subfolder\Filename.ext"

BlankClip()
subtitle("---> " + f, font="Courier New", y= 10, text_color=$FFFF00)
subtitle("-2 = " + Splitfilename(f,-2), font="Courier New", y= 40, text_color=$DDDDDD)
subtitle("-1 = " + Splitfilename(f,-1), font="Courier New", y= 70, text_color=$DDDDDD)
subtitle(" 0 = " + Splitfilename(f, 0), font="Courier New", y=100, text_color=$DDDDDD)
subtitle(" 1 = " + Splitfilename(f, 1), font="Courier New", y=130, text_color=$DDDDDD)
subtitle(" 2 = " + Splitfilename(f, 2), font="Courier New", y=160, text_color=$DDDDDD)
subtitle(" 3 = " + Splitfilename(f, 3), font="Courier New", y=190, text_color=$DDDDDD)
subtitle(" 4 = " + Splitfilename(f, 4), font="Courier New", y=220, text_color=$DDDDDD)

Gavino
22nd November 2011, 12:22
function SplitFilename(
\ string filename,
\ int which
\ ) {

which = Default(which, 0)
filename = default(filename, "")
...
folder = RightStr(dir, StrLen(dir) - StrLen(drive))
basename = MidStr(filename, StrLen(dir)+1, StrLen(filename) - StrLen(dir) - StrLen(ext))
...
}
The 'default' lines are redundant, since you have made the parameters mandatory. (Remember what I said yesterday?)
To make 'which' optional (it wouldn't make sense for 'filename' to be optional), put quotes around the name in the function header.

A filename with a drive and no folder (eg "C:file.ext", meaning use current folder on drive C) will give a negative length in RightStr, producing an error. You need to do this:
folder = StrLen(dir)>0 ? RightStr(dir, StrLen(dir) - StrLen(drive)) : ""
basename = MidStr(filename, StrLen(drive)+StrLen(folder)+1, StrLen(filename)-StrLen(drive)-StrLen(folder)-StrLen(ext))


Other observations:
- you don't need to use CHR(92) instead of "\", since \ has no special meaning in Avisynth;
- not sure if it is correct to regard filenames starting with a '.' as having no extension, eg a file called ".avs" will still be played by WMP as an Avisynth script.

vampiredom
22nd November 2011, 14:09
The 'default' lines are redundant, since you have made the parameters mandatory. (Remember what I said yesterday?)

Ha, yes. Actually, they were optional... then I changed it but forgot to remove the 'defaults'. Fixed. [I had it use GetWorkingPath() from [GetSystemEnv.dll] as default filename, for example, in case this being optional sounded completely crazy.]

you don't need to use CHR(92) instead of "\"
I know that. It's my text editor that gets a little confused with "\", so I avoid it. I have now restored the "\" for others' reading pleasure.

not sure if it is correct to regard filenames starting with a '.' as having no extension, eg a file called ".avs" will still be played by WMP as an Avisynth script.

It seems to me that a file called ".avs" does have a filename but not an extension. It is kind of an odd situation.

A filename with a drive and no folder (eg "C:file.ext", meaning use current folder on drive C) will give a negative length in RightStr, producing an error. You need to do this:

Excellent. I had not thought about that case. Thank you. Your suggested change has been implemented.

vampiredom
22nd November 2011, 21:11
As a "companion" to SplitFilename(), I wrote WinPath(), which translates / to \, and BSDPath(), which does the opposite. This is a way of converting filename strings to/from Windows <-> UN*X-style paths. This may be handy for interfacing with programs that provide or prefer one over the other.

In addition, the StrReplace() function provided by Gavino that powers the pathname translation can be used as a general string replacement function.

** EDIT ** Thanks to Gavino and IanB for the massive improvements here

function WinPath(string path) {
path.StrReplace("/", "\")
}

function BSDPath(string path) {
path.StrReplace("\", "/")
}

function StrReplace(string s, string find, string replace) {
i = s.FindStr(find)
return (i == 0 ? s : s.LeftStr(i-1)+replace+s.RightStr(StrLen(s)-Strlen(find)-i+1).StrReplace(find, replace))
}

so ... an example of these in action (and to test them) could be:

a = "C:/path/file.ext"
a2 = a.WinPath()
a3 = a2.StrReplace("C:\path", "C:\windows")
a4 = a3.StrReplace("C:\windows\", "\relative\to\root\")
a5 = a4.BSDPath()
a6 = a5.StrReplace("/", " ").StrReplace("l", "!").StrReplace("o", "*").StrReplace(".", ", ")

BlankClip()
subtitle("---> " + a, font="Courier New", y= 10, text_color=$FFFF00)
subtitle("---> " + a2, font="Courier New", y= 40, text_color=$DDDDDD)
subtitle("---> " + a3, font="Courier New", y= 70, text_color=$DDDDDD)
subtitle("---> " + a4, font="Courier New", y=100, text_color=$DDDDDD)
subtitle("---> " + a5, font="Courier New", y=130, text_color=$DDDDDD)
subtitle("---> " + a6, font="Courier New", y=160, text_color=$DDDDDD)

Gavino
22nd November 2011, 21:24
the ReplaceString() function that powers the pathname translation can be used as a general string replacement function, replacing up to 400 instances per string. (I know I could use Gavino's plugin for while(){} syntax, but I wanted this to be all native AviSynth code and I figured no path would ever have more than 400 slashes)
You could do it much more concisely and efficiently, and with unlimited replacements, with a recursive function. Here's what I use:
function StrReplace(string s, string find, string replace) {
i = s.FindStr(find)
return (i == 0 ? s : s.LeftStr(i-1)+replace+s.RightStr(StrLen(s)-Strlen(find)-i+1).StrReplace(find, replace))
}

vampiredom
22nd November 2011, 21:36
more concisely and efficiently, and with unlimited replacements, with a recursive function

Awesome. I do this kind of thing in other scripting languages. I don't know why I assumed it was impossible in AviSynth. Thanks so much. Big improvement and you taught me something that will be invaluable to me for future scripting.

The script at top has been edited include these improvements.

IanB
22nd November 2011, 22:33
MidStr(string, int pos [, int length]) with defaulted length is often a more useful choice than RightStr.function StrReplace(string s, string find, string replace) {
i = s.FindStr(find)
return (i == 0 ? s : s.LeftStr(i-1)+replace+s.MidStr(Strlen(find)+i).StrReplace(find, replace))
}

Gavino
22nd November 2011, 23:04
MidStr(string, int pos [, int length]) with defaulted length is often a more useful choice than RightStr.
Thanks, Ian - I'd overlooked that option.

vampiredom
23rd November 2011, 00:54
MidStr(string, int pos [, int length])

Excellent again. Now implemented above. Thanks!