1
|
|
|
#!/usr/bin/python |
|
|
|
|
2
|
|
|
# -*- coding: utf-8 -*- |
3
|
|
|
import time |
4
|
|
|
|
5
|
|
|
from titlesearch import get_similar_titles |
|
|
|
|
6
|
|
|
|
7
|
|
|
from saucenao import SauceNao, FileHandler |
8
|
|
|
|
9
|
|
|
|
10
|
|
|
class Worker(SauceNao): |
|
|
|
|
11
|
|
|
""" |
|
|
|
|
12
|
|
|
Worker class for checking a list of files |
13
|
|
|
""" |
14
|
|
|
|
15
|
|
|
def __init__(self, files, *args, **kwargs): |
|
|
|
|
16
|
|
|
""" |
|
|
|
|
17
|
|
|
initializing function |
18
|
|
|
|
19
|
|
|
:type files: list|tuple|Generator |
20
|
|
|
:param args: |
21
|
|
|
:param kwargs: |
22
|
|
|
""" |
23
|
|
|
super().__init__(*args, **kwargs) |
|
|
|
|
24
|
|
|
self.complete_file_list = files |
|
|
|
|
25
|
|
|
|
26
|
|
|
def run(self): |
|
|
|
|
27
|
|
|
"""Check all files with SauceNao and execute the specified tasks |
|
|
|
|
28
|
|
|
|
29
|
|
|
:return: |
30
|
|
|
""" |
31
|
|
|
for file_name in self.files: |
|
|
|
|
32
|
|
|
start_time = time.time() |
|
|
|
|
33
|
|
|
|
34
|
|
|
filtered_results = self.check_file(file_name) |
|
|
|
|
35
|
|
|
|
36
|
|
|
if not filtered_results: |
|
|
|
|
37
|
|
|
self.logger.info('No results found for image: {0:s}'.format(file_name)) |
|
|
|
|
38
|
|
|
continue |
|
|
|
|
39
|
|
|
|
40
|
|
|
if self._move_to_categories: |
|
|
|
|
41
|
|
|
self.move_to_categories(file_name=file_name, results=filtered_results) |
|
|
|
|
42
|
|
|
else: |
|
|
|
|
43
|
|
|
yield { |
|
|
|
|
44
|
|
|
'filename': file_name, |
45
|
|
|
'results': filtered_results |
46
|
|
|
} |
47
|
|
|
|
48
|
|
|
duration = time.time() - start_time |
|
|
|
|
49
|
|
|
if duration < (30 / SauceNao.LIMIT_30_SECONDS): |
|
|
|
|
50
|
|
|
self.logger.debug("sleeping '{:.2f}' seconds".format((30 / SauceNao.LIMIT_30_SECONDS) - duration)) |
|
|
|
|
51
|
|
|
time.sleep((30 / SauceNao.LIMIT_30_SECONDS) - duration) |
|
|
|
|
52
|
|
|
|
53
|
|
|
@property |
|
|
|
|
54
|
|
|
def excludes(self): |
|
|
|
|
55
|
|
|
"""Property for excludes |
|
|
|
|
56
|
|
|
|
57
|
|
|
:return: |
58
|
|
|
""" |
59
|
|
|
if self._exclude_categories: |
|
|
|
|
60
|
|
|
return [l.lower() for l in self._exclude_categories.split(",")] |
|
|
|
|
61
|
|
|
else: |
|
|
|
|
62
|
|
|
return [] |
|
|
|
|
63
|
|
|
|
64
|
|
|
@property |
|
|
|
|
65
|
|
|
def files(self): |
|
|
|
|
66
|
|
|
"""Property for files |
|
|
|
|
67
|
|
|
|
68
|
|
|
:return: |
69
|
|
|
""" |
70
|
|
|
if self._start_file: |
|
|
|
|
71
|
|
|
# change files from generator to list |
72
|
|
|
files = list(self.complete_file_list) |
|
|
|
|
73
|
|
|
try: |
|
|
|
|
74
|
|
|
return files[files.index(self._start_file):] |
|
|
|
|
75
|
|
|
except ValueError: |
|
|
|
|
76
|
|
|
return self.complete_file_list |
|
|
|
|
77
|
|
|
return self.complete_file_list |
|
|
|
|
78
|
|
|
|
79
|
|
|
def move_to_categories(self, file_name: str, results): |
|
|
|
|
80
|
|
|
"""Check the file for categories and move it to the corresponding folder |
|
|
|
|
81
|
|
|
|
82
|
|
|
:type file_name: str |
83
|
|
|
:type results: list|tuple|Generator |
84
|
|
|
:return: bool |
85
|
|
|
""" |
86
|
|
|
categories = self.get_content_value(results, SauceNao.CONTENT_CATEGORY_KEY) |
|
|
|
|
87
|
|
|
|
88
|
|
|
if not categories: |
|
|
|
|
89
|
|
|
self.logger.info("no categories found for file: {0:s}".format(file_name)) |
|
|
|
|
90
|
|
|
return False |
|
|
|
|
91
|
|
|
|
92
|
|
|
self.logger.debug('categories: {0:s}'.format(', '.join(categories))) |
|
|
|
|
93
|
|
|
|
94
|
|
|
# since many pictures are tagged as original and with a proper category |
95
|
|
|
# we remove the original category if we have more than 1 category |
96
|
|
|
if len(categories) > 1 and 'original' in categories: |
|
|
|
|
97
|
|
|
categories.remove('original') |
|
|
|
|
98
|
|
|
|
99
|
|
|
# take the first category |
100
|
|
|
category = categories[0] |
|
|
|
|
101
|
|
|
|
102
|
|
|
category = self.get_similar_title(category) |
|
|
|
|
103
|
|
|
|
104
|
|
|
# sub categories we don't want to move like original etc |
105
|
|
|
if category.lower() in self.excludes: |
|
|
|
|
106
|
|
|
self.logger.info("skipping excluded category: {0:s} ({1:s})".format(category, file_name)) |
|
|
|
|
107
|
|
|
return False |
|
|
|
|
108
|
|
|
|
109
|
|
|
self.logger.info("moving {0:s} to category: {1:s}".format(file_name, category)) |
|
|
|
|
110
|
|
|
FileHandler.move_to_category(file_name, category, base_directory=self._directory) |
|
|
|
|
111
|
|
|
return True |
|
|
|
|
112
|
|
|
|
113
|
|
|
def get_similar_title(self, category): |
|
|
|
|
114
|
|
|
""" |
|
|
|
|
115
|
|
|
|
116
|
|
|
:param category: |
117
|
|
|
:return: |
118
|
|
|
""" |
119
|
|
|
if get_similar_titles: |
|
|
|
|
120
|
|
|
similar_titles = get_similar_titles(category) |
|
|
|
|
121
|
|
|
|
122
|
|
|
if similar_titles and similar_titles[0]['similarity'] * 100 >= self._title_minimum_similarity: |
|
|
|
|
123
|
|
|
self.logger.info( |
|
|
|
|
124
|
|
|
"Similar title found: {0:s}, {1:s} ({2:.2f}%)".format( |
125
|
|
|
category, similar_titles[0]['title'], similar_titles[0]['similarity'] * 100)) |
|
|
|
|
126
|
|
|
return similar_titles[0]['title'] |
|
|
|
|
127
|
|
|
|
128
|
|
|
return category |
|
|
|
|
129
|
|
|
|
The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:
If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.