Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""LICENSE
2Copyright 2015 Hermann Krumrey <hermann@krumreyh.com>
4This file is part of toktokkie.
6toktokkie is free software: you can redistribute it and/or modify
7it under the terms of the GNU General Public License as published by
8the Free Software Foundation, either version 3 of the License, or
9(at your option) any later version.
11toktokkie is distributed in the hope that it will be useful,
12but WITHOUT ANY WARRANTY; without even the implied warranty of
13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14GNU General Public License for more details.
16You should have received a copy of the GNU General Public License
17along with toktokkie. If not, see <http://www.gnu.org/licenses/>.
18LICENSE"""
20import os
21from abc import ABC
22from typing import Dict, Any, List
23from puffotter.prompt import prompt_comma_list
24from toktokkie.enums import IdType
25from toktokkie.metadata.base.MetadataBase import MetadataBase
26from toktokkie.utils.IdFetcher import IdFetcher
27from toktokkie.exceptions import InvalidDirectoryState
30class Prompter(MetadataBase, ABC):
31 """
32 Class that's responsible for defining the metadata creation prompts
33 """
35 id_prompt_order = [
36 IdType.TVDB,
37 IdType.IMDB,
38 IdType.ISBN,
39 IdType.VNDB,
40 IdType.MUSICBRAINZ_ARTIST,
41 IdType.MUSICBRAINZ_RELEASE,
42 IdType.MUSICBRAINZ_RECORDING,
43 IdType.MYANIMELIST,
44 IdType.ANILIST,
45 IdType.KITSU,
46 IdType.MANGADEX
47 ]
48 """
49 The order in which ID types should be prompted
50 """
52 @classmethod
53 def prompt(cls, directory_path: str) -> Dict[str, Any]:
54 """
55 Generates a new Metadata object using prompts for a directory
56 :param directory_path: The path to the directory for which to generate
57 the metadata object
58 :return: The generated metadata object
59 """
60 name = os.path.basename(os.path.abspath(directory_path))
61 id_fetcher = cls.create_id_fetcher(directory_path)
62 print(f"Generating metadata for {name} "
63 f"(type: {cls.media_type().value}):")
65 cls.pre_prompt_check(directory_path)
67 data = {
68 "type": cls.media_type().value,
69 "tags": prompt_comma_list("Tags"),
70 "ids": cls.prompt_ids(
71 cls.valid_id_types(),
72 cls.required_id_types(),
73 {},
74 id_fetcher
75 )
76 }
77 return data
79 @classmethod
80 def create_id_fetcher(cls, directory: str) -> IdFetcher:
81 """
82 Creates an ID fetcher
83 :param directory: The directory for which to generate the ID fetcher
84 :return: The generated ID fetcher
85 """
86 return IdFetcher(
87 os.path.basename(os.path.abspath(directory)),
88 cls.media_type()
89 )
91 @classmethod
92 def prompt_ids(
93 cls,
94 valid_ids: List[IdType],
95 required_ids: List[IdType],
96 defaults: Dict[str, List[str]],
97 id_fetcher: IdFetcher,
98 mincount: int = 1
99 ) -> Dict[str, List[str]]:
100 """
101 Prompts the user for any valid IDs the metadata may contain
102 :param valid_ids: IDs that are valid for the prompt
103 :param required_ids: IDs that are required to be provided
104 :param defaults: Any potential default values for the IDs
105 :param id_fetcher: An ID fetcher
106 :param mincount: Minimal amount of IDs that the user needs to provide
107 :return: The IDs in a dictionary mapping the ID names to their IDs
108 """
109 ids = {} # type: Dict[str, List[str]]
110 for id_type in cls.id_prompt_order:
111 if id_type not in valid_ids:
112 continue
113 else:
114 defaults = cls._load_default_ids(
115 valid_ids, defaults, id_fetcher
116 )
118 default = defaults.get(id_type.value)
119 is_int = id_type in cls.int_id_types()
121 min_count = 0
122 if id_type in required_ids:
123 min_count = 1
125 prompted = prompt_comma_list(
126 "{} IDs".format(id_type.value),
127 min_count=min_count,
128 default=default,
129 primitive_type=int if is_int else lambda x: str(x)
130 )
131 prompted = [str(x) for x in prompted]
132 non_default = prompted != default
134 if len(prompted) > 0:
135 ids[id_type.value] = prompted
136 defaults[id_type.value] = prompted
138 # Update anilist IDs if myanimelist IDs were updated
139 if id_type == IdType.MYANIMELIST and non_default:
140 if IdType.ANILIST.value in defaults:
141 defaults.pop(IdType.ANILIST.value)
143 if len(ids) < mincount:
144 print("Please enter at least {} ID(s)".format(mincount))
145 return cls.prompt_ids(
146 valid_ids, required_ids, defaults, id_fetcher,
147 mincount=mincount
148 )
149 else:
150 return ids
152 @classmethod
153 def _load_default_ids(
154 cls,
155 valid_ids: List[IdType],
156 defaults: Dict[str, List[str]],
157 id_fetcher: IdFetcher
158 ) -> Dict[str, List[str]]:
159 """
160 Tries to load any missing default IDs using the name of the directory
161 and/or other default IDs
162 :param valid_ids: List of valid ID types
163 :param defaults: The current default IDs
164 :param id_fetcher: An ID fetcher
165 :return: The updated IDs
166 """
167 for id_type in valid_ids:
168 if id_type.value in defaults:
169 continue
170 else:
171 _defaults = cls.objectify_ids(defaults)
172 ids = id_fetcher.fetch_ids(id_type, _defaults)
173 if ids is not None:
174 defaults[id_type.value] = ids
176 return defaults
178 @classmethod
179 def prompt_component_ids(
180 cls,
181 valid_ids: List[IdType],
182 previous_ids: Dict[str, List[str]],
183 id_fetcher: IdFetcher
184 ) -> Dict[str, List[str]]:
185 """
186 Prompts for IDs for a component (for example, a season of a tv series)
187 Strips away any IDs that are the same as the root metadata ids
188 :param valid_ids: ID Types that are valid for the kind of metadata
189 :param previous_ids: The IDs previously aquired
190 :param id_fetcher: An ID fetcher
191 :return: The prompted IDs, mapped to id type strings
192 """
194 defaults = previous_ids.copy()
195 ids = cls.prompt_ids(valid_ids, [], defaults, id_fetcher, mincount=0)
197 # Strip unnecessary IDs
198 for key in list(ids.keys()):
199 value = ids[key]
200 if previous_ids.get(key) == value:
201 ids.pop(key)
203 return ids
205 @classmethod
206 def pre_prompt_check(cls, directory_path: str):
207 """
208 Performs checks before prompting
209 :return: None
210 """
211 if not os.path.isdir(directory_path):
212 raise InvalidDirectoryState(f"{directory_path} does not exist")