Exemple #1
0
 def run(self, args, config):
     mdb = self.get_metadata_db(args.tree)
     movies = sorted(mdb.itermovies(), key=lambda x: (x[1].get('title'), x[1].get('year')))
     for movie_hash, movie in movies:
         printer.p(u'{hash} {movie}',
                   hash=highlight_white(' ' + movie_hash + ' '),
                   movie=unicode(movie))
Exemple #2
0
    def _import(self, mdb, mds, args, config, filename):
        printer.debug('Importing file {filename}', filename=filename)
        short_filename = os.path.basename(filename)
        title, ext = os.path.splitext(short_filename)

        year, title = clean_title(title)

        # Disable the year filter if auto mode is disabled:
        if not args.auto:
            year = None

        while True:
            # Disable the title input if auto mode is enabled:
            if not args.auto:
                title = printer.input(u'Title to search', default=title)
            datasource, movie = self._search(mds, title, short_filename, year, auto=args.auto)
            if datasource == 'manual':
                movie = self.profile.object_class()
            elif datasource == 'abort':
                printer.p('Aborted import of {filename}', filename=filename)
                return
            break

        if datasource is None:
            return

        return movie
Exemple #3
0
    def _import(self, mdb, mds, args, config, filename):
        printer.debug('Importing file {filename}', filename=filename)
        short_filename = os.path.basename(filename)
        title, ext = os.path.splitext(short_filename)

        title, season_num, episode_num = clean_title_series(title)

        while True:
            # Disable the title input if auto mode is enabled:
            if not args.auto:
                title = printer.input(u'Title to search', default=title)
                season_num = int(printer.input(u'Season number', default=season_num))
                episode_num = int(printer.input(u'Episode number', default=episode_num))
            datasource, episode = self._search(mds, title, short_filename, season_num, episode_num, auto=args.auto)
            if datasource == 'manual':
                episode = self.profile.object_class()
            elif datasource == 'abort':
                printer.p('Aborted import of {filename}', filename=filename)
                return
            break

        if datasource is None:
            return

        return episode

        # Refresh the full data for the choosen movie:
        episode = mds.refresh(episode)
Exemple #4
0
    def run(self, args, config):
        mdb = self.get_metadata_db(args.tree)

        for movie_input in args.input:
            movie_hash = get_hash(movie_input)

            try:
                movie = mdb.get(movie_hash)
            except KeyError:
                printer.p('Unknown movie hash.')
                return
            if args.unflag:
                for flag in self.unflag_unset_flags:
                    try:
                        del movie[flag]
                    except KeyError:
                        pass
            else:
                for flag in self.flags:
                    movie[flag] = True
                for flag in self.unset_flags:
                    try:
                        del movie[flag]
                    except KeyError:
                        pass
            mdb.save(movie_hash, movie)
Exemple #5
0
    def _search(self, mdb, query, filename, season_num, episode_num, auto=False):
        """ Search the movie using all available datasources and let the user
            select a result. Return the choosen datasource and produced movie dict.

        If auto is enabled, directly returns the first movie found.
        """
        choices = []
        for datasource, movie in mdb.search(query, season=season_num, episode=episode_num):
            if auto:
                return datasource, movie
            fmt = u'<b>{title}</b> - <b>{ep}</b> S{season:02d}E{episode:02d} [{datasource}]'
            choices.append(option((datasource, movie), fmt, title=movie['title'],
                                                            ep=movie['episode_title'],
                                                            season=movie['season'],
                                                            episode=movie['episode'],
                                                            datasource=datasource.name))

        if not choices:
            printer.p('No results to display for the file: {fn}', fn=filename)
            return None, None

        choices.append(option(('manual', None), 'Enter information manually'))
        choices.append(option(('abort', None), 'None of these'))
        printer.p('Please choose the relevant result for the file: {fn}', fn=filename, end='\n\n')
        return printer.choice(choices)
