jutzin
Goto Top

Xspf autoplaylister

ein aus der windows command shell ausführbarer, scriptbarer xspf autoplaylister. hilft beim rekursiven erstellen von xspf playlists in großen mediensammlungen. funktioniert prima in verbindung mit flashplayern wie z.b. dem jw player.

ich habe vor kurzem Charlie Craigs xspf-playlister von vbs nach js (mit ActiveX und in verbindung mit MP3Tag.dll, also nur unter windows lauffähig) portiert und noch ein paar funktionen hinzugefügt. ursprünglich wollte ich ein utf-8/unicode problem, das beim auslesen der id3 tags von mp3-dateien auftritt, lösen, was mir in vbs zu schwer war; leider funktioniert das immer noch nur bei 2 bytes langen chars, also bis 0xff, d.h. japanische schriftzeichen etc. werden aus den tags immer noch nicht korrekt ausgelesen (die dateinamen können alle schriftzeichen, die in windows möglich sind enthalten und werden korrekt wiedergegeben).

wie auch immer: da ich selber ewig nach einer vernünftigen, scriptbaren lösung für den lokalen einsatz ohne php gesucht habe und es wirklich nur sehr wenig brauchbares in der richtung gibt, dachte ich, ich poste den code, für den fall, dass jemand so etwas sucht.

zur bedienung:
    • einmalige vorbereitungen:
        - besorge Dir die MP3Tag.dll
        - die dll in ein verzeichnis ablegen, in dem sie permanent aufbewahrt werden soll
        - die dll registrieren (in der kommandozeile regsvr32 pfad\zur\dll\MP3Tag.dll eingeben)
        - den untenstehenden javascript code als *.js datei speichern
    • im windows explorer: das verzeichnis mit den mediendateien auf die .js-datei ziehen und loslassen. doppelklick auf die datei gibt ein hilfe-fenster aus.
    • in der windows shell: cscript name_unter_dem_du_das_script_gespeichert_hast.js [optionen] \pfad\zu\den\mediendateien.
       für hilfe zur bedienung: cscript name_unter_dem_du_das_script_gespeichert_hast.js -h

beispiel:
    • folgender shell befehl
for /r /d %%i in (*) do cscript //U //Nologo "ID3_XSPF_Playlister.js" -s "http://mein.server.de" -p a="lw:\das\apache\server\root\verzeichnis" --overwrite "%%~i"   
       erzeugt für jedes unterverzeichnis des aktuellen verzeichnisses eine xml playlist die so aussehen könnte:
<?xml version="1.0" encoding="UTF-8"?>  
<playlist version="1" xmlns="http://xspf.org/ns/0/">  

	<title>playlist title</title>

	<trackList>

		<track>
			<location>http://mein.server.de/The%20Police/Reggatta%20De%20Blanc/The%20Police%20-%20Reggatta%20De%20Blanc%20-%2001%20-%20Message%20In%20A%20Bottle.mp3</location>
			<title>Message In A Bottle</title>
			<creator>The Police</creator>
			<album>Reggatta De Blanc</album>
			<tracknum>1/11</tracknum>
			<length>4m 51s</length>
			<year>1979</year>
			<annotation>The Police - Reggatta De Blanc - 01 - Message In A Bottle</annotation>
			<info>http://www.google.com/search?hl=en&q=The+Police+-+Reggatta+De+Blanc+-+01+-+Message+In+A+Bottle</info>
		</track>

		<track>
			<location>http://mein.server.de/Fishmans/Aloha%20Polydor/Fishmans%20-%20Aloha%20Polydor%20-%2002%20-%20%E3%83%8A%E3%82%A4%E3%83%88%E3%82%AF%E3%83%AB%E3%83%BC%E3%82%B8%E3%83%B3%E3%82%B0.mp3</location>
			<title>Fishmans - Aloha Polydor - 02 - _________</title>
			<creator>Fishmans</creator>
			<album>Aloha Polydor</album>
			<tracknum>1/10</tracknum>
			<length>0m 00s</length>
			<annotation>Fishmans - Aloha Polydor - 02 - ?????????</annotation>
			<info>http://www.google.com/search?hl=en&q=Fishmans+-+Aloha+Polydor+-+02+-+?????????</info>
		</track>
	</trackList>

</playlist>

      man beachte, dass beim zweiten track die sonderzeichen, die aus den id3 tags ausgelesen wurden, verstümmelt, die aus dem dateinamen und -pfad ausgelesenen sonderzeichen jedoch korrekt dargestellt werden.

also dann, hier kommt das script. viel spaß damit.

viele grüße
jutzin


