Plex Auto-Delete Script

cool idea

how about integrating a way to have it delete the oldest file for movies?  example, a movie that has never been watched over 90 days = deleted.

i'd like that.

cool idea

how about integrating a way to have it delete the oldest file for movies?  example, a movie that has never been watched over 90 days = deleted.

i'd like that.

That would actually be quite easy to do.

So you want it to simply look through all of your movie files and DELETE if its older than 90 days? (What it has been watched, do you want to keep those or delete thoses?)

That can actually be done with a much simpler script, assuming that you put your movies in a different directory from your TV shows.

Are you on Windows, Linux or MAC?

I see in your Signature you have a San Jose Server. What company is that hosted through? What kind of pricing are you paying on that if you don't mind me asking?


I'm sure there is a key identifier, but haven't had the time to clearly look through and make the distinction of how Plex is associating their ViewShows attributes to the attributes in there OnDeck.


Thank you for explaining your logic to me. I don't think like a programmer. I went back through the xml and I think I found the attributes that we need (and my best guess is the same attributes that Plex uses.) The recentlyViewed xml contains the season and episode attributes for each file! They are these two tags:
 
index="2" parentIndex="1"

Translated: Season 1 (parentIndex="1") episode 2 (index="2")


<br><br>
Would this make a clean solution? for example [ for each series: protect the largest index=value from deletion | allow deletion of all remaining files ] :o<br><br>
Edit: Or... Alternatively, you could possibly use the OnDeck file attributes to do the same thing in reverse: [ for each series: protect from deletion files that match the series and have an index=value of index=value - 1 for each onDeck file ]<br>
&nbsp;</p>

<blockquote  class="ipsBlockquote" data-author="Steven4x4" data-cid="537989" data-time="1388532306"><br>
My understanding was thatPlex already did the hard work after you watched the show it changed the attributes on the episodes and identified which one was OnDeck, (Since it Clearly has a Specific Section for OnDeck) so that means it should have already been marked and not dependent on other files, but its seems thats not the case.</blockquote>
<p>
&nbsp;</p>
<p>Yes, I believe you are exactly right. Plex does the work and marks the upcoming unwatched episode as onDeck. However, that tag doesn't stay very long. Plex removes it rather quickly (on next library scan.) During the library scan it finds that there are no longer watched episodes for that series in the library (because all watched episodes were deleted by this script.) Plex then removes the tag. The OnDeck tag is entirely dependent on the last watched episode remaining in the library.</p>

egretsareherons & Steven4x4

I tested the latest version of the script on mac. It works great. I have noticed one issue:

These files failed: 

Hawaii Five-0 - 4x09 - Hau’oli Lā Ho’omaika’i.mkv

1 views: /Volumes/ext3TB/TV/Hawaii Five-0/Season 04/Hawaii Five-0 - 4x09 - Hau%E2%80%99oli L%C4%81 Ho%E2%80%99omaika%E2%80%99i.mkv
##[NOT FOUND] /Volumes/ext3TB/TV/Hawaii Five-0/Season 04/Hawaii Five-0 - 4x09 - Hau%E2%80%99oli L%C4%81 Ho%E2%80%99omaika%E2%80%99i.mkv

Raising Hope - 4x01 - Déja Vu Man.mkv

 
1 views: /Volumes/ext3TB/TV/Raising Hope/Season 04/Raising Hope - 4x01 - D%C3%A9ja Vu Man.mkv
##[NOT FOUND] /Volumes/ext3TB/TV/Raising Hope/Season 04/Raising Hope - 4x01 - D%C3%A9ja Vu Man.mkv

It seems that special characters like (é & ā in this situation) are causing the issue. You probably have the answer/fix for this... My guess is that the data types used are incompatible with those characters.

Thanks again for the script!

Edit: Potentially helpful resource for solving: http://www.reddit.com/r/learnpython/comments/178juj/trouble_with_accent_characters/

 

egretsareherons & Steven4x4

I tested the latest version of the script on mac. It works great. I have noticed one issue:

These files failed: 

Hawaii Five-0 - 4x09 - Hau’oli Lā Ho’omaika’i.mkv

 

Hey STEEVo,

The XML coming from plex has percent-encoded unicode.  I have a fix for ya.  

Search for this, (around line 300 of my version):

  ####################################################################################
  ##  Get Files for Watched Shows
  ####################################################################################
  changed = 0
  for VideoNode in doc.getElementsByTagName("Video"):
    view = VideoNode.getAttribute("viewCount")
    if view == '':
      view = 0
    view = int(view)
    MediaNode = VideoNode.getElementsByTagName("Media")
    for Media in MediaNode:
      PartNode = Media.getElementsByTagName("Part")
      for Part in PartNode:
        file = Part.getAttribute("file")
        print str(view) + " views: " + file

        #NEW:
        if PC == ‘L’:
          file = urllib2.unquote(file.encode(‘utf-8’)).decode(‘utf-8’)
        else:
          import urllib
          file = urllib.unquote(file.encode(‘utf-8’)).decode(‘utf-8’)

The lines under "new" are the ones you want to add. This is python, so whitespace matters.  Make sure you use spaces (not tabs) and line everything up.

That windows fix (in the else) is untested & it's kinda ugly, seeing as how I'm importing a library there. It's just a kludge.  Steven4x4 imports only urllib.request above, and I'm not sure why.  I'm sure he has a good reason, but not being much of a Windows programmer, I don't know the intricacies of that sort of thing. It will work for now, but should probably be cleaned up a bit in a later version.

Thanks egretsareherons! I am not sure if it works or not yet because I deleted the files that had the accents. However, I added it and will let you know as soon as another file shows up! 

I thought of another feature to add to the list. The leftover metadata files are piling up (not deleted with the video files.) Having an option to delete metadata files by extension would be great! You could just enable a list of extensions to delete that match the video file name. In my case I would want to delete .NFO & .TBN I am sure some people would have others or more.

Thanks again!  :)

It would be cool that they include this in the PHT whit a FUI whit just a few click and don.

BTW nice job. 

There any way that when he delete he send the files to the trash can instead of permanently delete them?

Steven Johnson
 
This is what I have been looking for and also a thanks to
  egretsareherons as I just added to his v2 to make a v3
  
The only thing I wanted to add was a number of days to check
to make sure the video was not deleted early there are a number of ways
the video might get marked as watched like you go to sleep and 
the video plays to the end, or someone else may watch the video 
before you get a chance to.
 
I made a version v3 
DaysToKeep ( check number of days since added do not deleted early)
 
DaysToKeep = 14
 
    ################################################################
    ###Find number of days between date video was added and today
    addedAt      = VideoNode.getAttribute("addedAt")
    d1 = datetime.datetime.today()
    d2 = datetime.datetime.fromtimestamp(float(addedAt))
    DaysSincevideoAdded = (d1 -   d2).days
    ################################################################
 
 
## -- Check if video older than days to keep --
  if CantDelete == 0:
    if DaysToKeep > DaysSincevideoAdded:
      CantDelete += 1
      ShowFound = " [Days:"  + str(DaysSincevideoAdded) +"]" + ShowFound
      ShowsCount += 1
  else:
    if DaysToKeep > DaysSincevideoAdded:
      CantDelete += 1
      ShowFound = " [Days:"  + str(DaysSincevideoAdded) +"]" + ShowFound
 
What I would like to add is a number of days per show, and maybe
a flag to not count weekends to handle a daily show mon-fri.
 
Or maybe number of episode to save per show.

I know might be you already add this to it but it would be cool on the TV show section that he doesn't delete the last two episode, keep the two last one like reference. And some movies and Tv show specially in HD come in folder, i see it delete only the file.

Thanks.

much thanks to everyone who worked on this, I just used v3 and it worked great.

here is what it looked like for me:

--

PC = ""
Host = ""
Port = ""
SectionList = []
IgnoreSections = []
Delete = "0"
Move = "1"
Copy = "0"
Shows = [""]
OnDeck = ""
DestinationDir = "/t/trash"
DestinationSection = ""
TriggerRescan = False
DaysToKeep = 0
--
----------------------------------------------------------------------------
                Summary -- Script Completed Successfully
----------------------------------------------------------------------------
 
  Total File Count      117
  Kept Show Files       0
  On Deck Files         0
  Deleted Files         0
  Moved Files           117
  Copied Files          0
  Flagged Files         0
  Rescanned Sections 
----------------------------------------------------------------------------
$ du -sh /t/trash
94G

Is there an easy way to modify this to only delete a few specific shows? I think, if I reading this right, the logic is the other way around were it will delete every except what you shows you specify.

Hopefully that makes sense and great work Steven.

Is there an easy way to modify this to only delete a few specific shows? I think, if I reading this right, the logic is the other way around were it will delete every except what you shows you specify.

Hopefully that makes sense and great work Steven.

That is correct.