Exemple #6
0
    def run(self, args, config):
        mdb = self.get_metadata_db(args.tree)

        movie_hash = get_hash(args.input)

        try:
            movie = mdb.get(movie_hash)
        except KeyError:
            printer.p('Unknown movie hash.')
            return

        movie_json = json.dumps(movie, indent=4)

        while True:
            movie_json = printer.edit(movie_json)

            try:
                mdb.save(movie_hash, json.loads(movie_json))
            except ValueError:
                if printer.ask('Bad json data, would you like to try again?', default=True):
                    continue
                else:
                    break
            else:
                printer.p('Saved.')
                break
Exemple #7
0
    def run(self, args, config):
        mdb = self.get_metadata_db(args.tree)
        mds = MovieDatasource(config.subsections('datasource'), args.tree, self.profile.object_class)

        if args.input is None: # Refresh all movies
            if printer.ask('Would you like to refresh all movies?', default=True):
                with printer.progress(mdb.count(), task=True) as update:
                    for movie_hash, movie in list(mdb.itermovies()):
                        movie = mds.refresh(movie)
                        mdb.save(movie_hash, movie)
                        printer.verbose('Saved {hash}', hash=movie_hash)
                        update(1)
        else:
            movie_hash = get_hash(args.input)

            try:
                movie = mdb.get(movie_hash)
            except KeyError:
                printer.p('Unknown movie hash.')
                return
            else:
                movie = mds.refresh(movie)
                show(movie)
                if printer.ask('Would you like to save the movie?', default=True):
                    mdb.save(movie_hash, movie)
                    printer.p('Saved.')
Exemple #8
0
    def _search(self, mdb, query, filename, year=None, auto=False):
        """ Search the movie using all available datasources and let the user
            select a result. Return the choosen datasource and produced movie dict.

        If auto is enabled, directly returns the first movie found.
        """
        choices = []
        for datasource, movie in mdb.search(query, year=year):
            if auto:
                return datasource, movie
            if movie.get('directors'):
                directors = ' by '
                if len(movie['directors']) > 1:
                    directors += '%s and %s' % (', '.join(movie['directors'][0:-1]),
                                                          movie['directors'][-1])
                else:
                    directors += movie['directors'][0]
            else:
                directors = ''
            fmt = u'<b>{title}</b> ({year}){directors} [{datasource}]'
            choices.append(option((datasource, movie), fmt, title=movie['title'],
                                                            year=movie.get('year', 'Unknown'),
                                                            directors=directors,
                                                            datasource=datasource.name))

        if not choices:
            printer.p('No results to display for the file: {fn}', fn=filename)
            return None, None

        choices.append(option(('manual', None), 'Enter information manually'))
        choices.append(option(('abort', None), 'None of these'))
        printer.p('Please choose the relevant movie for the file: {fn}', fn=filename, end='\n\n')
        return printer.choice(choices)
Exemple #9
0
 def run(self, args, config):
     config_fullname = os.path.join(args.tree, '.kolekto', 'config')
     with open(config_fullname, 'r+') as fconfig:
         config = printer.edit(fconfig.read())
         fconfig.seek(0)
         fconfig.truncate()
         fconfig.write(config)
     printer.p('Saved.')
Exemple #10
0
 def run(self, args, config):
     mdb = self.get_metadata_db(args.tree)
     with open(args.file) as fdump:
         dump = json.load(fdump)
     for movie in dump:
         mdb.save(movie['hash'], movie['movie'])
         printer.verbose('Loaded {hash}', hash=movie['hash'])
     printer.p('Loaded {nb} movies.', nb=len(dump))
Exemple #11
0
    def run(self, args, config):
        mdb = self.get_metadata_db(args.tree)
        hash_by_title = defaultdict(lambda: [])
        for movie_hash, movie in mdb.itermovies():
            hash_by_title[movie.get('title', None), movie.get('year', None)].append(movie_hash)

        for (title, year), hashs in hash_by_title.iteritems():
            if len(hashs) > 1:
                printer.p('{title} ({year}): {hashs}', title=bold(title),
                          year=year, hashs=' '.join(hashs))