// JavaScript Document
// ID3_XSPF_Playlister.js
/******************************************************************
 * :: AUTHOR ::     based on vbs by charlie craig (http://www.iol.ie/~craigcharlie/blog/2006/04/xspf-mp3-playlister_06.html),
 *                  which was based on php by la_boost (http://www.interclasse.com/scripts/ Mp3Playlister_singleList.php),
 *                  char conversion by richard ishida (http://rishida.net/scripts/uniview/conversionfunctions.js).  
 *                  
 * :: HOWTO ::      installation: get the MP3Tag.dll from http://www.iol.ie/~craigcharlie/blog/
 *                  and follow instructions to register the dll.
 *                  start the script from shell by typing
 *                        cscript ID3_XSPF_Playlister.js --usage
 *                  or by doubleclicking on the script file for
 *                  instructions on usage      
 *
 * :: DATE ::       06-22-2009
 *  
 * :: VERSION ::    0.2
 *  
 * :: TODO ::    1) better checking of args
 *               2) there should be a switch to only write the list
 *                  to screen, not to fs. (:: DONE::)
 *               3) maybe a user definable regex feature that guesses
 *                  title info from file path for files that do not
 *                  have readable id3 tags
 *               4) instead of the binary silentoverwrite(true/false) a
 *                  dialogue controlled overwrite(yes/no/don't bother 
 *                  me anymore)
 *               5) the MP3Tag.dll unfortunately seems to mix up hex and
 *                  utf-8, so that e.g. "?ô?" in the artist tag comes 
 *                  out as ",ôn" in the playlist. i initially did the 
 *                  translation iot include richard ishidas encoding
 *                  conversion functions (which would have been a real
 *                  hassle to implement in vbs, at least to me), to
 *                  re-translate the misspelled characters. turns out
 *                  this is not possible due to information loss by
 *                  cutting off parts od the hex code points. bummer.
 *                  at least it replaces the more common chars up to
 *                  7 bit correctly.  
 *                  
 *                  if anybody finds a dll or other solution that can
 *                  handle this encoding issue, please drop me a line
 *                  at goetz.freytag_ät_gmail.com  
 *
 * :: BUGS ::       probably. please tell me if you find one.       
 * 
 ******************************************************************/
 
/**********************
 *    
 * :: begin script ::
 *
 **********************/
             
var id3              = new ActiveXObject("MP3Tag.clsMP3");  
var fso              = new ActiveXObject("Scripting.FileSystemObject");  
var WshShell         = WScript.CreateObject("WScript.Shell");  
var sAppName         = "ID3 XPSF Playlister";  
var fScript          = fso.GetFile(WScript.ScriptFullName);
var outputDir        = fScript.parentFolder.Path;

// switches (default args)
var sPlaylistName    = "playlist";                                                 // playlist file name  
var sPlaylistExt     = "xspf";                                                     // playlist file extension  
var sPlaylistTitle   = "";                                                         // change this if you want to give your playlist a certain title. when "", the name of the folder that gets playlisted, will be the playlist title  
var sExtToGetArr     = new Array("mp3","wma","flac","ogg","m4a","mpc","ape");      // file extensions to include in playlist  
var sExclDirsArr     = new Array("iTunes","System Volume Information","RECYCLER"); // directories wich contain one of the following strings are excluded from scanning  
var serverString     = "";                                                         // this string will be put at the beginning of the <location> tag  
var rootDir          = "";                                                         // root directory for absolute pathnames in <location> tag. only active when -p a="rootDir" is set, so that absPaths == true.  
var delim            = ":";                                                        // change this if you prefer another character to be the standard delimiter (something like "param 1,param 2,param 3" instead of "param 1:param 2:param 3")  
var pattern          = "\\s*" + delim + "\\s*";                                    // the pattern for the regular expression that is used to distinguish option argument list items (deletes whitespaces left and right of the delimiter, as it is.)  
var regex            = new RegExp(pattern, 'g');                                   // the instance of the regular expression itself  
var verbose          = false;                                                      // verbose. false unless user switches with -v or --verbose
var showMessages     = true;                                                       // show messages (includes messages that show up even before the verbose switch is read). put this on false if you want absolutely no messages.
var writeAnnotation  = true;                                                       // if this is set on true, each track gets its filename (without extension) written in the <annotation> tag. nice for jw-player playlists
var writeInfo        = true;                                                       // if this is set on true, each track gets a suiting (?) google search link written in the <info> tag.
var absPaths         = false;                                                      // if set on true, the paths in the <location> tag are absolute instead of relative to the playlist's location 
var silentoverwrite  = false;                                                      // if set to true, exisiting playlists will be overwritten without asking
var simulate         = false;                                                      // if this option is set, the playlist gets written to stdout instead of to file system
var badchar          = "_";                                                        // unreadable characters (anything with a value > 0xFF, that's hiragana, cyrillic, greece etc.) will be replaced wit this char  

// globals
var id3Readables     = new Array("mp3");    // array of file extensions whose id3 tags are readable by our chosen dll. change this if you use a dll that can also read id3 tags of other file types.  
var arrFolders       = new Array();         // array for ouput of recursive function. must be global because it needs to be static.

// errorcodes 
var ErrNoErrCode     = -1;
var ErrAllGood       =  0;
var ErrHelpMsgCall   =  1;
var ErrNoArg         =  2;
var ErrWrongArg      =  3;
var ErrPathNotExist  =  4;
var ErrIO            =  5;
var ErrOverwrite     =  6;
var ErrExclDir       =  7;

// helpmessage 
var usage = ""  
            + " ID3_XSPF_Playlister\n"  
            + "\n"  
            + " originally written in vbs by Charlie Craig,\n"  
            + " translated to js and rebored by vincent.\n\n"  
            + "\n    usage:\n\n"  
            + "  cscript ID3_XSPF_Playlister [options] pathToScan\n\n"  
            + "  where pathToScan is the path with the files that will be included in the playlist.\n\n"      
            + "\n    options:\n\n"  
            + "  -h, --help         \t  displays this message.\n\n"  
            + "  -u, --usage        \t  displays this message.\n\n"                         
            + "  -n    plName       \t  where plName will be the name of the created playlist file.\n"  
            + "                     \t  Default is \"" + sPlaylistName + "\".\n\n"  
            + "  -x    plExtension  \t  where plExtension will be the extension of the created playlist file.\n"  
            + "                     \t  Default is \"" + sPlaylistExt + "\".\n\n"  
            + "  -t    plTitle      \t  where plTitle is the title of the resulting playlist. Default is \n"  
            + "                     \t  the playlist\'s parent folder\'s name.\n\n"              
            + "  -e    extStr       \t  where extStr is a string of file extension to be considered.\n"  
            + "                     \t  The format of the string is \"String 1:String 2: ... :String n\".\n"  
            + "                     \t  Default is \"" + sExtToGetArr + "\".\n\n"  
            + "  --xcl excludeStr   \t  where excludeStr is a string that will be checked against \n"  
            + "                     \t  pathToScan. If pathToScan contains excludeStr, the according \n"  
            + "                     \t  subdirectory will not be scanned. The format of the string is\n"  
            + "                     \t  \"String 1:String 2: ... :String n\".\n"  
            + "                     \t  Default is \"" + sExclDirsArr + "\".\n\n"  
            + "  --overwrite        \t  if this flag is set, old playlists will be overwritten without asking.\n\n"  
            + "  -a                 \t  Write an <annotation> tag for each track. Default is the filename\n"  
            + "                     \t  without the extension.\n\n"  
            + "  -i                 \t  Write an <info> tag for each track. Default is a google search term\n"  
            + "                     \t  for the filename.\n\n"  
            + "  -p    r,a=rootDir  \t  where a switches to absolute and r to relative (to the playlist's location)\n"  
            + "                     \t  paths in the <location> tag. Default is r. When switched to a, the root\n"  
            + "                     \t  directory rootDir must be defined and locations will be relative to rootDir.\n\n"  
            + "  -s    serverString \t  where serverString is a string that will be put in the beginning \n"  
            + "                     \t  of each <location> tag of the playlist. Default is \"\".\n\n"  
            + "  --simulate         \t  writes the playlist to the screen instead of the file system.\n\n"  
            + "  -m, --showmsg      \t  shows messages.\n\n"  
            + "  -v, --verbose      \t  shows a lot of messages. Includes -m, --showmsg.\n\n"  
            + "\n  ... or in the explorer just drag and drop the directory you want to be playlisted on the script file.\n\n";  


var objArgs = WScript.Arguments;

// there is at least one argument
if (objArgs.length > 0)
{
    // the last argument should be the path
    pathToScan = objArgs(objArgs.length-1);
}
// there is no argument
else
{    
    if (showMessages)
    {   
        alert(usage);
    }
    if (objArgs.length == 0)
    {
        if (verbose) alert("Exiting with error code " + ErrNoArg + ".");  
        exit(ErrNoArg);
    }
    else
    {        
        if (verbose) alert("Exiting with error code " + ErrHelpMsgCall + ".");  
        exit(ErrHelpMsgCall);
    }    
}

var arg = "";  

// walk through the arguments and get parameters
for (i = 0; i < objArgs.length; i++)
{
    arg = objArgs(i);
    switch(arg)
    {
        case "-h" || "--help" || "-u" || "--usage":  
        {
            alert(usage);
            if (verbose) alert("Exiting with error code " + ErrHelpMsgCall + ".");  
            exit(ErrHelpMsgCall);
            break;
        }
        case "-v" || "--verbose":  
        {
            verbose        = true;
            showMessages   = true;
            break;             
        }
        case "-m" || "--showmsg":  
        {
             showMessages  = true;
             break;
        }
        case "-n":  
        {
            sPlaylistName  = objArgs(i+1);
            break;
        }
        case "-x":  
        {
            sPlaylistExt   = objArgs(i+1);
            break;
        }
        case "-t":  
        {
            sPlaylistTitle = objArgs(i+1);
            break;
        }
        case "-e":  
        {
            sExtToGetArr   = null;
            sExtToGetArr   = objArgs(i+1);
            var pattern    = "\\s*,\\s*";  
            sExtToGetArr   = sExtToGetArr.replace(regex, delim);
            sExtToGetArr   = sExtToGetArr.split(regex); 
            break;
        }
        case "--xcl":  
        {
            sExclDirsArr   = null;
            sExclDirsArr   = objArgs(i+1);
            sExclDirsArr   = sExclDirsArr.replace(regex, delim);
            sExclDirsArr   = sExclDirsArr.split(delim);
            break;
        }
        case "--overwrite":  
        {
            silentoverwrite = true;
        }
        case "-a":  
        {
             writeAnnotation = true;
             // :: TODO :: get an argument that holds a user defined <annotation>
             break;
        }
        case "-i":  
        {
             writeInfo     = true;
             // :: TODO :: get an argument that holds a user defined <info>
             break;
        }
        case "-p":  
        {
            absPaths       = objArgs(i+1);
            if (absPaths   == "r")  
            {
                absPaths   = false;
                break;
            }
            else if (absPaths.substring(0,2) == "a=")  
            {
                var rootDir  = absPaths;
                absPaths     = true;
                rootDir      = rootDir.substring(2, rootDir.length); 
                break;
            }
            else
            {                
                if (showMessages) alert("The argument for -p must either be r or a=\"rootDir\".");  
                if (verbose) alert("Exiting with error code " + ErrWrongArg + ".");  
                exit(ErrWrongArg);
                break; 
            }
        }
        case "-s":  
        {
            serverString   = objArgs(i+1);
            break;
        }
        case "--simulate":  
        {
            simulate       = true;
        }
        default:
        {
            // probably an argument of an option
        }
    }
}

// check, if our pathToScan exists
if (! fso.FolderExists(pathToScan))
{    
    if (showMessages) alert("It seems that the path to scan does not exist.");  
    if (verbose) alert("Exiting with error code " + ErrPathNotExist + ".");  
    exit(ErrPathNotExist);
}

// get the absolute path of pathToScan
pathToScan = fso.getAbsolutePathName(pathToScan);

// get the playlist's name (default: the lowest level of the AbsolutePathToScan) 
if (sPlaylistTitle == "")  
{
    sPlaylistTitle = pathToScan;
    sPlaylistTitle = sPlaylistTitle.substring(sPlaylistTitle.lastIndexOf("\\")+1, sPlaylistTitle.length);        
}

// input summary
if (verbose)
{
                             str = "\n ****  Input ****\n\n";  
                             str = str + "path to scan                        \t: " + pathToScan + "\n";  
                             str = str + "playlist name                       \t: " + sPlaylistName + "\n";  
                             str = str + "playlist extension                  \t: " + sPlaylistExt + "\n";  
                             str = str + "playlist title                      \t: " + sPlaylistTitle + "\n";  
                             str = str + "considered extensions               \t: " + sExtToGetArr + "\n";  
                             str = str + "excluded directories                \t: " + sExclDirsArr + "\n";  
                             str = str + "absolute paths                      \t: " + absPaths + "\n";  
    if (absPaths)            str = str + "root directory for absolute paths   \t: " + rootDir + "\n";  
    if (serverString != "")  str = str + "server string                       \t: " + serverString + "\n";      
                             str = str + "delimiting character                \t: " + delim + "\n";  
    if (delim != ":")        str = str + "regexp for splitting argument lists \t: " + regex + "\n";  
                             str = str + "\n\n **** Process ****\n";  
    alert(str);
}

// check if playlist already exists
playlistFile = pathToScan + "\\" + sPlaylistName + "." + sPlaylistExt;  
if (fso.fileExists(playlistFile))
{
    if(silentoverwrite)
    {
        // if not in simulation mode: delete the existing file and go ahead
        if (! simulate) fso.deleteFile(playlistFile);
    }
    else
    {        
        if (showMessages) alert("The playlist file \"" + pathToScan + "\\" + sPlaylistName + "." + sPlaylistExt + "\" already exists.\nNo playlist has been written.");  
        if (verbose) alert("Exiting with error code " + ErrOverwrite + ".");  
        exit(ErrOverwrite);
    }
}

// check, if pathToScan contains one of sExclDirsArr
for (i = 0; i < sExclDirsArr.length; i++)
{
    str   = sExclDirsArr[i];
    regex = new RegExp(str, 'i');  
    if (regex.test(pathToScan))
    {        
        if (showMessages) alert("The directory \"" + pathToScan + "\" has been excluded from playlisting. (exclusion string \"" + str + "\").");  
        if (verbose) alert ("Exiting with error code " + ErrExclDir + ".");  
        exit(ErrExclDir);
    }
}

// perform the scan 
var playlist = scan(pathToScan, sPlaylistName, sPlaylistExt, sPlaylistTitle, sExtToGetArr, serverString);

// write the playlist to stdout
if (simulate)
{
    WScript.echo("\n\n" + playlist + "\n\n");  
}

// write the playlist do disk
else
{
    if (! fso.fileExists(playlistFile))
    {
        try
        {
            var txtStream           = new ActiveXObject("ADODB.Stream");  
            var streamType          = 2;             // 1 := binary, 2:= text
            var charSet             = "utf-8";       // utf-8  
            var saveCreateOverWrite = 2;             // 1 := create if not exists, 2 := overwrite. we choose overwrite because we allready checke through fso if file already exists
            
            txtStream.Type          = streamType;
            txtStream.Charset       = charSet;
      
            txtStream.open()
            txtStream.writeText(playlist);
            txtStream.saveToFile(playlistFile, saveCreateOverWrite);
            txtStream.close();
            txtStream = null;
                        
            if (showMessages) alert("A playlist has been written to \"" + playlistFile + "\".");  
            if (verbose) alert("Exiting with error code " + ErrAllGood + " (meaning there have been no errors).");  
            exit(ErrAllGood);
        }
        catch (exc_io)
        {            
            if (showMessages) alert("There has been an exception writing the playlist.");  
            if (verbose) alert("The exception is: " + exc_io);  
            if (verbose) alert("Exiting with error code " + ErrIO + ".");  
            exit(ErrIO);
        }
    }
    else
    {
        if (silentoverwrite)
        {
            // if not in simulation mode: delete the existing file and go ahead
            if (! simulate) fso.deleteFile(playlistFile);
        }
        else
        {
            if (showMessages) alert("The playlist file \"" + pathToScan + "\\" + sPlaylistName + "." + sPlaylistExt + "\" already exists.\nNo playlist has been written.");  
            exit(ErrOverwrite);
        }
    }
}
// clean up
fso = null;


/**********************
 *    
 * :: begin functions ::
 *
 **********************/
 
// the scanning function
function scan(pathToScan, sPlaylistName, sPlaylistExt, sPlaylistTitle, sExtToGetArr, serverString)
{
    var i, j;                                    
    var strPlaylist;                               // the playlist as a string that will be returned
    var ext;                                       // the file extension we are looking for
    var fileEnum;                                  // enumerator of all the files in the respective folder (or subfolder)
    var itm;                                       // the respective item of the enumerator
    var fold          = fso.Getfolder(pathToScan); // the search folder (top)
        arrFolders.push(fold);                     // first add the pathToScan folder
        arrFolders    = getSubfolders(fold);       // then the subfolders (getSubfodlers(folder) returns an array of all the subfolders of the pathToScan. arrFolders needs to be static!)
    var arrFiles      = new Array();               // an array that will contain all the files we will put into the playlist
    var aktFile;                                   // the respective file
    var aktFileName;                               // the respective file's name  
    var aktFilePath;                               // the respective file's path, relatiove or absolute, depending pon how the user set the switch  
    var strFolderList = "";                        // a string containing the respective subfolder's part of the playlist  
    
    var id3Title, id3Artist, id3Album, id3Track, id3Year, id3Genre, id3Comment;
    var xspfLocation, xspfAnnotation, xspfInfo;
    
    // open the tags
    strPlaylist = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"  
                + "<playlist version=\"1\" xmlns=\"http:\/\/xspf.org\/ns\/0\/\">\n"  
                + "\n";  
    if (sPlaylistTitle) strPlaylist = strPlaylist
                                    + "\t<title>" + sPlaylistTitle + "<\/title>\n\n"  
                                    + "\t<trackList>\n";  
    
    // loop through all the folders and subfolders
    for (i = 0; i < arrFolders.length; i++)
    {
        fold = arrFolders[i];
        fileEnum = new Enumerator(fold.Files);
        
        // then loop through all the files of each folder and subfolder	
        for (; !fileEnum.atEnd(); fileEnum.moveNext())
        {
            itm = fileEnum.item().name;
            // check if the extension is in strExtToGetArr
             
            for (j = 0; j < sExtToGetArr.length; j++)
            {
                // if we are looking for files with this extension, write the file into the file-array
                if (itm.indexOf(sExtToGetArr[j]) != - 1)
                {
                   arrFiles.push(fileEnum.item());
                }
            }
        }
    }
    // let's give a little status message 
    if (verbose) alert(arrFiles.length + " files will be playlisted.\n");  
    
    // we now have an array of files with the extensions we were looking for, sorted by path.
    // loop through that array and collect each track's information 
    for (i = 0; i < arrFiles.length; i++)
    {
        aktFile     = arrFiles[i];
        if (verbose) alert((i+1) + "/" + (arrFiles.length) + ":   \t adding \"" + aktFile + "\" to the playlist.")  
        
        id3Title    = "";  
        id3Artist   = "";  
        id3Album    = "";  
        id3Track    = "";  
        id3Year     = "";  
        id3Duration = "";  
        id3Genre    = "";  
        id3Comment  = "";  
        
        // do we have a file whose id3 tags we can read?
        for (j = 0; j < id3Readables.length; j++)
        {
            if (getFileExtension(aktFile) == id3Readables[j])
            {
               // it seems we do, so let's get the id3 tags 
               // first the title; we want to catch exceptions here, especially for the readMP3 part
               try
               {
    		      id3.readMP3(aktFile.Path);
 			      id3Title = id3.Songname;
		       }
		       // catch unread title tags
               catch(exc_titleNotReadable)
               {
                  if (verbose) alert("\t\t The id3 tags of \"" + aktFile + "\" could not be received. Taking information from filename.");  
                  id3Title = aktFile.Name.replace("." + getFileExtension(aktFile), "");  
               }
               // catch empty <title> tags
               if (id3Title == "")  
               {
                   if (verbose) alert("\t\t The id3 tags of \"" + aktFile + "\" could not be received. Taking information from filename.");  
                   id3Title = aktFile.Name.replace("." + getFileExtension(aktFile), "");  
               }
               
               // then the rest, also encoding conversion of id3 tags iot remove false utf-8 interpretations at chars beyond asc(255)
               
               /*  :: TODO :: reverse the utf-8 char misinterpretation, branch 1 (leaves as is)
               id3Title      = id3Title;
               id3Artist     = id3.Artist;
               id3Album      = id3.Album;
               id3Track      = id3.Track;
               id3Year       = id3.Year;
               id3Duration   = id3.Duration;
               id3Genre      = id3.Genre;
               id3Comment    = id3.Comment;
               //  :: END TODO ::    */
                   
               //*  :: TODO :: reverse the utf-8 char misinterpretation, branch 2 (beautifies chars lower than 0xFF)
               id3Title      = repairUTF8(id3Title);
               id3Artist     = repairUTF8(id3.Artist);
               id3Album      = repairUTF8(id3.Album);
               id3Track      = repairUTF8(id3.Track);
               id3Year       = repairUTF8(id3.Year);
               id3Duration   = repairUTF8(id3.Duration);
               id3Genre      = repairUTF8(id3.Genre);
               id3Comment    = repairUTF8(id3.Comment);
               //  :: END TODO ::    */
               
               // no need to go through the rest of id3Readables
               break;
            }
            else
            {
                // no readable id3 tags, so we have to get our information from the filename or so.
                
                /*   :: TODO :: there is much room for improvement here, e.g. one could get the information through
                 *   a user defined regex from the filepath or so... one for normal albums, one for
                 *   compilations is what i'd do.  
                 */
               
                id3Title =  aktFile.Name.replace("." + getFileExtension(aktFile), "");                
            }
        }
        
        // get the <location> tag
        // user wants absolute paths in location tag
        if (absPaths)
        { 
            pattern     = rootDir.replace(/\\/g, "\\\\");                           // the pattern that portrays the rootDir  
            regex       = new RegExp(pattern, 'i');                                 // the regex, caseinsensitive, only first occurence  
            aktFilePath = aktFile.Path;                                             // the aktFile absolute path
            aktFilePath = aktFilePath.replace(regex, "");                           // replace first occurence of rootDir in path with ""  
        }
        // user wants relative paths in location tag
        else
        {
            aktFilePath  = aktFile.Path.replace(pathToScan, "");                    // the aktFile relative path  
        }       
        xspfLocation = serverString + aktFilePath.replace(/\\/g, "\/");             // make unix-like path and add server string  
        xspfLocation = encodeURL(xspfLocation);                                     // encode to url %-encoding                        
        aktFileName    = aktFile.Name.replace("." + getFileExtension(aktFile), ""); // the aktFile name (without extension)  
        // get, if the respective switch is on, the <annotation> tag
        if (writeAnnotation) xspfAnnotation = aktFileName;
        // and perhaps also the <info> tag
        if (writeInfo) xspfInfo = "http://www.google.com/search?hl=en&q=" + (aktFileName.replace(/\s/g, "+")).replace(/&/g,"&amp;");  
            
        // we have now collected the respective track's information. 
        // let's make a nicely formatted string of it 
        strFolderList = "\n\t\t<track>\n";  
                               strFolderList = strFolderList + "\t\t\t<location>"   + xspfLocation   + "<\/location>\n";  
                               strFolderList = strFolderList + "\t\t\t<title>"      + id3Title       + "<\/title>\n";  
        if (id3Artist   != "") strFolderList = strFolderList + "\t\t\t<creator>"    + id3Artist      + "<\/creator>\n";  
        if (id3Album    != "") strFolderList = strFolderList + "\t\t\t<album>"      + id3Album       + "<\/album>\n";  
        if (id3Track    != "") strFolderList = strFolderList + "\t\t\t<tracknum>"   + id3Track       + "<\/tracknum>\n";  
        if (id3Duration != "") strFolderList = strFolderList + "\t\t\t<length>"     + id3Duration    + "<\/length>\n";  
        if (id3Year     != "") strFolderList = strFolderList + "\t\t\t<year>"       + id3Year        + "<\/year>\n";  
        if (id3Comment  != "") strFolderList = strFolderList + "\t\t\t<comments>"   + id3Comment     + "<\/comments>\n";  
        if (writeAnnotation)   strFolderList = strFolderList + "\t\t\t<annotation>" + xspfAnnotation + "<\/annotation>\n";   
        if (writeInfo)         strFolderList = strFolderList + "\t\t\t<info>"       + xspfInfo       + "<\/info>\n";  
        strFolderList = strFolderList + "\t\t<\/track>\n";  
        
        // add this folder's list to the playlist 
        strPlaylist = strPlaylist + strFolderList
    }
    
    // close the tags
    strPlaylist = strPlaylist + "\n\t\<\/trackList>\n"  
                              + "\n<\/playlist>";  
    return strPlaylist;  				 	 	
}

