rewrote moviesort.py into two classes MovieHandler and MovieIdentify

This commit is contained in:
simon 2021-05-09 21:08:58 +07:00
parent a363d30b37
commit eb80ccd841
4 changed files with 217 additions and 238 deletions

View File

@ -57,7 +57,8 @@ Duplicate the config.sample.json file to a file named *config.json* and set the
* `tvpath`: Root folder where the organized tv episodes will go. * `tvpath`: Root folder where the organized tv episodes will go.
* `ext`: A list of valid media file extensions to easily filter out none media related files. * `ext`: A list of valid media file extensions to easily filter out none media related files.
* `log_path`: Path to a folder to output all renaming done to keep track and check for any errors and safe csv files. * `log_path`: Path to a folder to output all renaming done to keep track and check for any errors and safe csv files.
* `movie_db_api`: Register and get your themoviedb.com **API Key (v3 auth)** acces from [here](https://www.themoviedb.org/settings/api). * `movie_db_api`: Register and get your themoviedb.com **API Key (v3 auth)** acces from [here](https://www.themoviedb.org/settings/api).
* `min_file_size`: Minimal filesize to be considered a relevant media file in bytes.
#### Emby integration #### Emby integration
* `emby_url`: url where your emby instance is reachable * `emby_url`: url where your emby instance is reachable

View File

@ -7,7 +7,8 @@
"tvpath": "/media/movie/movie/tv", "tvpath": "/media/movie/movie/tv",
"ext": ["mp4", "mkv", "avi", "m4v"], "ext": ["mp4", "mkv", "avi", "m4v"],
"log_folder": "/home/user/logs/media_organize", "log_folder": "/home/user/logs/media_organize",
"movie_db_api": "aaaabbbbccccdddd1111222233333444" "movie_db_api": "aaaabbbbccccdddd1111222233333444",
"min_file_size": 50000000
}, },
"emby": { "emby": {
"emby_url": "http://media.local:8096/emby", "emby_url": "http://media.local:8096/emby",

View File

@ -34,10 +34,9 @@ def get_config():
def get_pending_all(config): def get_pending_all(config):
""" figure out what needs to be done """ """ figure out what needs to be done """
movie_downpath = config['media']['movie_downpath']
tv_downpath = config['media']['tv_downpath'] tv_downpath = config['media']['tv_downpath']
# call subfunction to collect pending # call subfunction to collect pending
pending_movie = moviesort.get_pending(movie_downpath) pending_movie = moviesort.MovieHandler().pending
pending_tv = tvsort.get_pending(tv_downpath) pending_tv = tvsort.get_pending(tv_downpath)
pending_trailer = len(trailers.get_pending(config)) pending_trailer = len(trailers.get_pending(config))
pending_movie_fix = len(id_fix.get_pending(config)) pending_movie_fix = len(id_fix.get_pending(config))
@ -91,13 +90,13 @@ def print_menu(stdscr, current_row_idx, menu, pending):
def sel_handler(menu_item, config): def sel_handler(menu_item, config):
""" lunch scripts from here based on selection """ """ lunch scripts from here based on selection """
if menu_item == 'All': if menu_item == 'All':
moviesort.main(config) moviesort.main()
tvsort.main(config, tvsort_id) tvsort.main(config, tvsort_id)
db_export.main(config) db_export.main(config)
trailers.main(config) trailers.main(config)
id_fix.main(config) id_fix.main(config)
elif menu_item == 'Movies': elif menu_item == 'Movies':
moviesort.main(config) moviesort.main()
elif menu_item == 'TV shows': elif menu_item == 'TV shows':
tvsort.main(config, tvsort_id) tvsort.main(config, tvsort_id)
elif menu_item == 'DB export': elif menu_item == 'DB export':

View File

@ -3,261 +3,239 @@
import logging import logging
import os import os
import re import re
import requests
import subprocess import subprocess
from time import sleep
import requests
from interface import get_config
def get_pending(movie_downpath): class MovieHandler():
""" """ handler for moving files around """
return how many movies are pending
return 0 when nothing to do
"""
pending = len(os.listdir(movie_downpath))
return pending
CONFIG = get_config()
def move_to_sort(movie_downpath, sortpath, ext): def __init__(self):
""" moves movies to sortpath """ """ check for pending movie files """
for dirpath, _, filenames in os.walk(movie_downpath): self.pending = self.get_pending()
for filename in filenames:
path = os.path.join(dirpath, filename)
_, extension = os.path.splitext(path)
extension = extension.lstrip('.').lower()
f_size = os.stat(path).st_size
# TODO: set f_size in config.json
if extension in ext and 'sample' not in filename and f_size > 50000000:
move_to = os.path.join(sortpath, filename)
os.rename(path, move_to)
pending = os.listdir(sortpath)
return pending
def get_pending(self):
"""
return how many movies are pending
return 0 when nothing to do
"""
movie_downpath = self.CONFIG['media']['movie_downpath']
pending = len(os.listdir(movie_downpath))
return pending
def split_filename(filename): def move_to_sort(self):
""" split the filename into moviename, year, file_ext """ """ moving files from movie_downpath to sortpath """
print(filename) # read out config
file_ext = os.path.splitext(filename)[1] sortpath = self.CONFIG['media']['sortpath']
year_id_pattern = re.compile(r'\d{4}') movie_downpath = self.CONFIG['media']['movie_downpath']
year_list = year_id_pattern.findall(filename) min_file_size = self.CONFIG['media']['min_file_size']
# remove clear false ext = self.CONFIG['media']['ext']
for year in year_list: for dirpath, _, filenames in os.walk(movie_downpath):
if year == '1080': for filename in filenames:
# there were no movies back in 1080 path = os.path.join(dirpath, filename)
year_list.remove(year) _, extension = os.path.splitext(path)
file_split = filename.split(year) extension = extension.lstrip('.').lower()
if len(file_split[0]) == 0: f_size = os.stat(path).st_size
year_list.remove(year) if (extension in ext and
if len(year_list) != 1: 'sample' not in filename and
print('year extraction failed for: ' + filename) f_size > min_file_size):
year = input('whats the year?\n') move_to = os.path.join(sortpath, filename)
else: os.rename(path, move_to)
year = year_list[0] pending = os.listdir(sortpath)
moviename = filename.split(year)[0].rstrip('.') return pending
return moviename, year, file_ext
def rename_files(self, identified):
def get_results(movie_db_api, moviename, year = None): """ apply the identified filenames and rename """
""" return results from api call """ sortpath = self.CONFIG['media']['sortpath']
moviename_encoded = moviename.lower().replace("its", "")\ renamed = []
.replace(" ", "%20").replace(".", "%20").replace("'", "%20") for movie in identified:
# call api with year passed or not old_file = os.path.join(sortpath, movie.filename)
if year: new_file = os.path.join(sortpath, movie.new_filename)
request = requests.get('https://api.themoviedb.org/3/search/movie?api_key=' +
movie_db_api + '&query=' + moviename_encoded + '&year=' +
year + '&language=en-US&include_adult=false').json()
else:
request = requests.get('https://api.themoviedb.org/3/search/movie?api_key=' +
movie_db_api + '&query=' + moviename_encoded +
'&language=en-US&include_adult=false').json()
try:
results = len(request['results'])
except KeyError:
results = 0
if results == 0:
# try again without last word of string
moviename_encoded = '%20'.join(moviename_encoded.split('%20')[0:-1])
request = requests.get('https://api.themoviedb.org/3/search/movie?api_key=' +
movie_db_api + '&query=' + moviename_encoded + '&year=' +
year + '&language=en-US&include_adult=false').json()
results = len(request['results'])
# return
return results, request
def search_for(movie_db_api, moviename, filename, year = None):
"""
takes the moviename and year from the filename
and returns clean name and year
"""
# get results from API
results, request = get_results(movie_db_api, moviename, year)
# clear when only one result
if results == 1:
selection = 0
# select for more than 0 result
elif results > 0:
short_list = []
long_list = []
counter = 0
for item in request['results']:
movie_title = item['title']
movie_date = item['release_date']
movie_year = movie_date.split('-')[0]
movie_desc = item['overview']
short_list_str = f'[{str(counter)}] {movie_title} - {movie_year}'
long_list_str = f'{short_list}\n{movie_desc}'
short_list.append(short_list_str)
long_list.append(long_list_str)
counter = counter + 1
short_list.append('[?] show more')
# print short menu
print('\nfilename: ' + filename)
for line in short_list:
print(line)
selection = input('select input: ')
# print long menu
if selection == '?':
for line in long_list:
print(line)
selection = input('select input: ')
# no result, try again
else:
return None, None
# get and return title and year
movie_title = request['results'][int(selection)]['title']
movie_year = request['results'][int(selection)]['release_date'].split('-')[0]
return movie_title, movie_year
def movie_rename(sortpath, movie_db_api):
""" renames the movie file """
to_rename = os.listdir(sortpath)
# os.chdir(sortpath)
for filename in to_rename:
# split up
moviename, year, file_ext = split_filename(filename)
# try to figure things out
while True:
# first try
movie_title, movie_year = search_for(movie_db_api, moviename, filename, year, )
if movie_title and movie_year:
break
# second try with - 1 year
movie_title, movie_year = search_for(movie_db_api, moviename, filename, str(int(year) - 1), )
if movie_title and movie_year:
break
# third try with + 1 year
movie_title, movie_year = search_for(movie_db_api, moviename, filename, str(int(year) + 1), )
if movie_title and movie_year:
break
# last try without year
movie_title, movie_year = search_for(movie_db_api, moviename, filename)
if movie_title and movie_year:
break
# manual overwrite
print(filename + '\nNo result found, search again with:')
moviename = input('movie name: ')
year = input('year: ')
movie_title, movie_year = search_for(moviename, year, filename)
break
if not movie_title or not movie_year:
# last check
return False
else:
# clean invalid chars
movie_title = movie_title.replace('/', '-')
# do it
rename_to = movie_title + ' (' + movie_year + ')' + file_ext
old_file = os.path.join(sortpath, filename)
new_file = os.path.join(sortpath, rename_to)
os.rename(old_file, new_file) os.rename(old_file, new_file)
logging.info('movie:from [{}] to [{}]'.format(filename,rename_to)) logging.info(
return True 'movie:from [%s] to [%s]', movie.filename, movie.new_filename
)
renamed.append(movie.filename)
return renamed
def move_to_archive(self, identified):
def move_to_archive(sortpath, moviepath): """ move renamed filed to archive """
""" moves renamed movie to archive, sortpath = self.CONFIG['media']['sortpath']
returns list for further processing """ moviepath = self.CONFIG['media']['moviepath']
new_movies = [] # confirm
to_move = os.listdir(sortpath) print('\nrenamed:')
if to_move: for movie in identified:
print() print(f'from: {movie.filename} \nto: {movie.new_filename}\n')
for i in to_move: to_continue = input('\ncontinue? Y/n')
print(i) if to_continue == 'n':
_ = input('\ncontinue?') print('cancle...')
to_move = os.listdir(sortpath) return False
for movie in to_move: moved = []
movie_name = os.path.splitext(movie)[0] for movie in identified:
year_pattern = re.compile(r'(\()([0-9]{4})(\))') old_file = os.path.join(sortpath, movie.new_filename)
year = year_pattern.findall(movie_name)[0][1] new_folder = os.path.join(
old_file = os.path.join(sortpath, movie) moviepath, str(movie.year), movie.new_moviename
new_folder = os.path.join(moviepath, year, movie_name) )
new_file = os.path.join(new_folder, movie) new_file = os.path.join(new_folder, movie.new_filename)
try: try:
os.makedirs(new_folder) os.makedirs(new_folder)
except FileExistsError: except FileExistsError:
print(f'{movie_name}\nalready exists in archive') print(f'{movie.new_filename}\nalready exists in archive')
double = input('[o]: overwrite, [s]: skip and ignore\n') double = input('[o]: overwrite, [s]: skip and ignore\n')
if double == 'o': if double == 'o':
subprocess.call(["trash", new_folder]) subprocess.call(["trash", new_folder])
os.makedirs(new_folder) os.makedirs(new_folder)
elif double == 's': elif double == 's':
continue continue
else:
pass
finally:
pass
os.rename(old_file, new_file) os.rename(old_file, new_file)
new_movies.append(movie_name) moved.append(movie.new_filename)
return new_movies return len(moved)
def cleanup(self, moved):
""" clean up movie_downpath and sortpath folder """
sortpath = self.CONFIG['media']['sortpath']
movie_downpath = self.CONFIG['media']['movie_downpath']
if moved:
# moved without errors
to_clean_list = os.listdir(movie_downpath)
for to_clean in to_clean_list:
to_trash = os.path.join(movie_downpath, to_clean)
subprocess.call(["trash", to_trash])
to_clean_list = os.listdir(sortpath)
for to_clean in to_clean_list:
to_trash = os.path.join(sortpath, to_clean)
subprocess.call(["trash", to_trash])
else:
# failed to rename
move_back = os.listdir(sortpath)
for movie_pending in move_back:
old_path = os.path.join(sortpath, movie_pending)
new_path = os.path.join(movie_downpath, movie_pending)
os.rename(old_path, new_path)
# clean up class MovieIdentify():
def cleanup(movie_downpath, sortpath, renamed): """ describes and identifies a single movie """
""" cleans up the movie_downpath folder """
if renamed: CONFIG = get_config()
# renamed without errors
to_clean_list = os.listdir(movie_downpath) def __init__(self, filename):
for to_clean in to_clean_list: """ parse filename """
to_trash = os.path.join(movie_downpath, to_clean) self.filename = filename
subprocess.call(["trash", to_trash]) self.moviename, self.year, self.file_ext = self.split_filename()
to_clean_list = os.listdir(sortpath) self.moviename_encoded = self.encode_moviename()
for to_clean in to_clean_list: self.new_moviename, self.new_filename = self.get_new_filename()
to_trash = os.path.join(sortpath, to_clean)
subprocess.call(["trash", to_trash]) def split_filename(self):
else: """ build raw values from filename """
# failed to rename file_ext = os.path.splitext(self.filename)[1]
move_back = os.listdir(sortpath) year_id_pattern = re.compile(r'\d{4}')
for movie in move_back: year_list = year_id_pattern.findall(self.filename)
old_path = os.path.join(sortpath, movie) # remove clear false
new_path = os.path.join(movie_downpath, movie) for year in year_list:
os.rename(old_path, new_path) if year == '1080':
# there were no movies back in 1080
year_list.remove(year)
file_split = self.filename.split(year)
if len(file_split[0]) == 0:
year_list.remove(year)
if len(year_list) != 1:
print('year extraction failed for:\n' + self.filename)
year = input('whats the year?\n')
else:
year = year_list[0]
moviename = self.filename.split(year)[0].rstrip('.')
return moviename, int(year), file_ext
def encode_moviename(self):
""" url encode and clean the moviename """
encoded = self.moviename.lower().replace(' ', '%20')
encoded = encoded.replace('.', '%20').replace("'", '%20')
return encoded
def get_results(self):
""" get all possible matches """
movie_db_api = self.CONFIG['media']['movie_db_api']
year_file = self.year
# try +/- one year
year_list = [year_file, year_file + 1, year_file - 1]
for year in year_list:
url = (
'https://api.themoviedb.org/3/search/movie?'
+ f'api_key={movie_db_api}&query={self.moviename_encoded}'
+ f'&year={year}&language=en-US&include_adult=false'
)
request = requests.get(url).json()
results = request['results']
# stop if found
if results:
break
return results
def pick_result(self, results):
""" select best possible match from list of results """
if len(results) == 1:
selection = 0
elif len(results) > 1:
short_list = []
long_list = []
counter = 0
for item in results:
nr = str(counter)
movie_title = item['title']
movie_date = item['release_date']
movie_year = movie_date.split('-')[0]
movie_desc = item['overview']
short_list_str = f'[{nr}] {movie_title} - {movie_year}'
long_list_str = f'{short_list_str}\n{movie_desc}'
short_list.append(short_list_str)
long_list.append(long_list_str)
counter = counter + 1
short_list.append('[?] show more')
# print short menu
print('\nfilename: ' + self.filename)
for line in short_list:
print(line)
selection = input('select input: ')
# print long menu
if selection == '?':
for line in long_list:
print(line)
selection = input('select input: ')
return int(selection)
def get_new_filename(self):
""" get the new filename """
results = self.get_results()
selection = self.pick_result(results)
result = results[selection]
# build new_filename
year_dedected = result['release_date'].split('-')[0]
name_dedected = result['title']
new_moviename = f'{name_dedected} ({year_dedected})'
new_filename = f'{new_moviename}{self.file_ext}'
return new_moviename, new_filename
def main(config): def main():
""" main to sort movies """ """ main to lunch moviesort """
# read config handler = MovieHandler()
movie_downpath = config['media']['movie_downpath']
sortpath = config['media']['sortpath']
moviepath = config['media']['moviepath']
ext = config['media']['ext']
movie_db_api = config['media']['movie_db_api']
# check if pending # check if pending
pending = get_pending(movie_downpath) if not handler.pending:
if not pending:
print('no movies to sort')
sleep(2)
return return
to_rename = handler.move_to_sort()
# move to sort folder # identify
pending = move_to_sort(movie_downpath, sortpath, ext) identified = []
if not pending: for i in to_rename:
print('no movies to sort') movie = MovieIdentify(i)
sleep(2) identified.append(movie)
return # rename and move
renamed = handler.rename_files(identified)
movie_renamed = movie_rename(sortpath, movie_db_api) if renamed:
if movie_renamed: moved = handler.move_to_archive(identified)
renamed = move_to_archive(sortpath, moviepath) handler.cleanup(moved)
# clean folders
cleanup(movie_downpath, sortpath, renamed)