Commit 120bddfb authored by Hermann Krumrey's avatar Hermann Krumrey

Some more metadata refactoring

parent 5ace7623
......@@ -24,7 +24,7 @@ from typing import Dict, Any
from puffotter.prompt import yn_prompt
from toktokkie.renaming.Renamer import Renamer
from toktokkie.iconizing.Iconizer import Iconizer, Procedure
from toktokkie.metadata.helper.functions import get_metadata, create_metadata
from toktokkie.metadata.functions import get_metadata, create_metadata
from toktokkie.metadata.MediaType import MediaType
from toktokkie.exceptions import MissingMetadata
from toktokkie.xdcc_update.XDCCUpdater import XDCCUpdater
......
......@@ -23,7 +23,8 @@ from typing import Dict, Optional, List
from datetime import datetime
from colorama import Fore, Style
from toktokkie.check.Checker import Checker
from toktokkie.renaming.Renamer import Renamer
from toktokkie.renaming.functions import load_tvdb_episode_name, \
generate_tv_episode_filename
from toktokkie.renaming.RenameOperation import RenameOperation
from toktokkie.metadata.types.TvSeries import TvSeries
from toktokkie.metadata.ids.IdType import IdType
......@@ -369,12 +370,12 @@ class TvSeriesChecker(Checker):
return RenameOperation.sanitize(
metadata.directory_path,
Renamer.generate_tv_episode_filename(
generate_tv_episode_filename(
"",
series_name,
season_number,
episode_number,
Renamer.load_tvdb_episode_name(
load_tvdb_episode_name(
tvdb_id,
season_number,
episode_number,
......
......@@ -22,10 +22,12 @@ import json
import logging
from typing import List, Dict, Any, Optional
from jsonschema import validate, ValidationError
from toktokkie.exceptions import InvalidMetadata, MissingMetadata
from toktokkie.exceptions import InvalidMetadata
from toktokkie.metadata.schema.SchemaBuilder import SchemaBuilder
from toktokkie.metadata.ids.IdType import IdType
from toktokkie.metadata.ids.mappings import valid_id_types
from toktokkie.metadata.ids.functions import objectify_ids, stringify_ids, \
fill_ids, minimize_ids
from toktokkie.metadata.MediaType import MediaType
from toktokkie.metadata.prompt.Prompter import Prompter
......@@ -171,29 +173,6 @@ class Metadata:
json_data = prompter.prompt()
return cls(directory_path, json_data)
@classmethod
def from_directory(cls, directory_path: str):
"""
Generates metadata for an existing media directory
:param directory_path: The path to the media directory
:raises InvalidMetadataException: if the metadata could not be
parsed correctly
:raises MissingMetadataException: if no metadata file was found
:return: The generated metadata object
"""
metadata_file = os.path.join(directory_path, ".meta/info.json")
if not os.path.isfile(metadata_file):
raise MissingMetadata()
try:
with open(metadata_file, "r") as info_json:
data = json.load(info_json)
return cls(directory_path, data)
except json.JSONDecodeError:
raise InvalidMetadata()
@property
def name(self) -> str:
"""
......@@ -236,20 +215,10 @@ class Metadata:
"""
:return: A dictionary containing lists of IDs mapped to ID types
"""
generated = {}
for id_type, _id in self.json["ids"].items():
if isinstance(_id, list):
generated[IdType(id_type)] = _id
else:
generated[IdType(id_type)] = [_id]
for id_type in IdType:
if id_type in valid_id_types[self.media_type()]:
if id_type not in generated:
generated[id_type] = []
return generated
return fill_ids(
objectify_ids(self.json["ids"]),
valid_id_types[self.media_type()]
)
@ids.setter
def ids(self, ids: Dict[IdType, List[str]]):
......@@ -259,9 +228,7 @@ class Metadata:
:param ids: The IDs to set
:return: None
"""
self.json["ids"] = {}
for id_type, values in ids.items():
self.json["ids"][id_type.value] = values
self.json["ids"] = stringify_ids(minimize_ids(ids))
def set_ids(self, id_type: IdType, ids: List[str]):
"""
......
......@@ -16,14 +16,3 @@ GNU General Public License for more details.
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.metadata.Metadata import Metadata
from toktokkie.metadata.types.Book import Book
from toktokkie.metadata.types.BookSeries import BookSeries
from toktokkie.metadata.types.Movie import Movie
from toktokkie.metadata.types.Manga import Manga
from toktokkie.metadata.types.TvSeries import TvSeries
from toktokkie.metadata.types.VisualNovel import VisualNovel
from toktokkie.metadata.MediaType import MediaType
from toktokkie.metadata.helper.functions import get_metadata, \
get_metadata_class, create_metadata
"""LICENSE
Copyright 2015 Hermann Krumrey <hermann@krumreyh.com>
This file is part of toktokkie.
toktokkie is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
toktokkie is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
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 Dict, List, Optional
from toktokkie.metadata.ids.IdType import IdType
def stringify_ids(ids: Dict[IdType, List[str]]) -> Dict[str, List[str]]:
"""
Converts the keys in an ID dictionary to strings
:param ids: The ID dictionary to convert
:return: The converted ID dictionary
"""
new_ids = {}
for id_type, values in ids.items():
new_ids[id_type.value] = values
return new_ids
def objectify_ids(ids: Dict[str, List[str]]) -> Dict[IdType, List[str]]:
"""
Converts the keys in an ID dictionary using string keys to IdType objects
:param ids: The ID dictionary to convert
:return: The converted ID dictionary
"""
new_ids = {}
for id_type, values in ids.items():
new_ids[IdType(id_type)] = values
return new_ids
def fill_ids(
ids: Dict[IdType, List[str]],
valid_ids: List[IdType],
_parent_ids: Optional[Dict[IdType, List[str]]] = None
) -> Dict[IdType, List[str]]:
"""
Fills in any missing id type key for an ID dictionary
:param ids: The ID dictionary to fill
:param valid_ids: Any valid ID types
:param _parent_ids: Optionally provided dictionary of parent IDs.
The values of this dictionary will be entered into the
filled dictionary if they aren't defined already
:return: The filled dictionary
"""
parent_ids = {} if _parent_ids is None else _parent_ids
for id_type, values in parent_ids.items():
if id_type not in ids:
ids[id_type] = values
for id_type in valid_ids:
if id_type not in ids:
ids[id_type] = []
return ids
def minimize_ids(
ids: Dict[IdType, List[str]],
_parent_ids: Optional[Dict[IdType, List[str]]] = None
) -> Dict[IdType, List[str]]:
"""
Removes redundant and empty IDs from a dictionary of IDs
:param ids: The dictionary to minimize
:param _parent_ids: Optionally provide a parent's IDs. These IDs will
be removed from the child dictionary if the values
are the same
:return: The minimized dictionary
"""
parent_ids = {} if _parent_ids is None else _parent_ids
minimized = {}
for id_type in ids.keys():
values = ids[id_type]
if values == parent_ids.get(id_type):
continue
if len(ids[id_type]) > 0:
minimized[id_type] = values
return minimized
......@@ -22,6 +22,7 @@ import logging
from typing import Dict, Any, List, Tuple
from puffotter.os import listdir, makedirs
from puffotter.prompt import prompt, yn_prompt, prompt_comma_list
from toktokkie.metadata.ids.functions import objectify_ids
from toktokkie.metadata.ids.IdType import IdType
from toktokkie.metadata.ids.IdFetcher import IdFetcher
from toktokkie.metadata.ids.mappings import valid_id_types, int_id_types, \
......@@ -177,14 +178,11 @@ class Prompter:
:param defaults: The current default IDs
:return: The updated IDs
"""
_defaults = {}
for id_type_str, _ids in defaults.items():
_defaults[IdType(id_type_str)] = _ids
for id_type in valid_ids:
if id_type.value in defaults:
continue
else:
_defaults = objectify_ids(defaults)
ids = self.id_fetcher.fetch_ids(id_type, _defaults)
if ids is not None:
defaults[id_type.value] = ids
......
......@@ -20,7 +20,7 @@ LICENSE"""
from typing import Dict
from puffotter.os import listdir
from toktokkie.metadata.types.Book import Book
from toktokkie.metadata.components.BookVolume import BookVolume
from toktokkie.metadata.types.components.BookVolume import BookVolume
from toktokkie.metadata.MediaType import MediaType
......@@ -45,11 +45,11 @@ class BookSeries(Book):
volume_files = listdir(self.directory_path, no_dirs=True)
for i, (volume, volume_file) in enumerate(volume_files):
json_data = self.json["volumes"].get(str(i + 1), {
"ids": self.ids,
"name": volume
})
volumes[i + 1] = BookVolume(self, json_data)
volume_num = i + 1
json_data = self.json["volumes"].get(str(volume_num), {"ids": {}})
volumes[i + 1] = BookVolume(
volume_num, volume, volume_file, self.ids, json_data
)
return volumes
@volumes.setter
......@@ -62,12 +62,3 @@ class BookSeries(Book):
self.json["volumes"] = {}
for i, volume in volumes.items():
self.json["volumes"][str(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
"""
files = len(listdir(self.directory_path, no_dirs=True))
self._assert_true(len(self.volumes) == files)
"""LICENSE
Copyright 2015 Hermann Krumrey <hermann@krumreyh.com>
This file is part of toktokkie.
toktokkie is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
toktokkie is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
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 Dict, List, Any
from toktokkie.metadata.ids.IdType import IdType
from toktokkie.metadata.ids.functions import stringify_ids, objectify_ids,\
minimize_ids, fill_ids
class BookVolume:
"""
Class that models a Volume in a Book Series
"""
def __init__(
self,
volume_number: int,
volume_name: str,
path: str,
parent_ids: Dict[IdType, List[str]],
json_data: Dict[str, Dict[str, List[str]]]
):
"""
Initializes the Book Volume
:param volume_number: The volume number
:param volume_name: THe volume name
:param path: The path to the volume file
:param parent_ids: The IDs of the parent BookSeries object
:param json_data: The JSON data for the book volume containing the
IDs for this specific volume
"""
self.volume_number = volume_number
self.volume_name = volume_name
self.path = path
self.ids = fill_ids(objectify_ids(json_data["ids"]), [], parent_ids)
self.parent_ids = parent_ids
@property
def json(self) -> Dict[str, Any]:
"""
:return: A JSON-compatible dictionary representing this object
"""
return {
"ids": stringify_ids(minimize_ids(self.ids, self.parent_ids))
}
......@@ -16,11 +16,3 @@ GNU General Public License for more details.
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.metadata.components.MetadataPart import MetadataPart
class BookVolume(MetadataPart):
"""
Class that models a single book volume
"""
"""LICENSE
Copyright 2015 Hermann Krumrey <hermann@krumreyh.com>
This file is part of toktokkie.
toktokkie is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
toktokkie is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with toktokkie. If not, see <http://www.gnu.org/licenses/>.
LICENSE"""
"""LICENSE
Copyright 2015 Hermann Krumrey <hermann@krumreyh.com>
This file is part of toktokkie.
toktokkie is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
toktokkie is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with toktokkie. If not, see <http://www.gnu.org/licenses/>.
LICENSE"""
......@@ -19,9 +19,6 @@ LICENSE"""
import os
import logging
import tvdb_api
from tvdb_api import tvdb_episodenotfound, tvdb_seasonnotfound, \
tvdb_shownotfound
from typing import List, Optional, Dict
from puffotter.os import listdir, replace_illegal_ntfs_chars, get_ext
from puffotter.prompt import yn_prompt
......@@ -32,6 +29,8 @@ from toktokkie.metadata.types.MusicArtist import MusicArtist
from toktokkie.metadata.ids.IdType import IdType
from toktokkie.metadata.MediaType import MediaType
from toktokkie.renaming.RenameOperation import RenameOperation
from toktokkie.renaming.functions import generate_tv_episode_filename, \
load_tvdb_episode_name
from anime_list_apis.api.AnilistApi import AnilistApi
from anime_list_apis.models.attributes.Title import TitleType
from anime_list_apis.models.attributes.MediaType import MediaType as \
......@@ -78,6 +77,11 @@ class Renamer:
operations = self._generate_manga_operations()
elif self.metadata.media_type() == MediaType.MUSIC_ARTIST:
operations = self._generate_music_operations()
elif self.metadata.media_type() == MediaType.VISUAL_NOVEL:
pass
else:
self.logger.error("Renaming methods for {} not implemented"
.format(self.metadata.media_type().name))
return operations
def get_active_operations(self) -> List[RenameOperation]:
......@@ -389,11 +393,11 @@ class Renamer:
else:
end = season_multis[episode_number]
episode_name = self.load_tvdb_episode_name(
episode_name = load_tvdb_episode_name(
tvdb_id, season_number, episode_number, end
)
new_name = self.generate_tv_episode_filename(
new_name = generate_tv_episode_filename(
episode_file,
series_name,
season_number,
......@@ -409,88 +413,3 @@ class Renamer:
episode_number += 1
return operations
@staticmethod
def generate_tv_episode_filename(
original_file: str,
series_name: str,
season_number: int,
episode_number: int,
episode_name: str,
multi_end: Optional[int] = None
):
"""
Generates an episode name for a given episode
:param original_file: The original file. Used to get the file extension
:param series_name: The name of the series
:param season_number: The season number
:param episode_name: The episode name
:param episode_number: The episode number
:param multi_end: Can be provided to create a multi-episode range
:return: The generated episode name
"""
ext = get_ext(original_file)
if ext is not None:
ext = "." + ext
else:
ext = ""
if multi_end is None:
return "{} - S{}E{} - {}{}".format(
series_name,
str(season_number).zfill(2),
str(episode_number).zfill(2),
episode_name,
ext
)
else:
return "{} - S{}E{}-E{} - {}{}".format(
series_name,
str(season_number).zfill(2),
str(episode_number).zfill(2),
str(multi_end).zfill(2),
episode_name,
ext
)
@staticmethod
def load_tvdb_episode_name(
tvdb_id: str,
season_number: int,
episode_number: int,
multi_end: Optional[int] = None
) -> str:
"""
Loads an episode name from TVDB
:param tvdb_id: The TVDB ID for the episode's series
:param season_number: The season number
:param episode_number: The episode number
:param multi_end: If provided,
will generate a name for a range of episodes
:return: The TVDB name
"""
if int(tvdb_id) == 0:
return "Episode " + str(episode_number)
if multi_end is not None:
episode_names = []
for episode in range(episode_number, multi_end + 1):
episode_names.append(Renamer.load_tvdb_episode_name(
tvdb_id,
season_number,
episode
))
return " | ".join(episode_names)
try:
tvdb = tvdb_api.Tvdb()
info = tvdb[int(tvdb_id)]
return info[season_number][episode_number]["episodeName"]
except (tvdb_episodenotfound, tvdb_seasonnotfound,
tvdb_shownotfound, ConnectionError, KeyError) as e:
# If not found, or other error, just return generic name
if str(e) == "cache_location": # pragma: no cover
logging.getLogger(__name__).warning("TheTVDB.com is down!")
return "Episode " + str(episode_number)
......@@ -16,6 +16,3 @@ GNU General Public License for more details.
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.renaming.Renamer import Renamer
from toktokkie.renaming.RenameOperation import RenameOperation
"""LICENSE
Copyright 2015 Hermann Krumrey <hermann@krumreyh.com>
This file is part of toktokkie.
toktokkie is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
toktokkie is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
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 logging
import tvdb_api
from tvdb_api import tvdb_episodenotfound, tvdb_seasonnotfound, \
tvdb_shownotfound
from typing import Optional
from puffotter.os import get_ext
def generate_tv_episode_filename(
original_file: str,
series_name: str,
season_number: int,
episode_number: int,
episode_name: str,
multi_end: Optional[int] = None
):
"""
Generates an episode name for a given episode
:param original_file: The original file. Used to get the file extension
:param series_name: The name of the series
:param season_number: The season number
:param episode_name: The episode name
:param episode_number: The episode number
:param multi_end: Can be provided to create a multi-episode range
:return: The generated episode name
"""
ext = get_ext(original_file)
if ext is not None:
ext = "." + ext
else:
ext = ""
if multi_end is None:
return "{} - S{}E{} - {}{}".format(
series_name,
str(season_number).zfill(2),
str(episode_number).zfill(2),
episode_name,
ext
)
else:
return "{} - S{}E{}-E{} - {}{}".format(
series_name,
str(season_number).zfill(2),
str(episode_number).zfill(2),
str(multi_end).zfill(2),
episode_name,
ext
)
def load_tvdb_episode_name(
tvdb_id: str,
season_number: int,
episode_number: int,
multi_end: Optional[int] = None
) -> str:
"""
Loads an episode name from TVDB
:param tvdb_id: The TVDB ID for the episode's series
:param season_number: The season number
:param episode_number: The episode number
:param multi_end: If provided,
will generate a name for a range of episodes
:return: The TVDB name
"""
if int(tvdb_id) == 0:
return "Episode " + str(episode_number)
if multi_end is not None:
episode_names = []
for episode in range(episode_number, multi_end + 1):
episode_names.append(load_tvdb_episode_name(
tvdb_id,
season_number,
episode
))
return " | ".join(episode_names)
try:
tvdb = tvdb_api.Tvdb()
info = tvdb[int(tvdb_id)]
return info[season_number][episode_number]["episodeName"]
except (tvdb_episodenotfound, tvdb_seasonnotfound,
tvdb_shownotfound, ConnectionError, KeyError) as e:
# If not found, or other error, just return generic name
if str(e) == "cache_location": # pragma: no cover
logging.getLogger(__name__).warning("TheTVDB.com is down!")
return "Episode " + str(episode_number)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment