Commit 38a569a5 authored by Hermann Krumrey's avatar Hermann Krumrey

Cleaned up available scripts

parent 2e3aa6b5
This diff is collapsed.
#!/usr/bin/env python
"""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 shutil
import argparse
from subprocess import Popen
from puffotter.os import listdir
def main():
parser = argparse.ArgumentParser()
parser.add_argument("files", nargs="+")
parser.add_argument("--delete-original", action="store_true")
args = parser.parse_args()
fill = len(str(len(args.files)))
dest_dir = args.files[0] + "_combined_dir"
dest_file = args.files[0] + "_combined.cbz"
src_images = []
if os.path.isdir(dest_dir):
shutil.rmtree(dest_dir)
os.makedirs(dest_dir)
for i, cbz in enumerate(args.files):
tempdir = "combine_temp"
if os.path.isdir(tempdir):
shutil.rmtree(tempdir)
Popen(["unzip", cbz, "-d", tempdir]).wait()
for name, path in listdir(tempdir):
new_name = str(i).zfill(fill) + " - " + name
new_path = os.path.join(dest_dir, new_name)
src_images.append(new_path)
os.rename(path, new_path)
shutil.rmtree(tempdir)
if args.delete_original:
os.remove(cbz)
Popen(["zip", "-j", dest_file] + src_images).wait()
shutil.rmtree(dest_dir)
if __name__ == "__main__":
main()
#!/usr/bin/env python
"""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 json
import argparse
import requests
from colorama import Fore, Style
def main():
parser = argparse.ArgumentParser()
parser.add_argument("anilist_username")
parser.add_argument("custom_list", default=None)
args = parser.parse_args()
query = """
query ($username: String) {
MediaListCollection(userName: $username, type: MANGA) {
lists {
name
entries {
status
progress
progressVolumes
media {
id
status
chapters
volumes
title {
english
romaji
}
}
}
}
}
}
"""
user_lists = json.loads(requests.post(
"https://graphql.anilist.co",
json={"query": query, "variables": {"username": args.anilist_username}}
).text)["data"]["MediaListCollection"]["lists"]
entries = []
for _list in user_lists:
if args.custom_list is None or _list["name"] == args.custom_list:
entries += _list["entries"]
for entry in entries:
name = entry["media"]["title"]["english"]
if name is None:
name = entry["media"]["title"]["romaji"]
user_progress = entry["progress"]
list_chapters = entry["media"]["chapters"]
if list_chapters is None:
list_chapters = guess_latest_chapter(entry["media"]["id"])
if user_progress != list_chapters:
print(Fore.LIGHTRED_EX, end="")
print("{}: ({}/{})".format(name, user_progress, list_chapters))
print(Style.RESET_ALL, end="")
def guess_latest_chapter(anilist_id: int) -> int:
"""
Guesses the latest chapter number based on anilist user activity
:param anilist_id: The anilist ID to check
:return: The latest chapter number
"""
query = """
query ($id: Int) {
Page(page: 1) {
activities(mediaId: $id, sort: ID_DESC) {
... on ListActivity {
progress
userId
}
}
}
}
"""
resp = requests.post(
"https://graphql.anilist.co",
json={"query": query, "variables": {"id": anilist_id}}
)
data = json.loads(resp.text)["data"]["Page"]["activities"]
progresses = []
for entry in data:
progress = entry["progress"]
if progress is not None:
progress = entry["progress"].split(" - ")[-1]
progresses.append(int(progress))
progresses.sort()
return progresses[-1]
if __name__ == "__main__":
main()
#!/usr/bin env python3
import os
import shutil
import argparse
from typing import List, Dict
from mutagen.easyid3 import EasyID3
def load_musicfiles(path: str) -> List[str]:
"""
Recursively searches for mp3 music files in a directory
:param path: The path in which to find the files
:return: A list containing the paths to the mp3 files
"""
files = []
for child in os.listdir(path):
child_path = os.path.join(path, child)
if os.path.isdir(child_path):
files += load_musicfiles(child_path)
elif os.path.isfile(child_path) and child.endswith(".mp3"):
files.append(child_path)
return files
def generate_structure(musicfiles: List[str]) \
-> Dict[str, Dict[str, List[str]]]:
"""
Generates a dictionary based on the mp3 tag data of the mp3 files mapping
songs to their artist and album
:param musicfiles: The list of mp3 files
:return: The generated dictionary: {artist: album: [songs]}
"""
structure = {}
for music in musicfiles:
mp3_info = EasyID3(music)
artist = mp3_info.get("artist", ["Unknown"])[0]
album = mp3_info.get("album", ["Unknown"])[0]
if artist not in structure:
structure[artist] = {}
if album not in structure[artist]:
structure[artist][album] = []
structure[artist][album].append(music)
return structure
def generate_library(structure: Dict[str, Dict[str, List[str]]], dest: str):
"""
Generates the tag-based folder structure
:param structure: The structure to create
:param dest: The path where to create the structure
:return: None
"""
for artist in structure:
artist_path = os.path.join(dest, artist)
os.makedirs(artist_path)
for album in structure[artist]:
album_path = os.path.join(artist_path, album)
os.makedirs(album_path)
for music in structure[artist][album]:
filename = os.path.basename(music)
music_path = os.path.join(album_path, filename)
shutil.copyfile(music, music_path)
def main():
"""
This script automatically generates a folder structure based on
the mp3 metadata of music files.
The songs are categorized by their artist, then the album.
The song files are only copied, not moved, so the original file structure
won't be destroyed
:return: None
"""
parser = argparse.ArgumentParser()
parser.add_argument("source", help="The source directory")
parser.add_argument("dest", help="The target directory")
args = parser.parse_args()
source = args.source
dest = args.dest
prompt = input(dest + " will be deleted, is this OK? (y|n)")
if prompt != "y":
exit()
if os.path.isdir(dest):
shutil.rmtree(dest)
os.makedirs(dest)
musicfiles = load_musicfiles(source)
structure = generate_structure(musicfiles)
generate_library(structure, dest)
if __name__ == "__main__":
main()
#!/usr/bin/env python
"""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 argparse
from toktokkie import Directory
from toktokkie.metadata.components.enums import TvIdType
def main():
"""
The toktokkie-metadata-gen main method
:return: None
"""
# noinspection PyTypeChecker
tv_id_types = set(map(lambda x: x.value, list(TvIdType)))
parser = argparse.ArgumentParser()
parser.add_argument("directory",
help="The directory for which to generate "
"a metadata file for")
subparser = parser.add_subparsers(required=True, dest="mode")
multi_episode_parser = subparser.add_parser(
"multi-episode", help="Add a multi-episode to a TV Series"
)
multi_episode_parser.add_argument("id_type", choices=tv_id_types,
help="The ID type")
multi_episode_parser.add_argument("season", type=int,
help="The season of the episode")
multi_episode_parser.add_argument("start_episode", type=int,
help="The start episode")
multi_episode_parser.add_argument("end_episode", type=int,
help="The end episode")
exclude_parser = subparser.add_parser(
"exclude", help="Add an excluded episode to a TV Series"
)
exclude_parser.add_argument("id_type", choices=tv_id_types,
help="The ID type")
exclude_parser.add_argument("season", type=int,
help="The season of the episode")
exclude_parser.add_argument("episode", type=int,
help="The episode to exclude")
args = parser.parse_args()
directory = Directory(args.directory)
if args.mode == "multi-episode":
id_type = TvIdType(args.id_type)
season = args.season
start = args.start_episode
end = args.end_episode
directory.metadata.add_multi_episode(id_type, season, start, end)
elif args.mode == "exclude":
id_type = TvIdType(args.id_type)
season = args.season
episode = args.episode
directory.metadata.add_exclude(id_type, season, episode)
directory.metadata.write()
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("Thanks for using toktokkie!")
This diff is collapsed.
......@@ -23,7 +23,8 @@ import logging
import tvdb_api
from typing import List, Dict, Any, Optional
from toktokkie.exceptions import InvalidMetadata, MissingMetadata
from toktokkie.metadata.components.enums import MediaType, IdType
from toktokkie.metadata.components.enums import MediaType, IdType, \
valid_id_types, required_id_types
from anime_list_apis.api.AnilistApi import AnilistApi
from anime_list_apis.models.attributes.MediaType import MediaType as \
AnimeListMediaType
......@@ -195,42 +196,7 @@ class Metadata:
"""
:return: The types of IDs that are valid for this metadata type
"""
return {
MediaType.MANGA: [
IdType.MANGADEX,
IdType.ANILIST,
IdType.MYANIMELIST,
IdType.KITSU,
IdType.ISBN
],
MediaType.TV_SERIES: [
IdType.ANILIST,
IdType.KITSU,
IdType.MYANIMELIST,
IdType.TVDB
],
MediaType.MOVIE: [
IdType.ANILIST,
IdType.KITSU,
IdType.MYANIMELIST,
IdType.IMDB
],
MediaType.BOOK: [
IdType.ISBN,
IdType.ANILIST,
IdType.KITSU,
IdType.MYANIMELIST
],
MediaType.BOOK_SERIES: [
IdType.ISBN,
IdType.ANILIST,
IdType.KITSU,
IdType.MYANIMELIST
],
MediaType.VISUAL_NOVEL: [
IdType.VNDB
]
}[cls.media_type()]
return valid_id_types[cls.media_type()]
def validate_json(self):
"""
......@@ -369,23 +335,12 @@ class Metadata:
:param defaults: The default values to use, mapped to id type names
:return: The generated IDs. At least one ID will be included
"""
valid_id_types = cls.valid_id_types()
ids = {} # type: Dict[str, List[str]]
mal_updated = False
while len(ids) < 1:
for id_type in [
IdType.TVDB,
IdType.IMDB,
IdType.ISBN,
IdType.VNDB,
IdType.MYANIMELIST,
IdType.ANILIST,
IdType.KITSU,
IdType.MANGADEX
]:
if id_type not in valid_id_types:
for id_type in IdType:
if id_type not in cls.valid_id_types():
continue
default = None # type: Optional[List[str]]
......@@ -454,16 +409,7 @@ class Metadata:
"""
:return: A list of required IDs for the metadata type
"""
idmap = {
MediaType.MANGA: [],
MediaType.BOOK: [],
MediaType.BOOK_SERIES: [],
MediaType.VISUAL_NOVEL: [IdType.VNDB],
MediaType.MOVIE: [IdType.IMDB],
MediaType.TV_SERIES: [IdType.TVDB]
} # type: Dict[MediaType, List[IdType]]
return idmap[cls.media_type()]
return required_id_types[cls.media_type()]
def print_folder_icon_source(self):
"""
......
......@@ -18,6 +18,7 @@ along with toktokkie. If not, see <http://www.gnu.org/licenses/>.
LICENSE"""
from enum import Enum
from typing import List, Dict
class IdType(Enum):
......@@ -44,3 +45,65 @@ class MediaType(Enum):
TV_SERIES = "tv"
VISUAL_NOVEL = "visual_novel"
MANGA = "manga"
valid_id_types = {
MediaType.BOOK: [
IdType.ISBN,
IdType.ANILIST,
IdType.MYANIMELIST,
IdType.KITSU
],
MediaType.BOOK_SERIES: [
IdType.ISBN,
IdType.ANILIST,
IdType.MYANIMELIST,
IdType.KITSU
],
MediaType.MOVIE: [
IdType.IMDB,
IdType.ANILIST,
IdType.MYANIMELIST,
IdType.KITSU
],
MediaType.TV_SERIES: [
IdType.TVDB,
IdType.ANILIST,
IdType.MYANIMELIST,
IdType.KITSU
],
MediaType.VISUAL_NOVEL: [
IdType.VNDB
],
MediaType.MANGA: [
IdType.ISBN,
IdType.ANILIST,
IdType.MYANIMELIST,
IdType.KITSU,
IdType.MANGADEX
]
} # type: Dict[MediaType, List[IdType]]
"""
Valid ID types for the various Media types
"""
required_id_types = {
MediaType.BOOK: [
],
MediaType.BOOK_SERIES: [
],
MediaType.MOVIE: [
IdType.IMDB
],
MediaType.TV_SERIES: [
IdType.TVDB
],
MediaType.VISUAL_NOVEL: [
IdType.VNDB
],
MediaType.MANGA: [
]
} # type: Dict[MediaType, List[IdType]]
"""
Required ID Types for the various Media Types
"""
......@@ -19,12 +19,14 @@ LICENSE"""
import argparse
from toktokkie.scripts.Command import Command
from toktokkie.metadata.components.enums import IdType
from toktokkie.metadata.components.enums import IdType, MediaType
from toktokkie.metadata.TvSeries import TvSeries
from toktokkie.Directory import Directory
class MetadataSetCommand(Command):
class MetadataAddCommand(Command):
"""
Class that encapsulates behaviour of the metadata-set command
Class that encapsulates behaviour of the metadata-add command
"""
@classmethod
......@@ -32,7 +34,7 @@ class MetadataSetCommand(Command):
"""
:return: The command name
"""
return "metadata-set"
return "metadata-add"
@classmethod
def prepare_parser(cls, parser: argparse.ArgumentParser):
......@@ -41,17 +43,17 @@ class MetadataSetCommand(Command):
:param parser: The parser to prepare
:return: None
"""
cls.add_directories_arg(parser)
parser.add_argument("directory",
help="The directory to which to add metadata")
id_types = [x.value for x in IdType]
subparser = parser.add_subparsers(dest="mode")
subparser = \
parser.add_subparsers(required=True, dest="mode") # type: ignore
tv_series_ids = [x.value for x in TvSeries.valid_id_types()]
multi_episode_parser = subparser.add_parser(
"multi-episode", help="Add a multi-episode to a TV Series"
)
multi_episode_parser.add_argument("id_type", choices=id_types,
multi_episode_parser.add_argument("id_type", choices=tv_series_ids,
help="The ID type")
multi_episode_parser.add_argument("season", type=int,
help="The season of the episode")
......@@ -63,7 +65,7 @@ class MetadataSetCommand(Command):
exclude_parser = subparser.add_parser(
"exclude", help="Add an excluded episode to a TV Series"
)
exclude_parser.add_argument("id_type", choices=id_types,
exclude_parser.add_argument("id_type", choices=tv_series_ids,
help="The ID type")
exclude_parser.add_argument("season", type=int,
help="The season of the episode")
......@@ -75,4 +77,25 @@ class MetadataSetCommand(Command):
Executes the commands
:return: None
"""
pass
directory = Directory(self.args.directory)
if self.args.mode == "multi-episode":
if directory.metadata.media_type() != MediaType.TV_SERIES:
print("Not a TV Series Directory")
return
id_type = IdType(self.args.id_type)
season = self.args.season
start = self.args.start_episode
end = self.args.end_episode
directory.metadata.add_multi_episode(id_type, season, start, end)
elif self.args.mode == "exclude":
if directory.metadata.media_type() != MediaType.TV_SERIES:
print("Not a TV Series Directory")
return
id_type = IdType(self.args.id_type)
season = self.args.season
episode = self.args.episode
directory.metadata.add_exclude(id_type, season, episode)
directory.metadata.write()
#!/usr/bin/env python
"""LICENSE
Copyright 2015 Hermann Krumrey <hermann@krumreyh.com>
......@@ -20,46 +18,46 @@ along with toktokkie. If not, see <http://www.gnu.org/licenses/>.
LICENSE"""
import os
import shutil
import argparse
from toktokkie import Directory
from subprocess import Popen
from toktokkie.exceptions import MissingMetadata, InvalidMetadata
from toktokkie.scripts.Command import Command
from toktokkie.metadata.components.enums import MediaType
def main():
class SetMangaCoverCommand(Command):
"""
The toktokkie-rename main method
:return: None
Class that encapsulates behaviour of the set-manga-cover command
"""
parser = argparse.ArgumentParser()
parser.add_argument("directories", nargs="+",
help="The directories for which to set cover files. "
"Files and directories that do not contain any "
"valid metadata configuration will be ignored.")
args = parser.parse_args()
directories = args.directories
for path in directories:
@classmethod
def name(cls) -> str:
"""
:return: The command name
"""
return "set-manga-cover"
@classmethod
def prepare_parser(cls, parser: argparse.ArgumentParser):
"""
Prepares an argumentparser for this command
:param parser: The parser to prepare
:return: None
"""
cls.add_directories_arg(parser)
def execute(self):
"""
Executes the commands
:return: None
"""
for directory in self.load_directories(
self.args.directories, restrictions=[MediaType.MANGA]
):
try:
directory = Directory(path)
cover = os.path.join(directory.metadata.icon_directory, "main.png")
target = os.path.join(path, "cover.cbz")
target = os.path.join(directory.path, "cover.cbz")
if os.path.isfile(cover):
if not os.path.isfile(target):
Popen(["zip", "-j", target, cover]).wait()
else:
print("No cover for {}".format(path))
except MissingMetadata:
print("{} has no metadata file.".format(path))
except InvalidMetadata:
print("{}'s metadata is invalid.".format(path))
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("Thanks for using toktokkie!")
print("No cover for {}".format(directory.path))
This diff is collapsed.
......@@ -27,12 +27,9 @@ from toktokkie.scripts.MangaCreateCommand import MangaCreateCommand
from toktokkie.scripts.MangaUpdateCommand import MangaUpdateCommand
from toktokkie.scripts.MetadataGenCommand import MetadataGenCommand
from toktokkie.scripts.XdccUpdateCommand import XdccUpdateCommand
from toktokkie.scripts.MetadataSetCommand import MetadataSetCommand
try:
from toktokkie.scripts.GuiCommand import GuiCommand
except ImportError as e:
GuiCommand = None # type: ignore
from toktokkie.scripts.MetadataAddCommand import MetadataAddCommand
from toktokkie.scripts.SetMangaCoverCommand import SetMangaCoverCommand
from toktokkie.scripts.SuperCutCommand import SuperCutCommand
toktokkie_commands = [
PrintCommand,
......@@ -45,11 +42,16 @@ toktokkie_commands = [
MangaCreateCommand,
MetadataGenCommand,