[Help] Media Center Master plug-ins

I've tried using these plug-ins from MCM, but they aren't working like they are supposed to.

It's supposed to be open

 

will pull only data from local xml docs, and local photos, audio, etc.

 

running this and swapping to plex scanner with personal media to add/delete shows

 

I'll update again as soon as I find time, but feel free to help if you want.

 
UPDATED: mcm agent 02/05/2015
should be working now, but only checked with tv shows, and mcm series scanner.

 

MCM agent

[spoiler]


#
#  Media Center Master Plex Agent (movies and T.V.)
#
#     Provides Plex with local movie and T.V. metadata as generated by the
#     Media Center Master standalone client (or any other movie.xml/series.xml-
#     compatible metadata).
#
#     Supports multiple artwork files, season correction, show matching by
#     series IDs in local metadata, DVD/BluRay/HD DVD, and just about every
#     metadata field that Plex provides.
#
#  All original code by copyrighted by Media Center Master:
#      (C) 2013-2015 Media Center Master, Inc.
#      All rights reserved.
#      http://www.MediaCenterMaster.com/
#
#  Version 1.00     2013-02-11
#  Version 1.01     2013-07-16
# version consistency with scanner (no code change)
#  Version 1.2      2015-02-01     compatible with non-Windows OS
#
 
import os
import re
import hashlib
import unicodedata
import urllib
 
 
def Start():
pass
 
 
class MediaCenterMaster(Agent.Movies):
name = 'Media Center Master'
primary_provider = True
languages = [Locale.Language.English]
 
def search(self, results, media, lang, manual=False):
try:
Log('Identifying \"" + media.title + '\"...")
results.Append(MetadataSearchResult(
'',
"id=media.items[0].parts[0].hash, year=media.year, name=media.title, "
"lang=lang, score=100"))
Log("      ... done")
except Exception, e:
Log("      *** FAILURE/error ***")
Log("      *** " + str(type(e)) + " ***")
Log("      *** " + str(e) + " ***")
pass
return
 
def update(self, metadata, media, lang, force=False):
VideoFilename = ""
 
try:
VideoFilename = media.items[0].parts[0].file.decode('utf-8')
except Exception, e:
Log("      *** FAILURE/error ***")
Log("      *** " + str(type(e)) + " ***")
Log("      *** " + str(e) + " ***")
return
 
Log("Scraping XML metadata for: " + VideoFilename)
 
MoviePath = os.path.dirname(VideoFilename)
 
if self.discSubstructure(MoviePath):
MoviePath = os.path.abspath(os.path.join(MoviePath, os.pardir))
 
XMLFilename = os.path.join(MoviePath, "movie.xml")
 
if not os.path.exists(XMLFilename):
Log("      metadata missing, skipping (fetch with Media Center Master)")
return
 
XMLData = XML.ElementFromString(Core.storage.load(XMLFilename))
 
try:
Log("      " + self.getTextFromXML(
'',
XMLData, 'LocalTitle') + (
+ self.getTextFromXML(XMLData, 'ProductionYear') + 'productionYear'))
 
metadata.title = self.getTextFromXML(XMLData, 'LocalTitle')
metadata.original_title = self.getTextFromXML(XMLData, 'OriginalTitle')
try metadata.sort_title = self.getTextFromXML(XMLData, 'SortTitle')
# 'metadata.sort_title' may not exist
except pass
metadata.summary = self.getTextFromXML(XMLData, 'Description')
metadata.tagline = self.getTextFromXML(XMLData, 'Tagline')
metadata.content_rating = self.getTextFromXML(XMLData, 'MPAARating')
metadata.studio = self.getTextFromXML(XMLData, 'Studios/Studio')
# Plex only holds one studio
try metadata.year = int(self.getTextFromXML(XMLData, 'ProductionYear'))
except pass
try metadata.rating = float(self.getTextFromXML(XMLData, 'IMDBrating'))
except pass
try metadata.duration = int(self.getTextFromXML(
'',
XMLData, 'RunningTime')) * 1000 * 60
# convert minutes to milliseconds
except pass
 
try temp_country = self.getTextFromXML(XMLData, 'Country')
if temp_country
not
in metadata.countries, metadata.countries.add(temp_country)
except pass
 
try
temp_directors = self.getTextFromXML(XMLData, 'Director')
if temp_directors
not
in metadata.directors, metadata.directors.add(temp_directors)
except pass
 
try
temp_writers = self.getTextFromXML(XMLData, 'WritersList').split("|")
for w in temp_writers
if w not in metadata.writers, metadata.writers.add(w)
except pass
 
try
temp_genres = self.getTagsFromXML(XMLData, 'Genres/Genre')
metadata.genres.clear()
for g in temp_genres
if g not in metadata.genres, metadata.genres.add(g.text)
except pass
 
try
if os.path.exists(os.path.join(MoviePath, "folder.jpg"))
poster_data = Core.storage.load(os.path.join(MoviePath, "folder.jpg"))
poster_name = hashlib.md5(poster_data).hexdigest()
if poster_name not in metadata.posters
metadata.posters[poster_name] = Proxy.Media(poster_data)
except pass
 
try
for i in range(0, 99)
sBackdropName = ""
if i == 0
sBackdropName = os.path.join(MoviePath, "backdrop.jpg")
else
sBackdropName = os.path.join(MoviePath, "backdrop" + str(i) + ".jpg")
if os.path.exists(sBackdropName)
art_data = Core.storage.load(sBackdropName)
art_name = hashlib.md5(art_data).hexdigest()
if art_name not in metadata.art
metadata.art[art_name] = Proxy.Media(art_data)
except pass
 
except Exception, e
Log("      *** FAILURE/error ***")
Log("      *** " + str(type(e)) + " ***")
Log("      *** " + str(e) + " ***")
pass
 
Log("      ... done!")
return
 
def getTagsFromXML(self, XMLData, tag)
try return XMLData.xpath('//Title/' + tag)
except pass
return {}
 
def getTextFromXML(self, XMLData, tag)
try return self.getTagsFromXML(XMLData, tag)[0].text
except pass
return ""
 
def discSubstructure(self, fullPath)
fullPath = fullPath.upper().replace("/", "\\")
if fullPath.endswith("\\VIDEO_TS")
return True
if fullPath.endswith("\\BDMV")
return True
if fullPath.endswith("\\HVDVD_TS")
return True
return False
 
class MediaCenterMaster(Agent.TV_Shows)
name = 'Media Center Master'
primary_provider = True
languages = [Locale.Language.English]
 
def search(self, results, media, lang, manual=False)
try
Log("Identifying \"" + media.title + "\"...")
results.Append(MetadataSearchResult(
'',
id="MCM_TV_X_" + str(media.id), year=media.year,
name=media.title, lang=lang, score=50))
 
try
# Let's try to get a thetvdb, tvrage, or imdb ID to uniquely identify
# this series (helps prevent series from breaking up so much).
 
EpisodeFilename = ""
 
try EpisodeFilename = urllib.parse.unquote(media.filename)  # Python v3
except EpisodeFilename = urllib.unquote(media.filename)       # Python v2
 
ShowPath = os.path.abspath(os.path.join(
'',
"os.path.dirname(EpisodeFilename),"
"os.pardir)"))
ShowMetadata = os.path.join(ShowPath, "series.xml")
ShowXML = XML.ElementFromString(Core.storage.load(ShowMetadata))
iSeriesID = int(self.getTextFromShowXML(ShowXML, "id"))
 
if iSeriesID > 0
results.Append(MetadataSearchResult(
'',
id="MCM_TV_A_" + str(iSeriesID), year=media.year, name=media.title,
lang=lang, score=100))
else
iSeriesID = int(self.getTextFromShowXML(ShowXML, "TVRageID"))
 
if iSeriesID > 0:
results.Append(MetadataSearchResult(
'',
id="MCM_TV_B_" + str(iSeriesID), year=media.year, name=media.title,
lang=lang, score=100))
else
sSeriesID = self.getTextFromShowXML(ShowXML, "IMDbId")
 
