Skip to content

Commit

Permalink
remove attrdict and other cleanup.
Browse files Browse the repository at this point in the history
closes #63
  • Loading branch information
saltydk committed Aug 28, 2022
1 parent 6650e39 commit 61e63c8
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 94 deletions.
79 changes: 29 additions & 50 deletions config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import os
import sys

from attrdict import AttrDict
from plexapi.myplex import MyPlexAccount
from getpass import getpass

Expand All @@ -29,23 +28,6 @@
cfg = None


class AttrConfig(AttrDict):
"""
Simple AttrDict subclass to return None when requested attribute does not exist
"""

def __init__(self, config):
super().__init__(config)

def __getattr__(self, item):
try:
return super().__getattr__(item)
except AttributeError:
pass
# Default behaviour
return None


def prefilled_default_config(configs):
default_config = base_config.copy()

Expand Down Expand Up @@ -88,51 +70,48 @@ def prefilled_default_config(configs):


def build_config():
if not os.path.exists(config_path):
print("Dumping default config to: %s" % config_path)
if os.path.exists(config_path):
return False
print(f"Dumping default config to: {config_path}")

configs = dict(url='', token='', auto_delete=False)
configs = dict(url='', token='', auto_delete=False)

# Get URL
configs['url'] = input("Plex Server URL: ")
# Get URL
configs['url'] = input("Plex Server URL: ")

# Get Credentials for plex.tv
user = input("Plex Username: ")
password = getpass('Plex Password: ')
# Get Credentials for plex.tv
user = input("Plex Username: ")
password = getpass('Plex Password: ')

# Get choice for Auto Deletion
# Get choice for Auto Deletion
auto_del = input("Auto Delete duplicates? [y/n]: ")
while auto_del.strip().lower() not in ['y', 'n']:
auto_del = input("Auto Delete duplicates? [y/n]: ")
while auto_del.strip().lower() not in ['y', 'n']:
auto_del = input("Auto Delete duplicates? [y/n]: ")
if auto_del.strip().lower() == 'y':
configs['auto_delete'] = True
elif auto_del.strip().lower() == 'n':
configs['auto_delete'] = False

account = MyPlexAccount(user, password)
configs['token'] = account.authenticationToken
if auto_del.strip().lower() == 'y':
configs['auto_delete'] = True
elif auto_del.strip().lower() == 'n':
configs['auto_delete'] = False

with open(config_path, 'w') as fp:
json.dump(prefilled_default_config(configs), fp, sort_keys=True, indent=2)
account = MyPlexAccount(user, password)
configs['token'] = account.authenticationToken

return True
with open(config_path, 'w') as fp:
json.dump(prefilled_default_config(configs), fp, sort_keys=True, indent=2)

else:
return False
return True


def dump_config():
if os.path.exists(config_path):
with open(config_path, 'w') as fp:
json.dump(cfg, fp, sort_keys=True, indent=2)
return True
else:
if not os.path.exists(config_path):
return False
with open(config_path, 'w') as fp:
json.dump(cfg, fp, sort_keys=True, indent=2)
return True


def load_config():
with open(config_path, 'r') as fp:
return AttrConfig(json.load(fp))
return json.load(fp)


def upgrade_settings(defaults, currents):
Expand All @@ -153,9 +132,9 @@ def inner_upgrade(default, current, key=None):
print("Added %r to config option %r: %s" % (str(k), str(key), str(v)))
continue
# iterate children
if isinstance(v, dict) or isinstance(v, list):
if isinstance(v, (dict, list)):
did_upgrade, merged[k] = inner_upgrade(default[k], current[k], key=k)
sub_upgraded = did_upgrade if did_upgrade else sub_upgraded
sub_upgraded = did_upgrade or sub_upgraded

elif isinstance(default, list) and key:
for v in default:
Expand All @@ -167,7 +146,7 @@ def inner_upgrade(default, current, key=None):
return sub_upgraded, merged

upgraded, upgraded_settings = inner_upgrade(defaults, currents)
return upgraded, AttrConfig(upgraded_settings)
return upgraded, upgraded_settings


############################################################
Expand Down
71 changes: 30 additions & 41 deletions plex_dupefinder.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#!/usr/bin/env python3
import collections
import itertools
import logging
import os
import sys
Expand Down Expand Up @@ -35,10 +36,11 @@

# Setup PlexServer object
try:
plex = PlexServer(cfg.PLEX_SERVER, cfg.PLEX_TOKEN)
plex = PlexServer(cfg['PLEX_SERVER'], cfg['PLEX_TOKEN'])
except:
log.exception("Exception connecting to server %r with token %r", cfg.PLEX_SERVER, cfg.PLEX_TOKEN)
print("Exception connecting to %s with token: %s" % (cfg.PLEX_SERVER, cfg.PLEX_TOKEN))
log.exception("Exception connecting to server %r with token %r", cfg['PLEX_SERVER'], cfg['PLEX_TOKEN'])
print(f"Exception connecting to {cfg['PLEX_SERVER']} with token: {cfg['PLEX_TOKEN']}")

