...
 
Commits (2)
V 0.19.0:
- Uses more puffotter convenience functions
- Adjusted to new xdcc-dl paradigms
- Major refactor of metadata structure
- Uses json schema to validate metadata
V 0.18.0:
- Redid metadata to be more minimalistic and easier to maintain
- Substituted verification for Checkers
......
......@@ -96,9 +96,9 @@ class TvSeriesChecker(Checker):
valid = True
metadata = self.metadata # type: TvSeries # type: ignore
ids = [metadata.tvdb_id]
ids = metadata.ids.get(IdType.TVDB, [])
for season in metadata.seasons:
ids.append(season.tvdb_id)
ids += season.ids.get(IdType.TVDB, [])
for tvdb_id in ids:
if int(tvdb_id) == 0: # TVDB ID 0 means show not on tvdb
......@@ -118,9 +118,10 @@ class TvSeriesChecker(Checker):
"""
valid = True
metadata = self.metadata # type: TvSeries # type: ignore
tvdb_ids = metadata.ids.get(IdType.TVDB, [])
ignores = self._generate_ignores_map()
tvdb_data = self.tvdb[int(metadata.tvdb_id)]
tvdb_data = self.tvdb[int(tvdb_ids[0])]
for season_number, season_data in tvdb_data.items():
episode_amount = len(season_data)
......@@ -135,7 +136,7 @@ class TvSeriesChecker(Checker):
episode_amount -= 1
_existing = metadata.get_episode_files()
existing = _existing[metadata.tvdb_id].get(season_number, [])
existing = _existing[tvdb_ids[0]].get(season_number, [])
if not len(existing) == episode_amount:
msg = "Mismatch in season {}; Should:{}; Is:{}".format(
......@@ -153,9 +154,10 @@ class TvSeriesChecker(Checker):
"""
valid = True
metadata = self.metadata # type: TvSeries # type: ignore
tvdb_ids = metadata.ids.get(IdType.TVDB, [])
ignores = self._generate_ignores_map()
tvdb_data = self.tvdb[int(metadata.tvdb_id)]
tvdb_data = self.tvdb[int(tvdb_ids[0])]
for season_number, season_data in tvdb_data.items():
for episode_number, episode_data in season_data.items():
......@@ -168,12 +170,14 @@ class TvSeriesChecker(Checker):
continue
episode_name = self._generate_episode_name(
metadata.tvdb_id, season_number, episode_number
tvdb_ids[0],
season_number,
episode_number
)
# Check if file exists
existing_files = \
metadata.get_episode_files()[metadata.tvdb_id].get(
metadata.get_episode_files()[tvdb_ids[0]].get(
season_number, []
)
......@@ -205,11 +209,13 @@ class TvSeriesChecker(Checker):
if not season.is_spinoff():
continue
tvdb_data = self.tvdb[int(season.tvdb_id)][1]
tvdb_data = self.tvdb[int(season.ids.get(IdType.TVDB)[0])][1]
# Check Length
should = len(episode_files[season.tvdb_id][1])
if not len(tvdb_data) == len(episode_files[season.tvdb_id][1]):
should = len(episode_files[season.ids.get(IdType.TVDB)[0]][1])
season_tvdb_id = season.ids.get(IdType.TVDB)[0]
if not len(tvdb_data) == len(episode_files[season_tvdb_id][1]):
msg = "Mismatch in spinoff {}; Should:{}; Is:{}".format(
season.name, should, len(tvdb_data)
)
......@@ -222,7 +228,7 @@ class TvSeriesChecker(Checker):
continue
name = self._generate_episode_name(
season.tvdb_id, 1, episode_number, season.name
season_tvdb_id, 1, episode_number, season.name
)
exists = False
......
......@@ -27,7 +27,7 @@ from threading import Thread
from PyQt5.QtGui import QPixmap
from PyQt5.QtWidgets import QMainWindow, QWidget, QListWidgetItem
from toktokkie.metadata.types.TvSeries import TvSeries
from toktokkie.metadata.components.TvSeason import TvSeason
from toktokkie.metadata.types.components.TvSeason import TvSeason
from toktokkie.metadata.ids.IdType import IdType
from toktokkie.gui.pyuic.tv_season_widget import Ui_TvSeasonWidget
......@@ -69,7 +69,7 @@ class TvSeasonWidget(QWidget, Ui_TvSeasonWidget):
self.name.setText(self.metadata.name)
self.season_name.setText(self.season.name)
self.tvdb_id_edit.setText(str(self.season.tvdb_id))
self.tvdb_id_edit.setText(str(self.season.ids.get(IdType.TVDB)[0]))
self.set_icon_image()
self.load_episode_files()
Thread(target=self.load_tvdb_info).start()
......
"""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, Any, List
from toktokkie.metadata.Metadata import Metadata
from toktokkie.metadata.ids.IdType import IdType
from toktokkie.exceptions import InvalidMetadata
class MetadataPart:
"""
Class that defines various functionality of a metadata part
"""
def __init__(self, parent: Metadata, json_data: Dict[str, Any]):
"""
Initializes the MetadataPart object using JSON data
:param parent: The parent metadata
:param json_data: The JSON data used to generate the MetadataPart
:raises InvalidMetadataException: If any errors were encountered
while generating the object
"""
self.parent = parent
self.json = json_data
self.validate()
def validate(self):
"""
Validates the JSON data of the metadata part
:return: None
:raises InvalidMetadataException: If something is wrong
with the JSON data
"""
if len(self.ids) < 1:
raise InvalidMetadata()
for _, ids in self.ids.items():
for _id in ids:
if not type(_id) == str:
raise InvalidMetadata()
@property
def ids(self) -> Dict[IdType, List[str]]:
"""
:return: A dictionary containing lists of IDs mapped to ID types
"""
generated = self.parent.ids
for id_type, _id in self.json.get("ids", {}).items():
if isinstance(_id, list):
generated[IdType(id_type)] = _id
else:
generated[IdType(id_type)] = [_id]
return generated
@ids.setter
def ids(self, ids: Dict[IdType, List[str]]):
"""
Setter method for the IDs of the metadata part object.
Previous IDs will be overwritten!
: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
"""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 os
from toktokkie.exceptions import InvalidMetadata
from toktokkie.metadata.ids.IdType import IdType
from toktokkie.metadata.components.MetadataPart import MetadataPart
class TvSeason(MetadataPart):
"""
Class that models a single tv season
"""
@property
def name(self) -> str:
"""
:return: The name of the season directory
"""
return self.json["name"]
@property
def path(self) -> str:
"""
:return: The path to the season directory
"""
return os.path.join(self.parent.directory_path, self.name)
@property
def tvdb_id(self) -> str:
"""
:return: The TVDB ID of the TV season
"""
return self.ids[IdType.TVDB][0]
@property
def season_number(self) -> int:
"""
:return: The season number of the season
"""
if self.name.lower().startswith("season "):
return int(self.name.lower().split("season")[1])
else:
return 0
def validate(self):
"""
Validates the JSON data of the tv season.
:return: None
:raises InvalidMetadataException: If something is wrong
with the JSON data
"""
super().validate()
if not os.path.exists(self.path) or "name" not in self.json:
raise InvalidMetadata()
if not os.path.isdir(self.path):
raise InvalidMetadata()
try:
if not self.tvdb_id == self.ids[IdType.TVDB][0]:
raise InvalidMetadata()
except (KeyError, IndexError):
raise InvalidMetadata("Missing TVDB ID")
def is_spinoff(self) -> bool:
"""
:return: Whether or not this season is a spinoff
"""
# noinspection PyUnresolvedReferences
return self.parent.tvdb_id != self.tvdb_id # type: ignore
......@@ -31,10 +31,21 @@ from anime_list_apis.models.attributes.MediaType import MediaType as \
class IdFetcher:
"""
Class that handles fetching ID types using the internet
"""
logger = logging.getLogger(__name__)
"""
Logger for this class
"""
def __init__(self, name: str, media_type: MediaType):
"""
Initializes the ID fetcher
:param name: The name of the media
:param media_type: The media type of the media
"""
self.name = name
self.media_type = media_type
......@@ -43,6 +54,13 @@ class IdFetcher:
id_type: IdType,
other_ids: Dict[IdType, List[str]]
) -> Optional[List[str]]:
"""
Retrieves any supported IDs based on the media name and/or existing
IDs.
:param id_type: The ID Type to fetch
:param other_ids: Any other known IDs
:return: The IDs or None if no ID could be determined
"""
if id_type == IdType.TVDB:
return self.__load_tvdb_ids()
elif id_type == IdType.ANILIST and IdType.MYANIMELIST in other_ids:
......@@ -53,12 +71,21 @@ class IdFetcher:
return None
def __load_tvdb_ids(self) -> List[str]:
"""
Retrieves TVDB IDs based on the media name
:return: THe TVDB IDs
"""
try:
return [str(tvdb_api.Tvdb()[self.name].data["id"])]
except (tvdb_api.tvdb_shownotfound, TypeError):
return []
def __load_anilist_ids(self, mal_ids: List[str]) -> List[str]:
"""
Loads anilist IDs based on myanimelist IDs
:param mal_ids: THe myanimelist IDs
:return: The anilist IDs
"""
self.logger.info("Loading anilist ID from mal IDs {}".format(mal_ids))
list_type = AnimeListMediaType.ANIME
if self.media_type in literature_media_types:
......@@ -75,6 +102,10 @@ class IdFetcher:
return ids
def __load_musicbrainz_ids(self) -> List[str]:
"""
Retrieves a musicbrainz ID based on the artist name
:return: The musicbrainz IDs
"""
musicbrainzngs.set_useragent(
"toktokkie media manager",
version,
......
......@@ -182,7 +182,8 @@ class SchemaBuilder:
"genre": {"type": "string"},
"year": {"type": "number"},
"ids": {}
}
},
"required": ["name", "genre", "year"]
}
},
"theme_songs": {
......@@ -196,7 +197,8 @@ class SchemaBuilder:
"type": "string",
"pattern": "(op|ed|insert|special|other){1}"
}
}
},
"required": ["name", "theme_type"]
}
}
}
......@@ -220,7 +222,8 @@ class SchemaBuilder:
"properties": {
"season": {"type": "number"},
"episode": {"type": "number"}
}
},
"required": ["season", "episode"]
}
}
excludes[id_type.value] = extra_base.copy()
......@@ -234,7 +237,8 @@ class SchemaBuilder:
"season": {"type": "number"},
"start_episode": {"type": "number"},
"end_episode": {"type": "number"}
}
},
"required": ["season", "start_episode", "end_episode"]
}
}
multi_episodes[id_type.value] = extra_base.copy()
......@@ -247,7 +251,8 @@ class SchemaBuilder:
"properties": {
"ids": ids,
"name": {"type": "string"}
}
},
"required": ["name"]
}
},
"excludes": {
......
......@@ -17,11 +17,11 @@ 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 copy import deepcopy
from typing import Dict, Any, List
from typing import List
from toktokkie.metadata.Metadata import Metadata
from toktokkie.metadata.ids.mappings import valid_id_types
from toktokkie.metadata.MediaType import MediaType
from toktokkie.metadata.types.components.MusicAlbum import MusicAlbum
from toktokkie.metadata.types.components.MusicThemeSong import MusicThemeSong
class MusicArtist(Metadata):
......@@ -36,65 +36,70 @@ class MusicArtist(Metadata):
"""
return MediaType.MUSIC_ARTIST
def add_album(self, album_data: Dict[str, Any]):
@property
def albums(self) -> List[MusicAlbum]:
"""
Adds an album to the metadata
:param album_data: The album metadata to add
:return: None
:return: All albums in the metadata, regardless if they're just normal
albums or theme songs
"""
if album_data["album_type"] == "theme_song":
ids = album_data["series_ids"]
album_data["series_ids"] = {}
for key, value in ids.items():
album_data["series_ids"][key.value] = value
self.logger.debug("Adding album metadata: {}".format(album_data))
self.json["albums"].append(album_data)
return list(map(
lambda x: MusicAlbum(self.directory_path, self.ids, x),
self.json["albums"]
))
@property
def all_albums(self) -> List[Dict[str, Any]]:
def theme_songs(self) -> List[MusicThemeSong]:
"""
:return: All album metadata
:return: All theme songs for this music artist
"""
return self.albums + self.singles + self.theme_songs
album_map = {}
for album in self.albums:
album_map[album.name] = album
theme_songs = []
for theme_song in self.json.get("theme_songs", []):
name = theme_song["name"]
album = album_map.get(name)
if album is None:
self.logger.warning("Missing album data for {}".format(name))
continue
else:
theme_songs.append(MusicThemeSong(album, theme_song))
return theme_songs
@property
def albums(self) -> List[Dict[str, Any]]:
def non_theme_song_albums(self) -> List[MusicAlbum]:
"""
:return: All 'album' album metadata
:return: Any albums that are not also theme songs
"""
theme_song_names = list(map(lambda x: x.name, self.theme_songs))
return list(filter(
lambda x: x["album_type"] == "album",
self.json["albums"]
lambda x: x.name not in theme_song_names,
self.albums
))
@property
def singles(self) -> List[Dict[str, Any]]:
def add_album(self, album_data: MusicAlbum):
"""
:return: All 'single' album metadata
Adds an album to the metadata
:param album_data: The album metadata to add
:return: None
"""
return list(filter(
lambda x: x["album_type"] == "single",
self.json["albums"]
))
existing = list(map(lambda x: x.name, self.albums))
if album_data.name not in existing:
self.json["albums"].append(album_data.json)
@property
def theme_songs(self) -> List[Dict[str, Any]]:
def add_theme_song(self, theme_song: MusicThemeSong):
"""
:return: All theme songs for this music artist
Adds a theme song to the metadata
:param theme_song: The theme song to add
:return: None
"""
valid = list(set(
valid_id_types[MediaType.TV_SERIES]
+ valid_id_types[MediaType.VISUAL_NOVEL]
))
existing = list(map(lambda x: x.name, self.theme_songs))
if theme_song.name not in existing:
self.add_album(theme_song.album)
if "theme_songs" not in self.json:
self.json["theme_songs"] = []
themes = []
for theme in list(filter(
lambda x: x["album_type"] == "theme_song",
deepcopy(self.json["albums"])
)):
ids = {}
for id_type in valid:
ids[id_type] = theme["series_ids"].get(id_type.value, [])
theme["series_ids"] = ids
themes.append(theme)
return themes
self.json["theme_songs"].append(theme_song.json)
......@@ -21,11 +21,11 @@ import os
from typing import List, Dict
from puffotter.os import listdir
from toktokkie.metadata.Metadata import Metadata
from toktokkie.metadata.components.TvSeason import TvSeason
from toktokkie.metadata.types.components.TvSeason import TvSeason
from toktokkie.metadata.ids.IdType import IdType
from toktokkie.metadata.MediaType import MediaType
from toktokkie.metadata.components.TvEpisode import TvEpisode
from toktokkie.metadata.components.TvEpisodeRange import TvEpisodeRange
from toktokkie.metadata.types.components.TvEpisode import TvEpisode
from toktokkie.metadata.types.components.TvEpisodeRange import TvEpisodeRange
from toktokkie.exceptions import InvalidMetadata
......@@ -41,20 +41,13 @@ class TvSeries(Metadata):
"""
return MediaType.TV_SERIES
@property
def tvdb_id(self) -> str:
"""
:return: The TVDB ID of the TV Series
"""
return self.ids[IdType.TVDB][0]
@property
def seasons(self) -> List[TvSeason]:
"""
:return: A list of TV seasons
"""
seasons_list = list(map(
lambda x: TvSeason(self, x),
lambda x: TvSeason(self.directory_path, self.ids, x),
self.json["seasons"]
))
seasons_list.sort(
......@@ -227,6 +220,11 @@ class TvSeries(Metadata):
elif len(self.seasons) > foldercount:
raise InvalidMetadata("Missing season directories")
for season in self.seasons:
if not os.path.isdir(season.path):
raise InvalidMetadata("Missing season directory {}"
.format(season.name))
def get_episode_files(self) -> Dict[str, Dict[int, List[str]]]:
"""
Generates a dictionary categorizing internal episode files for further
......@@ -253,7 +251,7 @@ class TvSeries(Metadata):
"No Metadata found for {}".format(season_name)
)
continue
tvdb_id = season_metadata.tvdb_id
tvdb_id = season_metadata.ids.get(IdType.TVDB)[0]
if tvdb_id not in content_info:
content_info[tvdb_id] = {}
......
......@@ -18,12 +18,13 @@ along with toktokkie. If not, see <http://www.gnu.org/licenses/>.
LICENSE"""
from typing import Dict, List, Any
from toktokkie.metadata.types.components.Component import Component
from toktokkie.metadata.ids.IdType import IdType
from toktokkie.metadata.ids.functions import stringify_ids, objectify_ids,\
minimize_ids, fill_ids
class BookVolume:
class BookVolume(Component):
"""
Class that models a Volume in a Book Series
"""
......@@ -32,23 +33,25 @@ class BookVolume:
self,
volume_number: int,
volume_name: str,
path: str,
volume_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 volume_name: The volume name
:param volume_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.number = volume_number
self.name = volume_name
self.path = volume_path
ids = objectify_ids(json_data.get("ids", {}))
self.ids = fill_ids(ids, [], parent_ids)
self.parent_ids = parent_ids
@property
......
"""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
from typing import Dict, Any
class Component:
"""
Specifies common features of metadata components
"""
logger = logging.getLogger(__name__)
"""
Logger for the components
"""
@property
def json(self) -> Dict[str, Any]:
"""
Converts the component into a JSON-compatible dictionary
:return: The JSON-compatible dictionary
"""
raise NotImplementedError()
......@@ -16,3 +16,52 @@ 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 os
from typing import Dict, Any, List
from toktokkie.metadata.types.components.Component import Component
from toktokkie.metadata.ids.IdType import IdType
from toktokkie.metadata.ids.functions import objectify_ids, stringify_ids, \
fill_ids, minimize_ids
class MusicAlbum(Component):
"""
Class that defines attributes of music albums
"""
def __init__(
self,
parent_path: str,
parent_ids: Dict[IdType, List[str]],
json_data: Dict[str, Any]
):
"""
Initializes the MusicAlbum object
:param parent_path: The path to the parent directory
:param parent_ids: The IDs associated with the parent
:param json_data: The JSON data of the album
"""
self.parent_path = parent_path
self.parent_ids = parent_ids
self.name = json_data["name"]
self.genre = json_data["genre"]
self.year = json_data["year"]
self.path = os.path.join(parent_path, self.name)
ids = objectify_ids(json_data.get("ids", {}))
self.ids = fill_ids(ids, [], parent_ids)
@property
def json(self) -> Dict[str, Any]:
"""
Converts the component into a JSON-compatible dictionary
:return: The JSON-compatible dictionary
"""
return {
"name": self.name,
"genre": self.genre,
"year": self.year,
"ids": stringify_ids(minimize_ids(self.ids, self.parent_ids))
}
......@@ -16,3 +16,40 @@ 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, Any
from toktokkie.metadata.types.components.MusicAlbum import MusicAlbum
from toktokkie.metadata.types.components.Component import Component
from toktokkie.metadata.ids.mappings import theme_song_ids
from toktokkie.metadata.ids.functions import objectify_ids, stringify_ids, \
fill_ids, minimize_ids
class MusicThemeSong(Component):
def __init__(
self,
album: MusicAlbum,
json_data: Dict[str, Any]
):
self.album = album
self.name = json_data["name"]
self.theme_type = json_data["theme_type"]
if self.name != self.album.name:
self.logger.warning("Theme song {} does not match album {}"
.format(self.name, self.album.name))
ids = objectify_ids(json_data.get("series_ids"))
self.series_ids = fill_ids(ids, theme_song_ids)
@property
def json(self) -> Dict[str, Any]:
"""
Converts the component into a JSON-compatible dictionary
:return: The JSON-compatible dictionary
"""
return {
"name": self.name,
"series_ids": stringify_ids(minimize_ids(self.series_ids))
}
......@@ -18,7 +18,7 @@ along with toktokkie. If not, see <http://www.gnu.org/licenses/>.
LICENSE"""
from typing import Dict, Any, List
from toktokkie.metadata.components.TvEpisode import TvEpisode
from toktokkie.metadata.types.components.TvEpisode import TvEpisode
from toktokkie.exceptions import InvalidMetadata
......
......@@ -16,3 +16,57 @@ 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 os
from typing import Dict, List, Any, Union
from toktokkie.metadata.types.components.Component import Component
from toktokkie.metadata.ids.IdType import IdType
from toktokkie.metadata.ids.functions import minimize_ids, fill_ids, \
objectify_ids, stringify_ids
class TvSeason(Component):
"""
Class that models a season of a TV Series
"""
def __init__(
self,
parent_path: str,
parent_ids: Dict[IdType, List[str]],
json_data: Dict[str, Union[Dict[str, List[str]], str]]
):
self.parent_path = parent_path
self.parent_ids = parent_ids
self.name = json_data["name"]
self.path = os.path.join(parent_path, self.name)
ids = objectify_ids(json_data.get("ids", {}))
self.ids = fill_ids(ids, [], parent_ids)
@property
def json(self) -> Dict[str, Any]:
"""
:return: A JSON-compatible dictionary representing this object
"""
return {
"name": self.name,
"ids": stringify_ids(minimize_ids(self.ids, self.parent_ids))
}
@property
def season_number(self) -> int:
"""
:return: The season number of the season
"""
if self.name.lower().startswith("season "):
return int(self.name.lower().split("season")[1])
else:
return 0
def is_spinoff(self) -> bool:
"""
:return: Whether or not this season is a spinoff
"""
return self.parent_ids.get(IdType.TVDB) != self.ids.get(IdType.TVDB)
......@@ -26,6 +26,7 @@ from toktokkie.metadata.Metadata import Metadata
from toktokkie.metadata.types.TvSeries import TvSeries
from toktokkie.metadata.types.Manga import Manga
from toktokkie.metadata.types.MusicArtist import MusicArtist
from toktokkie.metadata.types.components.MusicThemeSong import MusicThemeSong
from toktokkie.metadata.ids.IdType import IdType
from toktokkie.metadata.MediaType import MediaType
from toktokkie.renaming.RenameOperation import RenameOperation
......@@ -288,35 +289,34 @@ class Renamer:
music_exts = ["mp3", "flac", "wav"]
video_exts = ["mp4", "webm", "mkv", "avi"]
for album in music_metadata.all_albums:
path = os.path.join(
music_metadata.directory_path,
album["name"]
)
song_files = listdir(path)
theme_songs = {x.name: x for x in music_metadata.theme_songs}
for album in music_metadata.albums:
song_files = listdir(album.path)
if album["album_type"] == "theme_song":
if album.name in theme_songs:
theme_song = theme_songs[album.name] # type: MusicThemeSong
series_name = \
self.load_title_name(id_override=album["series_ids"])
self.load_title_name(id_override=theme_song.series_ids)
for song, path in song_files:
if song.startswith(album["name"]):
if song.startswith(theme_song.name):
continue # Skip renaming full version
ext = get_ext(song)
if ext in video_exts:
new_name = "{} {} - {}-video.{}".format(
series_name,
album["theme_type"],
album["name"],
theme_song.theme_type,
theme_song.name,
ext
)
operations.append(RenameOperation(path, new_name))
elif ext in music_exts:
new_name = "{} {} - {}.{}".format(
series_name,
album["theme_type"],
album["name"],
theme_song.theme_type,
theme_song.name,
ext
)
operations.append(RenameOperation(path, new_name))
......@@ -367,7 +367,7 @@ class Renamer:
content_info = tv_series_metadata.get_episode_files()
for tvdb_id, season_data in content_info.items():
is_spinoff = tv_series_metadata.tvdb_id != tvdb_id
is_spinoff = tv_series_metadata.ids.get(IdType.TVDB)[0] != tvdb_id
if is_spinoff:
sample_episode = season_data[list(season_data)[0]][0]
......
......@@ -25,6 +25,7 @@ from mutagen.id3 import ID3, APIC, TPE2
from puffotter.os import listdir, get_ext
from toktokkie.scripts.Command import Command
from toktokkie.metadata.MediaType import MediaType
from toktokkie.metadata.types.MusicArtist import MusicArtist
class MusicTagCommand(Command):
......@@ -56,9 +57,10 @@ class MusicTagCommand(Command):
for directory in self.load_directories(
self.args.directories, restrictions=[MediaType.MUSIC_ARTIST]
):
for album in directory.metadata.all_albums:
music_metadata = directory.metadata # type: MusicArtist
for album in music_metadata.albums:
for song, song_file in listdir(
os.path.join(directory.path, album["name"])
os.path.join(directory.path, album.name)
):
if get_ext(song) != "mp3":
self.logger.info("Not an MP3 file: " + song)
......@@ -71,14 +73,14 @@ class MusicTagCommand(Command):
mp3 = EasyID3(song_file)
mp3["title"] = title
mp3["artist"] = directory.metadata.name
mp3["album"] = album["name"]
mp3["date"] = str(album["year"])
mp3["genre"] = album["genre"]
mp3["album"] = album.name
mp3["date"] = str(album.year)
mp3["genre"] = album.genre
mp3.save()
cover_file = os.path.join(
directory.metadata.icon_directory,
album["name"] + ".png"
album.name + ".png"
)
if os.path.isfile(cover_file):
......@@ -91,4 +93,4 @@ class MusicTagCommand(Command):
id3.save()
else:
self.logger.warning("No cover file for {}"
.format(album["name"]))
.format(album.name))
"""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"""
import os
import argparse
from unittest import mock
from toktokkie.test.TestFramework import _TestFramework
from toktokkie.scripts.AnimeThemeDlCommand import AnimeThemeDlCommand
from toktokkie.metadata.types.MusicArtist import MusicArtist
from toktokkie.metadata.ids.functions import minimize_ids
from toktokkie.metadata.ids.IdType import IdType
class TestAniThemeDl(_TestFramework):
def test_downloading(self):
path = self.get("anitheme")
args = argparse.Namespace()
args.__dict__["year"] = "2019"
args.__dict__["season"] = "Fall"
args.__dict__["out"] = path
cmd = AnimeThemeDlCommand(args)
with mock.patch("builtins.input", side_effect=["1", ""]):
cmd.execute()
momo = os.path.join(path, "structured/ED/Momo Asakura")
chico = os.path.join(path, "structured/OP/CHiCO with HoneyWorks")
self.assertTrue(os.path.isdir(momo))
self.assertTrue(os.path.isdir(chico))
self.assertEqual(len(os.listdir(momo)), 2)
self.assertEqual(len(os.listdir(chico)), 2)
momo_obj = MusicArtist(momo)
chico_obj = MusicArtist(chico)
self.assertEqual(momo_obj.name, "Momo Asakura")
self.assertEqual(chico_obj.name, "CHiCO with HoneyWorks")
self.assertEqual(
minimize_ids(momo_obj.ids),
{IdType.MUSICBRAINZ: ["0"]}
)
self.assertEqual(
minimize_ids(chico_obj.ids),
{IdType.MUSICBRAINZ: ["0"]}
)
......@@ -25,9 +25,10 @@ from typing import Dict, Any
from xdcc_dl.xdcc import download_packs
from xdcc_dl.pack_search.SearchEngine import SearchEngineType, SearchEngine
from puffotter.prompt import prompt
from toktokkie.renaming import Renamer, RenameOperation
from toktokkie.renaming.Renamer import Renamer
from toktokkie.renaming.RenameOperation import RenameOperation
from toktokkie.metadata.types.TvSeries import TvSeries
from toktokkie.metadata.components.TvSeason import TvSeason
from toktokkie.metadata.types.components.TvSeason import TvSeason
from toktokkie.xdcc_update.enums import Resolution
from toktokkie.exceptions import MissingXDCCInstructions, \
InvalidXDCCInstructions
......