Exemple #12
0
 def run(self, args, config):
     mdb = self.get_metadata_db(args.tree)
     mds = MovieDatasource(config.subsections('datasource'), args.tree, self.profile.object_class)
     listing = self._config(args, config)
     def _sorter((movie_hash, movie)):
         return tuple(movie.get(x) for x in listing['order'])
     movies = sorted(mdb.itermovies(), key=_sorter)
     # Get the current used listing:
     for movie_hash, movie in movies:
         movie = mds.attach(movie_hash, movie)
         prepared_env = parse_pattern(listing['pattern'], movie, ListingFormatWrapper)
         printer.p(u'<inv><b> {hash} </b></inv> ' + listing['pattern'], hash=movie_hash, **prepared_env)
Exemple #13
0
    def _import(self, mdb, mds, args, config, filename):
        printer.debug('Importing file {filename}', filename=filename)
        short_filename = os.path.basename(filename)
        title, ext = os.path.splitext(short_filename)

        year, title = clean_title(title)

        # Disable the year filter if auto mode is disabled:
        if not args.auto:
            year = None

        while True:
            # Disable the title input if auto mode is enabled:
            if not args.auto:
                title = printer.input(u'Title to search', default=title)
            datasource, movie = self._search(mds, title, short_filename, year, auto=args.auto)
            if datasource == 'manual':
                movie = self.profile.object_class
            elif datasource == 'abort':
                printer.p('Aborted import of {filename}', filename=filename)
                return
            break

        if datasource is None:
            return

        # Refresh the full data for the choosen movie:
        movie = mds.refresh(movie)

        if args.show:
            show(movie)
            printer.p('')

        # Edit available data:
        if not args.auto and printer.ask('Do you want to edit the movie metadata', default=False):
            movie = self.profile.object_class(json.loads(printer.edit(json.dumps(movie, indent=True))))

        # Hardlink or copy the movie in the tree
        if args.hardlink or args.symlink:
            printer.p('\nComputing movie sha1sum...')
            movie_hash = link(args.tree, filename, args.symlink)
        else:
            printer.p('\nCopying movie in kolekto tree...')
            movie_hash = copy(args.tree, filename)
        printer.p('')

        mdb.save(movie_hash, movie)
        printer.debug('Movie {hash} saved to the database', hash=movie_hash)

        if args.delete:
            os.unlink(filename)
            printer.debug('Deleted original file {filename}', filename=filename)
Exemple #14
0
    def run(self, args, config):
        mds = MovieDatasource(config.subsections('datasource'), args.tree, self.profile.object_class)
        mdb = self.get_metadata_db(args.tree)
        hash_by_title = defaultdict(lambda: [])
        for movie_hash, movie in mdb.itermovies():
            movie = mds.attach(movie_hash, movie)
            hash_info = '<inv> %s </inv> (%s/%s)' % (movie_hash, movie.get('quality'), movie.get('ext'))
            hash_by_title[movie.get('title', None), movie.get('year', None)].append(hash_info)

        for (title, year), hashs in hash_by_title.iteritems():
            if len(hashs) > 1:
                printer.p('<b>{title}</b> ({year}): {hashs}', title=title,
                          year=year, hashs=' '.join(hashs))
Exemple #15
0
    def run(self, args, config):
        mdb = self.get_metadata_db(args.tree)
        mds = MovieDatasource(config.subsections('datasource'), args.tree, self.profile.object_class)

        movie_hash = get_hash(args.input)

        try:
            movie = mdb.get(movie_hash)
        except KeyError:
            printer.p('Unknown movie hash.')
            return
        movie = mds.attach(movie_hash, movie)
        show(movie)
Exemple #16
0
def show(movie):
    """ Show the movie metadata.
    """
    for key, value in sorted(movie.iteritems(), cmp=metadata_sorter, key=lambda x: x[0]):
        if isinstance(value, list):
            if not value:
                continue
            other = value[1:]
            value = value[0]
        else:
            other = []
        printer.p('{key}: {value}', key=bold(key), value=value)
        for value in other:
            printer.p('{pad}{value}', value=value, pad=' ' * (len(key) + 2))