exit(1)


Expand All @@ -50,14 +52,12 @@
def get_dupes(plex_section_name):
sec_type = get_section_type(plex_section_name)
dupe_search_results = plex.library.section(plex_section_name).search(duplicate=True, libtype=sec_type)
dupe_search_results_new = dupe_search_results.copy()

# filter out duplicates that do not have exact file path/name
if cfg.FIND_DUPLICATE_FILEPATHS_ONLY:
dupe_search_results_new = dupe_search_results.copy()
if cfg['FIND_DUPLICATE_FILEPATHS_ONLY']:
for dupe in dupe_search_results:
if not all(x == dupe.locations[0] for x in dupe.locations):
if any(x != dupe.locations[0] for x in dupe.locations):
dupe_search_results_new.remove(dupe)

return dupe_search_results_new


Expand All @@ -73,25 +73,25 @@ def get_section_type(plex_section_name):
def get_score(media_info):
score = 0
# score audio codec
for codec, codec_score in cfg.AUDIO_CODEC_SCORES.items():
for codec, codec_score in cfg['AUDIO_CODEC_SCORES'].items():
if codec.lower() == media_info['audio_codec'].lower():
score += int(codec_score)
log.debug("Added %d to score for audio_codec being %r", int(codec_score), str(codec))
break
# score video codec
for codec, codec_score in cfg.VIDEO_CODEC_SCORES.items():
for codec, codec_score in cfg['VIDEO_CODEC_SCORES'].items():
if codec.lower() == media_info['video_codec'].lower():
score += int(codec_score)
log.debug("Added %d to score for video_codec being %r", int(codec_score), str(codec))
break
# score video resolution
for resolution, resolution_score in cfg.VIDEO_RESOLUTION_SCORES.items():
for resolution, resolution_score in cfg['VIDEO_RESOLUTION_SCORES'].items():
if resolution.lower() == media_info['video_resolution'].lower():
score += int(resolution_score)
log.debug("Added %d to score for video_resolution being %r", int(resolution_score), str(resolution))
break
# score filename
for filename_keyword, keyword_score in cfg.FILENAME_SCORES.items():
for filename_keyword, keyword_score in cfg['FILENAME_SCORES'].items():
for filename in media_info['file']:
if fnmatch(os.path.basename(filename.lower()), filename_keyword.lower()):
score += int(keyword_score)
Expand All @@ -112,7 +112,7 @@ def get_score(media_info):
score += int(media_info['audio_channels']) * 1000
log.debug("Added %d to score for audio channels", int(media_info['audio_channels']) * 1000)
# add file size to score
if cfg.SCORE_FILESIZE:
if cfg['SCORE_FILESIZE']:
score += int(media_info['file_size']) / 100000
log.debug("Added %d to score for total file size", int(media_info['file_size']) / 100000)
return int(score)
Expand Down Expand Up @@ -178,8 +178,7 @@ def get_media_info(item):
for part in item.parts:
for stream in part.audioStreams():
if stream.channels:
log.debug("Added %d channels for %s audioStream", stream.channels,
stream.title if stream.title else 'Unknown')
log.debug(f"Added {stream.channels} channels for {stream.title if stream.title else 'Unknown'} audioStream")
info['audio_channels'] += stream.channels
if info['audio_channels'] == 0:
info['audio_channels'] = item.audioChannels if item.audioChannels else 0
Expand All @@ -198,9 +197,9 @@ def get_media_info(item):


def delete_item(show_key, media_id):
delete_url = urljoin(cfg.PLEX_SERVER, '%s/media/%d' % (show_key, media_id))
delete_url = urljoin(cfg['PLEX_SERVER'], '%s/media/%d' % (show_key, media_id))
log.debug("Sending DELETE request to %r" % delete_url)
if requests.delete(delete_url, headers={'X-Plex-Token': cfg.PLEX_TOKEN}).status_code == 200:
if requests.delete(delete_url, headers={'X-Plex-Token': cfg['PLEX_TOKEN']}).status_code == 200:
print("\t\tDeleted media item: %r" % media_id)
else:
print("\t\tError deleting media item: %r" % media_id)
Expand Down Expand Up @@ -228,11 +227,7 @@ def write_decision(title=None, keeping=None, removed=None):


def should_skip(files):
for files_item in files:
for skip_item in cfg.SKIP_LIST:
if skip_item in str(files_item):
return True
return False
return any(skip_item in str(files_item) for files_item, skip_item in itertools.product(files, cfg['SKIP_LIST']))


