Skip to content

Instantly share code, notes, and snippets.

@beloso
Last active August 29, 2015 14:13
Show Gist options
  • Save beloso/823a2c6ef0e15432adc2 to your computer and use it in GitHub Desktop.
Save beloso/823a2c6ef0e15432adc2 to your computer and use it in GitHub Desktop.
#!/usr/bin/python
# -*- coding: utf-8 -*-
####################################################################################
## INFORMATION
####################################################################################
## Developed by: Steven Johnson (minor modifications by egretsareherons)
## (a minor modifications by aross01)
## (a minor modification by STEEVo)
##
## Last Updated: 02/26/2014 5:05PM MST
##
## Description: Auto-Delete Watched Items In Plex
##
## Required Configurations:
## - PC (Blank = AutoDetect | W = Windows | L = Linux/Unix/MAC)
## - Host (Hostname or IP | Blank = 127.0.0.1)
## - Port (Port | Blank = 32400)
## - SectionList - A list of sections to operate on. If this is blank, the script
## will get a list of all sections from the server & operate on all, except
## for the sections listed in IgnoreSections
## - IgnoreSections - Only used if SectionList is blank. When SectionList is blank,
## the script will operate on all available sections of your install
## except for the ones listed here.
## - Delete (1 = Delete | Blank = 0 (For Testing or Review))
## - Move (1 = move files to DestinationDir, 0 = don't move)
## - Copy (1 = copy files to DestinationDir, 0 = don't copy)
## - Shows ( ["Show1","Show2"]; = Kept Shows OR [""]; = DEL ALL SHOWS )
## - OnDeck ( 1 = Keep If On Deck | Blank = Delete Regardless if onDeck )
## - DestinationDir - the place to move or copy your watched files for later manual deletion
## - DestinationSection - if your destination dir is also a configured section, set the
## number here so TriggerRescan knows to rescan that section when changes are made.
## only used if TriggerRescan is True;
## - TriggerRescan - automatically trigger a deep rescan of changed sections. True or False.
##
## - KeepMode (Blank = Disabled | W = "Watched" | A = "Added")
## Method used to keep items for X number of days (specified by DaysToKeep below.)
## You may choose to base the keep on the last "watched" or "added" date.
## - DaysToKeep ("30" = Keep items for 30 days following the KeepMode selected)
##
####################################################################################
####################################################################################
PC = ""
Host = ""
Port = ""
SectionList = []
IgnoreSections = []
Delete = ""
Move = "0"
Copy = "0"
Shows = []
OnDeck = ""
DestinationDir = ""
DestinationSection = ""
TriggerRescan = True
KeepMode = ""
DaysToKeep = 30
####################################################################################
## NO NEED TO EDIT BELOW THIS LINE
####################################################################################
import os
import xml.dom.minidom
import platform
import re
import shutil
import datetime
import glob
try:
import urllib.request as urllib2
except:
import urllib2
####################################################################################
## Check On Deck
####################################################################################
def CheckOnDeck( CheckDeckFile ):
InTheDeck = 0
for DeckVideoNode in deck.getElementsByTagName("Video"):
DeckMediaNode = DeckVideoNode.getElementsByTagName("Media")
for DeckMedia in DeckMediaNode:
DeckPartNode = DeckMedia.getElementsByTagName("Part")
for DeckPart in DeckPartNode:
Deckfile = DeckPart.getAttribute("file")
if CheckDeckFile==Deckfile:
InTheDeck += 1
else:
InTheDeck += 0
return InTheDeck
####################################################################################
## Check Shows And Delete If Configured
####################################################################################
def CheckShows( CheckFile ):
global FileCount
global DeleteCount
global MoveCount
global CopyCount
global FlaggedCount
global OnDeckCount
global ShowsCount
global DestinationDir
FileCount += 1
CantDelete = 0
ShowFound = ""
changed = 0
## -- 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 += 1
ShowFound = "[" + Show + "]"
ShowsCount += 1
else:
CantDelete += 0
else:
if Show in CheckFile:
CantDelete += 1
ShowFound = "[" + Show + "]"
ShowsCount += 1
else:
CantDelete += 0
## -- Check OnDeck --
if OnDeck=="1":
IsOnDeck = CheckOnDeck(CheckFile);
if IsOnDeck==0:
CantDelete += 0
else:
CantDelete += 1
ShowFound = "[OnDeck]" + ShowFound
OnDeckCount += 1
## -- Check if video older than days to keep [Viewed method] --
if KeepMode=="W":
if DaysToKeep > DaysSinceVideoLastViewed:
CantDelete += 1
ShowsCount += 1
# ShowFound = " | Days Since Viewed:" + str(DaysSinceVideoLastViewed) +" |" + ShowFound
## -- Check if video older than days to keep [Added method] --
if KeepMode=="A":
if DaysToKeep > DaysSinceVideoAdded:
CantDelete += 1
ShowsCount += 1
# ShowFound = " | Days Since Added:" + str(DaysSinceVideoAdded) +" |" + ShowFound
## -- DELETE SHOWS --
if CantDelete == 0:
if Delete=="1":
#print("**[DELETED] " + CheckFile)
similairFiles = re.sub('\[', '[[]', os.path.splitext(file)[0]) + "*"
print("Looking for " + similairFiles)
for deleteFile in glob.glob(similairFiles):
try:
os.remove(deleteFile)
print("**[DELETED] " + deleteFile)
changed = 1
DeleteCount += 1
except Exception as e:
print ("error deleting file: %s" % e)
elif Move == "1":
try:
os.utime(os.path.realpath(CheckFile), None)
shutil.move(os.path.realpath(CheckFile), DestinationDir)
print("**[MOVED] " + CheckFile)
changed = 1
except Exception as e:
print ("error moving file: %s" % e)
if os.path.islink(CheckFile):
os.unlink(CheckFile)
MoveCount += 1
elif Copy == "1":
try:
shutil.copy(os.path.realpath(CheckFile), DestinationDir)
changed = 1
except Exception as e:
print ("error copying file: %s" % e)
print("**[COPIED] " + CheckFile)
CopyCount += 1
else:
print("**[FLAGGED] " + CheckFile)
FlaggedCount += 1
else:
print("[KEEPING]")
return changed
####################################################################################
## Checking URL
####################################################################################
if Host=="":
Host="127.0.0.1"
if Port=="":
Port="32400"
print("----------------------------------------------------------------------------")
print(" Detected Settings")
print("----------------------------------------------------------------------------")
print("Host: " + Host)
print("Port: " + Port)
#print("Section: " + Section)
#print("URL: " + URL)
#print("OnDeck URL: " + OnDeckURL)
if KeepMode=="W":
print("Keep Mode: Keeping for " + str(DaysToKeep) + " days following the date last viewed")
elif KeepMode=="A":
print("Keep Mode: Keeping viewed items that were added to the library less than " + str(DaysToKeep) + " days ago")
else:
print("Keep Mode: DISABLED")
####################################################################################
## 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 Keeping: " + str(ShowCount))
print ("Shows to Keep:" + NoDelete)
###################################################################################
## Checking Delete
####################################################################################
if Delete=="1":
print("Delete: ***Enabled***")
else:
print("Delete: Disabled - Flagging Only")
if OnDeck=="1":
print("Delete OnDeck: No")
else:
print("Delete OnDeck: Yes")
####################################################################################
## Checking OS
####################################################################################
AD = ""
if PC=="":
AD = "(Auto Detected)"
if platform.system()=="Windows":
PC = "W"
elif platform.system()=="Linux":
PC = "L"
elif platform.system()=="Darwin":
PC = "L"
FileCount = 0
DeleteCount = 0
MoveCount = 0
CopyCount = 0
FlaggedCount = 0
OnDeckCount = 0
ShowsCount = 0
RescannedSections = []
####################################################################################
## Get list of sections
####################################################################################
if PC=="L":
print("Operating System: Linux " + AD)
import urllib2
elif PC=="W":
print("Operating System: Windows " + AD)
import urllib.request
else:
print("Operating System: ** Not Configured ** (" + platform.system() + ") is not recognized.")
exit()
if not SectionList:
URL = ("http://" + Host + ":" + Port + "/library/sections/")
doc = xml.dom.minidom.parse(urllib2.urlopen(URL))
for Section in doc.getElementsByTagName("Directory"):
if Section.getAttribute("key") not in IgnoreSections:
SectionList.append(Section.getAttribute("key"))
SectionList.sort(key=int)
print ("Section List Mode: Auto")
print ("Operating on sections: " + ','.join(str(x) for x in SectionList))
print ("Skipping Sections: " + ','.join(str(x) for x in IgnoreSections))
else:
print ("Section List Mode: User-defined")
print ("Operating on user-defined sections: " + ','.join(str(x) for x in SectionList))
####################################################################################
## Loop on sections
####################################################################################
rescan_destination = False
for Section in SectionList:
Section = str(Section)
URL = ("http://" + Host + ":" + Port + "/library/sections/" + Section + "/recentlyViewed")
OnDeckURL = ("http://" + Host + ":" + Port + "/library/sections/" + Section + "/onDeck")
####################################################################################
## Setting OS Based Variables
####################################################################################
if PC=="L":
doc = xml.dom.minidom.parse(urllib2.urlopen(URL))
deck = xml.dom.minidom.parse(urllib2.urlopen(OnDeckURL))
elif PC=="W":
doc = xml.dom.minidom.parse(urllib.request.urlopen(URL))
deck = xml.dom.minidom.parse(urllib.request.urlopen(OnDeckURL))
SectionName = doc.getElementsByTagName("MediaContainer")[0].getAttribute("title1")
print("")
print("--------- Section "+ Section +": " + SectionName + " -----------------------------------")
####################################################################################
## Get Files for Watched Shows
####################################################################################
changed = 0
for VideoNode in doc.getElementsByTagName("Video"):
view = VideoNode.getAttribute("viewCount")
if view == '':
view = 0
view = int(view)
################################################################
###Find number of days between date video was viewed and today
lastViewedAt = VideoNode.getAttribute("lastViewedAt")
d1 = datetime.datetime.today()
d2 = datetime.datetime.fromtimestamp(float(lastViewedAt))
DaysSinceVideoLastViewed = (d1 - d2).days
################################################################
################################################################
###Find number of days between date video was added and today
addedAt = VideoNode.getAttribute("addedAt")
d1 = datetime.datetime.today()
da2 = datetime.datetime.fromtimestamp(float(addedAt))
DaysSinceVideoAdded = (d1 - da2).days
################################################################
MediaNode = VideoNode.getElementsByTagName("Media")
for Media in MediaNode:
PartNode = Media.getElementsByTagName("Part")
for Part in PartNode:
file = Part.getAttribute("file")
#NEW (Removed by Mustang):
#if PC == 'L':
#file = urllib2.unquote(file.encode('utf-8')).decode('utf-8')
#else:
#import urllib
#file = urllib.parse.unquote(file.encode('utf-8')).decode('utf-8')
if str(view)!="0":
print(" ")
if KeepMode=="W":
if str(view)!="0":
print ("Viewed:" + str(view) + "x | Days Since Viewed: " + str(DaysSinceVideoLastViewed) + " | " + file)
if view > 0:
if os.path.isfile(file):
changed += CheckShows(file);
else:
print("##[NOT FOUND] " + file)
if KeepMode=="A":
if str(view)!="0":
print ("Viewed:" + str(view) + "x | Days Since Added: " + str(DaysSinceVideoAdded) + " | " + file)
if view > 0:
if os.path.isfile(file):
changed += CheckShows(file);
else:
print("##[NOT FOUND] " + file)
if KeepMode=="":
if str(view)!="0":
print ("Viewed:" + str(view) + "x | Days Since Viewed: " + str(DaysSinceVideoLastViewed) + " Added: " + str(DaysSinceVideoAdded) + " | " + file)
if view > 0:
if os.path.isfile(file):
changed += CheckShows(file);
else:
print("##[NOT FOUND] " + file)
# rescan this section if necessary
if TriggerRescan and changed:
URL = ("http://" + Host + ":" + Port + "/library/sections/" + Section + "/refresh?deep=1")
blah = urllib2.urlopen(URL)
RescannedSections.append(SectionName + '(' + Section + ')')
rescan_destination = True
# rescan destination section if necessary
if TriggerRescan and DestinationSection and rescan_destination:
URL = ("http://" + Host + ":" + Port + "/library/sections/" + str(DestinationSection) + "/refresh?deep=1")
blah = urllib2.urlopen(URL)
RescannedSections.append('[destination dir]' + '(' + str(DestinationSection) + ')')
print("")
print("----------------------------------------------------------------------------")
print("----------------------------------------------------------------------------")
print(" Summary -- Script Completed Successfully")
print("----------------------------------------------------------------------------")
print("")
print(" Total File Count " + str(FileCount))
print(" Kept Show Files " + str(ShowsCount))
print(" On Deck Files " + str(OnDeckCount))
print(" Deleted Files " + str(DeleteCount))
print(" Moved Files " + str(MoveCount))
print(" Copied Files " + str(CopyCount))
print(" Flagged Files " + str(FlaggedCount))
print(" Rescanned Sections " + ', '.join(str(x) for x in RescannedSections) )
print("")
print("----------------------------------------------------------------------------")
print("----------------------------------------------------------------------------")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment