272 lines
9.8 KiB
Python
272 lines
9.8 KiB
Python
""" sort and rename downloaded movies """
|
|
|
|
import logging
|
|
import os
|
|
import re
|
|
import subprocess
|
|
|
|
import requests
|
|
|
|
from src.config import get_config
|
|
|
|
|
|
class MovieHandler:
|
|
""" handler for moving files around """
|
|
|
|
CONFIG = get_config()
|
|
|
|
def __init__(self):
|
|
""" check for pending movie files """
|
|
self.pending = self.get_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 move_to_sort(self):
|
|
""" moving files from movie_downpath to sortpath """
|
|
# read out config
|
|
sortpath = self.CONFIG['media']['sortpath']
|
|
movie_downpath = self.CONFIG['media']['movie_downpath']
|
|
min_file_size = self.CONFIG['media']['min_file_size']
|
|
ext = self.CONFIG['media']['ext']
|
|
for dirpath, _, filenames in os.walk(movie_downpath):
|
|
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
|
|
if (extension in ext and
|
|
'sample' not in filename and
|
|
f_size > min_file_size):
|
|
move_to = os.path.join(sortpath, filename)
|
|
os.rename(path, move_to)
|
|
pending = os.listdir(sortpath)
|
|
return pending
|
|
|
|
def rename_files(self, identified):
|
|
""" apply the identified filenames and rename """
|
|
sortpath = self.CONFIG['media']['sortpath']
|
|
renamed = []
|
|
for movie in identified:
|
|
new_filename = movie.movie_details['new_filename']
|
|
old_file = os.path.join(sortpath, movie.filename)
|
|
new_file = os.path.join(sortpath, new_filename)
|
|
os.rename(old_file, new_file)
|
|
logging.info(
|
|
'movie:from [%s] to [%s]', movie.filename, new_filename
|
|
)
|
|
renamed.append(movie.filename)
|
|
return renamed
|
|
|
|
def move_to_archive(self, identified):
|
|
""" move renamed filed to archive """
|
|
sortpath = self.CONFIG['media']['sortpath']
|
|
moviepath = self.CONFIG['media']['moviepath']
|
|
# confirm
|
|
print('\nrenamed:')
|
|
for movie in identified:
|
|
new_filename = movie.movie_details['new_filename']
|
|
print(f'from: {movie.filename} \nto: {new_filename}\n')
|
|
to_continue = input('\ncontinue? Y/n')
|
|
if to_continue == 'n':
|
|
print('cancle...')
|
|
return False
|
|
moved = []
|
|
for movie in identified:
|
|
new_filename = movie.movie_details['new_filename']
|
|
year_dedected = movie.movie_details['year_dedected']
|
|
new_moviename = movie.movie_details['new_moviename']
|
|
new_filename = movie.movie_details['new_filename']
|
|
old_file = os.path.join(sortpath, new_filename)
|
|
new_folder = os.path.join(
|
|
moviepath, str(year_dedected), new_moviename
|
|
)
|
|
new_file = os.path.join(new_folder, new_filename)
|
|
try:
|
|
os.makedirs(new_folder)
|
|
except FileExistsError:
|
|
print(f'{new_filename}\nalready exists in archive')
|
|
double = input('[O]: overwrite, [s]: skip\n') or 'O'
|
|
if double == 'O':
|
|
subprocess.call(["trash", new_folder])
|
|
os.makedirs(new_folder)
|
|
elif double == 's':
|
|
continue
|
|
os.rename(old_file, new_file)
|
|
moved.append(new_filename)
|
|
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)
|
|
|
|
|
|
class MovieIdentify:
|
|
""" describes and identifies a single movie """
|
|
|
|
CONFIG = get_config()
|
|
|
|
def __init__(self, filename):
|
|
""" parse filename """
|
|
self.filename = filename
|
|
self.file_parsed = self.split_filename()
|
|
self.movie_details = self.get_new_filename()
|
|
|
|
def split_filename(self):
|
|
""" build raw values from filename """
|
|
file_ext = os.path.splitext(self.filename)[1]
|
|
year_id_pattern = re.compile(r'\d{4}')
|
|
year_list = year_id_pattern.findall(self.filename)
|
|
# remove clear false
|
|
for year in year_list:
|
|
if year in ('1080', '2160'):
|
|
# resolution not year
|
|
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('.')
|
|
moviename_encoded = self.encode_moviename(moviename)
|
|
# build file_parsed dict
|
|
file_parsed = {
|
|
'moviename': moviename,
|
|
'moviename_encoded': moviename_encoded,
|
|
'year': int(year),
|
|
'file_ext': file_ext
|
|
}
|
|
return file_parsed
|
|
|
|
@ staticmethod
|
|
def encode_moviename(moviename):
|
|
""" url encode and clean the moviename """
|
|
encoded = 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.file_parsed['year']
|
|
moviename_encoded = self.file_parsed['moviename_encoded']
|
|
# 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={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 """
|
|
file_ext = self.file_parsed['file_ext']
|
|
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']
|
|
cleaned_name = self.name_cleaner(name_dedected)
|
|
new_moviename = f'{cleaned_name} ({year_dedected})'
|
|
new_filename = f'{new_moviename}{file_ext}'
|
|
movie_details = {
|
|
'new_moviename': new_moviename,
|
|
'new_filename': new_filename,
|
|
'year_dedected': year_dedected
|
|
}
|
|
return movie_details
|
|
|
|
@staticmethod
|
|
def name_cleaner(name_dedected):
|
|
""" replace problematic characters in filename """
|
|
cleaned_name = name_dedected.replace('/', '-')
|
|
return cleaned_name
|
|
|
|
|
|
def main():
|
|
""" main to lunch moviesort """
|
|
handler = MovieHandler()
|
|
# check if pending
|
|
if not handler.pending:
|
|
print('no movies to sort')
|
|
return
|
|
to_rename = handler.move_to_sort()
|
|
# identify
|
|
identified = []
|
|
for i in to_rename:
|
|
movie = MovieIdentify(i)
|
|
identified.append(movie)
|
|
# rename and move
|
|
renamed = handler.rename_files(identified)
|
|
if renamed:
|
|
moved = handler.move_to_archive(identified)
|
|
print(f'renamed {moved} movies')
|
|
handler.cleanup(moved)
|