def millis_to_string(millis):
Expand All @@ -245,7 +240,7 @@ def millis_to_string(millis):
hours = (millis / (1000 * 60 * 60)) % 24
return "%02d:%02d:%02d" % (hours, minutes, seconds)
except Exception:
log.exception("Exception occurred converting %d millis to readable string: ", millis)
log.exception(f"Exception occurred converting {millis} millis to readable string: ")
return "%d milliseconds" % millis


Expand All @@ -255,25 +250,22 @@ def bytes_to_string(size_bytes):
"""
try:
if size_bytes == 1:
# because I really hate unnecessary plurals
return "1 byte"

suffixes_table = [('bytes', 0), ('KB', 0), ('MB', 1), ('GB', 2), ('TB', 2), ('PB', 2)]

num = float(size_bytes)
for suffix, precision in suffixes_table:
if num < 1024.0:
break
num /= 1024.0

if precision == 0:
formatted_size = "%d" % num
else:
formatted_size = str(round(num, ndigits=precision))

return "%s %s" % (formatted_size, suffix)
return f"{formatted_size} {suffix}"
except Exception:
log.exception("Exception occurred converting %d bytes to readable string: ", size_bytes)
log.exception(f"Exception occurred converting {size_bytes} bytes to readable string: ")

return "%d bytes" % size_bytes


Expand All @@ -284,14 +276,14 @@ def kbps_to_string(size_kbps):
else:
return "{:.2f} Mbps".format(size_kbps / 1024.)
except Exception:
log.exception("Exception occurred converting %d Kbps to readable string: ", size_kbps)
log.exception(f"Exception occurred converting {size_kbps} Kbps to readable string: ")
return "%d Bbps" % size_kbps


def build_tabulated(parts, items):
headers = ['choice', 'score', 'id', 'file', 'size', 'duration', 'bitrate', 'resolution',
'codecs']
if cfg.FIND_DUPLICATE_FILEPATHS_ONLY:
if cfg['FIND_DUPLICATE_FILEPATHS_ONLY']:
headers.remove('score')

part_data = []
Expand Down Expand Up @@ -350,7 +342,7 @@ def build_tabulated(parts, items):
process_later = {}
# process sections
print("Finding dupes...")
for section in cfg.PLEX_LIBRARIES:
for section in cfg['PLEX_LIBRARIES']:
dupes = get_dupes(section)
print("Found %d dupes for section %r" % (len(dupes), section))
# loop returned duplicates
Expand All @@ -368,7 +360,7 @@ def build_tabulated(parts, items):
parts = {}
for part in item.media:
part_info = get_media_info(part)
if not cfg.FIND_DUPLICATE_FILEPATHS_ONLY:
if not cfg['FIND_DUPLICATE_FILEPATHS_ONLY']:
part_info['score'] = get_score(part_info)
part_info['show_key'] = item.key
log.info("ID: %r - Score: %s - Meta:\n%r", part.id, part_info.get('score', 'N/A'),
Expand All @@ -379,15 +371,15 @@ def build_tabulated(parts, items):
# process processed items
time.sleep(5)
for item, parts in process_later.items():
if not cfg.AUTO_DELETE:
if not cfg['AUTO_DELETE']:
partz = {}
# manual delete
print("\nWhich media item do you wish to keep for %r ?\n" % item)

sort_key = None
sort_order = None

if cfg.FIND_DUPLICATE_FILEPATHS_ONLY:
if cfg['FIND_DUPLICATE_FILEPATHS_ONLY']:
sort_key = "id"
sort_order_reverse = False
else:
Expand All @@ -396,11 +388,8 @@ def build_tabulated(parts, items):

media_items = {}
best_item = None
pos = 0

for media_id, part_info in collections.OrderedDict(
sorted(parts.items(), key=lambda x: x[1][sort_key], reverse=sort_order_reverse)).items():
pos += 1
for pos, (media_id, part_info) in enumerate(collections.OrderedDict(
sorted(parts.items(), key=lambda x: x[1][sort_key], reverse=sort_order_reverse)).items(), start=1):
if pos == 1:
best_item = part_info
media_items[pos] = media_id
Expand Down Expand Up @@ -434,7 +423,7 @@ def build_tabulated(parts, items):
keep_score = 0
keep_id = None

if cfg.FIND_DUPLICATE_FILEPATHS_ONLY:
if cfg['FIND_DUPLICATE_FILEPATHS_ONLY']:
# select lowest id to keep
for media_id, part_info in parts.items():
if keep_score == 0 and keep_id is None:
Expand Down
5 changes: 2 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
tabulate~=0.8.2
requests~=2.18.4
attrdict~=2.0.0
PlexAPI~=3.0.6
requests==2.28.1
PlexAPI==4.13.0

0 comments on commit 61e63c8

Please sign in to comment.