commit ccf26a80797ec15e1ef95549279ddbc93fb9c98a Author: Khoa Nguyen Anh Date: Fri Mar 2 22:52:48 2018 +0700 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dcc9078 --- /dev/null +++ b/.gitignore @@ -0,0 +1,117 @@ + +# Created by https://www.gitignore.io/api/vim,python + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +.pytest_cache/ +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule.* + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +### Vim ### +# swap +.sw[a-p] +.*.sw[a-p] +# session +Session.vim +# temporary +.netrwhist +*~ +# auto-generated tag files +tags + + +# End of https://www.gitignore.io/api/vim,python diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..cb9d7d3 --- /dev/null +++ b/Pipfile @@ -0,0 +1,14 @@ +[[source]] + +url = "https://pypi.python.org/simple" +verify_ssl = true +name = "pypi" + + +[packages] + +tinytag = "*" + + +[dev-packages] + diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..4766690 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,38 @@ +{ + "_meta": { + "hash": { + "sha256": "ea2b7228db265fdaab23703135a88d5c005bf4225443197f890173608a692406" + }, + "host-environment-markers": { + "implementation_name": "cpython", + "implementation_version": "3.6.4", + "os_name": "posix", + "platform_machine": "x86_64", + "platform_python_implementation": "CPython", + "platform_release": "4.14.0-parrot13-amd64", + "platform_system": "Linux", + "platform_version": "#1 SMP Parrot 4.14.13-1parrot13 (2018-01-21)", + "python_full_version": "3.6.4", + "python_version": "3.6", + "sys_platform": "linux" + }, + "pipfile-spec": 6, + "requires": {}, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "tinytag": { + "hashes": [ + "sha256:8c86ea2cf812a9aff2d61b04fe954bd3feaeb4053f59809b7d1e09ea03be7cd3" + ], + "version": "==0.18.0" + } + }, + "develop": {} +} diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..a997628 --- /dev/null +++ b/__init__.py @@ -0,0 +1 @@ +from musipy import musipy diff --git a/common/__init__.py b/common/__init__.py new file mode 100644 index 0000000..0360e31 --- /dev/null +++ b/common/__init__.py @@ -0,0 +1,2 @@ +from .get_content import get_content +from .same_name_alert import same_name_alert diff --git a/common/get_content.py b/common/get_content.py new file mode 100644 index 0000000..4ecaf91 --- /dev/null +++ b/common/get_content.py @@ -0,0 +1,13 @@ +import os + + +def get_content(source): + files = [] + folders = [] + + for f in os.listdir(source): + if os.path.isfile(os.path.join(source, f)): + files.append(f) + else: + folders.append(f) + return files, folders diff --git a/common/same_name_alert.py b/common/same_name_alert.py new file mode 100644 index 0000000..6034274 --- /dev/null +++ b/common/same_name_alert.py @@ -0,0 +1,24 @@ +import os + + +def same_name_alert(oldfile, newfile): + print("Old: {}".format(oldfile)) + print("New: {}".format(newfile)) + print("File existed, overwrite?") + print("'y' for yes") + print("'m' to view more data") + print("'l' to listen to two song") + while True: + ans = input("Answer: ") + if ans == 'y': + os.rename(oldfile, newfile) + break + elif ans == 'm': + print("Old file data:") + print("New file data:") + elif ans == 'l': + print("Listen to song 1") + print("Listen to song 2") + else: + break + return diff --git a/musipy.py b/musipy.py new file mode 100644 index 0000000..57156d6 --- /dev/null +++ b/musipy.py @@ -0,0 +1,107 @@ +import os +from parser import Parser +from common import same_name_alert, get_content +from tinytag import TinyTag + + +class musipy: + def __init__(self): + # prepare data + self.data = {} + self.parser = Parser() + + # run + self.run() + + def run(self): + if self.parser.mode == 'sort': + self.collect() + self.move_files() + else: + pass + return + + # sort files bases on attribute + def sort(self, f, tag): + # get attribute from tag + # using self.attr + tag = getattr(tag, self.parser.attr) + if tag in self.data: + self.data[tag].append(f) + else: + self.data[tag] = [] + self.data[tag].append(f) + return + + # move files to new destination based on attribute + def move_files(self): + for folder, tracks in self.data.items(): + + if folder is None: + folder = "Undefined" + if not folder: + folder = "Undefined" + + new_folder = self.parser.output + '/' + folder + if not os.path.exists(new_folder): + os.makedirs(new_folder) + + print("Folder: {}".format(folder)) + moved_files = 0 + total_files = len(tracks) + for track in tracks: + moved_files += 1 + percent = int(moved_files / total_files * 100) + print("Processing ... {:3d}%".format(percent), end='\r') + + new_file = new_folder + '/' + os.path.basename(track) + if track == new_file: + # after sort, stay the same + continue + if os.path.exists(new_file): + same_name_alert(track, new_file) + else: + os.rename(track, new_file) + print("") + return + + # collect all files and store in self.data + def collect(self): + folder_queue = [self.parser.source] + home_path_len = len(folder_queue[0]) + + while (len(folder_queue) > 0): + + current_folder = folder_queue[0] + folder_queue = folder_queue[1:] + + if len(current_folder[home_path_len:]) == 0: + print("[+] Scan /") + else: + print("[+] Scan {}".format(current_folder[home_path_len:])) + + files, folders = get_content(current_folder) + + # skip folder named '.folder' + # generate full path + for folder in folders: + if folder[0] != '.': + full_path = current_folder + '/' + folder + folder_queue.append(full_path) + + # work with files + for f in files: + try: + fp = current_folder + '/' + f # full path to file + tag = TinyTag.get(fp) + except LookupError: + continue + except: + print("Cannot get tag from file --> Skip\n\t{}".format(fp)) + continue + + self.sort(fp, tag) + + +if __name__ == "__main__": + muse = musipy() diff --git a/parser/__init__.py b/parser/__init__.py new file mode 100644 index 0000000..2a3855a --- /dev/null +++ b/parser/__init__.py @@ -0,0 +1 @@ +from .parser import Parser diff --git a/parser/parser.py b/parser/parser.py new file mode 100644 index 0000000..e923770 --- /dev/null +++ b/parser/parser.py @@ -0,0 +1,53 @@ +import os +import getopt +import sys + + +class Parser(): + def __init__(self): + argv = sys.argv[1:] + self.source = None + self.output = None + self.attr = None + self.mode = None + + try: + opts, args = getopt.getopt( + argv, 'hs:o:a:m:', + ['source=', 'output=', 'attribute=', 'mode=']) + + except getopt.GetoptError: + print('') + exit(0) + + for opt, arg in opts: + if opt == '-h': + print("Help") + exit(0) + elif opt in ('-s', '--source'): + self.source = arg + elif opt in ('-o', '--output'): + self.output = arg + elif opt in ('-a', '--attribute'): + self.attr = arg + elif opt in ('-m', '--mode'): + self.mode = arg + else: + print("Unknown flag {} {}".format(opt, arg)) + + if self.source is None: + self.source = os.getcwd() + if self.output is None: + self.output = self.source + '/output' + if self.attr is None: + self.attr = 'album' + if self.mode is None: + self.mode = 'sort' + + +if __name__ == '__main__': + p = Parser() + print(p.source) + print(p.output) + print(p.attr) + print(p.mode)