#!/usr/bin/env python2 # coding: utf-8 # python3 works as well import os import sys import re import shutil from threading import Thread from functools import wraps import urwid import tmdbsimple as tmdb def run_async(func): @wraps(func) def async_func(*args, **kwargs): func_hl = Thread(target=func, args=args, kwargs=kwargs) func_hl.start() return func_hl return async_func def read_key(): if u"TMDB_KEY" in os.environ.keys(): return os.environ[u"TMDB_KEY"] if u"XDG_CONFIG_HOME" in os.environ.keys(): cfg_home = os.environ[u"XDG_CONFIG_HOME"] else: cfg_home = os.path.join(os.path.expanduser(u"~"), ".config") if os.path.exists(os.path.join(cfg_home, u"tmdbkey")): return open(os.path.join(cfg_home, u"tmdbkey"), "r").read().strip() if os.path.exists(os.path.join(os.path.expanduser(u"~"), ".tmdbkey")): return open(os.path.join(os.path.expanduser(u"~"), ".tmdbkey")).read().strip() raise Exception(u"No TheMovieDB Key defined. Set Env. var. TMDB_KEY or .tmpdbkey file") def read_filename(filename): stopwords = [ "dvd", "ac3", "r5", "unrated", "ts", "720p", "md", "ts", "ld", "bdrip", "tvrip", "dvdrip", "dvdscr", "uncut", "german", "english", "telesync", "20[0-1][0-9]|19[0-9][0-9]", "x264", "hdtv", "ws" ] def findword(word): return re.findall(u"(.*?)\.?" + word + u"[\.\- ]", filename, re.IGNORECASE) matches = [i for i in map(findword, stopwords) if i!=[]] matches.sort() if len(matches) > 0: name = matches[0][0] else: name = filename return name.replace(u".", u" ") def write_filename(filename): return re.sub(u"[^a-zA-Z0-9()\- _#]", u"_", filename) class FocusableFrame(urwid.Frame): def keypress(self, size, key): parts = ("header", "footer", "body") if key == "tab": i = parts.index(self.focus_position) self.set_focus(parts[(i+1)%3]) else: self.__super.keypress(size, key) class SearchField(urwid.Edit): signals = ["activate"] def keypress(self, size, key): if key == "enter": self._emit("activate") else: self.__super.keypress(size, key) class UI(urwid.WidgetWrap): def __init__(self, filename, search_service, loop=None): self.filename = filename self.search_service = search_service self.loop = loop self.term = read_filename(filename) self.edit_search = SearchField(("bold", u"Search for: "), self.term) header = urwid.Pile([self.edit_search, urwid.Divider(u"-"), urwid.Text((u"bold", u"Results:"))]) self.model = urwid.SimpleFocusListWalker([]) self.listbox = urwid.ListBox(self.model) btn_quit = urwid.Button(("button", u"Exit")) self.btn_apply = urwid.Button(("button", u"Apply")) self.description = urwid.Text(u"") self.btns = urwid.Columns([btn_quit, self.btn_apply]) footer = urwid.Pile([urwid.Divider(u"="), self.description, urwid.Divider(u"-"), self.btns]) self.view = FocusableFrame(self.listbox, header=header, footer=footer, focus_part="header") super(UI, self).__init__(self.view) urwid.connect_signal(btn_quit, "click", lambda *a: sys.exit(0)) urwid.connect_signal(self.edit_search, "activate", self.search) def search(self, *_): @run_async def query(*_): results = self.search_service.search(self.edit_search.edit_text) self.fill_results(self.loop, results) self.view.set_focus("body") while len(self.model) > 0: self.model.pop() self.description.set_text(u"Searching for {}".format(self.edit_search.edit_text)) query() def fill_results(self, loop, data): self.description.set_text(u"Found {} matches".format(len(data))) if len(data) == 0: self.view.set_focus("header") else: for result in data: if not result["release_date"]: continue result["year"] = result["release_date"].split("-")[0] btn = urwid.Button(u"{title} ({year})".format(**result)) urwid.connect_signal(btn, "click", self.select_movie, result["id"]) self.model.append(btn) loop.draw_screen() def select_movie(self, _, movie_id): @run_async def query(pos): try: info = self.search_service.info(movie_id) alt_titles = self.search_service.alternative_titles(movie_id)["titles"] info["year"] = info["release_date"].split("-")[0] info["filename"] = write_filename(u"{title} ({year}) #{imdb_id}".format(**info)) btn = urwid.Button(("fetched", u"{title} ({year}) #{imdb_id}".format(**info))) urwid.connect_signal(btn, "click", self.rename, info) self.model.insert(pos+1, btn) self.model.remove(self.model[pos]) self.description.set_text([ ("desc", u"Original Title: "), info["original_title"], u" ", ("desc", u"Release date: "), info["release_date"], u" ", ("desc", u"Genres: "), u", ".join(map(lambda e:e["name"],info["genres"])), " ", ("desc", u"Length: "), u"{runtime}min".format(**info), " ", "\n", ("desc", u"Titles: "), u", ".join(map(lambda e:u"{title} ({iso_3166_1})".format(**e),alt_titles)), "\n", ("desc", u"Overview: "), info["overview"], ]) except Exception as e: self.description.set_text((u"error", u"Request failed: {0}".format(e))) finally: self.loop.draw_screen() pos = self.listbox.focus_position query(pos) def rename(self, _, info): def rename(*_): urwid.disconnect_signal(self.btn_apply, u"click", rename) try: if not os.path.exists(info["filename"]): os.mkdir(info["filename"]) shutil.move(self.filename, info["filename"]) except Exception as e: self.view.set_focus("header") self.description.set_text((u"error", u"Rename failed: {0}".format(e))) return raise urwid.ExitMainLoop() self.description.set_text([ (u"bold", "Move "), (u"underline", self.filename), "\n" u" under new folder ", (u"underline", info["filename"]), u" ? (Click Apply or Exit)"]) self.view.set_focus(u"footer") self.btns.set_focus(self.btn_apply) urwid.connect_signal(self.btn_apply, u"click", rename) class Search(object): def __init__(self): self.tmdb_search = tmdb.Search() def search(self, term): return self.tmdb_search.movie(query=term)[u"results"] def info(self, movie_id): return tmdb.Movies(movie_id).info() def alternative_titles(self, movie_id): return tmdb.Movies(movie_id).alternative_titles() def main(): tmdb.API_KEY = read_key() palette = [ ("bold", "default,bold", "default", "bold"), ("fetched", "", "", "", "light green", ""), ("button", "default,bold", "default", "bold", "", "dark blue"), ("underline", "default,underline", "default", "underline"), ("desc", "", "", "light gray", "bold", ""), ("error", "", "", "light red", "bold", ""), ] args = sys.argv[1:] tmdb_ui = UI(len(args) > 0 and u" ".join(args) or "", Search()) loop = urwid.MainLoop(tmdb_ui, palette) loop.screen.set_terminal_properties(colors=256) tmdb_ui.loop = loop loop.run() if __name__ == u"__main__": main()