Edit - This code has been completely re-written. The new script can be found
here
Lately Ive found myself with a bunch of TV series stored on my external drive and they all have crytpic "scene group" names. I figured I could whip something up in AHK that could connect to TheTVDB.com and automatically rename my files for me.
Heres my rather unpolished and gritty attempt. Im not claiming it to be anything amazing, nor did I intend it to be. I spent my casual free time over the course of a week putting this together. For having no formal training in any sort of other programming language I dont feel like this is too shabby if I do say so myself =P
As always, tweaks, tips, etc will be greatly appreciated!!!
Code:
; AutoHotkey Version: 1.0.48+
;
; Script Function:
; Extracts season and episode info from the file name
; then retreives enough info from TheTVDB.com to rename
; the file to this format: Series Title - SSxEE - Episode Title
#NoEnv
SendMode, Input
SetWorkingDir, %A_ScriptDir%
Mirror_Path = http://www.thetvdb.com
apikey = 563E39CDC7F666DF
File_Types_Search = avi,mkv,mp4
FileSelectFolder, Target_Folder
File_List =
Loop, %Target_Folder%\*.*
{
If A_LoopFileExt contains %File_Types_Search%
File_List = %File_List%%A_LoopFileName%`n ; Append the current filename from the parsing loop to File_List
}
If File_List =
{
MsgBox, 0, Fatal Error!,No matching filetypes found in %Target_Folder%`n`nExiting script...
ExitApp
}
Loop, Parse, File_List, `n
{
If A_LoopField = ; Removes the blank item at the end of the list
Break
Original_File_Name = %A_LoopField% ; Stores the original file name for later use
No_Period_Name := RegExReplace(A_LoopField, "\.", " ") ; Remove all periods from file name
;Get file ext
IfInString, No_Period_Name, avi
RegExMatch(No_Period_Name, "avi", File_Ext) ; Store "avi" in File_Ext
IfInString, No_Period_Name, mkv
RegExMatch(No_Period_Name, "mkv", File_Ext) ; Store "mkv" in File_Ext
IfInString, No_Period_Name, mp4
RegExMatch(No_Period_Name, "mp4", File_Ext) ; Store "mp4" in File_Ext
;Get series title
RegExMatch(No_Period_Name, "[a-zA-Z -&]+", First_Word_Set) ; Match everything up to the first digit
StringTrimRight, Series_Name, First_Word_Set, 1 ; Remove proceeding space that almost always exists
StringLen, Series_Name_Len, Series_Name ; Count chars for StringMid
StringMid, Last_Char, Series_Name, %Series_Name_Len%, 1, L ; Pull the last char from the right
If (Last_Char = "-") or (Last_Char = " ") ; If last char is a dash or space
StringTrimRight, Series_Name, Series_Name, 1 ; Trim it
;Get season number and episode number
If RegExMatch(No_Period_Name, "i)s[0-9][0-9]?e[0-9][0-9]?", Season_Ep_Raw)
{ ; ===s04e02 and similar variations===
; ==Season==
StringTrimRight, Season_Num, Season_Ep_Raw, 2 ; Remove episode number(s) and potentially "e"
StringTrimLeft, Season_Num, Season_Num, 1 ; Remove preceeding "S"
IfInString, Season_Num, e ; If proceeding "e" still exists
StringTrimRight, Season_Num, Season_Num, 1 ; Trim it
IfInString, Season_Num, 0 ; If a zero exists in Season_Num
{
StringGetPos, Zero_Loc, Season_Num, 0 ; Determine the zero loc
If Zero_Loc = 0 ; If it is the first char
StringTrimLeft, Season_Num, Season_Num, 1 ; Trim it
}
; ==Episode==
StringTrimLeft, Episode_Num, Season_Ep_Raw, 3 ; Remove season number(s) and potentially "e"
IfInString, Episode_Num, e ; If proceeding "e" still exists
StringTrimleft, Episode_Num, Episode_Num, 1 ; Trim it
IfInString, Episode_Num, 0 ; If a zero exists in Episode_Num
{
StringGetPos, Zero_Loc, Episode_Num, 0 ; Determine the zero loc
If Zero_Loc = 0 ; If it is the first char
StringTrimLeft, Episode_Num, Episode_Num, 1 ; Trim it
}
}
Else If RegExMatch(No_Period_Name, "i)[0-9][0-9]?x[0-9][0-9]?", Season_Ep_Raw)
{ ; ===04x02 and similar variations===
; ==Season==
StringTrimRight, Season_Num, Season_Ep_Raw, 2 ; Remove episode number(s) and potentialy "x"
IfInString, Season_Num, x ; If proceeding "x" still exists
StringTrimRight, Season_Num, Season_Num, 1 ; Trim it
IfInString, Season_Num, 0 ; If a zero exists in Season_Num
{
StringGetPos, Zero_Loc, Season_Num, 0 ; Determine the zero loc
If Zero_Loc = 0 ; If it is the first char
StringTrimLeft, Season_Num, Season_Num, 1 ; Trim it
}
; ==Episode==
StringTrimLeft, Episode_Num, Season_Ep_Raw, 2 ; Remove season number(s) and potentially "x"
IfInString, Episode_Num, x ; If preceeding "x" still exists
StringTrimLeft, Episode_Num, Episode_Num, 1 ; Trim It
IfInString, Episode_Num, 0 ; If a zero exists in Episode_Num
{
StringGetPos, Zero_Loc, Episode_Num, 0 ; Determine the zero loc
If Zero_Loc = 0 ; If it is the first char
StringTrimLeft, Episode_Num, Episode_Num, 1 ; Trim it
}
}
Else If RegExMatch(No_Period_Name, "[0-9][0-9][0-9][0-9]?", Season_Ep_Raw)
{ ; ===0402 and 402 are the only supported formats===
; ==Season==
StringTrimRight, Season_Num, Season_Ep_Raw, 2 ; Remove two episode numbers
IfInString, Season_Num, 0 ; If a zero exists in Season_Num
{
StringGetPos, Zero_Loc, Season_Num, 0 ; Determine the zero loc
If Zero_Loc = 0 ; If it is the first char
StringTrimLeft, Season_Num, Season_Num, 1 ; Trim it
}
; ==Episode==
StringTrimLeft, Episode_Num, Season_Ep_Raw, 1 ; Remove first char as it should always be part of the season number
StringLen, Episode_Num_Len, Episode_Num ; Count string chars to determine if trim is needed
If Episode_Num_Len = 3 ; 3 chars usually means part of season number still exists
StringTrimLeft, Episode_Num, Episode_Num, 1 ; Trim it
IfInString, Episode_Num, 0 ; If a zero exists in Episode_Num
{
StringGetPos, Zero_Loc, Episode_Num, 0 ; Determine the zero loc
If Zero_Loc = 0 ; If it is the first char
StringTrimLeft, Episode_Num, Episode_Num, 1 ; Trim it
}
}
GoSub, Get_Series_ID
}
MsgBox, 0, Attention, No more files left to rename
ExitApp
Get_Series_ID:
{
IfExist, Series_ID.xml
FileDelete, Series_ID.xml
UrlDownloadToFile, http://www.thetvdb.com/api/GetSeries.php?seriesname="%Series_Name%", Series_ID.xml
Loop
{
IfExist, Series_ID.xml
Break
}
FileRead, Series_ID_Full, Series_ID.xml
Loop
{
StringReplace, Series_ID_Full, Series_ID_Full, </Series>, ¤, UseErrorLevel
If ErrorLevel = 0
Break
}
Loop, Parse, Series_ID_Full, ¤
{
RegExMatch(A_LoopField, "<seriesid>\K[^<>]+", Series_ID_No)
RegExMatch(A_LoopField, "i)<seriesname>\K[^<>]+", Series_X_Name)
RegExMatch(A_LoopField, "i)<overview>\K[^<>]+", Series_Overview)
If (Series_X_Name = "" && Series_ID_No = "")
Continue
MsgBox, 4, , Using "%Series_Name%" as the show name`n`nHeres what matches from TheTVDB.com:`n`n%A_Tab%Show Name:%A_Tab%%Series_X_Name%`n%A_Tab%Series ID:%A_Tab%%A_Tab%%Series_ID_No%`n%A_Tab%Series Overview: `n%Series_Overview%
IfMsgBox, Yes
{
GoSub, Get_Episode_Info
Break
}
}
If (Series_X_Name = "" && Series_ID_No = "")
MsgBox, 0, Critical Error!, No shows found for %A_LoopField% on TheTVDB.com
FileDelete, Series_ID.xml
}
Return
Get_Episode_Info:
{
IfExist, Episode_Info.xml
FileDelete, Episode_Info.xml
UrlDownloadToFile, %Mirror_Path%/api/%APIKey%/series/%Series_ID_No%/default/%Season_Num%/%Episode_Num%/en.xml, Episode_Info.xml
Loop
{
IfExist, Episode_Info.xml
Break
}
FileRead, Episode_Info_Full, Episode_Info.xml
Loop
{
StringReplace, Episode_Info_Full, Episode_Info_Full, </Episode>, ¤, UseErrorLevel
If ErrorLevel = 0
Break
}
Loop, Parse, Episode_Info_Full, ¤
{
RegExMatch(A_LoopField, "i)<EpisodeNumber>\K[^<>]+", Episode_X_Num)
RegExMatch(A_LoopField, "i)<EpisodeName>\K[^<>]+", Episode_X_Name)
RegExMatch(A_LoopField, "i)<Overview>\K[^<>]+", Episode_X_Overview)
RegExMatch(A_LoopField, "i)<SeasonNumber>\K[^<>]+", Season_X_Num)
If (Episode_X_Num = "" && Episode_X_Name = "" && Season_X_Num = "")
Continue
MsgBox, 4, , Using "%Series_X_Name%" with season number "%Season_Num%" and episode number "%Episode_Num%"`n`nHeres what matches on TheTVDB.com:`n`n%A_Tab%Show Name:%A_Tab%%Series_X_Name%`n%A_Tab%Season Number :%A_Tab%%Season_X_Num%`n%A_Tab%Episode Number :%A_Tab%%Episode_X_Num%`n%A_Tab%Episode Title :%A_Tab%%Episode_X_Name%`n%A_Tab%Episode Overview :`n%Episode_X_Overview%
IfMsgBox, Yes
{
GoSub, Rename_File
Break
}
}
If (Episode_X_Num = "" && Episode_X_Name = "" && Season_X_Num = "")
MsgBox, 0, Critical Error!, No episode matches for "%Series_X_Name%" season "%Season_Num%" episode "%Episode_Num%"
FileDelete, Episode_Info.xml
}
Return
Rename_File:
If (Season_X_Num != "" && Episode_X_Num != "" and Episode_X_Name != "")
{
StringLen, Season_X_Num_Len, Season_X_Num
If Season_X_Num_Len = 1
Season_X_Num = 0%Season_X_Num%
StringLen, Episode_X_Num_Len, Episode_X_Num
If Episode_X_Num_Len = 1
Episode_X_Num = 0%Episode_X_Num%
New_File_Name = %Series_X_Name% - %Season_X_Num%x%Episode_X_Num% - %Episode_X_Name%.%File_Ext%
MsgBox, 4, Confirm rename?, %Original_File_Name%`n`nwill become`n`n%New_File_Name%
IfMsgBox, No
MsgBox, 0, Rename cancelled!, File **WAS NOT** renamed
IfMsgBox, Yes
{
FileMove, %Target_Folder%\%Original_File_Name%, %Target_Folder%\%New_File_Name%
MsgBox, 0, Congratulations!, File was succesfully renamed!!
}
}
Return
Edit - Tweaked naming scheme (thanks Trubbleguy!).
Edit2 - Misc code tweaks and cleanup