Created
August 24, 2015 04:14
-
-
Save mauvehed/b2f6b2de6452ac63b865 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
import sqlite3 | |
import os | |
import urllib | |
import unicodedata | |
import getopt | |
import sys | |
import codecs | |
# Find unscanned files not in Plex database but on filesystem | |
# Version 0.03 | |
# - 0.01 - Initial release | |
# - 0.02 - Go through all sections and verify DB exists and optionally write results to file | |
# - 0.03 - Add option to generate lists of matched files for each section (-l) | |
VIDEO_EXTENSIONS = ['.m4v', '.3gp', '.nsv', '.ts', '.ty', '.strm', '.rm', '.rmvb', '.m3u', '.mov', '.qt', '.divx', '.xvid', '.bivx', '.vob', '.nrg', '.img', '.iso', '.pva', '.wmv', '.asf', '.asx', '.ogm', '.m2v', '.avi', '.bin', '.dat', '.dvr-ms', '.mpg', '.mpeg', '.mp4', '.mkv', '.avc', '.vp3', '.svq3', '.nuv', '.viv', '.dv', '.fli', '.flv', '.rar', '.001', '.wpl', '.zip', '.jpg'] | |
OK_FILES = ['imdb.nfo', '.DS_Store', '__db.001'] | |
OTHER_EXTENSIONS = ['.srt', '.sub', '.idx', '.jpg'] | |
def usage(): | |
print "Usage: python %s [OPTION]" % (__file__) | |
print "Options:" | |
print "-f, --file <filename> Write out results to <filename>" | |
print "-h, --help This message" | |
print "-i, --ignores Display list of files that are within paths that aren't video extensions or subtitles" | |
print "-l, --list Create files listing all matched files in plex" | |
return | |
def listTree(top, files=list()): | |
r = files[:] | |
if not os.path.exists(top): | |
print 'Not mounted: ' + top | |
return r | |
for f in os.listdir(top): | |
pathname = os.path.join(top, f) | |
if os.path.isdir(pathname): | |
r = listTree(pathname, r) | |
elif os.path.isfile(pathname): | |
r.append(unicodedata.normalize('NFC', pathname)) | |
else: | |
print 'Skipping %s' % pathname | |
return r | |
def main(): | |
display_ignores = False | |
fh = None | |
display_matches = False | |
try: | |
opts, args = getopt.getopt(sys.argv[1:], "f:ihl", ["file=", "ignores", "help", "list"]) | |
except getopt.GetoptError,exc: | |
print exc.msg | |
sys.exit(2) | |
for opt, arg in opts: | |
if opt in ("-f", "--file"): | |
filename = arg | |
fh = codecs.open(filename, "w", "utf-8") | |
if opt in ("-i", "--ignores"): | |
display_ignores = True | |
if opt in ("-h", "--help"): | |
usage() | |
exit() | |
if opt in ("-l", "--list"): | |
display_matches = True | |
dbPath = os.path.expanduser('~/Library/Application Support/Plex Media Server/Plug-in Support/Databases/com.plexapp.plugins.library.db') | |
#Linux DB path - TODO (detect OS or allow specification via command line) | |
#dbPath = '/var/lib/plexmediaserver/Library/Application Support/Plex Media Server/Plug-in Support/Databases/com.plexapp.plugins.library.db' | |
if (not os.path.isfile(dbPath)): | |
print "Cannot find database at %s. Running PMS locally on this Mac?\nExiting." % (dbPath) | |
exit() | |
conn = sqlite3.connect(dbPath) | |
rows = conn.cursor().execute('select L.id, L.name from library_sections L').fetchall() | |
for row in rows: | |
section_id = row[0] | |
section_name = row[1] | |
print "\n\nScanning %s[%s]" % (section_name, section_id) | |
root_paths = conn.cursor().execute('select root_path from section_locations where library_section_id=?', [section_id,]).fetchall() | |
basePaths = map(lambda p: p[0], root_paths) | |
print "Composed of %s" % (", ".join(basePaths)) | |
paths = list() | |
rows = conn.cursor().execute('SELECT mp.file, md.title from media_parts mp JOIN media_items mi ON mp.media_item_id=mi.id JOIN metadata_items md ON mi.metadata_item_id=md.id WHERE mi.library_section_id=?', [section_id,]).fetchall() | |
paths = map(lambda p: unicodedata.normalize('NFC', p[0]), rows) | |
if (display_matches): | |
match_fn = "plex_matches_%s.txt" % (section_id) | |
print "\tWriting matches to %s..." % (match_fn) | |
match_fh = codecs.open(match_fn, "w", "utf-8") | |
rows.sort(key=lambda x: x[1]) | |
for row in rows: | |
match_fh.write("%s -- %s\n" % (row[1], row[0])) | |
match_fh.close() | |
missing = list() | |
for basePath in basePaths: | |
print "\tHandling path %s ..." % basePath | |
filePaths = listTree(basePath) | |
for filePath in filePaths: | |
if filePath not in paths: | |
cext = os.path.splitext(filePath)[1].lower() | |
fname = os.path.split(filePath)[1] | |
if (fname in OK_FILES): | |
#Don't do anything for acceptable files | |
pass | |
elif (cext in OTHER_EXTENSIONS): | |
#ignore images and subtitles | |
pass | |
elif (cext not in VIDEO_EXTENSIONS): | |
#these shouldn't be here | |
if (display_ignores): | |
print 'Ignoring %s' % filePath | |
pass | |
else: | |
missing.append(filePath) | |
print '\tfinished %s' % basePath | |
print '\nMissing %i items:' % len(missing) | |
if (fh): | |
fh.write("\nSection %s:\n" % (section_name)) | |
fh.write("Missing %i items\n" % len(missing)) | |
for item in missing: | |
print item | |
if (fh): | |
fh.write(item+"\n") | |
if (fh): | |
fh.close() | |
conn.close() | |
exit() | |
if __name__ == "__main__": | |
main() | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment