def parser(): description = ( 'Plex Manager\n\nYou will be securely prompted for your password for the first login, after which a session' ' token will be cached') parser = ArgParser(description=description) with parser.add_subparser('action', 'sync', help='Sync Plex information') as sync_parser: ratings_parser = sync_parser.add_subparser( 'sync_action', 'ratings', help='Sync song rating information between Plex and files') ratings_parser.add_argument('direction', choices=('to_files', 'from_files'), help='Direction to sync information') ratings_parser.add_argument( '--path_filter', '-f', help= 'If specified, paths that will be synced must contain the given text (not case sensitive)' ) playlists_parser = sync_parser.add_subparser( 'sync_action', 'playlists', help='Sync playlists with custom filters') obj_types = ('track', 'artist', 'album', 'tracks', 'artists', 'albums') ops = ( 'contains, endswith, exact, exists, gt, gte, icontains, iendswith, iexact, in, iregex, istartswith, like, lt, ' 'lte, ne, regex, startswith') with parser.add_subparser('action', 'find', help='Find Plex information') as find_parser: find_parser.add_argument('obj_type', choices=obj_types, help='Object type') find_parser.add_argument('title', nargs='*', default=None, help='Object title (optional)') find_parser.add_argument( '--escape', '-e', default='()', help= 'Escape the provided regex special characters (default: %(default)r)' ) find_parser.add_argument( '--allow_inst', '-I', action='store_true', help= 'Allow search results that include instrumental versions of songs') find_parser.add_argument( '--full_info', '-F', action='store_true', help='Print all available info about the discovered objects') find_parser.add_argument( '--format', '-f', choices=PRINTER_FORMATS, default='yaml', help='Output format to use for --full_info (default: %(default)s)') find_parser.add_argument( 'query', nargs=argparse.REMAINDER, help= f'Query in the format --field[__operation] value; valid operations: {ops}' ) with parser.add_subparser('action', 'rate', help='Update ratings in Plex') as rate_parser: rate_parser.add_argument('obj_type', choices=obj_types, help='Object type') rate_parser.add_argument('rating', type=int, help='Rating out of 10') rate_parser.add_argument('title', nargs='*', default=None, help='Object title (optional)') rate_parser.add_argument( '--escape', '-e', default='()', help= 'Escape the provided regex special characters (default: %(default)r)' ) rate_parser.add_argument( '--allow_inst', '-I', action='store_true', help= 'Allow search results that include instrumental versions of songs') rate_parser.add_argument( 'query', nargs=argparse.REMAINDER, help= f'Query in the format --field[__operation] value; valid operations: {ops}' ) with parser.add_subparser( 'action', 'rate_offset', help='Update all track ratings in Plex with an offset' ) as rate_offset_parser: rate_offset_parser.add_argument( '--min_rating', '-min', type=int, default=2, help='Minimum rating for which a change will be made') rate_offset_parser.add_argument( '--max_rating', '-max', type=int, default=10, help='Maximum rating for which a change will be made') rate_offset_parser.add_argument('--offset', '-o', type=int, default=-1, help='Adjustment to make') with parser.add_subparser( 'action', 'playlist', help='Save or compare playlists') as playlist_parser: with playlist_parser.add_subparser( 'sub_action', 'dump', help='Save playlists') as playlist_dump: playlist_dump.add_argument( 'path', help='Location to write the playlist dump') playlist_dump.add_argument( '--playlist', '-p', help='Dump the specified playlist (default: all)') with playlist_parser.add_subparser( 'sub_action', 'compare', help='Compare playlists') as playlist_cmp: playlist_cmp.add_argument( 'path', help='Location of the playlist dump to compare') playlist_cmp.add_argument( '--playlist', '-p', help='Compare the specified playlist (default: all)') playlist_cmp.add_argument( '--strict', '-s', action='store_true', help= 'Perform a strict comparison (default: by artist/album/title)') with playlist_parser.add_subparser( 'sub_action', 'list', help='List playlists in a dump') as playlist_list: playlist_list.add_argument( 'path', help='Location of the playlist dump to read') parser.add_common_sp_arg( '--server_path_root', '-r', metavar='PATH', help= 'The root of the path to use from this computer to generate paths to files from the path used by Plex. When you click on the "..." for a song in Plex and click "Get Info", there will be a path in the "Files" box - for example, "/media/Music/a_song.mp3". If you were to access that file from this computer, and the path to that same file is "//my_nas/media/Music/a_song.mp3", then the server_path_root would be "//my_nas/" (only needed when not already cached)' ) parser.add_common_sp_arg( '--server_url', '-u', metavar='URL', help= 'The proto://host:port to use to connect to your local Plex server - for example: "https://10.0.0.100:12000" (only needed when not already cached)' ) parser.add_common_sp_arg( '--username', '-n', help='Plex username (only needed when a token is not already cached)') parser.add_common_sp_arg( '--config_path', '-c', metavar='PATH', default='~/.config/plexapi/config.ini', help= 'Config file in which your token and server_path_root / server_url are stored (default: %(default)s)' ) parser.add_common_sp_arg( '--music_library', '-m', default=None, help='Name of the Music library to use (default: Music)') parser.include_common_args('verbosity', 'dry_run') return parser
def parser(): # fmt: off parser = ArgParser(description='Music Manager') # region File Actions with parser.add_subparser('action', 'show', help='Show song/tag information') as show_parser: for name, help_text in SHOW_ARGS.items(): with show_parser.add_subparser('sub_action', name, help=help_text) as _parser: _parser.add_argument( 'path', nargs='*', default=['.'], help= 'Paths for music files or directories containing music files' ) if name in ('info', 'unique', 'table'): _parser.add_argument('--tags', '-t', nargs='+', help='The tags to display', required=(name == 'unique')) if name == 'info': _parser.add_argument('--no_trim', '-T', action='store_true', help='Do not trim tag IDs') if name == 'processed': _parser.add_argument( '--expand', '-x', action='count', default=0, help= 'Expand entities with a lot of nested info (may be specified multiple times to increase expansion level)' ) _parser.add_argument( '--only_errors', '-E', action='store_true', help='Only print entries with processing errors') with parser.add_subparser( 'action', 'path2tag', help='Update tags based on the path to each file') as p2t_parser: p2t_parser.add_argument( 'path', nargs='+', help= 'One or more paths of music files or directories containing music files' ) p2t_parser.add_argument('--title', '-t', action='store_true', help='Update title based on filename') p2t_parser.add_argument('--yes', '-y', action='store_true', help='Skip confirmation prompts') with parser.add_subparser( 'action', 'update', help= 'Set the value of the given tag on all music files in the given path' ) as set_parser: set_parser.add_argument( 'path', nargs='+', help= 'One or more paths of music files or directories containing music files' ) set_parser.add_argument( '--no_album_move', '-M', action='store_true', help='Do not rename the album directory (only applies to --load/-L)' ) set_parser.add_argument( '--replace_genre', '-G', action='store_true', help='Replace genre instead of combining genres') set_from_file = set_parser.add_argument_group( 'Load File Options', 'Options for loading updates from a file - may notbe combined with arguments from other option groups' ) set_from_file.add_argument( '--load', '-L', metavar='PATH', help= 'Load updates from a json file (may not be combined with other options)' ) set_from_file.add_argument( '--destination', '-d', metavar='PATH', help= f'Destination base directory for sorted files (default: {DEFAULT_DEST_DIR})' ) set_from_args = set_parser.add_argument_group('Tag Update Options') set_from_args.add_argument('--tag', '-t', nargs='+', help='Tag ID(s) to modify (required)') set_from_args.add_argument( '--value', '-V', help='Value to replace existing values with (required)') set_from_args.add_argument( '--replace', '-r', nargs='+', help= 'If specified, only replace tag values that match the given patterns(s)' ) set_from_args.add_argument( '--partial', '-p', action='store_true', help= 'Update only parts of tags that match a pattern specified via --replace/-r' ) set_parser.add_mutually_exclusive_arg_sets(set_from_file, set_from_args) with parser.add_subparser( 'action', 'clean', help='Clean undesirable tags from the specified files' ) as clean_parser: clean_parser.add_argument( 'path', nargs='+', help= 'One or more paths of music files or directories containing music files' ) bpm_group = clean_parser.add_mutually_exclusive_group() bpm_group.add_argument( '--bpm', '-b', action='store_true', default=None, help= 'Add a BPM tag if it is not already present (default: True if aubio is installed)' ) bpm_group.add_argument( '--no_bpm', '-B', dest='bpm', action='store_false', help='Do not add a BPM tag if it is not already present') with parser.add_subparser( 'action', 'remove', help='Remove the specified tags from the specified files' ) as rm_parser: rm_parser.add_argument( 'path', nargs='+', help= 'One or more paths of music files or directories containing music files' ) rm_group = rm_parser.add_mutually_exclusive_group() rm_group.add_argument('--tag', '-t', nargs='+', help='Tag ID(s) to remove') rm_group.add_argument('--all', '-A', action='store_true', help='Remove ALL tags') with parser.add_subparser( 'action', 'bpm', help='Add BPM info to the specified files') as bpm_parser: bpm_parser.add_argument( 'path', nargs='+', help= 'One or more paths of music files or directories containing music files' ) bpm_parser.include_common_args(parallel=4) # bpm_parser.add_argument('--parallel', '-P', type=int, default=1, help='Maximum number of workers to use in parallel (default: %(default)s)')) with parser.add_subparser( 'action', 'dump', help='Dump tag info about the specified files to json' ) as dump_parser: dump_parser.add_argument( 'path', help='A path for a music file or a directory containing music files' ) dump_parser.add_argument('output', help='The destination file path') dump_parser.add_argument( '--title_case', '-T', action='store_true', help= 'Fix track and album names to use Title Case when they are all caps' ) with parser.add_subparser('action', 'cover', help='Extract or add cover art') as cover_parser: cover_parser.add_argument( 'path', help='A path for a music file or a directory containing music files' ) dump_cover_group = cover_parser.add_argument_group( 'Save Cover Options') dump_cover_group.add_argument( '--save', '-s', metavar='PATH', help='Path to save the cover images from the specified file(s)') load_cover_group = cover_parser.add_argument_group( 'Load Cover Options') load_cover_group.add_argument('--load', '-L', metavar='PATH', help='Path to an image file') load_cover_group.add_argument( '--max_width', '-w', type=int, default=1200, help='Resize the provided image if it is larger than this value') del_cover_group = cover_parser.add_argument_group('Save Cover Options') del_cover_group.add_argument('--remove', '-R', action='store_true', help='Remove all cover images') cover_parser.add_mutually_exclusive_arg_sets(dump_cover_group, load_cover_group, del_cover_group) # endregion with parser.add_subparser( 'action', 'wiki', help='Wiki matching / informational functions') as wiki_parser: with wiki_parser.add_subparser( 'sub_action', 'pprint', help='Pretty-print the parsed page content') as pp_parser: pp_parser.add_argument('url', help='A wiki entity URL') pp_parser.add_argument('--mode', '-m', choices=('content', 'processed', 'reprs', 'headers', 'raw'), default='content', help='Pprint mode (default: %(default)s)') with wiki_parser.add_subparser( 'sub_action', 'raw', help='Print the raw page content') as ppr_parser: ppr_parser.add_argument('url', help='A wiki entity URL') with wiki_parser.add_subparser( 'sub_action', 'show', help='Show info about the entity with the given URL' ) as ws_parser: ws_parser.add_argument('identifier', help='A wiki URL or title/name') ws_parser.add_argument( '--expand', '-x', action='count', default=0, help= 'Expand entities with a lot of nested info (may be specified multiple times to increase expansion level)' ) ws_parser.add_argument( '--limit', '-L', type=int, default=0, help= 'Maximum number of discography entry parts to show for a given album (default: unlimited)' ) ws_parser.add_argument( '--types', '-t', nargs='+', help= 'Filter albums to only those that match the specified types') ws_parser.add_argument( '--type', '-T', help= 'An EntertainmentEntity subclass to require that the given page matches' ) with wiki_parser.add_subparser( 'sub_action', 'update', help='Update tracks in the given path(s) based on wiki info' ) as upd_parser: upd_parser.add_argument( 'path', nargs='+', help= 'One or more paths of music files or directories containing music files' ) upd_parser.add_argument( '--destination', '-d', metavar='PATH', default=DEFAULT_DEST_DIR, help= 'Destination base directory for sorted files (default: %(default)s)' ) upd_parser.add_argument( '--url', '-u', help= 'A wiki URL (can only specify one file/directory when providing a URL)' ) upd_parser.add_argument( '--soloist', '-S', action='store_true', help= 'For solo artists, use only their name instead of including their group, and do not sort them with their group' ) upd_parser.add_argument( '--collab_mode', '-c', choices=('title', 'artist', 'both'), default='artist', help= 'List collaborators in the artist tag, the title tag, or both (default: %(default)s)' ) upd_parser.add_argument( '--hide_edition', '-E', action='store_true', help= 'Exclude the edition from the album title, if present (default: include it)' ) upd_parser.add_argument( '--title_case', '-T', action='store_true', help= 'Fix track and album names to use Title Case when they are all caps' ) upd_parser.add_argument( '--artist', '-a', metavar='URL', help= 'Force the use of the given artist instead of an automatically discovered one' ) upd_parser.add_argument( '--update_cover', '-C', action='store_true', help= 'Update the cover art for the album if it does not match an image in the matched wiki page' ) upd_parser.add_argument('--no_album_move', '-M', action='store_true', help='Do not rename the album directory') upd_parser.add_argument( '--artist_only', '-I', action='store_true', help= 'Only match the artist / only use the artist URL if provided') upd_parser.add_argument( '--replace_genre', '-G', action='store_true', help='Replace genre instead of combining genres') upd_sites = upd_parser.add_argument_group( 'Site Options').add_mutually_exclusive_group() upd_sites.add_argument('--sites', '-s', nargs='+', default=None, help='The wiki sites to search') upd_sites.add_argument('--all', '-A', action='store_const', const=ALL_SITES, dest='sites', help='Search all sites') upd_sites.add_argument('--ost', '-O', action='store_const', const=['wiki.d-addicts.com'], dest='sites', help='Search only wiki.d-addicts.com') bpm_group = upd_parser.add_argument_group( 'BPM Options').add_mutually_exclusive_group() bpm_group.add_argument( '--bpm', '-b', action='store_true', default=None, help= 'Add a BPM tag if it is not already present (default: True if aubio is installed)' ) bpm_group.add_argument( '--no_bpm', '-B', dest='bpm', action='store_false', help='Do not add a BPM tag if it is not already present') upd_data = upd_parser.add_argument_group( 'Track Data Options').add_mutually_exclusive_group() upd_data.add_argument( '--dump', '-P', metavar='PATH', help= 'Dump track updates to a json file instead of updating the tracks' ) upd_data.add_argument( '--load', '-L', metavar='PATH', help= 'Load track updates from a json file instead of from a wiki') with wiki_parser.add_subparser( 'sub_action', 'match', help='Match tracks in the given path(s) with wiki info' ) as match_parser: match_parser.add_argument( 'path', nargs='+', help= 'One or more paths of music files or directories containing music files' ) with wiki_parser.add_subparser( 'sub_action', 'test', help= 'Test matching of tracks in a given path with a given wiki URL' ) as test_parser: test_parser.add_argument( 'path', help= 'One path of music files or directories containing music files' ) test_parser.add_argument( 'url', help= 'A wiki URL for a page to test whether it matches the given files' ) parser.include_common_args('verbosity', 'dry_run') parser.add_common_sp_arg( '--match_log', action='store_true', help='Enable debug logging for the album match processing logger') # fmt: on return parser