// recursive subfolder search. Needs static var "arrFolders". 
function getSubfolders(dir)
{     
    //arrFolders.push(fso.getFolder(dir));
    var foldEnum = new Enumerator(fso.getFolder(dir).SubFolders);
    for (; !foldEnum.atEnd(); foldEnum.moveNext())
    {
        getSubfolders(foldEnum.item()) // recursive
        if (foldEnum.item() != null) arrFolders.push(foldEnum.item());   
    }
    if (foldEnum.item() != null) arrFolders.push(foldEnum.item());
    return arrFolders;
}

// helper functions
// makro for WScript.echo
function alert(string)
{
    //time = new Date();
    //time = time.getHours() + ":" + time.getMinutes() + ":" + time.getSeconds() + "," + time.getMilliseconds(); 
    //WScript.Echo(time + " ... " + string); 
    WScript.Echo(string);
}

// makro for WScript.quit()
function exit(errcode)
{
   if(isNaN(errcode))
   {
       errcode = ErrNoErrCode; // exit without valid errorcode;
   }
   WScript.quit(errcode);
}

// get fileextension
function getFileExtension(filename)
{
    return (/[.]/.exec(filename)) ? /[^.]+$/.exec(filename) : undefined;
}

// encode url
function encodeURL(urlstr)
{
    urlstr = getCPfromChar(urlstr);                    // convert to code points
    urlstr = urlstr.substring(0, urlstr.length-1);     // cut off last space
    urlstr = convertCP2pEsc(urlstr);                   // convert to url escape coding
    urlstr = urlstr.replace(/%2F/g, "\/");             // re-replace code %2f with "/"  
    urlstr = urlstr.replace(/%3A/, ":");               // re-replace the first occurence of %3a with ":"  
    return urlstr;  
}

function encodeXML(xmlstr)
{
    xmlstr = getCPfromChar(xmlstr);                    // convert to code points
    xmlstr = xmlstr.substring(0, xmlstr.length-1);     // cut off last space
    xmlstr = convertCP2XML(xmlstr);                    // convert to xml compliant
    return xmlstr;
}

//*  :: TODO :: reversion of utf-8 misinterpretation
// re-encode utf-8 special chars
function repairUTF8(utfstr)
{
    utfstr = getCPfromChar(utfstr);
    utfstr = utfstr.substring(0, utfstr.length-1);
    utfstr = convertUTF82CP(utfstr);
    utfstr = convertCP2Char(utfstr);
    utfstr = utfstr.replace(/_NAN/g , badchar);
    return utfstr;  
}
//  :: END TODO ::    */ 

/**********************
 *    
 * :: begin encoding conversion fucntions by richard ishida ::
 *
 **********************/
 
var escapeMap = '';  
var CPstring  = '';  

var hexNum    = {0:1, 1:1, 2:1, 3:1, 4:1, 5:1, 6:1, 7:1, 8:1, 9:1, 
				 A:1, B:1, C:1, D:1, E:1, F:1, 
				 a:1, b:1, c:1, d:1, e:1, f:1};
var jEscape   = {0:1, b:1, t:1, n:1, v:1, f:1, r:1};
var decDigit  = {0:1, 1:1, 2:1, 3:1, 4:1, 5:1, 6:1, 7:1, 8:1, 9:1};


function dec2hex(textString)
{
    return (textString+0).toString(16).toUpperCase();
}

function  dec2hex2(textString)
{
    var hexequiv = new Array ("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F");  
    return hexequiv[(textString >> 4) & 0xF] + hexequiv[textString & 0xF];
}

function  dec2hex4(textString)
{
    var hexequiv = new Array ("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F");  
    return hexequiv[(textString >> 12) & 0xF] + hexequiv[(textString >> 8) & 0xF] + hexequiv[(textString >> 4) & 0xF] + hexequiv[textString & 0xF];
}

// converts a character or sequence of characters to hex codepoint values
// copes with supplementary characters
// returned values include a space between each hex value and at the end
function getCPfromChar(textString)
{
	var codepoint = "";  
	var haut = 0;
	var n = 0; 
	for (var i = 0; i < textString.length; i++)
    {
		var b = textString.charCodeAt(i); 
		if (b < 0 || b > 0xFFFF)
        {
			//codepoint += 'Error: Initial byte out of range in getCPfromChar: '+dec2hex(b); 
			codepoint += '_' + dec2hex(b);  
		}
		if (haut != 0)
        {
            // we should be dealing with the second part of a supplementary character
			if (0xDC00 <= b && b <= 0xDFFF)
            {
				codepoint += '_' + dec2hex(0x10000 + ((haut - 0xD800) << 10) + (b - 0xDC00)) + ' ';  
				haut = 0;
				continue;
			}
			else
            {
				//codepoint += 'Error: Second byte out of range in getCPfromChar: '+dec2hex(haut); 
				codepoint += dec2hex(haut);
				haut = 0;
			}
		}
		if (0xD800 <= b && b <= 0xDBFF)
        {
            //b is the first part of a supplementary character
			haut = b;
		}
		else
        {   
            // this is not a supplementary character
            // codepoint += dec2hex(b);
			codepoint += b.toString(16).toUpperCase()+' ';  
		}
	} 
    //alert('>'+codepoint+'<'); 
	return codepoint;
}

// converts hex codepoint values to url %-escape values 	
function convertCP2pEsc(textString)
{
	// textstring: sequence of Unicode code points, derived from convertChar2CP()
	var outputString = "";  
	// remove initial spaces
	textString = textString.replace(/^\s+/, '');  
	if (textString.length == 0) {return "";}  
	// make all multiple spaces a single space
	textString = textString.replace(/\s+/g, ' ');  
	var listArray = textString.split(' ');  
	// process each codepoint
	for (var i = 0; i < listArray.length; i++)
    {
		var n = parseInt(listArray[i], 16);
		//if (i > 0) {outputString += ' ';} 
		if (n == 0x20) {outputString += '%20';}  
		else if (n >= 0x41 && n <= 0x5A) {outputString += String.fromCharCode(n);}                           // alpha
		else if (n >= 0x61 && n <= 0x7A) {outputString += String.fromCharCode(n);}                           // alpha
		else if (n >= 0x30 && n <= 0x39) {outputString += String.fromCharCode(n);}                           // digits
		else if (n == 0x2D || n == 0x2E || n == 0x5F || n == 0x7E) {outputString += String.fromCharCode(n);} // - . _ ~
		else if (n <= 0x7F) {outputString += '%'+dec2hex2(n);}  
		else if (n <= 0x7FF) {outputString += '%'+dec2hex2(0xC0 | ((n>>6) & 0x1F)) + '%' + dec2hex2(0x80 | (n & 0x3F));}   
		else if (n <= 0xFFFF) {outputString += '%'+dec2hex2(0xE0 | ((n>>12) & 0x0F)) + '%' + dec2hex2(0x80 | ((n>>6) & 0x3F)) + '%' + dec2hex2(0x80 | (n & 0x3F));}   
		else if (n <= 0x10FFFF) {outputString += '%'+dec2hex2(0xF0 | ((n>>18) & 0x07)) + '%' + dec2hex2(0x80 | ((n>>12) & 0x3F)) + '%' + dec2hex2(0x80 | ((n>>6) & 0x3F)) + '%' + dec2hex2(0x80 | (n & 0x3F));}   
		//else {outputString += '!Error ' + dec2hex(n) +'!';} 
		else {outputString += '_' + dec2hex(n) +'!';}  
	}
	return(outputString);
}

// converts hex codepoints to characters
function convertCP2Char (textString)
{
    var outputString = '';  
    textString = textString.replace(/^\s+/, '');  
    if (textString.length == 0) {return "";}  
  	textString = textString.replace(/\s+/g, ' ');  
    var listArray = textString.split(' ');  
    for (var i = 0; i < listArray.length; i++)
    {
        var n = parseInt(listArray[i], 16);
        if (n <= 0xFFFF)
        {
            outputString += String.fromCharCode(n);
        }
        else if (n <= 0x10FFFF)
        {
            n -= 0x10000
            outputString += String.fromCharCode(0xD800 | (n >> 10)) + String.fromCharCode(0xDC00 | (n & 0x3FF));
        }
        else
        {
            //outputString += 'convertCP2Char error: Code point out of range: '+dec2hex(n); 
            outputString += '_' + dec2hex(n);  
        }
    }
    return(outputString);
}