Exemple #17
0
    def run(self, args, config):
        mdb = self.get_metadata_db(args.tree)

        movie_hash = get_hash(args.input)

        try:
            mdb.get(movie_hash)
        except KeyError:
            printer.p('Unknown movie hash.')
            return

        if printer.ask('Are you sure?', default=False):
            mdb.remove(movie_hash)
            printer.p('Removed. You need to launch gc to free space.')
Exemple #18
0
    def run(self, args, config):
        if config is not None:
            raise KolektoRuntimeError('Already a Kolekto tree')

        movies_directory = os.path.join(args.tree, '.kolekto', 'movies')

        # Ensure that the .kolekto/movies directory exists:
        if not os.path.isdir(movies_directory):
            os.makedirs(movies_directory)

        # Write the default config:
        with open(os.path.join(args.tree, '.kolekto', 'config'), 'w') as fconfig:
            fconfig.write(DEFAULT_CONFIG.format(profile=args.profile))
        printer.p('Initialized empty Kolekto tree in {where}.', where=os.path.abspath(args.tree))

        # Open the metadata db to create it automatically:
        self.get_metadata_db(args.tree)
Exemple #19
0
 def run(self, args, config):
     mdb = self.get_metadata_db(args.tree)
     db_files = set()
     for movie_hash, movie in mdb.itermovies():
         db_files.add(movie_hash)
         db_files.update(movie.get("_externals", []))
     printer.verbose("Found {nb} files in database", nb=len(db_files))
     fs_files = set(os.listdir(os.path.join(args.tree, ".kolekto", "movies")))
     printer.verbose("Found {nb} files in filesystem", nb=len(fs_files))
     orphan_files = fs_files - db_files
     printer.p("Found {nb} orphan files to delete", nb=len(orphan_files))
     if orphan_files:
         printer.verbose("Files to delete: {files}", files=", ".join(orphan_files))
         if printer.ask("Would you like to delete orphans?"):
             for orphan_file in orphan_files:
                 try:
                     os.remove(os.path.join(args.tree, ".kolekto", "movies", orphan_file))
                 except OSError as err:
                     printer.p("Unable to delete {file}: {err}", file=orphan_file, err=err)
                 else:
                     printer.verbose("Deleted {file}", file=orphan_file)
Exemple #20
0
    def run(self, args, config):
        # Check the args:
        if args.symlink and args.delete:
            raise KolektoRuntimeError('--delete can\'t be used with --symlink')
        elif args.symlink and args.hardlink:
            raise KolektoRuntimeError('--symlink and --hardlink are mutually exclusive')

        # Load the metadata database:
        mdb = self.get_metadata_db(args.tree)

        # Load informations from db:
        mds = MovieDatasource(config.subsections('datasource'), args.tree, self.profile.object_class)

        attachment_store = AttachmentStore(os.path.join(args.tree, '.kolekto', 'attachments'))

        for filename in args.file:
            filename = filename.decode('utf8')
            movie = self._import(mdb, mds, args, config, filename)

            # Refresh the full data for the choosen movie:
            movie = mds.refresh(movie)

            # Append the import date
            movie['import_date'] = datetime.datetime.now().strftime('%d/%m/%Y %H:%M:%S')

            if args.show:
                show(movie)
                printer.p('')

            # Edit available data:
            if not args.auto and printer.ask('Do you want to edit the movie metadata', default=False):
                movie = self.profile.object_class(json.loads(printer.edit(json.dumps(movie, indent=True))))

            # Hardlink or copy the movie in the tree
            if args.hardlink or args.symlink:
                printer.p('\nComputing movie sha1sum...')
                movie_hash = link(args.tree, filename, args.symlink)
            else:
                printer.p('\nCopying movie in kolekto tree...')
                movie_hash = copy(args.tree, filename)
            printer.p('')

            mdb.save(movie_hash, movie)
            printer.debug('Movie {hash} saved to the database', hash=movie_hash)

            if args.delete:
                os.unlink(filename)
                printer.debug('Deleted original file {filename}', filename=filename)

            # Import the attachments
            if movie_hash is not None and args.import_attachments:
                attachments = list_attachments(filename)
                if attachments:
                    printer.p('Found {nb} attachment(s) for this movie:', nb=len(attachments))
                    for attach in attachments:
                        printer.p(' - {filename}', filename=attach)
                    if not args.auto and printer.ask('Import them?', default=True):
                        for attach in attachments:
                            _, ext = os.path.splitext(attach)
                            attachment_store.store(movie_hash, ext.lstrip('.'), open(attach))
Exemple #21
0
    def run(self, args, config):
        mdb = self.get_metadata_db(args.tree)
        mds = MovieDatasource(config.subsections('datasource'), args.tree, self.profile.object_class)
        total_runtime = 0
        total_size = 0
        count_by_genre = defaultdict(lambda: 0)
        count_by_director = defaultdict(lambda: 0)
        count_by_quality = defaultdict(lambda: 0)
        count_by_container = defaultdict(lambda: 0)
        for movie_hash, movie in mdb.itermovies():
            movie_fullpath = os.path.join(args.tree, '.kolekto', 'movies', movie_hash)
            movie = mds.attach(movie_hash, movie)
            total_runtime += movie.get('runtime', 0)
            total_size += os.path.getsize(movie_fullpath)
            for genre in movie.get('genres', []):
                count_by_genre[genre] += 1
            for director in movie.get('directors', []):
                count_by_director[director] += 1
            count_by_quality[movie.get('quality', 'n/a')] += 1
            count_by_container[movie.get('container', 'n/a')] += 1

        printer.p(bold('Number of movies:'), mdb.count())
        printer.p(bold('Total runtime:'), timedelta(seconds=total_runtime * 60))
        printer.p(bold('Total size:'), humanize_filesize(total_size))
        printer.p(bold('Genres top3:'), format_top(count_by_genre))
        printer.p(bold('Director top3:'), format_top(count_by_director))
        printer.p(bold('Quality:'), format_top(count_by_quality, None))
        printer.p(bold('Container:'), format_top(count_by_container, None))
Exemple #22
0
    def run(self, args, config):
        mdb = self.get_metadata_db(args.tree)
        mds = MovieDatasource(config.subsections('datasource'), args.tree, self.profile.object_class)

        if args.dry_run:
            printer.p('Dry run: I will not create or delete any link')

        # Create the list of links that must exists on the fs:
        db_links = {}
        with printer.progress(mdb.count(), task=True) as update:
            for movie_hash, movie in mdb.itermovies():
                movie = mds.attach(movie_hash, movie)
                for view in config.subsections('view'):
                    for pattern in view.get('pattern'):
                        for result in format_all(pattern, movie):
                            filename = os.path.join(view.args, result)
                            if filename in db_links:
                                printer.p('Warning: duplicate link {link}', link=filename)
                            else:
                                db_links[filename] = movie_hash
                update(1)

        # Create the list of links already existing on the fs:
        fs_links = {}
        for view in config.subsections('view'):
            view_links = walk_links(os.path.join(args.tree, view.args),
                                    prefix=view.args,
                                    linkbase=os.path.join(args.tree, '.kolekto', 'movies'))
            fs_links.update(view_links)

        db_links = set(db_links.iteritems())
        fs_links = set(fs_links.iteritems())

        links_to_delete = fs_links - db_links
        links_to_create = db_links - fs_links

        printer.p('Found {rem} links to delete, {add} links to create',
                  rem=len(links_to_delete),
                  add=len(links_to_create))

        dirs_to_cleanup = set()

        # Delete the old links:
        for filename, link in links_to_delete:
            printer.verbose('Deleting {file}', file=filename)
            if not args.dry_run:
                os.remove(os.path.join(args.tree, filename))
            while filename:
                filename = os.path.split(filename)[0]
                dirs_to_cleanup.add(filename)

        dirs_to_cleanup.discard('')  # Avoid to delete view roots

        # Delete empty directories:
        for directory in dirs_to_cleanup:
            if not args.dry_run:
                try:
                    os.rmdir(os.path.join(args.tree, directory))
                except OSError, err:
                    if err.errno != 39:  # Ignore "Directory not empty" error
                        raise
                else:
                    printer.verbose('Deleted directory {dir}', dir=directory)