However, The Code is simple enough that it can be re-versed fair easily.

Unfortunately, I haven't had time to work on this project in several months, however there are few guys in here that have made some updates and changes that might be able to chime in with the solution much quicker than I.

So I will see if I can come up with the answer for you...

Few Sections Require Changes:

For the Header Information Change this part to: (Really Is Just The Last 2 Lines For Informational Purposes)

####################################################################################
##  Checking Shows
####################################################################################
NoDelete = " | "
ShowCount = len(Shows)
print("Show Count: " + str(ShowCount))

for Show in Shows:
Show = re.sub(’[^A-Za-z0-9 ]+’, ‘’, Show).strip()
if Show=="":
NoDelete += "(None Listed) | "
ShowCount -= 1
else:
NoDelete += Show + " | "

print("Number of Shows Detected For Deletion: " + str(ShowCount))
print (“Shows to Delete:” + NoDelete)

THEN

####################################################################################
##  Check Shows And Delete If Configured
####################################################################################
def CheckShows( CheckFile ):
  global FileCount
  global DeleteCount
  global FlaggedCount
  global OnDeckCount
  global ShowsCount
  FileCount += 1
  CantDelete = 0
  ShowFound = ""

– CHECK SHOWS –

for Show in Shows:
Show = re.sub(’[^A-Za-z0-9 ]+’, ‘’, Show).strip()
if Show=="":
CantDelete = 0
else:
if (’ ’ in Show) == True:
if all(str(Word) in CheckFile for Word in Show.split()):
CantDelete += 0
ShowFound = “[” + Show + “]”
ShowsCount += 1
else:
CantDelete += 1
else:
if Show in CheckFile:
CantDelete += 0
ShowFound = “[” + Show + “]”
ShowsCount += 1
else:
CantDelete += 1

– Check OnDeck –

if OnDeck==“1”:
IsOnDeck = CheckOnDeck(CheckFile);
if IsOnDeck==0:
CantDelete += 0
else:
CantDelete += 1
ShowFound = “[OnDeck]” + ShowFound
OnDeckCount += 1

– DELETE SHOWS –