if len(sSeriesID) > 8:
results.Append(MetadataSearchResult(
'',
id="MCM_TV_C_" + sSeriesID, year=media.year, name=media.title,
lang=lang, score=100))
 
except pass
 
Log("... done")
except Exception, e:
Log("*** FAILURE/error ***")
Log("*** " + str(type(e)) + " ***")
Log("*** " + str(e) + " ***")
pass
return
 
def update(self, metadata, media, lang, force=False):
try:
bHasShowMeta = False
ShowXML = None
 
for s in media.seasons:
# Log('Searching through season: %s', s)
 
metadata.seasons[s].index = int(s)
# Sanity check, this doesn't really do much
SeasonPath = ""
 
try:
FirstEpisodeFile = media.seasons[s].episodes[e].items[0].parts[0].file
SeasonPath = os.path.dirname(FirstEpisodeFile)
 
if os.path.exists(os.path.join(SeasonPath, "folder.jpg")):
poster_data = Core.storage.load(os.path.join(SeasonPath, "folder.jpg"))
poster_name = hashlib.md5(poster_data).hexdigest()
if poster_name not in metadata.seasons[s].posters:
metadata.seasons[s].posters[poster_name] = Proxy.Media(poster_data)
except pass
 
for e in media.seasons[s].episodes:
episode = metadata.seasons[s].episodes[e]
try EpisodeFile = media.seasons[s].episodes[e].items[0].parts[0].file
 
SeasonPath = os.path.dirname(EpisodeFile)
if bHasShowMeta is False:
ShowPath = os.path.abspath(os.path.join(
os.path.dirname(EpisodeFile), os.pardir))
ShowMetadata = os.path.join(ShowPath, "series.xml")
Log("    show metadata: " + ShowMetadata)
ShowXML = XML.ElementFromString(Core.storage.load(ShowMetadata))
bHasShowMeta = True
metadata.title = self.getTextFromShowXML(ShowXML, "SeriesName")
metadata.summary = self.getTextFromShowXML(ShowXML, "Overview")
try metadata.duration = int(self.getTextFromShowXML(
ShowXML, "Runtime")) * 1000 * 60  # convert minutes to milliseconds
except pass
try metadata.studio = self.getTextFromShowXML(ShowXML, "Network")
metadata.content_rating = self.getTextFromShowXML(
ShowXML, "ContentRating")
except pass
try metadata.rating = float(self.getTextFromShowXML(ShowXML, "Rating"))
except pass
 
try metadata.originally_available_at = Datetime.ParseDate(
self.getTextFromShowXML(ShowXML, "FirstAired")).date()
except pass
 
try temp_genres = self.getTextFromShowXML(ShowXML, "Genre").split("|")
metadata.genres.clear()
for g in temp_genres
if len(g) > 0
if g not in metadata.genres
metadata.genres.add(g)
except pass
 
try
if os.path.exists(os.path.join(ShowPath, "folder.jpg"))
poster_data = Core.storage.load(os.path.join(ShowPath, "folder.jpg"))
poster_name = hashlib.md5(poster_data).hexdigest()
if poster_name not in metadata.posters
metadata.posters[poster_name] = Proxy.Media(poster_data)
except pass
 
try
if os.path.exists(os.path.join(ShowPath, "banner.jpg"))
banner_data = Core.storage.load(os.path.join(ShowPath, "banner.jpg"))
banner_name = hashlib.md5(banner_data).hexdigest()
if banner_name not in metadata.banners
metadata.banners[banner_name] = Proxy.Media(banner_data)
except pass
 
try
for i in range(0, 99):
sBackdropName = ""
if i == 0:
sBackdropName = os.path.join(ShowPath, "backdrop.jpg")
else:
sBackdropName = os.path.join(ShowPath, "backdrop" + str(i) + ".jpg")
if os.path.exists(sBackdropName):
backdrop_data = Core.storage.load(sBackdropName)
backdrop_name = hashlib.md5(backdrop_data).hexdigest()
if backdrop_name not in metadata.art:
metadata.art[backdrop_name] = Proxy.Media(backdrop_data)
except pass
 
Log("    video: " + EpisodeFile)
 
(EpisodeFileNoExt, EpisodeFileExt) = os.path.splitext(EpisodeFile)
EpisodeMetadata = os.path.join(
'',
os.path.dirname(EpisodeFile), "metadata",
os.path.basename(EpisodeFileNoExt) + ".xml")
Log("        metadata: " + EpisodeMetadata)
 
if not os.path.exists(EpisodeMetadata):
Log("metadata file missing, skipping (fetch with Media Center Master)")
continue
 
EpisodeXML = XML.ElementFromString(Core.storage.load(EpisodeMetadata))
 
Log("episode: " + self.getTextFromEpisodeXML(EpisodeXML, "EpisodeName"))
 
# Try to correct the season number
try:
MCMSeason = -1
 
sSeasonNumber = self.getTextFromEpisodeXML(EpisodeXML, "SeasonNumber")
 
if sSeasonNumber != "":
MCMSeason = int(sSeasonNumber)
else:
SeasonFolderOnly = os.path.basename(os.path.normpath(SeasonPath))
MCMSeason = -1
match = re.match(
'',
'.*?(?P[0-9]+)$',
SeasonFolderOnly, re.IGNORECASE)
if match:
MCMSeason = int(match.group('season'))
 
if MCMSeason > -1:
metadata.seasons[s].index = MCMSeason
try episode.season = MCMSeason
# http://dev.plexapp.com/docs/api/objectkit.html#EpisodeObject
except pass
 
except pass
 
episode.title = self.getTextFromEpisodeXML(EpisodeXML, "EpisodeName")
episode.summary = self.getTextFromEpisodeXML(EpisodeXML, "Overview")
# episode.duration - intentionally blank (Plex will fill)
 
try episode.rating = float(self.getTextFromEpisodeXML(
EpisodeXML, "Rating"))
except pass
 
try episode.absolute_index = int(self.getTextFromEpisodeXML(
EpisodeXML, "absolute_number"))
except pass
 
try episode.originally_available_at = Datetime.ParseDate(
'',
self.getTextFromEpisodeXML(EpisodeXML, "FirstAired")).date()
except pass
 
try
temp_writers = self.getTextFromEpisodeXML(
'',
EpisodeXML, "Writer").split(", ")
episode.writers.clear()
for w in temp_writers:
if len(w) > 0:
if w not in episode.writers:
episode.writers.add(w)
except pass
 
try
episode.directors.clear()
episode.directors.add(self.getTextFromEpisodeXML(
'',
EpisodeXML, "Director"))
except pass
 
try
temp_guests = self.getTextFromEpisodeXML(
'',
EpisodeXML, "GuestStars").split("|")
episode.guest_stars.clear()
for g in temp_guests:
if len(g) > 0:
if g not in episode.guest_stars:
episode.guest_stars.add(g)
except pass
 
try
thumb_file = os.path.join(
'',
os.path.dirname(EpisodeFile), "metadata",
self.getTextFromEpisodeXML(EpisodeXML, "filename")
)
if thumb_file.upper().endswith(".JPG"):
if os.path.exists(thumb_file):
thumb_data = Core.storage.load(thumb_file)
thumb_name = hashlib.md5(thumb_data).hexdigest()
if thumb_name not in episode.thumbs:
episode.thumbs[thumb_name] = Proxy.Media(thumb_data)
except pass
 
except Exception, e:
Log("          *** EPISODE FAILURE/error ***")
Log("          *** " + str(type(e)) + " ***")
Log("          *** " + str(e) + " ***")
pass
 
except Exception, e:
Log("      *** FAILURE/error ***")
Log("      *** " + str(type(e)) + " ***")
Log("      *** " + str(e) + " ***")
pass
 
Log("    ... done!")
return
 
def getTextFromShowXML(self, XMLData, tag):
try return XMLData.xpath('//Series/' + tag)[0].text
except pass
return ""
 
def getTextFromEpisodeXML(self, XMLData, tag):
try return XMLData.xpath('//Item/' + tag)[0].text
except pass
return ""
 

