diff --git a/README.md b/README.md index 12884fe..37c6fe0 100644 --- a/README.md +++ b/README.md @@ -144,3 +144,25 @@ not being accessed/modified during this process. It is easy to access `yggdrasil ```sh python scripts/scaleway/baldur.py delete ``` + +## Music organisation + +The `playbooks/music.yml` playbook sets up tools and configuration for organising music. The process +is manual though. The steps for adding a new CD. + +All steps below are to be executed as the `music` user. + +### Ripping a CD + +1. Use a CD ripper and rip the CD to `/var/lib/yggdrasil/home/music/rip` using flac encoding. +2. Samba has been set up to give Windows access to the above directory. Therefore, CD rippers + available only for Windows can also be used, e.g. dBpoweramp. + +### Import new music + +1. Run `beet import /var/lib/yggdrasil/home/music/rip`. This will move the music files to + `/var/lib/yggdrasil/data/music/flac`. +2. Run `beet convert `, where `` is used to narrow down to new music only. This will + convert the flac files into mp3 files for sharing via Nextcloud. +3. Run `nextcloud-upload /var/tmp/music/mp3/` for every artist to upload to Nextcloud. +4. Remove the `/var/tmp/music/mp3/` directory. diff --git a/inventory/host_vars/yggdrasil/vars.yml b/inventory/host_vars/yggdrasil/vars.yml index 814e986..cd3e41f 100644 --- a/inventory/host_vars/yggdrasil/vars.yml +++ b/inventory/host_vars/yggdrasil/vars.yml @@ -98,11 +98,21 @@ music_user_data_directory: "{{ system_var_data_directory }}/{{ music_user_name } music_user_home_dataset: "rpool{{ music_user_home_directory }}" music_user_data_dataset: "rpool{{ music_user_data_directory }}" +# -------------------------------------------------------------------------------------------------- +# music:backups +# -------------------------------------------------------------------------------------------------- music_user_backups_snapshots_data_dataset: "\ {{ system_backups_snapshots_data_dataset }}/{{ music_user_name }}" music_user_backups_snapshots_recursive: true music_user_backups_snapshots_skip_parent: true +# -------------------------------------------------------------------------------------------------- +# music:org +# -------------------------------------------------------------------------------------------------- +music_user_nextcloud_url: "https://cloud.wojciechkozlowski.eu/public.php/webdav" +music_user_nextcloud_user: "{{ vault_music_user_nextcloud_user }}" +music_user_nextcloud_pswd: "{{ vault_music_user_nextcloud_pswd }}" + # -------------------------------------------------------------------------------------------------- # services # -------------------------------------------------------------------------------------------------- diff --git a/playbooks/music.yml b/playbooks/music.yml index 2edfd14..16bdcaa 100644 --- a/playbooks/music.yml +++ b/playbooks/music.yml @@ -35,5 +35,5 @@ - "music:backups" - "music:backups:restic" - "music:backups:restic:user" - # - role: "music/org" - # tags: "music:org" + - role: "music/org" + tags: "music:org" diff --git a/playbooks/roles/music/org/files/nextcloud-upload b/playbooks/roles/music/org/files/nextcloud-upload new file mode 100755 index 0000000..1ffb6b4 --- /dev/null +++ b/playbooks/roles/music/org/files/nextcloud-upload @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 + +import argparse +import os +import requests +import requests.auth +import urllib +import urllib.parse +import yaml + + +class WebDavManager: + + def __init__(self, url, user, pswd): + self.__url = url + self.__auth = requests.auth.HTTPBasicAuth(user, pswd) + + @staticmethod + def __remote_path(dstdir, dst): + path = [] + if dstdir is not None: + path.append(dstdir.strip('/')) + path.append(dst.strip('/')) + return '/'.join(path) + + def __url_path(self, dstdir, dst): + return '/'.join([ + self.__url.strip('/'), + urllib.parse.quote(self.__remote_path(dstdir, dst)), + ]) + + def mkdir(self, dstdir, dir): + print(f"mkdir: {self.__remote_path(dstdir, dir)}", flush=True) + + rc = requests.request("MKCOL", self.__url_path(dstdir, dir), auth=self.__auth) + print(f"response: {rc}", flush=True) + + if ((rc.status_code // 100) != 2) and (rc.status_code != 405): + raise RuntimeError(f"unexpected response ({rc}) to MKCOL ({rc.url}):\n{rc.text}") + + def upload_file(self, srcdir, dstdir, file): + print(f"upload: {self.__remote_path(dstdir, file)}", flush=True) + + with open(os.path.join(srcdir, file), "rb") as fobj: + rc = requests.put(self.__url_path(dstdir, file), auth=self.__auth, data=fobj) + print(f"response: {rc}", flush=True) + + if (rc.status_code // 100) != 2: + raise RuntimeError(f"unexpected response ({rc}) to PUT ({rc.url}):\n{rc.text}") + + @staticmethod + def __walkraise(error): + raise error + + def upload_dir(self, target): + prefix = os.path.dirname(os.path.realpath(target)) + self.mkdir(None, os.path.relpath(target, prefix)) + + for root, dirs, files in os.walk(target, topdown=True, onerror=self.__walkraise): + for dir in dirs: + self.mkdir(os.path.relpath(root, prefix), dir) + for file in files: + self.upload_file(root, os.path.relpath(root, prefix), file) + + def upload(self, target): + if os.path.isfile(target): + self.upload_file( + os.path.dirname(os.path.realpath(args.target)), + None, + os.path.basename(args.target), + ) + else: + assert os.path.isdir(target) + self.upload_dir(target) + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="upload files to nextcloud") + + parser.add_argument("--config", type=str, default="~/.config/nextcloud-upload/config.yml", + help="path to configuration") + parser.add_argument("target", type=str, help="file or folder to upload") + + args = parser.parse_args() + + with open(args.config, encoding="utf-8") as config_file: + config = yaml.safe_load(config_file) + + for key in ["url", "user", "pswd"]: + if key not in config: + raise KeyError(f"{key} must be present in {args.config}") + + webdav = WebDavManager(config["url"], config["user"], config["pswd"]) + webdav.upload(args.target) diff --git a/playbooks/roles/music/org/meta/argument_specs.yml b/playbooks/roles/music/org/meta/argument_specs.yml new file mode 100644 index 0000000..e7f6eea --- /dev/null +++ b/playbooks/roles/music/org/meta/argument_specs.yml @@ -0,0 +1,13 @@ +--- +argument_specs: + main: + options: + music_user_name: + type: "str" + required: true + music_user_home_directory: + type: "str" + required: true + music_user_data_directory: + type: "str" + required: true diff --git a/playbooks/roles/music/org/tasks/main.yml b/playbooks/roles/music/org/tasks/main.yml new file mode 100644 index 0000000..1f4b114 --- /dev/null +++ b/playbooks/roles/music/org/tasks/main.yml @@ -0,0 +1,52 @@ +--- +- name: "install beets" + ansible.builtin.apt: + name: + - "beets" + - "ffmpeg" + +- name: "install nextcloud-upload" + ansible.builtin.copy: + src: "./nextcloud-upload" + dest: "/usr/local/bin/nextcloud-upload" + mode: 0755 + +- block: + + - name: "create beets config directory" + ansible.builtin.file: + path: "{{ music_user_home_directory }}/.config/beets" + state: "directory" + owner: "{{ music_user_name }}" + group: "{{ music_user_name }}" + mode: 0755 + + - name: "create beets convert directory" + ansible.builtin.file: + path: "/var/tmp/{{ music_user_name }}/mp3" + state: "directory" + owner: "{{ music_user_name }}" + group: "{{ music_user_name }}" + mode: 0755 + + - name: "configure beets" + ansible.builtin.template: + src: "./beets.yml" + dest: "{{ music_user_home_directory }}/.config/beets/config.yaml" + mode: 0644 + + - name: "create nextcloud-upload config directory" + ansible.builtin.file: + path: "{{ music_user_home_directory }}/.config/nextcloud-upload" + state: "directory" + owner: "{{ music_user_name }}" + group: "{{ music_user_name }}" + mode: 0755 + + - name: "configure nextcloud-upload" + ansible.builtin.template: + src: "./nextcloud-upload.yml" + dest: "{{ music_user_home_directory }}/.config/nextcloud-upload/config.yml" + mode: 0644 + + become_user: "{{ music_user_name }}" diff --git a/playbooks/roles/music/org/templates/beets.yml b/playbooks/roles/music/org/templates/beets.yml new file mode 100644 index 0000000..da06c02 --- /dev/null +++ b/playbooks/roles/music/org/templates/beets.yml @@ -0,0 +1,10 @@ +plugins: convert +library: {{ music_user_data_directory }}/flac/library.db + +directory: {{ music_user_data_directory }}/flac +import: + move: yes + write: no + +convert: + dest: /var/tmp/{{ music_user_name }}/mp3 diff --git a/playbooks/roles/music/org/templates/nextcloud-upload.yml b/playbooks/roles/music/org/templates/nextcloud-upload.yml new file mode 100644 index 0000000..4355337 --- /dev/null +++ b/playbooks/roles/music/org/templates/nextcloud-upload.yml @@ -0,0 +1,3 @@ +url: "{{ music_user_nextcloud_url }}" +user: "{{ music_user_nextcloud_user }}" +pswd: "{{ music_user_nextcloud_pswd }}"