Add c2dbdl script
This commit is contained in:
		
							
								
								
									
										128
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,128 @@ | ||||
| # C3DB Download Tool | ||||
|  | ||||
| The C3DB Download Tool allows for easy scraping to a local JSON database and downloading of files from the C3 | ||||
| (Customs Creators Collective) database, a collection of custom songs for Guitar Hero, Rock Band, and similar clone | ||||
| games. | ||||
|  | ||||
| This tool exists because the C3DB is very hard to mass download from: each song must be found in the extensive | ||||
| list, selected manually, and a second link clicked through, before a random file name is obtained. This tool | ||||
| simplifies the process by first collecting information about all available songs of a particular type, and then is | ||||
| able to download songs based on customizable filters (e.g. by genre, artist, author, etc.) and output them in a | ||||
| standardized format. | ||||
|  | ||||
| To use the tool, first use the "database" command to build or modify your local JSON database, then use the | ||||
| "download" command to download songs. | ||||
|  | ||||
| To avoid overloading or abusing the C3DB website, this tool operates exclusively in sequential mode by design; at | ||||
| most one page is scraped (for "database build") or song downloaded (for "download") at once. Additionally, the tool | ||||
| design ensures that the JSON database of songs is stored locally, so it only needs to be built once and then is | ||||
| reused to perform actual downloads without putting further load on the website. | ||||
|  | ||||
| ## Installation | ||||
|  | ||||
| 1. Install the Python3 requirements from `requirements.txt`. | ||||
|  | ||||
| 1. Copy the script to a virtualenv, somewhere in your $PATH or execute directly from this folder (see Usage below). | ||||
|  | ||||
| ## Usage | ||||
|  | ||||
| Before running a command, use the build-in help via the `-h`/`--help` option to view the available option(s) of | ||||
| the command. | ||||
|  | ||||
| The general process of using `c3dbdl` is as follows: | ||||
|  | ||||
| 1. Select a download location, and either specify it with the `-d`/`--download-directory` option or via the | ||||
| environment variable `C3DBDL_DOWNLOAD_DIRECTORY`. | ||||
|  | ||||
| 1. Select a base URL. Use this to determine what game(s) you want to want to limit to, or use the default to | ||||
| fetch all avilable songs for all games, and either specify it with the `-u`/`--base-url` option or via the | ||||
| environment variable `C3DBDL_BASE_URL`. | ||||
|  | ||||
| 1. Initialize your C3DB JSON database with `c3dbdl [options] database build`. This will take a fair amount | ||||
| of time to complete as all pages of the chosen base URL are scanned. Note that if you cancel this process, no | ||||
| data will be saved, so let it complete! | ||||
|  | ||||
| 1. Download any song(s) you want with `c3dbdl [options] download [options]`. | ||||
|  | ||||
| ## Filtering | ||||
|  | ||||
| Filtering out the songs in the database is a key part of this tool. You might want to be able to grab only select | ||||
| genres, artists, authors, etc. to make your custom song packs. | ||||
|  | ||||
| `c3dbdl` is able to filter by several key categories: | ||||
|  | ||||
| * `genre`: The genre of the song. | ||||
| * `artist`: The artist of the song. | ||||
| * `album`: The album of the song. | ||||
| * `title`: The title of the song. | ||||
| * `year`: The year of the album/song. | ||||
| * `author`: The author of the file on C3DB. | ||||
|  | ||||
| Note that we *cannot* filter - mostly for parsing difficulty reasons - by intrument type or difficulty, by song | ||||
| length, or by any other information not mentioned above. | ||||
|  | ||||
| Filtering is always done during the download stage; the JSON database will always contain all possible entries. | ||||
|  | ||||
| To use filters, append one or more `--filter` options to your `c3dbdl download` command. A filter option begins | ||||
| with the literal `--filter`, followed by the category (e.g. `genre` or `artist`), then finally the text to filter | ||||
| on, for instance `Rock` or `Santana` or `2012`. The text must be quoted if it contains whitespace. | ||||
|  | ||||
| If more that one filter is specified, they are treated as a logical AND, i.e. all the listed filters must apply to | ||||
| a given song for it to be downloaded in that run. | ||||
|  | ||||
| Filters allow powerfully specific download selections to be run. For example, let's look for all songs by Rush | ||||
| from the album Vapor Trails (the remixed version) authored by ejthedj: | ||||
|  | ||||
| ``` | ||||
| c3dbdl download --filter artist Rush --filter album "Vapor Trails [Remixed]" --author ejthedj | ||||
| ``` | ||||
|  | ||||
| This shouldfind , as of 2023-04-02, exactly one song, "Sweet Miracle": | ||||
|  | ||||
| ``` | ||||
| Found 28942 songs from JSON database file 'Downloads/c3db.json' | ||||
| Downloading 1 song files... | ||||
| Downloading song "Rush - Sweet Miracle" by ejthedj... | ||||
| Downloading from https://dl.c3universe.com/s/ejthedj/sweetMiracle... | ||||
| ``` | ||||
|  | ||||
| Feel free to experiment. | ||||
|  | ||||
| ## Output Format | ||||
|  | ||||
| When downloading files, it may be advantageous to customize the output directory and filename structure to better | ||||
| match what you plan to do with the files. For instance, for pure organiation you might want nicely laid out | ||||
| files with clear directory structures and names, while for Onyx packaging you might want everything in a flat | ||||
| directory. | ||||
|  | ||||
| `c3dbdl` provides complete flexibility in the output file format. When downloading, use the `--file-structure` | ||||
| option to set the file structure. This value is an interpolated string containing one or more field variables, | ||||
| which are mapped at download file. The available fields are: | ||||
|  | ||||
| * `genre`: The genre of the song. | ||||
| * `artist`: The artist of the song. | ||||
| * `album`: The album of the song. | ||||
| * `title`: The title of the song. | ||||
| * `year`: The year of the album/song. | ||||
| * `author`: The author of the file on C3DB. | ||||
| * `orig_file`: The original filename that would be downloaded by e.g. a browser. | ||||
|  | ||||
| The default structure leverages all of these options to create an archive-ready structure as follows: | ||||
|  | ||||
| ``` | ||||
| {genre}/{artist}/{album}/{title} [{year}] ({author}).{orig_file} | ||||
| ``` | ||||
|  | ||||
| As an example: | ||||
|  | ||||
| ``` | ||||
| Prog/Rush/Vapor Trails [Remixed]/Sweet Miracle [2002] (ejthedj).sweetMiracle | ||||
| ``` | ||||
|  | ||||
| Note that any parent director(ies) will be automatically created down the whole tree until the final filename. | ||||
|  | ||||
| ## Help | ||||
|  | ||||
| This is a quick and dirty tool I wrote to quickly grab collections of songs. I provide no guarantee of success | ||||
| when using this tool. If you have issues, please open an issue on this repository and provide *full details* | ||||
| of your problem. | ||||
							
								
								
									
										404
									
								
								c3dbdl
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										404
									
								
								c3dbdl
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,404 @@ | ||||
| #!/usr/bin/env python3 | ||||
|  | ||||
| import click | ||||
| import requests | ||||
| import re | ||||
| import json | ||||
| import os | ||||
| from time import sleep | ||||
| from difflib import unified_diff | ||||
| from colorama import Fore | ||||
| from bs4 import BeautifulSoup | ||||
| from urllib.error import HTTPError | ||||
|  | ||||
| CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'], max_content_width=120) | ||||
|  | ||||
|  | ||||
| def buildDatabase(pages=None): | ||||
|     found_songs = [] | ||||
|  | ||||
|     if pages is None: | ||||
|         r = requests.get(f"{config['base_songs_url']}") | ||||
|         if r.status_code != 200: | ||||
|             return | ||||
|  | ||||
|         root_page_html = BeautifulSoup(r.text, 'html.parser') | ||||
|         pages = int(root_page_html.body.find('a', attrs={'class':'paginationLastPage'}).get('href').replace('?page=', '')) | ||||
|  | ||||
|     click.echo(f"Collecting data from {pages} pages") | ||||
|  | ||||
|     # Get a list of song URIs | ||||
|     for i in range(1, pages + 1): | ||||
|         attempts = 1 | ||||
|         p = None | ||||
|         while attempts <= 5: | ||||
|             try: | ||||
|                 click.echo(f"Parsing page {i} (attempt #{attempts})...") | ||||
|                 p = requests.get(f"{config['base_songs_url']}?page={i}") | ||||
|                 break | ||||
|             except Exception: | ||||
|                 sleep(attempts) | ||||
|                 attempts += 1 | ||||
|         if p is None or p.status_code != 200: | ||||
|             break | ||||
|  | ||||
|         parsed_html = BeautifulSoup(p.text, 'html.parser') | ||||
|  | ||||
|         table_html = parsed_html.body.find('div', attrs={'class':'portlet-body'}).find('tbody') | ||||
|  | ||||
|         for entry in table_html.find_all('tr', attrs={'class':'odd'}): | ||||
|             if len(entry) < 1: | ||||
|                 break | ||||
|  | ||||
|             song_entry = dict() | ||||
|  | ||||
|             for idx, td in enumerate(entry.find_all('td')): | ||||
|                 if idx == 1: | ||||
|                     # Download link | ||||
|                     song_entry["dl_link"] = td.find('a', attrs={'target':'_blank'}).get('href') | ||||
|                 elif idx == 2: | ||||
|                     # Artist | ||||
|                     song_entry["artist"] = td.find('a').get_text().strip().replace('/', '+') | ||||
|                 elif idx == 3: | ||||
|                     # Song | ||||
|                     song_entry["title"] = td.find('div', attrs={'class':'c3ttitlemargin'}).get_text().strip().replace('/', '+') | ||||
|                     song_entry["album"] = td.find('div', attrs={'class':'c3tartist'}).get_text().strip().replace('/', '+') | ||||
|                 elif idx == 4: | ||||
|                     # Genre | ||||
|                     song_entry["genre"] = td.find('a').get_text().strip() | ||||
|                 elif idx == 5: | ||||
|                     # Year | ||||
|                     song_entry["year"] = td.find('a').get_text().strip() | ||||
|                 elif idx == 6: | ||||
|                     # Length | ||||
|                     song_entry["length"] = td.find('a').get_text().strip() | ||||
|                 elif idx == 8: | ||||
|                     # Author (of chart) | ||||
|                     song_entry["author"] = td.find('a').get_text().strip().replace('/', '+') | ||||
|  | ||||
|             if song_entry and song_entry['title']: | ||||
|                 click.echo(f"Found song entry for {song_entry['artist']} - {song_entry['title']} by {song_entry['author']}") | ||||
|                 found_songs.append(song_entry) | ||||
|  | ||||
|     return found_songs | ||||
|  | ||||
|  | ||||
| def downloadSong(destination, filename, entry): | ||||
|     click.echo(f"""Downloading song "{entry['artist']} - {entry['title']}" by {entry['author']}...""") | ||||
|  | ||||
|     try: | ||||
|         p = requests.get(entry['dl_link']) | ||||
|         if p.status_code != 200: | ||||
|             raise HTTPError(entry['dl_link'], p.status_code, "", None, None) | ||||
|  | ||||
|         parsed_html = BeautifulSoup(p.text, 'html.parser') | ||||
|         download_url = parsed_html.body.find('div', attrs={'class':'lock-head'}).find('a').get('href') | ||||
|     except Exception as e: | ||||
|         click.echo(f"Failed parsing or retrieving HTML link: {e}") | ||||
|         return None | ||||
|  | ||||
|     download_filename = filename.format( | ||||
|         genre=entry['genre'], | ||||
|         artist=entry['artist'], | ||||
|         album=entry['album'], | ||||
|         title=entry['title'], | ||||
|         year=entry['year'], | ||||
|         author=entry['author'], | ||||
|         orig_name=download_url.split('/')[-1], | ||||
|     ) | ||||
|     download_filename = f"{destination}/{download_filename}" | ||||
|     download_path = '/'.join(f"{download_filename}".split('/')[0:-1]) | ||||
|  | ||||
|     if not os.path.exists(download_path): | ||||
|         os.makedirs(download_path) | ||||
|  | ||||
|     if os.path.exists(download_filename): | ||||
|         click.echo(f"File exists at {download_filename}") | ||||
|         return None | ||||
|  | ||||
|     click.echo(f"""Downloading from {download_url}...""") | ||||
|     attempts = 1 | ||||
|     p = None | ||||
|     try: | ||||
|         with requests.get(download_url, stream=True) as r: | ||||
|             while attempts <= 5: | ||||
|                 try: | ||||
|                     r.raise_for_status() | ||||
|                     break | ||||
|                 except Exception: | ||||
|                     click.echo(f"Download attempt failed: HTTP {r.status_code}; retrying {attempts}/5") | ||||
|                     sleep(attempts) | ||||
|                     attempts += 1 | ||||
|             if r is None or r.status_code != 200: | ||||
|                 if r: | ||||
|                     code = r.status_code | ||||
|                 else: | ||||
|                     code = "-1" | ||||
|                 raise HTTPError(download_url, code, "", None, None) | ||||
|             with open(download_filename, 'wb') as f: | ||||
|                 for chunk in r.iter_content(chunk_size=8192): | ||||
|                     f.write(chunk) | ||||
|             click.echo(f"Successfully downloaded to {download_filename}") | ||||
|     except Exception as e: | ||||
|         click.echo(f"Download attempt failed: {e}") | ||||
|         return None | ||||
|  | ||||
|  | ||||
|  | ||||
| @click.command(name='build', short_help='Build the local database.') | ||||
| @click.option( | ||||
|     "-o", "--overwrite", '_overwrite', is_flag=True, default=False, envvar='C3DLDB_BUILD_OVERWRITE', | ||||
|     help="Overwrite existing database file." | ||||
| ) | ||||
| @click.option( | ||||
|     "-p", "--pages", "_pages", type=int, default=None, envvar='C3DBDL_BUILD_PAGES', | ||||
|     help="Number of pages to scan (default is all)." | ||||
| ) | ||||
| def build_database(_overwrite, _pages): | ||||
|     """ | ||||
|     Initialize the local JSON database of C3DB songs from the website. | ||||
|  | ||||
|     \b | ||||
|     The following environment variables can be used for scripting purposes: | ||||
|       * C3DLDB_BUILD_OVERWRITE: equivalent to "--overwrite" | ||||
|       * C3DBDL_BUILD_PAGES: equivalent to "--pages" | ||||
|     """ | ||||
|  | ||||
|     if os.path.exists(config['database_filename']) and not _overwrite: | ||||
|         click.echo(f"Database already exists at '{config['database_filename']}'; use '--overwrite' to rebuild.") | ||||
|         exit(1) | ||||
|  | ||||
|     click.echo("Building JSON database; this will take a long time...") | ||||
|     songs_database = buildDatabase(_pages) | ||||
|     click.echo('') | ||||
|     click.echo(f"Found {len(songs_database)} songs, dumping to database file '{config['database_filename']}'") | ||||
|     if not os.path.exists(config['download_directory']): | ||||
|         click.echo(f"Creating download directory '{config['download_directory']}'") | ||||
|         os.makedirs(config['download_directory']) | ||||
|     with open(config['database_filename'], "w") as fh: | ||||
|         json.dump(songs_database, fh, indent=2) | ||||
|         fh.write('\n') | ||||
|  | ||||
|  | ||||
| @click.command(name='edit', short_help='Edit the local database in EDITOR.') | ||||
| def edit_database(): | ||||
|     """ | ||||
|     Edit the local JSON database of C3DB songs with your $EDITOR. | ||||
|     """ | ||||
|  | ||||
|     if not os.path.exists(config['database_filename']): | ||||
|         click.echo(f"WARNING: Database filename '{config['database_filename']}' does not exist!") | ||||
|         click.echo("Ensure you build a database first with the 'database build' command.") | ||||
|         exit(1) | ||||
|  | ||||
|     with open(config['database_filename'], "r") as fh: | ||||
|         songs_database = fh.read() | ||||
|  | ||||
|     new_songs_database = click.edit(text=songs_database, require_save=True, extension='.json') | ||||
|     while True: | ||||
|         if new_songs_database is None: | ||||
|             click.echo("Aborting with no modifications") | ||||
|             exit(0) | ||||
|         | ||||
|         click.echo('') | ||||
|         click.echo("Pending modifications:") | ||||
|         click.echo('') | ||||
|         diff = list(unified_diff( | ||||
|                                  songs_database.split('\n'), | ||||
|                                  new_songs_database.split('\n'), | ||||
|                                  fromfile='current', | ||||
|                                  tofile='modified', | ||||
|                                  fromfiledate='', | ||||
|                                  tofiledate='', | ||||
|                                  n=3, | ||||
|                                  lineterm='')) | ||||
|         for line in diff: | ||||
|             if re.match(r'^\+', line) is not None: | ||||
|                 click.echo(Fore.GREEN + line + Fore.RESET) | ||||
|             elif re.match(r'^\-', line) is not None: | ||||
|                 click.echo(Fore.RED + line + Fore.RESET) | ||||
|             elif re.match(r'^\^', line) is not None: | ||||
|                 click.echo(Fore.BLUE + line + Fore.RESET) | ||||
|             else: | ||||
|                 click.echo(line) | ||||
|         click.echo('') | ||||
|  | ||||
|         try: | ||||
|             json.loads(new_songs_database) | ||||
|             break | ||||
|         except Exception: | ||||
|             click.echo('ERROR: Invalid JSON syntax.') | ||||
|             click.confirm('Continue editing?', abort=True) | ||||
|             new_songs_database = click.edit(text=new_songs_database, require_save=True, extension='.json') | ||||
|  | ||||
|     click.confirm('Write modifications to songs database?', abort=True) | ||||
|  | ||||
|     with open(config['database_filename'], "w") as fh: | ||||
|         fh.write(new_songs_database) | ||||
|  | ||||
|  | ||||
| @click.group(name="database", short_help='Manage the local database.') | ||||
| def database(): | ||||
|     """ | ||||
|     Manage the local JSON database of C3DB songs. | ||||
|     """ | ||||
|  | ||||
|     pass | ||||
|  | ||||
|  | ||||
| @click.command(name="download", short_help='Download files from C3DB.') | ||||
| @click.option( | ||||
|     '-s', '--file-structure', '_file_structure', envvar='C3DBDL_DL_FILE_STRUCTURE', | ||||
|     default="{genre}/{artist}/{album}/{title} [{year}] ({author}).{orig_name}", | ||||
|     help='Specify the output file/directory stucture.' | ||||
| ) | ||||
| @click.option( | ||||
|     '-f', '--filter', '_filters', envvar='C3DBDL_DL_FILTERS', | ||||
|     default=[], multiple=True, | ||||
|     nargs=2, | ||||
|     help='Add a filter option.' | ||||
| ) | ||||
| @click.option( | ||||
|     '-l', '--limit', '_limit', envvar='C3DBDL_DL_LIMIT', | ||||
|     default=None, type=int, | ||||
|     help='Limit to this many songs (first N matches).' | ||||
| ) | ||||
| def download(_filters, _limit, _file_structure): | ||||
|     """ | ||||
|     Download song(s) from the C3DB webpage. | ||||
|  | ||||
|     \b | ||||
|     The output file structure can be specified as a path format with any of the following | ||||
|     fields included, surrounded by curly braces: | ||||
|       * genre: The genre of the song. | ||||
|       * artist: The artist of the song. | ||||
|       * album: The album of the song. | ||||
|       * title: The title of the song. | ||||
|       * year: The year of the album/song. | ||||
|       * author: The author of the file on C3DB. | ||||
|       * orig_name: The original filename from the website. | ||||
|  | ||||
|     \b | ||||
|     The default output file structure is: | ||||
|         "{genre}/{artist}/{album}/{title} [{year}] ({author}).{orig_file}" | ||||
|  | ||||
|     \b | ||||
|     Filters allow granular selection of the song(s) to download. Multiple filters can be | ||||
|     specified, and a song is selected only if ALL filters match (logical AND). Each filter | ||||
|     is in the form: | ||||
|       --filter [database_key] [value] | ||||
|  | ||||
|     \b | ||||
|     The valid "database_key" values are identical to the output file fields above. | ||||
|  | ||||
|     \b | ||||
|     For example, to download all songs in the genre "Rock": | ||||
|       --filter genre Rock | ||||
|  | ||||
|     \b | ||||
|     Or to download all songs by the artist "Rush" and the author "MyName": | ||||
|       --filter artist Rush --filter author MyName | ||||
|  | ||||
|     \b | ||||
|     The following environment variables can be used for scripting purposes: | ||||
|       * C3DBDL_DL_FILE_STRUCTURE: equivalent to "--file-structure" | ||||
|       * C3DBDL_DL_FILTERS: equivalent to "--filter"; limited to one instance | ||||
|       * C3DBDL_DL_LIMIT: equivalent to "--limit" | ||||
|     """ | ||||
|  | ||||
|     with open(config['database_filename'], "r") as fh: | ||||
|         all_songs = json.load(fh) | ||||
|     click.echo(f"Found {len(all_songs)} songs from JSON database file '{config['database_filename']}'") | ||||
|  | ||||
|     pending_songs = list() | ||||
|  | ||||
|     for song in all_songs: | ||||
|         if len(_filters) < 1: | ||||
|             add_to_pending = True | ||||
|         else: | ||||
|             add_to_pending = all(song[_filter[0]] == _filter[1] for _filter in _filters) | ||||
|  | ||||
|         if add_to_pending: | ||||
|             pending_songs.append(song) | ||||
|  | ||||
|     if _limit is not None: | ||||
|         pending_songs = pending_songs[0:_limit] | ||||
|  | ||||
|     click.echo(f"Downloading {len(pending_songs)} song files...") | ||||
|  | ||||
|     for song in pending_songs: | ||||
|         downloadSong(config['download_directory'], _file_structure, song) | ||||
|  | ||||
|  | ||||
| @click.group(context_settings=CONTEXT_SETTINGS) | ||||
| @click.option( | ||||
|     '-u', '--base-url', '_base_url', envvar='C3DBDL_BASE_URL', | ||||
|     default='https://db.c3universe.com/songs/all', show_default=True, | ||||
|     help='Base URL of the online C3DB songs page' | ||||
| ) | ||||
| @click.option( | ||||
|     '-d', '--download-directory', '_download_directory', envvar='C3DBDL_DOWNLOAD_DIRECTORY', | ||||
|     default='~/Downloads', show_default=True, | ||||
|     help='Download directory for JSON database and songs' | ||||
| ) | ||||
| @click.option( | ||||
|     '-j', '--json-database', '_json_database', envvar='C3DBDL_JSON_DATABASE', | ||||
|     default='c3db.json', show_default=True, | ||||
|     help='JSON database filename within download directory' | ||||
| ) | ||||
| def cli(_base_url, _download_directory, _json_database): | ||||
|     """ | ||||
|     C3DB Download Tool | ||||
|  | ||||
|     The C3DB Download Tool allows for easy scraping to a local JSON database and downloading | ||||
|     of files from the C3 (Customs Creators Collective) database, a collection of custom songs | ||||
|     for Guitar Hero, Rock Band, and similar clone games. | ||||
|  | ||||
|     This tool exists because the C3DB is very hard to mass download from: each song must | ||||
|     be found in the extensive list, selected manually, and a second link clicked through, | ||||
|     before a random file name is obtained. This tool simplifies the process by first collecting | ||||
|     information about all available songs of a particular type, and then is able to download | ||||
|     songs based on customizable filters (e.g. by genre, artist, author, etc.) and output them | ||||
|     in a standardized format. | ||||
|      | ||||
|     To use the tool, first use the "database" command to build or modify your local JSON | ||||
|     database, then use the "download" command to download songs. | ||||
|  | ||||
|     To avoid overloading or abusing the C3DB website, this tool operates exclusively in | ||||
|     sequential mode by design; at most one page is scraped (for "database build") or song | ||||
|     downloaded (for "download") at once. Additionally, the tool design ensures that the JSON | ||||
|     database of songs is stored locally, so it only needs to be built once and then is reused | ||||
|     to perform actual downloads without putting further load on the website. | ||||
|  | ||||
|     \b | ||||
|     The following environment variables can be used for scripting purposes: | ||||
|       * C3DBDL_BASE_URL: equivalent to "--base-url" | ||||
|       * C3DBDL_DOWNLOAD_DIRECTORY: equivalent to "--download_directory" | ||||
|       * C3DBDL_JSON_DATABASE: equivalent to "--json-database" | ||||
|  | ||||
|     """ | ||||
|      | ||||
|     global config | ||||
|  | ||||
|     # Expand any ~ in the download directory pathname | ||||
|     _download_directory = os.path.expanduser(_download_directory) | ||||
|  | ||||
|     # Populate the configuration store | ||||
|     config['base_songs_url'] = _base_url | ||||
|     config['download_directory'] = _download_directory | ||||
|     config['database_filename'] = f"{_download_directory}/{_json_database}" | ||||
|  | ||||
|  | ||||
| config = dict() | ||||
|  | ||||
| database.add_command(build_database) | ||||
| database.add_command(edit_database) | ||||
|  | ||||
| cli.add_command(database) | ||||
| cli.add_command(download) | ||||
|  | ||||
| def main(): | ||||
|     return cli(obj={}) | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     main() | ||||
							
								
								
									
										4
									
								
								requirements.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								requirements.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| Click | ||||
| requests | ||||
| colorama | ||||
| beautifulsoup4 | ||||
		Reference in New Issue
	
	Block a user