if CantDelete >= 1:
if Delete==“1”:
print("[DELETED] " + CheckFile)
os.remove(file)
DeleteCount += 1
else:
print("
[FLAGGED] " + CheckFile)
FlaggedCount += 1
else:
print("[KEEPING]" + ShowFound + " " + CheckFile)

I Believe those changes shoul do it...

FOR SAFETY REASONS: (AND THE LACK OF TESTING)

BACKUP YOUR DATA FIRST (BEFORE RUNNING)

SECONDLY Make Sure DELETE = "0" in the top settings to make sure that its only informational and not actually deleting anything...

Hope this works for you..

SIDE NOTE: You may have to remove in the 2nd code box I posted the small sub section regaring OnDeck as this might mess up what you are tryting to do..

(YOU MAY or MAY NOT NEED TO REMOVE THE BELOW SECTION

## -- Check OnDeck --
  if OnDeck=="1":
    IsOnDeck = CheckOnDeck(CheckFile);
    if IsOnDeck==0:
      CantDelete += 0
    else:
      CantDelete += 1
      ShowFound = "[OnDeck]" + ShowFound
      OnDeckCount += 1

I made a couple updates if anyone is interested:

  • Added an additional keep mode based on last viewed date (rather than the added date.)
  • Added the toggle to choose between the two
  • Simplified the logging formatting a bit and added additional logging for the above additions

Edit: Updated/Fixed Code Here

Thanks again Steven4x4 and the rest who have contributed!

If anyone has any helpful pointers I am still having trouble with files that use special characters:

Filename:
Hawaii Five-0 - 4x14 - Nā Hala a Ka Makua - 720p.mkv

Plex’s XML gives this:
Hawaii Five-0 - 4x14 - N%C4%81 Hala a Ka Makua - 720p.mkv

Of course the script then fails to find/delete the file...

Here is what I have been able to figure out

The %C4%81 portion of the filename in the XML needs to have the HTML entities stripped/replaced and corrected to this: \xC4\x81 which can then be correctly converted back into the proper filename before file deletion.

Can anyone help me with code to do this to any filenames with these special characters? Not a big deal if not. I can just manually delete them.

in python I enter this: and get the correct output:

>>> print '\xC4\x81'
ā

Thanks again Steven4x4 and the rest who have contributed!

If anyone has any helpful pointers I am still having trouble with files that use special characters:

Filename:
Hawaii Five-0 - 4x14 - Nā Hala a Ka Makua - 720p.mkv

Plex’s XML gives this:
Hawaii Five-0 - 4x14 - N%C4%81 Hala a Ka Makua - 720p.mkv

Of course the script then fails to find/delete the file...

Here is what I have been able to figure out

The %C4%81 portion of the filename in the XML needs to have the HTML entities stripped/replaced and corrected to this: \xC4\x81 which can then be correctly converted back into the proper filename before file deletion.

Can anyone help me with code to do this to any filenames with these special characters? Not a big deal if not. I can just manually delete them.

in python I enter this: and get the correct output:

>>> print '\xC4\x81'
ā

Did my above fix (comment #45) not work for you, Steevo?  I tested it on Linux & it worked great.  

https://forums.plex.tv/topic/84822-plex-auto-delete-script/page-3#entry547782

Did my above fix (comment #45) not work for you, Steevo?  I tested it on Linux & it worked great.  

https://forums.plex.tv/topic/84822-plex-auto-delete-script/page-3#entry547782

It did not work for me unfortunately. I think it would however if I could reformat the filename retrieved from XML from %C4%81 to '\xC4\x81' (from example above) first. Did you it work for you with a filename with those % entities? Thanks again for the help!

EDIT: I see what you mean. My terminal output:

>>> import urllib2
>>> target = '%C4%81'
>>> print urllib2.unquote(target)
ā

It should work. Let me try again

@egretsareherons

I think it works but then it causes this error on the next print command that includes 'file':

UnicodeEncodeError: 'ascii' codec can't encode character u'\u0101' in position 112: ordinal not in range(128)

Errors on this line (in my script):

print "Viewed:" + str(view) + "x | Days Since Viewed: " + str(DaysSinceVideoLastViewed) + " Added: " + str(DaysSincevideoAdded) + " | " + file

Well, some of this is probably going to come down to how the os & shutil modules handle escaped characters.  Those are somewhat OS specific.  On linux and presumably on osx, it would not work to remove the \x style encoding.  What the urllib.unquote function does (urllib2.unquote(file.encode('utf-8')).decode('utf-8')) is take the %encoded XML and translate it back to the goofy looking (ie ā) character.  So it's using the actual system filename when going to remove the file.

I'm moving my files, not deleting them. IIRC you are removing your files, right?  I see that the remove command is os.remove, while the move and copy are called from shutil.  There could be a difference in unicode handling between those two modules.  (If you haven't yet realized this, unicode is a giant pain the ass in python 2.x.)  That is to say, os.remove() may be balking at the raw unicode character.  (I really have no idea, I'm just guessing here.) 

One thing you could try, as a test, is using my fix with the copy or move function.  If that works, it's likely that its os.remove() that's having the problem.  In that case, you could convert the os.remove() call to a shutil.rmtree() call, though that's a little dangerous looking, since it has the potential to remove a whole directory tree.  You'd wanna be real careful there.  Another solution would be to use shutil.move() to move the unicode containing filename to a non-unicode encoded name, then remove that file using os.remove().

All of that sounds pretty hacky, I know.  I would have expected the original fix to work on all os's, but Unicode is not my strong suit.  

@egretsareherons

I think it works but then it causes this error on the next print command that includes 'file':

UnicodeEncodeError: 'ascii' codec can't encode character u'\u0101' in position 112: ordinal not in range(128)

Errors on this line (in my script):

print "Viewed:" + str(view) + "x | Days Since Viewed: " + str(DaysSinceVideoLastViewed) + " Added: " + str(DaysSincevideoAdded) + " | " + file

OK!  So it is working, it's just the print command that's failing. That's good!

So that means that we just have to make the file thingy print properly.

Try this:  

print "Viewed:" + str(view) + "x | Days Since Viewed: " + str(DaysSinceVideoLastViewed) + " Added: " + str(DaysSincevideoAdded) + " | " + file.encode('utf-8').decode('utf-8')

Edit: If that encode() decode thing doesn't work, in the meantime, you can wrap the whole thing in a try/except block.  That way the whole script won't barf on one error.  So like this:  

try:
  print "Viewed:" + str(view) + "x | Days Since Viewed: " + str(DaysSinceVideoLastViewed) + " Added: " + str(DaysSincevideoAdded) + " | " + file 

except Exception, e:
  print “error printing unicode character”

It doesn't fix the problem per se, but it leaves you with a working script until someone smarter than me can tell you how to get it to print properly.  

Man, I hate unicode.