Архив за день: 2021-06-01

Черновик консольной программы для разбора бардака на компьютере (Python)

План был такой:

  1. Выбрать папку для разбора бардака
  2. Программа будет по очереди показывать оттуда файлы (фотки, видео и т.д.)
  3. Пользователь через пробел вводит теги для просмотренного файла (например «фото машина зима» или просто «trash»)
  4. программа переходит к следующему файлу, пока список не кончится
  5. В результате формируется база данных по файлам (sqlite files.db)
  6. Тэги привязываются не к пути файла, а к его контрольной сумме MD5, если копия папки попадётся где-то ещё — программа файлы узнает
  7. В результате файлы любого типа можно находить по тегам
  8. Для удаления мусора можно составить список файлов например с тегом «trash» и передать список например команде «del»

Главное меню:

                Программа для разбора бардака с файлами
                1. Выбор папки
                2. Анализ
                3. Разбор
                4. Сохранить списки
                
                0. Выход

Исходный код черновика (написано за 2 дня):

#! -*- coding: utf-8 -*-

'''
Программа для разбора бардака на дисках

LOG:
    добавил ввод нескольких тэгов через пробел
    добавил таблицу с путями к файлам
    добавил просмотр видео
    добавил разбор по выбранному тэгу, можно для просмотра
    добавил вывод текущих тегов
    добавил удаление тегов через -...
'''

import os
import sqlite3
import hashlib
import time

# просмотрщики
IMAGE_PROGRAM = "eog"
VIDEO_PROGRAM = "vlc"
MUSIC_PROGRAM = "vlc"
TEXT_PROGRAM = "gedit"

CREATE_TABLES_QUERY = """
CREATE TABLE `FILES_INFO` ( `FILES_INFO_ID` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, `PATH` TEXT NOT NULL, `FILE_MD5` TEXT NOT NULL, `LAST_MODIFIED` TEXT )

CREATE TABLE `FILES_TAGS` ( `FILES_TAGS_ID` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, `FILE_MD5` TEXT NOT NULL, `TAG` TEXT NOT NULL )
"""