//converts hex-codepoints to xml compliant chars
function convertCP2XML (textString)
{
	var outputString = '';  
	textString = textString.replace(/^\s+/, '');  
	if (textString.length == 0) {return "";}  
	
	textString = textString.replace(/\s+/g, ' ');  
	var listArray = textString.split(' ');  
	for (var i = 0; i < listArray.length; i++)
    {
		var n = parseInt(listArray[i], 16);
		if (n <= 0xFFFF)
        {
			switch (n)
            {
				case 34: outputString += '&quot;'; break;  
				case 38: outputString += '&amp;'; break;  
				case 60: outputString += '&lt;'; break;  
				case 62: outputString += '&gt;'; break;  
				default: outputString += String.fromCharCode(n);
			}
		} 
		else if (n <= 0x10FFFF)
        {
			n -= 0x10000;
			outputString += String.fromCharCode(0xD800 | (n >> 10)) + String.fromCharCode(0xDC00 | (n & 0x3FF));
		} 
		else
        {
			//outputString += 'convertCP2Char error: Code point out of range: '+dec2hex(n); 
			outputString += '_' + dec2hex(n);  
		}
	}
	return(outputString);
}

// converts hex codepoints to utf-8 double digit codepoints
function convertCP2UTF8(textString)
{
    var outputString = "";  
    textString = textString.replace(/^\s+/, '');  
    if (textString.length == 0) { return ""; }  
    textString = textString.replace(/\s+/g, ' ');  
    var listArray = textString.split(' ');  
    for (var i = 0; i < listArray.length; i++)
    {
        var n = parseInt(listArray[i], 16);
        if (i > 0) {outputString += ' ';}  
        if (n <= 0x7F)
        {
            outputString += dec2hex2(n);
        }
        else if (n <= 0x7FF)
        {
            outputString += dec2hex2(0xC0 | ((n>>6) & 0x1F)) + ' ' + dec2hex2(0x80 | (n & 0x3F));  
        }
        else if (n <= 0xFFFF)
        {
            outputString += dec2hex2(0xE0 | ((n>>12) & 0x0F)) + ' ' + dec2hex2(0x80 | ((n>>6) & 0x3F)) + ' ' + dec2hex2(0x80 | (n & 0x3F));  
        }
        else if (n <= 0x10FFFF)
        {
            outputString += dec2hex2(0xF0 | ((n>>18) & 0x07)) + ' ' + dec2hex2(0x80 | ((n>>12) & 0x3F)) + ' ' + dec2hex2(0x80 | ((n>>6) & 0x3F)) + ' ' + dec2hex2(0x80 | (n & 0x3F));  
        }
        else
        {
            //outputString += '!erreur ' + dec2hex(n) +'!'; 
            outputString += '_' + dec2hex(n) +'!';  
        }
    }
    return(outputString);
}

// converts UTF-8 values to hex codepoints
function convertUTF82CP (textString)
{
    var outputString = "";  
    CPstring = '';  
    var compte = 0;
    var n = 0;
    textString = textString.replace(/^\s+/, '');  
    if (textString.length == 0) {return "";}  
    textString = textString.replace(/\s+/g, ' ');  
    var listArray = textString.split(' ');  
    
    for (var i = 0; i < listArray.length; i++)
    {
        var b = parseInt(listArray[i], 16);  // alert('b:'+dec2hex(b)); 
        switch (compte)
        {
            case 0:
            {
                if (0 <= b && b <= 0x7F)
                { 
                    // 0xxxxxxx
                    outputString += dec2hex(b) + ' ';  
                }
                else if (0xC0 <= b && b <= 0xDF)
                {
                    // 110xxxxx
                    compte = 1;
                    n = b & 0x1F;
                }
                else if (0xE0 <= b && b <= 0xEF)
                {
                    // 1110xxxx
                    compte = 2;
                    n = b & 0xF;
                }
                else if (0xF0 <= b && b <= 0xF7)
                {
                    // 11110xxx
                    compte = 3;
                    n = b & 0x7;
                }
                else
                {
                    //outputString += '!erreur ' + dec2hex(b) + '! '; 
                    outputString += '_' + dec2hex(b) + '! ';  
                }
                break;
                }
                
            case 1:
            {
                if (b < 0x80 || b > 0xBF)
                {
                    //outputString += '!erreur ' + dec2hex(b) + '! '; 
                    outputString += '_' + dec2hex(b) + '! ';  
                }
                compte--;
                outputString += dec2hex((n << 6) | (b-0x80)) + ' ';  
                n = 0;
                break;
            }
            
            case 2: case 3:
            {
                if (b < 0x80 || b > 0xBF)
                {
                    //outputString += '!erreur ' + dec2hex(b) + '! '; 
                    outputString += '_' + dec2hex(b) + '! ';  
                }
                n = (n << 6) | (b-0x80);
                compte--;
                break;
            }
        }
    }
    CPstring = outputString.replace(/ $/, '');  
    return CPstring;
}

/**********************
 *    
 * :: end encoding conversion functions by richard ishida ::
 *
 **********************/

/**********************
 *    
 * :: end functions ::
 *
 **********************/
 
/**********************
 *    
 * :: end script ::
 *
 **********************/

Content-Key: 118935

Url: https://administrator.de/contentid/118935

Printed on: April 25, 2024 at 15:04 o'clock