[/spoiler]

 

MCM Custom Video Files

[spoiler]

#!/usr/bin/python2.4
# Modified by Cyrille Lefevre to remove french tags

import Filter
import os.path, re, datetime, titlecase, unicodedata

video_exts = [‘3g2’, ‘3gp’, ‘asf’, ‘asx’, ‘avc’, ‘avi’, ‘avs’, ‘bin’, ‘bivx’, ‘bup’, ‘divx’, ‘dv’, ‘dvr-ms’, ‘evo’, ‘fli’, ‘flv’, ‘ifo’, ‘img’,
‘iso’, ‘m2t’, ‘m2ts’, ‘m2v’, ‘m4v’, ‘mkv’, ‘mov’, ‘mp4’, ‘mpeg’, ‘mpg’, ‘mts’, ‘nrg’, ‘nsv’, ‘nuv’, ‘ogm’, ‘ogv’,
‘pva’, ‘qt’, ‘rm’, ‘rmvb’, ‘sdp’, ‘svq3’, ‘strm’, ‘ts’, ‘ty’, ‘vdr’, ‘viv’, ‘vob’, ‘vp3’, ‘wmv’, ‘wpl’, ‘wtv’, ‘xsp’, ‘xvid’, ‘webm’]

ignore_files = [’[-._ ]sample’, ‘sample[-._ ]’, ‘-trailer.’]
ignore_dirs = [‘extras?’, ‘!?samples?’, ‘bonus’, ‘.bonus disc.’]
ignore_suffixes = [’.dvdmedia’]

source_dict = {‘bluray’:[‘bdrc’,‘bdrip’,‘bluray’,‘bd’,‘brrip’,‘hdrip’,‘hddvd’,‘hddvdrip’],‘cam’:[‘cam’],‘dvd’:[‘ddc’,‘dvdrip’,‘dvd’,‘r1’,‘r3’],‘retail’:[‘retail’],
‘dtv’:[‘dsr’,‘dsrip’,‘hdtv’,‘pdtv’,‘ppv’],‘stv’:[‘stv’,‘tvrip’,‘tv’],‘r5’:[‘r5’],‘screener’:[‘bdscr’,‘dvdscr’,‘dvdscreener’,‘scr’,‘screener’],
‘svcd’:[‘svcd’],‘vcd’:[‘vcd’],‘telecine’:[‘tc’,‘telecine’],‘telesync’:[‘ts’,‘telesync’],‘workprint’:[‘wp’,‘workprint’],‘vhs’:[‘vhs’,‘vhsrip’]}
source =
for d in source_dict:
for s in source_dict[d]:
if source != ‘’:
source.append(s)

audio = [’([^0-9])5.1*ch(.)’,’([^0-9])5.1([^0-9]?)’,’([^0-9])7.1*ch(.)’,’([^0-9])7.1([^0-9])’]
subs = [‘multi’,‘multisubs’]
misc = [‘cd1’,‘cd2’,‘1cd’,‘2cd’,‘custom’,‘internal’,‘repack’,‘read.nfo’,‘readnfo’,‘nfofix’,‘proper’,‘rerip’,‘dubbed’,‘subbed’,‘extended’,‘unrated’,‘xxx’,‘nfo’,‘dvxa’,‘lte’]
french = [‘french’,‘truefrench’,‘subfrench’,‘frenchedit’,‘vf’,‘vvf’,‘vo’,‘vostfr’,‘vost’,‘rip’]
format = [‘ac3’,‘dc’,‘divx’,‘fragment’,‘limited’,‘ogg’,‘ogm’,‘ntsc’,‘pal’,‘ps3avchd’,‘r1’,‘r3’,‘r5’,‘720i’,‘720p’,‘1080i’,‘1080p’,‘x264’,‘xvid’,‘vorbis’,‘aac’,‘dts’,‘fs’,‘ws’,‘1920x1080’,‘1280x720’,‘h264’]
edition = [‘dc’,‘se’] # dc = directors cut, se = special edition
yearRx = ‘([([.-])([1-2][0-9]{3})([.-)],+])’

Cleanup folder / filenames

def CleanName(name, noYear=False):

orig = name

Make sure we pre-compose.

name = unicodedata.normalize(‘NFKC’, name.decode(‘utf-8’))
name = name.lower()

grab the year, if there is one. set ourselves up to ignore everything after the year later on.

year = None
if noYear == False:
yearMatch = re.search(yearRx, name)
if yearMatch:
yearStr = yearMatch.group(2)
yearInt = int(yearStr)
if yearInt > 1900 and yearInt < (datetime.date.today().year + 1):
year = int(yearStr)
name = name.replace(yearMatch.group(1) + yearStr + yearMatch.group(3), ’ yearBreak ')

Take out things in brackets. (sub acts weird here, so we have to do it a few times)

done = False
while done == False:
(name, count) = re.subn(r’[[^]]+]’, ‘’, name, re.IGNORECASE)
if count == 0:
done = True

Take out bogus suffixes.

for suffix in ignore_suffixes:
rx = re.compile(suffix + ‘$’, re.IGNORECASE)
name = rx.sub(’’, name)

Take out audio specs, after suffixing with space to simplify rx.

name = name + ’ ’
for s in audio:
rx = re.compile(s, re.IGNORECASE)
name = rx.sub(’ ', name)

Now tokenize.

tokens = re.split(’([^ -_.()+]+)’, name)

Process tokens.

newTokens =
for t in tokens:
t = t.strip()
if not re.match(’[.-()+]+’, t) and len(t) > 0:
#if t not in (’.’, ‘-’, '
’, ‘(’, ‘)’) and len(t) > 0:
newTokens.append(t)

Now build a bitmap of good and bad tokens.

tokenBitmap =

garbage = subs
garbage.extend(misc)
garbage.extend(french)
garbage.extend(format)
garbage.extend(edition)
garbage.extend(source)
garbage.extend(video_exts)
garbage = set(garbage)

for t in newTokens:
if t.lower() in garbage:
tokenBitmap.append(False)
else:
tokenBitmap.append(True)

Now strip out the garbage, with one heuristic; if we encounter 2+ BADs after encountering

a GOOD, take out the rest (even if they aren’t BAD). Special case for director’s cut.

numGood = 0
numBad = 0

finalTokens =

for i in range(len(tokenBitmap)):
good = tokenBitmap*

# If we've only got one or two tokens, don't whack any, they might be part of
# the actual name (e.g. "Internal Affairs" "XXX 2")
#
if len(tokenBitmap) <= 2:
  good = True

if good and numBad < 1:
  if newTokens* == '*yearBreak*':
    #if we have a year, we can ignore everything after this.
    break
  else:
    finalTokens.append(newTokens*)
elif not good and newTokens*.lower() == 'dc':
  finalTokens.append("(Director's cut)")

if good == True:
  numGood += 1
else:
  numBad += 1

If we took all the tokens out, use the first one, otherwise we’ll end up with no name at all.

if len(finalTokens) == 0 and len(newTokens) > 0:
finalTokens.append(newTokens[0])

#print “CLEANED [%s] => [%s]” % (orig, u’ '.join(finalTokens))
#print "TOKENS: ", newTokens
#print "BITMAP: ", tokenBitmap
#print "FINAL: ", finalTokens

cleanedName = ’ '.join(finalTokens)
cleanedName = cleanedName.encode(‘utf-8’)
return (titlecase.titlecase(cleanedName), year)
 

[/spoiler]

1 Like

UPDATED: mcm agent 02/05/2015

used pep8online.com to check code

used resources on stackoverflow.com to help understand the code and rules

credit goes mainly to author for the framework

went through and updated to follow python rules, and organization

should be working now, but only checked with tv shows, and mcm series scanner.

will pull only data from local xml docs, and local photos, audio, etc.

running this and swapping to plex scanner with personal media to add/delete shows

I'll update again as soon as I find time, but feel free to help if you want.

How do I use this info? Bit of a newb here.  Can you point me in the direction to download?

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.