class FilesAnalyzer:
    
    def __init__(self):
        self.db_connection = sqlite3.connect('files.db')
        self.path = ''
        self.count_by_tags = {}
        self.files_by_tags = {}
        
    def analyze(self):
        '''
        1. Статистика по количеству файлов по каждому тегу
        и неразмеченных
        2. Сохранение списков файлов по каждому тегу
        и неразмеченных
        '''
        # Счётчики файлов по тегам
        self.count_by_tags = {}
        self.count_by_tags[""] = 0
        
        # списки файлов по тегам
        self.files_by_tags = {}
        self.files_by_tags[""] = []
        
        for root, dirs, files in os.walk(self.path):
            for f in files:
                f_full = '%s/%s' % (root, f)
                f_md5 = self.get_md5(f_full)
                f_tags = self.get_tags(f_md5)
                ''' DEBUG
                print('Файл: %s' % f_full)
                print('MD5: %s' % f_md5)
                print('Тэги: %s' % ', '.join(f_tags))
                '''
                if len(f_tags) > 0:
                    for tag in f_tags:
                        if tag not in self.count_by_tags.keys():
                            self.count_by_tags[tag] = 1
                            self.files_by_tags[tag] = []
                            self.files_by_tags[tag].append(f_full)
                        else:
                            self.count_by_tags[tag] += 1
                            self.files_by_tags[tag].append(f_full)
                else:
                    self.count_by_tags[''] += 1
                    self.files_by_tags[''].append(f_full)
        for key in self.count_by_tags.keys():
            print("Тэг '%s': %s" % (key, self.count_by_tags[key]))
        ''' DEBUG
        for key in self.files_by_tags.keys():
            print("Файлы '%s':" % key)
            for f in self.files_by_tags[key]:
                print(f)
        '''
        
    def manual_sort(self, selected_tag=""):
        '''
        Ручная маркировка тегами неразмеченных файлов
        '''
        if selected_tag in self.files_by_tags.keys():
            for f in self.files_by_tags[selected_tag]:
                # проверить что новый md5
                md5 = self.get_md5(f)
                tags = self.get_tags(md5)
                if (selected_tag == "" and len(tags) == 0) \
                        or selected_tag in tags:
                    print("Файл: '%s'" % f)
                    print("Текущие тэги: %s" % \
                            self.get_tags(md5))
                    # пердосмотр или открыть файл
                    self.try_preview(f)
                    new_tags_text = input("Введи тэги для файла: ")
                    if len(new_tags_text) > 0:
                        new_tags = new_tags_text.split()
                        for new_tag in new_tags:
                            self.save_tag(f, new_tag)
                        self.save_info(f)
            # теперь прошлый анализ бесполезен
            self.files_by_tags = {}
            self.count_by_tags = {}
        else:
            print('Ошибка: файлов с тегом "%s" не найдено' % selected_tag)
            
    def save_info(self, f):
        '''
        сохранение информации о файле в БД
        /param[in] f - имя файла
        '''
        # приготовить информацию
        md5 = self.get_md5(f)
        mtime = os.path.getmtime(f)
        last_modified = time.strftime('%Y-%m-%d %H:%M:%S',
                time.localtime(mtime))
        
        # проверить, что в базе такого ещё нет
        cur = self.db_connection.cursor()
        check_query = """SELECT * 
                FROM FILES_INFO
                WHERE PATH = '%s'
                AND FILE_MD5 = '%s'
                AND LAST_MODIFIED = '%s'""" % (
                f, md5, last_modified)
        is_new = True
        for row in cur.execute(check_query):
            is_new = False
        
        if is_new:
            # сохранение в БД
            query = """INSERT INTO FILES_INFO
            (PATH, FILE_MD5, LAST_MODIFIED)
            VALUES ('%s', '%s', '%s')""" % (
            f, md5, last_modified)
            cur.execute(query)
            self.db_connection.commit()
        
    def try_preview(self, f):
        if f.lower()[-4:] in ('.png', '.bmp', '.gif', '.jpg', 'jpeg'):
            #os.system('eog "%s"' % f)
            os.system('%s "%s"' % (IMAGE_PROGRAM, f))
        elif f.lower()[-4:] in ('.avi', '.mpg', '.3gp', '.mov'):
            os.system('%s "%s"' % (VIDEO_PROGRAM, f))
        elif f.lower()[-4:] in ('.mp3', '.wav', '.ogg'):
            os.system('%s "%s"' % (MUSIC_PROGRAM, f))
        
        
    def save_tag(self, f, tag):
        md5 = self.get_md5(f)
        cur = self.db_connection.cursor()
        if tag[0] == "-":
            # TODO: УДАЛИТЬ ТЕГ
            del_query = """DELETE FROM FILES_TAGS
                    WHERE FILE_MD5 = '%s'
                    AND TAG = '%s'""" % (md5, tag[1:])
            cur.execute(del_query)
            self.db_connection.commit()
        else:
            # Сначала проверить что тега у файла ещё нет
            check_query = """SELECT * FROM FILES_TAGS
                    WHERE FILE_MD5 = '%s'
                    AND TAG = '%s'""" % (md5, tag)
            is_new = True
            for row in cur.execute(check_query):
                is_new = False
                break
            
            if is_new:
                # Добавить файлу тег
                query = """INSERT INTO FILES_TAGS
                        (FILE_MD5, TAG) VALUES ('%s', '%s')""" % (
                        md5, tag)
                cur.execute(query)
                self.db_connection.commit()
            
    def show_main_menu(self):
        while True:
            print('\n' * 3 + '''
                Программа для разбора бардака с файлами
                1. Выбор папки
                2. Анализ
                3. Разбор
                4. Сохранить списки
                
                0. Выход
            ''')
            
            selected = input("Что делать? > ")
            if selected == "0":
                self.close()
            elif selected == "1":
                self.show_select_path_menu()
            elif selected == "2":
                self.show_analyze_menu()
            elif selected == "3":
                self.show_manual_sort_menu()
            elif selected == "4":
                self.show_save_lists_menu()
            
    def close(self):
        self.db_connection.close()
        print("Выход из программы")
        quit()

    def show_save_lists_menu(self):
        print("Сохранение списков файлов")
        self.save_lists()
        
    def save_lists(self):
        for key in self.files_by_tags.keys():
            file_name = "%s.txt" % key
            if key == "":
                file_name = "_unsorted.txt"
            if len(self.files_by_tags[key]) > 0:
                f = open(file_name, 'w')
                for item in self.files_by_tags[key]:
                    f.write(item)
                    f.write('\n')
                f.close()
                print(file_name)
    
    def show_select_path_menu(self):
        selected = input('Введите имя папки для разбора: ')
        if self.is_valid_path(selected):
            self.path = selected
            print("Выбрана папка: '%s'" % selected)
        else:
            print("Ошибка: не удалось выбрать папку: '%s'" % selected)
        
    def show_analyze_menu(self):
        print("Анализ файлов в %s:" % self.path)
        self.analyze()
    
    def show_manual_sort_menu(self):
        print("Ручной разбор файлов")
        selected_tag = input("Выбери тег для разбора (по умолчанию без тегов): ")
        self.manual_sort(selected_tag)
    
    def is_valid_path(self, str_path):
        if os.path.exists(str_path):
            return True
        else:
            return False
            
    def get_md5(self, file_path):
        result = ''
        f = open(file_path, 'rb')
        content = f.read()
        f.close()
        result = hashlib.md5(content).hexdigest()
        return result
    
    def get_tags(self, md5):
        result = []
        
        cur = self.db_connection.cursor()
        query = "SELECT TAG FROM FILES_TAGS WHERE FILE_MD5 = '%s'" % md5
        for row in cur.execute(query):
            result.append(row[0])
        return result
    
if __name__ == "__main__":
    fa = FilesAnalyzer()
    fa.show_main_menu()