Ich habe mal versucht, ein kleines Tool für Playlisten zu erstellen. Dabei ging es mir vor Allem darum, Duplikate und verwaiste Einträge zu entfernen oder zu korrigieren.
- einlesen von m3u, m3u8 Playlists
- erkennen doppelter Einträge
- erkennen von verwaisten Einträgen
- automatische Suche nach verwaisten Einträgen neu
- entfernen von Einträgen
- neu sortieren von Einträgen
- kompletten Ordnerinhalt an PL anfügen
- per Drag&Drop Dateien hinzufügen
- per Drag&Drop Dateien verschieben(dank Pullovers Class_LV_Rows)


Code:
Spoiler
/* Thanks to Deo for 'Shell Context Menu' Pulover for 'Class_LV_Rows' */ #NoEnv ; Recommended for performance and compatibility with future AutoHotkey releases. SendMode Input ; Recommended for new scripts due to its superior speed and reliability. #Include Class_LV_Rows.ahk StringCaseSense, Locale ;~ MsgBox, % A_IsUnicode FileInstall, M3U-Editor-AHK.mht, % A_Temp "\M3U-Editor-AHK.mht", 1 ctc_hilfe := A_Temp "\M3U-Editor-AHK.mht" ftmp := A_Temp "\tmp.m3u" ; Tempdatei PL Menu, mymenü, add , Datei im Explorer anzeigen, zeige_datei Menu, mymenü, Default , Datei im Explorer anzeigen ; ---------BLOCK Shell Context Menu von Deo--------------------------------------- /* [AHK_L] Shell Context Menu von Deo http://www.autohotkey.com/board/topic/89281-ahk-l-shell-context-menu/#entry565580 optionales Kontextmenü für Playlisteneinträge falls nicht benötigt, diesen Block und Sub 'zeige_ex' entfernen/auskommentieren */ #Include, %A_ScriptDir%\ShellContextMenu.ahk Menu, mymenü, add , Kontextmenü des Explorers anzeigen, zeige_ex ; -----------Ende BLOCK Shell Context Menu von Deo------------------------------- ; ---------------------Größen für Controls--------------------------------------- b_w := 80 b_h := 35 ; ------------------------Drag&Drop-List----------------------------------------- ; akzeptierte Dateitypen audio_list := ( "AAC,AMF,APL,ASF,AU,AVI,CDA,FLAC,MIDI,MOD,MPG,MPEG,ES,MP3,MP4,PS,PVA,TS,MP1,MP2 ,MP3,MP4,MTM,M2V,M4A,NSA,NST,NSV,OGG,VOC,WAV,WMA,WMV" ) ; ------------------------------------------------------------------------------ men1 := "Ordnerinhalt hinzufügen" men2 := "Speichern und Abs&pielen`tCtrl+P" Random := "Liste zufällig mischen" doppelte_er := "doppete ermitteln" missingfiles := "fehlende suchen in..." ; ------------------------------------------------------------------------------ tool := {"doppelte auswählen":"selektiert die doppelten Einträge`nDer erste Eintrag wird nicht selektiert!" ,"Löschen":"entfernt die selektierten Einträge, ohne die Playlist zu Speichern" ,"Speichern":"Speichert die aktuelle Ansicht der Titel in die gewählte Playlist im UTF-8-Format und als erweiterte m3u" ,"SysListView321":"Titelliste`nDrag&Drop möglich"} ; ------------------------------------------------------------------------------ Menu, mymenü, add , Dateipfad ändern, pfad SetTitleMatchMode, 2 file := A_UserName = "CF" or A_UserName = "Christian" ? "D:\Eigene Dateien\eigene Musik\Sauber\Robby und andere Playlisten\2012.Juni-30\" : "" ;~ FileSelectFile, file, , %file%, Playlist auswählen..., M3U-Playlists(*.m3u;*.m3u8) ;~ if ErrorLevel ;~ ExitApp Menu, Dateimenü, Add, &Neu`tCtrl+N, neu ; Siehe untere Bemerkungen zu Ctrl+F. Menu, Dateimenü, Add, &Speichen`tCtrl+S, speichern ; Siehe untere Bemerkungen zu Ctrl+F. Menu, Dateimenü, Add, Speichen unter, speichern_u ; Siehe untere Bemerkungen zu Ctrl+F. Menu, Dateimenü, Add, &Öffnen`tCtrl+O, öffnen ; Siehe untere Bemerkungen zu Ctrl+F. Menu, Dateimenü, Add, %men2%, MenuHandler ; Siehe untere Bemerkungen zu Ctrl+F. Menu, Dateimenü, Add, &Beenden, Exit Menu, Bearbeitenmenü, Add, %men1%, MenuHandler Menu, Bearbeitenmenü, Add, %random%, MenuHandler Menu, Bearbeitenmenü, Add, %missingfiles%, MenuHandler Menu, Hilfsmenü, Add, Inf&o, MenuHandler Menu, menüleiste, Add, &Datei, :Dateimenü ; Fügt die oben erstellten Untermenüs hinzu. Menu, menüleiste, Add, &Bearbeiten, :Bearbeitenmenü Menu, menüleiste, Add, &?, :Hilfsmenü Gui, 1:Menu, menüleiste gui, 1:+Resize MinSize420x400 gui, 1:+HwndAusgabevariable gui, 1:add, edit, vmytext w400 R2 +ReadOnly, % file gui, 1:add, ListView, vmylv w400 R20 gd_lv +AltSubmit LV0x10000, Index|Titel|Vorkommen|mehrfach|Bemerkung|Pfad gui, 1:add, Button, w%b_w% h%b_h% gdoppelt_markieren +Hwndidbutt, doppelte auswählen gui, 1:add, Button, x+5 w%b_w% h%b_h% geintr_löschen +Hwndidbutt2, Löschen gui, 1:add, Button, x+5 w%b_w% h%b_h% gspeichern +Hwndidbutt3, Speichern gui, 1:add, StatusBar, SB_SetParts(200) gui, 1:Show, % "w" A_ScreenWidth // 5 * 3 SetTimer, tool_control , 200 LV_ModifyCol(1, "Integer") ; For sorting purposes, indicate that column 2 is an integer. ; Create a handle for the History of first ListView. HistoryLv1 := New LV_Rows() HistoryLv1.Add() drop: LV_Delete() SetWorkingDir, % SplitPath(file).Dir GuiControl, , mytext, % file FileRead, st, % file t := {} ; Titelindex, alter Dateien t_d := {} ; Duplikate fehlt := {} ; fehlende Dateien, derzeit nicht genutzt pl := {} ; neue Titelindex zz := 0 ; Zähler für neuen Index ; ------------------------------------------------------------------------------ Loop, Parse, st , `n, `r t[A_Index] := A_LoopField SB_SetText("PL einlesen...",2) Loop, % t.MaxIndex() { if (!t[A_Index] or InStr(t[A_Index],"#EXTM3U") or InStr(t[A_Index - 1],"#EXTINF:")) continue SB_SetText("PL einlesen fertig(" Round(A_Index / t.MaxIndex() * 100,2) "%)",2) if (InStr(t[A_Index],"#EXTINF:")) { zz++ RegExMatch(t[A_Index],"#EXTINF:-?\d+,(.*)", un) pl[zz,"Titel"] := un1 pl[zz,"Meta"] := t[A_Index] pl[zz,"Pfad"] := t[A_Index + 1] pl[zz,"Funde"] := "" if !FileExist(t[A_Index + 1]) { fehlt.Insert(zz) pl[zz,"Bemerk"] .= "Fehlt" } } else { if (RegExMatch(t[A_Index - 1],"#EXTINF:-?\d+,(.*)", un)) continue zz++ pt := t[A_Index] te := SplitPath(pt) pl[zz,"Titel"] := te.NameNoExt pl[zz,"Meta"] := "#EXTINF:-1," te.NameNoExt pl[zz,"Pfad"] := t[A_Index] pl[zz,"Funde"] := "" if !FileExist(t[A_Index]) { fehlt.Insert(zz) pl[zz,"Bemerk"] .= "Fehlt" } } } SB_SetText("PL einlesen fertig",2) gosub, doppelte_er return ; ------------------------------------------------------------------------------ GuiSize: GuiControl, MoveDraw, mytext, % "W" . (A_GuiWidth - 30) GuiControl, MoveDraw, mylv, % "W" . (A_GuiWidth - 30) "H" (A_GuiHeight - 115) GuiControl, MoveDraw, %idbutt%, % "Y" . (A_GuiHeight - 62) GuiControl, MoveDraw, %idbutt2%, % "Y" . (A_GuiHeight - 62) GuiControl, MoveDraw, %idbutt3%, % "Y" . (A_GuiHeight - 62) return ; ------------------------------------------------------------------------------ MenuHandler: if A_ThisMenuItem = %men1% gosub, men1 if A_ThisMenuItem = %men2% { gosub, speichern gosub, play } if A_ThisMenuItem = %Random% gosub, random if A_ThisMenuItem = Inf&o gosub, info if A_ThisMenuItem = %missingfiles% gosub, fehlende_suchen return ; ------------------------------------------------------------------------------ fehlende_suchen: ToolTip found = foundz = gesamt = SB_SetText("wird markiert...",2) Gui 1:ListView, mylv LV_Modify(0, "-Select") ; selektion für alle aufheben Loop, % LV_GetCount() { LV_GetText(lv_text, A_Index, 5) if (lv_text = "fehlt") LV_Modify(A_Index,"Select") } GuiControl, Focus, mylv, SB_SetText(LV_GetCount("S") . " von " . LV_GetCount() . " Einträgen markiert",1) if !LV_GetCount("S") { Gui 1:+OwnDialogs MsgBox, 64, Hinweis!, Keine fehlenden Einträge gefunden.`nAbbruch! return } lsuch := RegRead("HKEY_CURRENT_USER", "Software\M3U-Editor", "Suchordner") FileSelectFolder, suchfolder, *%lsuch%, , In welchem Ordner soll gesucht werden? if Errorlevel return RegWrite("REG_SZ", "HKEY_CURRENT_USER", "Software\M3U-Editor", "Suchordner", suchfolder) SB_SetText("suche Dateien",2) SplashTextOn, 300, 150, arbeite..., Bitte Warten! Dateien werden gesucht. Reihennummer = 0 Loop { Reihennummer := LV_GetNext(Reihennummer) ; Setzt die Suche bei der nächsten Reihe fort. If not Reihennummer ; Wenn die obige Funktion eine 0 zurückgibt, ist keine weitere Reihe ausgewählt. break o_lv := {} Loop, 6 { LV_GetText(ttext, Reihennummer, A_Index) o_lv[A_Index] := ttext } datei := SplitPath(o_lv.6).FileName ; zwischenspeichern (Performance) ;~ MsgBox, % "suche nach: " datei " in:`n" suchfolder gesamt++ FileFullPath = Loop, %suchfolder%\*.* , 0, 1 { if (A_LoopFileName = datei) { FileFullPath := A_LoopFileFullPath foundz++ found .= foundz ": " Datei "`t -->> `t" FileFullPath "`r`n" break } } if FileFullPath { pl[o_lv.1,"Pfad"] := FileFullPath LV_Modify(Reihennummer, "", o_lv.1,o_lv.2,o_lv.3,o_lv.4,"",FileFullPath) } } SplashTextOff SB_SetText("fertig",2) MsgBox, 68, Dateien neu zugeordnet, % "Es konnten " foundz " von " gesamt " Dateien automatisch neu zugeordnet werden. Soll ein Protokoll gezeigt werden?" IfMsgBox, Yes notepad_out(found) return ; ------------------------------------------------------------------------------ Random: ; Liste zufällig mischen ran := "" for, key, val in pl { if (StrLen(pl[key,"Titel"]) > 1) ran .= key "ÿ" pl[key,"Titel"] "ÿ" pl[key,"Funde"] "ÿ" zähle_pipe(pl[key,"Funde"]) "ÿ" pl[key,"Bemerk"] "ÿ" pl[key,"Pfad"] "`n" } StringTrimRight, ran, ran, 1 Sort, ran, Random LV_Delete() Loop, Parse, ran , `n { CurrentField := StringSplit(A_LoopField, "ÿ") ;~ MsgBox, % CurrentField.1 "," CurrentField.2 "," CurrentField.3 "," CurrentField.4 "," CurrentField.5 "," CurrentField.6 LV_Add("",CurrentField.1,CurrentField.2,CurrentField.3,CurrentField.4,CurrentField.5,CurrentField.6) } SB_SetText("Pfad ändern...",2) pl[row,"Pfad"] := new_pf SB_SetText("Pfad ändern fertig",2) return ; ------------------------------------------------------------------------------ doppelte_er: ; doppelte Einträge ermitteln und LV neu aufbauen t_d := {} for, key, val in pl { pl[key,"Funde"] := "" t_d[pl[key,"Titel"]] := t_d[pl[key,"Titel"]] ? t_d[pl[key,"Titel"]] "|" key : key } for, key, val in pl pl[key,"Funde"] := t_d[pl[key,"Titel"]] LV_Delete() gosub, lvadd return ; ------------------------------------------------------------------------------ men1: ; Ordnerinhalt hinzufügen gui, +OwnDialogs FileSelectFolder, folder, *%A_WorkingDir%, , Ordner wählen if ErrorLevel return Loop, % folder "\*.*", 0, 1 { file_p := SplitPath(A_LoopFileFullPath) if A_LoopFileExt in %audio_list% { jo++ zz++ pl[zz,"Titel"] := file_p.NameNoExt pl[zz,"Meta"] := "#EXTINF:-1," file_p.NameNoExt pl[zz,"Pfad"] := A_LoopFileFullPath } } if jo { LV_Delete() gosub, doppelte_er SB_SetText(jo " Audiodateien hinzugefügt",2) } else SB_SetText("keine Audiodateien gefunden",2) SB_SetText(LV_GetCount("S") . " von " . LV_GetCount() . " Einträgen markiert",1) return ; ------------------------------------------------------------------------------ play: run, %file% SB_SetText("Playlist gestartet",2) return ; ------------------------------------------------------------------------------ lvadd: ; LV füllen GuiControl, -Redraw, mylv for, key, val in pl { if (StrLen(pl[key,"Titel"]) > 1) LV_Add("",key, pl[key,"Titel"], pl[key,"Funde"],zähle_pipe(pl[key,"Funde"]),pl[key,"Bemerk"],pl[key,"Pfad"]) } GuiControl, +Redraw, mylv LV_ModifyCol() SB_SetText(LV_GetCount("S") . " von " . LV_GetCount() . " Einträgen markiert",1) return ; ------------------------------------------------------------------------------ GuiDropFiles: ToolTip drop_files := {} jo = Loop, parse, A_GuiEvent, `n drop_files.Insert(A_LoopField) file_p := SplitPath(drop_files.1) if (file_p.Extension = "m3u" or file_p.Extension = "m3u8") ; m3u Datei gedropt, muss die erste oder besser einzige Datei sein { file := drop_files.1 goto, drop } for key, v in drop_files { file_p := SplitPath(drop_files[A_Index]) ex := file_p.Extension if ex in %audio_list% { jo++ zz++ pl[zz,"Titel"] := file_p.NameNoExt pl[zz,"Meta"] := "#EXTINF:-1," file_p.NameNoExt pl[zz,"Pfad"] := drop_files[A_Index] LV_Add("",zz, pl[zz,"Titel"], pl[zz,"Funde"],zähle_pipe(pl[zz,"Funde"]),pl[zz,"Bemerk"],pl[zz,"Pfad"]) } } if jo { gosub, doppelte_er SB_SetText("Drop Files: " jo " Audiodateien hinzugefügt",2) } else SB_SetText("Drop Files: keine m3u-, oder Audiodatei",2) SB_SetText(LV_GetCount("S") . " von " . LV_GetCount() . " Einträgen markiert",1) return ; ------------------------------------------------------------------------------ öffnen: gui, +OwnDialogs FileSelectFile, file_t, , %file%, neue Playlist speichern, M3U-Playlists(*.m3u;*.m3u8) if Errorlevel return file := file_t goto, drop return ; ------------------------------------------------------------------------------ speichern_u: gui, +OwnDialogs FileSelectFile, file_t, S16, %file%, neue Playlist speichern, M3U-Playlists(*.m3u;*.m3u8) if Errorlevel return file := SplitPath(file_t).Extension = "m3u" ? file_t : file_t ".m3u" gosub, speichern return ; ------------------------------------------------------------------------------ speichern: ToolTip SB_SetText("wird gespeichert...",2) FileDelete, % ftmp stmp := "#EXTM3U" Loop, % LV_GetCount() { LV_GetText(lv_text, A_Index, 1) stmp .= "`n" pl[lv_text,"Meta"] "`n" pl[lv_text,"Pfad"] } ;~ StrPutVar(stmp, &stmp, "UTF-8") ; wandelt String in UTF-8 um FileAppend, % stmp, % ftmp, ; UTF-8 FileCopy, % ftmp, % file, 1 SB_SetText("fertig gespeichert",2) return ; ------------------------------------------------------------------------------ doppelt_markieren: ; markiert mehrfache Einträge, aber nicht den Ersten ToolTip SB_SetText("wird markiert...",2) Gui 1:ListView, mylv LV_Modify(0, "-Select") ; selektion für alle aufheben Loop, % LV_GetCount() { LV_GetText(lv_text, A_Index, 4) LV_GetText(lv_text2, A_Index, 3) LV_GetText(lv_text3, A_Index, 1) StringSplit, feld, lv_text2, | if (lv_text > 0 and feld1 != lv_text3) LV_Modify(A_Index,"Select") } GuiControl, Focus, mylv, SB_SetText(LV_GetCount("S") . " von " . LV_GetCount() . " Einträgen markiert",1) SB_SetText("fertig markiert",2) return ; ------------------------------------------------------------------------------ eintr_löschen: ; entfernt selektierte Einträge ToolTip gui, +OwnDialogs MsgBox, 262212, Löschen bestätigen!, Sollen ausgewählte Einträge wirklich aus der Playliste entfernt werden?, IfMsgBox, No return SB_SetText("wird gelöscht...",2) Loop { Reihennummer := LV_GetNext(0) ; Setzt die Suche bei der nächsten Reihe fort. If not Reihennummer ; Wenn die obige Funktion eine 0 zurückgibt, ist keine weitere Reihe ausgewählt. break LV_GetText(dertext, Reihennummer, 1) pl[dertext,"Meta"] := {} pl[dertext,"Titel"] := {} pl[dertext,"Funde"] := {} pl[dertext,"Pfad"] := {} LV_Delete(Reihennummer) } if !LV_GetCount() { zz= t := {} pl := {} Fehlt := {} } gosub, doppelte_er LV_Delete() gosub, lvadd SB_SetText(LV_GetCount("S") . " von " . LV_GetCount() . " Einträgen markiert",1) SB_SetText("fertig gelöscht",2) return ; ------------------------------------------------------------------------------ neu: ; neue PL erzeugen ToolTip gui, +OwnDialogs FileSelectFile, file_t, S16, %file%, neue Playlist speichern, M3U-Playlists(*.m3u;*.m3u8) if Errorlevel return file := SplitPath(file_t).Extension = "m3u" ? file_t : file_t ".m3u" LV_Delete() SetWorkingDir, % SplitPath(file).Dir ;~ MsgBox, % "#" SplitPath(file).Extension "#`n" file "`n" file_t GuiControl, , mytext, % file SB_SetText(LV_GetCount("S") . " von " . LV_GetCount() . " Einträgen markiert",1) SB_SetText("neue Playlist",2) ToolTip, Audiodateien per Drag&Drop hinzufügen return ; ------------------------------------------------------------------------------ Exit: GuiClose: ExitApp ; ------------------------------------------------------------------------------ zähle_pipe(st) { StringReplace, out, st, |, , UseErrorLevel return ErrorLevel } ; ------------------------------------------------------------------------------ ; http://www.autohotkey.com/board/topic/73107-solvedscript-in-unicode-string-in-ansisolved/ StrPutVar(String, ByRef Var, Codierung) { ; Kapazität gewährleisten. VarSetCapacity(Var, StrPut(String, Codierung) ; StrPut gibt die Zeichenanzahl zurück, aber VarSetCapacity benötigt Bytes. * ((Codierung = "utf-16" || Codierung = "cp1200") ? 2 : 1) ) ; Kopiert oder wandelt den String um. Return StrPut(String, &Var, Codierung) } ; ------------------------------------------------------------------------------ SplitPath(InputVar="") { out := Object() SplitPath, InputVar, FileName, Dir, Extension, NameNoExt, Drive out["FileName"] := FileName out["Dir"] := Dir out["Extension"] := Extension out["NameNoExt"] := NameNoExt out["Drive"] := Drive out["Errorlevel"] := Errorlevel Return out } ; ------------------------------------------------------------------------------ zeige_ex: ShellContextMenu(pl[o_lv.1,"Pfad"]) return ; ------------------------------------------------------------------------------ zeige_datei: run, % A_WinDir "\explorer.exe /select,/e," (InStr(pl[o_lv.1,"Pfad"], "\") ? "" : SplitPath(file).Dir "\") pl[o_lv.1,"Pfad"], , Max return ; ------------------------------------------------------------------------------ d_lv: ToolTip If A_GuiEvent = D { LV_Rows.Drag() ; Call Drag function. History%ActiveList%.Add() ; Add an entry in History. return } SB_SetText(LV_GetCount("S") . " von " . LV_GetCount() . " Einträgen markiert",1) if A_GuiEvent not in RightClick,DoubleClick return row := !LV_GetNext(1, "F") ? 1 : LV_GetNext(1, "F") o_lv := {} Loop, 6 { LV_GetText(lv_text, row, A_Index) o_lv[A_Index] := lv_text } if A_GuiEvent = DoubleClick gosub, zeige_datei if A_GuiEvent = RightClick Menu, mymenü, Show Return ; ------------------------------------------------------------------------------ pfad: gui, +OwnDialogs SB_SetText("Pfad ändern...",2) FileSelectFile, new_pf, , % SplitPath(pl[o_lv.1,"Pfad"]).Dir , neuen Pfad zum Titel , Audio(*.mp3;.*wma;*.ogg;*.acc;*.wav) if ErrorLevel { SB_SetText("Pfad ändern abgebrochen",2) return } LV_Modify(row, "", o_lv.1,o_lv.2,o_lv.3,o_lv.4,"",new_pf) pl[row,"Pfad"] := new_pf SB_SetText("Pfad ändern fertig",2) return ; ------------------------------------------------------------------------------ tt: ToolTip return ; ------------------------------------------------------------------------------ tool_control: if (A_TimeIdle > 700) { MouseGetPos, , , id, Control ControlGetText, Text, %Control%, ahk_id %Ausgabevariable% if (tool[text] and id = Ausgabevariable and control) { ToolTip, % tool[text] ; "`n" id "`n" control is_tool++ SetTimer, tt , -2000 } if (tool[Control] and id = Ausgabevariable) { is_tool++ ToolTip, % tool[Control] ; "`n" id "`n" control SetTimer, tt , -2000 } } else if is_tool { is_tool := "" ToolTip, ; % Control "`n" text } return ; ------------------------------------------------------------------------------ StringSplit(InputVar="", Delimiters="", OmitChars="") { realArray := {} StringSplit, out, InputVar, %Delimiters%, %OmitChars% loop, % out0 realArray.Insert(out%A_Index%) realArray["Errorlevel"] := Errorlevel Return realArray } ; ------------------------------------------------------------------------------ info: breite := A_ScreenWidth // 10 * 9 Gui 3: Add, ActiveX, x0 y-5 w%breite% h600 vwb, Shell.Explorer Gui 3: font, S12 bold Gui 3: Add, Button, x5 y+10 w55 gok, OK ; Document.write(html) WB.Navigate(ctc_hilfe) ; Speziell für das Web-Browser-Steuerelement. Gui 3:+Owner1 Gui 1:+Disabled Gui 3: Show, , Return ; ------------------------------------------------------------------------------ 3GuiClose: ok: Gui 3: Destroy gui 1:Default Gui 1:-Disabled if !Hidden_f Gui 1:Show Return notepad_out( data) { static Pid := 0 Process, exist, % PID If (Errorlevel = 0) Run Notepad.exe, , , PID WinActivate, ahk_pid %PID% WinWaitActive, ahk_pid %PID% ControlSetText, Edit1, %data%, ahk_pid %PID% } ; ------------------------------------------------------------------------------ RegRead(RootKey="", SubKey="", ValueName="") { RegRead, out, %RootKey%, %SubKey%, %ValueName% Return out } ; ------------------------------------------------------------------------------ RegWrite(ValueType="", RootKey="", SubKey="", ValueName="", Value="") { try RegWrite, %ValueType%, %RootKey%, %SubKey%, %ValueName%, %Value% catch e Return e.Message }
Edited by fredchf, 02 February 2014 - 08:42 PM.