Skip to content
GitLab
Explore
Sign in
Commits on Source (3)
Redo prompt structure of metadata objects
· de1a7e2c
Hermann Krumrey
authored
Sep 30, 2019
de1a7e2c
Fix static type issues
· 432b8287
Hermann Krumrey
authored
Sep 30, 2019
432b8287
Add some JSON validation tests
· b2a6bbd1
Hermann Krumrey
authored
Sep 30, 2019
b2a6bbd1
Show whitespace changes
Inline
Side-by-side
.gitlab-ci.yml
View file @
b2a6bbd1
...
...
@@ -5,7 +5,7 @@ stages:
-
release
default
:
image
:
namboy94/ci-docker-environment:0.
6
.0
image
:
namboy94/ci-docker-environment:0.
8
.0
before_script
:
-
echo "$SERVER_ACCESS_KEY" > ~/.ssh/id_rsa
-
chmod 0600 ~/.ssh/id_rsa
...
...
@@ -25,7 +25,13 @@ stylecheck:
stage
:
test
tags
:
[
docker
]
script
:
-
python-codestyle-check
-
python-codestyle-check --exclude toktokkie/gui/pyuic
type_check
:
stage
:
test
tags
:
[
docker
]
script
:
-
python-static-type-check
unittest
:
stage
:
test
...
...
toktokkie/check/BookSeriesChecker.py
View file @
b2a6bbd1
...
...
@@ -43,7 +43,7 @@ class BookSeriesChecker(Checker):
Checks if the anilist user is up-to-date with all available volumes
:return: The check result
"""
metadata
=
self
.
metadata
# type: BookSeries
metadata
=
self
.
metadata
# type: BookSeries
# type: ignore
manga_list
=
self
.
config
[
"
anilist_manga_list
"
]
anilist_id
=
metadata
.
ids
.
get
(
IdType
.
ANILIST
,
[
None
])[
0
]
...
...
toktokkie/check/MangaChecker.py
View file @
b2a6bbd1
...
...
@@ -69,7 +69,7 @@ class MangaChecker(Checker):
:return: The result of the check
"""
# noinspection PyTypeChecker
metadata
=
self
.
metadata
# type: Manga
metadata
=
self
.
metadata
# type: Manga
# type: ignore
anilist_entries
=
self
.
config
[
"
anilist_manga_list
"
]
local_chaptercount
=
len
(
os
.
listdir
(
metadata
.
main_path
))
...
...
toktokkie/check/TvSeriesChecker.py
View file @
b2a6bbd1
...
...
@@ -19,7 +19,7 @@ along with toktokkie. If not, see <http://www.gnu.org/licenses/>.
import
os
from
tvdb_api
import
tvdb_shownotfound
from
typing
import
Dict
,
Optional
from
typing
import
Dict
,
Optional
,
List
from
datetime
import
datetime
from
colorama
import
Fore
,
Style
from
toktokkie.check.Checker
import
Checker
...
...
@@ -68,7 +68,7 @@ class TvSeriesChecker(Checker):
:return: The result of the check
"""
valid
=
True
metadata
=
self
.
metadata
# type: TvSeries
metadata
=
self
.
metadata
# type: TvSeries
# type: ignore
for
season_name
in
os
.
listdir
(
metadata
.
directory_path
):
season_path
=
os
.
path
.
join
(
metadata
.
directory_path
,
season_name
)
...
...
@@ -92,7 +92,7 @@ class TvSeriesChecker(Checker):
:return: The result of the check
"""
valid
=
True
metadata
=
self
.
metadata
# type: TvSeries
metadata
=
self
.
metadata
# type: TvSeries
# type: ignore
ids
=
[
metadata
.
tvdb_id
]
for
season
in
metadata
.
seasons
:
...
...
@@ -115,7 +115,7 @@ class TvSeriesChecker(Checker):
:return: The result of the check
"""
valid
=
True
metadata
=
self
.
metadata
# type: TvSeries
metadata
=
self
.
metadata
# type: TvSeries
# type: ignore
ignores
=
self
.
_generate_ignores_map
()
tvdb_data
=
self
.
tvdb
[
int
(
metadata
.
tvdb_id
)]
...
...
@@ -132,8 +132,8 @@ class TvSeriesChecker(Checker):
elif
episode_number
in
ignores
.
get
(
season_number
,
[]):
episode_amount
-=
1
existing
=
metadata
.
get_episode_files
()
existing
=
existing
[
metadata
.
tvdb_id
].
get
(
season_number
,
[])
_
existing
=
metadata
.
get_episode_files
()
existing
=
_
existing
[
metadata
.
tvdb_id
].
get
(
season_number
,
[])
if
not
len
(
existing
)
==
episode_amount
:
msg
=
"
Mismatch in season {}; Should:{}; Is:{}
"
.
format
(
...
...
@@ -150,7 +150,7 @@ class TvSeriesChecker(Checker):
:return: The result of the check
"""
valid
=
True
metadata
=
self
.
metadata
# type: TvSeries
metadata
=
self
.
metadata
# type: TvSeries
# type: ignore
ignores
=
self
.
_generate_ignores_map
()
tvdb_data
=
self
.
tvdb
[
int
(
metadata
.
tvdb_id
)]
...
...
@@ -196,7 +196,7 @@ class TvSeriesChecker(Checker):
:return: The result of the check
"""
valid
=
True
metadata
=
self
.
metadata
# type: TvSeries
metadata
=
self
.
metadata
# type: TvSeries
# type: ignore
episode_files
=
metadata
.
get_episode_files
()
for
season
in
metadata
.
seasons
:
...
...
@@ -243,7 +243,7 @@ class TvSeriesChecker(Checker):
:return: The check result
"""
metadata
=
self
.
metadata
# type: TvSeries
metadata
=
self
.
metadata
# type: TvSeries
# type: ignore
api
=
self
.
config
[
"
anilist_api
"
]
# type: AnilistApi
user_list
=
self
.
config
[
"
anilist_anime_list
"
]
completed_ids
=
list
(
...
...
@@ -283,7 +283,7 @@ class TvSeriesChecker(Checker):
if
anilist_id
is
not
None
:
anilist_ids
.
append
(
str
(
anilist_id
))
resp
=
pr
int
(
resp
=
in
pu
t
(
"
{}Set anilist IDs to {}? (y|n){}
"
.
format
(
Fore
.
LIGHTGREEN_EX
,
anilist_ids
,
Style
.
RESET_ALL
)
...
...
@@ -311,15 +311,14 @@ class TvSeriesChecker(Checker):
# check if amount of episode files is correct
# Note: Make sure to think of multi-episodes etc
def
_generate_ignores_map
(
self
)
->
Dict
[
int
,
int
]:
def
_generate_ignores_map
(
self
)
->
Dict
[
int
,
List
[
int
]
]
:
"""
Generates a dictionary mapping the excluded episode number to their
respective episodes.
:return: The generated dictionary: {season: [episodes]}
"""
metadata
=
self
.
metadata
# type: TvSeries
ignores
=
{}
metadata
=
self
.
metadata
# type: TvSeries # type: ignore
ignores
=
{}
# type: Dict[int, List[int]]
excluded
=
metadata
.
excludes
.
get
(
IdType
.
TVDB
,
{})
multis
=
metadata
.
multi_episodes
.
get
(
IdType
.
TVDB
,
{})
...
...
@@ -358,7 +357,7 @@ class TvSeriesChecker(Checker):
:param series_name_override: Overrides the series name
:return: The generated name
"""
metadata
=
self
.
metadata
# type: TvSeries
metadata
=
self
.
metadata
# type: TvSeries
# type: ignore
multis
=
metadata
.
multi_episodes
.
get
(
IdType
.
TVDB
,
{})
series_name
=
metadata
.
name
...
...
toktokkie/check/VisualNovelChecker.py
View file @
b2a6bbd1
...
...
@@ -66,7 +66,7 @@ class VisualNovelChecker(Checker):
file.
:return: The check result
"""
metadata
=
self
.
metadata
# type: VisualNovel
metadata
=
self
.
metadata
# type: VisualNovel
# type: ignore
valid
=
True
if
metadata
.
has_op
and
metadata
.
ops
is
None
:
...
...
toktokkie/gui/widgets/BookWidget.py
View file @
b2a6bbd1
...
...
@@ -17,6 +17,7 @@ You should have received a copy of the GNU General Public License
along with toktokkie. If not, see <http://www.gnu.org/licenses/>.
LICENSE
"""
from
typing
import
Optional
from
subprocess
import
Popen
from
PyQt5.QtGui
import
QPixmap
from
PyQt5.QtWidgets
import
QMainWindow
,
QWidget
...
...
@@ -37,7 +38,7 @@ class BookWidget(QWidget, Ui_BookWidget):
"""
super
().
__init__
(
parent
)
self
.
setupUi
(
self
)
self
.
metadata
=
None
# type: Book
self
.
metadata
=
None
# type:
Optional[
Book
]
self
.
initialize_buttons
()
def
initialize_buttons
(
self
):
...
...
toktokkie/gui/widgets/MovieWidget.py
View file @
b2a6bbd1
...
...
@@ -17,6 +17,7 @@ You should have received a copy of the GNU General Public License
along with toktokkie. If not, see <http://www.gnu.org/licenses/>.
LICENSE
"""
from
typing
import
Optional
from
subprocess
import
Popen
from
PyQt5.QtGui
import
QPixmap
from
PyQt5.QtWidgets
import
QMainWindow
,
QWidget
...
...
@@ -37,7 +38,7 @@ class MovieWidget(QWidget, Ui_MovieWidget):
"""
super
().
__init__
(
parent
)
self
.
setupUi
(
self
)
self
.
metadata
=
None
# type: Movie
self
.
metadata
=
None
# type:
Optional[
Movie
]
self
.
initialize_buttons
()
def
initialize_buttons
(
self
):
...
...
toktokkie/gui/widgets/TvSeasonWidget.py
View file @
b2a6bbd1
...
...
@@ -21,6 +21,7 @@ import os
import
tvdb_api
import
requests
import
webbrowser
from
typing
import
Optional
from
subprocess
import
Popen
from
threading
import
Thread
from
PyQt5.QtGui
import
QPixmap
...
...
@@ -43,8 +44,8 @@ class TvSeasonWidget(QWidget, Ui_TvSeasonWidget):
"""
super
().
__init__
(
parent
)
self
.
setupUi
(
self
)
self
.
metadata
=
None
# type: TvSeries
self
.
season
=
None
# type: TvSeason
self
.
metadata
=
None
# type:
Optional[
TvSeries
]
self
.
season
=
None
# type:
Optional[
TvSeason
]
self
.
initialize_buttons
()
def
initialize_buttons
(
self
):
...
...
toktokkie/gui/widgets/TvSeriesWidget.py
View file @
b2a6bbd1
...
...
@@ -19,6 +19,7 @@ LICENSE"""
import
tvdb_api
import
webbrowser
from
typing
import
Optional
from
subprocess
import
Popen
from
threading
import
Thread
from
PyQt5.QtGui
import
QPixmap
...
...
@@ -40,7 +41,7 @@ class TvSeriesWidget(QWidget, Ui_TvSeriesWidget):
"""
super
().
__init__
(
parent
)
self
.
setupUi
(
self
)
self
.
metadata
=
None
# type: TvSeries
self
.
metadata
=
None
# type:
Optional[
TvSeries
]
self
.
initialize_buttons
()
def
initialize_buttons
(
self
):
...
...
toktokkie/iconizing/__init__.py
View file @
b2a6bbd1
...
...
@@ -20,12 +20,13 @@ LICENSE"""
from
typing
import
Type
from
toktokkie.iconizing.procedures.Procedure
import
Procedure
from
toktokkie.iconizing.procedures.GnomeProcedure
import
GnomeProcedure
from
toktokkie.iconizing.procedures.NoopProcedure
import
NoopProcedure
from
toktokkie.iconizing.Iconizer
import
Iconizer
procedures
=
[
GnomeProcedure
]
def
default_procedure
()
->
Type
[
Procedure
]
or
None
:
def
default_procedure
()
->
Type
[
Procedure
]:
"""
Checks all available procedures for eligibility
:return: The eligible procedure or None if none were found
...
...
@@ -33,4 +34,4 @@ def default_procedure() -> Type[Procedure] or None:
for
procedure
in
procedures
:
if
procedure
.
is_applicable
():
return
procedure
return
No
n
e
return
No
opProcedur
e
toktokkie/iconizing/procedures/GnomeProcedure.py
View file @
b2a6bbd1
...
...
@@ -79,3 +79,6 @@ class GnomeProcedure(Procedure):
and
gvfs_installed
and
gvfs_check
except
KeyError
:
# pragma: no cover
return
False
else
:
return
False
toktokkie/
metadata/prompt/PromptTyp
e.py
→
toktokkie/
iconizing/procedures/NoopProcedur
e.py
View file @
b2a6bbd1
...
...
@@ -17,32 +17,31 @@ You should have received a copy of the GNU General Public License
along with toktokkie. If not, see <http://www.gnu.org/licenses/>.
LICENSE
"""
from
toktokkie.iconizing.procedures.Procedure
import
Procedure
class
PromptType
:
"""
Class that allows the automatic conversion of user-provided strings to
values
"""
def
__init__
(
self
,
value
:
str
):
class
NoopProcedure
(
Procedure
):
"""
I
nitializes the prompt type
:param value: The string value to pars
e
I
conizing Procedure that does nothing. Used as a fallback when no
other procedures are availabl
e
"""
self
.
_value
=
value
# noinspection PyStatementEffect
self
.
value
@
property
def
value
(
self
):
@
classmethod
def
iconize
(
cls
,
directory
:
str
,
icon_path_no_ext
):
"""
Converts the string value into its actual value
:return: The generated value
Doesn
'
t do anything
:param directory: The directory to iconize
:param icon_path_no_ext: The icon file without a file extension.
.png will be appended
:return: None
"""
raise
NotImplementedError
()
pass
def
__str__
(
self
)
->
str
:
@classmethod
def
is_applicable
(
cls
)
->
bool
:
"""
:return: A string representation of this object
Checks if this procedure is applicable to the current system.
NOOP Procedure is always applicable
:return: True if applicable, else False
"""
return
str
(
self
.
val
ue
)
return
Tr
ue
toktokkie/metadata/Book.py
View file @
b2a6bbd1
...
...
@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with toktokkie. If not, see <http://www.gnu.org/licenses/>.
LICENSE
"""
import
os
from
typing
import
Dict
,
Any
from
toktokkie.metadata.Metadata
import
Metadata
from
toktokkie.metadata.components.enums
import
MediaType
...
...
@@ -35,16 +35,22 @@ class Book(Metadata):
return
MediaType
.
BOOK
@classmethod
def
prompt
(
cls
,
directory_path
:
str
)
->
Metadata
:
def
_prompt
(
cls
,
directory_path
:
str
,
json_data
:
Dict
[
str
,
Any
])
\
->
Dict
[
str
,
Any
]:
"""
Generates a new Metadata object using prompts for a directory
Prompts the user for metadata-type-specific information
Should be extended by child classes
:param directory_path: The path to the directory for which to generate
the metadata object
:return: The generated metadata object
"""
print
(
"
Generating metadata for {}:
"
.
format
(
os
.
path
.
basename
(
directory_path
)))
return
cls
(
directory_path
,
{
"
ids
"
:
cls
.
prompt_for_ids
(),
"
type
"
:
cls
.
media_type
().
value
})
the metadata
:param json_data: Previously generated JSON data
:return: The generated metadata JSON data
"""
return
{}
def
_validate_json
(
self
):
"""
Validates the JSON data to make sure everything has valid values
:raises InvalidMetadataException: If any errors were encountered
:return: None
"""
pass
toktokkie/metadata/BookSeries.py
View file @
b2a6bbd1
...
...
@@ -18,7 +18,7 @@ along with toktokkie. If not, see <http://www.gnu.org/licenses/>.
LICENSE
"""
import
os
from
typing
import
List
from
typing
import
List
,
Dict
,
Any
from
puffotter.os
import
listdir
from
toktokkie.metadata.Book
import
Book
from
toktokkie.metadata.components.BookVolume
import
BookVolume
...
...
@@ -39,16 +39,16 @@ class BookSeries(Book):
return
MediaType
.
BOOK_SERIES
@classmethod
def
prompt
(
cls
,
directory_path
:
str
)
->
Book
:
def
_prompt
(
cls
,
directory_path
:
str
,
json_data
:
Dict
[
str
,
Any
])
\
->
Dict
[
str
,
Any
]:
"""
Generates a new Metadata object using prompts for a directory
Prompts the user for metadata-type-specific information
Should be extended by child classes
:param directory_path: The path to the directory for which to generate
the metadata object
:return: The generated metadata object
the metadata
:param json_data: Previously generated JSON data
:return: The generated metadata JSON data
"""
print
(
"
Generating metadata for {}:
"
.
format
(
os
.
path
.
basename
(
directory_path
)))
series_ids
=
cls
.
prompt_for_ids
()
series
=
cls
(
directory_path
,
{
"
volumes
"
:
[],
...
...
@@ -80,9 +80,9 @@ class BookSeries(Book):
})
series
.
volumes
=
volumes
return
series
return
series
.
json
@property
@property
# type: ignore
@json_parameter
def
volumes
(
self
)
->
List
[
BookVolume
]:
"""
...
...
@@ -109,3 +109,11 @@ class BookSeries(Book):
self
.
json
[
"
volumes
"
]
=
{}
for
i
,
volume
in
enumerate
(
volumes
):
self
.
json
[
"
volumes
"
][
i
]
=
volume
.
json
def
_validate_json
(
self
):
"""
Validates the JSON data to make sure everything has valid values
:raises InvalidMetadataException: If any errors were encountered
:return: None
"""
raise
NotImplementedError
()
toktokkie/metadata/Manga.py
View file @
b2a6bbd1
...
...
@@ -18,11 +18,11 @@ along with toktokkie. If not, see <http://www.gnu.org/licenses/>.
LICENSE
"""
import
os
from
typing
import
List
from
typing
import
List
,
Dict
,
Any
from
toktokkie.metadata.Metadata
import
Metadata
from
toktokkie.metadata.components.enums
import
MediaType
from
toktokkie.metadata.prompt.CommaList
import
CommaList
from
toktokkie.metadata.helper.wrappers
import
json_parameter
from
puffotter.prompt
import
prompt_comma_list
class
Manga
(
Metadata
):
...
...
@@ -38,15 +38,16 @@ class Manga(Metadata):
return
MediaType
.
MANGA
@classmethod
def
prompt
(
cls
,
directory_path
:
str
)
->
Metadata
:
def
_prompt
(
cls
,
directory_path
:
str
,
json_data
:
Dict
[
str
,
Any
])
\
->
Dict
[
str
,
Any
]:
"""
Generates a new Metadata object using prompts for a directory
Prompts the user for metadata-type-specific information
Should be extended by child classes
:param directory_path: The path to the directory for which to generate
the metadata object
:return: The generated metadata object
the metadata
:param json_data: Previously generated JSON data
:return: The generated metadata JSON data
"""
print
(
"
Generating metadata for {}:
"
.
format
(
os
.
path
.
basename
(
directory_path
)))
series
=
cls
(
directory_path
,
{
"
ids
"
:
cls
.
prompt_for_ids
(),
"
type
"
:
cls
.
media_type
().
value
,
...
...
@@ -57,12 +58,11 @@ class Manga(Metadata):
print
(
"
Please enter identifiers for special chapters:
"
)
for
_file
in
sorted
(
os
.
listdir
(
series
.
special_path
)):
print
(
_file
)
series
.
special_chapters
=
cls
.
input
(
"
Special Chapters
"
,
CommaList
(
""
),
CommaList
).
value
return
series
series
.
special_chapters
=
prompt_comma_list
(
"
Special Chapters
"
)
@property
return
series
.
json
@property
# type: ignore
def
main_path
(
self
)
->
str
:
"""
The path to the main manga directory
...
...
@@ -70,7 +70,7 @@ class Manga(Metadata):
"""
return
os
.
path
.
join
(
self
.
directory_path
,
"
Main
"
)
@property
@property
# type: ignore
def
special_path
(
self
)
->
str
:
"""
The path to the special manga directory
...
...
@@ -78,7 +78,7 @@ class Manga(Metadata):
"""
return
os
.
path
.
join
(
self
.
directory_path
,
"
Special
"
)
@property
@property
# type: ignore
@json_parameter
def
special_chapters
(
self
)
->
List
[
str
]:
"""
...
...
@@ -96,3 +96,11 @@ class Manga(Metadata):
max_len
=
len
(
max
(
special_chapters
,
key
=
lambda
x
:
len
(
x
)))
special_chapters
.
sort
(
key
=
lambda
x
:
x
.
zfill
(
max_len
))
self
.
json
[
"
special_chapters
"
]
=
special_chapters
def
_validate_json
(
self
):
"""
Validates the JSON data to make sure everything has valid values
:raises InvalidMetadataException: If any errors were encountered
:return: None
"""
raise
NotImplementedError
()
toktokkie/metadata/Metadata.py
View file @
b2a6bbd1
...
...
@@ -19,17 +19,14 @@ LICENSE"""
import
os
import
json
from
enum
import
Enum
from
typing
import
List
,
Dict
,
Any
,
Optional
from
toktokkie.metadata.helper.wrappers
import
json_parameter
from
toktokkie.metadata.prompt.PromptType
import
PromptType
from
toktokkie.metadata.prompt.CommaList
import
CommaList
from
toktokkie.exceptions
import
InvalidMetadata
,
\
MissingMetadata
from
toktokkie.exceptions
import
InvalidMetadata
,
MissingMetadata
from
toktokkie.metadata.components.enums
import
MediaType
,
IdType
from
anime_list_apis.api.AnilistApi
import
AnilistApi
from
anime_list_apis.models.attributes.MediaType
import
MediaType
as
\
AnimeListMediaType
from
puffotter.prompt
import
prompt_comma_list
class
Metadata
:
...
...
@@ -37,6 +34,33 @@ class Metadata:
Class that acts as the base class for all possible metadata types
"""
def
__init__
(
self
,
directory_path
:
str
,
json_data
:
Optional
[
Dict
[
str
,
Any
]]
=
None
):
"""
Inititalizes the metadata object using JSON data
:param directory_path: The directory of the media for which to
generate the metadata
:param json_data: Optional metadata JSON.
Will be used instead of info.json metadata
if provided
:raises InvalidMetadataException: if the metadata could not be
parsed correctly
"""
self
.
directory_path
=
directory_path
self
.
metadata_file
=
os
.
path
.
join
(
directory_path
,
"
.meta/info.json
"
)
self
.
icon_directory
=
os
.
path
.
join
(
directory_path
,
"
.meta/icons
"
)
if
json_data
is
None
:
with
open
(
self
.
metadata_file
,
"
r
"
)
as
info
:
self
.
json
=
json
.
load
(
info
)
else
:
self
.
json
=
json_data
self
.
validate_json
()
def
__str__
(
self
)
->
str
:
"""
:return: A string representation of the metadata
...
...
@@ -63,7 +87,8 @@ class Metadata:
str
(
self
.
json
)
)
@property
@property
# type: ignore
@json_parameter
def
name
(
self
)
->
str
:
"""
:return: The name of the media
...
...
@@ -81,7 +106,7 @@ class Metadata:
os
.
rename
(
self
.
directory_path
,
new_path
)
self
.
directory_path
=
new_path
@property
@property
# type: ignore
@json_parameter
def
tags
(
self
)
->
List
[
str
]:
"""
...
...
@@ -98,9 +123,9 @@ class Metadata:
"""
self
.
json
[
"
tags
"
]
=
tags
@property
@property
# type: ignore
@json_parameter
def
ids
(
self
)
->
Dict
[
Enum
,
List
[
str
]]:
def
ids
(
self
)
->
Dict
[
IdType
,
List
[
str
]]:
"""
:return: A dictionary containing lists of IDs mapped to ID types
"""
...
...
@@ -115,7 +140,7 @@ class Metadata:
return
generated
@ids.setter
def
ids
(
self
,
ids
:
Dict
[
Enum
,
List
[
str
]]):
def
ids
(
self
,
ids
:
Dict
[
IdType
,
List
[
str
]]):
"""
Setter method for the IDs of the metadata object.
Previous IDs will be overwritten!
...
...
@@ -145,7 +170,7 @@ class Metadata:
IdType
.
MYANIMELIST
,
IdType
.
KITSU
],
MediaType
.
TV
:
[
MediaType
.
TV
_SERIES
:
[
IdType
.
ANILIST
,
IdType
.
KITSU
,
IdType
.
MYANIMELIST
,
...
...
@@ -174,33 +199,6 @@ class Metadata:
]
}[
cls
.
media_type
()]
def
__init__
(
self
,
directory_path
:
str
,
json_data
:
Optional
[
Dict
[
str
,
Any
]]
=
None
):
"""
Inititalizes the metadata object using JSON data
:param directory_path: The directory of the media for which to
generate the metadata
:param json_data: Optional metadata JSON.
Will be used instead of info.json metadata
if provided
:raises InvalidMetadataException: if the metadata could not be
parsed correctly
"""
self
.
directory_path
=
directory_path
self
.
metadata_file
=
os
.
path
.
join
(
directory_path
,
"
.meta/info.json
"
)
self
.
icon_directory
=
os
.
path
.
join
(
directory_path
,
"
.meta/icons
"
)
if
json_data
is
None
:
with
open
(
self
.
metadata_file
,
"
r
"
)
as
info
:
self
.
json
=
json
.
load
(
info
)
else
:
self
.
json
=
json_data
self
.
validate_json
()
def
validate_json
(
self
):
"""
Validates the JSON data to make sure everything has valid values
...
...
@@ -214,6 +212,16 @@ class Metadata:
self
.
_assert_true
(
len
(
self
.
ids
)
==
len
(
self
.
json
[
"
ids
"
]))
self
.
_assert_true
(
len
(
self
.
ids
)
>
0
)
self
.
_assert_true
(
self
.
media_type
().
value
==
self
.
json
[
"
type
"
])
self
.
_validate_json
()
def
_validate_json
(
self
):
"""
Validates the JSON data to make sure everything has valid values
Should be implemented by child classes
:raises InvalidMetadataException: If any errors were encountered
:return: None
"""
raise
NotImplementedError
()
def
write
(
self
):
"""
...
...
@@ -234,13 +242,46 @@ class Metadata:
))
@classmethod
def
prompt
(
cls
,
directory_path
:
str
):
def
prompt
(
cls
,
directory_path
:
str
)
->
"
Metadata
"
:
"""
Generates a new Metadata object using prompts for a directory
:param directory_path: The path to the directory for which to generate
the metadata object
:return: The generated metadata object
"""
print
(
"
Generating metadata for {}:
"
.
format
(
os
.
path
.
basename
(
directory_path
)))
idmap
=
{
MediaType
.
MANGA
:
[],
MediaType
.
BOOK
:
[],
MediaType
.
BOOK_SERIES
:
[],
MediaType
.
VISUAL_NOVEL
:
[
IdType
.
VNDB
],
MediaType
.
MOVIE
:
[],
MediaType
.
TV_SERIES
:
[
IdType
.
TVDB
]
}
# type: Dict[MediaType, List[IdType]]
required_ids
=
idmap
[
cls
.
media_type
()]
json_data
=
{
"
type
"
:
cls
.
media_type
().
value
,
"
tags
"
:
prompt_comma_list
(
"
Tags:
"
),
"
ids
"
:
cls
.
prompt_for_ids
(
required
=
required_ids
)
}
json_data
.
update
(
cls
.
_prompt
(
directory_path
,
json_data
))
return
cls
(
directory_path
,
json_data
)
@classmethod
def
_prompt
(
cls
,
directory_path
:
str
,
json_data
:
Dict
[
str
,
Any
])
\
->
Dict
[
str
,
Any
]:
"""
Prompts the user for metadata-type-specific information
Should be extended by child classes
:param directory_path: The path to the directory for which to generate
the metadata
:param json_data: Previously generated JSON data
:return: The generated metadata JSON data
"""
raise
NotImplementedError
()
@classmethod
...
...
@@ -266,46 +307,11 @@ class Metadata:
except
json
.
JSONDecodeError
:
raise
InvalidMetadata
()
@classmethod
def
input
(
cls
,
prompt_text
:
str
,
default
:
Optional
[
PromptType
],
_type
:
type
(
PromptType
),
required
:
bool
=
False
)
->
Any
:
"""
Creates a user prompt that supports default options and automatic
type conversions.
:param prompt_text: The text to prompt the user
:param default: The default value to use
:param _type: The type of the prompted value
:param required: Whether or not a response is required
:return: The user
'
s response
"""
if
default
is
not
None
:
prompt_text
+=
"
{}
"
.
format
(
str
(
default
))
prompt_text
+=
"
:
"
response
=
input
(
prompt_text
).
strip
()
while
response
==
""
and
default
is
None
:
response
=
input
(
prompt_text
).
strip
()
if
response
==
""
and
default
is
not
None
:
return
default
elif
response
==
""
and
required
:
return
cls
.
input
(
prompt_text
,
default
,
_type
,
required
)
else
:
try
:
return
_type
(
response
)
except
(
TypeError
,
ValueError
):
return
cls
.
input
(
prompt_text
,
default
,
_type
,
required
)
@classmethod
def
prompt_for_ids
(
cls
,
defaults
:
Optional
[
Dict
[
str
,
List
[
str
]]]
=
None
,
required
:
Optional
[
List
[
Enum
]]
=
None
required
:
Optional
[
List
[
IdType
]]
=
None
)
->
Dict
[
str
,
List
[
str
]]:
"""
Prompts the user for IDs
...
...
@@ -315,17 +321,18 @@ class Metadata:
"""
required
=
required
if
required
is
not
None
else
[]
ids
=
{}
ids
=
{}
# type: Dict[str, List[str]]
mal_updated
=
False
while
len
(
ids
)
<
1
:
for
id_type
in
cls
.
valid_id_types
():
default
=
None
# type: Optional[List[str]]
if
defaults
is
not
None
:
default
=
defaults
.
get
(
id_type
.
value
,
[])
default
=
CommaList
(
"
,
"
.
join
(
default
))
else
:
default
=
None
if
id_type
in
required
else
CommaList
(
""
)
elif
id_type
not
in
required
:
default
=
[]
# Load anilist ID from myanimelist ID
if
IdType
.
MYANIMELIST
.
value
in
ids
and
mal_updated
:
if
id_type
.
value
==
IdType
.
ANILIST
.
value
:
...
...
@@ -346,41 +353,26 @@ class Metadata:
int
(
mal_id
)
)
anilist_ids
.
append
(
str
(
anilist_id
))
default
=
CommaList
(
"
,
"
.
join
(
anilist_ids
))
prompted
=
cls
.
input
(
"
{} IDs
"
.
format
(
id_type
.
value
),
default
,
CommaList
,
required
=
(
id_type
in
required
)
).
value
default
=
anilist_ids
prompted
=
list
(
filter
(
lambda
x
:
x
!=
""
,
prompted
))
min_count
=
1
if
id_type
in
required
else
0
prompted
=
prompt_comma_list
(
"
{} IDs
"
.
format
(
id_type
.
value
),
min_count
=
min_count
)
if
len
(
prompted
)
>
0
:
ids
[
id_type
.
value
]
=
prompted
# Check if myanimelist ID was updated
if
id_type
.
value
==
IdType
.
MYANIMELIST
.
value
:
if
default
.
value
is
not
None
:
mal_updated
=
prompted
!=
default
.
value
if
default
is
not
None
:
mal_updated
=
prompted
!=
default
if
len
(
ids
)
<
1
:
print
(
"
Please provide at least one ID
"
)
return
ids
@staticmethod
def
_assert_true
(
condition
:
bool
):
"""
Makes sure a statement is true by raising an exception if it
'
s not
:param condition: The condition to check
:raises InvalidMetadataException: If the condition was False
:return: Nine
"""
if
not
condition
:
raise
InvalidMetadata
()
def
print_folder_icon_source
(
self
):
"""
Prints a message with a URL for possible folder icons on deviantart
...
...
@@ -420,3 +412,14 @@ class Metadata:
for
_id
in
self
.
ids
.
get
(
IdType
.
ANILIST
,
[]):
urls
.
append
(
"
https://anilist.co/{}/{}
"
.
format
(
media_type
,
_id
))
return
urls
@staticmethod
def
_assert_true
(
condition
:
bool
):
"""
Makes sure a statement is true by raising an exception if it
'
s not
:param condition: The condition to check
:raises InvalidMetadataException: If the condition was False
:return: Nine
"""
if
not
condition
:
raise
InvalidMetadata
()
toktokkie/metadata/Movie.py
View file @
b2a6bbd1
...
...
@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with toktokkie. If not, see <http://www.gnu.org/licenses/>.
LICENSE
"""
import
os
from
typing
import
Dict
,
Any
from
toktokkie.metadata.Metadata
import
Metadata
from
toktokkie.metadata.components.enums
import
MediaType
...
...
@@ -35,16 +35,22 @@ class Movie(Metadata):
return
MediaType
.
MOVIE
@classmethod
def
prompt
(
cls
,
directory_path
:
str
)
->
Metadata
:
def
_prompt
(
cls
,
directory_path
:
str
,
json_data
:
Dict
[
str
,
Any
])
\
->
Dict
[
str
,
Any
]:
"""
Generates a new Metadata object using prompts for a directory
Prompts the user for metadata-type-specific information
Should be extended by child classes
:param directory_path: The path to the directory for which to generate
the metadata object
:return: The generated metadata object
"""
print
(
"
Generating metadata for {}:
"
.
format
(
os
.
path
.
basename
(
directory_path
)))
return
cls
(
directory_path
,
{
"
ids
"
:
cls
.
prompt_for_ids
(),
"
type
"
:
cls
.
media_type
().
value
})
the metadata
:param json_data: Previously generated JSON data
:return: The generated metadata JSON data
"""
return
{}
def
_validate_json
(
self
):
"""
Validates the JSON data to make sure everything has valid values
:raises InvalidMetadataException: If any errors were encountered
:return: None
"""
pass
toktokkie/metadata/TvSeries.py
View file @
b2a6bbd1
...
...
@@ -19,7 +19,7 @@ LICENSE"""
import
os
import
tvdb_api
from
typing
import
List
,
Dict
from
typing
import
List
,
Dict
,
Any
,
Optional
from
toktokkie.metadata.Metadata
import
Metadata
from
toktokkie.metadata.helper.wrappers
import
json_parameter
from
toktokkie.metadata.components.TvSeason
import
TvSeason
...
...
@@ -42,21 +42,24 @@ class TvSeries(Metadata):
return
MediaType
.
TV_SERIES
@classmethod
def
prompt
(
cls
,
directory_path
:
str
)
->
Metadata
:
def
_prompt
(
cls
,
directory_path
:
str
,
json_data
:
Dict
[
str
,
Any
])
\
->
Dict
[
str
,
Any
]:
"""
Generates a new Metadata object using prompts for a directory
Prompts the user for metadata-type-specific information
Should be extended by child classes
:param directory_path: The path to the directory for which to generate
the metadata object
:return: The generated metadata object
the metadata
:param json_data: Previously generated JSON data
:return: The generated metadata JSON data
"""
name
=
os
.
path
.
basename
(
directory_path
)
print
(
"
Generating metadata for {}:
"
.
format
(
name
))
probable_defaults
=
None
# type: Optional[Dict[str, List[str]]]
try
:
probable_tvdb_id
=
str
(
tvdb_api
.
Tvdb
()[
name
].
data
[
"
id
"
])
probable_defaults
=
{
IdType
.
TVDB
.
value
:
[
probable_tvdb_id
]}
except
(
tvdb_api
.
tvdb_shownotfound
,
TypeError
):
p
robable_defaults
=
None
p
ass
series_ids
=
cls
.
prompt_for_ids
(
defaults
=
probable_defaults
,
...
...
@@ -89,9 +92,9 @@ class TvSeries(Metadata):
}))
series
.
seasons
=
seasons
return
series
return
series
.
json
@property
@property
# type: ignore
@json_parameter
def
tvdb_id
(
self
)
->
str
:
"""
...
...
@@ -99,7 +102,7 @@ class TvSeries(Metadata):
"""
return
self
.
ids
[
IdType
.
TVDB
][
0
]
@property
@property
# type: ignore
@json_parameter
def
seasons
(
self
)
->
List
[
TvSeason
]:
"""
...
...
@@ -137,7 +140,7 @@ class TvSeries(Metadata):
return
season
raise
KeyError
(
season_name
)
@property
@property
# type: ignore
@json_parameter
def
excludes
(
self
)
->
Dict
[
IdType
,
Dict
[
int
,
List
[
int
]]]:
"""
...
...
@@ -145,7 +148,7 @@ class TvSeries(Metadata):
:return A dictionary mapping episode info to seasons and id types
Form: {idtype: {season: [ep1, ep2]}}
"""
generated
=
{}
generated
=
{}
# type: Dict[IdType, Dict[int, List[int]]]
for
_id_type
in
self
.
json
.
get
(
"
excludes
"
,
{}):
...
...
@@ -167,7 +170,7 @@ class TvSeries(Metadata):
return
generated
@property
@property
# type: ignore
@json_parameter
def
season_start_overrides
(
self
)
->
Dict
[
IdType
,
Dict
[
int
,
int
]]:
"""
...
...
@@ -175,7 +178,7 @@ class TvSeries(Metadata):
point to ID types
Form: {idtype: {season: episode}}
"""
generated
=
{}
generated
=
{}
# type: Dict[IdType, Dict[int, int]]
for
_id_type
,
overrides
in
\
self
.
json
.
get
(
"
season_start_overrides
"
,
{}).
items
():
...
...
@@ -191,14 +194,14 @@ class TvSeries(Metadata):
return
generated
@property
@property
# type: ignore
@json_parameter
def
multi_episodes
(
self
)
->
Dict
[
IdType
,
Dict
[
int
,
Dict
[
int
,
int
]]]:
"""
:return: A dictionary mapping lists of multi-episodes to id types
Form: {idtype: {season: {start: end}}}
"""
generated
=
{}
generated
=
{}
# type: Dict[IdType, Dict[int, Dict[int, int]]]
for
_id_type
in
self
.
json
.
get
(
"
multi_episodes
"
,
{}):
...
...
@@ -268,13 +271,12 @@ class TvSeries(Metadata):
"
episode
"
:
episode
})
def
validate_json
(
self
):
def
_
validate_json
(
self
):
"""
Validates the JSON data to make sure everything has valid values
:raises InvalidMetadataException: If any errors were encountered
:return: None
"""
super
().
validate_json
()
self
.
_assert_true
(
"
seasons
"
in
self
.
json
)
self
.
_assert_true
(
len
(
self
.
seasons
)
==
len
(
self
.
json
[
"
seasons
"
]))
self
.
_assert_true
(
...
...
@@ -304,7 +306,7 @@ class TvSeries(Metadata):
:return: The generated dictionary. It will have the following form:
{tvdb_id: {season_number: [episode_files]}}
"""
content_info
=
{}
content_info
=
{}
# type: Dict[str, Dict[int, List[str]]]
for
season_name
in
os
.
listdir
(
self
.
directory_path
):
...
...
toktokkie/metadata/VisualNovel.py
View file @
b2a6bbd1
...
...
@@ -18,11 +18,11 @@ along with toktokkie. If not, see <http://www.gnu.org/licenses/>.
LICENSE
"""
import
os
from
typing
import
Optional
,
List
,
Dict
from
typing
import
Optional
,
List
,
Dict
,
Any
from
puffotter.os
import
listdir
from
toktokkie.metadata.Metadata
import
Metadata
from
toktokkie.metadata.helper.wrappers
import
json_parameter
from
toktokkie.metadata.components.enums
import
IdType
,
MediaType
from
toktokkie.metadata.components.enums
import
MediaType
class
VisualNovel
(
Metadata
):
...
...
@@ -38,21 +38,19 @@ class VisualNovel(Metadata):
return
MediaType
.
VISUAL_NOVEL
@classmethod
def
prompt
(
cls
,
directory_path
:
str
)
->
Metadata
:
def
_prompt
(
cls
,
directory_path
:
str
,
json_data
:
Dict
[
str
,
Any
])
\
->
Dict
[
str
,
Any
]:
"""
Generates a new Metadata object using prompts for a directory
Prompts the user for metadata-type-specific information
Should be extended by child classes
:param directory_path: The path to the directory for which to generate
the metadata object
:return: The generated metadata object
the metadata
:param json_data: Previously generated JSON data
:return: The generated metadata JSON data
"""
print
(
"
Generating metadata for {}:
"
.
format
(
os
.
path
.
basename
(
directory_path
)))
return
cls
(
directory_path
,
{
"
ids
"
:
cls
.
prompt_for_ids
(
required
=
[
IdType
.
VNDB
]),
"
type
"
:
cls
.
media_type
().
value
})
return
{}
@property
@property
# type: ignore
@json_parameter
def
has_ed
(
self
)
->
bool
:
"""
...
...
@@ -69,7 +67,7 @@ class VisualNovel(Metadata):
"""
self
.
json
[
"
has_ed
"
]
=
has_ed
@property
@property
# type: ignore
@json_parameter
def
has_op
(
self
)
->
bool
:
"""
...
...
@@ -86,7 +84,7 @@ class VisualNovel(Metadata):
"""
self
.
json
[
"
has_op
"
]
=
has_op
@property
@property
# type: ignore
@json_parameter
def
has_cgs
(
self
)
->
bool
:
"""
...
...
@@ -103,7 +101,7 @@ class VisualNovel(Metadata):
"""
self
.
json
[
"
has_cgs
"
]
=
has_cgs
@property
@property
# type: ignore
@json_parameter
def
has_ost
(
self
)
->
bool
:
"""
...
...
@@ -187,3 +185,11 @@ class VisualNovel(Metadata):
return
None
else
:
return
files
def
_validate_json
(
self
):
"""
Validates the JSON data to make sure everything has valid values
:raises InvalidMetadataException: If any errors were encountered
:return: None
"""
pass
toktokkie/metadata/components/TvSeason.py
View file @
b2a6bbd1
...
...
@@ -65,4 +65,4 @@ class TvSeason(MetadataPart):
"""
:return: Whether or not this season is a spinoff
"""
return
self
.
parent
.
tvdb_id
!=
self
.
tvdb_id
return
self
.
parent
.
tvdb_id
!=
self
.
tvdb_id
# type: ignore
Prev
1
2
Next